修复:订单详情、及发货功能

This commit is contained in:
2025-08-05 21:09:42 +08:00
parent 669f10c15a
commit 032db01193
3 changed files with 379 additions and 107 deletions

View File

@@ -13,13 +13,40 @@
> >
<template #extra> <template #extra>
<a-space> <a-space>
<!-- 发货按钮已付款且未发货时显示 -->
<a-button <a-button
v-if="form.payStatus === 1 && form.deliveryStatus === 10"
type="primary" type="primary"
@click="save" @click="handleDelivery"
:loading="loading" :loading="loading"
>发货 >
发货
</a-button>
<!-- 取消订单按钮未完成且未取消时显示 -->
<a-button
v-if="form.orderStatus === 0"
@click="handleCancelOrder"
danger
:loading="loading"
>
取消订单
</a-button>
<!-- 删除订单按钮已取消或已完成时显示 -->
<a-button
v-if="form.orderStatus === 1 || form.orderStatus === 2"
@click="handleDeleteOrder"
danger
:loading="loading"
>
删除订单
</a-button>
<!-- 关闭按钮 -->
<a-button @click="updateVisible(false)">
关闭
</a-button> </a-button>
<a-button @click="updateVisible(false)" danger>取消订单</a-button>
</a-space> </a-space>
</template> </template>
<a-card title="基本信息" style="margin-bottom: 20px" :bordered="false"> <a-card title="基本信息" style="margin-bottom: 20px" :bordered="false">
@@ -27,7 +54,7 @@
<!-- 第一排--> <!-- 第一排-->
<a-descriptions-item <a-descriptions-item
label="订单编号" label="订单编号"
span="3" :span="3"
:labelStyle="{ width: '90px', color: '#808080' }" :labelStyle="{ width: '90px', color: '#808080' }"
> >
<span @click="copyText(form.orderNo)">{{ form.orderNo }}</span> <span @click="copyText(form.orderNo)">{{ form.orderNo }}</span>
@@ -233,22 +260,103 @@
</a-descriptions> </a-descriptions>
</a-card> </a-card>
<!-- 订单流程步骤条 -->
<a-card title="订单流程" style="margin-bottom: 20px" :bordered="false">
<a-steps :current="active" :items="steps" />
</a-card>
<a-card title="商品信息" style="margin-bottom: 20px" :bordered="false"> <a-card title="商品信息" style="margin-bottom: 20px" :bordered="false">
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<a-table <a-table
:data-source="orderGoods" :data-source="orderGoods"
:columns="columns" :columns="columns"
:pagination="false" :pagination="false"
/> >
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'goodsName'">
<div style="display: flex; align-items: center; gap: 12px;">
<a-avatar
:src="record.image || record.goodsImage"
shape="square"
:size="50"
style="flex-shrink: 0;"
>
<template #icon>
<img src="/default-goods.png" alt="默认商品图片" style="width: 100%; height: 100%; object-fit: cover;" />
</template>
</a-avatar>
<span style="flex: 1;">{{ record.goodsName || '未知商品' }}</span>
</div>
</template>
</template>
</a-table>
</a-spin> </a-spin>
</a-card> </a-card>
<a-card title="收货信息" style="margin-bottom: 20px" :bordered="false"> <a-card title="收货信息" style="margin-bottom: 20px" :bordered="false">
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<a-table <a-descriptions :column="2">
:data-source="orderGoods" <a-descriptions-item
:columns="columns" label="收货人"
:pagination="false" :labelStyle="{ width: '90px', color: '#808080' }"
/> >
{{ form.realName || '未填写' }}
</a-descriptions-item>
<a-descriptions-item
label="联系电话"
:labelStyle="{ width: '90px', color: '#808080' }"
>
{{ form.phone || form.mobile || '未填写' }}
</a-descriptions-item>
<a-descriptions-item
label="收货地址"
:labelStyle="{ width: '90px', color: '#808080' }"
:span="2"
>
{{ form.address || '未填写' }}
</a-descriptions-item>
<a-descriptions-item
label="配送方式"
:labelStyle="{ width: '90px', color: '#808080' }"
>
<a-tag v-if="form.deliveryType === 0">快递配送</a-tag>
<a-tag v-else-if="form.deliveryType === 1" color="blue">自提</a-tag>
<a-tag v-else>未设置</a-tag>
</a-descriptions-item>
<a-descriptions-item
label="配送时间"
:labelStyle="{ width: '90px', color: '#808080' }"
>
<div v-if="form.sendStartTime && form.sendEndTime">
{{ form.sendStartTime }} - {{ form.sendEndTime }}
</div>
<span v-else class="text-gray-400">未设置</span>
</a-descriptions-item>
<a-descriptions-item
v-if="form.deliveryType === 1"
label="自提店铺"
:labelStyle="{ width: '90px', color: '#808080' }"
:span="2"
>
{{ form.selfTakeMerchantName || '未设置' }}
</a-descriptions-item>
<a-descriptions-item
v-if="form.deliveryType === 0"
label="发货店铺"
:labelStyle="{ width: '90px', color: '#808080' }"
:span="2"
>
{{ form.expressMerchantName || '未设置' }}
</a-descriptions-item>
<a-descriptions-item
v-if="form.selfTakeCode"
label="自提码"
:labelStyle="{ width: '90px', color: '#808080' }"
>
<a-tag color="orange" @click="copyText(form.selfTakeCode)" class="cursor-pointer">
{{ form.selfTakeCode }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-spin> </a-spin>
</a-card> </a-card>
</a-drawer> </a-drawer>
@@ -258,11 +366,8 @@
import {ref, reactive, watch} from 'vue'; import {ref, reactive, watch} from 'vue';
import {Form} from 'ant-design-vue'; import {Form} from 'ant-design-vue';
import {assignObject} from 'ele-admin-pro'; import {assignObject} from 'ele-admin-pro';
import {ColumnItem} from 'ele-admin-pro/es/ele-pro-table/types';
import { import {
CheckOutlined,
CloseOutlined,
CoffeeOutlined,
WechatOutlined, WechatOutlined,
AlipayCircleOutlined, AlipayCircleOutlined,
IdcardOutlined IdcardOutlined
@@ -272,6 +377,8 @@ import {BszxPay} from '@/api/bszx/bszxPay/model';
import {toDateString} from 'ele-admin-pro'; import {toDateString} from 'ele-admin-pro';
import {copyText} from "@/utils/common"; import {copyText} from "@/utils/common";
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods"; import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
import {updateShopOrder, removeShopOrder} from '@/api/shop/shopOrder';
import {message, Modal} from 'ant-design-vue';
const useForm = Form.useForm; const useForm = Form.useForm;
@@ -282,10 +389,10 @@ const props = defineProps<{
data?: ShopOrder | null; data?: ShopOrder | null;
}>(); }>();
export interface step { interface Step {
title?: String | undefined; title?: string | undefined;
subTitle?: String | undefined; subTitle?: string | undefined;
description?: String | undefined; description?: string | undefined;
} }
// 是否是修改 // 是否是修改
@@ -296,7 +403,7 @@ const maxAble = ref(true);
const orderGoods = ref<BszxPay[]>([]); const orderGoods = ref<BszxPay[]>([]);
// 步骤条 // 步骤条
const steps = ref<step[]>([ const steps = ref<Step[]>([
{ {
title: '报餐', title: '报餐',
description: undefined description: undefined
@@ -473,27 +580,26 @@ const updateVisible = (value: boolean) => {
emit('update:visible', value); emit('update:visible', value);
}; };
const columns = ref<ColumnItem[]>([ const columns = ref([
{ {
title: '商品名称', title: '商品名称',
dataIndex: 'goodsName', dataIndex: 'goodsName',
key: 'goodsName', key: 'goodsName',
align: 'center',
width: 280 width: 280
}, },
{ {
title: '金额', title: '金额',
dataIndex: 'price', dataIndex: 'price',
align: 'center', align: 'center' as const,
customRender: ({record}) => { customRender: ({record}: {record: any}) => {
return `${record.price || 0}`; return `${record.price || 0}`;
} }
}, },
{ {
title: '数量', title: '数量',
dataIndex: 'quantity', dataIndex: 'quantity',
align: 'center', align: 'center' as const,
customRender: ({record}) => { customRender: ({record}: {record: any}) => {
return record.quantity || 1; return record.quantity || 1;
} }
}, },
@@ -501,74 +607,77 @@ const columns = ref<ColumnItem[]>([
title: '备注', title: '备注',
dataIndex: 'comments', dataIndex: 'comments',
key: 'comments', key: 'comments',
align: 'center' align: 'center' as const
}, },
{ {
title: '是否免费', title: '是否免费',
dataIndex: 'isFree', dataIndex: 'isFree',
align: 'center', align: 'center' as const,
customRender: ({record}) => { customRender: ({record}: {record: any}) => {
return record.isFree ? '是' : '否'; return record.isFree ? '是' : '否';
} }
} }
]); ]);
/* 制作步骤条 */ /* 制作步骤条 */
const loadSteps = (order) => { const loadSteps = (order: ShopOrder) => {
steps.value = []; steps.value = [
steps.value.push({ {
title: '下单' title: '下单',
}); description: order.createTime ? toDateString(order.createTime, 'MM-dd HH:mm') : undefined
steps.value.push({ },
title: '付款' {
}); title: '付款',
steps.value.push({ description: undefined
title: '发货' },
}); {
steps.value.push({ title: '发货',
title: '收货' description: undefined
}); },
steps.value.push({ {
title: '完成' title: '收货',
}); description: undefined
},
{
title: '完成',
description: undefined
}
];
// 下单 // 根据订单状态设置当前步骤
if (order.payStatus == 10) { if (order.orderStatus === 2) {
// 已取消
active.value = -1;
steps.value = [
{
title: '下单',
description: order.createTime ? toDateString(order.createTime, 'MM-dd HH:mm') : undefined
},
{
title: '已取消',
description: '订单已取消'
}
];
} else if (order.payStatus === 0) {
// 未付款
active.value = 0; active.value = 0;
steps.value[0].description = order.createTime; } else if (order.payStatus === 1) {
} // 已付款
// 付款
if (order.payStatus == 20) {
active.value = 1; active.value = 1;
steps.value[0].description = order.createTime; steps.value[1].description = order.payTime ? toDateString(order.payTime, 'MM-dd HH:mm') : '已付款';
steps.value[1].description = order.payTime;
} if (order.deliveryStatus === 20) {
// 发货 // 发货
if (order.payStatus == 20 && order.deliveryStatus == 20) { active.value = 2;
active.value = 2; steps.value[2].description = order.deliveryTime ? toDateString(order.deliveryTime, 'MM-dd HH:mm') : '已发货';
steps.value[0].description = order.createTime;
steps.value[1].description = order.payTime; if (order.orderStatus === 1) {
steps.value[2].description = order.deliveryTime; // 已完成
} active.value = 4;
// 收货 steps.value[3].description = '已收货';
if (order.payStatus == 20 && order.receiptStatus == 20) { steps.value[4].description = '订单完成';
active.value = 3; }
steps.value[0].description = order.createTime; }
steps.value[1].description = order.payTime;
steps.value[2].description = order.deliveryTime;
steps.value[3].description = order.receiptTime;
}
// 完成
if (order.payStatus == 20 && order.orderStatus == 30) {
active.value = 4;
steps.value[0].description = order.createTime;
steps.value[1].description = order.payTime;
steps.value[2].description = order.deliveryTime;
steps.value[3].description = order.receiptTime;
}
// 已取消
if (order.orderStatus == 20) {
active.value = 4;
} }
}; };
@@ -579,8 +688,81 @@ const loadSteps = (order) => {
// }); // });
// }; // };
/* 发货处理 */
const handleDelivery = () => {
Modal.confirm({
title: '确认发货',
content: '确定要将此订单标记为已发货吗?',
onOk: async () => {
try {
loading.value = true;
await updateShopOrder({
...form,
deliveryStatus: 20, // 已发货
deliveryTime: new Date().toISOString()
});
message.success('发货成功');
emit('done');
updateVisible(false);
} catch (error: any) {
message.error(error.message || '发货失败');
} finally {
loading.value = false;
}
}
});
};
/* 取消订单 */
const handleCancelOrder = () => {
Modal.confirm({
title: '确认取消订单',
content: '确定要取消此订单吗?取消后无法恢复。',
onOk: async () => {
try {
loading.value = true;
await updateShopOrder({
...form,
orderStatus: 2 // 已取消
});
message.success('订单已取消');
emit('done');
updateVisible(false);
} catch (error: any) {
message.error(error.message || '取消订单失败');
} finally {
loading.value = false;
}
}
});
};
/* 删除订单 */
const handleDeleteOrder = () => {
Modal.confirm({
title: '确认删除订单',
content: '确定要删除此订单吗?删除后无法恢复。',
onOk: async () => {
try {
loading.value = true;
if (form.orderId) {
await removeShopOrder(form.orderId);
message.success('订单已删除');
emit('done');
updateVisible(false);
}
} catch (error: any) {
message.error(error.message || '删除订单失败');
} finally {
loading.value = false;
}
}
});
};
/* 保存编辑 */ /* 保存编辑 */
const save = () => { const save = () => {
// 保留原有的保存功能
}; };
watch( watch(

View File

@@ -67,6 +67,18 @@
/> />
<a-button @click="reset">重置</a-button> <a-button @click="reset">重置</a-button>
<a-button @click="handleExport">导出</a-button> <a-button @click="handleExport">导出</a-button>
<a-button
danger
type="primary"
class="ele-btn-icon"
:disabled="selection?.length === 0"
@click="removeBatch"
>
<template #icon>
<DeleteOutlined/>
</template>
<span>批量删除</span>
</a-button>
</a-space> </a-space>
</template> </template>
@@ -74,6 +86,7 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { utils, writeFile } from 'xlsx'; import { utils, writeFile } from 'xlsx';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import {DeleteOutlined} from '@ant-design/icons-vue';
import useSearch from "@/utils/use-search"; import useSearch from "@/utils/use-search";
import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model"; import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
import {listShopOrder} from "@/api/shop/shopOrder"; import {listShopOrder} from "@/api/shop/shopOrder";
@@ -81,8 +94,8 @@
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
// 选中的角色 // 选中的订单
selection?: []; selection?: ShopOrder[];
}>(), }>(),
{} {}
); );
@@ -113,6 +126,11 @@
emit('search', where); emit('search', where);
}; };
// 批量删除
const removeBatch = () => {
emit('remove');
};
/* 搜索 */ /* 搜索 */
const search = () => { const search = () => {
const [d1, d2] = dateRange.value ?? []; const [d1, d2] = dateRange.value ?? [];

View File

@@ -124,20 +124,24 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref} from 'vue'; import {createVNode, ref} from 'vue';
import type {EleProTable} from 'ele-admin-pro'; import type {EleProTable} from 'ele-admin-pro';
import type { import type {
DatasourceFunction, DatasourceFunction,
ColumnItem ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types'; } from 'ele-admin-pro/es/ele-pro-table/types';
import {
ExclamationCircleOutlined
} from '@ant-design/icons-vue';
import Search from './components/search.vue'; import Search from './components/search.vue';
import {getPageTitle} from "@/utils/common"; import {getPageTitle} from "@/utils/common";
import {toDateString} from 'ele-admin-pro'; import {toDateString} from 'ele-admin-pro';
import OrderInfo from './components/orderInfo.vue'; import OrderInfo from './components/orderInfo.vue';
import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model"; import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
import {pageShopOrder, repairOrder} from "@/api/shop/shopOrder"; import {pageShopOrder, repairOrder, removeShopOrder, removeBatchShopOrder} from "@/api/shop/shopOrder";
import {updateUser} from "@/api/system/user"; import {updateUser} from "@/api/system/user";
import {getPayType} from '@/utils/shop'; import {getPayType} from '@/utils/shop';
import {message, Modal} from 'ant-design-vue';
// 表格实例 // 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
@@ -179,7 +183,8 @@ const columns = ref<ColumnItem[]>([
title: '订单编号', title: '订单编号',
dataIndex: 'orderNo', dataIndex: 'orderNo',
key: 'orderNo', key: 'orderNo',
align: 'center' align: 'center',
width: 200,
}, },
{ {
title: '商品信息', title: '商品信息',
@@ -243,14 +248,14 @@ const columns = ref<ColumnItem[]>([
ellipsis: true, ellipsis: true,
customRender: ({text}) => toDateString(text) customRender: ({text}) => toDateString(text)
}, },
// { {
// title: '操作', title: '操作',
// key: 'action', key: 'action',
// width: 180, width: 180,
// fixed: 'right', fixed: 'right',
// align: 'center', align: 'center',
// hideInSetting: true hideInSetting: true
// } }
]); ]);
/* 搜索 */ /* 搜索 */
@@ -260,21 +265,51 @@ const reload = (where?: ShopOrderParam) => {
}; };
const onTabs = () => { const onTabs = () => {
// 将语义化的key转换为后端需要的statusFilter值 // 根据tabs的key设置筛选条件
const statusFilterMap: Record<string, number | undefined> = { const filterParams: Record<string, any> = {};
'all': undefined, // 全部不传statusFilter
'unpaid': 0, // 待支付对应原来的key="0"
'undelivered': 1, // 待发货对应原来的key="1"
'unverified': 2, // 待核销对应原来的key="2"
'unreceived': 3, // 待收货对应原来的key="3"
'unevaluated': 4, // 待评价对应原来的key="4"
'completed': 5, // 已完成对应原来的key="5"
'refunded': 6, // 已退款对应原来的key="6"
'deleted': 7 // 已删除对应原来的key="7"
};
const statusFilter = statusFilterMap[activeKey.value as string]; switch (activeKey.value) {
reload({statusFilter}); case 'all':
// 全部订单,不设置任何筛选条件
break;
case 'unpaid':
// 待支付支付状态为0未付款
filterParams.payStatus = 0;
break;
case 'undelivered':
// 待发货:已付款但未发货
filterParams.payStatus = 1;
filterParams.deliveryStatus = 10; // 未核销
break;
case 'unverified':
// 待核销:已付款但未核销
filterParams.payStatus = 1;
filterParams.deliveryStatus = 10;
break;
case 'unreceived':
// 待收货:已发货但未收货
filterParams.payStatus = 1;
filterParams.deliveryStatus = 30; // 部分核销
break;
case 'unevaluated':
// 待评价:已完成但未评价
filterParams.orderStatus = 1; // 已完成
break;
case 'completed':
// 已完成
filterParams.orderStatus = 1;
break;
case 'refunded':
// 已退款
filterParams.orderStatus = 6;
break;
case 'deleted':
// 已删除
filterParams.orderStatus = 2; // 已取消
break;
}
reload(filterParams);
} }
const onSearch = (item: ShopOrder) => { const onSearch = (item: ShopOrder) => {
@@ -317,6 +352,43 @@ const query = () => {
loading.value = true; loading.value = true;
}; };
/* 删除单个订单 */
const remove = (row: ShopOrder) => {
removeShopOrder(row.orderId)
.then(() => {
message.success('删除成功');
reload();
})
.catch((e) => {
message.error(e.message);
});
};
/* 批量删除订单 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const ids = selection.value.map((d) => d.orderId);
removeBatchShopOrder(ids)
.then(() => {
message.success('删除成功');
reload();
})
.catch((e) => {
message.error(e.message);
});
}
});
};
/* 自定义行属性 */ /* 自定义行属性 */
const customRow = (record: ShopOrder) => { const customRow = (record: ShopOrder) => {
return { return {