后端提供修改勾选状态的接口
视图代码:
from rest_framework.viewsets import ViewSet
from rest_framework.permissions import IsAuthenticated
from courses.models import Course
from rest_framework.response import Response
from rest_framework import status
from django_redis import get_redis_connection
from django.conf import settings
import logging
log = logging.getLogger("django")
from rest_framework.decorators import action
class CartAPIView(ViewSet):
"""读取多条数据"""
# permission_classes = [IsAuthenticated, ]
@action(methods=["POST"],detail=False)
def add_course(self,request):
"""添加商品到购物车中"""
"""获取商品ID,用户ID,有效期选项,购物车勾选状态"""""
user_id = request.user.id
course_id = request.data.get("course_id")
is_selected = True # 勾选状态
expire = 0 # 默认为0,0表示永久有效
# 查找和验证数据
try:
course = Course.objects.get(is_delete=False, is_show=True, pk=course_id)
except:
return Response({"message": "对不起,您购买的商品不存在!"}, status=status.HTTP_400_BAD_REQUEST)
# 添加数据到购物车中
try:
redis = get_redis_connection("cart")
pip = redis.pipeline()
pip.multi()
# 保存商品信息到购物车中
pip.hset("cart_%s" % user_id, course_id, expire )
# 保存商品勾选状态到购物车中
pip.sadd("selected_%s" % user_id, course_id )
# 执行管道中命令
pip.execute()
# 获取当前用户的购物车中商品的数量
total = redis.hlen("cart_%s" % user_id)
except:
log.error("购物车商品添加失败,redis操作出错!")
return Response({"message":"商品添加失败,请联系客服工作人员!"},status=status.HTTP_507_INSUFFICIENT_STORAGE)
# 返回购物车的状态信息
return Response({"message":"添加商品成功!","total":total},status=status.HTTP_201_CREATED)
@action(methods=["get"],detail=False)
def get(self,request):
"""购物车商品列表"""
user_id = 1 # request.user.id
redis = get_redis_connection("cart")
# 从hash里面读取购物车基本信息
cart_course_list = redis.hgetall("cart_%s" % user_id)
# 从set集合中查询所有已经勾选的商品ID
cart_selected_list = redis.smembers("selected_%s" % user_id)
# 如果提取到的商品购物车信息为空!,则直接返回空列表
if len(cart_course_list) < 1:
return Response([])
data = []
# 苟泽我们就要组装商品课程新返回给客户端
for course_bytes, expire_bytes in cart_course_list.items():
# print("课程ID", course_bytes)
# print("有效期", expire_bytes)
course_id = course_bytes.decode()
try:
course = Course.objects.get(pk=course_id)
except Course.DoesNotExist:
# 当前商品不存在!
pass
data.append({
"id": course_id,
"name": course.name,
"course_img": settings.DOMAIL_IMAGE_URL + course.course_img.url,
"price": course.price,
"is_selected": True if course_bytes in cart_selected_list else False
})
return Response(data)
def put(self,request):
"""切换购物车中的商品勾选状态"""
# 接受数据user_id,course_id,is_selected
user_id = request.user.id
course_id = request.data.get("course_id")
is_selected = bool( request.data.get("is_selected") )
# 校验数据
try:
course = Course.objects.get(is_delete=False, is_show=True, pk=course_id)
except:
return Response({"message": "对不起,您购买的商品不存在!"}, status=status.HTTP_400_BAD_REQUEST)
# 更新购物车中的商品ID
try:
redis = get_redis_connection("cart")
if is_selected:
# 网redis的集合中添加执行商品课程ID
redis.sadd("selected_%s" % user_id, course_id)
else:
# 网redis的集合中删除执行商品课程ID
redis.srem("selected_%s" % user_id, course_id)
except:
return Response({"message":"购物车数据操作有误"},status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({"message":"切换勾选状态成功!"})
# def delete(self,request):
# pass
#
CartItem.vue
<template>
<div class="cart_item">
<div class="cart_column column_1">
<el-checkbox class="my_el_checkbox" v-model="cart.is_selected">el-checkbox>
div>
<div class="cart_column column_2">
<img :src="cart.course_img" alt="">
<span><router-link :to="`/course/${cart.id}`">{{cart.name}}router-link>span>
div>
<div class="cart_column column_3">
<el-select v-model="expire" size="mini" placeholder="请选择购买有效期" class="my_el_select">
<el-option label="1个月有效" value="30" key="30">el-option>
<el-option label="2个月有效" value="60" key="60">el-option>
<el-option label="3个月有效" value="90" key="90">el-option>
<el-option label="永久有效" value="10000" key="10000">el-option>
el-select>
div>
<div class="cart_column column_4">¥{{cart.price.toFixed(2)}}div>
<div class="cart_column column_4">删除div>
div>
template>
<script>
export default {
name: "CartItem",
props: ["cart"],
watch:{
"cart.is_selected": function(value){
this.selectedChange()
}
},
data() {
return {
checked: false,
expire: "1个月有效",
}
},
methods:{
selectedChange(){
let course_id = this.cart.id;
let is_selected = this.cart.is_selected
this.$axios.patch(`${this.$settings.Host}/cart/course/patch/`,{
course_id, // course_id: course_id
is_selected: Boolean(is_selected),
}).then(response=>{
this.$message("切换商品勾选状态成功");
}).catch(error=>{
console.log("对不起,切换勾选状态失败!");
})
}.
}
};
script>
<style scoped>
...
style>
创建课程有效期选项模型courses/models.py
class CourseExpire(BaseModel):
"""课程有效期的勾选"""
course = models.ForeignKey("Course", related_name='course_expire', on_delete=models.CASCADE, verbose_name="课程名称")
expire_time = models.IntegerField(verbose_name="有效期数值", null=True,blank=True)
expire_text = models.CharField(max_length=150, verbose_name="有效期提示文本",null=True,blank=True)
price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程价格",default=0)
class Meta:
db_table = "ly_course_expire"
verbose_name = "课程有效期选项"
verbose_name_plural = "课程有效期选项"
def __str__(self):
return "课程:%s,有效期:%s,价格:%s" % (self.course, self.expire_text, self.price)
数据迁移
python manage.py makemigrations
pythonm manage.py migrate
在xadmin中添加测试并显示给客户端。
adminx.py
from .models import CourseExpire
class CourseExpireModelAdmin(object):
"""课程有效期模型管理类"""
pass
xadmin.site.register(CourseExpire, CourseExpireModelAdmin)
在原有的购物车商品信息接口中新增返回商品的有效期价格选项信息.
购物车课程列表中,新增返回字段
cart/views.py
from django.shortcuts import render
from rest_framework.viewsets import ViewSet
from rest_framework.permissions import IsAuthenticated
from courses.models import Course
from rest_framework.response import Response
from rest_framework import status
from django_redis import get_redis_connection
from django.conf import settings
import logging
log = logging.getLogger("django")
from rest_framework.decorators import action
class CartAPIView(ViewSet):
"""读取多条数据"""
# permission_classes = [IsAuthenticated, ]
@action(methods=["POST"],detail=False)
def add_course(self,request):
"""添加商品到购物车中"""
"""获取商品ID,用户ID,有效期选项,购物车勾选状态"""""
user_id = request.user.id
course_id = request.data.get("course_id")
is_selected = True # 勾选状态
expire = 0 # 默认为0,0表示永久有效
# 查找和验证数据
try:
course = Course.objects.get(is_delete=False, is_show=True, pk=course_id)
except:
return Response({"message": "对不起,您购买的商品不存在!"}, status=status.HTTP_400_BAD_REQUEST)
# 添加数据到购物车中
try:
redis = get_redis_connection("cart")
pip = redis.pipeline()
pip.multi()
# 保存商品信息到购物车中
pip.hset("cart_%s" % user_id, course_id, expire )
# 保存商品勾选状态到购物车中
pip.sadd("selected_%s" % user_id, course_id )
# 执行管道中命令
pip.execute()
# 获取当前用户的购物车中商品的数量
total = redis.hlen("cart_%s" % user_id)
except:
log.error("购物车商品添加失败,redis操作出错!")
return Response({"message":"商品添加失败,请联系客服工作人员!"},status=status.HTTP_507_INSUFFICIENT_STORAGE)
# 返回购物车的状态信息
return Response({"message":"添加商品成功!","total":total},status=status.HTTP_201_CREATED)
@action(methods=["get"],detail=False)
def get(self,request):
"""购物车商品列表"""
user_id = 1 # request.user.id
redis = get_redis_connection("cart")
# 从hash里面读取购物车基本信息
cart_course_list = redis.hgetall("cart_%s" % user_id)
# 从set集合中查询所有已经勾选的商品ID
cart_selected_list = redis.smembers("selected_%s" % user_id)
# 如果提取到的商品购物车信息为空!,则直接返回空列表
if len(cart_course_list) < 1:
return Response([])
data = []
# 苟泽我们就要组装商品课程新返回给客户端
for course_bytes, expire_bytes in cart_course_list.items():
# print("课程ID", course_bytes)
# print("有效期", expire_bytes)
course_id = course_bytes.decode()
try:
course = Course.objects.get(pk=course_id)
except Course.DoesNotExist:
# 当前商品不存在!
pass
data.append({
"id": course_id,
"name": course.name,
"course_img": settings.DOMAIL_IMAGE_URL + course.course_img.url,
"price": course.price,
"is_selected": True if course_bytes in cart_selected_list else False,
"expire_list": course.expire_list,
})
return Response(data)
@action(methods=["patch"],detail=False)
def patch(self,request):
"""切换购物车中的商品勾选状态"""
# 接受数据user_id,course_id,is_selected
user_id = 1 # request.user.id
course_id = request.data.get("course_id")
is_selected = bool( request.data.get("is_selected") )
# 校验数据
try:
course = Course.objects.get(is_delete=False, is_show=True, pk=course_id)
except:
return Response({"message": "对不起,您购买的商品不存在!"}, status=status.HTTP_400_BAD_REQUEST)
# 更新购物车中的商品ID
try:
redis = get_redis_connection("cart")
if is_selected:
# 网redis的集合中添加执行商品课程ID
redis.sadd("selected_%s" % user_id, course_id)
else:
# 网redis的集合中删除执行商品课程ID
redis.srem("selected_%s" % user_id, course_id)
except:
return Response({"message":"购物车数据操作有误"},status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response({"message":"切换勾选状态成功!"})
# def delete(self,request):
# pass
#
在课程模型中,新增自定义数据字段,
courses/models.py
from ckeditor_uploader.fields import RichTextUploadingField
from django.conf import settings
class Course(BaseModel):
"""
专题课程
"""
...
@property
def expire_list(self):
data = []
expire_list = self.course_expire.all().order_by("expire_time")
for item in expire_list:
data.append({
"expire_text": item.expire_text,
"expire_time": item.expire_time,
"price": item.price,
})
# 永久价格选项
if self.price > 0:
data.append({
"expire_text": "永久有效",
"expire_time": -1,
"price": self.price
})
return data
前端展示课程的有效期选项.
CartItem.vue
<template>
<div class="cart_item">
<div class="cart_column column_1">
<el-checkbox class="my_el_checkbox" v-model="cart.is_selected">el-checkbox>
div>
<div class="cart_column column_2">
<img :src="cart.course_img" alt="">
<span><router-link :to="`/course/${cart.id}`">{{cart.name}}router-link>span>
div>
<div class="cart_column column_3">
<el-select v-model="expire" size="mini" placeholder="请选择购买有效期" class="my_el_select">
<el-option v-for="item in cart.expire_list" :label="item.expire_text" :value="item.expire_time" :key="item.expire_time">el-option>
el-select>
div>
<div class="cart_column column_4">¥{{cart.price.toFixed(2)}}div>
<div class="cart_column column_4">删除div>
div>
template>
<script>
export default {
name: "CartItem",
props: ["cart"],
watch:{
"cart.is_selected": function(value){
this.selectedChange()
}
},
data() {
return {
checked: false,
expire: -1,
}
},
methods:{
selectedChange(){
let course_id = this.cart.id;
let is_selected = this.cart.is_selected;
this.$axios.patch(`${this.$settings.Host}/cart/course/patch/`,{
course_id, // course_id: course_id
is_selected: Boolean(is_selected),
}).then(response=>{
this.$message("切换商品勾选状态成功");
}).catch(error=>{
console.log("对不起,切换勾选状态失败!");
})
},
}
};
script>
<style scoped>
...
style>
切换课程时,切换有效期对应的价格
CartItem.vue
<template>
<div class="cart_item">
<div class="cart_column column_1">
<el-checkbox class="my_el_checkbox" v-model="cart.is_selected">el-checkbox>
div>
<div class="cart_column column_2">
<img :src="cart.course_img" alt="">
<span><router-link :to="`/course/${cart.id}`">{{cart.name}}router-link>span>
div>
<div class="cart_column column_3">
<el-select v-model="expire" size="mini" placeholder="请选择购买有效期" class="my_el_select">
<el-option v-for="item in cart.expire_list" :label="item.expire_text" :value="item.expire_time" :key="item.expire_time">el-option>
el-select>
div>
<div class="cart_column column_4">¥{{cart.price.toFixed(2)}}div>
<div class="cart_column column_4">删除div>
div>
template>
<script>
export default {
name: "CartItem",
props: ["cart"],
watch:{
"cart.is_selected": function(value){
this.selectedChange()
},
"expire":function(value){
console.log(value);
this.cart.expire_list.forEach((item,key)=>{
if(item.expire_time == value){
this.cart.price = item.price;
}
})
}
},
data() {
return {
checked: false,
expire: -1,
}
},
methods:{
selectedChange(){
let course_id = this.cart.id;
let is_selected = this.cart.is_selected;
this.$axios.patch(`${this.$settings.Host}/cart/course/patch/`,{
course_id, // course_id: course_id
is_selected: Boolean(is_selected),
}).then(response=>{
this.$message("切换商品勾选状态成功");
}).catch(error=>{
console.log("对不起,切换勾选状态失败!");
})
},
}
};
script>
价格优惠活动类型名称: 限时免费, 限时折扣, 限时减免, 积分抵扣, 满减, 优惠券
公式:
限时免费 原价-原价
限时折扣 原价*0.8
限时减免 原价-减免价
满减 原价-(满减计算后换算价格)
积分抵扣 总价-(积分计算后换算价格) ->> 积分换算比率
优惠券 总价-优惠券价格 -->> 优惠券
模型代码:
courses/models.py
class CourseExpire(BaseModel):
course = models.ForeignKey("Course", related_name='courseexpire', on_delete=models.CASCADE, verbose_name="课程ID")
expire = models.CharField(max_length=100,verbose_name="课程有效期(月)",help_text="课程有效期")
price = models.DecimalField(max_digits=6,decimal_places=2, verbose_name="课程价格",default=0)
class Meta:
db_table = "ly_course_expire"
verbose_name = "课程与有效期"
verbose_name_plural = verbose_name
def __str__(self):
return "%s" % (self.course)
"""价格相关的模型"""
class PriceDiscountType(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_price_discount_type"
verbose_name = "课程优惠类型"
verbose_name_plural = "课程优惠类型"
def __str__(self):
return "%s" % (self.name)
class PriceDiscount(BaseModel):
"""课程优惠模型"""
discount_type = models.ForeignKey("PriceDiscountType", on_delete=models.CASCADE, related_name='pricediscounts',
verbose_name="优惠类型")
condition = models.IntegerField(blank=True, default=0, verbose_name="满足优惠的价格条件",help_text="设置参与优惠的价格门槛,表示商品必须在xx价格以上的时候才参与优惠活动,
如果不填,则不设置门槛")
sale = models.TextField(verbose_name="优惠公式",blank=True,null=True, help_text="""
不填表示免费;
*号开头表示折扣价,例如*0.82表示八二折;
-号开头则表示减免,例如-20表示原价-20;
如果需要表示满减,则需要使用 原价-优惠价格,例如表示课程价格大于100,优惠10;大于200,优惠20,格式如下:
满100-10
满200-20
""")
class Meta:
db_table = "ly_price_discount"
verbose_name = "价格优惠策略"
verbose_name_plural = "价格优惠策略"
def __str__(self):
return "价格优惠:%s,优惠条件:%s,优惠值:%s" % (self.discount_type.name, self.condition, self.sale)
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("PriceDiscount",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)
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
python manage.py makemigrations
python manage.py migrate
在xadmin中注册模型管理器,courses/adminx.py
代码:
from .models import CourseExpire
class CourseExpireModelAdmin(object):
"""课程与有效期模型管理类"""
pass
xadmin.site.register(CourseExpire, CourseExpireModelAdmin)
from .models import PriceDiscountType
class PriceDiscountTypeModelAdmin(object):
"""价格优惠类型"""
pass
xadmin.site.register(PriceDiscountType, PriceDiscountTypeModelAdmin)
from .models import PriceDiscount
class PriceDiscountModelAdmin(object):
"""价格优惠公式"""
pass
xadmin.site.register(PriceDiscount, PriceDiscountModelAdmin)
from .models import CoursePriceDiscount
class CoursePriceDiscountModelAdmin(object):
"""价格优惠公式"""
pass
xadmin.site.register(CoursePriceDiscount, CoursePriceDiscountModelAdmin)
from .models import Activity
class ActivityModelAdmin(object):
"""商品活动模型"""
pass
xadmin.site.register(Activity, ActivityModelAdmin)
添加测试数据
因为课程的优惠是具有时效性的,所以我们计算价格的时候需要先判断当前优惠是否过期了。
关于时间的判断,那么就需要在项目中配置下时区.
settings/dev.py
,代码:
USE_TZ = False
在课程模型中,新增2个属性方法,分别用于获取当前课程的优惠类型和真实价格, courses/models.py
,代码:
from django.db import models
from ckeditor_uploader.fields import RichTextUploadingField
from django.conf import settings
class Course(BaseModel):
"""
专题课程
"""
...
@property
def discount_name(self):
"""折扣类型"""
course_price_discount_list = self.activeprices.filter(is_show=True,is_delete=False).first()
if course_price_discount_list is None:
"""查找不到当前商品课程参与的活动,则表示没有参加活动,直接返回原价"""
return None
discount = course_price_discount_list.discount.discount_type
return discount.name
def real_price(self):
"""计算课程的真实价格"""
price = float( self.price )
course_price_discount_list = self.activeprices.filter(is_show=True,is_delete=False).first()
if course_price_discount_list is None:
"""查找不到当前商品课程参与的活动,则表示没有参加活动,直接范围原价"""
return price
# 获取当前商品参加的活动,判断商品是否在活动期间
active = course_price_discount_list.active
# 活动开始时间和结束时间
start_time = active.start_time.timestamp()
end_time = active.end_time.timestamp()
from datetime import datetime
current_time = datetime.now().timestamp()
# print( "当前时间:%s,活动开始:%s,活动结束:%s" % (current_time, start_time,end_time) )
# 判断商品是否还在活动有效期内
if current_time <= start_time or current_time >= end_time:
"""不在活动期间"""
return price
"""计算真实价格"""
discount = course_price_discount_list.discount
if discount.sale == "":
"""如果sale不填写,则表示价格免费"""
return 0
elif discount.sale[0] == "*":
"""限时折扣"""
sale = float(discount.sale[1:])
return "%.2f" % (price * sale)
elif discount.sale[0] == '-':
"""限时减免"""
sale = float(discount.sale[1:])
return "%.2f" % (price - sale)
elif discount.sale[0] == "满":
"""限时满减"""
sale = discount.sale
sale = sale.replace("满","")
sale_list = sale.split("\r\n")
data_dict = []
for item in sale_list:
item_list = item.split("-")
if price >= float( item_list[0] ):
data_dict.append( float( item_list[1] ) )
data_dict.sort()
data_dict.reverse()
return "%.2f" % (price - data_dict[0])
# 返回原价格
return price
class Teacher(BaseModel):
"""讲师、导师表"""
role_choices = (
(0, '讲师'),
(1, '导师'),
(2, '班主任'),
)
name = models.CharField(max_length=32, verbose_name="讲师title")
role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="讲师身份")
title = models.CharField(max_length=64, verbose_name="职位、职称")
signature = models.CharField(max_length=255, verbose_name="导师签名", help_text="导师签名", blank=True, null=True)
image = models.ImageField(upload_to="teacher", null=True, verbose_name = "讲师封面")
brief = models.TextField(max_length=1024, verbose_name="讲师描述")
class Meta:
db_table = "ly_teacher"
verbose_name = "讲师导师"
verbose_name_plural = "讲师导师"
def __str__(self):
return "%s" % self.name
class CourseChapter(BaseModel):
"""课程章节"""
course = models.ForeignKey("Course", related_name='coursechapters', on_delete=models.CASCADE, verbose_name="课程名称")
chapter = models.SmallIntegerField(verbose_name="第几章", default=1)
name = models.CharField(max_length=128, verbose_name="章节标题")
summary = models.TextField(verbose_name="章节介绍", blank=True, null=True)
pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True)
class Meta:
db_table = "ly_course_chapter"
verbose_name = "课程章节"
verbose_name_plural = "课程章节"
def __str__(self):
return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)
class CourseLesson(BaseModel):
"""课程课时"""
section_type_choices = (
(0, '文档'),
(1, '练习'),
(2, '视频')
)
chapter = models.ForeignKey("CourseChapter", related_name='coursesections', on_delete=models.CASCADE,verbose_name="课程章节")
name = models.CharField(max_length=128,verbose_name = "课时标题")
orders = models.PositiveSmallIntegerField(verbose_name="课时排序")
section_type = models.SmallIntegerField(default=2, choices=section_type_choices, verbose_name="课时种类")
section_link = models.CharField(max_length=255, blank=True, null=True, verbose_name="课时链接", help_text = "若是video,填vid,若是文档,填link")
duration = models.CharField(verbose_name="视频时长", blank=True, null=True, max_length=32) # 仅在前端展示使用
pub_date = models.DateTimeField(verbose_name="发布时间", auto_now_add=True)
free_trail = models.BooleanField(verbose_name="是否可试看", default=False)
is_show_list = models.BooleanField(verbose_name="是否展示到列表页中", default=False)
# related_name 必须保证在整个项目中具有唯一性,不能和其他的模型中related_name同名,否则django报错!
# ERRORS:
# courses.CourseChapter.course: (fields.E304) Reverse accessor for 'CourseChapter.course' clashes with reverse accessor for 'CourseLesson.course'.
# HINT: Add or change a related_name argument to the definition for 'CourseChapter.course' or 'CourseLesson.course'.
course = models.ForeignKey("Course", related_name='courselessons', on_delete=models.CASCADE, verbose_name="课程名称")
class Meta:
db_table = "ly_course_lesson"
verbose_name = "课程课时"
verbose_name_plural = "课程课时"
def __str__(self):
return "%s-%s" % (self.chapter, self.name)
class CourseExpire(BaseModel):
"""课程有效期的勾选"""
course = models.ForeignKey("Course", related_name='course_expire', on_delete=models.CASCADE, verbose_name="课程名称")
expire_time = models.IntegerField(verbose_name="有效期数值", null=True,blank=True)
expire_text = models.CharField(max_length=150, verbose_name="有效期提示文本",null=True,blank=True)
price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程价格",default=0)
class Meta:
db_table = "ly_course_expire"
verbose_name = "课程有效期选项"
verbose_name_plural = "课程有效期选项"
def __str__(self):
return "课程:%s,有效期:%s,价格:%s" % (self.course, self.expire_text, self.price)
"""价格相关的模型"""
class PriceDiscountType(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_price_discount_type"
verbose_name = "课程优惠类型"
verbose_name_plural = "课程优惠类型"
def __str__(self):
return "%s" % (self.name)
class PriceDiscount(BaseModel):
"""课程优惠模型"""
discount_type = models.ForeignKey("PriceDiscountType", on_delete=models.CASCADE, related_name='pricediscounts',
verbose_name="优惠类型")
condition = models.IntegerField(blank=True, default=0, verbose_name="满足优惠的价格条件",help_text="设置参与优惠的价格门槛,表示商品必须在xx价格以上的时候才参与优惠活动,
如果不填,则不设置门槛")
sale = models.TextField(verbose_name="优惠公式",blank=True,null=True, help_text="""
不填表示免费;
*号开头表示折扣价,例如*0.82表示八二折;
-号开头则表示减免,例如-20表示原价-20;
如果需要表示满减,则需要使用 原价-优惠价格,例如表示课程价格大于100,优惠10;大于200,优惠20,格式如下:
满100-10
满200-20
""")
class Meta:
db_table = "ly_price_discount"
verbose_name = "价格优惠策略"
verbose_name_plural = "价格优惠策略"
def __str__(self):
return "价格优惠:%s,优惠条件:%s,优惠值:%s" % (self.discount_type.name, self.condition, self.sale)
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("PriceDiscount",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)
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
修改序列化器,增加返回字段[优惠类型和课程真实价格]
courses/serializers.py
文件中找到 CourseSerializer
类:
class CourseModelSerializer(serializers.ModelSerializer):
"""课程信息的序列化器"""
teacher = CourseTeacherModelSerializer() # 老师 1 : 多课程
# teacher = CourseTeacherModelSerializer(many=True) # 多对1
class Meta:
model = Course
fields = ("id","name","course_img","students","lessons","pub_lessons","price","real_price","discount_name", "teacher","lesson_list")
判断是否有课程类型,如果有,则显示优惠价格。没有则显示课程原价。
Course.vue
<template>
<div class="course">
<Header>Header>
<div class="main">
<div class="condition">
<ul class="cate-list">
<li class="title">课程分类:li>
<li :class="filter.course_category == 0?'this':''" @click="filter.course_category=0">全部li>
<li :class="filter.course_category == category.id?'this':''" @click="filter.course_category=category.id" v-for="category in category_list" :key="category.id">{{category.name}}li>
ul>
<div class="ordering">
<ul>
<li class="title">筛 选: li>
<li class="default" :class="if_change('id',filter.ordering)" @click="change('id')" >默认li>
<li class="hot" :class="if_change('students',filter.ordering)" @click="change('students')" >人气li>
<li class="price" :class="if_change('price',filter.ordering)" @click="change('price')" >价格li>
ul>
<p class="condition-result">共{{total}}个课程p>
div>
div>
<div class="course-list">
<div class="course-item" v-for="course in course_list" :key="course.id">
<div class="course-image">
<img :src="course.course_img" :alt="course.name">
div>
<div class="course-info">
<h3><router-link :to="`/course/${course.id}`">{{course.name}}router-link> <span><img src="/static/image/avatar1.svg" alt="">{{course.students}}人已加入学习span>h3>
<p class="teather-info">{{course.teacher.name}} {{course.teacher.signature}} {{course.teacher.title}} <span>共{{course.lessons}}课时/{{course.lessons==course.pub_lessons?'更新完成':`已更新${course.pub_lessons}课时`}}span>p>
<ul class="lesson-list">
<li v-for="lesson,key in course.lesson_list" :key="key"><span class="lesson-title">0{{key+1}} | 第{{lesson.chapter}}章:{{lesson.name}}span> <span class="free" v-if="lesson.free_trail">免费span>li>
ul>
<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>
div>
div>
div>
<el-pagination
background
layout="prev, pager, next"
:page-size="filter.size"
:total="total"
@current-change="pageChange"
:hide-on-single-page="true"
>
el-pagination>
div>
<Footer>Footer>
div>
template>
<script>
import Header from "./common/Header"
import Footer from "./common/Footer"
export default {
name: "Course",
data(){
return{
total: 0,
course_list: [],
category_list: [],
filter:{
course_category: 0,
ordering: "-id",
page: 1,
size: 5,
}
}
},
components:{
Header,
Footer,
},
created(){
this.get_course_category();
this.get_course();
},
watch:{
"filter.course_category":function(){
// 当切换分类条件时,因为有些分类没有那么多页的数据,所以我们需要重置页码,否则后端获取不到数据
this.filter.page = 1;
this.get_course();
},
"filter.ordering": function(){
this.get_course();
console.log(this.filter.ordering);
},
"filter.page": function(){
this.get_course();
}
},
methods:{
get_course_category(){
// 获取课程分类信息
this.$axios.get(`${this.$settings.Host}/course/category/`).then(response=>{
this.category_list = response.data;
}).catch(error=>{
console.log(error.response);
});
},
get_course(){
// 组装和判断过滤的参数
let params = {
ordering: this.filter.ordering,
page: this.filter.page,
size: this.filter.size,
};
// 如果分类是0,则表示不进行筛选,查询全部,则不需要传递任何分类参数
if(this.filter.course_category>0){
params.course_category = this.filter.course_category;
}
// 获取课程信息
this.$axios.get(`${this.$settings.Host}/course/`,{
params
}).then(response=>{
// results 是后端的drf使用了分页器返回的数据结果
this.course_list = response.data.results;
this.total = response.data.count
}).catch(error=>{
console.log(error.response);
});
},
if_change(type, ordering){
// type 表示条件
// ordering 表示升降序
if (type == ordering) {
return 'this this_asc';
} else if ('-' + type == ordering) {
return "this this_desc";
}
return "";
},
change(type){
// 从别的条件中切换过来点击默认的
// 从别的条件中切换过来点击默认的
if (type != this.filter.ordering) {
this.filter.ordering = "-" + type;
}
if (type == this.filter.ordering) {
this.filter.ordering = "-" + type;
} else if ('-' + type == this.filter.ordering) {
this.filter.ordering = type;
}
},
pageChange(page){
// 切换页码
this.filter.page = page;
}
}
}
script>