后端提供查询当前用户拥有的优惠券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
经过上面的处理以后,我们就可以在用户的下单时,让用户可以选择抵扣积分或者使用优惠券.