Python笔记_82_订单页面_优惠券_用户积分

岳均
2023-12-01

优惠券

在订单页面查询当前用户拥有的优惠券

后端提供查询当前用户拥有的优惠券api接口

序列化器,coupon/serializer.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")

视图,coupon.views.py代码:

from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from .models import UserCoupon
from .serializers import UserCouponModelSerializer
class UserCouponAPIVew(ListAPIView):
    """我的优惠券"""
    queryset = UserCoupon.objects.filter(is_show=True,is_delete=False,is_use=False)
    serializer_class = UserCouponModelSerializer
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend]
    filter_fields = ('user_id',)

子应用路由,coupon/urls.py代码:

from django.urls import path
from . import views
urlpatterns = [
    path(r"",views.UserCouponAPIVew.as_view()),
]

总路由,urls.py代码:

    path('coupon/', include("coupon.urls")),

前端展示当前用户拥有的优惠券,并根据是否过期,可以让用户勾选.

Order.vue

<template>
 ...

        <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/image/12.png" alt=""></a>
                <span class="coupon-num">有{{coupon_list.length}}张可用</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="check_use(item)" @click="check_click(item)" v-for="item in coupon_list">
                  <p class="coupon-name">{{item.coupon.name}}</p>
                  <p class="coupon-condition">满{{item.coupon.condition}}元可以使用</p>
                  <p class="coupon-time start_time">开始时间:{{item.start_time.replace("T"," ")}}</p>
                  <p class="coupon-time end_time">过期时间:{{end_time(item.start_time,item.coupon.timer)}}</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>

        <div class="calc">
            <el-row class="pay-row">
              <el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col>
              <el-col :span="8">
                <span class="alipay" @click="pay_type=1">
                  <img v-if="pay_type==1" src="../../static/image/alipay2.png" alt="">
                  <img v-else src="../../static/image/alipay.png" alt="">
                </span>
                <span class="alipay wechat" @click="pay_type=2">
                  <img v-if="pay_type==2" src="../../static/image/wechat2.png" alt="">
                  <img v-else src="../../static/image/wechat.png" alt="">
                </span>
              </el-col>
              <el-col :span="8" class="count">实付款: <span>¥{{get_total()}}</span></el-col>
              <el-col :span="4" class="cart-pay"><span @click="payHander">去支付</span></el-col>
            </el-row>
        </div>
    </div>
    <Footer/>
  </div>
</template>

<script>
  import Header from "./common/Header"
  import Footer from "./common/Footer"
  export default {
    name:"Order",
    data(){
      return {
          course_list:[],     // 勾选商品
          pay_type: 1,        // 支付方式
          use_credit: false,  // 是否使用了优惠券
          credit: 0,          // 积分
          use_coupon: false,  // 优惠券ID,0表示没有使用优惠券
          coupon: 0,          // 优惠券ID,0表示没有使用优惠券
          coupon_list:[]      // 优惠券列表
      }
    },
    components:{
      Header,
      Footer,
    },
    created(){
      this.check_user_login();
      this.get_selected_course();
      this.get_user_coupon();
    },
    methods: {
      check_user_login(){
        // 检查用户是否登录了
        let user_token = localStorage.user_token || sessionStorage.user_token;
        if( !user_token ){
            // 判断用户是否登录了
            this.$confirm("对不起,您尚未登录!请登录后继续操作!","警告").then(()=>{
                this.$router.push("/user/login");
            });
        }
        return user_token;
      },
      check_click(user_coupon){
          let start = new Date( user_coupon.start_time ) - 0;
          let end   = start + user_coupon.coupon.timer * 24 * 60 * 60 * 1000;
          let now   = new Date() - 0;
          if( now > start && now < end ){
              this.coupon = user_coupon.id;
          }
      },
      check_use(user_coupon){
          let start = new Date( user_coupon.start_time ) - 0;
          let end   = start + user_coupon.coupon.timer * 24 * 60 * 60 * 1000;
          let now   = new Date() - 0;
          let disable = false;
          if( start > now ){
              disable = true;
              return "disable";
          }

          if( now > end ){
              disable = true;
              return "disable";
          }

          if(this.coupon == user_coupon.id && !disable){
              return "active";
          }

          return "";
      },
      end_time(start_time,timer){        
         // 计算优惠券的过期时间
        let start = (new Date(start_time) - 0) / 1000;
        let end = (start + timer * 24 * 60 * 60) * 1000; // 毫秒
        let end_date = new Date(end);
        let Y = end_date.getFullYear();
        let m = (end_date.getMonth() + 1);
        let d = end_date.getDate();
        m = m > 9 ? m : '0' + m;
        d = d > 9 ? d : '0' + d;
        return Y + "-" + m + "-" + d + " " + start_time.substr(11)
      },
      get_user_coupon(){
          // 获取当前登录用户的优惠券
          let user_id = localStorage.user_id || sessionStorage.user_id;
          this.$axios.get(`${this.$settings.Host}/coupon/`,{
              params:{
                  user_id: user_id,
              },
              headers:{
                "Authorization":"jwt " + this.check_user_login(),
              }
          }).then(response=>{
              this.coupon_list = response.data;
          }).catch(error=>{
              console.log(error.response);
          })
      },
      get_selected_course(){
          // 获取购物车中的勾选商品
                  // 获取购物车的勾选商品信息
        this.$axios.get(`${this.$settings.Host}/cart/course/selected/`,{
          headers:{
            "Authorization":"jwt " + this.check_user_login(),
          }
        }).then(response=>{
          this.course_list = response.data;
        }).catch(error=>{
          console.log(error.response);
        });
      },
      get_total(){
          // 计算总价格
          let total = 0;

          for(let key in this.course_list){
              total += parseFloat(this.course_list[key].real_price);
          }

          return total.toFixed(2);
      },
      payHander(){
          // 生成订单
          this.$axios.post(`${this.$settings.Host}/orders/`,{
              pay_type: this.pay_type,
              credit: this.credit,
              coupon: this.coupon
          },{
              headers:{
                "Authorization":"jwt " + this.check_user_login(),
              }
          }).then(response=>{
              // 下单成功
              console.log(response);

              // 发起支付

          }).catch(error=>{
              console.log(error.response);
          })


      }
    }
  }
