day85:luffy:购物车根据有效期不同切换价格&购物车删除操作&价格结算&订单页面前戏

2022-11-03,,,,

目录

1.购物车有效期切换

2.根据有效期不同切换价格

3.购物车删除操作

4.价格结算

5.订单页面-初始化

1.购物车有效期切换

1.关于有效期表结构的设计

1.course/models.py

class CourseExpire(BaseModel):
"""课程有效期模型"""
# 后面可以在数据库把course和expire_time字段设置为联合索引
course = models.ForeignKey("Course", related_name='course_expire', on_delete=models.CASCADE, verbose_name="课程名称") # 有效期限,天数
expire_time = models.IntegerField(verbose_name="有效期", null=True, blank=True, help_text="有效期按天数计算") # 一个月有效等等
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 = verbose_name def __str__(self):
return "课程:%s,有效期:%s,价格:%s" % (self.course, self.expire_text, self.price)

课程有效期表结构设计

在course表中的price字段加一个提示文本

price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价", default=0,help_text='如果填写的价格为0,那么表示当前课程在购买的时候,没有永久有效的期限。')

2.插入一些测试数据

INSERT INTO `ly_course_expire`
(`id`,`orders`,`is_show`,`is_deleted`,`created_time`,`updated_time`,`expire_time`,`expire_text`,`course_id`,`price`)
VALUES
(1,1,1,0,'2019-08-19 02:05:22.368823','2019-08-19 02:05:22.368855',30,'一个月有效',1,398.00),
(2,2,1,0,'2019-08-19 02:05:37.397205','2019-08-19 02:05:37.397233',60,'2个月有效',1,588.00),
(3,3,1,0,'2019-08-19 02:05:57.029411','2019-08-19 02:05:57.029440',180,'半年内有效',1,1000.00),
(4,4,1,0,'2019-08-19 02:07:29.066617','2019-08-19 02:08:29.156730',3,'3天内有效',3,0.88),
(5,3,1,0,'2019-08-19 02:07:46.120827','2019-08-19 02:08:18.652452',30,'1个月有效',3,188.00),
(6,3,1,0,'2019-08-19 02:07:59.876421','2019-08-19 02:07:59.876454',60,'2个月有效',3,298.00);

3.xadmin注册

course/adminx.py

from .models import CourseExpire
class CourseExpireModelAdmin(object):
"""商品有效期模型"""
pass
xadmin.site.register(CourseExpire, CourseExpireModelAdmin)

2.课程有效期-后端接口

course/models.py

class Course:
# 获取课程有效期
def get_expire(self): # 课程表和课程有效期表时一对多的关系 反向查询
expire_list = self.course_expire.all() # 查询到当前课程所拥有的有效期种类
data = []
for expire in expire_list:
data.append({
'id':expire.id,
'expire_text':expire.expire_text, # 有效期的那个文本 比如'三个月有效'
'price':expire.price, # 有效期对应的价格
}) # 当价格为0时,没有永久有效这一项,其他的都有
if self.price > 0:
data.append({
'id': 0,
'expire_text': '永久有效',
'price': self.price,
}) return data

cart/views.py

class AddCartView(ViewSet):

    def cart_list(self,request):

        try:
......
cart_data_list.append({
......
'expire_list':course_obj.get_expire(),
......
})
except Exception:
......
return Response({'msg':'xxx','cart_data_list':cart_data_list})

3.课程有效期-前端

cartitem.vue

<div class="cart_column column_3">
<el-select v-model="cart.expire_id" size="mini" placeholder="请选择购买有效期" class="my_el_select">
<el-option :label="expire.expire_text" :value="expire.id" :key="expire.id" v-for="(expire,expire_index) in cart.expire_list"></el-option>
</el-select>
</div>

2.根据有效期不同切换价格

1.有效期切换更改redis中数据

cart/views.py

class AddCartView:
def change_expire(self,request): user_id = request.user.id
course_id = request.data.get('course_id')
expire_id = request.data.get('expire_id') # 检验课程id是否有效
try:
course_obj = models.Course.objects.get(id=course_id)
except:
return Response({'msg':'课程不存在'},status=status.HTTP_400_BAD_REQUEST)
# 检验有效期id是否有效
try:
if expire_id > 0:
expire_object = models.CourseExpire.objects.get(id=expire_id)
except:
return Response({'msg':'课程有效期不存在'},status=status.HTTP_400_BAD_REQUEST) # 计算xx课程xx有效期的真实价格
real_price = course_obj.real_price(expire_id) # 更改了新的有效期,要将更改之后的有效期存放到redis中
conn = get_redis_connection('cart')
conn.hset('cart_%s' % user_id, course_id, expire_id) return Response({'msg':'切换成功!', 'real_price': real_price})

