feat(gltTicketOrder): 添加配送订单时间追踪和配送员指派功能
- 在订单模型中新增派送时间、送达时间和签收时间字段 - 实现配送订单状态动态渲染,支持待配送、已派送、已送达、已签收状态 - 添加配送时间节点显示,包括下单时间、派送时间、送达时间和签收时间 - 实现配送员指派弹窗功能,支持选择新的配送人员 - 集成配送员列表查询和搜索过滤功能 - 优化配送信息展示布局,分离配送人员信息和联系信息显示 - 实现配送员指派的表单验证和提交逻辑
This commit is contained in:
@@ -50,6 +50,12 @@ export interface GltTicketOrder {
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 派送时间(后端可能返回该字段用于派单/出库时间)
|
||||
sendTime?: string;
|
||||
// 送达时间
|
||||
arriveTime?: string;
|
||||
// 签收时间
|
||||
signTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,16 @@
|
||||
<template v-if="column.key === 'riderName'">
|
||||
<a-space>
|
||||
<div class="flex flex-col">
|
||||
<div v-if="record.sendTime">配送时间:{{ record.sendTime || '-' }}</div>
|
||||
<div class="text-gray-400 flex justify-between">配送人员:{{ record.riderName || '-' }} <a-tag color="blue" class="cursor-pointer">指派</a-tag></div>
|
||||
<div class="text-gray-400 flex justify-between">
|
||||
配送人员:{{ record.riderName || '-' }}
|
||||
<a-tag
|
||||
color="blue"
|
||||
class="cursor-pointer"
|
||||
@click.stop="openAssign(record)"
|
||||
>
|
||||
指派
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="text-blue-400">联系电话:{{ record.riderPhone || '-' }}</div>
|
||||
</div>
|
||||
</a-space>
|
||||
@@ -54,18 +62,25 @@
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">待配送</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">已派送</a-tag>
|
||||
<a-tag v-if="record.status === 2" color="red">已送达</a-tag>
|
||||
<a-tag v-if="record.status === 3" color="red">已签收</a-tag>
|
||||
<a-tag :color="getOrderStatus(record).color">
|
||||
{{ getOrderStatus(record).label }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key ==='createTime'">
|
||||
<template v-if="column.key ==='times'">
|
||||
<a-space>
|
||||
<div class="flex flex-col">
|
||||
<div class="text-gray-400">下单时间:{{ record.createTime }}</div>
|
||||
<div class="text-gray-400">派送时间:{{ record.updateTime }}</div>
|
||||
<div class="text-gray-400">送达时间:{{ record.createTime }}</div>
|
||||
<div class="text-gray-400">签收时间:{{ record.updateTime }}</div>
|
||||
<div class="text-gray-400">
|
||||
下单时间:{{ formatTime(getOrderTimes(record).orderTime) }}
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
派送时间:{{ formatTime(getOrderTimes(record).sendTime) }}
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
送达时间:{{ formatTime(getOrderTimes(record).arriveTime) }}
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
签收时间:{{ formatTime(getOrderTimes(record).signTime) }}
|
||||
</div>
|
||||
</div>
|
||||
</a-space>
|
||||
</template>
|
||||
@@ -85,13 +100,53 @@
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 指派弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="assignVisible"
|
||||
title="指派配送人员"
|
||||
:confirm-loading="assignConfirmLoading"
|
||||
:maskClosable="false"
|
||||
width="520px"
|
||||
@ok="submitAssign"
|
||||
@cancel="closeAssign"
|
||||
>
|
||||
<a-form
|
||||
ref="assignFormRef"
|
||||
:model="assignForm"
|
||||
:rules="assignRules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-form-item label="当前配送员">
|
||||
<span>
|
||||
{{ assignRow?.riderName || '-' }}
|
||||
<span v-if="assignRow?.riderPhone" class="text-gray-400">
|
||||
({{ assignRow?.riderPhone }})
|
||||
</span>
|
||||
</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="新配送员" name="riderId">
|
||||
<a-select
|
||||
v-model:value="assignForm.riderId"
|
||||
placeholder="请选择配送人员"
|
||||
show-search
|
||||
allow-clear
|
||||
:loading="riderLoading"
|
||||
option-filter-prop="label"
|
||||
:filter-option="filterRiderOption"
|
||||
:options="riderOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<GltTicketOrderEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { computed, createVNode, reactive, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
@@ -103,8 +158,11 @@
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import GltTicketOrderEdit from './components/gltTicketOrderEdit.vue';
|
||||
import { pageGltTicketOrder, removeGltTicketOrder, removeBatchGltTicketOrder } from '@/api/glt/gltTicketOrder';
|
||||
import { pageGltTicketOrder, removeGltTicketOrder, removeBatchGltTicketOrder, getGltTicketOrder, updateGltTicketOrder } from '@/api/glt/gltTicketOrder';
|
||||
import type { GltTicketOrder, GltTicketOrderParam } from '@/api/glt/gltTicketOrder/model';
|
||||
import { listShopStoreRider } from '@/api/shop/shopStoreRider';
|
||||
import type { ShopStoreRider } from '@/api/shop/shopStoreRider/model';
|
||||
import type { FormInstance } from 'ant-design-vue/es/form';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
@@ -120,6 +178,149 @@
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
/* 指派配送员 */
|
||||
const assignVisible = ref(false);
|
||||
const assignConfirmLoading = ref(false);
|
||||
const riderLoading = ref(false);
|
||||
const riderList = ref<ShopStoreRider[]>([]);
|
||||
const assignRow = ref<GltTicketOrder | null>(null);
|
||||
const assignFormRef = ref<FormInstance | null>(null);
|
||||
const assignForm = reactive<{ riderId: string | number | undefined }>({
|
||||
riderId: undefined
|
||||
});
|
||||
const assignRules = reactive({
|
||||
riderId: [{ required: true, message: '请选择配送人员', trigger: 'change' }]
|
||||
});
|
||||
|
||||
const filterRiderOption = (input: string, option: any) => {
|
||||
const label = String(option?.label ?? '');
|
||||
return label.toLowerCase().includes(input.toLowerCase());
|
||||
};
|
||||
|
||||
const riderOptions = computed(() => {
|
||||
const currentRiderId = assignRow.value?.riderId;
|
||||
return (riderList.value || []).map((r) => {
|
||||
const label = `${r.realName || '-'}${r.mobile ? `(${r.mobile})` : ''}`;
|
||||
return {
|
||||
value: r.id,
|
||||
label,
|
||||
disabled:
|
||||
r.status === 0 ||
|
||||
(currentRiderId != null && String(r.id ?? '') === String(currentRiderId))
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const loadRiders = async (row: GltTicketOrder) => {
|
||||
riderLoading.value = true;
|
||||
try {
|
||||
// 优先按配送点/门店过滤;若后端不支持或字段不匹配,可回退到全量列表
|
||||
const dealerId = (row as any).dealerId ?? row.storeId;
|
||||
const data = await listShopStoreRider(
|
||||
dealerId != null ? ({ dealerId } as any) : undefined
|
||||
);
|
||||
riderList.value = data || [];
|
||||
if (!riderList.value.length) {
|
||||
riderList.value = (await listShopStoreRider()) || [];
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
riderList.value = [];
|
||||
} finally {
|
||||
riderLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openAssign = (row: GltTicketOrder) => {
|
||||
assignRow.value = row;
|
||||
assignForm.riderId = undefined;
|
||||
assignVisible.value = true;
|
||||
loadRiders(row);
|
||||
};
|
||||
|
||||
const closeAssign = () => {
|
||||
assignVisible.value = false;
|
||||
assignRow.value = null;
|
||||
assignForm.riderId = undefined;
|
||||
};
|
||||
|
||||
const submitAssign = async () => {
|
||||
if (!assignFormRef.value || !assignRow.value?.id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await assignFormRef.value.validate();
|
||||
const nextRiderId = assignForm.riderId;
|
||||
if (nextRiderId == null) {
|
||||
return;
|
||||
}
|
||||
if (String(nextRiderId) === String(assignRow.value.riderId ?? '')) {
|
||||
message.error('请选择其他配送人员');
|
||||
return;
|
||||
}
|
||||
assignConfirmLoading.value = true;
|
||||
|
||||
// 先取详情再更新,避免后端 PUT 语义为“全量覆盖”导致字段被清空
|
||||
const detail = await getGltTicketOrder(assignRow.value.id);
|
||||
const normalizedRiderId =
|
||||
typeof nextRiderId === 'string'
|
||||
? Number.isNaN(Number(nextRiderId))
|
||||
? nextRiderId
|
||||
: Number(nextRiderId)
|
||||
: nextRiderId;
|
||||
const payload: GltTicketOrder = {
|
||||
...(detail as any),
|
||||
id: assignRow.value.id,
|
||||
riderId: normalizedRiderId as any
|
||||
};
|
||||
const msg = await updateGltTicketOrder(payload);
|
||||
message.success(msg);
|
||||
closeAssign();
|
||||
reload();
|
||||
} catch (e: any) {
|
||||
// 表单校验失败时 e 可能不是 Error
|
||||
if (e?.message) {
|
||||
message.error(e.message);
|
||||
}
|
||||
} finally {
|
||||
assignConfirmLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/* 状态&时间:按你的规则派生(配送时间不为空=已派送、送达时间不为空=已送达、签收时间不为空=已签收) */
|
||||
const pickTime = (record: any, keys: string[]) => {
|
||||
for (const k of keys) {
|
||||
const v = record?.[k];
|
||||
if (v) return v as string;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getOrderTimes = (record: any) => {
|
||||
return {
|
||||
orderTime: pickTime(record, ['createTime', 'orderTime']),
|
||||
sendTime: pickTime(record, ['sendTime', 'dispatchTime']),
|
||||
arriveTime: pickTime(record, ['arriveTime', 'deliveredTime']),
|
||||
signTime: pickTime(record, ['signTime', 'signedTime', 'receiveTime'])
|
||||
};
|
||||
};
|
||||
|
||||
const formatTime = (value?: string) => {
|
||||
return value ? toDateString(value, 'yyyy-MM-dd HH:mm:ss') : '-';
|
||||
};
|
||||
|
||||
const getOrderStatus = (record: any) => {
|
||||
const { sendTime, arriveTime, signTime } = getOrderTimes(record);
|
||||
if (signTime) return { value: 3, label: '已签收', color: 'red' };
|
||||
if (arriveTime) return { value: 2, label: '已送达', color: 'orange' };
|
||||
if (sendTime) return { value: 1, label: '已派送', color: 'blue' };
|
||||
// 兼容后端只返回 status、不返回时间的情况
|
||||
if (record?.status === 3) return { value: 3, label: '已签收', color: 'red' };
|
||||
if (record?.status === 2) return { value: 2, label: '已送达', color: 'orange' };
|
||||
if (record?.status === 1) return { value: 1, label: '已派送', color: 'blue' };
|
||||
return { value: 0, label: '待配送', color: 'green' };
|
||||
};
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
@@ -191,12 +392,11 @@
|
||||
// ellipsis: true
|
||||
// },
|
||||
{
|
||||
title: '下单时间',
|
||||
title: '时间节点',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
width: 250,
|
||||
align: 'center',
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||
key: 'times',
|
||||
width: 260,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
|
||||
Reference in New Issue
Block a user