</script>

当前用户勾选使用优惠券以后,会自动调整订单实付价格

Order.vue代码:

<template>
  <div class="cart">
   ...
        <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/image/12.png" alt=""></a>
                <span class="coupon-num">有{{coupon_list.length}}张可用</span>
              </div>
              <p class="sum-price-wrap">商品总金额:<span class="sum-price">{{total}}元</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="check_use(item)" @click="check_click(item)" v-for="item in coupon_list">
                  <p class="coupon-name">{{item.coupon.name}}</p>
                  <p class="coupon-condition">满{{item.coupon.condition}}元可以使用</p>
                  <p class="coupon-time start_time">开始时间:{{item.start_time.replace("T"," ")}}</p>
                  <p class="coupon-time end_time">过期时间:{{end_time(item.start_time,item.coupon.timer)}}</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>

        <div class="calc">
            <el-row class="pay-row">
              <el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col>
              <el-col :span="8">
                <span class="alipay" @click="pay_type=1">
                  <img v-if="pay_type==1" src="../../static/image/alipay2.png" alt="">
                  <img v-else src="../../static/image/alipay.png" alt="">
                </span>
                <span class="alipay wechat" @click="pay_type=2">
                  <img v-if="pay_type==2" src="../../static/image/wechat2.png" alt="">
                  <img v-else src="../../static/image/wechat.png" alt="">
                </span>
              </el-col>
              <el-col :span="8" class="count">实付款: <span>¥{{real_total}}</span></el-col>
              <el-col :span="4" class="cart-pay"><span @click="payHander">去支付</span></el-col>
            </el-row>
        </div>
    </div>
    <Footer/>
  </div>
</template>

