zoukankan      html  css  js  c++  java
  • 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)  
        ......
  • 相关阅读:
    learnyou 相关网站
    hdu 3038 How Many Answers Are Wrong
    hdu 3047 Zjnu Stadium 并查集高级应用
    poj 1703 Find them, Catch them
    poj 1182 食物链 (带关系的并查集)
    hdu 1233 还是畅通工程
    hdu 1325 Is It A Tree?
    hdu 1856 More is better
    hdu 1272 小希的迷宫
    POJ – 2524 Ubiquitous Religions
  • 原文地址:https://www.cnblogs.com/libolun/p/13950429.html
Copyright © 2011-2022 走看看