cart/urls.py

urlpatterns = [
......
path('expires/', views.AddCartView.as_view({'put':'change_expire'}))
]

2.有效期切换让页面价格变化

course/models.py

class Course:
def real_price(self,expire_id=0): # 增加expire_id=0参数 让真实价格根据不同的有效期显示不同的价格
price = float(self.price)
'''
1.如果不是永久有效(expire_id>0),那就从course_expire获取该有效期对应的价格
2.如果是永久有效(expire_id=0),那么真实价格就等于它原来的价格
'''
if expire_id > 0:
expire_obj = self.course_expire.get(id=expire_id)
price = float(expire_obj.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) 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
if price >= float(a):
dis_list.append(float(b)) max_dis = max(dis_list)
r_price = price - max_dis return r_price

cartitem.vue

// js
watch:{
......
// 当用户选择的课程有效期发生变化时(在前端下拉框选择了别的有效期)
'cart.expire_id':function (){
let token = localStorage.token || sessionStorage.token; this.$axios.put(`${this.$settings.Host}/cart/expires/`,{
// 将课程id和课程对应的有效期id提交到后端
course_id: this.cart.course_id,
expire_id:this.cart.expire_id,
},{
headers:{
'Authorization':'jwt ' + token
} }
).then((res)=>{
this.$message.success(res.data.msg); // 修改有效期成功,将真实价格返回给前端
this.cart.real_price = res.data.real_price; // 修改有效期成功,触发cart父组件重新计算总价格的事件
this.$emit('change_expire_handler',) }).catch((error)=>{
this.$message.error(error.response.data.msg)
})
}
},

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" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem> </div>

3.页面刷新 应该重置有效期

cart/views.py

class AddCartView:
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') # 当用户查看购物车页面时->默认显示永久有效
conn.hset('cart_%s' % user_id, course_id, 0)
expire_id = 0 course_obj = models.Course.objects.get(id=course_id) cart_data_list.append({
'course_id':course_obj.id,
'name':course_obj.name,
'course_img':contains.SERVER_ADDR + course_obj.course_img.url ,
'price':course_obj.price,
'real_price':course_obj.real_price(),
'expire_id':expire_id,
'expire_list':course_obj.get_expire(),
'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})

3.购物车删除操作

cartitem.vue

<div class="cart_column column_4" id="delete" @click="delete_course">删除</div>
delete_course(){
let token = localStorage.token || sessionStorage.token; this.$axios.delete(`${this.$settings.Host}/cart/add_cart/`,{
params:{
course_id: this.cart.course_id, // request.query_params.get('course_id')
},
headers:{
'Authorization':'jwt ' + token
}
})
.then((res)=>{
this.$message.success(res.data.msg); // 当用户要删除购物车中的某条课程记录时 执行Cart父组件的删除课程事件
this.$emit('delete_course_handler')
})
.catch((error)=>{
this.$message.error(error.response.data.msg);
})
}

1.后端redis删除

cart/urls.py

urlpatterns = [
path('add_cart/', views.AddCartView.as_view(
{'post':'add','get':'cart_list',
'patch':'change_select','put':'cancel_select',
'delete':'delete_course'
})),
path('expires/', views.AddCartView.as_view({'put':'change_expire'})) ]

cart/views.py

class AddCartView:
def delete_course(self,request):
user_id = request.user.id
course_id = request.query_params.get('course_id') conn = get_redis_connection('cart')
pipe = conn.pipeline()
pipe.hdel('cart_%s' % user_id, course_id)
pipe.srem('selected_cart_%s' % user_id, course_id)
pipe.execute() return Response({'msg':'删除成功'})

2.前端同步实现删除效果

用户删除一条购物车数据 后端已经删除了 但是前端页面也要同步删除

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" @change_expire_handler="cal_total_price" @delete_course_handler="delete_c(index)"></CartItem> </div>
delete_c(index){
this.cart_data_list.splice(index,1)
this.cal_total_price() // 删除之后 重新触发计算总价格的方法
}

Cartitem.vue

delete_course(){
let token = localStorage.token || sessionStorage.token; this.$axios.delete(`${this.$settings.Host}/cart/add_cart/`,{
params:{
course_id: this.cart.course_id,
},
headers:{
'Authorization':'jwt ' + token
}
})
.then((res)=>{ // 删除时 触发Cart父组件的删除事件
this.$message.success(res.data.msg);
this.$emit('delete_course_handler')
})
.catch((error)=>{
this.$message.error(error.response.data.msg);
})
}
}

4.价格结算

1.价格结算页面-准备工作

1.结算初始页面

<!-- 结算页面初始界面 -->

2.配置路由

index.js

import Vue from 'vue'
import Order from "@/components/Order" Vue.use(Router)
export default new Router({
mode:'history',
routes: [
......
{
path: '/order/',
component: Order
},
]
})

3.点击去结算按钮 看到页面

cart.vue

<span class="goto_pay"><router-link to="/order/">去结算</router-link></span>          

2.价格结算页面-后端

cart/urls.py

urlpatterns = [
...
path('expires/', views.AddCartView.as_view({'get':'show_pay_info'})) ]

cart/views.py

def show_pay_info(self,request):

    user_id = request.user.id

    conn = get_redis_connection('cart')

    # 获取用户购物车选中的所有课程id
select_list = conn.smembers('selected_cart_%s' % user_id)
data = [] # 获取用户购物车的课程id:有效期id
ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'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'))
# 获取这个'被勾选的课程'model对象
course_obj = models.Course.objects.get(id=course_id) # 如果课程的有效期不是永久有效
if expire_id > 0:
expire_obj = models.CourseExpire.objects.get(id=expire_id)
data.append({
'course_id':course_obj.id,
'name':course_obj.name,
'course_img':contains.SERVER_ADDR + course_obj.course_img.url ,
'real_price':course_obj.real_price(expire_id),
'expire_text':expire_obj.expire_text,
}) # 如果课程的有效期是永久有效
else:
data.append({
'course_id': course_obj.id,
'name': course_obj.name,
'course_img': contains.SERVER_ADDR + course_obj.course_img.url,
'real_price': course_obj.real_price(expire_id),
'expire_text': '永久有效',
}) return Response({'data':data})

