您现在的位置是: 网站首页 >Django >Vue+Django REST framework前后端分离生鲜电商 Django

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调

admin2019年7月22日 21:02 Django | Html | Python | Vue 1198人已围观

Vue+Django REST framework前后端分离生鲜电商简介 Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。 Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ; Django版本:2.2、djangorestframework:3.9.2。 前端Vue模板可以直接联系我拿。

## 订单管理接口功能 先看下之前的模型 ```python class OrderInfo(models.Model): """ 订单 """ ORDER_STATUS = ( ('success', '成功'), ('cancel', '取消'), ('topaid', '待支付') ) user = models.ForeignKey(User, verbose_name='用户', help_text='用户', on_delete=models.CASCADE, related_name='order_infos') order_sn = models.CharField(max_length=30, unique=True, verbose_name='订单号', help_text='订单号') trade_no = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name='支付') pay_status = models.CharField(choices=ORDER_STATUS, max_length=20, verbose_name='订单状态', help_text='订单状态') post_script = models.CharField(max_length=50, blank=True, null=True, verbose_name='订单留言', help_text='订单留言') order_amount = models.FloatField(default=0.0, verbose_name='订单金额', help_text='订单金额') pay_time = models.DateTimeField(null=True, blank=True, verbose_name='支付时间', help_text='支付时间') # 用户信息 address = models.CharField(max_length=200, default='', verbose_name='收货地址', help_text='收货地址') signer_name = models.CharField(max_length=20, default='', verbose_name='签收人', help_text='签收人') signer_mobile = models.CharField(max_length=11, verbose_name='联系电话', help_text='联系电话') add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间') class Meta: verbose_name_plural = verbose_name = '订单' def __str__(self): return "{}".format(self.order_sn) ``` 分析,`order_sn`字段不能为空,这是不行的,订单号需要由后台生成。但用户点击去结算时,后台需要生成一个订单号,但是如果我们用到`CreateModelMixin`,这个会对该字段进行验证,在用户界面是不可能post一个订单号到后台,从而导致验证失败,所以该字段增加设置`blank=True, null=True` `pay_status`可以设置一个默认值,为待支付状态`default='topaid'`,提交订单时,用户也不用指定该字段了。 `address`不直接使用外键的原因是:用户如果修改了个人中心的收货地址,订单中地址就会跟着变化,显然是不对的,所以订单中的地址就保存一个当时下单的地址组合的字符串。 修改后 ```python # apps/trade/models.py class OrderInfo(models.Model): """ 订单 """ ORDER_STATUS = ( ('success', '成功'), ('cancel', '取消'), ('topaid', '待支付') ) user = models.ForeignKey(User, verbose_name='用户', help_text='用户', on_delete=models.CASCADE, related_name='order_infos') order_sn = models.CharField(max_length=30, unique=True, blank=True, null=True, verbose_name='订单号', help_text='订单号') trade_no = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name='支付交易号', help_text='支付交易号') pay_status = models.CharField(choices=ORDER_STATUS, default='topaid', max_length=20, verbose_name='订单状态', help_text='订单状态') post_script = models.CharField(max_length=50, blank=True, null=True, verbose_name='订单留言', help_text='订单留言') order_amount = models.FloatField(default=0.0, verbose_name='订单金额', help_text='订单金额') pay_time = models.DateTimeField(null=True, blank=True, verbose_name='支付时间', help_text='支付时间') # 用户信息 address = models.CharField(max_length=200, default='', verbose_name='收货地址', help_text='收货地址') signer_name = models.CharField(max_length=20, default='', verbose_name='签收人', help_text='签收人') signer_mobile = models.CharField(max_length=11, verbose_name='联系电话', help_text='联系电话') add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间') class Meta: verbose_name_plural = verbose_name = '订单' def __str__(self): return "{}".format(self.order_sn) ``` 对于一个订单,有多个商品,所以需要设置另外一个model,用外键关联。 ### OrderInfoSerializer创建订单序列化 序列化中用户也是需要不可见的 ```python # apps/trade/serializers.py from trade.models import ShoppingCart, OrderInfo class OrderInfoSerializer(serializers.ModelSerializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault() # 表示user为隐藏字段,默认为获取当前登录用户 ) class Meta: model = OrderInfo fields = "__all__" ``` ### OrderInfoViewSet订单管理视图 不使用`viewsets.ModelViewSet`原因是,订单一般不允许修改的,所有要自定义继承 ```python # apps/trade/views.py from .serializers import ShoppingCartSerializer, ShoppingCartListSerializer, OrderInfoSerializer from .models import ShoppingCart, OrderInfo class OrderInfoViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin,mixins.DestroyModelMixin, viewsets.GenericViewSet): """ 订单管理 list: 获取个人订单 create: 新建订单 delete: 删除订单 detail: 订单详情 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 queryset = OrderInfo.objects.all() serializer_class = OrderInfoSerializer # 添加序列化 def get_queryset(self): return self.queryset.filter(user=self.request.user) ``` ### 配置订单管理的URL ```python # DjangoOnlineFreshSupermarket/urls.py from trade.views import ShoppingCartViewSet, OrderInfoViewSet router.register(r'orderinfo', OrderInfoViewSet, base_name='orderinfo') # 订单管理 ``` 现在访问 http://127.0.0.1:8000/orderinfo/ 可以看到DRF生成的页面 ![BLOG_20190722_210550_48](/media/blog/images/2019/07/BLOG_20190722_210550_48.png "博客图集BLOG_20190722_210550_48.png") 对于订单提交,会告诉后台创建一个订单,在这提交订单是一个比较简单的逻辑,即将购物车`ShoppingCart`中的该用户对应的单品和数量,添加到订单商品`OrderGoods`中,然后清空该用户的购物车。 ### OrderInfoViewSet添加购物车的商品到订单 可以在`OrderInfoViewSet`中重载`perform_create(self, serializer)`方法(这个在`mixins.CreateModelMixin`类中) ```python def perform_create(self, serializer): serializer.save() ``` 在`.save()`之前,需要生成一个订单号,所以修改序列化类,在数据验证通过后生成一个订单号,修改`OrderInfoSerializer`,重载`validate(self, attrs)`方法 ```python # apps/trade/serializers.py class OrderInfoSerializer(serializers.ModelSerializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault() # 表示user为隐藏字段,默认为获取当前登录用户 ) def generate_order_sn(self): # 当前时间+userid+随机数 import time from random import randint order_sn = '{time_str}{user_id}{random_str}'.format(time_str=time.strftime('%Y%m%d%H%M%S'), user_id=self.context['request'].user.id, random_str=randint(10, 99)) return order_sn def validate(self, attrs): # 数据验证成功后,生成一个订单号 attrs['order_id'] = self.generate_order_sn() return attrs class Meta: model = OrderInfo fields = "__all__" ``` 之后修改`OrderInfoViewSet`,重载`perform_create(self, serializer)`方法 ```python class OrderInfoViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet): """ 订单管理 list: 获取个人订单 create: 新建订单 delete: 删除订单 detail: 订单详情 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 queryset = OrderInfo.objects.all() serializer_class = OrderInfoSerializer # 添加序列化 def get_queryset(self): return self.queryset.filter(user=self.request.user) def perform_create(self, serializer): # 完成创建后保存到数据库,可以拿到保存的OrderInfo对象 order = serializer.save() shopping_carts = ShoppingCart.objects.filter(user=self.request.user) # 将该用户购物车所有商品都取出来放在订单商品中 for shopping_cart in shopping_carts: OrderGoods.objects.create( order=order, goods=shopping_cart.goods, goods_nums=shopping_cart.nums ) # 然后清空该用户购物车 shopping_carts.delete() ``` ### OrderInfoSerializer隐藏不能显示的字段 在上面的图中,订单号、支付交易号、订单状态、支付日期(需要支付后才有时间)信息是不能显示给用户的,只能后台去修改 ```python # apps/trade/serializers.py class OrderInfoSerializer(serializers.ModelSerializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault() # 表示user为隐藏字段,默认为获取当前登录用户 ) order_sn = serializers.CharField(read_only=True) # 只能读,不能显示给用户修改,只能后台去修改 trade_no = serializers.CharField(read_only=True) # 只读 pay_status = serializers.CharField(read_only=True) # 只读 pay_time = serializers.DateTimeField(read_only=True) # 只读 def generate_order_sn(self): # 当前时间+userid+随机数 import time from random import randint order_sn = '{time_str}{user_id}{random_str}'.format(time_str=time.strftime('%Y%m%d%H%M%S'), user_id=self.context['request'].user.id, random_str=randint(10, 99)) return order_sn def validate(self, attrs): # 数据验证成功后,生成一个订单号 attrs['order_sn'] = self.generate_order_sn() return attrs class Meta: model = OrderInfo fields = "__all__" ``` ![BLOG_20190722_210534_86](/media/blog/images/2019/07/BLOG_20190722_210534_86.png "博客图集BLOG_20190722_210534_86.png") `return attrs`打上断点调试,在DRF中 http://127.0.0.1:8000/orderinfo/ 填入一些信息后POST,可以在后端看到`attrs`的内容 ![BLOG_20190722_210527_85](/media/blog/images/2019/07/BLOG_20190722_210527_85.png "博客图集BLOG_20190722_210527_85.png") 然后进入`OrderInfoViewSet`完成创建`perform_create`处理,将购物车的商品添加到订单商品中,并删除购物车商品 ![BLOG_20190722_210522_17](/media/blog/images/2019/07/BLOG_20190722_210522_17.png "博客图集BLOG_20190722_210522_17.png") 访问 http://127.0.0.1:8000/orderinfo/1/ 可以看到有订单删除 ![BLOG_20190722_210516_45](/media/blog/images/2019/07/BLOG_20190722_210516_45.png "博客图集BLOG_20190722_210516_45.png") 该功能可以将订单关联的商品一起删除。 ## Vue中订单接口联调 访问 http://127.0.0.1:8080/#/app/home/member/order 可以查看我的订单 ![BLOG_20190722_210510_51](/media/blog/images/2019/07/BLOG_20190722_210510_51.png "博客图集BLOG_20190722_210510_51.png") 由于刚才订单删除了,所以为空。在Vue中,添加商品到购物车,然后选择配送地址,点击**去结算** ![BLOG_20190722_210501_44](/media/blog/images/2019/07/BLOG_20190722_210501_44.png "博客图集BLOG_20190722_210501_44.png") ![BLOG_20190722_210455_85](/media/blog/images/2019/07/BLOG_20190722_210455_85.png "博客图集BLOG_20190722_210455_85.png") ### 订单创建功能分析 当用户准备创建订单时,会点击去结算 ```html <!-- src/views/cart/cart.vue --> <p class="sumup"><a class="btn" @click="balanceCount">去结算</a></p> ``` 点击之后进入 ```JavaScript // src/views/cart/cart.vue balanceCount() { // 结算 if (this.addrInfo.length == 0) { alert("请选择收货地址") } else { createOrder( { post_script: this.post_script, address: this.address, signer_name: this.signer_name, signer_mobile: this.signer_mobile, order_amount: this.totalPrice } ).then((response) => { alert('订单创建成功'); window.location.href = response.data.alipay_url; }).catch(function (error) { console.log(error); }); } }, ``` 当没有选择地址弹框提示,接下来请求`createOrder()`也就是调用 api.js 中的接口 ```javascript //添加订单 export const createOrder = params => { return axios.post(`${local_host}/orderinfo/`, params) }; ``` 传递用户留言、收货地址、签收人、电话、订单总金额这个信息到后台,订单创建成功后会跳转到 http://127.0.0.1:8080/undefined 因为给出支付的地址,所以地址有误。 ### 订单列表显示 用户中心可以查看订单列表 http://127.0.0.1:8080/#/app/home/member/order ```html <!--src/views/member/order.vue--> <tr v-for="item in orders"> <td align="center" bgcolor="#ffffff"><a class="f6" @click="goDetail(item.id)">{{item.order_sn}}</a></td> <td align="center" bgcolor="#ffffff">{{item.add_time}}</td> <td align="right" bgcolor="#ffffff">¥{{item.order_amount}}元</td> <td v-if="item.pay_status == 'topaid' " align="center" bgcolor="#ffffff">待支付</td> <td v-if="item.pay_status == 'success' " align="center" bgcolor="#ffffff">已支付</td> <td align="center" bgcolor="#ffffff"><font class="f6"><a @click="cancelOrder(item.id)">取消订单</a></font></td> </tr> ``` 当组件创建时,调用 ```JavaScript // src/views/member/order.vue created () { this.getOrder(); }, ``` 即 ```JavaScript // src/views/member/order.vue getOrder () { getOrders().then((response)=> { this.orders = response.data; }).catch(function (error) { console.log(error); }); }, ``` 该函数会请求api获取订单数据 ```JavaScript // src/api/api.js //获取订单 export const getOrders = () => { return axios.get(`${local_host}/orderinfo/`) }; ``` 将结果遍历显示到表格中。 ![BLOG_20190722_210439_56](/media/blog/images/2019/07/BLOG_20190722_210439_56.png "博客图集BLOG_20190722_210439_56.png") 这是一个待支付状态,另外还有一个取消订单操作,用户点击取消订单,将该订单id传递给`cancelOrder (id)`函数 ```JavaScript //src/views/member/order.vue cancelOrder (id) { alert('您确认要取消该订单吗?取消后此订单将视为无效订单'); delOrder(id).then((response)=> { alert('订单删除成功') }).catch(function (error) { console.log(error); }); }, ``` 请求api对订单进行删除 ```JavaScript //src/api/api.js //删除订单 export const delOrder = orderId => { return axios.delete(`${local_host}/orderinfo/` + orderId + '/') }; ``` ### 订单详情显示 点击订单号可以进入订单详情 ![BLOG_20190722_210425_66](/media/blog/images/2019/07/BLOG_20190722_210425_66.png "博客图集BLOG_20190722_210425_66.png") 但是订单中的商品列表没有正常显示出来,所以要动态使用序列化,在显示订单详情时,使用另一个Serializer,并返回商品详情信息 #### 创建OrderInfoDetailSerializer用于订单详情显示 添加一个新的序列化类,用于显示订单详情 ```python # apps/trade/serializers.py from trade.models import ShoppingCart, OrderInfo, OrderGoods from goods.serializers import GoodsSerializer # 订单中的商品序列化 class OrderGoodsSerializer(serializers.ModelSerializer): goods = GoodsSerializer() # 直接用goods.serializers商品详情的序列化 class Meta: model = OrderGoods fields = "__all__" # 订单详情序列化 class OrderInfoDetailSerializer(serializers.ModelSerializer): order_goods = OrderGoodsSerializer(many=True) # 为OrderGoods中外键关联名称 class Meta: model = OrderInfo fields = "__all__" ``` #### 修改OrderInfoViewSet动态获取Serializer 当访问订单详情就会使用专用的Serializer ```python # apps/trade/views.py class OrderInfoViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet): """ 订单管理 list: 获取个人订单 create: 新建订单 delete: 删除订单 detail: 订单详情 """ permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 queryset = OrderInfo.objects.all() # serializer_class = OrderInfoSerializer # 添加序列化 def get_queryset(self): return self.queryset.filter(user=self.request.user) def get_serializer_class(self): # 动态序列化,当显示订单详情,用另一个Serializer if self.action == 'retrieve': return OrderInfoDetailSerializer else: return OrderInfoSerializer def perform_create(self, serializer): # 完成创建后保存到数据库,可以拿到保存的值 order = serializer.save() shopping_carts = ShoppingCart.objects.filter(user=self.request.user) # 将该用户购物车所有商品都取出来放在订单商品中 for shopping_cart in shopping_carts: OrderGoods.objects.create( order=order, goods=shopping_cart.goods, goods_nums=shopping_cart.nums ) # 然后清空该用户购物车 shopping_carts.delete() ``` 访问 http://127.0.0.1:8000/orderinfo/3/?format=json 可以看到订单详情序列化的内容,格式化为json,变化收起 ![BLOG_20190722_210414_88](/media/blog/images/2019/07/BLOG_20190722_210414_88.png "博客图集BLOG_20190722_210414_88.png") #### Vue中订单商品列表分析 ![BLOG_20190722_210407_18](/media/blog/images/2019/07/BLOG_20190722_210407_18.png "博客图集BLOG_20190722_210407_18.png") 访问 http://127.0.0.1:8080/#/app/home/member/orderDetail/3 可以进入该订单详情页 组件创建时,获取url的订单id,然后请求订单详情 ```JavaScript // src/views/member/orderDetail.vue created() { this.orderId = this.$route.params.orderId; this.getOrderInfo(); //this.getReceiveByOrderId(); }, ``` 获取订单详情 ```JavaScript // src/views/member/orderDetail.vue getOrderInfo() { //获取订单信息 getOrderDetail(this.orderId).then((response) => { this.orderInfo = response.data; var totalPrice = 0; //console.log(response.data.order_goods); response.data.order_goods.forEach(function (entry) { totalPrice += entry.goods_nums * entry.goods.shop_price }); this.totalPrice = totalPrice }).catch(function (error) { console.log(error); }); }, ``` 这里面请求api ```JavaScript // src/api/api.js //获取订单详情 export const getOrderDetail = orderId => { return axios.get(`${local_host}/orderinfo/` + orderId + '/') }; ``` 请求成功后,在页面中显示 ```html <!-- src/views/member/orderDetail.vue --> <div class="userCenterBox boxCenterList clearfix" style="_height:1%;"> <h5><span>订单状态</span></h5> <div class="blank"></div> <table width="100%" border="0" cellpadding="5" cellspacing="1" bgcolor="#dddddd"> <tbody> <tr> <td width="15%" align="right" bgcolor="#ffffff">订单号:</td> <td align="left" bgcolor="#ffffff">{{orderInfo.order_sn}} <!-- <a href="http://sx.youxueshop.com/user.php?act=message_list&amp;order_id=778" class="f6">[发送/查看商家留言]</a> --> </td> </tr> <tr> <td align="right" bgcolor="#ffffff">订单状态:</td> <td v-if="orderInfo.pay_status == 'topaid' " align="left" bgcolor="#ffffff">待支付&nbsp;&nbsp;&nbsp;&nbsp;<div style="text-align:center"><a :href="orderInfo.alipay_url"><input type="button" onclick="" value="立即使用支付宝支付"></a></div> </td> <td v-if="orderInfo.pay_status == 'success' " align="left" bgcolor="#ffffff">已支付</td> </tr> </tbody> </table> <table></table> <div class="blank"></div> <h5> <span>商品列表</span> </h5> <div class="blank"></div> <table width="100%" border="0" cellpadding="5" cellspacing="1" bgcolor="#dddddd"> <tbody> <tr> <th width="30%" align="center" bgcolor="#ffffff">商品名称</th> <!--<th>市场价</th>--> <th width="19%" align="center" bgcolor="#ffffff">商品价格</th> <th width="9%" align="center" bgcolor="#ffffff">购买数量</th> <th width="20%" align="center" bgcolor="#ffffff">小计</th> </tr> <tr v-for="item in orderInfo.order_goods"> <td bgcolor="#ffffff"> <router-link :to="'/app/home/productDetail/'+item.id" class="f6">{{item.goods.name}}</router-link> <!-- <a href="" target="_blank" class="f6">{{item.name}}</a> --> </td> <td align="center" bgcolor="#ffffff">¥{{item.goods.shop_price}}元</td> <td align="center" bgcolor="#ffffff">{{item.goods_nums}}</td> <td align="center" bgcolor="#ffffff">¥{{item.goods.shop_price*item.goods_nums}}元</td> </tr> <tr> <td colspan="8" bgcolor="#ffffff" align="right"> 商品总价: ¥{{totalPrice}}元 </td> </tr> </tbody> </table> <div class="blank"></div> <div class="blank"></div> <h5><span>收货人信息</span></h5> <div class="blank"></div> <form name="formAddress" id="formAddress"> <table width="100%" border="0" cellpadding="5" cellspacing="1" bgcolor="#dddddd"> <tbody> <tr> <td width="15%" align="right" bgcolor="#ffffff">收货人姓名:</td> <td width="35%" align="left" bgcolor="#ffffff"><input name="consignee" type="text" class="inputBg" v-model="orderInfo.signer_name" size="25"> </td> <td width="15%" align="right" bgcolor="#ffffff">收货地址:</td> <td width="35%" align="left" bgcolor="#ffffff"><input name="email" type="text" class="inputBg" v-model="orderInfo.address" size="25"> </td> </tr> <tr> <td align="right" bgcolor="#ffffff">电话:</td> <td align="left" bgcolor="#ffffff"><input name="address" type="text" class="inputBg" v-model="orderInfo.signer_mobile" size="25"></td> </tr> </tbody> </table> </form> <div class="blank"></div> </div> ``` 也就是上图中的效果。

很赞哦! (1)

文章交流

  • emoji
0人参与,0条评论

当前用户

未登录,点击   登录

站点信息

  • 建站时间:网站已运行2074天
  • 系统信息:Linux
  • 后台程序:Python: 3.8.10
  • 网站框架:Django: 3.2.6
  • 文章统计:256 篇
  • 文章评论:60 条
  • 腾讯分析网站概况-腾讯分析
  • 百度统计网站概况-百度统计
  • 公众号:微信扫描二维码,关注我们
  • QQ群:QQ加群,下载网站的学习源码
返回
顶部
标题 换行 登录
网站