zoukankan      html  css  js  c++  java
  • day86:luffy:前端发送请求生成订单&结算页面优惠劵的实现

    目录

    1.前端发送请求生成订单

      1.前端点击支付按钮生成订单

      2.结算成功之后应该清除结算页面的数据

      3.后端计算结算页面总原价格和总的真实价格并存到数据库订单表中

    2.优惠劵

      1.准备工作

      2.前端展示优惠券信息-初始化

      3.优惠券-前端获取优惠券数据+后端接口

      4.结算页面计算真实总价格

      5.优惠券是否真的能够使用+优惠劵前端计算

        1.优惠劵的选中效果

        2.不可点击的不应该具有点击效果

        3.优惠券箭头收缩之后 取消优惠券的选中状态

        4.当选中的优惠劵发生变化时 重新计算总价

      6.优惠劵后端对优惠劵进行校验

    1.前端发送请求生成订单

    1.前端点击支付按钮生成订单

    昨日讲到了后端如何生成订单(order/add_money)[传送门:后端生成订单的接口]  但是前端的请求还没有发,现在来做一下前端发送请求来生成订单。

    点击支付按钮,触发生成订单事件,生成一个订单

    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);

    2.前端展示优惠券信息-初始化

    <!-- 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

    3.优惠券-前端获取优惠券数据+后端接口

    1.优惠劵后端接口

    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>

    4.结算页面计算真实总价格

    之前的结算页面只差真实的总价格没有计算了。现在通过后端计算真实总价格然后发送给前端。

    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;
        })
    },

    5.优惠券是否真的能够使用+优惠劵前端计算

    1.优惠劵的选中效果

    不在活动范围内的优惠劵应该设置不可选中的效果

    如果选中了当期优惠劵,则应该给优惠劵设置为选中的效果

    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

    // 当选中的优惠券发生变化时,重新计算总价
    watch:{
        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;
    
            }
    
          },

    6.优惠劵后端对优惠劵进行校验

    前端虽然对优惠劵进行校验,但是前端所显示的都是虚假的,后端也要对优惠劵进行校验。

    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
  • 相关阅读:
    vue项目发布到服务器之后出现空白页和图片找不到的问题
    H5中设置一个元素一直在页面的最底部
    vue项目打包出现的问题(日常记录)
    vue写H5注册页面
    vue项目中动态图片生成
    Java中boolean类型占用多少个字节
    Java将一个目录下的所有数据复制到另一个目录下
    Java使用递归找出某目录下的所有子目录以及子文件
    实现短信验证码
    C#连接Oracle数据库(直接引用dll使用)
  • 原文地址:https://www.cnblogs.com/libolun/p/13956296.html
Copyright © 2011-2022 走看看