3.价格结算页面-前端

order.vue

// js
methods: {
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;
})
},
<!-- html -->
<div class="cart-item" v-for="(course,index) in course_list">
<el-row>
<el-col :span="2" class="checkbox">&nbsp;&nbsp;</el-col>
<el-col :span="10" class="course-info">
<img :src="course.course_img" alt="">
<span>{{course.name}}</span>
</el-col>
<el-col :span="8"><span>{{course.expire_text}}</span></el-col>
<el-col :span="4" class="course-price">¥{{course.real_price}}</el-col>
</el-row>
</div>

4.切换支付方式-微信/支付宝

其实就是几张图片 点击显示图片而已

Order.vue

<el-col :span="8">
<span class="alipay" v-if="pay_type===1"><img src="../../static/img/alipay2.png" alt=""></span>
<span class="alipay" @click="pay_type=1" v-else><img src="../../static/img/alipay.png" alt=""></span>
<span class="alipay wechat" v-if="pay_type===2"><img src="../../static/img/wechat2.png" alt="" ></span>
<span class="alipay wechat" @click="pay_type=2" v-else><img src="../../static/img/wechat.png" alt=""></span> </el-col>

5.订单页面-初始化

order/models.py

from django.db import models

# Create your models here.

from lyapi.utils.models import BaseModel
from users.models import User
from course.models import Course
class Order(BaseModel):
"""订单模型"""
status_choices = (
(0, '未支付'),
(1, '已支付'),
(2, '已取消'),
(3, '超时取消'),
)
pay_choices = (
(0, '支付宝'),
(1, '微信支付'),
)
order_title = models.CharField(max_length=150,verbose_name="订单标题")
total_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="订单总价", default=0)
real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="实付金额", default=0)
order_number = models.CharField(max_length=64,verbose_name="订单号")
order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
credit = models.IntegerField(default=0, verbose_name="使用的积分数量")
coupon = models.IntegerField(null=True, verbose_name="用户优惠券ID")
order_desc = models.TextField(max_length=500, verbose_name="订单描述",null=True,blank=True)
pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING,verbose_name="下单用户") class Meta:
db_table="ly_order"
verbose_name= "订单记录"
verbose_name_plural= "订单记录" def __str__(self):
return "%s,总价: %s,实付: %s" % (self.order_title, self.total_price, self.real_price) class OrderDetail(BaseModel):
"""
订单详情
"""
order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, verbose_name="订单ID")
course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, verbose_name="课程ID")
expire = models.IntegerField(default='0', verbose_name="有效期周期",help_text="0表示永久有效")
price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价")
real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价")
discount_name = models.CharField(max_length=120,default="",verbose_name="优惠类型") class Meta:
db_table="ly_order_detail"
verbose_name= "订单详情"
verbose_name_plural= "订单详情" def __str__(self):
return "%s" % (self.course.name)

