feat:【mall 商城】门店自提的迁移(ele 80%)

This commit is contained in:
YunaiV
2025-10-13 22:21:08 +08:00
parent 75c24789ff
commit a3d56ae6a3
3 changed files with 142 additions and 46 deletions

View File

@@ -49,6 +49,7 @@ export namespace MallOrderApi {
afterSaleStatus?: null | number; afterSaleStatus?: null | number;
/** 属性数组 */ /** 属性数组 */
properties?: ProductProperty[]; properties?: ProductProperty[];
price?: number;
} }
/** 订单日志 */ /** 订单日志 */
@@ -248,7 +249,7 @@ export function getOrderPage(params: PageParam) {
} }
/** 查询交易订单统计 */ /** 查询交易订单统计 */
export function getOrderSummary(params: PageParam) { export function getOrderSummary(params: any) {
return requestClient.get<MallOrderApi.OrderSummary>('/trade/order/summary', { return requestClient.get<MallOrderApi.OrderSummary>('/trade/order/summary', {
params, params,
}); });

View File

@@ -4,15 +4,24 @@ import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickU
import { ref } from 'vue'; import { ref } from 'vue';
import { DeliveryTypeEnum, DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
import { useUserStore } from '@vben/stores';
import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore'; import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore';
import { getRangePickerDefaultProps } from '#/utils'; import { getRangePickerDefaultProps } from '#/utils';
// TODO @芋艿:统一风格;
const userStore = useUserStore();
const pickUpStoreList = ref<MallDeliveryPickUpStoreApi.PickUpStore[]>([]); const pickUpStoreList = ref<MallDeliveryPickUpStoreApi.PickUpStore[]>([]);
/** 自提门店列表 */
getSimpleDeliveryPickUpStoreList().then((res) => { getSimpleDeliveryPickUpStoreList().then((res) => {
pickUpStoreList.value = res; pickUpStoreList.value = res;
// 移除自己无法核销的门店
const userId = userStore?.userInfo?.id;
pickUpStoreList.value = pickUpStoreList.value.filter((item) =>
item.verifyUserIds?.includes(userId),
);
}); });
/** 列表的搜索表单 */ /** 列表的搜索表单 */
@@ -28,18 +37,53 @@ export function useGridFormSchema(): VbenFormSchema[] {
}, },
}, },
{ {
fieldName: 'pickUpStoreId', fieldName: 'pickUpStoreIds',
label: '自提门店', label: '自提门店',
component: 'ApiSelect', component: 'Select',
componentProps: { componentProps: {
api: getSimpleDeliveryPickUpStoreList, options: pickUpStoreList,
labelField: 'name', props: {
valueField: 'id', label: 'name',
value: 'id',
}, },
dependencies: { placeholder: '请选择自提门店',
triggerFields: ['deliveryType'], },
trigger: (values) => defaultValue: pickUpStoreList.value[0]?.id,
values.deliveryType === DeliveryTypeEnum.PICK_UP.type, },
{
fieldName: 'no',
label: '订单号',
component: 'Input',
componentProps: {
placeholder: '请输入订单号',
clearable: true,
},
},
{
fieldName: 'userId',
label: '用户 UID',
component: 'Input',
componentProps: {
placeholder: '请输入用户 UID',
clearable: true,
},
},
{
fieldName: 'userNickname',
label: '用户昵称',
component: 'Input',
componentProps: {
placeholder: '请输入用户昵称',
clearable: true,
},
},
{
fieldName: 'userMobile',
label: '用户电话',
component: 'Input',
componentProps: {
placeholder: '请输入用户电话',
clearable: true,
}, },
}, },
]; ];
@@ -67,17 +111,13 @@ export function useGridColumns(): VxeGridPropTypes.Columns {
{ {
field: 'spuName', field: 'spuName',
title: '商品信息', title: '商品信息',
minWidth: 100, minWidth: 300,
formatter: ({ row }) => { slots: { default: 'spuName' },
if (row.items.length > 1) {
return row.items.map((item: any) => item.spuName).join(',');
}
},
}, },
{ {
field: 'payPrice', field: 'payPrice',
title: '实付金额(元)', title: '实付金额(元)',
formatter: 'formatFenToYuanAmount', formatter: 'formatAmount2',
minWidth: 180, minWidth: 180,
}, },
{ {
@@ -90,9 +130,10 @@ export function useGridColumns(): VxeGridPropTypes.Columns {
title: '核销门店', title: '核销门店',
minWidth: 160, minWidth: 160,
formatter: ({ row }) => { formatter: ({ row }) => {
return pickUpStoreList.value.find( return (
(item) => item.id === row.pickUpStoreId, pickUpStoreList.value.find((item) => item.id === row.pickUpStoreId)
)?.name; ?.name || ''
);
}, },
}, },
{ {

View File

@@ -2,19 +2,27 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallOrderApi } from '#/api/mall/trade/order'; import type { MallOrderApi } from '#/api/mall/trade/order';
import { h, onMounted, ref } from 'vue'; import { h, ref } from 'vue';
import { Page, prompt } from '@vben/common-ui'; import { Page, prompt } from '@vben/common-ui';
import { DeliveryTypeEnum, TradeOrderStatusEnum } from '@vben/constants'; import { DeliveryTypeEnum } from '@vben/constants';
import { $t } from '@vben/locales';
import { fenToYuan } from '@vben/utils'; import { fenToYuan } from '@vben/utils';
import { ElCard, ElInput, ElMessage } from 'element-plus'; import {
ElCard,
ElImage,
ElInput,
ElLoading,
ElMessage,
ElTag,
} from 'element-plus';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
getOrderByPickUpVerifyCode,
getOrderPage, getOrderPage,
getOrderSummary, getOrderSummary,
pickUpOrderByVerifyCode,
} from '#/api/mall/trade/order'; } from '#/api/mall/trade/order';
import { SummaryCard } from '#/components/summary-card'; import { SummaryCard } from '#/components/summary-card';
@@ -22,15 +30,22 @@ import { useGridColumns, useGridFormSchema } from './data';
const summary = ref<MallOrderApi.OrderSummary>(); const summary = ref<MallOrderApi.OrderSummary>();
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 获取订单统计数据 */
async function getOrderSum() { async function getOrderSum() {
const query = await gridApi.formApi.getValues(); const query = await gridApi.formApi.getValues();
query.deliveryType = DeliveryTypeEnum.PICK_UP.type; query.deliveryType = DeliveryTypeEnum.PICK_UP.type;
const res = await getOrderSummary(query as any); summary.value = await getOrderSummary(query);
summary.value = res;
} }
/** 核销 */ /** 核销订单 */
async function handlePickup(pickUpVerifyCode?: string) { async function handlePickup(pickUpVerifyCode?: string) {
// 如果没有传核销码,则弹窗输入
// TODO @AI不太对
if (!pickUpVerifyCode) { if (!pickUpVerifyCode) {
await prompt({ await prompt({
component: () => { component: () => {
@@ -48,13 +63,17 @@ async function handlePickup(pickUpVerifyCode?: string) {
if (!pickUpVerifyCode) { if (!pickUpVerifyCode) {
return; return;
} }
const data = await getOrderByPickUpVerifyCode(pickUpVerifyCode);
if (data?.deliveryType !== DeliveryTypeEnum.PICK_UP.type) { // 执行核销
ElMessage.error('未查询到订单'); const loadingInstance = ElLoading.service({
return; text: '订单核销中 ...',
} });
if (data?.status !== TradeOrderStatusEnum.UNDELIVERED.status) { try {
ElMessage.error('订单不是待核销状态'); await pickUpOrderByVerifyCode(pickUpVerifyCode);
ElMessage.success($t('ui.actionMessage.operationSuccess'));
handleRefresh();
} finally {
loadingInstance.close();
} }
} }
@@ -80,7 +99,7 @@ async function connectToSerialPort() {
return; return;
} }
// 获取用户之前授予该网站访问权限的所有串口 // 获取用户之前授予该网站访问权限的所有串口
ports.value = await (navigator.serial as any).getPorts(); ports.value = await (navigator.serial as any).getPorts();
// 等待串口打开 // 等待串口打开
@@ -92,7 +111,7 @@ async function connectToSerialPort() {
ElMessage.success('成功连接扫码枪'); ElMessage.success('成功连接扫码枪');
serialPort.value = true; serialPort.value = true;
readData(); await readData();
} catch (error) { } catch (error) {
// 处理连接串口出错的情况 // 处理连接串口出错的情况
console.error('Error connecting to serial port:', error); console.error('Error connecting to serial port:', error);
@@ -120,7 +139,7 @@ async function readData() {
data = ''; // 清空下次读取不会叠加 data = ''; // 清空下次读取不会叠加
console.warn(`二维码数据:${codeData}`); console.warn(`二维码数据:${codeData}`);
// 处理拿到数据逻辑 // 处理拿到数据逻辑
handlePickup(codeData); await handlePickup(codeData);
} }
} }
} }
@@ -143,12 +162,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
cellConfig: {
height: 100,
},
columns: useGridColumns(), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
query: async ({ page }, formValues) => { query: async ({ page }, formValues) => {
await getOrderSum();
return await getOrderPage({ return await getOrderPage({
pageNo: page.currentPage, pageNo: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
@@ -160,6 +183,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
rowConfig: { rowConfig: {
keyField: 'id', keyField: 'id',
isHover: true,
}, },
toolbarConfig: { toolbarConfig: {
refresh: true, refresh: true,
@@ -167,15 +191,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<MallOrderApi.Order>, } as VxeTableGridOptions<MallOrderApi.Order>,
}); });
onMounted(() => {
getOrderSum();
});
</script> </script>
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<ElCard class="m-4"> <ElCard class="mb-2">
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<SummaryCard <SummaryCard
class="flex flex-1" class="flex flex-1"
@@ -215,8 +235,43 @@ onMounted(() => {
/> />
</div> </div>
</ElCard> </ElCard>
<!-- TODO @核销订单点出的弹窗无法输入内容 -->
<Grid class="h-4/5" table-title="核销订单"> <Grid class="h-4/5" table-title="核销订单">
<template #spuName="{ row }">
<div class="flex flex-col gap-2">
<div
v-for="item in row.items"
:key="item.id!"
class="flex items-start gap-2"
>
<!-- TODO @AI长宽不太对 -->
<ElImage
:src="item.picUrl || ''"
:alt="item.spuName || ''"
:width="30"
:height="30"
class="flex-shrink-0"
:preview-src-list="item.picUrl ? [item.picUrl] : []"
/>
<div class="flex flex-1 flex-col gap-1">
<span class="text-sm">{{ item.spuName }}</span>
<div class="flex flex-wrap gap-1">
<ElTag
v-for="property in item.properties"
:key="property.propertyId!"
size="small"
type="info"
>
{{ property.propertyName }}: {{ property.valueName }}
</ElTag>
</div>
<span class="text-xs text-gray-500">
{{ fenToYuan(item.price) }} x {{ item.count || 0 }}
</span>
</div>
</div>
</div>
</template>
<template #toolbar-tools> <template #toolbar-tools>
<TableAction <TableAction
:actions="[ :actions="[
@@ -229,9 +284,8 @@ onMounted(() => {
}, },
{ {
label: serialPort ? '断开扫描枪' : '连接扫描枪', label: serialPort ? '断开扫描枪' : '连接扫描枪',
type: 'primary', type: serialPort ? 'danger' : 'primary',
icon: serialPort ? 'lucide:circle-x' : 'lucide:circle-play', icon: serialPort ? 'lucide:circle-x' : 'lucide:circle-play',
link: true,
onClick: serialPort ? cutPort : connectToSerialPort, onClick: serialPort ? cutPort : connectToSerialPort,
}, },
]" ]"