- - 实际箱数
- {{ customs.actualBoxCount }}
- 退税总票
- {{ customs.taxRefundCount }}
- 已覆盖
- {{ customs.coveredTaxRefundCount }}
- 缺失
- {{ missingCoverageCount }}
-
- - 首次发送批次
- {{ brokerSubmission.batchNo }}
- 发送状态
- {{ brokerSubmission.status === 'SENT' ? '已发送' : brokerSubmission.status }}
- 资料版本
- v{{ brokerSubmission.sourceVersion }}
- - 发送人
- {{ brokerSubmission.sentByName || '—' }}
- 发送时间
- {{ formatDateTime(brokerSubmission.sentAt) }}
-
+
+ - 首次发送批次
- {{ brokerSubmission.batchNo }}
- 发送状态
- {{ brokerSubmission.status === 'SENT' ? '已发送' : brokerSubmission.status }}
- 资料版本
- v{{ brokerSubmission.sourceVersion }}
+ - 发送人
- {{ brokerSubmission.sentByName || '—' }}
- 发送时间
- {{ formatDateTime(brokerSubmission.sentAt) }}
缺退税资料清单
@@ -370,5 +366,5 @@ onMounted(load)
diff --git a/src/modules/container-loading/components/ContainerLastLegFeesPanel.vue b/src/modules/container-loading/components/ContainerLastLegFeesPanel.vue
index 9ec2b1e7..4afed29c 100644
--- a/src/modules/container-loading/components/ContainerLastLegFeesPanel.vue
+++ b/src/modules/container-loading/components/ContainerLastLegFeesPanel.vue
@@ -186,9 +186,9 @@ onMounted(load)
{{ row.reviewReason || '—' }}
-
+
-
+
| {{ customerName(form.customerId) || editing.customerName || '—' }} | |
| |
| |
diff --git a/src/modules/container-loading/components/ContainerOverviewPanel.vue b/src/modules/container-loading/components/ContainerOverviewPanel.vue
index 8463d763..4c2c024e 100644
--- a/src/modules/container-loading/components/ContainerOverviewPanel.vue
+++ b/src/modules/container-loading/components/ContainerOverviewPanel.vue
@@ -153,7 +153,7 @@ onBeforeUnmount(() => { photoGeneration += 1; revokePhotoUrls() })
- - 完成进度
- 任务明细
- {{ overview.pda.itemCount }}
- 已处理
- {{ overview.pda.completedItemCount }}
- 未解决
- {{ overview.pda.unresolvedItemCount }}
- 有效照片
- {{ overview.pda.photoCounts.reduce((sum, item) => sum + item.count, 0) }}
暂无可查看的有效照片
+ - 完成进度
暂无可查看的有效照片
@@ -170,9 +170,9 @@ onBeforeUnmount(() => { photoGeneration += 1; revokePhotoUrls() })
.overview-section p { margin: 5px 0; color: var(--workbench-muted); font-size: var(--admin-font-body); }
.exception-management{padding-top:var(--admin-space-4);padding-bottom:var(--admin-space-4)}.exception-management__header{margin-bottom:7px!important}.exception-management__header>div:first-child{display:flex;min-width:0;align-items:center;gap:var(--admin-space-5)}.exception-current{display:flex;min-width:0;align-items:center;gap:9px;margin:0!important}.exception-current strong,.exception-current span{max-width:360px;white-space: normal;overflow-wrap: anywhere}.exception-current strong{color:var(--el-color-danger)}.exception-current span{font-size:var(--admin-font-caption)}.exception-management__actions{display:flex;align-items:center;gap:var(--admin-space-3)}.exception-history{overflow-x:auto;border-top:1px solid var(--workbench-border);font-size:var(--admin-font-caption)}.exception-history__head,.exception-history li{display:grid;grid-template-columns:145px 120px 130px minmax(220px,1fr);gap:var(--admin-space-3);align-items:center;min-width:680px;padding:var(--admin-space-2) var(--admin-space-1)}.exception-history__head{color:var(--workbench-muted);font-weight:650}.exception-history ol{margin:0;padding:0;list-style:none}.exception-history li{border-top:1px solid var(--workbench-border)}.exception-history li time,.exception-history li span{color:var(--workbench-muted);white-space: normal;overflow-wrap: anywhere}.exception-history__empty{margin:0!important;padding:7px var(--admin-space-1);border-top:1px solid var(--workbench-border);font-size:var(--admin-font-caption)!important}
.document-table-wrap{width:100%;overflow-x:auto;border:1px solid var(--workbench-border)}.document-table{width:100%;min-width:940px;table-layout:fixed}.document-table tbody tr:last-child td{border-bottom:0}.document-table th:nth-child(1){width:260px}.document-table th:nth-child(2){width:180px}.document-table th:nth-child(3){width:210px}.document-table th:nth-child(4){width:120px}.document-table th:nth-child(5){width:150px}.document-table th:nth-child(6){width:90px}.document-table__actions{position:sticky;left:0;z-index:1;box-shadow:1px 0 0 var(--workbench-border)}.document-table th.document-table__actions{z-index:2}.document-table td strong{font-size:var(--admin-font-body)}.document-table td small{display:block;overflow:hidden;max-width:170px;margin-top:2px;text-overflow:ellipsis;white-space:nowrap}.document-file-name{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.document-blocked-reason{color:var(--workbench-muted)}.document-actions{white-space:nowrap}.document-actions :deep(.el-button){margin-left:0}.no-action{color:var(--workbench-muted)}.pickup-code-row :deep(.el-input){max-width:260px}.pickup-code-row :deep(.el-input__inner){font-variant-numeric:tabular-nums}
-.overview-panel .pda-summary { grid-template-columns: minmax(280px, 2fr) repeat(4, minmax(0, 1fr)); margin-bottom: var(--admin-space-4); }
+.overview-panel .pda-summary { grid-template-columns: minmax(0, 1fr); margin-bottom: var(--admin-space-4); }
.pda-progress dd { width: 100%; }
.photo-gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,220px));gap:var(--admin-space-3);margin:var(--admin-space-4) 0}.photo-thumb{display:grid;min-width:0;overflow:hidden;padding:0;border:1px solid var(--workbench-border);background:var(--admin-surface);color:inherit;text-align:left;cursor:pointer}.photo-thumb:disabled{cursor:default}.photo-thumb img,.photo-placeholder{width:100%;height:112px;object-fit:contain;background:var(--cc-bg-subtle,#f7f8fa)}.photo-placeholder{display:grid;place-items:center;padding:var(--admin-space-3);color:var(--workbench-muted);font-size:var(--admin-font-caption)}.photo-placeholder--error{color:var(--el-color-danger)}.photo-caption{display:grid;gap:var(--admin-space-2);padding:var(--admin-space-2) var(--admin-space-3)}.photo-caption small{white-space:nowrap}.compact-empty{margin:var(--admin-space-2) 0;color:var(--workbench-muted);font-size:var(--admin-font-caption)}.compact-timeline{display:grid;margin:0;padding:0;list-style:none}.compact-timeline li{display:grid;grid-template-columns:130px 130px 110px minmax(0,1fr);gap:var(--admin-space-3);padding:var(--admin-space-2) 0;border-top:1px solid var(--workbench-border);font-size:var(--admin-font-caption)}.compact-timeline li:first-child{border-top:0}.compact-timeline time,.compact-timeline span,.compact-timeline small{overflow:hidden;color:var(--workbench-muted);text-overflow:ellipsis;white-space:nowrap}
-@media (max-width: 1200px){.photo-gallery{grid-template-columns:repeat(4,minmax(0,1fr))}.overview-panel .pda-summary{grid-template-columns:1fr 1fr}.compact-timeline li{grid-template-columns:120px 120px minmax(0,1fr)}.compact-timeline small{grid-column:2/-1}}
+@media (max-width: 1200px){.photo-gallery{grid-template-columns:repeat(4,minmax(0,1fr))}.overview-panel .pda-summary{grid-template-columns:1fr}.compact-timeline li{grid-template-columns:120px 120px minmax(0,1fr)}.compact-timeline small{grid-column:2/-1}}
@media (max-width: 720px) { .exception-management__header{align-items:flex-start;flex-direction:column}.exception-management__header>div:first-child{align-items:flex-start;flex-direction:column;gap:3px}.exception-current{align-items:flex-start;flex-direction:column;gap:2px}.photo-gallery{grid-template-columns:repeat(2,minmax(0,1fr))}.compact-timeline li{grid-template-columns:1fr 1fr}.compact-timeline span,.compact-timeline small{grid-column:auto;white-space:normal} }
diff --git a/src/modules/container-loading/components/OceanBookingLinkDialog.vue b/src/modules/container-loading/components/OceanBookingLinkDialog.vue
index a3c03766..c05ca065 100644
--- a/src/modules/container-loading/components/OceanBookingLinkDialog.vue
+++ b/src/modules/container-loading/components/OceanBookingLinkDialog.vue
@@ -101,9 +101,9 @@ defineExpose({ candidates, candidateKey, containerNo, save })
-
+
-
+
| {{ container?.cabinetNo || '—' }} |
|
|
diff --git a/src/modules/container-loading/pages/ContainerDetailView.vue b/src/modules/container-loading/pages/ContainerDetailView.vue
index 3c8b792f..05580c31 100644
--- a/src/modules/container-loading/pages/ContainerDetailView.vue
+++ b/src/modules/container-loading/pages/ContainerDetailView.vue
@@ -93,12 +93,10 @@ async function rememberWorkbenchTab(tabName: string | number) {
const workbenchDetail = ref(null)
const arrivalTime = ref(null)
const expectedArrivalText = computed(() => {
- const row = workbenchDetail.value
const arrival = arrivalTime.value
- const projected = projectWarehouseArrival(row?.actualFacts?.arrivedDestinationPortAt || row?.actualPortArrivalAt || row?.estimatedPortArrivalAt,
- arrival ? { ...arrival, plannedAt: arrival.estimatedWarehouseArrivalAt } : undefined)
+ const projected = projectWarehouseArrival(arrival ? { ...arrival, plannedAt: arrival.estimatedWarehouseArrivalAt } : undefined)
return arrival?.estimatedWarehouseArrivalAt ? formatTime(arrival.estimatedWarehouseArrivalAt)
- : projected.date ? `${projected.date}(默认推算)` : '—'
+ : projected.date ? `${projected.date}${projected.isDefault ? '(默认推算)' : ''}` : '—'
})
const hasAction = (action: ContainerWorkbenchAction) => Boolean(workbenchDetail.value?.allowedActions?.includes(action))
const canEdit = computed(() => isDomestic.value && access.can('platform:firstleg:container:edit') && hasAction('EDIT_CONTAINER'))
@@ -322,9 +320,6 @@ const overseasStageText = computed(() => {
const primaryStatusText = computed(() => isDomestic.value ? statusText(detail.value?.planStatus) : overseasStageText.value)
const loadingListMode = computed(() => detail.value?.planStatus === 'COMPLETED' ? 'confirmed' : 'preload')
const loadingListHeading = computed(() => loadingListMode.value === 'confirmed' ? '装柜清单' : '预装清单')
-const loadingListDescription = computed(() => loadingListMode.value === 'confirmed'
- ? '核对作业证据、报关资料与装柜货物。'
- : '核对预装货物与 PDA 作业进度。')
const overseasDestinationText = computed(() => {
const bookingDestination = [detail.value?.destinationWarehouseCode, detail.value?.destinationWarehouseName]
.filter(Boolean).join(' / ')
@@ -739,7 +734,7 @@ onMounted(async () => {
-
+
@@ -757,24 +752,24 @@ onMounted(async () => {
-
- 选择文件{{ selectedDocument ? '上传新文件将替换当前资料并保留历史。' : `请选择需要提交的${documentTitle}。` }}
-
-
-
+
+ 选择文件{{ selectedDocument ? '上传新文件将替换当前资料并保留历史。' : `请选择需要提交的${documentTitle}。` }}
+
+
+
正在读取实际装柜来源…
实际装柜来源
| 操作 | 入仓码 / 订单号 | 目的地 | 箱数 | 实际卸货 |
|---|
| {{ row.actualAt ? '查看签收' : canWriteDirect ? '登记本票' : '查看本票' }} | {{ row.inboundCode || row.orderNo || '—' }} | {{ row.destinationName || '—' }} | {{ row.packageCount ?? '—' }} | {{ row.actualAt || '尚未登记' }} {{ row.timezone || '' }} |
当前没有可登记的直送来源。
- {{ directSelected.inboundCode || directSelected.orderNo || '本票签收' }}
| 实际卸货当地时间 按真实装柜来源逐票登记签收;全部来源完成后核验还柜资格。海外仓接仓仍使用原仓库收货流程。 {{ directSelected?.actualAt || '尚未登记' }} | {{ directSelected?.timezone || '—' }} |
| {{ directSelected?.receivedBy || '—' }} | {{ directSelected?.evidenceNo || '—' }} |
|
该票已登记,签收事实只读。
+ {{ directSelected.inboundCode || directSelected.orderNo || '本票签收' }}
| 实际卸货当地时间 按真实装柜来源逐票登记签收;全部来源完成后核验还柜资格。海外仓接仓仍使用原仓库收货流程。 {{ directSelected?.actualAt || '尚未登记' }} | {{ directSelected?.timezone || '—' }} |
| {{ directSelected?.receivedBy || '—' }} | {{ directSelected?.evidenceNo || '—' }} |
|
该票已登记,签收事实只读。
-
+
正在核对码头拖车当前状态…
时间沿用原业务当地时间。
- | {{ terminal?.containerNo || detail?.containerNo || '—' }} |
| {{ terminal?.fleetSupplierName || (terminal?.fleetSupplierId ? '—' : '尚未安排') }} | {{ terminal?.actualPickupAt || '尚未登记' }} |
| visible && searchTerminalSuppliers()"> | |
| |
|
|
+ | {{ terminal?.containerNo || detail?.containerNo || '—' }} |
| {{ terminal?.fleetSupplierName || (terminal?.fleetSupplierId ? '—' : '尚未安排') }} | {{ terminal?.actualPickupAt || '尚未登记' }} |
| visible && searchTerminalSuppliers()"> | |
| |
|
|
@@ -784,6 +779,7 @@ onMounted(async () => {
diff --git a/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.layout.test.ts b/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.layout.test.ts
index b5dacb3a..5d3b4e3d 100644
--- a/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.layout.test.ts
+++ b/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.layout.test.ts
@@ -4,12 +4,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import DispatchWorkflowDialog from './DispatchWorkflowDialog.vue'
import DispatchEvidenceFiles from './DispatchEvidenceFiles.vue'
import DispatchResourceFields from './DispatchResourceFields.vue'
+import { blankVehicleMaintenance } from '../dispatchVehicleMaintenance'
import AppDialog from '@/shared/components/AppDialog.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { ApiResponseError } from '@/shared/api/http'
import * as api from '../api/dispatches'
import { command, detail, loadingPreview, resources } from '../dispatchTestFixtures'
-import type { DispatchAction, DispatchActions, DispatchEvidenceFile, UpdateDispatch } from '../dispatchContracts'
+import type { DispatchAction, DispatchActions, DispatchDetail, DispatchEvidenceFile, UpdateDispatch } from '../dispatchContracts'
const identity = vi.hoisted(() => ({ id: '' }))
const can = vi.hoisted(() => vi.fn((_code: string) => true))
@@ -43,8 +44,8 @@ beforeEach(() => {
vi.mocked(api.getDispatchEvidenceContent).mockResolvedValue(new Blob(['png'], { type: 'image/png' }))
vi.mocked(api.uploadDispatchEvidence).mockResolvedValue(evidence('302'))
})
-async function render(action: DispatchAction, operation?: 'ROUTE', files = false) {
- const initial = detail({ actions }); if (files) initial.vehicleInfo.vehicle!.evidenceFileIds = ['301']
+async function render(action: DispatchAction, operation?: 'ROUTE', files = false, overrides: Partial = {}) {
+ const initial = detail({ actions, ...overrides }); if (files) initial.vehicleInfo.vehicle!.evidenceFileIds = ['301']
vi.mocked(api.getDispatch).mockResolvedValueOnce(initial)
const wrapper = mount(DispatchWorkflowDialog, { attachTo: document.body, props: { modelValue: true, dispatchNo: 'PC/1', action, operation }, global: { plugins: [ElementPlus] } }); await flushPromises()
return { wrapper, dialog: wrapper.findComponent(AppDialog) }
@@ -52,6 +53,20 @@ async function render(action: DispatchAction, operation?: 'ROUTE', files = false
function field(wrapper: Pick, label: string) { return wrapper.findAllComponents({ name: 'ElFormItem' }).find(item => item.props('label') === label)! }
describe('dispatch workflow native layout', () => {
+ it('restores saved maintenance facts and shows associated orders without editable departure facts', async () => {
+ const saved = { ...blankVehicleMaintenance(), plannedDeliveryDate: '2026-11-02', fleetEmail: 'fleet@example.test', appointmentNo: 'APPT-SAVED' }
+ const { dialog } = await render('EDIT', undefined, false, { vehicleInfo: { ...resources(), maintenance: saved } })
+ expect((field(dialog, '车队邮箱').get('input').element as HTMLInputElement).value).toBe('fleet@example.test')
+ expect((field(dialog, '预约号').get('input').element as HTMLInputElement).value).toBe('APPT-SAVED')
+ expect(dialog.text()).toContain('关联订单 (1)')
+ const orders = dialog.findAll('[role="tab"]').find(tab => tab.text().includes('关联订单'))!
+ await orders.trigger('click')
+ expect(dialog.get('.maintenance-orders').text()).toContain('LM-1')
+ expect(dialog.get('.maintenance-orders').findAll('button, input')).toHaveLength(0)
+ dialog.vm.$emit('confirm'); await flushPromises()
+ expect(api.updateDispatch).toHaveBeenCalledWith('PC/1', expect.objectContaining({ maintenance: saved }))
+ expect(vi.mocked(api.updateDispatch).mock.calls[0]![1]).not.toHaveProperty('departedAt')
+ })
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('blocks %s approval for missing appointment but keeps rejection available', async action => {
vi.mocked(api.getDispatch).mockResolvedValue(detail({ actions: { ...actions, [action]: { allowed: true, blockCode: 'APPROVAL_BLOCKED', blockReason: '预约收货时间尚未确认' } } }))
const wrapper = mount(DispatchWorkflowDialog, { attachTo: document.body, props: { modelValue: true, dispatchNo: 'PC/1', action }, global: { plugins: [ElementPlus] } }); await flushPromises()
@@ -71,11 +86,17 @@ describe('dispatch workflow native layout', () => {
field(dialog, '实际托位容量').getComponent({ name: 'ElInputNumber' }).vm.$emit('update:modelValue', 22)
await field(dialog, '司机姓名').get('input').setValue('新司机')
field(dialog, '计划发车时间').getComponent({ name: 'ElDatePicker' }).vm.$emit('update:modelValue', '2026-11-01T10:00:00Z')
+ field(dialog, '预派送日期').getComponent({ name: 'ElDatePicker' }).vm.$emit('update:modelValue', '2026-11-02')
+ for (const [label, value] of [['派送方式', '外配'], ['提货方式', '一提多卸'], ['装车类型', '混装'], ['车辆类型', '厢式货车']]) field(dialog, label!).getComponent({ name: 'ElSelect' }).vm.$emit('update:modelValue', value)
+ await field(dialog, '车队邮箱').get('input').setValue(' fleet@example.test ')
+ await field(dialog, '预约账号').get('input').setValue(' fleet-account ')
+ await field(dialog, '预约号').get('input').setValue(' APPT-NEW ')
+ expect(field(dialog, '发车时间').get('input').attributes('readonly')).toBeDefined()
expect(dialog.find('textarea[aria-label="本次操作备注"]').exists()).toBe(false)
await dialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises()
dialog.vm.$emit('confirm'); await flushPromises()
const { vehicleStatus: _status, ...vehicle } = resources().vehicle
- expect(api.updateDispatch).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, publishLooseboardDemand: true, idempotencyKey: expect.any(String), vehicle: { ...vehicle, plate: 'CA-NEW', capacity: { ...vehicle.capacity, palletSpaces: 22 } }, driver: { ...resources().driver, name: '新司机' }, plannedDepartureAt: '2026-11-01T10:00:00Z', estimatedArrivalAt: null, remark: '真实备注' })
+ expect(api.updateDispatch).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, publishLooseboardDemand: true, idempotencyKey: expect.any(String), vehicle: { ...vehicle, plate: 'CA-NEW', capacity: { ...vehicle.capacity, palletSpaces: 22 } }, driver: { ...resources().driver, name: '新司机' }, maintenance: { plannedDeliveryDate: '2026-11-02', deliveryMethod: '外配', pickupMethod: '一提多卸', loadType: '混装', fleetEmail: 'fleet@example.test', vehicleType: '厢式货车', appointmentAccount: 'fleet-account', appointmentNo: 'APPT-NEW' }, plannedDepartureAt: '2026-11-01T10:00:00Z', estimatedArrivalAt: null, remark: '真实备注' })
})
it('saves all three resource photos before submitting review with the refreshed version', async () => {
const { dialog } = await render('EDIT')
@@ -179,6 +200,7 @@ describe('dispatch workflow native layout', () => {
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('shows saved photos and old audit evidence read-only in %s', async action => {
const { wrapper, dialog } = await render(action)
const value = detail({ actions })
+ value.vehicleInfo.vehicle!.evidenceFileIds = ['401']
value.vehicleInfo.driver!.evidence!.photoFileIds = ['402']
value.reviews.dispatchReview.attachments = { vehicleFileIds: [], driverPhotoFileIds: ['403'], licenseFileIds: [] }
vi.mocked(api.getDispatch).mockResolvedValueOnce(value)
@@ -186,6 +208,9 @@ describe('dispatch workflow native layout', () => {
const fields = dialog.findAllComponents(DispatchEvidenceFiles)
expect(fields.map(item => item.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片'])
expect(fields.every(item => item.props('readonly') && item.props('photoCard'))).toBe(true)
+ expect(fields[0]!.props('modelValue')).toEqual(['401'])
+ expect(api.getDispatchEvidence).toHaveBeenCalledWith('PC/1', 'VEHICLE', '401')
+ expect(api.getDispatchEvidenceContent).toHaveBeenCalledWith('PC/1', 'VEHICLE', '401')
expect(fields[1]!.props('modelValue')).toEqual(['402', '403'])
expect(dialog.findAll('input[type="file"]')).toHaveLength(0)
expect(api.uploadDispatchEvidence).not.toHaveBeenCalled()
diff --git a/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.vue b/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.vue
index 640f65dc..203337a5 100644
--- a/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.vue
+++ b/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.vue
@@ -15,7 +15,9 @@ import type { DispatchRoute, DispatchRouteCommand, DispatchRouteStop } from '../
import { DispatchInputError, requireVersion } from '../dispatchContracts'
import { dispatchExceptionLabels, dispatchReturnDirectionLabels, canDispatchAction, dispatchActionLabels, dispatchRejectionReason, dispatchReviewAttachmentFields, dispatchReviewEvidence } from '../dispatchPresentation'
import { isDefiniteDispatchRejection, useDispatchCommandRecovery, type PendingDispatchCommand } from '../dispatchCommandRecovery'
-import DispatchResourceFields from './DispatchResourceFields.vue'
+import DispatchVehicleMaintenanceForm from './DispatchVehicleMaintenanceForm.vue'
+import DispatchVehicleMaintenanceFees from './DispatchVehicleMaintenanceFees.vue'
+import { blankVehicleMaintenance, vehicleMaintenancePayload } from '../dispatchVehicleMaintenance'
import DispatchEvidenceFiles from './DispatchEvidenceFiles.vue'
const open = defineModel({ default: false })
@@ -42,7 +44,7 @@ const isReview = computed(() => ['DISPATCH_REVIEW', 'LOADING_REVIEW'].includes(c
const approvalBlock = computed(() => isReview.value && detail.value?.actions[currentAction.value]?.blockCode === 'APPROVAL_BLOCKED' ? detail.value.actions[currentAction.value]?.blockReason : null)
const reviewTime = (value: string | null | undefined) => value ? new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', dateStyle: 'short', timeStyle: 'short', hour12: false }).format(new Date(value)) : '未填写'
const exception = reactive({ exceptionType: '' as DispatchExceptionType | '', returnDirection: '' as DispatchReturnDirection | '', description: '', imageFileIds: [] as string[] })
-const form = reactive({ publishLooseboardDemand: true, plannedDepartureAt: '', estimatedArrivalAt: '', remark: '', decision: 'APPROVED' as 'APPROVED' | 'REJECTED', reason: '' })
+const form = reactive({ publishLooseboardDemand: true, plannedDepartureAt: '', estimatedArrivalAt: '', maintenance: blankVehicleMaintenance(), remark: '', decision: 'APPROVED' as 'APPROVED' | 'REJECTED', reason: '' })
let generation = 0
let editorRequestKey = ''
const cargoAction = computed(() => ['ADD_CARGO', 'REMOVE_CARGO'].includes(currentAction.value))
@@ -67,6 +69,7 @@ async function load() {
if (sequence !== generation) return
if (value.dispatchNo !== no) throw new DispatchInputError('排车业务号不一致,请重新打开')
detail.value = value
+ form.maintenance = value.vehicleInfo.maintenance ? { ...value.vehicleInfo.maintenance } : blankVehicleMaintenance()
Object.assign(exception, { exceptionType: '', returnDirection: '', description: '', imageFileIds: [] })
const oldVehicle = value.vehicleInfo.vehicle
vehicle.value = oldVehicle ? { mode: oldVehicle.mode, vehicleId: oldVehicle.vehicleId, plate: oldVehicle.plate, carrierName: oldVehicle.carrierName, capacity: oldVehicle.capacity ? { ...oldVehicle.capacity } : null, evidenceFileIds: [...oldVehicle.evidenceFileIds] } : blankVehicle()
@@ -107,7 +110,7 @@ function payload(): object {
case 'MARK_EXCEPTION':
if (!exception.exceptionType) throw new DispatchInputError('请选择异常类型')
return { ...common, exceptionType: exception.exceptionType, returnDirection: exception.returnDirection || null, description: exception.description.trim() || null, imageFileIds: [...exception.imageFileIds] } satisfies MarkDispatchException
- case 'EDIT': return { ...common, publishLooseboardDemand: form.publishLooseboardDemand, vehicle: vehicle.value, driver: driver.value, plannedDepartureAt: form.plannedDepartureAt || null, estimatedArrivalAt: form.estimatedArrivalAt || null, remark: form.remark || null } satisfies UpdateDispatch
+ case 'EDIT': return { ...common, publishLooseboardDemand: form.publishLooseboardDemand, vehicle: vehicle.value, driver: driver.value, maintenance: vehicleMaintenancePayload(form.maintenance), plannedDepartureAt: form.plannedDepartureAt || null, estimatedArrivalAt: form.estimatedArrivalAt || null, remark: form.remark || null } satisfies UpdateDispatch
case 'DISPATCH_REVIEW': case 'LOADING_REVIEW':
if (form.decision === 'APPROVED' && approvalBlock.value) throw new DispatchInputError(approvalBlock.value)
if (form.decision === 'REJECTED' && !form.reason.trim()) throw new DispatchInputError('驳回时必须填写原因')
@@ -231,9 +234,16 @@ watch([open, () => props.dispatchNo, () => props.action, () => props.operation],
-
- 发布公共散板需求
-
+
+
+
+
+
+
+
+
+
+
| 车牌 | {{ detail.vehicleInfo.vehicle?.plate || '—' }} | 司机 | {{ detail.vehicleInfo.driver?.name || '—' }} |
|---|
| 托盘数 | {{ detail.counts.palletCount }} 托 | 箱数 | {{ detail.counts.packageCount }} 箱 |
|---|
@@ -248,12 +258,7 @@ watch([open, () => props.dispatchNo, () => props.action, () => props.operation],
-
-
-
-
-
-
+
@@ -274,8 +279,6 @@ watch([open, () => props.dispatchNo, () => props.action, () => props.operation],