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)
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})
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>
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'})) ]
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(' ') 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>
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})
<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); }) }
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':'删除成功'})
用户删除一条购物车数据 后端已经删除了 但是前端页面也要同步删除
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); }) } }
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>
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})
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"> </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>
<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>
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)
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