From 5b6cb43457729046ecc31f238bc6737b8e7d3e8f Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Fri, 9 Oct 2026 17:32:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=90=E5=8D=95=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=97=A5=E5=BF=97=20Tab=EF=BC=8C=E5=B9=B6=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=8F=BD=E6=94=B6=E4=B8=8B=E5=8D=95=E4=B8=8E=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- src/api/order/index.ts | 65 ++ src/api/order/pickupOrder.ts | 19 + src/locale/en-US.ts | 31 +- src/locale/zh-CN.ts | 31 +- .../components/CreatePickupOrderModal.vue | 11 +- .../myOrders/components/OrderDetailDrawer.vue | 222 ++++++- .../pickupOrder/PickupOrderDetailDrawer.vue | 605 ++++++++++++++++-- .../components/CreatePickupSheetModal.vue | 11 +- src/views/order/pickupOrder/index.vue | 206 +----- src/views/order/placeOrder/index.vue | 21 + .../components/OrderDetailDrawer.vue | 224 ++++++- src/views/order/waybillManage/index.vue | 2 +- 12 files changed, 1185 insertions(+), 263 deletions(-) diff --git a/src/api/order/index.ts b/src/api/order/index.ts index f71469f..ce236ee 100644 --- a/src/api/order/index.ts +++ b/src/api/order/index.ts @@ -1181,6 +1181,71 @@ function mapExceptionEventItem(raw: any): OrderExceptionEventItem { } } +export interface OrderOperationLogChangeItem { + path: string + label: string + before?: string | null + after?: string | null +} + +export interface OrderOperationLogItem { + id: number + orderId: number + serviceNo?: string + action: string + actionName: string + operatorId: number + operatorName?: string + operatorType?: string + source?: string + changes: OrderOperationLogChangeItem[] + remark?: string + createTime?: string +} + +export interface OrderOperationLogListResult { + items: OrderOperationLogItem[] +} + +function mapOperationLogItem(raw: any): OrderOperationLogItem { + const changesRaw = raw?.changes ?? raw?.Changes ?? [] + const changes = Array.isArray(changesRaw) + ? changesRaw.map((c: any) => ({ + path: String(c?.path ?? c?.Path ?? ''), + label: String(c?.label ?? c?.Label ?? c?.path ?? c?.Path ?? ''), + before: c?.before ?? c?.Before ?? null, + after: c?.after ?? c?.After ?? null, + })) + : [] + return { + id: Number(raw?.id ?? raw?.Id ?? 0), + orderId: Number(raw?.orderId ?? raw?.OrderId ?? 0), + serviceNo: raw?.serviceNo ?? raw?.ServiceNo, + action: String(raw?.action ?? raw?.Action ?? ''), + actionName: String(raw?.actionName ?? raw?.ActionName ?? ''), + operatorId: Number(raw?.operatorId ?? raw?.OperatorId ?? 0), + operatorName: raw?.operatorName ?? raw?.OperatorName, + operatorType: raw?.operatorType ?? raw?.OperatorType, + source: raw?.source ?? raw?.Source, + changes, + remark: raw?.remark ?? raw?.Remark, + createTime: raw?.createTime ?? raw?.CreateTime, + } +} + +/** GET /order/operation-log/list */ +export async function getOrderOperationLogList( + orderId: number | string, +): Promise { + const res = await axiosInstance.get(orderUrl('/order/operation-log/list'), { + params: { orderId: Number(orderId) }, + }) + const data = unwrapData(res.data) ?? {} + const itemsRaw = data?.items ?? data?.Items ?? [] + const items = Array.isArray(itemsRaw) ? itemsRaw.map(mapOperationLogItem) : [] + return { items } +} + /** GET /order/exception-events/list */ export async function getOrderExceptionEventList(orderId: number | string): Promise { const res = await axiosInstance.get(orderUrl('/order/exception-events/list'), { diff --git a/src/api/order/pickupOrder.ts b/src/api/order/pickupOrder.ts index b948fe8..aeddc19 100644 --- a/src/api/order/pickupOrder.ts +++ b/src/api/order/pickupOrder.ts @@ -73,6 +73,25 @@ export const schedulePickupOrder = (body: { export const reschedulePickupOrder = (body: { id: number | string; expectPickupTime: string }) => postOk('/order/pickup-order/reschedule', body as unknown as Record) +export const updatePickupOrderInfo = (body: { + id: number | string + warehouseId?: number + senderAddressId?: number + expectPickupTime?: string + carrierService?: string + carrierWaybillNo?: string + remark?: string +}) => postOk('/order/pickup-order/update-info', body as unknown as Record) + +export const updatePickupOrderMeasure = (body: { + id: number | string + totalActualWeight: number + packageSizes: Array<{ length: number; width: number; height: number; count: number }> +}) => postOk('/order/pickup-order/update-measure', body as unknown as Record) + +export const recalcPickupOrderFee = (id: number | string) => + postOk('/order/pickup-order/recalc-fee', { id }) + export const markPickupPicked = (id: number | string) => postOk('/order/pickup-order/mark-picked', { id }) diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts index 0d2986c..e169002 100644 --- a/src/locale/en-US.ts +++ b/src/locale/en-US.ts @@ -896,6 +896,26 @@ export default { 'order.pickupOrder.settlementStatus.pending': 'Pending', 'order.pickupOrder.settlementStatus.settled': 'Settled', 'order.pickupOrder.actions.reschedule': 'Reschedule', + 'order.pickupOrder.actions.editInfo': 'Edit pickup info', + 'order.pickupOrder.actions.saveInfo': 'Save', + 'order.pickupOrder.actions.cancelEdit': 'Cancel', + 'order.pickupOrder.actions.editMeasure': 'Edit dimensions', + 'order.pickupOrder.actions.saveMeasure': 'Save dimensions', + 'order.pickupOrder.actions.recalcFee': 'Recalculate fee', + 'order.pickupOrder.actions.addSize': 'Add size', + 'order.pickupOrder.actions.removeSize': 'Remove', + 'order.pickupOrder.form.carrierServicePlaceholder': 'Select carrier service', + 'order.pickupOrder.form.sizeLength': 'L', + 'order.pickupOrder.form.sizeWidth': 'W', + 'order.pickupOrder.form.sizeHeight': 'H', + 'order.pickupOrder.form.sizeCount': 'Qty', + 'order.pickupOrder.message.infoSaveOk': 'Pickup info saved', + 'order.pickupOrder.message.measureSaveOk': 'Dimensions saved', + 'order.pickupOrder.message.recalcOk': 'Fee recalculated', + 'order.pickupOrder.message.recalcNeedMeasure': 'Save weight and sizes before recalculating', + 'order.pickupOrder.message.packageSizeRequired': 'Enter at least one valid size', + 'order.pickupOrder.message.expectTimeRequired': 'Enter expected pickup time', + 'order.pickupOrder.message.optionsLoadFailed': 'Failed to load options', 'order.pickupOrder.actions.batchConfirmFreight': 'Batch Confirm Freight', 'order.pickupOrder.actions.batchSchedule': 'Batch Draft Forecast', 'order.pickupOrder.actions.copyServiceNos': 'Copy all service Nos.', @@ -1799,7 +1819,7 @@ export default { 'order.myOrders.actions.markProcessed': 'Mark processed', 'order.myOrders.reallocate.modalTitle': 'Reallocate ({count} selected)', 'order.myOrders.reallocate.hint': - 'Only recalculates payable and updates the service channel. Receivable and dimensions are unchanged. Paid orders are skipped. Changing channel clears provider service no. / tracking no.; re-forward downstream if needed.', + 'Only recalculates payable and updates the service channel. Receivable and dimensions are unchanged. Available for predeclared / pending allocation / payment / ship (including paid). Changing channel clears provider service no. / tracking no.; re-forward downstream if needed.', 'order.myOrders.reallocate.section.scheme': 'Allocation scheme', 'order.myOrders.reallocate.section.channel': 'Designated channel', 'order.myOrders.reallocate.schemeLabel': 'Allocation scheme', @@ -2110,6 +2130,15 @@ export default { 'order.myOrders.drawer.tabs.track': 'Tracking', 'order.myOrders.drawer.tabs.attachments': 'Attachments', 'order.myOrders.drawer.tabs.stockBind': 'Bound stock', + 'order.myOrders.drawer.tabs.operationLog': 'Operation log', + 'order.myOrders.drawer.operationLog.empty': 'No operation logs', + 'order.myOrders.drawer.operationLog.refresh': 'Refresh', + 'order.myOrders.drawer.operationLog.count': '{count} items', + 'order.myOrders.drawer.operationLog.field': 'Field', + 'order.myOrders.drawer.operationLog.before': 'Before', + 'order.myOrders.drawer.operationLog.after': 'After', + 'order.myOrders.drawer.operationLog.noChanges': 'No field changes', + 'order.myOrders.drawer.operationLog.operator': 'Operator', 'order.myOrders.drawer.tabs.issueHandling': 'Issue handling', 'order.myOrders.drawer.issueHandling.pendingCount': 'Pending {count}', 'order.myOrders.drawer.issueHandling.processedCount': 'Processed {count}', diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts index c98f79a..9c000a6 100644 --- a/src/locale/zh-CN.ts +++ b/src/locale/zh-CN.ts @@ -885,6 +885,26 @@ export default { 'order.pickupOrder.settlementStatus.pending': '待结算', 'order.pickupOrder.settlementStatus.settled': '已结算', 'order.pickupOrder.actions.reschedule': '改约', + 'order.pickupOrder.actions.editInfo': '编辑揽收信息', + 'order.pickupOrder.actions.saveInfo': '保存', + 'order.pickupOrder.actions.cancelEdit': '取消', + 'order.pickupOrder.actions.editMeasure': '编辑件重尺', + 'order.pickupOrder.actions.saveMeasure': '保存件重尺', + 'order.pickupOrder.actions.recalcFee': '重算费用', + 'order.pickupOrder.actions.addSize': '添加尺寸', + 'order.pickupOrder.actions.removeSize': '删除', + 'order.pickupOrder.form.carrierServicePlaceholder': '请选择承运服务', + 'order.pickupOrder.form.sizeLength': '长', + 'order.pickupOrder.form.sizeWidth': '宽', + 'order.pickupOrder.form.sizeHeight': '高', + 'order.pickupOrder.form.sizeCount': '件数', + 'order.pickupOrder.message.infoSaveOk': '揽收信息已保存', + 'order.pickupOrder.message.measureSaveOk': '件重尺已保存', + 'order.pickupOrder.message.recalcOk': '费用已重算', + 'order.pickupOrder.message.recalcNeedMeasure': '请先保存实重与尺寸后再重算', + 'order.pickupOrder.message.packageSizeRequired': '请至少填写一条有效尺寸', + 'order.pickupOrder.message.expectTimeRequired': '请填写期望揽收时间', + 'order.pickupOrder.message.optionsLoadFailed': '加载选项失败', 'order.pickupOrder.actions.batchConfirmFreight': '批量确认运费', 'order.pickupOrder.actions.batchSchedule': '批量草稿预报', 'order.pickupOrder.actions.copyServiceNos': '复制全部运单号', @@ -1626,7 +1646,7 @@ export default { 'order.waybillManage.importDownstream.failRow': '第 {row} 行({waybill}):{reason}', 'order.myOrders.reallocate.modalTitle': '重新排货(已选 {count} 单)', 'order.myOrders.reallocate.hint': - '仅重算应付并更新服务渠道,不改应收与件重尺;已付款订单会自动跳过。换渠道会清空服务商单号/跟踪号,需自行重新预报下游。', + '仅重算应付并更新服务渠道,不改应收与件重尺;已预报/待排货/待支付/待发货可用(含已付款)。换渠道会清空服务商单号/跟踪号,需自行重新预报下游。', 'order.myOrders.reallocate.section.scheme': '排货方案', 'order.myOrders.reallocate.section.channel': '指定渠道', 'order.myOrders.reallocate.schemeLabel': '排货方案', @@ -1971,6 +1991,15 @@ export default { 'order.myOrders.drawer.tabs.track': '轨迹', 'order.myOrders.drawer.tabs.attachments': '附件', 'order.myOrders.drawer.tabs.stockBind': '绑定包裹/库存', + 'order.myOrders.drawer.tabs.operationLog': '操作日志', + 'order.myOrders.drawer.operationLog.empty': '暂无操作日志', + 'order.myOrders.drawer.operationLog.refresh': '刷新', + 'order.myOrders.drawer.operationLog.count': '{count} 条', + 'order.myOrders.drawer.operationLog.field': '字段', + 'order.myOrders.drawer.operationLog.before': '修改前', + 'order.myOrders.drawer.operationLog.after': '修改后', + 'order.myOrders.drawer.operationLog.noChanges': '无字段变更', + 'order.myOrders.drawer.operationLog.operator': '操作人', 'order.myOrders.drawer.tabs.issueHandling': '问题处理', 'order.myOrders.drawer.issueHandling.pendingCount': '待处理 {count}', 'order.myOrders.drawer.issueHandling.processedCount': '已处理 {count}', diff --git a/src/views/order/myOrders/components/CreatePickupOrderModal.vue b/src/views/order/myOrders/components/CreatePickupOrderModal.vue index 4681895..c410b3e 100644 --- a/src/views/order/myOrders/components/CreatePickupOrderModal.vue +++ b/src/views/order/myOrders/components/CreatePickupOrderModal.vue @@ -38,11 +38,10 @@ style="width: 100%" /> - + @@ -178,7 +177,7 @@ async function open(rows: Array<{ id: number | string; serviceNo?: string; wareh })) form.warehouseId = whIds[0] form.expectPickupTime = defaultPickupTime() - form.carrierService = '' + form.carrierService = carrierServiceOptions.value[0]?.value || '' form.remark = '' form.senderAddressId = undefined @@ -200,6 +199,10 @@ async function handleOk() { $baseMessage(t('order.myOrders.createPickup.needTime'), 'warning') return } + if (!form.carrierService?.trim()) { + $baseMessage(t('order.pickupOrder.message.carrierServiceRequired'), 'warning') + return + } submitting.value = true try { @@ -216,7 +219,7 @@ async function handleOk() { senderAddressId: form.senderAddressId, direction: 'forward', warehouseId: form.warehouseId, - carrierService: form.carrierService || undefined, + carrierService: form.carrierService.trim(), expectPickupTime: form.expectPickupTime, orderIds: orderIds.value, remark: form.remark || undefined, diff --git a/src/views/order/myOrders/components/OrderDetailDrawer.vue b/src/views/order/myOrders/components/OrderDetailDrawer.vue index 806b273..0f35f56 100644 --- a/src/views/order/myOrders/components/OrderDetailDrawer.vue +++ b/src/views/order/myOrders/components/OrderDetailDrawer.vue @@ -17,7 +17,7 @@
@@ -65,6 +65,17 @@ >{{ $t('order.myOrders.drawer.track.refresh') }} +
@@ -566,6 +577,63 @@ hide-refresh /> + + +
+ +
+
+
+
+ {{ item.actionName || item.action }} + + {{ item.changes.length }} + +
+
+ {{ item.operatorName || $t('order.myOrders.drawer.operationLog.operator') }} + {{ formatOperationLogTime(item.createTime) }} + + +
+
+
+

{{ item.remark }}

+
+
+ {{ $t('order.myOrders.drawer.operationLog.field') }} + {{ $t('order.myOrders.drawer.operationLog.before') }} + {{ $t('order.myOrders.drawer.operationLog.after') }} +
+
+ {{ change.label || change.path }} + {{ change.before ?? '--' }} + {{ change.after ?? '--' }} +
+
+ +
+
+
+ +
+
@@ -608,6 +676,8 @@ import { postOrderWeightUpdate, postOrderRecalculateReceivable, postOrderRecalculatePayable, + getOrderOperationLogList, + type OrderOperationLogItem, } from '@/api/order'; import { getDictListAll, type DictDto } from '@/api/system/dict'; import { getOrderStatusMeta, getOrderStatusTagStyle, resolveOrderStatusCode } from '@/constants/orderStatus'; @@ -983,8 +1053,13 @@ const tabList = computed(() => [ { key: 'track', label: t('order.myOrders.drawer.tabs.track') }, { key: 'attachments', label: t('order.myOrders.drawer.tabs.attachments') }, { key: 'stockBind', label: t('order.myOrders.drawer.tabs.stockBind') }, + { key: 'operationLog', label: t('order.myOrders.drawer.tabs.operationLog') }, ]); +const operationLogList = ref([]); +const operationLogLoading = ref(false); +const operationLogExpandedIds = ref>(new Set()); + const slidingTabItems = computed(() => tabList.value.map((tab) => ({ key: tab.key, label: tab.label })), ); @@ -1987,6 +2062,45 @@ async function handleRecalculatePayable() { } } +async function loadOperationLogs() { + const id = orderId.value; + if (!id) { + operationLogList.value = []; + return; + } + operationLogLoading.value = true; + try { + const res = await getOrderOperationLogList(id); + operationLogList.value = res.items || []; + operationLogExpandedIds.value = new Set( + operationLogList.value.slice(0, 3).map((x) => x.id), + ); + } catch { + operationLogList.value = []; + } finally { + operationLogLoading.value = false; + } +} + +function isOperationLogExpanded(id: number) { + return operationLogExpandedIds.value.has(id); +} + +function toggleOperationLogExpand(id: number) { + const next = new Set(operationLogExpandedIds.value); + if (next.has(id)) next.delete(id); + else next.add(id); + operationLogExpandedIds.value = next; +} + +function formatOperationLogTime(value?: string) { + if (!value) return '--'; + const d = new Date(value); + if (Number.isNaN(d.getTime())) return String(value); + const pad = (n: number) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; +} + function handleTabChange(tabKey: string) { const next = resolveDrawerTab(tabKey); if (next !== tabKey) { @@ -2003,6 +2117,9 @@ function handleTabChange(tabKey: string) { weightViewMode.value = 'all'; void ensureFeeItemDictLoaded(); } + if (next === 'operationLog') { + void loadOperationLogs(); + } } function handleCancel() { @@ -2024,13 +2141,16 @@ watch( if (activeTab.value === 'weightFee') { void ensureFeeItemDictLoaded(); } + if (activeTab.value === 'operationLog') { + void loadOperationLogs(); + } } }, { immediate: true, deep: true } ); defineExpose({ - openDrawer: (opts?: { tab?: 'basic' | 'weightFee' | 'weight' | 'fee' | 'track' | 'attachments' | 'stockBind' }) => { + openDrawer: (opts?: { tab?: 'basic' | 'weightFee' | 'weight' | 'fee' | 'track' | 'attachments' | 'stockBind' | 'operationLog' }) => { const tab = resolveDrawerTab(opts?.tab); activeTab.value = tab; basicViewMode.value = 'all'; @@ -3117,4 +3237,102 @@ defineExpose({ padding-bottom: 0; } } + +.operation-log-spin { + width: 100%; +} + +.operation-log-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.operation-log-item { + border: 1px solid var(--color-border-2); + border-radius: 4px; + background: #fff; + overflow: hidden; +} + +.operation-log-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 12px; + cursor: pointer; + user-select: none; + + &:hover { + background: var(--color-fill-1); + } +} + +.operation-log-head-main { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.operation-log-action { + font-size: 13px; + font-weight: 600; + color: var(--color-text-1); +} + +.operation-log-head-meta { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; + font-size: 12px; + color: var(--color-text-3); +} + +.operation-log-body { + padding: 0 12px 12px; + border-top: 1px solid var(--color-border-2); +} + +.operation-log-remark { + margin: 10px 0 0; + font-size: 12px; + color: var(--color-text-3); + word-break: break-word; +} + +.operation-log-changes { + margin-top: 10px; + border: 1px solid var(--color-border-2); + border-radius: 4px; + overflow: hidden; +} + +.operation-log-changes-header, +.operation-log-changes-row { + display: grid; + grid-template-columns: 1.1fr 1fr 1fr; + gap: 8px; + padding: 8px 10px; + font-size: 12px; +} + +.operation-log-changes-header { + background: var(--color-fill-2); + color: var(--color-text-3); + font-weight: 500; +} + +.operation-log-changes-row { + color: var(--color-text-2); + border-top: 1px solid var(--color-border-2); + word-break: break-all; +} + +.operation-log-after { + color: var(--app-primary-color); + font-weight: 500; +} diff --git a/src/views/order/pickupOrder/PickupOrderDetailDrawer.vue b/src/views/order/pickupOrder/PickupOrderDetailDrawer.vue index bf73e82..27cd6a0 100644 --- a/src/views/order/pickupOrder/PickupOrderDetailDrawer.vue +++ b/src/views/order/pickupOrder/PickupOrderDetailDrawer.vue @@ -1,13 +1,26 @@