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

专题:Vue+Django REST framework前后端分离生鲜电商

Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。
Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ;
Django版本:2.2、djangorestframework:3.9.2。

更多内容请点击 我的博客 查看,欢迎来访。

订单管理接口功能

先看下之前的模型

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不直接使用外键的原因是:用户如果修改了个人中心的收货地址,订单中地址就会跟着变化,显然是不对的,所以订单中的地址就保存一个当时下单的地址组合的字符串。

修改后

# 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创建订单序列化

序列化中用户也是需要不可见的

# 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原因是,订单一般不允许修改的,所有要自定义继承

# 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

# 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生成的页面

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第1张图片

对于订单提交,会告诉后台创建一个订单,在这提交订单是一个比较简单的逻辑,即将购物车ShoppingCart中的该用户对应的单品和数量,添加到订单商品OrderGoods中,然后清空该用户的购物车。

OrderInfoViewSet添加购物车的商品到订单

可以在OrderInfoViewSet中重载perform_create(self, serializer)方法(这个在mixins.CreateModelMixin类中)

    def perform_create(self, serializer):
        serializer.save()

.save()之前,需要生成一个订单号,所以修改序列化类,在数据验证通过后生成一个订单号,修改OrderInfoSerializer,重载validate(self, attrs)方法

# 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)方法

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隐藏不能显示的字段

在上面的图中,订单号、支付交易号、订单状态、支付日期(需要支付后才有时间)信息是不能显示给用户的,只能后台去修改

# 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__"

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第2张图片

return attrs打上断点调试,在DRF中 http://127.0.0.1:8000/orderinfo/ 填入一些信息后POST,可以在后端看到attrs的内容

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第3张图片

然后进入OrderInfoViewSet完成创建perform_create处理,将购物车的商品添加到订单商品中,并删除购物车商品

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第4张图片

访问 http://127.0.0.1:8000/orderinfo/1/ 可以看到有订单删除

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第5张图片

该功能可以将订单关联的商品一起删除。

Vue中订单接口联调

访问 http://127.0.0.1:8080/#/app/home/member/order 可以查看我的订单

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第6张图片

由于刚才订单删除了,所以为空。在Vue中,添加商品到购物车,然后选择配送地址,点击去结算

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第7张图片

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第8张图片

订单创建功能分析

当用户准备创建订单时,会点击去结算



<p class="sumup"><a class="btn" @click="balanceCount">去结算a>p>

点击之后进入

// 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 中的接口

//添加订单
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



<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>

当组件创建时,调用

// src/views/member/order.vue

created () {
    this.getOrder();
},

// src/views/member/order.vue

getOrder () {
    getOrders().then((response)=> {
        this.orders = response.data;
    }).catch(function (error) {
        console.log(error);
    });
},

该函数会请求api获取订单数据

// src/api/api.js

//获取订单
export const getOrders = () => {
    return axios.get(`${local_host}/orderinfo/`)
};

将结果遍历显示到表格中。

BLOG_20190722_210439_56

这是一个待支付状态,另外还有一个取消订单操作,用户点击取消订单,将该订单id传递给cancelOrder (id)函数

//src/views/member/order.vue

cancelOrder (id) {
    alert('您确认要取消该订单吗?取消后此订单将视为无效订单');
    delOrder(id).then((response)=> {
      alert('订单删除成功')
    }).catch(function (error) {
        console.log(error);
    });
},

请求api对订单进行删除

//src/api/api.js

//删除订单
export const delOrder = orderId => {
    return axios.delete(`${local_host}/orderinfo/` + orderId + '/')
};

订单详情显示

点击订单号可以进入订单详情

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第9张图片

但是订单中的商品列表没有正常显示出来,所以要动态使用序列化,在显示订单详情时,使用另一个Serializer,并返回商品详情信息

创建OrderInfoDetailSerializer用于订单详情显示

添加一个新的序列化类,用于显示订单详情

# 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

# 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,变化收起

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第10张图片

Vue中订单商品列表分析

【Vue+DRF生鲜电商】26.订单接口功能,Vue和订单接口联调_第11张图片

访问 http://127.0.0.1:8080/#/app/home/member/orderDetail/3 可以进入该订单详情页

组件创建时,获取url的订单id,然后请求订单详情

// src/views/member/orderDetail.vue

created() {
    this.orderId = this.$route.params.orderId;
    this.getOrderInfo();
    //this.getReceiveByOrderId();
},

获取订单详情

// 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

// src/api/api.js

//获取订单详情
export const getOrderDetail = orderId => {
    return axios.get(`${local_host}/orderinfo/` + orderId + '/')
};

请求成功后,在页面中显示



<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}}
                
            td>
        tr>
        <tr>
            <td align="right" bgcolor="#ffffff">订单状态:td>
            <td v-if="orderInfo.pay_status == 'topaid' " align="left" bgcolor="#ffffff">待支付    <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 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>
                
            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>

也就是上图中的效果。

你可能感兴趣的:(Web开发)