Luffy之结算订单页面(订单模型表的创建,订单的生成,以及订单详情展示等)
订单页面
在前面我们已经构建了,购物车的页面,接下来到了结算页面
1.首先,在购物车页面点击去结算按钮时,我们需要做如下动作
.前端发送生成订单的请求,点击标签内触发事件 create_order
template: <el-col :span="3" class="cart-calc"><span @click="create_order">去结算</span></el-col> script,methods中: create_order(){ // 生成订单 this.$axios.post("http://127.0.0.1:8000/orders/",{},{ headers: { // 附带已经登录用户的jwt token 提供给后端,一定不能疏忽这个空格 'Authorization': 'JWT ' + this.token }, responseType: "json", }).then(response=>{ //在session中保存订单ID,便于在结算页面发送请求时带上order_id,查询订单详情 sessionStorage.order_id = response.data.order_id; // 跳转到结算页面 this.$router.push("/order") }).catch(error=>{ // 生成订单失败 alert("生成订单失败") })
后端需要对该请求作出处理,将订单保存到mysql中,并返回对应的订单的ID值(不是订单号),但是在做订单保存之前,我们必须先创建订单的数据模型,其中模型如下所示
order.models:
订单的模型:
# Create your models here. from django.db import models # Create your models here. from luffy.apps.user.models import User from luffy.apps.courses.models import Course class Order(models.Model): """订单记录""" status_choices = ( (0, '未支付'), (1, '已支付'), (2, '已取消'), ) total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0) order_number = models.CharField(max_length=32,verbose_name="订单号") order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态") order_desc = models.CharField(max_length=120,verbose_name="订单描述") created_time = models.DateTimeField(verbose_name="订单生成时间", auto_now_add=True) pay_time = models.DateTimeField(verbose_name="订单支付时间", auto_now_add=True) user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING,verbose_name="用户ID") class Meta: db_table="ly_order" verbose_name= "订单记录" verbose_name_plural= "订单记录" class OrderDetail(models.Model): """订单详情""" order = models.ForeignKey("Order", related_name='order_course', on_delete=models.CASCADE, verbose_name="订单ID") course = models.ForeignKey(Course, related_name='course_order', on_delete=models.CASCADE, verbose_name="课程ID") user = models.ForeignKey(User, null=True, related_name="course", on_delete=models.DO_NOTHING, verbose_name="用户ID") unit_price = models.DecimalField(max_digits=8, decimal_places=2, null=True, verbose_name="课程价格") class Meta: db_table="ly_order_detail" verbose_name= "订单详情" verbose_name_plural= "订单详情"
而后端的处理代码如下所示
class OrderAPIView(APIView): def post(self,request): # 获取用户ID try: user_id = request.user.id except: return Response({"message": "用户不存在!"}) # 自己生成一个订单号,# 结合时间戳和当前用户ID来生成,才能保证整站唯一 order_number = datetime.now().strftime("%Y%m%d%H%M%S") + "%07d" % int(user_id) + "%04d" % random.randint(0,9999) # 从redis中获取商品信息[先获取勾选集,然后根据勾选集,到购物车中查询对应的商品价格] redis = get_redis_connection("cart") course_id_list = redis.smembers("cart_select_%s" % user_id) # 计算总价格 total_price = 0 cart_info = redis.hgetall("cart_%s" % user_id) # 返回哈希数据中的键值对 for course_id,course_price in cart_info.items(): if course_id in course_id_list: total_price += Decimal(course_price.decode()) # 创建订单数据 order = Order.objects.create( user_id = user_id, order_number = order_number, order_status = 0, # 订单状态默认为未支付 order_desc = "路飞学成课程购买", # # 订单描述信息 total_price = total_price ) # 返回响应信息给客户端 print("order",order) print("order_type", type(order)) if order: for course_id in course_id_list: # 记录订单相关的课程信息到订单详情 OrderDetail.objects.create( course_id = course_id, order_id = order.id, user_id = user_id, unit_price = redis.hget("cart_%s" % user_id, course_id).decode(), ) # 删除redis中已经生成订单的商品信息 redis.hdel("cart_%s" % user_id, course_id.decode()) redis.srem("cart_select_%s" % user_id, course_id.decode()) return Response({"order_id": order.id}, status=status.HTTP_200_OK) else: return Response({"message": "生成订单失败!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
2.在1步骤完成后,前端会跳转到order页面,在加载order页面,我们需要将order页面的数据加载过来,需要发送请求
created() { // 判断用户是否已经登陆了。 if( !this.token){ this.$router.push("/login"); } let _this = this; // 发起请求获取购物车中的商品信息,_this.order_id时在cart发送创建订单请求返回数据时,保存在session中的订单ID _this.$axios.get("http://127.0.0.1:8000/orders/detail/"+_this.order_id,{ headers: { 'Authorization': 'JWT ' + _this.token }, responseType: 'json', }).then(response=>{ console.log("response.data",response.data) _this.course_list = response.data.order_course; _this.total = response.data.total_price })
当前端发送请求后,我们需要在后端作出处理来响应请求,返回对应订单的数据
order/views
class OrderDetailAPIView(APIView): def get(self,request,order_id): """显示订单中的商品信息""" try: order = Order.objects.get(pk=order_id) except Order.DoesNotExist(): return Response({"message":"当前订单不存在!"},status=status.HTTP_400_BAD_REQUEST) seriazlier = OrderSerializer(instance=order) return Response(seriazlier.data,status=status.HTTP_200_OK)
其中使用了序列化器:
from rest_framework import serializers from .models import Order,OrderDetail from courses.models import Course class CourseSerializer(serializers.ModelSerializer): class Meta: model = Course fields = ("course_http_img","name") class OrderDetailSerializer(serializers.ModelSerializer): course = CourseSerializer() class Meta: model = OrderDetail fields = ("course","unit_price") class OrderSerializer(serializers.ModelSerializer): order_course = OrderDetailSerializer(many=True) class Meta: model= Order fields = ("id","total_price","order_course")
可以看到上面代码的序列化器中,有些字段嵌套了三层,
order_course ==> order_course = OrderDetailSerializer(many=True) ==> course = CourseSerializer()
注意:在CourseSerializer这个序列化器中,
class CourseSerializer(serializers.ModelSerializer): class Meta: model = Course fields = ("course_http_img","name")
字段 course_http_img 原本是不在模型表中的,是我们自定义的一个字段,需要在对应的models中,做出定义,如下代码
courses/models:
class Course(models.Model): ..... # 自定义显示字段 def course_http_img(self): return settings.HOST + self.course_img.url
3.前端接收到后端返回的数据,进行数据渲染即可
完整前后端代码如下:
order.vue:
1 <template> 2 <div class="cart"> 3 <Header/> 4 <div class="cart-info"> 5 <h3 class="cart-top">购物车结算 <span>共1门课程</span></h3> 6 <div class="cart-title"> 7 <el-row> 8 <el-col :span="2"> </el-col> 9 <el-col :span="10">课程</el-col> 10 <el-col :span="8">有效期</el-col> 11 <el-col :span="4">价格</el-col> 12 </el-row> 13 </div> 14 <div class="cart-item" v-for="item in course_list" > 15 <el-row> 16 <el-col :span="2" class="checkbox"> </el-col> 17 <el-col :span="10" class="course-info"> 18 <img :src="item.course.course_http_img" alt=""> 19 <span>{{item.course.name}}</span> 20 </el-col> 21 <el-col :span="8"><span>永久有效</span></el-col> 22 <el-col :span="4" class="course-price">¥{{item.unit_price}}</el-col> 23 </el-row> 24 </div> 25 <div class="calc"> 26 <el-row class="pay-row"> 27 <el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col> 28 <el-col :span="4"><span class="alipay"><img src="../../static/images/1554167287107.png" alt=""></span></el-col> 29 <el-col :span="12" class="count">实付款: <span>¥{{total}}</span></el-col> 30 <el-col :span="4" class="cart-pay"><span >支付宝支付</span></el-col> 31 </el-row> 32 </div> 33 </div> 34 <Footer/> 35 </div> 36 </template> 37 38 <script> 39 import Header from "./common/Header" 40 import Footer from "./common/Footer" 41 42 export default { 43 name:"Order", 44 data(){ 45 return { 46 total:0, 47 course_list:[], 48 token: localStorage.token || sessionStorage.token, 49 id : localStorage.id || sessionStorage.id, 50 order_id:sessionStorage.order_id || null, 51 } 52 }, 53 54 components:{ 55 Header, 56 Footer, 57 58 }, 59 methods:{ 60 61 }, 62 created() { 63 // 判断用户是否已经登陆了。 64 if( !this.token){ 65 this.$router.push("/login"); 66 } 67 68 let _this = this; 69 // 发起请求获取购物车中的商品信息 70 _this.$axios.get("http://127.0.0.1:8000/orders/detail/"+_this.order_id,{ 71 headers: { 72 'Authorization': 'JWT ' + _this.token 73 }, 74 responseType: 'json', 75 }).then(response=>{ 76 console.log("response.data",response.data) 77 _this.course_list = response.data.order_course; 78 _this.total = response.data.total_price 79 80 }) 81 }, 82 } 83 </script> 84 85 86 <style scoped> 87 .cart{ 88 margin-top: 80px; 89 } 90 .cart-info{ 91 overflow: hidden; 92 width: 1200px; 93 margin: auto; 94 } 95 .cart-top{ 96 font-size: 18px; 97 color: #666; 98 margin: 25px 0; 99 font-weight: normal; 100 } 101 .cart-top span{ 102 font-size: 12px; 103 color: #d0d0d0; 104 display: inline-block; 105 } 106 .cart-title{ 107 background: #F7F7F7; 108 height: 70px; 109 } 110 .calc{ 111 margin-top: 25px; 112 margin-bottom: 40px; 113 } 114 115 .calc .count{ 116 text-align: right; 117 margin-right: 10px; 118 vertical-align: middle; 119 } 120 .calc .count span{ 121 font-size: 36px; 122 color: #333; 123 } 124 .calc .cart-pay{ 125 margin-top: 5px; 126 width: 110px; 127 height: 38px; 128 outline: none; 129 border: none; 130 color: #fff; 131 line-height: 38px; 132 background: #ffc210; 133 border-radius: 4px; 134 font-size: 16px; 135 text-align: center; 136 cursor: pointer; 137 } 138 .cart-item{ 139 height: 120px; 140 line-height: 120px; 141 } 142 .course-info img{ 143 width: 175px; 144 height: 115px; 145 margin-right: 35px; 146 vertical-align: middle; 147 } 148 .alipay{ 149 display: block; 150 height: 48px; 151 } 152 .alipay img{ 153 height: 100%; 154 width:auto; 155 } 156 157 .pay-text{ 158 display: block; 159 text-align: right; 160 height: 100%; 161 line-height: 100%; 162 vertical-align: middle; 163 margin-top: 20px; 164 } 165 </style>
order.views:
1 import random 2 from datetime import datetime 3 from decimal import Decimal 4 from django_redis import get_redis_connection 5 from rest_framework import status 6 from rest_framework.response import Response 7 from rest_framework.views import APIView 8 9 from luffy.apps.orders.models import Order, OrderDetail 10 from luffy.apps.orders.serializers import OrderSerializer 11 12 13 class OrderAPIView(APIView): 14 def get(self,request): 15 # 获取用户ID 16 user_id = request.user.id 17 18 return Response({"message":"ok"}) 19 def post(self,request): 20 # 获取用户ID 21 try: 22 user_id = request.user.id 23 except: 24 return Response({"message": "用户不存在!"}) 25 26 # 自己生成一个订单号,# 结合时间戳和当前用户ID来生成,才能保证整站唯一 27 order_number = datetime.now().strftime("%Y%m%d%H%M%S") + "%07d" % int(user_id) + "%04d" % random.randint(0,9999) 28 # 从redis中获取商品信息[先获取勾选集,然后根据勾选集,到购物车中查询对应的商品价格] 29 redis = get_redis_connection("cart") 30 course_id_list = redis.smembers("cart_select_%s" % user_id) 31 # 计算总价格 32 total_price = 0 33 cart_info = redis.hgetall("cart_%s" % user_id) # 返回哈希数据中的键值对 34 for course_id,course_price in cart_info.items(): 35 if course_id in course_id_list: 36 total_price += Decimal(course_price.decode()) 37 # 创建订单数据 38 order = Order.objects.create( 39 user_id = user_id, 40 order_number = order_number, 41 order_status = 0, # 订单状态默认为未支付 42 order_desc = "路飞学成课程购买", # # 订单描述信息 43 total_price = total_price 44 ) 45 # 返回响应信息给客户端 46 print("order",order) 47 print("order_type", type(order)) 48 if order: 49 for course_id in course_id_list: 50 # 记录订单相关的课程信息到订单详情 51 OrderDetail.objects.create( 52 course_id = course_id, 53 order_id = order.id, 54 user_id = user_id, 55 unit_price = redis.hget("cart_%s" % user_id, course_id).decode(), 56 ) 57 # 删除redis中已经生成订单的商品信息 58 redis.hdel("cart_%s" % user_id, course_id.decode()) 59 redis.srem("cart_select_%s" % user_id, course_id.decode()) 60 61 return Response({"order_id": order.id}, status=status.HTTP_200_OK) 62 else: 63 return Response({"message": "生成订单失败!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) 64 65 66 class OrderDetailAPIView(APIView): 67 def get(self,request,order_id): 68 """显示订单中的商品信息""" 69 try: 70 order = Order.objects.get(pk=order_id) 71 except Order.DoesNotExist(): 72 return Response({"message":"当前订单不存在!"},status=status.HTTP_400_BAD_REQUEST) 73 74 seriazlier = OrderSerializer(instance=order) 75 return Response(seriazlier.data,status=status.HTTP_200_OK)
order/serializer:
1 from rest_framework import serializers 2 3 from luffy.apps.courses.models import Course 4 from luffy.apps.orders.models import Order, OrderDetail 5 6 class CourseDetailSerializer(serializers.ModelSerializer): 7 class Meta: 8 model = Course 9 fields=("course_http_img","name") 10 11 class OrderDetailSerializer(serializers.ModelSerializer): 12 course = CourseDetailSerializer() 13 class Meta: 14 model = OrderDetail 15 fields=("unit_price","course") 16 17 class OrderSerializer(serializers.ModelSerializer): 18 order_course = OrderDetailSerializer(many=True) 19 class Meta: 20 model = Order 21 fields=("id","total_price","order_course")