<script>
  import Header from "./common/Header"
  import Footer from "./common/Footer"
  export default {
    name:"Order",
    data(){
      return {
          course_list:[],     // 勾选商品
          pay_type: 1,        // 支付方式
          use_credit: false,  // 是否使用了优惠券
          credit: 0,          // 积分
          use_coupon: false,  // 优惠券ID,0表示没有使用优惠券
          coupon: 0,          // 优惠券ID,0表示没有使用优惠券
          coupon_list:[],     // 优惠券列表
          total: 0,           // 购物车中商品总金额
          real_total: 0,      // 实付金额
      }
    },
    components:{
      Header,
      Footer,
    },
    created(){
      this.check_user_login();
      this.get_selected_course();
      this.get_user_coupon();
    },
    watch:{
        coupon(){
            // 每次用户优惠券时,总价格都要重新计算
            this.total = this.get_total();
            this.real_total = this.get_total(true);
        },
        use_coupon(){
            if(!this.use_coupon){
                // 当用户不使用优惠券时,把用户当前选择的优惠券ID重置为0
                this.coupon = 0;
            }
            // 每次用户优惠券时,总价格都要重新计算
            this.total = this.get_total();
            this.real_total = this.get_total(true);
        }
    },
    methods: {
    ...
      get_total(user_coupon){
          // user_coupon 是否使用了优惠券进行计算总价
          // 计算总价格
          let total = 0;

          for(let key in this.course_list){
              total += parseFloat(this.course_list[key].real_price);
          }
          if(user_coupon && this.coupon>0){
              console.log(223);
              for(let item in this.coupon_list){
                  let coupon_item = this.coupon_list[item];
                  console.log("225", coupon_item, this.coupon );
                  if(coupon_item.id == this.coupon){
                    // 判断当购物车中商品总价必须大于等于 优惠条件
                    if(total >= coupon_item.coupon.condition){
                      // 当前优惠券的优惠数值
                      let sale_num = parseFloat( coupon_item.coupon.sale.slice(1) );

                      if( coupon_item.coupon.sale[0] == "-" ){
                          // 抵扣优惠券
                          total -= sale_num;
                      }else if(coupon_item.coupon.sale[0]== "*"){
                          // 折扣优惠券
                          total *= sale_num;
                      }
                    }
                  }
              }
          }
          return total.toFixed(2);
      },
      payHander(){
          // 生成订单
          this.$axios.post(`${this.$settings.Host}/orders/`,{
              pay_type: this.pay_type,
              credit: this.credit,
              coupon: this.coupon
          },{
              headers:{
                "Authorization":"jwt " + this.check_user_login(),
              }
          }).then(response=>{
              // 下单成功
              console.log(response);

              // 发起支付

          }).catch(error=>{
              console.log(error.response);
          })


      }
    }
  }
</script>

上面的功能完成以后,我们可以看到客户端的总价格和实付总价格是正确的,但是在生成订单以后,返回的服务端的实付总金额是不正确的,所以我们需要再生成订单的时候,加入计算优惠券的金额计算.

orders/serializers.py中,代码:

