订单模型设计
-
后台下单逻辑
后台的下单逻辑是相当复杂的,这里只简单描述。
后台接收到下订单的请求,为用户创建一个订单,此时订单会有如下几种状态:
‘待支付’ ,等待用户付款
‘待发货’, 用户付款成功,等待商户发货
‘待收货’,,商户已发货,等待用户收货
‘待评价’, 用户已确认收货, 待用户评价商品
‘已完成’,订单已完成,处于关闭状态
实际生产环境可能会更复杂,比如,用户的退款,支付失败等。
订单创建完成,其相关的数据存入mysql数据库,在有效期内,如果用户没有完成支付,则订单失效,自动关闭,并从mysql 删除该订单信息。 -
订单表结构设计
订单表:
订单id、 varchar(100),主键
用户、 外键
收货地址、 外键
总价、decimal(9,2)
总商品数、int
运费、decimal(7, 2)
支付方式、char(10) (1支付宝 2银联)
支付状态、char(10)
创建时间、 datetime
更新时间, datetime
订单商品表
id、int 主键
商品id、外键
数量、 int
单价、decimal(7,2)
评价、text
评分、smallint, default =5 (0-5)
订单id、 外键
是否匿名评价、boolean
是否评价 boolean -
订单模型类定义
# 订单表:订单id、用户(外键)、收货地址(外键)、总价、总商品数、运费、支付方式、订单状态、创建时间
class Order(models.Model):
order_id = models.CharField(max_length=100, primary_key=True, verbose_name='订单号')
user = models.ForeignKey(User, related_name='order', on_delete=models.CASCADE, verbose_name='用户') #related_name用于反向查询 user.order.all()
address = models.ForeignKey(Addr, on_delete=models.CASCADE, verbose_name='收货地址')
total_amount = models.DecimalField(max_digits=9, decimal_places=2, verbose_name='总价')
freight = models.DecimalField(max_digits=7, decimal_places=2, verbose_name='运费')
total_count = models.IntegerField(verbose_name='商品总数', default=1)
pay_method = models.CharField(max_length=10, choices=((1, '支付宝'), (2, '银联')), verbose_name='支付方式')
pay_status = models.CharField(max_length=10, choices=((0, '待支付'), (1, '待发货'), (2, '待收货'), (3, '待评价'), (4, '已完成')), verbose_name='支付状态')
created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
def __str__(self):
return "订单:%s"%self.order_id
class Meta:
db_table = 'order_tb'
verbose_name = '订单表'
verbose_name_plural = verbose_name
# 订单商品表: id、商品id(外键)、数量、单价、评价、评分、订单id(外键)、是否匿名评价、是否评价
class OrderGoods(models.Model):
"""订单商品"""
SCORE_CHOICES = (
(0, '0分'),
(1, '20分'),
(2, '40分'),
(3, '60分'),
(4, '80分'),
(5, '100分'),
)
good = models.ForeignKey(Good, on_delete=models.CASCADE, verbose_name="订单商品")
count = models.IntegerField(default=1, verbose_name="数量")
price = models.DecimalField(max_digits=7, decimal_places=2, verbose_name="单价")
comment = models.TextField(default="", verbose_name="评价信息")
score = models.SmallIntegerField(choices=SCORE_CHOICES, default=5, verbose_name='满意度评分')
order = models.ForeignKey(Order, related_name='orderGood', on_delete=models.CASCADE, verbose_name="订单")
is_anonymous = models.BooleanField(default=False, verbose_name='是否匿名评价')
is_commented = models.BooleanField(default=False, verbose_name='是否评价')
class Meta:
db_table = "order_goods_table"
verbose_name = '订单商品表'
verbose_name_plural = verbose_name
def __str__(self):
return "订单%s中的商品%s"%(self.order.order_id, self.good.sku_name)
订单确认页面
- 下单流程讲解
- 在Vue的购物车页面,点击结算,跳转到Vue的订单确认页面
- 在订单确认页面:展示用户的收货地址。向Django后端发送请求,查询所有收货地址,在Vue中循环展示。
- 可以实现添加收货地址
- 选择支付方式
- 展示购物车中被选中的商品,从后端加载购物车中所有被选中的商品, 这里使用Vuex中管理的商品。
- 点击 ‘结算’ ,向Django后端发送请求,创建订单。
- 收货地址添加
在ConfirmOrder组件
<li class="add-address">
<i class="el-icon-circle-plus-outline" @click="addAddr"></i>
<p>添加新地址</p>
</li>
// 添加地址
addAddr(){
console.log("正添加地址...")
this.$store.dispatch("setAddMyAddr", true)
},
//展示添加地址组件AddMyAddr.vue
//子组件给父组件传值,使用的全局事件总线
另外还可以删除一个地址,直接发送delete请求。
- 收货地址确认
// ===========切换地址的选中==========
changeStatus(e, id){
for(let i=0; i < this.address.length; i++){
let temp = this.address[i]
if(temp.id == id){
temp.is_default = true
}else{
temp.is_default = false
}
}
},
订单购物信息展示
- 购物车选中商品展示
可以从后端Django加载购物车中选中的商品,这里因为Vuex集中式状态管理的选中状态与后端一致,直接使用Vuex中的数据。
创建订单
- 创建订单的流程
前端Vue的流程 + 后端Django的流程
// 创建订单
addOrder() {
this.$axios
.post("/orders/user/addOrder/", {
user: this.$store.getters.getUser,
products: this.getCheckGoods, //获取勾选的商品{}
addr: this.getSelectedAddr(),
payMethod: this.radio, //支付方式:1支付宝 2银联卡
})
.then(res => {
console.log("@@创建订单的响应:", res)
let products = this.getCheckGoods
switch (res.data.code) {
case 200: //多个条件执行共同代码,中间不能有间隔
case 302:// 添加如下, 跳转到支付地址
{
//创建订单成功,删除前端购物车中购买的商品
for (let i = 0; i < products.length; i++) {
const temp = products[i];
// 删除已经结算的购物车商品
this.deleteShoppingCart(temp.productID);
}
// 跳转我的订单页面
// this.$router.push({ path: "/order" });
this.notifySucceed(res.data.msg)
// 跳转到支付地址
setTimeout(window.location.href = res.data.url, 2000);
}
break;
default:
this.notifyError(res.data.msg)
}
})
.catch(err => {
return Promise.reject(err);
});
}
- Django后端创建订单
class OrderAPIView(APIView):
@check_login
def post(self, request):
# 1. 获取当前登录用户
user = request.user
# 2. 接收前端数据products商品对象数组/addr(选中的地址id)/payMethod(str)
goods_list = request.data.get("products")
addr_id = request.data.get("addr")
pay_method = request.data.get("payMethod")
# 3. 准备订单数据
# 唯一的订单编号
order_id = datetime.now().strftime("%Y%m%d%H%M%S") + "_%s"%user.id
# 查询地址对象
try:
addr = Addr.objects.get(id=addr_id)
except:
return Response({"code":204, "msg":"收货地址不存在"})
total_amount = 0
total_count = 0
freight = 10
# 4. 创建订单对象
order = Order.objects.create(order_id=order_id, user=user, addr=addr, total_amount=total_amount, total_count=total_count, freight=freight, pay_method=pay_method, pay_status="0")
# 5.
# 创建订单商品对象,注意判断库存是否满足要求
# 如果库存不足,就返回响应
# 如果库存充足,正常购买,正常创建订单商品对象,并存储到数据库
for i in goods_list:
#i就是每一个商品字典
try:
good = Good.objects.get(id=i.get("productID"))
origin_stock = good.stock
origin_count = good.count
#raise ValueError("商品库存不足!")
except:
return Response({"code":204, "msg":"商品已下架!"})
#对比库存
if i.get("num") > origin_stock:
return Response({"code":204, "msg":"商品库存不足,无法购买!"})
#创建订单商品对象,并保存
OrderGoods.objects.create(good=good, count=i.get("num"), price=good.selling_price, order=order)
# 6.
# 更新购买商品的库存、销量。 更新一下订单里的总商品数 & 总价格
new_stock = origin_stock - i.get("num")
new_count = origin_count + i.get("num")
good.stock = new_stock
good.count = new_count
good.save()
total_count += i.get("num")
total_amount += i.get("num")*i.get("selling_price")
order.total_count = total_count
order.total_amount = total_amount
order.save()
# 7.
# 删除redis购物车里的已经购买的商品
redis_conn = redis.Redis(host="localhost", port=6379, db=0)
cart_key = "cart:%s"%user.id
cart_selected_key = "cart_selected:%s"%user.id
for i in goods:
redis_conn.hdel(cart_key, i.get("productID"))
redis_conn.srem(cart_selected_key, i.get("productID"))
redis_conn.close()
# 7.
# 返回响应
# {'code': 200, "msg": "创建订单成功!"}
# 201, 防止前端重定向
return Response({"code":201, "msg":"创建订单成功!"})
- 后端查看订单
在后端mysql数据库中,查看创建订单是否成功。