订单表结构设计

order/adminx.py

import xadmin
from .models import Order
class OrderModelAdmin(object):
"""订单模型管理类"""
pass xadmin.site.register(Order, OrderModelAdmin) from .models import OrderDetail
class OrderDetailModelAdmin(object):
"""订单详情模型管理类"""
pass xadmin.site.register(OrderDetail, OrderDetailModelAdmin)

order/__init__.py

default_app_config = "order.apps.OrderConfig"

order/app.py

from django.apps import AppConfig

class OrderConfig(AppConfig):
name = 'order'
verbose_name = '订单管理'

order/urls.py

from django.urls import path,re_path
from . import views urlpatterns = [
path('add_money/',views.OrderView.as_view(),) ]

order/views.py

from django.shortcuts import render
from rest_framework.generics import CreateAPIView
# Create your views here.
from . import models
from .serializers import OrderModelSerializer
from rest_framework.permissions import IsAuthenticated class OrderView(CreateAPIView):
queryset = models.Order.objects.filter(is_deleted=False,is_show=True)
serializer_class = OrderModelSerializer
permission_classes = [IsAuthenticated, ]

order/serializers.py

import datetime
from rest_framework import serializers
from . import models
from django_redis import get_redis_connection
from course.models import Course
from course.models import CourseExpire
from django.db import transaction class OrderModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Order
fields = ['id', 'order_number', 'pay_type', 'coupon', 'credit'] '''
1.用户在订单页面需要提交过来的数据:
支付类型:微信/支付宝
优惠券
积分
2.用户提交数据成功后,前端页面需要返回过来的数据:
id
订单号
'''
extra_kwargs = {
'id':{'read_only':True},
'order_number':{'read_only':True},
'pay_type':{'write_only':True},
'coupon':{'write_only':True},
'credit':{'write_only':True},
} 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('支付方式不对!') # todo 优惠券校验,看看是否过期了等等 # todo 积分上限校验 return attrs def create(self, validated_data):
try:
# 生成订单号 [日期,用户id,自增数据]
current_time = datetime.datetime.now()
now = current_time.strftime('%Y%m%d%H%M%S')
user_id = self.context['request'].user.id conn = get_redis_connection('cart')
num = conn.incr('num') # 生成一个唯一的订单号
order_number = now + "%06d" % user_id + "%06d" % num with transaction.atomic(): # 添加事务 # 生成订单
order_obj = models.Order.objects.create(**{
'order_title': '31期订单',
'total_price': 0,
'real_price': 0,
'order_number': order_number,
'order_status': 0,
'pay_type': validated_data.get('pay_type', 0),
'credit': 0,
'coupon': 0,
'order_desc': '女朋友',
'pay_time': current_time,
'user_id': user_id,
# 'user':user_obj,
}) select_list = conn.smembers('selected_cart_%s' % user_id) ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'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 = Course.objects.get(id=course_id)
if expire_id > 0:
expire_text = CourseExpire.objects.get(id=expire_id).expire_text # 生成订单详情
models.OrderDetail.objects.create(**{
'order': order_obj,
'course': course_obj,
'expire': expire_id,
'price': course_obj.price,
'real_price': course_obj.real_price(expire_id),
'discount_name': course_obj.discount_name(),
})
# print('xxxxx')
except Exception:
raise models.Order.DoesNotExist return order_obj

day85:luffy:购物车根据有效期不同切换价格&购物车删除操作&价格结算&订单页面前戏的相关教程结束。

《day85:luffy:购物车根据有效期不同切换价格&购物车删除操作&价格结算&订单页面前戏.doc》

下载本文的Word格式文档,以方便收藏与打印。