from rest_framework import serializers
from .models import Order,OrderDetail
from datetime import datetime
import random
from django_redis import get_redis_connection
from courses.models import Course,CourseExpire
from django.db import transaction
from coupon.models import UserCoupon
class OrderModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = Order
        fields = [
            "id", "order_title", "total_price",
            "real_price", "order_number", "order_status",
            "pay_type", "credit",
            "coupon", "pay_time",
        ]
        extra_kwargs = {
            "id": {"read_only": True, },
            "order_title": {"read_only": True, },
            "total_price": {"read_only": True, },
            "real_price": {"read_only": True, },
            "order_number": {"read_only": True, },
            "order_status": {"read_only": True, },
            "pay_time": {"read_only": True, },
            "pay_type": {"required": True, },
            "credit": {"required": True, "min_value": 0},
            "coupon": {"required": True, },
        }

    def create(self, validated_data):
        """生成订单"""
        """1. 先生成订单记录"""
        # 接受客户端提交的数据
        pay_type = validated_data.get("pay_type")
        credit = validated_data.get("credit", 0)
        coupon = validated_data.get("coupon", 0)
        # 生成必要参数
        user_id = self.context["request"].user.id  # 在序列化器中获取视图中的数据,通过self.context
        order_title = "路飞学城课程购买"
        order_number = datetime.now().strftime("%Y%m%d%H%M%S")+("%06d" % user_id)+("%04d" % random.randint(0,9999))
        order_status = 0 # 未支付

        # 从redis中提取勾选商品
        redis = get_redis_connection("cart")
        # 从购物车中一区订单信息
        course_set = redis.smembers("selected_%s" % user_id)
        cart_list = redis.hgetall("cart_%s" % user_id)

        # 如果没有任何勾选的商品,则不能继续下单
        if len(course_set) < 1:
            raise serializers.ValidationError("对不起,当前没有选中任何课程!")

        # 生成订单记录
        with transaction.atomic():
            # 设置SQL语句的回滚位置
            save_id = transaction.savepoint()

            order = super().create({
                "order_title":order_title,
                "total_price":0,  # 等后面生成订单详情的时候,需要循环购物车中商品时,再计算总价格,再填进来
                "real_price":0,
                "order_number":order_number,
                "order_status":order_status,
                "pay_type": pay_type,
                "credit": credit,
                "coupon": coupon,
                "order_desc": "",
                "user_id": user_id,
                "orders": 0,  # 排序字段
            })

            """2. 再生成订单详情"""
            # 声明订单总价格和订单实价
            total_price = 0

            for course_id_bytes in course_set:
                """在循环中把每一件商品添加订单详情"""
                course_expire_bytes = cart_list[course_id_bytes]
                expire_time = int( course_expire_bytes.decode() )
                course_id = int( course_id_bytes.decode() )

                try:
                    course = Course.objects.get(pk=course_id)
                except:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,商品课程不存在!")

                # 提取课程的有效期选项
                try:
                    """有效期选项"""
                    course_expire = CourseExpire.objects.get(expire_time=expire_time,course=course)
                    price = course_expire.price
                except CourseExpire.DoesNotExist:
                    """永久有效"""
                    price = course.price


                # 生成订单详情记录
                try:
                    order_detail = OrderDetail.objects.create(
                        order=order,
                        course=course,
                        expire= expire_time,
                        price = price,
                        real_price = course.real_price(price),
                        discount_name = course.discount_name,
                        orders=0,  # 排序字段
                    )
                except:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,订单生成失败!请联系客服工作人员!")

                total_price += float(order_detail.real_price)

            # 保存订单的总价格
            order.total_price = total_price
            order.real_price = total_price # 先默认当前商品总价是实付金额

            # 如果用户使用了优惠券,则计算加入优惠券以后的实付金额
            if coupon>0:
                # 1. 查找优惠券
                user_coupon = UserCoupon.objects.get(is_show=True, is_delete=False, pk=coupon)
                # 2. 判断优惠券是否已经过期
                start_time = user_coupon.start_time.timestamp()
                now_time = datetime.now().timestamp()
                end_time = start_time + user_coupon.coupon.timer * 24 * 3600

                if start_time > now_time or now_time > end_time:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,当前优惠券无法使用!请重新确认使用的优惠券")

                # 3. 判断优惠券是否满足使用条件
                if user_coupon.coupon.condition > total_price:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,当前优惠券无法使用!请重新确认使用的优惠券")

                # 3. 根据优惠券的不同类型,采用不同的公式计算实付金额
                sale_num = float(user_coupon.coupon.sale[1:])
                if user_coupon.coupon.sale[0] == "-":
                    # 3.1 抵扣优惠券,实付总金额 = 商品总价 - 优惠券的金额
                    order.real_price = total_price - sale_num
                elif user_coupon.coupon.sale[0] == "*":
                    # 3.2 折扣优惠券,实付总金额 = 商品总价 * 优惠券的金额
                    order.real_price = total_price * sale_num

                # 防止出现无条件使用的优惠券产生负数的实付金额
                if order.real_price < 0:
                    order.real_price = 0

            order.save()

        """3. 清除掉购物车中勾选的商品"""
        pip = redis.pipeline()
        pip.multi()
        for course_id_bytes in cart_list:
            if course_id_bytes in course_set:
                pip.hdel("cart_%s" % user_id, course_id_bytes)
                pip.srem("selected_%s" % user_id, course_id_bytes)
        pip.execute()

        return order

用户积分

用户积分是商城里面促销的一种常见手段,我们可以认为积分是另一种购买或兑换商品的货币.所以,我们可以把积分理解为用户模型里面的一个字段.表示每一个用户都拥有属于自己的积分.

修改用户模型users/models.py

注意,因为我们前面已经调整了django中auth模块为我们创建的自定义模型了,所以我们可以很方便的增加用户模型的字段,不需要删除任何文件或迁移数据

from django.db import models
from django.contrib.auth.models import AbstractUser
from luffyapi.utils.models import BaseModel
# Create your models here.
class User(AbstractUser):
    SEX_OPT = (
        (1,"女"),
        (2,"男"),
    )
    avatar = models.ImageField(upload_to="avatar", blank=True,null=True ,verbose_name="头像")
    mobile = models.CharField(max_length=15, unique=True, blank=True, null=True, verbose_name="手机号码")
    sex    = models.BooleanField(default=1, verbose_name="性别")
    credit = models.IntegerField(default=0, verbose_name="贝壳")
    class Meta:
        db_table = 'ly_users'
        verbose_name = '用户'
        verbose_name_plural = verbose_name

    def __str__(self):
        return "%s" % self.username

