1.course/models.py
class CourseExpire(BaseModel): """課程有效期模型""" # 後面可以在資料庫把course和expire_time欄位設定為聯合索引 course = models.ForeignKey("Course", related_name='course_expire', on_delete=models.CASCADE, verbose_name="課程名稱") # 有效期限,天數 expire_time = models.IntegerField(verbose_name="有效期", null=True, blank=True, help_text="有效期按天數計算") # 一個月有效等等 expire_text = models.CharField(max_length=150, verbose_name="提示文字", null=True, blank=True) # 每個有效期的價格 price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程價格", default=0) class Meta: db_table = "ly_course_expire" verbose_name = "課程有效期" verbose_name_plural = verbose_name def __str__(self): return "課程:%s,有效期:%s,價格:%s" % (self.course, self.expire_text, self.price)
在course表中的price欄位加一個提示文字
price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程原價", default=0,help_text='如果填寫的價格為0,那麼表示當前課程在購買的時候,沒有永久有效的期限。')
2.插入一些測試資料
INSERT INTO `ly_course_expire` (`id`,`orders`,`is_show`,`is_deleted`,`created_time`,`updated_time`,`expire_time`,`expire_text`,`course_id`,`price`) VALUES (1,1,1,0,'2019-08-19 02:05:22.368823','2019-08-19 02:05:22.368855',30,'一個月有效',1,398.00), (2,2,1,0,'2019-08-19 02:05:37.397205','2019-08-19 02:05:37.397233',60,'2個月有效',1,588.00), (3,3,1,0,'2019-08-19 02:05:57.029411','2019-08-19 02:05:57.029440',180,'半年內有效',1,1000.00), (4,4,1,0,'2019-08-19 02:07:29.066617','2019-08-19 02:08:29.156730',3,'3天內有效',3,0.88), (5,3,1,0,'2019-08-19 02:07:46.120827','2019-08-19 02:08:18.652452',30,'1個月有效',3,188.00), (6,3,1,0,'2019-08-19 02:07:59.876421','2019-08-19 02:07:59.876454',60,'2個月有效',3,298.00);
3.xadmin註冊
course/adminx.py
from .models import CourseExpire class CourseExpireModelAdmin(object): """商品有效期模型""" pass xadmin.site.register(CourseExpire, CourseExpireModelAdmin)
course/models.py
class Course: # 獲取課程有效期 def get_expire(self): # 課程表和課程有效期表時一對多的關係 反向查詢 expire_list = self.course_expire.all() # 查詢到當前課程所擁有的有效期種類 data = [] for expire in expire_list: data.append({ 'id':expire.id, 'expire_text':expire.expire_text, # 有效期的那個文字 比如'三個月有效' 'price':expire.price, # 有效期對應的價格 }) # 當價格為0時,沒有永久有效這一項,其他的都有 if self.price > 0: data.append({ 'id': 0, 'expire_text': '永久有效', 'price': self.price, }) return data
cart/views.py
class AddCartView(ViewSet): def cart_list(self,request): try: ...... cart_data_list.append({ ...... 'expire_list':course_obj.get_expire(), ...... }) except Exception: ...... return Response({'msg':'xxx','cart_data_list':cart_data_list})
cartitem.vue
<div class="cart_column column_3"> <el-select v-model="cart.expire_id" size="mini" placeholder="請選擇購買有效期" class="my_el_select"> <el-option :label="expire.expire_text" :value="expire.id" :key="expire.id" v-for="(expire,expire_index) in cart.expire_list"></el-option> </el-select> </div>
cart/views.py
class AddCartView: def change_expire(self,request): user_id = request.user.id course_id = request.data.get('course_id') expire_id = request.data.get('expire_id') # 檢驗課程id是否有效 try: course_obj = models.Course.objects.get(id=course_id) except: return Response({'msg':'課程不存在'},status=status.HTTP_400_BAD_REQUEST) # 檢驗有效期id是否有效 try: if expire_id > 0: expire_object = models.CourseExpire.objects.get(id=expire_id) except: return Response({'msg':'課程有效期不存在'},status=status.HTTP_400_BAD_REQUEST) # 計算xx課程xx有效期的真實價格 real_price = course_obj.real_price(expire_id) # 更改了新的有效期,要將更改之後的有效期存放到redis中 conn = get_redis_connection('cart') conn.hset('cart_%s' % user_id, course_id, expire_id) return Response({'msg':'切換成功!', 'real_price': real_price})
cart/urls.py
urlpatterns = [ ...... path('expires/', views.AddCartView.as_view({'put':'change_expire'})) ]
class Course: def real_price(self,expire_id=0): # 增加expire_id=0引數 讓真實價格根據不同的有效期顯示不同的價格 price = float(self.price) ''' 1.如果不是永久有效(expire_id>0),那就從course_expire獲取該有效期對應的價格 2.如果是永久有效(expire_id=0),那麼真實價格就等於它原來的價格 ''' if expire_id > 0: expire_obj = self.course_expire.get(id=expire_id) price = float(expire_obj.price) r_price = price a = self.activity() if a: sale = a[0].discount.sale condition_price = a[0].discount.condition # 限時免費 if not sale.strip(): r_price = 0 # 限時折扣 *0.5 elif '*' in sale.strip(): if price >= condition_price: _, d = sale.split('*') r_price = price * float(d) # 限時減免 -100 elif sale.strip().startswith('-'): if price >= condition_price: _, d = sale.split('-') r_price = price - float(d) elif '滿' in sale: if price >= condition_price: l1 = sale.split('\r\n') dis_list = [] #10 50 25 for i in l1: a, b = i[1:].split('-') #400 if price >= float(a): dis_list.append(float(b)) max_dis = max(dis_list) r_price = price - max_dis return r_price
cartitem.vue
// js watch:{ ...... // 當使用者選擇的課程有效期發生變化時(在前端下拉框選擇了別的有效期) 'cart.expire_id':function (){ let token = localStorage.token || sessionStorage.token; this.$axios.put(`${this.$settings.Host}/cart/expires/`,{ // 將課程id和課程對應的有效期id提交到後端 course_id: this.cart.course_id, expire_id:this.cart.expire_id, },{ headers:{ 'Authorization':'jwt ' + token } } ).then((res)=>{ this.$message.success(res.data.msg); // 修改有效期成功,將真實價格返回給前端 this.cart.real_price = res.data.real_price; // 修改有效期成功,觸發cart父元件重新計算總價格的事件 this.$emit('change_expire_handler',) }).catch((error)=>{ this.$message.error(error.response.data.msg) }) } },
cart.vue
<div class="cart_course_list"> <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value" @cal_t_p="cal_total_price" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem> </div>
cart/views.py
class AddCartView: def cart_list(self,request): user_id = request.user.id conn = get_redis_connection('cart') conn.delete('selected_cart_%s' % user_id) ret = conn.hgetall('cart_%s' % user_id) #dict {b'1': b'0', b'2': b'0'} cart_data_list = [] try: for cid, eid in ret.items(): course_id = cid.decode('utf-8') # 當使用者檢視購物車頁面時->預設顯示永久有效 conn.hset('cart_%s' % user_id, course_id, 0) expire_id = 0 course_obj = models.Course.objects.get(id=course_id) cart_data_list.append({ 'course_id':course_obj.id, 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , 'price':course_obj.price, 'real_price':course_obj.real_price(), 'expire_id':expire_id, 'expire_list':course_obj.get_expire(), 'selected':False, # 預設沒有勾選 }) except Exception: logger.error('獲取購物車資料失敗') return Response({'msg':'後臺資料庫出問題了,請聯絡管理員'},status=status.HTTP_507_INSUFFICIENT_STORAGE) return Response({'msg':'xxx','cart_data_list':cart_data_list})
<div class="cart_column column_4" id="delete" @click="delete_course">刪除</div>
delete_course(){ let token = localStorage.token || sessionStorage.token; this.$axios.delete(`${this.$settings.Host}/cart/add_cart/`,{ params:{ course_id: this.cart.course_id, // request.query_params.get('course_id') }, headers:{ 'Authorization':'jwt ' + token } }) .then((res)=>{ this.$message.success(res.data.msg); // 當使用者要刪除購物車中的某條課程記錄時 執行Cart父元件的刪除課程事件 this.$emit('delete_course_handler') }) .catch((error)=>{ this.$message.error(error.response.data.msg); }) }
urlpatterns = [ path('add_cart/', views.AddCartView.as_view( {'post':'add','get':'cart_list', 'patch':'change_select','put':'cancel_select', 'delete':'delete_course' })), path('expires/', views.AddCartView.as_view({'put':'change_expire'})) ]
cart/views.py
class AddCartView: def delete_course(self,request): user_id = request.user.id course_id = request.query_params.get('course_id') conn = get_redis_connection('cart') pipe = conn.pipeline() pipe.hdel('cart_%s' % user_id, course_id) pipe.srem('selected_cart_%s' % user_id, course_id) pipe.execute() return Response({'msg':'刪除成功'})
使用者刪除一條購物車資料 後端已經刪除了 但是前端頁面也要同步刪除
Cart.vue
<div class="cart_course_list"> <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value" @cal_t_p="cal_total_price" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem> </div>
delete_c(index){ this.cart_data_list.splice(index,1) this.cal_total_price() // 刪除之後 重新觸發計算總價格的方法 }
Cartitem.vue
delete_course(){ let token = localStorage.token || sessionStorage.token; this.$axios.delete(`${this.$settings.Host}/cart/add_cart/`,{ params:{ course_id: this.cart.course_id, }, headers:{ 'Authorization':'jwt ' + token } }) .then((res)=>{ // 刪除時 觸發Cart父元件的刪除事件 this.$message.success(res.data.msg); this.$emit('delete_course_handler') }) .catch((error)=>{ this.$message.error(error.response.data.msg); }) } }
1.結算初始頁面
<!-- 結算頁面初始介面 -->
2.配置路由
index.js
import Vue from 'vue' import Order from "@/components/Order" Vue.use(Router) export default new Router({ mode:'history', routes: [ ...... { path: '/order/', component: Order }, ] })
3.點選去結算按鈕 看到頁面
cart.vue
<span class="goto_pay"><router-link to="/order/">去結算</router-link></span>
cart/urls.py
urlpatterns = [ ... path('expires/', views.AddCartView.as_view({'get':'show_pay_info'})) ]
cart/views.py
def show_pay_info(self,request): user_id = request.user.id conn = get_redis_connection('cart') # 獲取使用者購物車選中的所有課程id select_list = conn.smembers('selected_cart_%s' % user_id) data = [] # 獲取使用者購物車的課程id:有效期id ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'} for cid, eid in ret.items(): expire_id = int(eid.decode('utf-8')) if cid in select_list: # 如果課程是被勾選的 course_id = int(cid.decode('utf-8')) # 獲取這個'被勾選的課程'model物件 course_obj = models.Course.objects.get(id=course_id) # 如果課程的有效期不是永久有效 if expire_id > 0: expire_obj = models.CourseExpire.objects.get(id=expire_id) data.append({ 'course_id':course_obj.id, 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , 'real_price':course_obj.real_price(expire_id), 'expire_text':expire_obj.expire_text, }) # 如果課程的有效期是永久有效 else: data.append({ 'course_id': course_obj.id, 'name': course_obj.name, 'course_img': contains.SERVER_ADDR + course_obj.course_img.url, 'real_price': course_obj.real_price(expire_id), 'expire_text': '永久有效', }) return Response({'data':data})
order.vue
// js methods: { get_order_data(){ let token = localStorage.token || sessionStorage.token; this.$axios.get(`${this.$settings.Host}/cart/expires/`,{ headers:{ 'Authorization':'jwt ' + token } }).then((res)=>{ this.course_list = res.data.data; }) },
<!-- html --> <div class="cart-item" v-for="(course,index) in course_list"> <el-row> <el-col :span="2" class="checkbox"> </el-col> <el-col :span="10" class="course-info"> <img :src="course.course_img" alt=""> <span>{{course.name}}</span> </el-col> <el-col :span="8"><span>{{course.expire_text}}</span></el-col> <el-col :span="4" class="course-price">¥{{course.real_price}}</el-col> </el-row> </div>
<el-col :span="8"> <span class="alipay" v-if="pay_type===1"><img src="../../static/img/alipay2.png" alt=""></span> <span class="alipay" @click="pay_type=1" v-else><img src="../../static/img/alipay.png" alt=""></span> <span class="alipay wechat" v-if="pay_type===2"><img src="../../static/img/wechat2.png" alt="" ></span> <span class="alipay wechat" @click="pay_type=2" v-else><img src="../../static/img/wechat.png" alt=""></span> </el-col>
from django.db import models # Create your models here. from lyapi.utils.models import BaseModel from users.models import User from course.models import Course class Order(BaseModel): """訂單模型""" status_choices = ( (0, '未支付'), (1, '已支付'), (2, '已取消'), (3, '超時取消'), ) pay_choices = ( (0, '支付寶'), (1, '微信支付'), ) order_title = models.CharField(max_length=150,verbose_name="訂單標題") total_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="訂單總價", default=0) real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="實付金額", default=0) order_number = models.CharField(max_length=64,verbose_name="訂單號") order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="訂單狀態") pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式") credit = models.IntegerField(default=0, verbose_name="使用的積分數量") coupon = models.IntegerField(null=True, verbose_name="使用者優惠券ID") order_desc = models.TextField(max_length=500, verbose_name="訂單描述",null=True,blank=True) pay_time = models.DateTimeField(null=True, verbose_name="支付時間") user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING,verbose_name="下單使用者") class Meta: db_table="ly_order" verbose_name= "訂單記錄" verbose_name_plural= "訂單記錄" def __str__(self): return "%s,總價: %s,實付: %s" % (self.order_title, self.total_price, self.real_price) class OrderDetail(BaseModel): """ 訂單詳情 """ order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, verbose_name="訂單ID") course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, verbose_name="課程ID") expire = models.IntegerField(default='0', verbose_name="有效期週期",help_text="0表示永久有效") price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程原價") real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程實價") discount_name = models.CharField(max_length=120,default="",verbose_name="優惠型別") class Meta: db_table="ly_order_detail" verbose_name= "訂單詳情" verbose_name_plural= "訂單詳情" def __str__(self): return "%s" % (self.course.name)
order/adminx.py
import xadmin from .models import Order class OrderModelAdmin(object): """訂單模型管理類""" pass xadmin.site.register(Order, OrderModelAdmin) from .models import OrderDetail class OrderDetailModelAdmin(object): """訂單詳情模型管理類""" pass xadmin.site.register(OrderDetail, OrderDetailModelAdmin)
default_app_config = "order.apps.OrderConfig"
order/app.py
from django.apps import AppConfig class OrderConfig(AppConfig): name = 'order' verbose_name = '訂單管理'
order/urls.py
from django.urls import path,re_path from . import views urlpatterns = [ path('add_money/',views.OrderView.as_view(),) ]
order/views.py
from django.shortcuts import render from rest_framework.generics import CreateAPIView # Create your views here. from . import models from .serializers import OrderModelSerializer from rest_framework.permissions import IsAuthenticated class OrderView(CreateAPIView): queryset = models.Order.objects.filter(is_deleted=False,is_show=True) serializer_class = OrderModelSerializer permission_classes = [IsAuthenticated, ]
order/serializers.py
import datetime from rest_framework import serializers from . import models from django_redis import get_redis_connection from course.models import Course from course.models import CourseExpire from django.db import transaction class OrderModelSerializer(serializers.ModelSerializer): class Meta: model = models.Order fields = ['id', 'order_number', 'pay_type', 'coupon', 'credit'] ''' 1.使用者在訂單頁面需要提交過來的資料: 支付型別:微信/支付寶 優惠券 積分 2.使用者提交資料成功後,前端頁面需要返回過來的資料: id 訂單號 ''' extra_kwargs = { 'id':{'read_only':True}, 'order_number':{'read_only':True}, 'pay_type':{'write_only':True}, 'coupon':{'write_only':True}, 'credit':{'write_only':True}, } def validate(self, attrs): # 獲取使用者使用的支付方式 pay_type = int(attrs.get('pay_type',0)) # # 驗證使用者使用的支付方式是否是支付寶或微信中的一種 if pay_type not in [i[0] for i in models.Order.pay_choices]: raise serializers.ValidationError('支付方式不對!') # todo 優惠券校驗,看看是否過期了等等 # todo 積分上限校驗 return attrs def create(self, validated_data): try: # 生成訂單號 [日期,使用者id,自增資料] current_time = datetime.datetime.now() now = current_time.strftime('%Y%m%d%H%M%S') user_id = self.context['request'].user.id conn = get_redis_connection('cart') num = conn.incr('num') # 生成一個唯一的訂單號 order_number = now + "%06d" % user_id + "%06d" % num with transaction.atomic(): # 新增事務 # 生成訂單 order_obj = models.Order.objects.create(**{ 'order_title': '31期訂單', 'total_price': 0, 'real_price': 0, 'order_number': order_number, 'order_status': 0, 'pay_type': validated_data.get('pay_type', 0), 'credit': 0, 'coupon': 0, 'order_desc': '女朋友', 'pay_time': current_time, 'user_id': user_id, # 'user':user_obj, }) select_list = conn.smembers('selected_cart_%s' % user_id) ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'} for cid, eid in ret.items(): expire_id = int(eid.decode('utf-8')) if cid in select_list: course_id = int(cid.decode('utf-8')) course_obj = Course.objects.get(id=course_id) if expire_id > 0: expire_text = CourseExpire.objects.get(id=expire_id).expire_text # 生成訂單詳情 models.OrderDetail.objects.create(**{ 'order': order_obj, 'course': course_obj, 'expire': expire_id, 'price': course_obj.price, 'real_price': course_obj.real_price(expire_id), 'discount_name': course_obj.discount_name(), }) # print('xxxxx') except Exception: raise models.Order.DoesNotExist return order_obj