14.1 带你实现订单配送功能
效果
表格里展示配送信息
SQL orders
CREATE TABLE `orders` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '订单编号',
`total` decimal(10,2) DEFAULT NULL COMMENT '总价格',
`user_id` int DEFAULT NULL COMMENT '下单人ID',
`status` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '状态',
`time` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '下单时间',
`deliver_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '配送类型',
`address` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '收货地址',
`deliver` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '配送信息',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单信息';
后端接口逻辑
Orders
package com.example.entity;
import java.math.BigDecimal;
import java.util.List;
public class Orders {
/**主键ID */
private Integer id;
/**订单编号 */
private String orderNo;
/**总价格 */
private BigDecimal total;
/**下单人ID */
private Integer userId;
private String userName;
/**状态 */
private String status;
/**下单时间 */
private String time;
/**配送类型 */
private String deliverType;
private String address;
private String deliver;
private List<Cart> cartList;
private List<OrderDetail> orderDetailList;
private String goodsName;
public String getGoodsName() {
return goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDeliver() {
return deliver;
}
public void setDeliver(String deliver) {
this.deliver = deliver;
}
public List<OrderDetail> getOrderDetailList() {
return orderDetailList;
}
public void setOrderDetailList(List<OrderDetail> orderDetailList) {
this.orderDetailList = orderDetailList;
}
public List<Cart> getCartList() {
return cartList;
}
public void setCartList(List<Cart> cartList) {
this.cartList = cartList;
}
public String getDeliverType() {
return deliverType;
}
public void setDeliverType(String deliverType) {
this.deliverType = deliverType;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public BigDecimal getTotal() {
return total;
}
public void setTotal(BigDecimal total) {
this.total = total;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
OrdersMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.OrdersMapper">
<select id="selectAll" resultType="com.example.entity.Orders">
select orders.*, user.name as userName from `orders`
left join user on orders.user_id = user.id
left join order_detail on order_detail.order_id = orders.id
<where>
<if test="orderNo != null"> and orders.order_no like concat('%', #{orderNo}, '%')</if>
<if test="goodsName != null"> and order_detail.goods_name like concat('%', #{goodsName}, '%')</if>
<if test="userId != null"> and orders.user_id = #{userId}</if>
</where>
group by orders.id
order by orders.id desc
</select>
<select id="selectById" resultType="com.example.entity.Orders">
select * from `orders` where id = #{id}
</select>
<delete id="deleteById">
delete from `orders` where id = #{id}
</delete>
<insert id="insert" parameterType="com.example.entity.Orders" useGeneratedKeys="true" keyProperty="id">
insert into `orders`
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="orderNo != null">order_no,</if>
<if test="total != null">total,</if>
<if test="userId != null">user_id,</if>
<if test="status != null">status,</if>
<if test="time != null">time,</if>
<if test="deliverType != null">deliver_type,</if>
<if test="address != null">address,</if>
<if test="deliver != null">deliver,</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="orderNo != null">#{orderNo},</if>
<if test="total != null">#{total},</if>
<if test="userId != null">#{userId},</if>
<if test="status != null">#{status},</if>
<if test="time != null">#{time},</if>
<if test="deliverType != null">#{deliverType},</if>
<if test="address != null">#{address},</if>
<if test="deliver != null">#{deliver},</if>
</trim>
</insert>
<update id="updateById" parameterType="com.example.entity.Orders">
update `orders`
<set>
<if test="id != null">
id = #{id},
</if>
<if test="orderNo != null">
order_no = #{orderNo},
</if>
<if test="total != null">
total = #{total},
</if>
<if test="userId != null">
user_id = #{userId},
</if>
<if test="status != null">
status = #{status},
</if>
<if test="time != null">
time = #{time},
</if>
<if test="deliverType != null">
deliver_type = #{deliverType},
</if>
<if test="address != null">
address = #{address},
</if>
<if test="deliver != null">
deliver = #{deliver},
</if>
</set>
where id = #{id}
</update>
</mapper>
OrdersService.java
package com.example.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.example.entity.*;
import com.example.exception.CustomException;
import com.example.mapper.*;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 业务处理
**/
@Service
public class OrdersService {
@Resource
private OrdersMapper ordersMapper;
@Resource
GoodsMapper goodsMapper;
@Resource
UserMapper userMapper;
@Resource
OrderDetailMapper orderDetailMapper;
@Resource
CartMapper cartMapper;
/**
* 购物车批量和单个商品 通用的下单接口
*/
@Transactional
public void add(Orders orders) {
orders.setStatus("待接单");
orders.setTime(DateUtil.now());
// 随机的订单编号 20251202
String orderNo = DateUtil.format(new Date(), "yyyyMMdd") + System.currentTimeMillis() + RandomUtil.randomNumbers(4);
orders.setOrderNo(orderNo);
ordersMapper.insert(orders);
Integer orderId = orders.getId();
List<Cart> cartList = orders.getCartList();
User user = userMapper.selectById(orders.getUserId());
BigDecimal totalPrice = BigDecimal.ZERO;
for (Cart cart : cartList) {
Integer goodsId = cart.getGoodsId();
Goods goods = goodsMapper.selectById(goodsId);
if (goods.getStore() < cart.getNum()) { // 库存不足
throw new CustomException(goods.getName() + "商品库存不足");
}
goods.setStore(goods.getStore() - cart.getNum()); // 扣减库存
goods.setSaleCount(goods.getSaleCount() + cart.getNum()); // 增加销量
goodsMapper.updateById(goods); // 更新商品的库存
// 新增订单详情
OrderDetail orderDetail = new OrderDetail();
orderDetail.setNum(cart.getNum());
orderDetail.setGoodsId(goodsId);
orderDetail.setGoodsImg(goods.getImg());
orderDetail.setGoodsName(goods.getName());
orderDetail.setGoodsPrice(goods.getPrice());
orderDetail.setOrderId(orderId);
orderDetailMapper.insert(orderDetail);
// 删除下单的商品所对应的购物车记录
if (cart.getId() != null) {
cartMapper.deleteById(cart.getId());
}
totalPrice = totalPrice.add(goods.getPrice().multiply(BigDecimal.valueOf(cart.getNum())));
}
if (user.getAccount().compareTo(totalPrice) < 0) { // 用户的余额小于订单的总价格
throw new CustomException("对不起,您的账户余额不足,请充值!");
}
user.setAccount(user.getAccount().subtract(totalPrice));
userMapper.updateById(user); // 更新余额
orders.setTotal(totalPrice);
ordersMapper.updateById(orders); // 更新订单总额
}
/**
* 删除
*/
@Transactional
public void deleteById(Integer id) {
ordersMapper.deleteById(id);
orderDetailMapper.deleteByOrderId(id);
}
/**
* 修改
*/
@Transactional
public void updateById(Orders orders) {
if ("已取消".equals(orders.getStatus())) {
Integer userId = orders.getUserId();
User user = userMapper.selectById(userId);
user.setAccount(user.getAccount().add(orders.getTotal()));
userMapper.updateById(user);
// 商品库存 加回去 商品的销量 减去
OrderDetail orderDetail = new OrderDetail();
orderDetail.setOrderId(orders.getId());
List<OrderDetail> orderDetailList = orderDetailMapper.selectAll(orderDetail);
for (OrderDetail detail : orderDetailList) {
Integer goodsId = detail.getGoodsId();
Goods goods = goodsMapper.selectById(goodsId);
if (goods != null) {
goods.setStore(goods.getStore() + detail.getNum());
goods.setSaleCount(goods.getSaleCount() - detail.getNum());
goodsMapper.updateById(goods);
}
}
}
ordersMapper.updateById(orders);
}
/**
* 根据ID查询
*/
public Orders selectById(Integer id) {
return ordersMapper.selectById(id);
}
/**
* 查询所有
*/
public List<Orders> selectAll(Orders orders) {
return ordersMapper.selectAll(orders);
}
/**
* 分页查询
*/
public PageInfo<Orders> selectPage(Orders orders, Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Orders> list = ordersMapper.selectAll(orders);
for (Orders o : list) {
OrderDetail orderDetail = new OrderDetail();
orderDetail.setOrderId(o.getId());
List<OrderDetail> orderDetailList = orderDetailMapper.selectAll(orderDetail);
o.setOrderDetailList(orderDetailList);
}
return PageInfo.of(list);
}
}
前端页面逻辑
GoodsDetail.vue
<template>
<div class="front-container" style="width: 50%">
<div class="card" style="padding: 20px; display: flex; grid-gap: 20px; margin-bottom: 10px">
<img :src="data.goods.img" alt="" style="width: 300px; height: 300px">
<div style="flex: 1">
<div style="display: flex; align-items: flex-start; grid-gap: 20px; margin-bottom: 10px">
<div style="font-size: 22px; font-weight: bold; line-height: 25px; flex: 1">
<el-tag style="margin-right: 5px; float: left; background-color: red; color: white" type="danger" v-if="data.goods.recommend === '是'">推荐</el-tag>
{{ data.goods.name }}
</div>
<div style="width: 60px; cursor: pointer; color: #666" @click="addCollect" v-if="!data.userCollect?.id">
<el-icon style="position: relative; top: 3px" size="18"><Star /></el-icon>收藏
</div>
<div style="width: 100px; cursor: pointer; color: orange" @click="removeCollect" v-if="data.userCollect?.id">
<el-icon style="position: relative; top: 3px" size="18"><StarFilled /></el-icon>取消收藏
</div>
</div>
<div style="margin-bottom: 20px">
<span style="color: red; font-size: 18px">¥</span><b style="color: red; font-size: 30px">{{ data.goods.price }}</b>
<span style="color: #666; margin-left: 20px">累计销量 {{ data.goods.saleCount }}</span>
<span style="color: #666; margin-left: 20px">剩余库存 {{ data.goods.store }}</span>
</div>
<div style="margin-bottom: 20px; padding: 10px; border-radius: 5px; background-color: #e8e4e4; line-height: 25px; text-align: justify">{{ data.goods.description }}</div>
<div>
<el-input-number style="width: 150px; height: 40px" :min="1" v-model="data.num"></el-input-number>
<el-button @click="addCart" style="height: 40px; margin-left: 5px" type="danger">加入购物车</el-button>
<el-button @click="handleAddOrder" style="height: 40px; margin-left: 5px" type="danger">立即购买</el-button>
</div>
<div style="margin-top: 10px; color: #666">校园小卖部销售并发货的商品,由小卖部提供发票和相应的售后服务。请您放心购买!</div>
</div>
</div>
<div class="card" style="padding: 20px; margin-bottom: 50px">
<div style="font-size: 20px; padding-bottom: 10px; border-bottom: 1px solid #ddd">
<span @click="changeTab('商品详情')" style="cursor: pointer" :class="{'current-active': data.current === '商品详情' }">商品详情</span>
<span @click="changeTab('商品评论')" :class="{'current-active': data.current === '商品评论' }" style="cursor: pointer; margin-left: 20px">商品评论</span>
</div>
<div v-if="data.current === '商品详情'" style="padding: 10px" v-html="data.goods.content"></div>
<div v-if="data.current === '商品评论'" style="min-height: 700px">
<div v-if="data.commentList.length === 0" style="padding: 50px; text-align: center; color: #666">暂无评论...</div>
<div v-if="data.commentList.length > 0" style="padding: 20px; text-align: center">
<!-- 显示评论列表-->
</div>
</div>
</div>
<el-dialog title="下单信息" width="30%" v-model="data.formVisible" :close-on-click-modal="false" destroy-on-close>
<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="100px" style="padding-right: 30px">
<el-form-item label="配送类型" prop="deliverType">
<el-radio-group v-model="data.form.deliverType">
<el-radio-button value="自提" label="自提"></el-radio-button>
<el-radio-button value="外送" label="外送"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="收货地址" prop="address" v-if="data.form.deliverType === '外送'">
<el-input v-model="data.form.address" type="textarea" :rows="3" placeholder="请输入外送的接收地址,包括联系人、联系电话、地址信息"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false">取 消</el-button>
<el-button type="primary" @click="addOrder">确 认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref } from "vue";
import router from "@/router";
import request from "@/utils/request";
import {ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
id: router.currentRoute.value.query.id,
goods: {},
num: 1,
current: '商品详情',
commentList: [],
userCollect: {},
form: {},
formVisible: false,
rules: {
deliverType: [
{ required: true, message: '请选择配送类型', trigger: 'change' }
],
address: [
{ required: true, message: '请输入配送地址', trigger: 'blur' }
]
}
})
const handleAddOrder = () => {
data.form = {}
data.formVisible = true
}
const addOrder = () => {
formRef.value.validate(valid => {
if (valid) {
data.form.userId = data.user.id
data.form.cartList = [ {goodsId: data.id, num: data.num } ]
request.post('/orders/add', data.form).then(res => {
if (res.code === '200') {
ElMessage.success('下单成功')
load()
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
})
}
const addCart = () => {
request.post('/cart/add', { goodsId: data.id, num: data.num, userId: data.user.id }).then(res => {
if (res.code === '200') {
ElMessage.success('加入购物车成功')
} else {
ElMessage.error(res.msg)
}
})
}
// 当前的商品是否被当前登录的用户收藏过
const loadCollect = () => {
request.get('/collect/selectAll', {
params:{
goodsId: data.id,
userId: data.user.id
}
}).then(res => {
if (res.data?.length > 0) { // 查询到数据了 表示用户收藏过了
data.userCollect = res.data[0]
} else {
data.userCollect = {}
}
})
}
loadCollect()
// 取消收藏
const removeCollect = () => {
request.delete('/collect/delete/' + data.userCollect.id).then(res => {
if (res.code === '200') {
ElMessage.success('操作成功')
loadCollect()
} else {
ElMessage.error(res.msg)
}
})
}
const addCollect = () => {
request.post('/collect/add', { goodsId: data.id, userId: data.user.id }).then(res => {
if (res.code === '200') {
ElMessage.success('操作成功')
loadCollect()
} else {
ElMessage.error(res.msg)
}
})
}
const changeTab = (tabName) => {
data.current = tabName
}
const load = () => {
request.get('/goods/selectById/' + data.id).then(res => {
data.goods = res.data
})
}
load()
</script>
<style>
.current-active {
color: red;
border-bottom: 2px solid red;
padding-bottom: 10px
}
</style>
UserOrders.vue
<template>
<div class="front-container" style="width: 80%">
<div style="margin-bottom: 10px;">
<el-input clearable @clear="load" v-model="data.orderNo" style="width: 400px; height: 40px; margin-right: 10px" placeholder="请输入订单编号查询"></el-input>
<el-input clearable @clear="load" v-model="data.goodsName" style="width: 400px; height: 40px; margin-right: 10px" placeholder="请输入商品名称查询"></el-input>
<el-button style="height: 40px" type="primary" @click="load">查 询</el-button>
</div>
<div class="card">
<el-table :data="data.tableData" stripe :cell-style="{'backgroundColor': '#e8efff'}" default-expand-all>
<el-table-column type="expand">
<template #default="props">
<div style="padding: 10px">
<el-table :data="props.row.orderDetailList" border>
<el-table-column label="商品图片" prop="goodsImg" width="100">
<template #default="scope">
<img :src="scope.row.goodsImg" alt="" style="width: 50px; height: 50px">
</template>
</el-table-column>
<el-table-column label="商品名称" prop="goodsName" show-overflow-tooltip></el-table-column>
<el-table-column label="商品单价" prop="goodsPrice" width="100"></el-table-column>
<el-table-column label="数量" prop="num" width="100">
<template #default="scope">
X {{ scope.row.num }}
</template>
</el-table-column>
<el-table-column label="小计" width="150">
<template #default="scope">
<b style="color: red">{{ (scope.row.goodsPrice * scope.row.num).toFixed(2) }} 元</b>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column prop="orderNo" label="订单编号" width="240">
<template #default="scope">
<b style="color: #333">{{ scope.row.orderNo }}</b>
</template>
</el-table-column>
<el-table-column prop="total" label="总价格">
<template #default="scope">
<b style="color: red">{{ scope.row.total }}元</b>
</template>
</el-table-column>
<el-table-column prop="deliverType" label="配送类型"></el-table-column>
<el-table-column prop="status" label="状态">
<template #default="scope">
<el-tag type="danger" v-if="scope.row.status === '已取消'">已取消</el-tag>
<el-tag type="warning" v-if="scope.row.status === '待接单'">待接单</el-tag>
<el-tag type="primary" v-if="scope.row.status === '已配送'">已配送</el-tag>
<el-tag type="primary" v-if="scope.row.status === '已出货'">已出货</el-tag>
<el-tag type="success" v-if="scope.row.status === '已完成'">已完成</el-tag>
</template>
</el-table-column>
<el-table-column prop="time" label="下单时间"></el-table-column>
<el-table-column prop="address" label="地址" width="300"></el-table-column>
<el-table-column prop="deliver" label="配送信息" width="300"></el-table-column>
<el-table-column label="订单操作" align="center" width="120">
<template #default="scope">
<el-button @click="cancel(scope.row)" type="danger" v-if="scope.row.status === '待接单'">取消</el-button>
<el-button type="success" v-if="scope.row.status === '已完成'">评价</el-button>
<el-button @click="done(scope.row)" type="primary" v-if="scope.row.status === '已出货' || scope.row.status === '已配送'">确认收货</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 20px">
<el-pagination @current-change="load" layout="total, prev, pager, next" v-model:page-size="data.pageSize" v-model:current-page="data.pageNum" :total="data.total"/>
</div>
</div>
</div>
</template>
<script setup>
import request from "@/utils/request";
import {reactive, ref} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
pageNum: 1,
pageSize: 3,
total: 0,
formVisible: false,
form: {},
tableData: [],
orderNo: null,
goodsName: null,
rules: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
]
}
})
// 分页查询
const load = () => {
request.get('/orders/selectPage', {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
orderNo: data.orderNo,
goodsName: data.goodsName,
userId: data.user.id
}
}).then(res => {
data.tableData = res.data?.list
data.total = res.data?.total
})
}
load()
// 新增
const handleAdd = () => {
data.form = {}
data.formVisible = true
}
// 编辑
const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row))
data.formVisible = true
}
// 新增保存
const add = () => {
request.post('/orders/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
const cancel = (row) => {
ElMessageBox.confirm('您确认取消订单吗?', '二次确认', { type: 'warning' }).then(res => {
data.form = row
data.form.status = '已取消'
update()
}).catch(err => {})
}
const done = (row) => {
ElMessageBox.confirm('您确认订单货物已经收到了吗?', '二次确认', { type: 'warning' }).then(res => {
data.form = row
data.form.status = '已完成'
update()
}).catch(err => {})
}
// 编辑保存
const update = () => {
request.put('/orders/update', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}
// 弹窗保存
const save = () => {
formRef.value.validate(valid => {
if (valid) {
// data.form有id就是更新,没有就是新增
data.form.id ? update() : add()
}
})
}
// 删除
const handleDelete = (id) => {
ElMessageBox.confirm('删除后数据无法恢复,您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {
request.delete('/orders/delete/' + id).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}
// 重置
const reset = () => {
data.orderNo = null
data.goodsName = null
load()
}
</script>
<style scoped>
.el-tag {
font-weight: bold;
}
.el-tag--warning {
color: orange;
background-color: #fff2de;
}
</style>
Orders.vue
<template>
<div>
<div class="card" style="margin-bottom: 5px;">
<el-input v-model="data.orderNo" style="width: 300px; margin-right: 10px" placeholder="请输入订单编号查询"></el-input>
<el-button type="primary" @click="load">查询</el-button>
<el-button type="info" style="margin: 0 10px" @click="reset">重置</el-button>
</div>
<div class="card" style="margin-bottom: 5px">
<el-table :data="data.tableData" stripe :cell-style="{'backgroundColor': '#e8efff'}" default-expand-all>
<el-table-column type="expand">
<template #default="props">
<div style="padding: 10px">
<el-table :data="props.row.orderDetailList" border>
<el-table-column label="商品图片" prop="goodsImg" width="100">
<template #default="scope">
<img :src="scope.row.goodsImg" alt="" style="width: 50px; height: 50px">
</template>
</el-table-column>
<el-table-column label="商品名称" prop="goodsName" show-overflow-tooltip></el-table-column>
<el-table-column label="商品单价" prop="goodsPrice" width="100"></el-table-column>
<el-table-column label="数量" prop="num" width="100">
<template #default="scope">
X {{ scope.row.num }}
</template>
</el-table-column>
<el-table-column label="小计" width="150">
<template #default="scope">
<b style="color: red">{{ (scope.row.goodsPrice * scope.row.num).toFixed(2) }} 元</b>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column prop="orderNo" label="订单编号">
<template #default="scope">
<b style="color: #333">{{ scope.row.orderNo }}</b>
</template>
</el-table-column>
<el-table-column prop="total" label="总价格">
<template #default="scope">
<b style="color: red">{{ scope.row.total }}元</b>
</template>
</el-table-column>
<el-table-column prop="userName" label="下单人"></el-table-column>
<el-table-column prop="deliverType" label="配送类型"></el-table-column>
<el-table-column prop="status" label="状态">
<template #default="scope">
<el-tag type="danger" v-if="scope.row.status === '已取消'">已取消</el-tag>
<el-tag type="warning" v-if="scope.row.status === '待接单'">待接单</el-tag>
<el-tag type="primary" v-if="scope.row.status === '已配送'">已配送</el-tag>
<el-tag type="primary" v-if="scope.row.status === '已出货'">已出货</el-tag>
<el-tag type="success" v-if="scope.row.status === '已完成'">已完成</el-tag>
</template>
</el-table-column>
<el-table-column prop="time" label="下单时间"></el-table-column>
<el-table-column prop="address" label="地址" width="300"></el-table-column>
<el-table-column prop="deliver" label="配送信息" width="300"></el-table-column>
<el-table-column label="订单操作" align="center" width="100">
<template #default="scope">
<el-button v-if="scope.row.deliverType === '自提' && scope.row.status === '待接单'" type="primary" @click="out(scope.row)">出货</el-button>
<el-button v-if="scope.row.deliverType === '外送' && scope.row.status === '待接单'" type="primary" @click="handleDeliver(scope.row)">配送</el-button>
</template>
</el-table-column>
<el-table-column label="删除" align="center" width="100">
<template #default="scope">
<el-button type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="card">
<el-pagination @current-change="load" background layout="total, prev, pager, next" v-model:page-size="data.pageSize" v-model:current-page="data.pageNum" :total="data.total"/>
</div>
<el-dialog title="订单信息" width="30%" v-model="data.formVisible" :close-on-click-modal="false" destroy-on-close>
<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding-right: 30px; padding-top: 20px">
<el-form-item label="配送信息" prop="deliver">
<el-input placeholder="请输入配送员名称、联系方式" type="textarea" :rows="3" v-model="data.form.deliver" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false">取 消</el-button>
<el-button type="primary" @click="save">保 存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import request from "@/utils/request";
import {reactive, ref} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
pageNum: 1,
pageSize: 3,
total: 0,
formVisible: false,
form: {},
tableData: [],
name: null,
rules: {
deliver: [
{ required: true, message: '请输入配送信息', trigger: 'blur' },
]
}
})
// 分页查询
const load = () => {
request.get('/orders/selectPage', {
params: {
pageNum: data.pageNum,
pageSize: data.pageSize,
orderNo: data.orderNo
}
}).then(res => {
data.tableData = res.data?.list
data.total = res.data?.total
})
}
load()
// 新增
const handleAdd = () => {
data.form = {}
data.formVisible = true
}
// 编辑
const handleDeliver = (row) => {
data.form = JSON.parse(JSON.stringify(row))
data.formVisible = true
}
// 新增保存
const add = () => {
request.post('/orders/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
const out = (row) => {
ElMessageBox.confirm('您确认订单已出货吗?', '二次确认', { type: 'warning' }).then(res => {
data.form = row
data.form.status = '已出货'
request.put('/orders/update', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}
// 编辑保存
const update = () => {
if (data.form.deliverType === '自提') {
data.form.status = '已出货'
}
if (data.form.deliverType === '外送') {
data.form.status = '已配送'
}
request.put('/orders/update', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 弹窗保存
const save = () => {
formRef.value.validate(valid => {
if (valid) {
// data.form有id就是更新,没有就是新增
data.form.id ? update() : add()
}
})
}
// 删除
const handleDelete = (id) => {
ElMessageBox.confirm('删除后数据无法恢复,您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {
request.delete('/orders/delete/' + id).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}
// 重置
const reset = () => {
data.orderNo = null
load()
}
</script>
<style scoped>
.el-tag {
font-weight: bold;
}
.el-tag--warning {
color: orange;
background-color: #fff2de;
}
</style>
Cart.vue
<template>
<div class="front-container">
<div class="card" style="padding: 20px">
<div>
<el-table :data="data.tableData" stripe @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="商品图片">
<template #default="scope">
<img style="width: 50px; height: 50px; display: block" :src="scope.row.goodsImg" alt="">
</template>
</el-table-column>
<el-table-column label="商品名称" prop="goodsName"></el-table-column>
<el-table-column label="商品单价">
<template #default="scope">
<b style="color: red">¥{{ scope.row.goodsPrice }}</b>
</template>
</el-table-column>
<el-table-column label="商品数量">
<template #default="scope">
<el-input-number @change="changeNum(scope.row)" v-model="scope.row.num" :min="1" style="width: 150px"></el-input-number>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div style="text-align: right; margin-top: 20px; font-size: 20px">总价格:
<b style="color: red; display: inline-block; min-width: 60px; text-align: left">{{ data.total }} 元</b>
<div style="margin-top: 10px"><el-button :disabled="data.total === 0" @click="handleAddOrder" type="danger">立即下单</el-button></div>
</div>
</div>
<el-dialog title="下单信息" width="30%" v-model="data.formVisible" :close-on-click-modal="false" destroy-on-close>
<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="100px" style="padding-right: 30px">
<el-form-item label="配送类型" prop="deliverType">
<el-radio-group v-model="data.form.deliverType">
<el-radio-button value="自提" label="自提"></el-radio-button>
<el-radio-button value="外送" label="外送"></el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="收货地址" prop="address" v-if="data.form.deliverType === '外送'">
<el-input v-model="data.form.address" type="textarea" :rows="3" placeholder="请输入外送的接收地址,包括联系人、联系电话、地址信息"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="data.formVisible = false">取 消</el-button>
<el-button type="primary" @click="addOrder">确 认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import request from "@/utils/request";
import {reactive, ref} from "vue";
import {ElMessageBox, ElMessage} from "element-plus";
const formRef = ref()
const data = reactive({
user: JSON.parse(localStorage.getItem('system-user') || '{}'),
total: 0,
formVisible: false,
form: {},
tableData: [],
selectedRows: [],
rules: {
deliverType: [
{ required: true, message: '请选择配送类型', trigger: 'change' }
],
address: [
{ required: true, message: '请输入配送地址', trigger: 'blur' }
]
}
})
const handleAddOrder = () => {
data.form = {}
data.formVisible = true
}
const addOrder = () => {
if (!data.selectedRows?.length) {
ElMessage.warning('请选择商品')
return
}
data.form.userId = data.user.id
data.form.cartList = data.selectedRows
request.post('/orders/add', data.form).then(res => {
if (res.code === '200') {
ElMessage.success('下单成功')
data.formVisible = false
load()
} else {
ElMessage.error(res.msg)
}
})
}
const changeNum = (row) => {
calTotal()
data.form = row
update()
}
const calTotal = () => {
data.total = 0
// rows是选中行
data.selectedRows.forEach(item => {
data.total += item.goodsPrice * item.num
})
if (data.total > 0) {
data.total = data.total.toFixed(2)
}
}
const handleSelectionChange = (rows) => {
data.selectedRows = rows
calTotal()
}
// 分页查询
const load = () => {
request.get('/cart/selectAll', {
params: {
userId: data.user.id
}
}).then(res => {
data.tableData = res.data
})
}
load()
// 新增
const handleAdd = () => {
data.form = {}
data.formVisible = true
}
// 编辑
const handleEdit = (row) => {
data.form = JSON.parse(JSON.stringify(row))
data.formVisible = true
}
// 新增保存
const add = () => {
request.post('/cart/add', data.form).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
data.formVisible = false
} else {
ElMessage.error(res.msg)
}
})
}
// 编辑保存
const update = () => {
request.put('/cart/update', data.form).then(res => {
if (res.code === '200') {
} else {
ElMessage.error(res.msg)
}
})
}
// 弹窗保存
const save = () => {
formRef.value.validate(valid => {
if (valid) {
// data.form有id就是更新,没有就是新增
data.form.id ? update() : add()
}
})
}
// 删除
const handleDelete = (id) => {
ElMessageBox.confirm('您确定删除吗?', '删除确认', { type: 'warning' }).then(res => {
request.delete('/cart/delete/' + id).then(res => {
if (res.code === '200') {
load()
ElMessage.success('操作成功')
} else {
ElMessage.error(res.msg)
}
})
}).catch(err => {})
}
// 重置
const reset = () => {
data.name = null
load()
}
</script>