class Credit(BaseModel):
    """积分流水"""
    OPERA_OPION = (
        (1, "赚取"),
        (2, "消费"),
    )
    user = models.ForeignKey("User", on_delete=models.CASCADE, verbose_name="用户")
    opera = models.SmallIntegerField(choices=OPERA_OPION,verbose_name="操作类型")
    number = models.SmallIntegerField(default=0, verbose_name="积分数值")

    class Meta:
        db_table = 'ly_credit'
        verbose_name = '积分流水'
        verbose_name_plural = verbose_name

    def __str__(self):
        return "%s %s %s 贝壳" % ( self.user.username, self.OPERA_OPION[self.opera][1], self.number )

数据迁移

python manage.py makemigrations
python manage.py migrate

迁移成功以后,我们就可以在xadmin运营站点中,给当前测试用户设置积分了.

接下来,有了积分以后,那么用户在购买课程的时候,就可以使用积分进行抵扣了.

在配置文件中,设置积分和真实货币的换算比例

settings/dev.py增加配置:

# 积分和现金的兑换比例[兑换1元的积分数量]
CREDIT_MONEY = 10

在用户登录的代码中,增加返回积分和换算比例的字段

服务端中users/utils.py,代码;

from django.conf import settings
def jwt_response_payload_handler(token, user=None, request=None):
    # 自定义登录以后的返回数据
    return {
        "token": token,
        "user_id": user.id,
        "user_credit": user.credit,
        "credit_to_money": settings.CREDIT_MONEY,
        "user_name": user.username
    }

在客户端中,保存积分到本地存储中.

Login.vue,在登录成功以后的代码中,

              if(this.remember){
                  // 永久存储
                  // localStorage.setItem("user_token",response.data.token);
                  localStorage.user_token = response.data.token; // 上面一句和当前一句是同样意思
                  localStorage.user_id = response.data.user_id;
                  localStorage.user_credit = response.data.user_credit;
                  localStorage.credit_to_money = response.data.credit_to_money;
                  localStorage.user_name = response.data.user_name;
                  sessionStorage.removeItem("user_token");
                  sessionStorage.removeItem("user_id");
                  sessionStorage.removeItem("user_credit");
                  sessionStorage.removeItem("credit_to_money");
                  sessionStorage.removeItem("user_name");
              }else{
                  // 回话存储
                  sessionStorage.user_token = response.data.token; // 上面一句和当前一句是同样意思
                  sessionStorage.user_id = response.data.user_id;
                  sessionStorage.user_credit = response.data.user_credit;
                  sessionStorage.credit_to_money = response.data.credit_to_money;
                  sessionStorage.user_name = response.data.user_name;
                  localStorage.removeItem("user_token");
                  localStorage.removeItem("user_id");
                  localStorage.removeItem("user_credit");
                  localStorage.removeItem("credit_to_money");
                  localStorage.removeItem("user_name");
              }

在用户购买课程进入计算页面时把积分显示并让用户可以设置要抵扣的积分金额

App.vue,代码,调整css样式.


.el-icon-minus,.el-icon-plus{
 font-size: 12px;
}

Order.vue,代码:

<template>
  <div class="cart">
    <Header/>
   ...
          <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>总积分:{{user_credit}},抵扣 ¥<el-input-number v-model="credit" :min="1" :max="max_credit()" label="请填写积分"></el-input-number>,本次花费以后,剩余{{parseInt(user_credit-credit)}}积分</span></p>
          </div>
          <p class="sun-coupon-num">优惠券抵扣:<span>{{(credit/credit_to_money).toFixed(2)}}元</span></p>
        </div>

        <div class="calc">
            <el-row class="pay-row">
              <el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col>
              <el-col :span="8">
                <span class="alipay" @click="pay_type=1">
                  <img v-if="pay_type==1" src="../../static/image/alipay2.png" alt="">
                  <img v-else src="../../static/image/alipay.png" alt="">
                </span>
                <span class="alipay wechat" @click="pay_type=2">
                  <img v-if="pay_type==2" src="../../static/image/wechat2.png" alt="">
                  <img v-else src="../../static/image/wechat.png" alt="">
                </span>
              </el-col>
              <el-col :span="8" class="count">实付款: <span>¥{{(real_total - credit/credit_to_money).toFixed(2)}}</span></el-col>
              <el-col :span="4" class="cart-pay"><span @click="payHander">去支付</span></el-col>
            </el-row>
        </div>
    </div>
    <Footer/>
  </div>
