day84:luffy:优惠活动策略&用户认证&购物车商品的勾选/结算
目录
1.课程列表页活动和真实价格计算
既然提到了活动,那与之对应的肯定是优惠策略、优惠活动等等。
所以我们要为优惠活动策略单独建立表结构
1.优惠活动策略的model表结构
class CourseDiscountType(BaseModel):
"""课程优惠类型"""
name = models.CharField(max_length=32, verbose_name="优惠类型名称")
remark = models.CharField(max_length=250, blank=True, null=True, verbose_name="备注信息") class Meta:
db_table = "ly_course_discount_type"
verbose_name = "课程优惠类型"
verbose_name_plural = "课程优惠类型" def __str__(self):
return "%s" % (self.name) class CourseDiscount(BaseModel):
"""课程优惠模型"""
discount_type = models.ForeignKey("CourseDiscountType", on_delete=models.CASCADE, related_name='coursediscounts', verbose_name="优惠类型")
condition = models.IntegerField(blank=True, default=0, verbose_name="满足优惠的价格条件",help_text="设置参与优惠的价格门槛,表示商品必须在xx价格以上的时候才参与优惠活动,<br>如果不填,则不设置门槛") #因为有的课程不足100,你减免100,还亏钱了
sale = models.TextField(verbose_name="优惠公式",blank=True,null=True, help_text="""
不填表示免费;<br>
*号开头表示折扣价,例如*0.82表示八二折;<br>
-号开头则表示减免,例如-20表示原价-20;<br>
如果需要表示满减,则需要使用 原价-优惠价格,例如表示课程价格大于100,优惠10;大于200,优惠25,格式如下:<br>
满100-10<br>
满200-25<br>
""") class Meta:
db_table = "ly_course_discount"
verbose_name = "价格优惠策略"
verbose_name_plural = "价格优惠策略" def __str__(self):
return "价格优惠:%s,优惠条件:%s,优惠值:%s" % (self.discount_type.name, self.condition, self.sale) class Activity(BaseModel):
"""优惠活动"""
name = models.CharField(max_length=150, verbose_name="活动名称")
start_time = models.DateTimeField(verbose_name="优惠策略的开始时间")
end_time = models.DateTimeField(verbose_name="优惠策略的结束时间")
remark = models.CharField(max_length=250, blank=True, null=True, verbose_name="备注信息") class Meta:
db_table = "ly_activity"
verbose_name="商品活动"
verbose_name_plural="商品活动" def __str__(self):
return self.name class CoursePriceDiscount(BaseModel):
"""课程与优惠策略的关系表"""
course = models.ForeignKey("Course",on_delete=models.CASCADE, related_name="activeprices",verbose_name="课程")
active = models.ForeignKey("Activity",on_delete=models.DO_NOTHING, related_name="activecourses",verbose_name="活动")
discount = models.ForeignKey("CourseDiscount",on_delete=models.CASCADE,related_name="discountcourse",verbose_name="优惠折扣") class Meta:
db_table = "ly_course_price_dicount"
verbose_name="课程与优惠策略的关系表"
verbose_name_plural="课程与优惠策略的关系表" def __str__(self):
return "课程:%s,优惠活动: %s,开始时间:%s,结束时间:%s" % (self.course.name, self.active.name, self.active.start_time,self.active.end_time)
优惠活动策略的表结构设计
2.课程列表页显示优惠类型名称
1.course/models.py
在模型类中写入discount_name 让课程列表页页面显示优惠类型名称
class Course: def activity(self):
import datetime
now = datetime.datetime.now() # 获取课程参加的活动名称
activity_list = self.activeprices.filter(is_show=True, is_deleted=False, active__start_time__lte=now, active__end_time__gte=now)
return activity_list # 优惠类型名称
def discount_name(self):
dis_name = ''
a = self.activity()
if a:
discount_n_list = []
for i in a:
# 获取课程的折扣类型名称
discount_n = i.discount.discount_type.name
discount_n_list.append(discount_n)
dis_name = discount_n_list[0] return dis_name
2.course/serializers.py
序列化器加入该字段
class CourseModelSerializer:
field = [,discount_name]
class CourseDetailModelSerializer:
field = [,discount_name]
3.drf测试:course/courses
3.课程列表页显示真实价格
1.dev.py
USE_TZ = False # 修改时区
2.course/models.py
class Course:
def real_price(self):
price = float(self.price) # 获取真实价格
r_price = price a = self.activity() # 获取课程对应的活动名称
if a: # 如果课程参加了活动
sale = a[0].discount.sale # 查看活动对应的优惠公式
condition_price = a[0].discount.condition # 查看活动对应的满足优惠的价格条件
# 限时免费
if not sale.strip():
r_price = 0 # 限时折扣 *0.5
elif '*' in sale.strip():
if price >= condition_price:
_, d = sale.split('*')
r_price = price * float(d)
# 限时减免 -100
elif sale.strip().startswith('-'):
if price >= condition_price:
_, d = sale.split('-')
r_price = price - float(d)
# 满减 满100-15
elif '满' in sale:
if price >= condition_price: # 只有价格满足优惠条件价格才能满减
l1 = sale.split('\r\n')
dis_list = [] #10 50 25
for i in l1:
a, b = i[1:].split('-') # 当商品价格(400) 满足100-200-300 应该选择满300那个优惠
if price >= float(a):
dis_list.append(float(b)) max_dis = max(dis_list) # 取到最大的那个满减优惠
r_price = price - max_dis # 原价格-满减价格=真实价格 return r_price
3.course/serializers.py
class CourseModelSerializer:
field = [,discount_name,real_price]
class CourseDetailModelSerializer:
field = [,discount_name,real_price]
4.drf测试:course/courses
4.将优惠类型名称和真实价格显示到前端页面上
1.课程列表页前端
<!-- Course.vue -->
<div class="pay-box">
<span class="discount-type" v-if="course.discount_name">{{course.discount_name}}</span>
<span class="discount-price">¥{{course.real_price}}元</span>
<span class="original-price" v-if="course.discount_name">原价:{{course.price}}元</span>
<span class="buy-now">立即购买</span>
</div>
2.课程详情页前端
<!-- Detail.vue -->
<div class="sale-time">
<p class="sale-type">{{course_data.discount_name}}</p>
<p class="expire">距离结束:仅剩 567 天 12小时 52分 <span class="second">32</span> 秒</p>
</div>
<p class="course-price">
<span>活动价</span>
<span class="discount">¥{{course_data.real_price}}</span>
<span class="original">¥{{course_data.price}}</span>
</p>
5.课程列表页显示具体结束时间
1.course/models.py
class Course:
def left_time(self):
import datetime
now = datetime.datetime.now().timestamp() # 获取当前时间戳
left_t = 0
a = self.activity() # 获取当前课程参加的活动
if a: # 如果当前课程有参加活动
end_time = a[0].active.end_time.timestamp() # 获取当前课程活动的结束时间
left_t = end_time - now # 剩余时间=结束时间-当前时间 return left_t
2.序列化器放入left_time
course/serializers.py
class CourseDetailModelSerializer:
field = [,discount_name,real_price,left_time]
3.前端渲染距离结束时间
Detail.vue
<!-- html -->
<div class="sale-time">
<p class="sale-type">{{course_data.discount_name}}</p>
<p class="expire">距离结束:仅剩 {{course_data.left_time/60/60/24 | pInt}}天 {{course_data.left_time/60/60 % 24| pInt}}小时 {{course_data.left_time/60 % 60 | pInt}}分 <span class="second">{{course_data.left_time % 60 | pInt}}</span> 秒</p>
</div>
在js部分需要设置一个定时器,让时间能够一直减1s
Detail.vue
get_course_data(){
this.$axios.get(`${this.$settings.Host}/course/detail/${this.course_id}/`)
.then((res)=>{
//console.log(res.data);
this.course_data = res.data;
this.playerOptions.sources[0].src = res.data.course_video
this.playerOptions.poster = res.data.course_img // 设置计时器
setInterval(()=>{
this.course_data.left_time--; },1000) })
},
补充:让页面可以显示出0x分0x秒的效果→过滤器
Detail.vue
<!-- Detail.vue--html -->
<p class="expire">距离结束:仅剩 {{course_data.left_time/60/60/24 | pInt}}天
{{course_data.left_time/60/60 % 24| pInt}}小时
{{course_data.left_time/60 % 60 | pInt}}分
<span class="second">{{course_data.left_time % 60 | pInt}}</span> 秒</p>
// Detail.vue--js
filters:{
pInt(val){
let a = parseInt(val);
if (a < 10){
a = `0${a}`;
}
return a
}
}
2.添加购物车/查看购物车的用户认证
为视图添加IsAuthenticated用户认证
# cart/views.py
class AddCartView: # 请求头必须携带着token
permission_classes = [IsAuthenticated,] # 添加用户认证
def add:
user_id = request.user.id # 获取真实的用户id
'''
request = user_obj
'''
drf接口: cart/add_cart 获取不到数据,因为加了IsAuthenticated认证
添加购物车时需要携带token 否则不能添加购物车
// Detail.vue 添加购物车
methods: { addCart(){ let token = localStorage.token || sessionStorage.token; if (token){
this.$axios.post(`${this.$settings.Host}/users/verify/`,{
token:token,
}).then((res)=>{
this.$axios.post(`${this.$settings.Host}/cart/add_cart/`,{
course_id:this.course_id,
},{
// 向后端提交数据需要加上header项,将token也要提交过去
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.$message.success(res.data.msg);
console.log('>>>>>',this.$store)
this.$store.commit('add_cart', res.data.cart_length) ;
console.log(this.$store.state);
}) }).catch((error)=>{
......
}) } else {
......
})
} },
查看购物车时需要携带token 否则不能添加购物车
// Cart.vue 购物车页面
created() {
let token = sessionStorage.token || localStorage.token;
if (token){ this.$axios.get(`${this.$settings.Host}/cart/add_cart/`,{
// 查看购物车页面也要携带token 否则不能查看
headers:{
'Authorization':'jwt ' + token
}
})
.then((res)=>{
this.cart_data_list = res.data.cart_data_list
})
.catch((error)=>{
this.$message.error(error.response.data.msg);
})
3.购物车商品价格的勾选/结算
1.每个课程的真实价格显示到购物车页面上
# cart/views.py class AddCartView:
def cart_list(self, request):
......
cart_data_list.append({
......
'real_price': course_obj.real_price(),
......
})
......
<!-- cartitem.vue -->
<div class="cart_column column_4">¥{{cart.real_price}}</div>
2.勾选/非勾选应在redis中实时存储-后端接口
# cart/views.py
class AddCartView:
def change_select(self, request): # 拿到课程id
course_id = request.data.get('course_id') # 校验课程id合法性
try:
models.Course.objects.get(id=course_id)
except:
return Response({'msg': '课程不存在,不要乱搞!'}, status=status.HTTP_400_BAD_REQUEST) # 拿到user_id
user_id = request.user.id # 去redis数据库:cart
conn = get_redis_connection('cart') # redis存数据-用集合存:用户id:勾选课程id
conn.sadd('selected_cart_%s' % user_id, course_id) return Response({'msg':'勾选成功'}) def cancel_select(self, request):
course_id = request.data.get('course_id') try:
models.Course.objects.get(id=course_id)
except:
return Response({'msg': '课程不存在,不要乱搞!'}, status=status.HTTP_400_BAD_REQUEST) user_id = request.user.id
conn = get_redis_connection('cart') # 1:{1,3}
conn.srem('selected_cart_%s' % user_id, course_id) return Response({'msg': '恭喜你!少花钱了,但是你真的不学习了吗!'})
为两个函数配置路由
# cart/urls.py
from django.urls import path,re_path
from . import views urlpatterns = [
path('add_cart/', views.AddCartView.as_view({'post':'add',
'get':'cart_list',
'patch':'change_select',
'put':'cancel_select'})) # 不同的请求方法走不同函数 ]
3.勾选/非勾选应该在前端页面重新计算价格
<!-- 当用户点击前面的勾选框时,会改变selected的值
近而会被watch监听到
在监听中 无论是选中还是取消选中都会触发父级标签重新计算价格的动作(this.$emit) -->
<el-checkbox class="my_el_checkbox" v-model="cart.selected"></el-checkbox>
// Cartitem.vue
watch:{ 'cart.selected':function (){ // 添加选中
let token = localStorage.token || sessionStorage.token;
if (this.cart.selected){
this.$axios.patch(`${this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.$message.success(res.data.msg);
this.$emit('cal_t_p') // 触发cart组件计算商品总价格的方法
}).catch((error)=>{
this.$message.error(res.data.msg);
})
}
else { // 取消选中
this.$axios.put(`${this.$settings.Host}/cart/add_cart/`,{
course_id: this.cart.course_id, },{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.$message.success(res.data.msg);
this.$emit('cal_t_p') // 触发cart组件计算商品总价格的方法
}).catch((error)=>{
this.$message.error(res.data.msg);
})
}
}, }
触发Cart组件(父组件)的计算商品总价格的方法
<!-- Cart.vue -->
<div class="cart_course_list"> <CartItem v-for="(value,index) in cart_data_list" :key="index" :cart="value" @cal_t_p="cal_total_price"></CartItem> </div>
// Cart.vue
methods:{
cal_total_price(){ let t_price = 0
this.cart_data_list.forEach((v,k)=>{ // v是值 k是索引
if (v.selected){
t_price += v.real_price
}
})
this.total_price = t_price }
}
4.购物车列表显示-后端接口
# cart/views.py
def cart_list(self, request): user_id = request.user.id conn = get_redis_connection('cart') conn.delete('selected_cart_%s' % user_id)
ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'}
cart_data_list = [] try:
for cid, eid in ret.items():
course_id = cid.decode('utf-8') # 取出用户购物车里的课程id(redis中存着呢)
expire_id = eid.decode('utf-8') # 取出用户购物车里的有效期id(redis中存着呢) course_obj = models.Course.objects.get(id=course_id) # 根据课程id,通过ORM查询得到课程对象,在下面就可以通过课程对象.字段 取到对应课程的参数信息 cart_data_list.append({
'course_id': course_obj.id,
'name': course_obj.name,
'course_img': constants.SERVER_ADDR + course_obj.course_img.url,
'price': course_obj.price,
'real_price': course_obj.real_price(),
'expire_id': expire_id,
'selected': False, # 默认没有勾选
})
except Exception:
logger.error('获取购物车数据失败')
return Response({'msg': '后台数据库出问题了,请联系管理员'}, status=status.HTTP_507_INSUFFICIENT_STORAGE) return Response({'msg': 'xxx', 'cart_data_list': cart_data_list})
BUG:勾选两个课程 刷新页面 redis中仍然存着两个课程id
# cart/views.py
def cart_list(self, request):
......
conn = get_redis_connection('cart')
# 用户刷新页面时,从redis中删除用户对应的课程id
conn.delete('selected_cart_%s' % user_id)
ret = conn.hgetall('cart_%s' % user_id)
......
day84:luffy:优惠活动策略&用户认证&购物车商品的勾选/结算的更多相关文章
- 第八十二篇:Vue购物车(三) 实现全选功能
好家伙, 继续完善购物车相应功能 1.如何实现全选和反全选 1.1.全选框的状态显示(父传子) 来一波合理分析: 在页面中,有三个商品中 三个商品中的第二个未选择, 我么使用一个计算属性(fullSt ...
- redis介绍及在购物车项目中的应用,用户认证
1.redis 2.购物车的构建 api结构: models.py(创建完后自行添加数据) from django.db import models from django.contrib.conte ...
- python 全栈开发,Day104(DRF用户认证,结算中心,django-redis)
考试第二部分:MySQL数据库 6. MySQL中char和varchar的区别(1分) char是定长,varchar是变长. char的查询速度比varchar要快. 7. MySQL中va ...
- 用户认证授权和Shiro入门
1.权限管理基础(认证和授权): 前言 本文主要讲解的知识点有以下: 权限管理的基础知识 模型 粗粒度和细粒度的概念 回顾URL拦截的实现 Shiro的介绍与简单入门 一.Shiro基础知识 在学习S ...
- Cookie、Session、Token那点事儿和前后端分离之JWT用户认证
(两篇文章转自:https://www.jianshu.com/p/bd1be47a16c1:https://www.jianshu.com/p/180a870a308a) 什么是Cookie? Co ...
- Nodejs之MEAN栈开发(八)---- 用户认证与会话管理详解
用户认证与会话管理基本上是每个网站必备的一个功能.在Asp.net下做的比较多,大体的思路都是先根据用户提供的用户名和密码到数据库找到用户信息,然后校验,校验成功之后记住用户的姓名和相关信息,这个信息 ...
- Django 中的用户认证
Django 自带一个用户认证系统,这个系统处理用户帐户.组.权限和基于 cookie 的 会话.本文说明这个系统是如何工作的. 概览 认证系统由以下部分组成: 用户 权限:控制用户进否可以执行某项任 ...
- apache的用户认证
1. 限制用户访问的方式: 1. 限制访问服务的客户端主机 2. 需要用户名和密码 2. 行为用户验证需要两步: 1. 创建一个包含用户名和密码的文件 2. 服务器上的哪些资源需要保护,哪些用户可以进 ...
- MySQL用户认证及权限grant-revoke
一.MySQL用户认证: 登录并不属于访问控制机制,而属于用户身份识别和认证: 1.用户名-user 2.密码-password 3.登录mysqld主机-host 实现用户登录MySQL,建立连接. ...
- 社交系统ThinkSNS+ APP更新至V0.8.3---新增打赏、用户认证
一.ThinkSNS简介 目前社交系统ThinkSNS(简称TS)有两个版本并行: ThinkSNS V4----最新版本ThinkSNS V4.6.1,第一次发布时间为2015年7月15日,最近更新 ...
随机推荐
- python3GUI--仿做一个网易云音乐By:PyQt5(附下载地址)
@ 目录 一.前言 二.展示-主界面 1.静图1 2.静图2 3.静图3 3.静图3 4.动图1 三.展示-登录界面 1.静图1 2.静图2 5.动图2 四.展示-系统托盘 五.UI设计记录 1.UI ...
- 面向对象ooDay9
精华笔记: 多态:多种形态 同一个对象被造型为不同的类型时,有不同的功能-------所有对象都是多态的(明天总结详细讲) 对象的多态:水.我.你...... 同一类型的引用在指向不同的对象时,有不同 ...
- 在gibhub上传本地项目代码(新手入门)
一.首先注册github账号 地址:https://github.com/ 二.其次下载安装git工具 地址:https://gitforwindows.org/ 直接进入安装,这里就不多做介绍 三. ...
- oracle 将以逗号分隔的列拆成多行的的方法
原表如下 select * from hs_acct.custattach a where a.client_id='888827395'; 将列拆分成多行的语句 select * from ( -- ...
- Jenkins系列(1)-离线安装插件
插件地址:http://updates.jenkins-ci.org/download/plugins/
- gensim
官方文档: https://radimrehurek.com/gensim/models/word2vec.html 1.训练模型定义 from gensim.models import word2v ...
- termux搭建服务器方式
pkg install vim apt update 安装debian系统apt install proot-distroproot-distro install debianproot-distro ...
- ssh原理及应用
SSH原理与运用(一):远程登录 SSH原理与运用(一):远程登录 SSH原理与运用(二):远程操作与端口转发 SSH原理与运用(二):远程操作与端口转发 mitm应用: python开源三方库:ss ...
- win10 自带输入法设置小鹤双拼
1.创建bat文件: 小鹤双拼.bat 2.编辑小鹤双拼.bat 添加内容: reg add HKEY_CURRENT_USER\Software\Microsoft\InputMethod\Sett ...
- element表格样式的修改
修改表格头部背景 .el-table th{ background: #f00; } 修改表格行背景 .el-table tr{ background: #f00; } 修改斑马线表格的背景 .el- ...