day86:luffy:前端发送请求生成订单&结算页面优惠劵的实现
目录
3.后端计算结算页面总原价格和总的真实价格并存到数据库订单表中
1.前端发送请求生成订单
1.前端点击支付按钮生成订单
昨日讲到了后端如何生成订单(order/add_money),但是前端的请求还没有发,现在来做一下前端发送请求来生成订单。
点击支付按钮,触发生成订单事件,生成一个订单
order.vue
<!-- html -->
<!-- 给支付按钮绑定一个事件 该事件向后端发起请求来生成订单 -->
<el-col :span="4" class="cart-pay"><span @click="payhander">支付</span></el-col>
// js
// 生成订单
payhander(){
let token = localStorage.token || sessionStorage.token;
this.$axios.post(`${this.$settings.Host}/order/add_money/`,{ // 生成订单需要提交的支付类型、优惠券、积分
"pay_type":this.pay_type,
"coupon":this.current_coupon,
"credit":0 },{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.$message.success('订单已经生成,马上跳转支付页面')
}).catch((error)=>{
this.$message.error(error.response.data.msg);
}) }
2.结算成功之后应该清除结算页面的数据
结算成功之后应该清除结算界面的数据,如果用户还想购买课程的话,应该去购物车去再次选中自己想要购买的商品再购买,然后再重新生成订单。
所以应该redis中删除用户选中的课程id,也就是selected_cart的数据
order/serializers.py
# serializers.py
def create(self, validated_data): # 结算成功之后,再清除
conn = get_redis_connection('cart')
conn.delete('selected_cart_%s' % user_id) return order_obj
3.后端计算结算页面总原价格和总的真实价格并存到数据库订单表中
order/serializers.py
def create(self, validated_data):
try:
# 生成订单号 [日期,用户id,自增数据] total_price = 0 # 总原价
total_real_price = 0 # 总真实价格 with transaction.atomic(): # 添加事务
# 生成订单,保存到数据库中
......
# 生成订单详情
......
# 计算所有课程的总原价
total_price += course_obj.price # 计算所有课程的总真实价格
total_real_price += course_obj.real_price(expire_id) # 将订单总原价和总真实价格存到数据库表中
order_obj.total_price = total_price
order_obj.real_price = total_real_price
order_obj.save() except Exception:
raise models.Order.DoesNotExist return order_obj
2.优惠劵
1.准备工作
1.创建coupon应用,并配置INSTALLAPP
python3 ../../manage.py startapp coupon
2.coupon/models.py
from django.db import models
from lyapi.utils.models import BaseModel
from users.models import User
from datetime import timedelta # Create your models here.
class Coupon(BaseModel):
"""优惠券"""
coupon_choices = (
(0, '折扣优惠'),
(1, '减免优惠')
)
name = models.CharField(max_length=32, verbose_name="优惠券标题")
coupon_type = models.SmallIntegerField(choices=coupon_choices, default=0, verbose_name="优惠券类型")
timer = models.IntegerField(verbose_name="优惠券有效期", default=7, help_text="默认当前优惠券7天有效,如果设置值为-1则表示当前优惠券永久有效")
condition = models.IntegerField(blank=True, default=0, verbose_name="满足使用优惠券的价格条件,如果设置值为0,则表示没有任何条件")
sale = models.TextField(verbose_name="优惠公式", help_text="""
*号开头表示折扣价,例如*0.82表示八二折;<br>
-号开头表示减免价,例如-10表示在总价基础上减免10元<br>
""") class Meta:
db_table = "ly_coupon"
verbose_name="优惠券"
verbose_name_plural="优惠券" def __str__(self):
return "%s" % (self.name) class UserCoupon(BaseModel):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="coupons", verbose_name="用户")
coupon = models.ForeignKey(Coupon, on_delete=models.CASCADE, related_name="users", verbose_name="优惠券")
start_time = models.DateTimeField(verbose_name="优惠策略的开始时间")
is_use = models.BooleanField(default=False,verbose_name="优惠券是否使用过") class Meta:
db_table = "ly_user_coupon"
verbose_name = "用户的优惠券"
verbose_name_plural = "用户的优惠券" def __str__(self):
return "优惠券:%s,用户:%s" % (self.coupon.name, self.user.username) @property
def end_time(self):
s_time = self.start_time
timer = self.coupon.timer #天数 return s_time + timedelta(days=timer)
优惠劵表结构设计
3.数据库迁移指令
python3 ../../manage.py makemigrations
python3 ../../manage.py migrate
4.adminx注册
coupon/adminx.py
import xadmin
from .models import Coupon
class CouponModelAdmin(object):
"""优惠券模型管理类"""
list_display = ["name","coupon_type","timer"]
xadmin.site.register(Coupon, CouponModelAdmin) from .models import UserCoupon
class UserCouponModelAdmin(object):
"""我的优惠券模型管理类"""
list_display = ["user","coupon","start_time","is_use"] xadmin.site.register(UserCoupon, UserCouponModelAdmin)
5.插入一些数据
INSERT INTO `ly_coupon` VALUES (1,1,1,0,'2019-08-21 15:59:04.568037','2019-08-21 15:59:04.568061','十元优惠券',1,30,10,'-10'),(2,2,1,0,'2019-08-21 15:59:33.764807','2019-08-21 15:59:33.764830','五十元优惠券',1,30,50,'-50'),(3,3,1,0,'2019-08-21 16:00:10.090100','2019-08-21 16:00:10.090126','9折优惠券',2,7,0,'*0.9'); INSERT INTO `ly_user_coupon` VALUES
(1,1,1,0,'2019-08-21 16:00:40.823977','2019-08-23 19:23:58.117600','2019-08-21 01:00:00.000000',1,3,1),
(2,2,1,0,'2019-08-21 16:00:49.868597','2019-08-22 09:37:46.010037','2019-10-01 01:00:00.000000',0,2,1),
(3,3,1,0,'2019-08-21 16:01:09.051862','2019-08-23 19:31:02.605253','2019-08-21 01:01:00.000000',1,1,1),
(4,5,1,0,'2019-08-22 08:48:56.406671','2019-08-22 08:48:56.406694','2019-08-22 17:48:00.000000',0,2,1);
2.前端展示优惠券信息-初始化
<!-- html -->
<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/img/12.png" alt=""></a>
<span class="coupon-num">有0张可用</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="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)">
<p class="coupon-name">8.5折优惠券</p>
<p class="coupon-condition">满0元可以使用</p>
<p class="coupon-time start_time">开始时间:</p>
<p class="coupon-time end_time">过期时间:</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>
前端展示优惠劵信息-HTML
/* css */
.coupon-box{
text-align: left;
padding-bottom: 22px;
padding-left:30px;
border-bottom: 1px solid #e8e8e8;
}
.coupon-box::after{
content: "";
display: block;
clear: both;
}
.icon-box{
float: left;
}
.icon-box .select-coupon{
float: left;
color: #666;
font-size: 16px;
}
.icon-box::after{
content:"";
clear:both;
display: block;
}
.select-icon{
width: 20px;
height: 20px;
float: left;
}
.select-icon img{
max-height:100%;
max-width: 100%;
margin-top: 2px;
transform: rotate(-90deg);
transition: transform .5s;
}
.is_show_select{
transform: rotate(0deg)!important;
}
.coupon-num{
height: 22px;
line-height: 22px;
padding: 0 5px;
text-align: center;
font-size: 12px;
float: left;
color: #fff;
letter-spacing: .27px;
background: #fa6240;
border-radius: 2px;
margin-left: 20px;
}
.sum-price-wrap{
float: right;
font-size: 16px;
color: #4a4a4a;
margin-right: 45px;
}
.sum-price-wrap .sum-price{
font-size: 18px;
color: #fa6240;
} .no-coupon{
text-align: center;
width: 100%;
padding: 50px 0px;
align-items: center;
justify-content: center; /* 文本两端对其 */
border-bottom: 1px solid rgb(232, 232, 232);
}
.no-coupon-tips{
font-size: 16px;
color: #9b9b9b;
}
.credit-box{
height: 30px;
margin-top: 40px;
display: flex;
align-items: center;
justify-content: flex-end
}
.my_el_check_box{
position: relative;
}
.my_el_checkbox{
margin-right: 10px;
width: 16px;
height: 16px;
}
.discount{
overflow: hidden;
}
.discount-num1{
color: #9b9b9b;
font-size: 16px;
margin-right: 45px;
}
.discount-num2{
margin-right: 45px;
font-size: 16px;
color: #4a4a4a;
}
.sun-coupon-num{
margin-right: 45px;
margin-bottom:43px;
margin-top: 40px;
font-size: 16px;
color: #4a4a4a;
display: inline-block;
float: right;
}
.sun-coupon-num span{
font-size: 18px;
color: #fa6240;
}
.coupon-list{
margin: 20px 0;
}
.coupon-list::after{
display: block;
content:"";
clear: both;
}
.coupon-item{
float: left;
margin: 15px 8px;
width: 180px;
height: 100px;
padding: 5px;
background-color: #fa3030;
cursor: pointer;
}
.coupon-list .active{
background-color: #fa9000;
}
.coupon-list .disable{
cursor: not-allowed;
background-color: #fa6060;
}
.coupon-condition{
font-size: 12px;
text-align: center;
color: #fff;
}
.coupon-name{
color: #fff;
font-size: 24px;
text-align: center;
}
.coupon-time{
text-align: left;
color: #fff;
font-size: 12px;
}
.unselect{
margin-left: 0px;
transform: rotate(-90deg);
}
.is_selected{
transform: rotate(-1turn)!important;
}
.coupon-item p{
margin: 0;
padding: 0;
}
前端展示优惠劵信息样式-CSS
3.优惠券-前端获取优惠券数据+后端接口
1.优惠劵后端接口
coupon/urls.py
# coupon/urls.py
from django.urls import path,re_path
from . import views urlpatterns = [
re_path(r'list/', views.CouponView.as_view(),), ]
lyapi/urls.py
# lyapi/urls.py from xadmin.plugins import xversion
xversion.register_models() urlpatterns = [
......
path(r'coupon/',include('coupon.urls')), ]
coupon/views.py
# coupon/views.py
from django.shortcuts import render
from rest_framework.generics import ListAPIView
from . import models
from rest_framework.permissions import IsAuthenticated from .serializers import UserCouponModelSerializer class CouponView(ListAPIView): serializer_class = UserCouponModelSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self): return models.UserCoupon.objects.filter(is_show=True,is_deleted=False,is_use=False, user_id=self.request.user.id)
coupon/serializers.py
# coupon/serializers.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","end_time")
2.间接计算优惠劵的结束时间
# coupon/models.py
class UserCoupon(BaseModel):
......A
@property # 调用类中该方法时不需要加大括号 将其视作为属性
def end_time(self):
s_time = self.start_time
timer = self.coupon.timer #天数
return s_time + timedelta(days=timer)
3.前端发送请求获取优惠券数据
order.vue
// order.vue -js
get_user_coupon(){
let token = localStorage.token || sessionStorage.token;
this.$axios.get(`${this.$settings.Host}/coupon/list/`,{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.coupon_list = res.data; // 获取到的优惠劵数据
}).catch((error)=>{
this.$message.error('优惠券获取错误')
}) },
<!-- order.vue html -->
<ul class="coupon-list" v-if="coupon_list.length>0">
<li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)">
<p class="coupon-name">{{coupon.coupon.name}}</p>
<p class="coupon-condition">满{{coupon.coupon.condition}}元可以使用</p>
<p class="coupon-time start_time">开始时间:{{coupon.start_time.replace('T',' ')}}</p>
<p class="coupon-time end_time">过期时间:{{coupon.end_time.replace('T',' ')}}</p>
</li> </ul>
4.结算页面计算真实总价格
之前的结算页面只差真实的总价格没有计算了。现在通过后端计算真实总价格然后发送给前端。
1.后端计算好结算页面的总价格和总真实价格
cart/views.py
# cart/views.py # 结算页面数据
def show_pay_info(self,request):
user_id = request.user.id
conn = get_redis_connection('cart')
select_list = conn.smembers('selected_cart_%s' % user_id)
data = [] ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'} total_price = 0
total_real_price = 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 = models.Course.objects.get(id=course_id) if expire_id > 0:
expire_obj = models.CourseExpire.objects.get(id=expire_id) # 查询到每个已勾选课程的真实价格
course_real_price = course_obj.real_price(expire_id) # 计算出所有课程的总真实价格
total_real_price += course_real_price data.append({
'course_id':course_obj.id,
'name':course_obj.name,
'course_img':contains.SERVER_ADDR + course_obj.course_img.url , # 结算页面的每条数据都显示为每个课程的真实价格
'real_price':course_real_price, 'expire_text':expire_obj.expire_text,
})
else:
course_real_price = course_obj.real_price(expire_id)
total_real_price += course_real_price
data.append({
'course_id': course_obj.id,
'name': course_obj.name,
'course_img': contains.SERVER_ADDR + course_obj.course_img.url,
'real_price': course_real_price,
'expire_text': '永久有效',
}) return Response({'data':data,'total_real_price':total_real_price})
2.前端发送请求 获取结算页面的数据、总价格、总真实价格
order.vue
// Order.vue
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; // 获取到后端发送过来的总真实价格
this.total_real_price = res.data.total_real_price; // 获取到后端发送过来的总原价格
this.total_price = res.data.total_real_price;
})
},
5.优惠券是否真的能够使用+优惠劵前端计算
1.优惠劵的选中效果
不在活动范围内的优惠劵应该设置不可选中的效果
如果选中了当期优惠劵,则应该给优惠劵设置为选中的效果
order.vue
// order.vue js
select_coupon(index,coupon_id){ // 拿到你当前点击的那条优惠劵数据
let current_c = this.coupon_list[index]
if (this.total_real_price < current_c.coupon.condition){
return 'disable'
} // '/1000'拿到时间戳
let current_time = new Date() / 1000;
let s_time = new Date(current_c.start_time) / 1000
let e_time = new Date(current_c.end_time) / 1000 // 如果优惠劵不在活动时间范围内 则设置效果为不可选中
if (current_time < s_time || current_time > e_time){
return 'disable'
} // 如果优惠劵是当前被选中的优惠劵 则设置效果为选中
if (this.current_coupon === coupon_id){
return 'active'
} return ''
},
<!-- order.vue html -->
<li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)">
2.不可点击的不应该具有点击效果
change_coupon(index,coupon_id){
let current_c = this.coupon_list[index] // 如果优惠劵不符合条件 则优惠劵是不可点击的
if (this.total_real_price < current_c.coupon.condition){
return false
}
let current_time = new Date() / 1000;
let s_time = new Date(current_c.start_time) / 1000
let e_time = new Date(current_c.end_time) / 1000 // 如果优惠劵不符合条件 则优惠劵是不可点击的
if (current_time < s_time || current_time > e_time){
return false
} this.current_coupon = coupon_id;
this.coupon_obj = current_c; },
3.优惠券箭头收缩之后 取消优惠券的选中状态
order.vue
watch:{
use_coupon(){
// 如果点击箭头收缩 那么就将优惠劵的选中状态取消
if (this.use_coupon === false){
this.current_coupon = 0; }
},
4.当选中的优惠劵发生变化时 重新计算总价
order.vue
// 当选中的优惠券发生变化时,重新计算总价
current_coupon(){
this.cal_total_price();
} methods: { cal_total_price(){
// 当用户选中了某个优惠劵
if (this.current_coupon !== 0){ // 1.获取用户使用优惠劵前的真实价格
let tt = this.total_real_price; // 2.获取当前优惠劵的优惠公式
let sales = this.coupon_obj.coupon.sale; // 3.取出优惠公式的数字部分
let d = parseFloat(this.coupon_obj.coupon.sale.substr(1));
if (sales[0] === '-'){
tt = this.total_real_price - d
}else if (sales[0] === '*'){
tt = this.total_real_price * d
}
this.total_price = tt; } },
6.优惠劵后端对优惠劵进行校验
前端虽然对优惠劵进行校验,但是前端所显示的都是虚假的,后端也要对优惠劵进行校验。
class OrderModelSerializers:
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('支付方式不对!') # 优惠券校验,看看是否过期了等等
coupon_id = attrs.get('coupon', 0)
if coupon_id > 0:
try:
user_conpon_obj = UserCoupon.objects.get(id=coupon_id)
except:
raise serializers.ValidationError('订单创建失败,优惠券id不对')
# 校验优惠劵是否在活动时间范围内
now = datetime.datetime.now().timestamp()
start_time = user_conpon_obj.start_time.timestamp()
end_time = user_conpon_obj.end_time.timestamp()
if now < start_time or now > end_time:
raise serializers.ValidationError('订单创建失败,优惠券不在使用范围内,滚犊子') # todo 积分上限校验 return attrs
day86:luffy:前端发送请求生成订单&结算页面优惠劵的实现的更多相关文章
- java web前端发送请求的4种方式
表单 action, 链接href,js 绑定按钮 ajax绑定标签 <h1>通过表单提交参数</h1> <form action="/day46_v1/Ser ...
- day87:luffy:结算页面积分&支付宝接口
目录 1.积分 2.支付 1.积分 1.关于积分的表结构 1.在user表中添加credit字段 + 设计一个积分的表结构 user/models.py class User(AbstractUser ...
- java后台发送请求并获取返回值
项目中需要前端发送请求给后端,而后端需要从另一个平台中取数据然后再透传给前端,通过下述代码将其实现.在此记录一下. package com.autotest.utils; import java.io ...
- 用vue.js重构订单计算页面
在很久很久以前做过一个很糟糕的订单结算页面,虽然里面各区域(收货地址)使用模块化加载,但是偶尔会遇到某个模块加载失败的问题导致订单提交的数据有误. 大致问题如下: 1. 每个模块都采用usercont ...
- 向.net后端发送请求获取数据,在前端动态填充表格
实现效果 实现步骤 通过Ajax请求的方式 1.在前端定义Table 2.通过Ajax向.net后端发送数据请求 3.在.net后端定义方法供前端调用,并返回所需的数据 4.通过构造字符串的方式,将后 ...
- angularjs和jquery前端发送以http请求formdata数据
formdata是比较常见的前端发送给后端的请求,不仅可以上传数据,而且同时可以上传文件. jquery使用http请求上传formdata数据的方法: var formdata = new Form ...
- Java生成二进制文件与Postman以二进制流的形式发送请求
业务描述: 模拟终端(智能家居)发送HTTP POST请求,请求参数为二进制流:而且,二进制流是加密后的数据,因此调试分两步: 1.Java代码生成加密后数据,并保存为二进制流文件 (电脑上的图片就是 ...
- Query通过Ajax向PHP服务端发送请求并返回JSON数据
Query通过Ajax向PHP服务端发送请求并返回JSON数据 服务端PHP读取MYSQL数据,并转换成JSON数据,传递给前端Javascript,并操作JSON数据.本文将通过实例演示了jQuer ...
- 爬虫模块介绍--request(发送请求模块)
爬虫:可见即可爬 # 每个网站都有爬虫协议 基础爬虫需要使用到的三个模块 requests 模块 # 模拟发请求的模块 PS:python原来有两个模块urllib和urllib的升级urlli ...
随机推荐
- 1个LED的亮度自动控制
控制任务和要求 通过程序控制LED的亮度按照要求变化 电路设计 程序设计 1 int bright_number = 0; //此变量用来表示LED的亮度 2 int bright_gap = 5; ...
- P4454 [CQOI2018]破解D-H协议
链接 这题并不难只是需要把题读懂 - By ShadderLeave 一句话题意 给定两个数 \(p\)和\(g\),有\(t\)组询问,每组询问给出\(A\)和\(B\) 其中 A = \(g^a ...
- RHSA-2018:0395-重要: 内核 安全和BUG修复更新(需要重启、本地提权、代码执行)
[root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 修复命令: 使用root账号登陆She ...
- mongoose 查询数据属性为数组,且包含某个值的方法
mongoose在创建schema的时候有些属性需要设置为数组类型,比如商品图片.商品标签.不同尺寸.价格等. 那么怎么查询具有某个标签的商品了,下面记录一下两种情况: 查询具有'vue'标签的文章 ...
- 多测师讲解_ 高级自动化测试selenium_001基本学习
高级自动化测试python+selenium教程手册 --高级讲师肖sir 第 1 章webdriver 环境搭建好了,我们正式学习 selenium 的 webdriver 框架,它不像 QTP 之 ...
- 如何从0到1的构建一款Java数据生成器-第一章
前提 某天晚上老夫在神游时,想起白天公司同事说起的问题,这老表抱怨使用mysql生成大批的随机测试数据太过麻烦,问大家有没有好的工具推荐,老夫对这种事情当然不关心,毕竟我也不知道. 秉承着不懂就要问, ...
- spring boot:单文件上传/多文件上传/表单中多个文件域上传(spring boot 2.3.2)
一,表单中有多个文件域时如何实现说明和文件的对应? 1,说明和文件对应 文件上传页面中,如果有多个文件域又有多个相对应的文件说明时, 文件和说明如何对应? 我们在表单中给对应的file变量和text变 ...
- Python-selenium显示等待
#coding=utf-8 from selenium import webdriver from selenium.webdriver.common.by import By from seleni ...
- 【API管理 APIM】APIM中如何配置使用URL路径的方式传递参数(如由test.htm?name=xxx 变为test\xxx)
问题描述 在默认的URL传递参数中,我们使用的是https://test01.azure-api.cn/echo/resource?param1=sample¶m2=testname这 ...
- C# 面试前的准备_基础知识点的回顾_02
1.数据库的范式 这算入门问题了吧,但凡是个数据库类的,都得问吧, 但我们在回答的时候开始背书啦 第一范式(1NF)无重复的列 第二范式(2NF)属性完全依赖于主键 [ 消除部分子函数依赖 ] 第三范 ...