</template>

<script>
  import Header from "./common/Header"
  import Footer from "./common/Footer"
  export default {
    name:"Order",
    data(){
      return {
          user_credit: localStorage.user_credit || sessionStorage.user_credit,  // 用户积分
          credit_to_money: localStorage.credit_to_money || sessionStorage.credit_to_money,  // 积分换算比例
          course_list:[],     // 勾选商品
          pay_type: 1,        // 支付方式
          use_credit: false,  // 是否使用了优惠券
          credit: 0,          // 积分
          use_coupon: false,  // 优惠券ID,0表示没有使用优惠券
          coupon: 0,          // 优惠券ID,0表示没有使用优惠券
          coupon_list:[],     // 优惠券列表
          total: 0,           // 购物车中商品总金额
          real_total: 0,      // 实付金额
      }
    },
    components:{
      Header,
      Footer,
    },
    created(){
      this.check_user_login();
      this.get_selected_course();
      this.get_user_coupon();
    },
    watch:{
        coupon(){
            // 每次用户优惠券时,总价格都要重新计算
            this.total = this.get_total();
            this.real_total = this.get_total(true);
        },
        use_coupon(){
            if(!this.use_coupon){
                // 当用户不使用优惠券时,把用户当前选择的优惠券ID重置为0
                this.coupon = 0;
            }
            // 每次用户优惠券时,总价格都要重新计算
            this.total = this.get_total();
            this.real_total = this.get_total(true);
        },
        use_credit(){
            if(!this.use_credit){
                // 地方用户不适用积分抵扣时,把用户当前设置的抵扣积分进行重置
                this.credit = 0;
            }
            // 每次用户优惠券时,总价格都要重新计算
            this.total = this.get_total();
            this.real_total = this.get_total(true);
        }
    },
    methods: {
      check_user_login(){
        // 检查用户是否登录了
        let user_token = localStorage.user_token || sessionStorage.user_token;
        if( !user_token ){
            // 判断用户是否登录了
            this.$confirm("对不起,您尚未登录!请登录后继续操作!","警告").then(()=>{
                this.$router.push("/user/login");
            });
        }
        return user_token;
      },
      max_credit(){
          // 计算当前用户可以抵扣的最大积分数值
          let user_credit = parseInt( this.user_credit );
          let credit_to_money = parseInt( this.credit_to_money );
          if( user_credit / credit_to_money > this.total ){
              console.log(this.total * credit_to_money);
              return this.total * credit_to_money;
          }else{
              console.log(user_credit);
              return user_credit;
          }
      },
      check_click(user_coupon){
          let start = new Date( user_coupon.start_time ) - 0;
          let end   = start + user_coupon.coupon.timer * 24 * 60 * 60 * 1000;
          let now   = new Date() - 0;
          if( now > start && now < end ){
              this.coupon = user_coupon.id;
          }
      },
      check_use(user_coupon){
          let start = new Date( user_coupon.start_time ) - 0;
          let end   = start + user_coupon.coupon.timer * 24 * 60 * 60 * 1000;
          let now   = new Date() - 0;
          let disable = false;
          if( start > now ){
              disable = true;
              return "disable";
          }

          if( now > end ){
              disable = true;
              return "disable";
          }

          if(this.coupon == user_coupon.id && !disable){
              return "active";
          }

          return "";
      },
      end_time(start_time,timer){
          // 计算优惠券的过期时间
          // 开始时间
          let start = (new Date(start_time) - 0) / 1000;
          // 过期时间
          let end = (start + timer * 24 * 60 * 60) * 1000; // 微秒
          let end_date = new Date(end);
          let Y = end_date.getFullYear();
          let m = (end_date.getMonth()+1);
          let d = end_date.getDate();
          let H = end_date.getHours();
          let i = end_date.getMinutes();
          let s = end_date.getSeconds();
          m = m > 9?m:'0'+m;
          d = d > 9?d:'0'+m;
          H = H > 9?H:'0'+H;
          i = i > 9?i:'0'+i;
          s = s > 9?s:'0'+s;
          return Y+"-"+m+"-"+d+" "+H+":"+i+":"+s;
      },
      get_user_coupon(){
          // 获取当前登录用户的优惠券
          let user_id = localStorage.user_id || sessionStorage.user_id;
          this.$axios.get(`${this.$settings.Host}/coupon/`,{
              params:{
                  user_id: user_id,
              },
              headers:{
                "Authorization":"jwt " + this.check_user_login(),
              }
          }).then(response=>{
              this.coupon_list = response.data;
              this.total = this.get_total();
              this.real_total = this.get_total(true);
          }).catch(error=>{
              console.log(error.response);
          })
      },
      get_selected_course(){
        // 获取购物车中的勾选商品
        // 获取购物车的勾选商品信息
        this.$axios.get(`${this.$settings.Host}/cart/course/selected/`,{
          headers:{
            "Authorization":"jwt " + this.check_user_login(),
          }
        }).then(response=>{
          this.course_list = response.data;
          this.total = this.get_total();
          this.real_total = this.get_total(true);
        }).catch(error=>{
          console.log(error.response);
        });
      },
      get_total(user_coupon){
          // user_coupon 是否使用了优惠券进行计算总价
          // 计算总价格
          let total = 0;

          for(let key in this.course_list){
              total += parseFloat(this.course_list[key].real_price);
          }
          if(user_coupon && this.coupon>0){
              console.log(223);
              for(let item in this.coupon_list){
                  let coupon_item = this.coupon_list[item];
                  console.log("225", coupon_item, this.coupon );
                  if(coupon_item.id == this.coupon){
                    // 判断当购物车中商品总价必须大于等于 优惠条件
                    if(total >= coupon_item.coupon.condition){
                      // 当前优惠券的优惠数值
                      let sale_num = parseFloat( coupon_item.coupon.sale.slice(1) );

                      if( coupon_item.coupon.sale[0] == "-" ){
                          // 抵扣优惠券
                          total -= sale_num;
                      }else if(coupon_item.coupon.sale[0]== "*"){
                          // 折扣优惠券
                          total *= sale_num;
                      }
                    }
                  }
              }
          }
          return total.toFixed(2);
      },
      payHander(){
          // 生成订单
          this.$axios.post(`${this.$settings.Host}/orders/`,{
              pay_type: this.pay_type,
              credit: this.credit,
              coupon: this.coupon
          },{
              headers:{
                "Authorization":"jwt " + this.check_user_login(),
              }
          }).then(response=>{
              // 下单成功
              console.log(response);

              // 发起支付

          }).catch(error=>{
              console.log(error.response);
          })


      }
    }
  }
