Files
mp-10584/src/views/glt/gltTicketOrder/index.vue
赵忠林 8fdb06162a feat(glt): 添加订单编号和状态字段支持
- 在 GltTicketOrder 模型中新增 orderNo 和 orderStatus 字段
- 在 GltUserTicket 模型中新增 orderStatus 字段
- 在订单编辑页面添加订单编号输入框并调整表单初始化逻辑
- 移除图片上传相关功能代码
- 在订单列表页面添加订单编号显示和订单状态标签展示
- 实现订单状态的标签渲染和颜色区分
- 调整表格列配置,替换原有的状态列为订单状态列
- 在删除确认按钮上添加订单状态条件判断
- 启用表格行双击事件以支持快速编辑功能
2026-03-01 21:11:36 +08:00

541 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key ==='nickname'">
<a-space>
<a-avatar :src="record.avatar" />
<div class="flex flex-col">
<div class="font-bold">{{ record.nickname }}</div>
<div class="text-gray-400">订单编号{{ record.orderNo }}</div>
<div class="text-gray-400">送货地址{{ record.address }}</div>
<div class="text-blue-400">联系电话{{ record.phone }}</div>
<div class="text-gray-400">买家留言{{ record.buyerRemarks }}</div>
</div>
</a-space>
</template>
<template v-if="column.key === 'storeName'">
<a-space>
<div class="flex flex-col">
<div class="font-bold">{{ record.storeName }}</div>
<div class="text-gray-400">门店地址{{ record.storeAddress }}</div>
<div class="text-blue-400">门店电话{{ record.storePhone }}</div>
<div class="text-gray-400">仓库地址{{ record.warehouseAddress }}</div>
</div>
</a-space>
</template>
<template v-if="column.key === 'riderName'">
<a-space>
<div class="flex flex-col">
<div class="text-gray-400">配送时间{{ formatDay(record.sendTime) || '-'}}</div>
<div class="text-gray-400 flex justify-between" style="min-width: 224px">
配送人员{{ 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 class="text-gray-400">留档图片<a-image :src="record.sendEndImg" v-if="record.sendEndImg" :width="50" /></div>
</div>
</a-space>
</template>
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag :color="getOrderStatus(record).color">
{{ getOrderStatus(record).label }}
</a-tag>
</template>
<template v-if="column.key === 'orderStatus'">
<a-space :size="6" wrap>
<!-- 订单状态 -->
<a-tag v-if="record.orderStatus === 0">未完成</a-tag>
<a-tag v-if="record.orderStatus === 1" color="green"
>已完成</a-tag
>
<a-tag v-if="record.orderStatus === 2">已关闭</a-tag>
<a-tag v-if="record.orderStatus === 3" color="red"
>关闭中</a-tag
>
<a-tag v-if="record.orderStatus === 4" color="red"
>退款申请中</a-tag
>
<a-tag v-if="record.orderStatus === 5" color="red"
>退款被拒绝</a-tag
>
<a-tag v-if="record.orderStatus === 6" color="orange"
>已退款</a-tag
>
<a-tag v-if="record.orderStatus === 7" color="pink"
>客户端申请退款</a-tag
>
</a-space>
</template>
<template v-if="column.key === 'totalNum'">
<div class="text-gray-800 text-3xl">{{ record.totalNum }}</div>
</template>
<template v-if="column.key ==='times'">
<a-space>
<div class="flex flex-col">
<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>
<template v-if="column.key === 'action'">
<a-space>
<!-- <a @click="openEdit(record)">修改</a>-->
<!-- <a-divider type="vertical" />-->
<a-popconfirm
v-if="record.orderStatus == 6"
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</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 { 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';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import {getPageTitle} from '@/utils/common';
import GltTicketOrderEdit from './components/gltTicketOrderEdit.vue';
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);
// 表格选中数据
const selection = ref<GltTicketOrder[]>([]);
// 当前编辑数据
const current = ref<GltTicketOrder | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
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.userId,
label,
disabled:
r.status === 0 ||
(currentRiderId != null && String(r.userId ?? '') === 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 formatDay = (value?: string) => {
return value ? toDateString(value, 'yyyy-MM-dd') : '-';
};
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,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageGltTicketOrder({
...where,
...orders,
page,
limit
});
};
// 完整的列配置(包含所有字段)
const columns = ref<ColumnItem[]>([
{
title: '票号',
dataIndex: 'userTicketId',
key: 'userTicketId',
width: 90
},
{
title: '订水用户',
dataIndex: 'nickname',
key: 'nickname'
},
// {
// title: '送货地址',
// dataIndex: 'address',
// key: 'address'
// },
{
title: '下单门店',
dataIndex: 'storeName',
key: 'storeName'
},
{
title: '配送信息',
dataIndex: 'riderName',
key: 'riderName'
},
{
title: '送水数量(桶)',
dataIndex: 'totalNum',
key: 'totalNum',
align: 'center',
width: 120
},
// {
// title: '买家留言',
// dataIndex: 'buyerRemarks',
// key: 'buyerRemarks'
// },
// {
// title: '用于统计',
// dataIndex: 'price',
// key: 'price'
// },
// {
// title: '备注',
// dataIndex: 'comments',
// key: 'comments',
// ellipsis: true
// },
{
title: '时间节点',
dataIndex: 'createTime',
key: 'times',
width: 260,
align: 'center'
},
{
title: '订单状态',
dataIndex: 'orderStatus',
key: 'orderStatus',
align: 'center',
width: 120
},
{
title: '操作',
key: 'action',
width: 90,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: GltTicketOrderParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: GltTicketOrder) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: GltTicketOrder) => {
const hide = message.loading('请求中..', 0);
removeGltTicketOrder(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchGltTicketOrder(selection.value.map((d) => d.id))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: GltTicketOrder) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
// openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'GltTicketOrder'
};
</script>
<style lang="less" scoped></style>