• day85:luffy:购物车根据有效期不同切换价格&购物车删除操作&价格结算&订单页面前戏


    目录

    1.购物车有效期切换

    2.根据有效期不同切换价格

    3.购物车删除操作

    4.价格结算

    5.订单页面-初始化

    1.购物车有效期切换

    1.关于有效期表结构的设计

    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)

    2.课程有效期-后端接口

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

    3.课程有效期-前端

    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>

    2.根据有效期不同切换价格

    1.有效期切换更改redis中数据

    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'}))
    ]

    2.有效期切换让页面价格变化

    course/models.py

    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('
    ')
                            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>

    3.页面刷新 应该重置有效期

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

    3.购物车删除操作

    cartitem.vue

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

    1.后端redis删除

    cart/urls.py

    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':'删除成功'})

    2.前端同步实现删除效果

    用户删除一条购物车数据 后端已经删除了 但是前端页面也要同步删除

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

    4.价格结算

    1.价格结算页面-准备工作

    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>          

    2.价格结算页面-后端

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

    3.价格结算页面-前端

    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">&nbsp;&nbsp;</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>

    4.切换支付方式-微信/支付宝

    其实就是几张图片 点击显示图片而已

    Order.vue

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

    5.订单页面-初始化

    order/models.py

    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)

    order/__init__.py

    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
  • 相关阅读:
    浅谈Java Future
    图解java泛型的协变和逆变
    编写junit单元测试
    VBA中的Declare PtrSafe兼容64位系统
    在Python反编译中出现Magic value mismatch
    在Python反编译中批量pyc转 py
    ArcGIS Earth数据加载
    ArcGIS Earth 3D交互测量
    Linux系统清空或删除大文件内容5种方法
    安卓模拟器
  • 原文地址:https://www.cnblogs.com/libolun/p/13951556.html
Copyright © 2020-2023  润新知