</script>

在用户确定确认下单时, 在序列化器中计算实付金额时纳入积分抵扣

序列化器,orders/serializer.py代码:

from rest_framework import serializers
from .models import Order,OrderDetail
from datetime import datetime
import random
from django_redis import get_redis_connection
from courses.models import Course,CourseExpire
from django.db import transaction
from coupon.models import UserCoupon
from django.conf import settings

class OrderModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = Order
        fields = [
            "id", "order_title", "total_price",
            "real_price", "order_number", "order_status",
            "pay_type", "credit",
            "coupon", "pay_time",
        ]
        extra_kwargs = {
            "id": {"read_only": True, },
            "order_title": {"read_only": True, },
            "total_price": {"read_only": True, },
            "real_price": {"read_only": True, },
            "order_number": {"read_only": True, },
            "order_status": {"read_only": True, },
            "pay_time": {"read_only": True, },
            "pay_type": {"required": True, },
            "credit": {"required": True, "min_value": 0},
            "coupon": {"required": True, },
        }

    def create(self, validated_data):
        """生成订单"""
        """1. 先生成订单记录"""
        # 接受客户端提交的数据
        pay_type = validated_data.get("pay_type")
        credit = validated_data.get("credit", 0)
        coupon = validated_data.get("coupon", 0)
        # 生成必要参数
        user_id = self.context["request"].user.id  # 在序列化器中获取视图中的数据,通过self.context
        order_title = "路飞学城课程购买"
        order_number = datetime.now().strftime("%Y%m%d%H%M%S")+("%06d" % user_id)+("%04d" % random.randint(0,9999))
        order_status = 0 # 未支付

        # 从redis中提取勾选商品
        redis = get_redis_connection("cart")
        # 从购物车中一区订单信息
        course_set = redis.smembers("selected_%s" % user_id)
        cart_list = redis.hgetall("cart_%s" % user_id)

        # 如果没有任何勾选的商品,则不能继续下单
        if len(course_set) < 1:
            raise serializers.ValidationError("对不起,当前没有选中任何课程!")

        # 生成订单记录
        with transaction.atomic():
            # 设置SQL语句的回滚位置
            save_id = transaction.savepoint()

            order = super().create({
                "order_title":order_title,
                "total_price":0,  # 等后面生成订单详情的时候,需要循环购物车中商品时,再计算总价格,再填进来
                "real_price":0,
                "order_number":order_number,
                "order_status":order_status,
                "pay_type": pay_type,
                "credit": credit,
                "coupon": coupon,
                "order_desc": "",
                "user_id": user_id,
                "orders": 0,  # 排序字段
            })

            """2. 再生成订单详情"""
            # 声明订单总价格和订单实价
            total_price = 0

            for course_id_bytes in course_set:
                """在循环中把每一件商品添加订单详情"""
                course_expire_bytes = cart_list[course_id_bytes]
                expire_time = int( course_expire_bytes.decode() )
                course_id = int( course_id_bytes.decode() )

                try:
                    course = Course.objects.get(pk=course_id)
                except:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,商品课程不存在!")

                # 提取课程的有效期选项
                try:
                    """有效期选项"""
                    course_expire = CourseExpire.objects.get(expire_time=expire_time,course=course)
                    price = course_expire.price
                except CourseExpire.DoesNotExist:
                    """永久有效"""
                    price = course.price


                # 生成订单详情记录
                try:
                    order_detail = OrderDetail.objects.create(
                        order=order,
                        course=course,
                        expire= expire_time,
                        price = price,
                        real_price = course.real_price(price),
                        discount_name = course.discount_name,
                        orders=0,  # 排序字段
                    )
                except:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,订单生成失败!请联系客服工作人员!")

                total_price += float(order_detail.real_price)

            # 保存订单的总价格
            order.total_price = total_price
            order.real_price = total_price # 先默认当前商品总价是实付金额

            # 如果用户使用了优惠券,则计算加入优惠券以后的实付金额
            if coupon>0:
                # 1. 查找优惠券
                user_coupon = UserCoupon.objects.get(is_show=True, is_delete=False, pk=coupon)
                # 2. 判断优惠券是否已经过期
                start_time = user_coupon.start_time.timestamp()
                now_time = datetime.now().timestamp()
                end_time = start_time + user_coupon.coupon.timer * 24 * 3600

                if start_time > now_time or now_time > end_time:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,当前优惠券无法使用!请重新确认使用的优惠券")

                # 3. 判断优惠券是否满足使用条件
                if user_coupon.coupon.condition > total_price:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,当前优惠券无法使用!请重新确认使用的优惠券")

                # 3. 根据优惠券的不同类型,采用不同的公式计算实付金额
                sale_num = float(user_coupon.coupon.sale[1:])
                if user_coupon.coupon.sale[0] == "-":
                    # 3.1 抵扣优惠券,实付总金额 = 商品总价 - 优惠券的金额
                    order.real_price = total_price - sale_num
                elif user_coupon.coupon.sale[0] == "*":
                    # 3.2 折扣优惠券,实付总金额 = 商品总价 * 优惠券的金额
                    order.real_price = total_price * sale_num

                # 防止出现无条件使用的优惠券产生负数的实付金额
                if order.real_price < 0:
                    order.real_price = 0

            # 积分汇算
            if credit > 0:
                # 获取用户积分以及本次购买的总商品价格,进行判断使用的积分是否在合理范围内
                user_credit = self.context["request"].user.credit
                if credit > user_credit or credit / settings.CREDIT_MONEY > total_price:
                    transaction.savepoint_rollback(save_id)
                    raise serializers.ValidationError("对不起,积分换算有误!请重新确认积分数值")
                # 进行积分抵扣
                order.real_price = float( "%.2f" % (order.real_price - credit / settings.CREDIT_MONEY) )


            order.save()

        """3. 清除掉购物车中勾选的商品"""
        pip = redis.pipeline()
        pip.multi()
        for course_id_bytes in cart_list:
            if course_id_bytes in course_set:
                pip.hdel("cart_%s" % user_id, course_id_bytes)
                pip.srem("selected_%s" % user_id, course_id_bytes)
        pip.execute()

        return order

经过上面的处理以后,我们就可以在用户的下单时,让用户可以选择抵扣积分或者使用优惠券.

 类似资料: