day86:luffy:前端發送請求生成訂單&結算頁面優惠劵的實現
- 2020 年 11 月 10 日
- 筆記
- PythonS31-筆記, Python全棧31期-筆記
3.後端計算結算頁面總原價格和總的真實價格並存到資料庫訂單表中
點擊支付按鈕,觸發生成訂單事件,生成一個訂單
order.vue
<!-- html --> <!-- 給支付按鈕綁定一個事件 該事件向後端發起請求來生成訂單 --> <el-col :span="4" class="cart-pay"><span @click="payhander">支付</span></el-col>
// js // 生成訂單 payhander(){ let token = localStorage.token || sessionStorage.token; this.$axios.post(`${this.$settings.Host}/order/add_money/`,{ // 生成訂單需要提交的支付類型、優惠券、積分 "pay_type":this.pay_type, "coupon":this.current_coupon, "credit":0 },{ headers:{ 'Authorization':'jwt ' + token } }).then((res)=>{ this.$message.success('訂單已經生成,馬上跳轉支付頁面') }).catch((error)=>{ this.$message.error(error.response.data.msg); }) }
2.結算成功之後應該清除結算頁面的數據
所以應該redis中刪除用戶選中的課程id,也就是selected_cart的數據
order/serializers.py
# serializers.py def create(self, validated_data): # 結算成功之後,再清除 conn = get_redis_connection('cart') conn.delete('selected_cart_%s' % user_id) return order_obj
3.後端計算結算頁面總原價格和總的真實價格並存到資料庫訂單表中
order/serializers.py
def create(self, validated_data): try: # 生成訂單號 [日期,用戶id,自增數據] total_price = 0 # 總原價 total_real_price = 0 # 總真實價格 with transaction.atomic(): # 添加事務 # 生成訂單,保存到資料庫中 ...... # 生成訂單詳情 ...... # 計算所有課程的總原價 total_price += course_obj.price # 計算所有課程的總真實價格 total_real_price += course_obj.real_price(expire_id) # 將訂單總原價和總真實價格存到資料庫表中 order_obj.total_price = total_price order_obj.real_price = total_real_price order_obj.save() except Exception: raise models.Order.DoesNotExist return order_obj
2.優惠劵
1.準備工作
1.創建coupon應用,並配置INSTALLAPP
python3 ../../manage.py startapp coupon
2.coupon/models.py
from django.db import models from lyapi.utils.models import BaseModel from users.models import User from datetime import timedelta # Create your models here. class Coupon(BaseModel): """優惠券""" coupon_choices = ( (0, '折扣優惠'), (1, '減免優惠') ) name = models.CharField(max_length=32, verbose_name="優惠券標題") coupon_type = models.SmallIntegerField(choices=coupon_choices, default=0, verbose_name="優惠券類型") timer = models.IntegerField(verbose_name="優惠券有效期", default=7, help_text="默認當前優惠券7天有效,如果設置值為-1則表示當前優惠券永久有效") condition = models.IntegerField(blank=True, default=0, verbose_name="滿足使用優惠券的價格條件,如果設置值為0,則表示沒有任何條件") sale = models.TextField(verbose_name="優惠公式", help_text=""" *號開頭表示折扣價,例如*0.82表示八二折;<br> -號開頭表示減免價,例如-10表示在總價基礎上減免10元<br> """) class Meta: db_table = "ly_coupon" verbose_name="優惠券" verbose_name_plural="優惠券" def __str__(self): return "%s" % (self.name) class UserCoupon(BaseModel): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="coupons", verbose_name="用戶") coupon = models.ForeignKey(Coupon, on_delete=models.CASCADE, related_name="users", verbose_name="優惠券") start_time = models.DateTimeField(verbose_name="優惠策略的開始時間") is_use = models.BooleanField(default=False,verbose_name="優惠券是否使用過") class Meta: db_table = "ly_user_coupon" verbose_name = "用戶的優惠券" verbose_name_plural = "用戶的優惠券" def __str__(self): return "優惠券:%s,用戶:%s" % (self.coupon.name, self.user.username) @property def end_time(self): s_time = self.start_time timer = self.coupon.timer #天數 return s_time + timedelta(days=timer)
優惠劵表結構設計
3.資料庫遷移指令
python3 ../../manage.py makemigrations
python3 ../../manage.py migrate
4.adminx註冊
coupon/adminx.py
import xadmin from .models import Coupon class CouponModelAdmin(object): """優惠券模型管理類""" list_display = ["name","coupon_type","timer"] xadmin.site.register(Coupon, CouponModelAdmin) from .models import UserCoupon class UserCouponModelAdmin(object): """我的優惠券模型管理類""" list_display = ["user","coupon","start_time","is_use"] xadmin.site.register(UserCoupon, UserCouponModelAdmin)
5.插入一些數據
INSERT INTO `ly_coupon` VALUES (1,1,1,0,'2019-08-21 15:59:04.568037','2019-08-21 15:59:04.568061','十元優惠券',1,30,10,'-10'),(2,2,1,0,'2019-08-21 15:59:33.764807','2019-08-21 15:59:33.764830','五十元優惠券',1,30,50,'-50'),(3,3,1,0,'2019-08-21 16:00:10.090100','2019-08-21 16:00:10.090126','9折優惠券',2,7,0,'*0.9'); INSERT INTO `ly_user_coupon` VALUES (1,1,1,0,'2019-08-21 16:00:40.823977','2019-08-23 19:23:58.117600','2019-08-21 01:00:00.000000',1,3,1), (2,2,1,0,'2019-08-21 16:00:49.868597','2019-08-22 09:37:46.010037','2019-10-01 01:00:00.000000',0,2,1), (3,3,1,0,'2019-08-21 16:01:09.051862','2019-08-23 19:31:02.605253','2019-08-21 01:01:00.000000',1,1,1), (4,5,1,0,'2019-08-22 08:48:56.406671','2019-08-22 08:48:56.406694','2019-08-22 17:48:00.000000',0,2,1);
<!-- html --> <div class="discount"> <div id="accordion"> <div class="coupon-box"> <div class="icon-box"> <span class="select-coupon">使用優惠劵:</span> <a class="select-icon unselect" :class="use_coupon?'is_selected':''" @click="use_coupon=!use_coupon"><img class="sign is_show_select" src="../../static/img/12.png" alt=""></a> <span class="coupon-num">有0張可用</span> </div> <p class="sum-price-wrap">商品總金額:<span class="sum-price">0.00元</span></p> </div> <div id="collapseOne" v-if="use_coupon"> <ul class="coupon-list" v-if="coupon_list.length>0"> <li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)"> <p class="coupon-name">8.5折優惠券</p> <p class="coupon-condition">滿0元可以使用</p> <p class="coupon-time start_time">開始時間:</p> <p class="coupon-time end_time">過期時間:</p> </li> </ul> <div class="no-coupon" v-if="coupon_list.length<1"> <span class="no-coupon-tips">暫無可用優惠券</span> </div> </div> </div> <div class="credit-box"> <label class="my_el_check_box"><el-checkbox class="my_el_checkbox" v-model="use_credit"></el-checkbox></label> <p class="discount-num1" v-if="!use_credit">使用我的貝里</p> <p class="discount-num2" v-else><span>總積分:100,已抵扣 ¥0.00,本次花費0積分</span></p> </div> <p class="sun-coupon-num">優惠券抵扣:<span>0.00元</span></p> </div>
前端展示優惠劵資訊-HTML
/* css */ .coupon-box{ text-align: left; padding-bottom: 22px; padding-left:30px; border-bottom: 1px solid #e8e8e8; } .coupon-box::after{ content: ""; display: block; clear: both; } .icon-box{ float: left; } .icon-box .select-coupon{ float: left; color: #666; font-size: 16px; } .icon-box::after{ content:""; clear:both; display: block; } .select-icon{ width: 20px; height: 20px; float: left; } .select-icon img{ max-height:100%; max-width: 100%; margin-top: 2px; transform: rotate(-90deg); transition: transform .5s; } .is_show_select{ transform: rotate(0deg)!important; } .coupon-num{ height: 22px; line-height: 22px; padding: 0 5px; text-align: center; font-size: 12px; float: left; color: #fff; letter-spacing: .27px; background: #fa6240; border-radius: 2px; margin-left: 20px; } .sum-price-wrap{ float: right; font-size: 16px; color: #4a4a4a; margin-right: 45px; } .sum-price-wrap .sum-price{ font-size: 18px; color: #fa6240; } .no-coupon{ text-align: center; width: 100%; padding: 50px 0px; align-items: center; justify-content: center; /* 文本兩端對其 */ border-bottom: 1px solid rgb(232, 232, 232); } .no-coupon-tips{ font-size: 16px; color: #9b9b9b; } .credit-box{ height: 30px; margin-top: 40px; display: flex; align-items: center; justify-content: flex-end } .my_el_check_box{ position: relative; } .my_el_checkbox{ margin-right: 10px; width: 16px; height: 16px; } .discount{ overflow: hidden; } .discount-num1{ color: #9b9b9b; font-size: 16px; margin-right: 45px; } .discount-num2{ margin-right: 45px; font-size: 16px; color: #4a4a4a; } .sun-coupon-num{ margin-right: 45px; margin-bottom:43px; margin-top: 40px; font-size: 16px; color: #4a4a4a; display: inline-block; float: right; } .sun-coupon-num span{ font-size: 18px; color: #fa6240; } .coupon-list{ margin: 20px 0; } .coupon-list::after{ display: block; content:""; clear: both; } .coupon-item{ float: left; margin: 15px 8px; width: 180px; height: 100px; padding: 5px; background-color: #fa3030; cursor: pointer; } .coupon-list .active{ background-color: #fa9000; } .coupon-list .disable{ cursor: not-allowed; background-color: #fa6060; } .coupon-condition{ font-size: 12px; text-align: center; color: #fff; } .coupon-name{ color: #fff; font-size: 24px; text-align: center; } .coupon-time{ text-align: left; color: #fff; font-size: 12px; } .unselect{ margin-left: 0px; transform: rotate(-90deg); } .is_selected{ transform: rotate(-1turn)!important; } .coupon-item p{ margin: 0; padding: 0; }
前端展示優惠劵資訊樣式-CSS
coupon/urls.py
# coupon/urls.py from django.urls import path,re_path from . import views urlpatterns = [ re_path(r'list/', views.CouponView.as_view(),), ]
lyapi/urls.py
# lyapi/urls.py from xadmin.plugins import xversion xversion.register_models() urlpatterns = [ ...... path(r'coupon/',include('coupon.urls')), ]
coupon/views.py
# coupon/views.py from django.shortcuts import render from rest_framework.generics import ListAPIView from . import models from rest_framework.permissions import IsAuthenticated from .serializers import UserCouponModelSerializer class CouponView(ListAPIView): serializer_class = UserCouponModelSerializer permission_classes = [IsAuthenticated, ] def get_queryset(self): return models.UserCoupon.objects.filter(is_show=True,is_deleted=False,is_use=False, user_id=self.request.user.id)
coupon/serializers.py
# coupon/serializers.py from rest_framework import serializers from .models import Coupon, UserCoupon class CouponModelSerializer(serializers.ModelSerializer): class Meta: model = Coupon fields = ("name","coupon_type","timer","condition","sale") class UserCouponModelSerializer(serializers.ModelSerializer): coupon = CouponModelSerializer() class Meta: model = UserCoupon fields = ("id","start_time","coupon","end_time")
2.間接計算優惠劵的結束時間
# coupon/models.py class UserCoupon(BaseModel): ......A @property # 調用類中該方法時不需要加大括弧 將其視作為屬性 def end_time(self): s_time = self.start_time timer = self.coupon.timer #天數 return s_time + timedelta(days=timer)
3.前端發送請求獲取優惠券數據
order.vue
// order.vue -js get_user_coupon(){ let token = localStorage.token || sessionStorage.token; this.$axios.get(`${this.$settings.Host}/coupon/list/`,{ headers:{ 'Authorization':'jwt ' + token } }).then((res)=>{ this.coupon_list = res.data; // 獲取到的優惠劵數據 }).catch((error)=>{ this.$message.error('優惠券獲取錯誤') }) },
<!-- order.vue html --> <ul class="coupon-list" v-if="coupon_list.length>0"> <li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)"> <p class="coupon-name">{{coupon.coupon.name}}</p> <p class="coupon-condition">滿{{coupon.coupon.condition}}元可以使用</p> <p class="coupon-time start_time">開始時間:{{coupon.start_time.replace('T',' ')}}</p> <p class="coupon-time end_time">過期時間:{{coupon.end_time.replace('T',' ')}}</p> </li> </ul>
之前的結算頁面只差真實的總價格沒有計算了。現在通過後端計算真實總價格然後發送給前端。
1.後端計算好結算頁面的總價格和總真實價格
cart/views.py
# cart/views.py # 結算頁面數據 def show_pay_info(self,request): user_id = request.user.id conn = get_redis_connection('cart') select_list = conn.smembers('selected_cart_%s' % user_id) data = [] ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'} total_price = 0 total_real_price = 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 = models.Course.objects.get(id=course_id) if expire_id > 0: expire_obj = models.CourseExpire.objects.get(id=expire_id) # 查詢到每個已勾選課程的真實價格 course_real_price = course_obj.real_price(expire_id) # 計算出所有課程的總真實價格 total_real_price += course_real_price data.append({ 'course_id':course_obj.id, 'name':course_obj.name, 'course_img':contains.SERVER_ADDR + course_obj.course_img.url , # 結算頁面的每條數據都顯示為每個課程的真實價格 'real_price':course_real_price, 'expire_text':expire_obj.expire_text, }) else: course_real_price = course_obj.real_price(expire_id) total_real_price += course_real_price data.append({ 'course_id': course_obj.id, 'name': course_obj.name, 'course_img': contains.SERVER_ADDR + course_obj.course_img.url, 'real_price': course_real_price, 'expire_text': '永久有效', }) return Response({'data':data,'total_real_price':total_real_price})
2.前端發送請求 獲取結算頁面的數據、總價格、總真實價格
order.vue
// Order.vue 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; // 獲取到後端發送過來的總真實價格 this.total_real_price = res.data.total_real_price; // 獲取到後端發送過來的總原價格 this.total_price = res.data.total_real_price; }) },
如果選中了當期優惠劵,則應該給優惠劵設置為選中的效果
order.vue
// order.vue js select_coupon(index,coupon_id){ // 拿到你當前點擊的那條優惠劵數據 let current_c = this.coupon_list[index] if (this.total_real_price < current_c.coupon.condition){ return 'disable' } // '/1000'拿到時間戳 let current_time = new Date() / 1000; let s_time = new Date(current_c.start_time) / 1000 let e_time = new Date(current_c.end_time) / 1000 // 如果優惠劵不在活動時間範圍內 則設置效果為不可選中 if (current_time < s_time || current_time > e_time){ return 'disable' } // 如果優惠劵是當前被選中的優惠劵 則設置效果為選中 if (this.current_coupon === coupon_id){ return 'active' } return '' },
<!-- order.vue html --> <li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)">
2.不可點擊的不應該具有點擊效果
change_coupon(index,coupon_id){ let current_c = this.coupon_list[index] // 如果優惠劵不符合條件 則優惠劵是不可點擊的 if (this.total_real_price < current_c.coupon.condition){ return false } let current_time = new Date() / 1000; let s_time = new Date(current_c.start_time) / 1000 let e_time = new Date(current_c.end_time) / 1000 // 如果優惠劵不符合條件 則優惠劵是不可點擊的 if (current_time < s_time || current_time > e_time){ return false } this.current_coupon = coupon_id; this.coupon_obj = current_c; },
3.優惠券箭頭收縮之後 取消優惠券的選中狀態
order.vue
watch:{ use_coupon(){ // 如果點擊箭頭收縮 那麼就將優惠劵的選中狀態取消 if (this.use_coupon === false){ this.current_coupon = 0; } },
4.當選中的優惠劵發生變化時 重新計算總價
order.vue
// 當選中的優惠券發生變化時,重新計算總價 current_coupon(){ this.cal_total_price(); } methods: { cal_total_price(){ // 當用戶選中了某個優惠劵 if (this.current_coupon !== 0){ // 1.獲取用戶使用優惠劵前的真實價格 let tt = this.total_real_price; // 2.獲取當前優惠劵的優惠公式 let sales = this.coupon_obj.coupon.sale; // 3.取出優惠公式的數字部分 let d = parseFloat(this.coupon_obj.coupon.sale.substr(1)); if (sales[0] === '-'){ tt = this.total_real_price - d }else if (sales[0] === '*'){ tt = this.total_real_price * d } this.total_price = tt; } },
前端雖然對優惠劵進行校驗,但是前端所顯示的都是虛假的,後端也要對優惠劵進行校驗。
class OrderModelSerializers: 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('支付方式不對!') # 優惠券校驗,看看是否過期了等等 coupon_id = attrs.get('coupon', 0) if coupon_id > 0: try: user_conpon_obj = UserCoupon.objects.get(id=coupon_id) except: raise serializers.ValidationError('訂單創建失敗,優惠券id不對') # 校驗優惠劵是否在活動時間範圍內 now = datetime.datetime.now().timestamp() start_time = user_conpon_obj.start_time.timestamp() end_time = user_conpon_obj.end_time.timestamp() if now < start_time or now > end_time: raise serializers.ValidationError('訂單創建失敗,優惠券不在使用範圍內,滾犢子') # todo 積分上限校驗 return attrs