• day84:luffy:优惠活动策略&用户认证&购物车商品的勾选/结算


    目录

    1.课程列表页活动和真实价格计算

      1.优惠活动策略的model表结构

      2.课程列表页显示优惠类型名称

      3.课程列表页显示真实价格

      4.将优惠类型名称和真实价格显示到前端页面上

      5.课程列表页显示具体结束时间

    2.添加购物车/查看购物车的用户认证

    3.购物车商品价格的勾选/结算

      1.每个课程的真实价格显示到购物车页面上

      2.勾选/非勾选应在redis中实时存储-后端接口

      3.勾选/非勾选应该在前端页面重新计算价格

      4.购物车列表显示-后端接口

    1.课程列表页活动和真实价格计算

    既然提到了活动,那与之对应的肯定是优惠策略、优惠活动等等。

    所以我们要为优惠活动策略单独建立表结构

    1.优惠活动策略的model表结构

    class CourseDiscountType(BaseModel):
        """课程优惠类型"""
        name = models.CharField(max_length=32, verbose_name="优惠类型名称")
        remark = models.CharField(max_length=250, blank=True, null=True, verbose_name="备注信息")
    
        class Meta:
            db_table = "ly_course_discount_type"
            verbose_name = "课程优惠类型"
            verbose_name_plural = "课程优惠类型"
    
        def __str__(self):
            return "%s" % (self.name)
    
    
    class CourseDiscount(BaseModel):
        """课程优惠模型"""
        discount_type = models.ForeignKey("CourseDiscountType", on_delete=models.CASCADE, related_name='coursediscounts', verbose_name="优惠类型")
        condition = models.IntegerField(blank=True, default=0, verbose_name="满足优惠的价格条件",help_text="设置参与优惠的价格门槛,表示商品必须在xx价格以上的时候才参与优惠活动,<br>如果不填,则不设置门槛") #因为有的课程不足100,你减免100,还亏钱了
        sale = models.TextField(verbose_name="优惠公式",blank=True,null=True, help_text="""
        不填表示免费;<br>
        *号开头表示折扣价,例如*0.82表示八二折;<br>
        -号开头则表示减免,例如-20表示原价-20;<br>
        如果需要表示满减,则需要使用 原价-优惠价格,例如表示课程价格大于100,优惠10;大于200,优惠25,格式如下:<br>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;满100-10<br>
        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;满200-25<br>
        """)
    
        class Meta:
            db_table = "ly_course_discount"
            verbose_name = "价格优惠策略"
            verbose_name_plural = "价格优惠策略"
    
        def __str__(self):
            return "价格优惠:%s,优惠条件:%s,优惠值:%s" % (self.discount_type.name, self.condition, self.sale)
    
    class Activity(BaseModel):
        """优惠活动"""
        name = models.CharField(max_length=150, verbose_name="活动名称")
        start_time = models.DateTimeField(verbose_name="优惠策略的开始时间")
        end_time = models.DateTimeField(verbose_name="优惠策略的结束时间")
        remark = models.CharField(max_length=250, blank=True, null=True, verbose_name="备注信息")
    
        class Meta:
            db_table = "ly_activity"
            verbose_name="商品活动"
            verbose_name_plural="商品活动"
    
        def __str__(self):
            return self.name
    
    class CoursePriceDiscount(BaseModel):
        """课程与优惠策略的关系表"""
        course = models.ForeignKey("Course",on_delete=models.CASCADE, related_name="activeprices",verbose_name="课程")
        active = models.ForeignKey("Activity",on_delete=models.DO_NOTHING, related_name="activecourses",verbose_name="活动")
        discount = models.ForeignKey("CourseDiscount",on_delete=models.CASCADE,related_name="discountcourse",verbose_name="优惠折扣")
    
        class Meta:
            db_table = "ly_course_price_dicount"
            verbose_name="课程与优惠策略的关系表"
            verbose_name_plural="课程与优惠策略的关系表"
    
        def __str__(self):
            return "课程:%s,优惠活动: %s,开始时间:%s,结束时间:%s" % (self.course.name, self.active.name, self.active.start_time,self.active.end_time)
    优惠活动策略的表结构设计

    2.课程列表页显示优惠类型名称

    1.course/models.py

    在模型类中写入discount_name 让课程列表页页面显示优惠类型名称

    class Course:
        
        def activity(self):
            import datetime
            now = datetime.datetime.now()
            
            # 获取课程参加的活动名称
            activity_list = self.activeprices.filter(is_show=True, is_deleted=False, active__start_time__lte=now, active__end_time__gte=now)
            return activity_list
    
        # 优惠类型名称
        def discount_name(self):
            dis_name = ''
            a = self.activity()
            if a:
                discount_n_list = []
                for i in a:
                    # 获取课程的折扣类型名称
                    discount_n = i.discount.discount_type.name
                    discount_n_list.append(discount_n)
                dis_name = discount_n_list[0]
    
            return dis_name

    2.course/serializers.py

    序列化器加入该字段

    class CourseModelSerializer:
        field = [,discount_name]
    class CourseDetailModelSerializer:
        field = [,discount_name]

    3.drf测试:course/courses

    3.课程列表页显示真实价格

    1.dev.py

    USE_TZ = False # 修改时区

    2.course/models.py

    class Course:
        def real_price(self):
            price = float(self.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)
                # 满减 满100-15
                elif '' in sale:
                    if price >= condition_price: # 只有价格满足优惠条件价格才能满减
                        l1 = sale.split('
    ')
                        dis_list = []  #10 50  25
                        for i in l1:
                            a, b = i[1:].split('-')
    
                            # 当商品价格(400) 满足100-200-300 应该选择满300那个优惠
                            if price >= float(a):
                                dis_list.append(float(b))
    
                        max_dis = max(dis_list) # 取到最大的那个满减优惠
                        r_price = price - max_dis # 原价格-满减价格=真实价格
    
            return r_price

    3.course/serializers.py

    class CourseModelSerializer:
        field = [,discount_name,real_price]
    class CourseDetailModelSerializer:
        field = [,discount_name,real_price]

    4.drf测试:course/courses

    4.将优惠类型名称和真实价格显示到前端页面上

    1.课程列表页前端

    <!-- Course.vue -->
    <div class="pay-box">
        <span class="discount-type" v-if="course.discount_name">{{course.discount_name}}</span>
        <span class="discount-price">¥{{course.real_price}}元</span>
        <span class="original-price" v-if="course.discount_name">原价:{{course.price}}元</span>
        <span class="buy-now">立即购买</span>
    </div>
        

    2.课程详情页前端

    <!-- Detail.vue -->
    <div class="sale-time">
        <p class="sale-type">{{course_data.discount_name}}</p>
        <p class="expire">距离结束:仅剩 567 天 12小时 52分 <span class="second">32</span></p>
    </div>
    <p class="course-price">
        <span>活动价</span>
        <span class="discount">¥{{course_data.real_price}}</span>
        <span class="original">¥{{course_data.price}}</span>
    </p>

    5.课程列表页显示具体结束时间

    1.course/models.py

    class Course:
        def left_time(self):
            import datetime
            now = datetime.datetime.now().timestamp() # 获取当前时间戳
            left_t = 0
            a = self.activity() # 获取当前课程参加的活动
            if a: # 如果当前课程有参加活动
                end_time = a[0].active.end_time.timestamp() # 获取当前课程活动的结束时间
                left_t = end_time - now # 剩余时间=结束时间-当前时间
    
                return left_t

    2.序列化器放入left_time

    course/serializers.py

    class CourseDetailModelSerializer:
        field = [,discount_name,real_price,left_time]

    3.前端渲染距离结束时间

    Detail.vue

    <!-- html -->
    <div class="sale-time">
        <p class="sale-type">{{course_data.discount_name}}</p>
        <p class="expire">距离结束:仅剩 {{course_data.left_time/60/60/24 | pInt}}天 {{course_data.left_time/60/60 % 24| pInt}}小时 {{course_data.left_time/60 % 60 | pInt}}分 <span class="second">{{course_data.left_time % 60 | pInt}}</span></p>
    </div>

    在js部分需要设置一个定时器,让时间能够一直减1s

    Detail.vue

    get_course_data(){
            this.$axios.get(`${this.$settings.Host}/course/detail/${this.course_id}/`)
            .then((res)=>{
              //console.log(res.data);
              this.course_data = res.data;
              this.playerOptions.sources[0].src = res.data.course_video
              this.playerOptions.poster = res.data.course_img
    
             // 设置计时器
              setInterval(()=>{
                this.course_data.left_time--;
    
              },1000)
    
            })
          },

    补充:让页面可以显示出0x分0x秒的效果→过滤器

    Detail.vue

    <!-- Detail.vue--html -->
             <p class="expire">距离结束:仅剩 {{course_data.left_time/60/60/24 | pInt}}天 
    {{course_data.left_time/60/60 % 24| pInt}}小时
    {{course_data.left_time/60 % 60 | pInt}}分
    <span class="second">{{course_data.left_time % 60 | pInt}}</span></p>
    // Detail.vue--js
     filters:{
          pInt(val){
            let a = parseInt(val);
            if (a < 10){
              a = `0${a}`;
            }
            return a
          }
        }

    2.添加购物车/查看购物车的用户认证

    为视图添加IsAuthenticated用户认证

    # cart/views.py
    class AddCartView:
        
        # 请求头必须携带着token
        permission_classes = [IsAuthenticated,] # 添加用户认证
        def add:
            user_id = request.user.id # 获取真实的用户id
            '''
            request = user_obj
            '''
        

    drf接口: cart/add_cart 获取不到数据,因为加了IsAuthenticated认证

    添加购物车时需要携带token 否则不能添加购物车

    // Detail.vue 添加购物车
        methods: {
    
          addCart(){
    
            let token = localStorage.token || sessionStorage.token;
    
            if (token){
              this.$axios.post(`${this.$settings.Host}/users/verify/`,{
                  token:token,
                }).then((res)=>{
        this.$axios.post(`${this.$settings.Host}/cart/add_cart/`,{
                    course_id:this.course_id,
                  },{
                  // 向后端提交数据需要加上header项,将token也要提交过去
                  headers:{
                    'Authorization':'jwt ' + token
                  }
                }).then((res)=>{
                    this.$message.success(res.data.msg);
                    console.log('>>>>>',this.$store)
                    this.$store.commit('add_cart', res.data.cart_length) ;
                    console.log(this.$store.state);
                  })
    
                }).catch((error)=>{
                 ......
                })
    
    
            } else {
              ......
                  })
            }
    
          },

    查看购物车时需要携带token 否则不能添加购物车

    // Cart.vue 购物车页面
    created() {
        let token = sessionStorage.token || localStorage.token;
        if (token){
    
          this.$axios.get(`${this.$settings.Host}/cart/add_cart/`,{
             // 查看购物车页面也要携带token 否则不能查看
             headers:{
                    'Authorization':'jwt ' + token
                  }
          })
          .then((res)=>{
            this.cart_data_list = res.data.cart_data_list
          })
          .catch((error)=>{
            this.$message.error(error.response.data.msg);
          })

    3.购物车商品价格的勾选/结算

    1.每个课程的真实价格显示到购物车页面上

    # cart/views.py
    
    class AddCartView:
    def cart_list(self, request):
           ......
            cart_data_list.append({
               ......
                'real_price': course_obj.real_price(),
               ......
            })
           ......
    <!-- cartitem.vue -->
    <div class="cart_column column_4">¥{{cart.real_price}}</div>

    2.勾选/非勾选应在redis中实时存储-后端接口

    # cart/views.py
    class AddCartView:
        def change_select(self, request):
            
            # 拿到课程id
            course_id = request.data.get('course_id')
            
            # 校验课程id合法性
            try:
                models.Course.objects.get(id=course_id)
            except:
                return Response({'msg': '课程不存在,不要乱搞!'}, status=status.HTTP_400_BAD_REQUEST)
            
            # 拿到user_id
            user_id = request.user.id
            
            # 去redis数据库:cart
            conn = get_redis_connection('cart')
            
            # redis存数据-用集合存:用户id:勾选课程id
            conn.sadd('selected_cart_%s' % user_id, course_id)
            
            return Response({'msg':'勾选成功'})   
        
           def cancel_select(self, request):
            course_id = request.data.get('course_id')
    
            try:
                models.Course.objects.get(id=course_id)
            except:
                return Response({'msg': '课程不存在,不要乱搞!'}, status=status.HTTP_400_BAD_REQUEST)
    
            user_id = request.user.id
            conn = get_redis_connection('cart')  # 1:{1,3}
            conn.srem('selected_cart_%s' % user_id, course_id)
    
            return Response({'msg': '恭喜你!少花钱了,但是你真的不学习了吗!'}) 

    为两个函数配置路由

    # cart/urls.py
    from django.urls import path,re_path
    from . import views
    
    urlpatterns = [
        path('add_cart/', views.AddCartView.as_view({'post':'add',
    'get':'cart_list',
    'patch':'change_select',
    'put':'cancel_select'})) # 不同的请求方法走不同函数 ]

    3.勾选/非勾选应该在前端页面重新计算价格

    <!-- 当用户点击前面的勾选框时,会改变selected的值
    近而会被watch监听到
    在监听中 无论是选中还是取消选中都会触发父级标签重新计算价格的动作(this.$emit) -->
    <el-checkbox class="my_el_checkbox" v-model="cart.selected"></el-checkbox>
    // Cartitem.vue
    watch:{
    
          'cart.selected':function (){
              
            // 添加选中
            let token = localStorage.token || sessionStorage.token;
            if (this.cart.selected){
              this.$axios.patch(`${this.$settings.Host}/cart/add_cart/`,{
                course_id: this.cart.course_id,
    
              },{
                headers:{
                  'Authorization':'jwt ' + token
                }
              }).then((res)=>{
                this.$message.success(res.data.msg);
                this.$emit('cal_t_p') // 触发cart组件计算商品总价格的方法
              }).catch((error)=>{
                this.$message.error(res.data.msg);
              })
            }
            else {
                
              // 取消选中
              this.$axios.put(`${this.$settings.Host}/cart/add_cart/`,{
                course_id: this.cart.course_id,
    
              },{
                headers:{
                  'Authorization':'jwt ' + token
                }
              }).then((res)=>{
                this.$message.success(res.data.msg);
                this.$emit('cal_t_p') // 触发cart组件计算商品总价格的方法
              }).catch((error)=>{
                this.$message.error(res.data.msg);
              })
            }
          },
    
    
        }

    触发Cart组件(父组件)的计算商品总价格的方法

    <!-- 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"></CartItem>
    
    </div>
    // Cart.vue
    methods:{
        cal_total_price(){
            
            let t_price = 0
            this.cart_data_list.forEach((v,k)=>{ // v是值 k是索引
              if (v.selected){
                t_price += v.real_price
              }
            })
            this.total_price = t_price
    
          }
    }

    4.购物车列表显示-后端接口

    # cart/views.py
    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') # 取出用户购物车里的课程id(redis中存着呢)
                    expire_id = eid.decode('utf-8') # 取出用户购物车里的有效期id(redis中存着呢)
    
                    course_obj = models.Course.objects.get(id=course_id) # 根据课程id,通过ORM查询得到课程对象,在下面就可以通过课程对象.字段 取到对应课程的参数信息
    
                    cart_data_list.append({
                        'course_id': course_obj.id,
                        'name': course_obj.name,
                        'course_img': constants.SERVER_ADDR + course_obj.course_img.url,
                        'price': course_obj.price,
                        'real_price': course_obj.real_price(),
                        'expire_id': expire_id,
                        '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})
        

    BUG:勾选两个课程 刷新页面 redis中仍然存着两个课程id

    # cart/views.py
    def cart_list(self, request):
        ......
        conn = get_redis_connection('cart')
        # 用户刷新页面时,从redis中删除用户对应的课程id
        conn.delete('selected_cart_%s' % user_id)
        ret = conn.hgetall('cart_%s' % user_id)  
        ......
  • 相关阅读:
    CodeForces
    设计模式之装饰模式和代理模式区别与联系
    java反射 概念
    Java 反射详解 转载
    Spring--AOP 例子
    MD5加密
    面向对象编程思想(OOP)
    软件测试assert
    junit4.9测试用例 spring测试用例 Assert 注解
    断言
  • 原文地址:https://www.cnblogs.com/libolun/p/13950429.html
Copyright © 2020-2023  润新知