This commit is contained in:
2026-10-20 11:54:36 +08:00
parent 836e210b09
commit 7005228b52
8 changed files with 64 additions and 31 deletions
@@ -12,7 +12,7 @@ import { availableTruckCommands, truckCommandHints, truckCommandLabels, type Tru
import type { LocalTransportDetail } from '../types'
const props = defineProps<{ detail: LocalTransportDetail }>()
const emit = defineEmits<{ updated: [detail: LocalTransportDetail] }>()
const emit = defineEmits<{ updated: [detail: LocalTransportDetail]; busy: [value: boolean] }>()
const access = useAccessPreviewStore()
const commands = computed(() => access.can(overseasBusinessPermissions.truckStatus) ? availableTruckCommands(props.detail) : [])
const open = ref(false), saving = ref(false)
@@ -23,7 +23,8 @@ const pending = ref<TruckOperation | null>(null)
const form = reactive({ decision: 'APPROVE' as 'APPROVE' | 'REJECT', actualPalletCount: 1, packageCount: undefined as number | undefined, palletIds: [] as string[], remark: '' })
const selectablePallets = computed(() => props.detail.pallets.filter(pallet => pallet.status === 'RECEIVED'))
const review = computed(() => active.value === 'review' || active.value === 'review-pallet-overage')
const { captureBaseline } = useUnsavedWorkRegistration({ key: 'truck-order-command', label: computed(() => active.value ? truckCommandLabels[active.value] : '卡车单据操作'), isOpen: open, model: form, requiredPermission: computed(() => overseasBusinessPermissions.truckStatus) })
const { captureBaseline } = useUnsavedWorkRegistration({ key: 'truck-order-command', label: computed(() => active.value ? truckCommandLabels[active.value] : '卡车单据操作'), isOpen: computed(() => open.value || !!pending.value), model: reactive({ form, pending }), requiredPermission: computed(() => overseasBusinessPermissions.truckStatus) })
watch(saving, value => emit('busy', value))
watch(() => [props.detail.customerId, props.detail.orderNo, props.detail.version], () => {
pending.value = null; open.value = false; active.value = null; error.value = null
@@ -42,7 +43,7 @@ function start(command: TruckCommand) {
function buildOperation(): TruckOperation | null {
if (!active.value) return null
const base = { version: props.detail.version, idempotencyKey: crypto.randomUUID(), remark: form.remark.trim() || undefined }
const base = { version: props.detail.version, idempotencyKey: globalThis.crypto?.randomUUID?.() || `truck-${Date.now()}-${Math.random().toString(36).slice(2)}`, remark: form.remark.trim() || undefined }
if ((active.value === 'cancel' || (review.value && form.decision === 'REJECT')) && !base.remark) {
validationError.value = '请填写拒绝或取消原因'; return null
}
@@ -57,8 +58,8 @@ function buildOperation(): TruckOperation | null {
}
return { command: active.value, payload: { ...base, actualPalletCount: form.actualPalletCount, ...(form.packageCount == null ? {} : { packageCount: form.packageCount }) } }
case 'allocate-pallets':
if (!form.palletIds.length || form.palletIds.some(id => !selectablePallets.value.some(pallet => pallet.id === id))) {
validationError.value = '请至少选择一个已实收且未分配的托盘'; return null
if (!form.palletIds.length || form.palletIds.length > 500 || form.palletIds.some(id => !selectablePallets.value.some(pallet => pallet.id === id))) {
validationError.value = '请选择本单已实收且未分配的托盘,每次最多分配 500 托'; return null
}
return { command: active.value, payload: { ...base, palletIds: [...form.palletIds] } }
case 'cancel': return { command: active.value, payload: { ...base, status: 'CANCELLED' } }
@@ -98,14 +99,14 @@ async function submit() {
<el-form :model="form" label-width="130px" :disabled="saving || !!pending" @submit.prevent="submit">
<el-form-item v-if="review" label="审核结果"><el-radio-group v-model="form.decision"><el-radio value="APPROVE">通过</el-radio><el-radio value="REJECT">拒绝</el-radio></el-radio-group></el-form-item>
<template v-if="active === 'receive-pallets'">
<el-form-item label="累计实收托数" required><el-input-number v-model="form.actualPalletCount" :min="Math.max(1, detail.receivedPalletCount)" :precision="0" /></el-form-item>
<el-form-item label="累计实收托数" required><el-input-number v-model="form.actualPalletCount" :min="Math.max(1, detail.receivedPalletCount)" :precision="0" placeholder="请输入累计实收托数" /></el-form-item>
<el-form-item label="实收件数"><el-input-number v-model="form.packageCount" :min="0" :precision="0" placeholder="选填" /></el-form-item>
</template>
<el-form-item v-if="active === 'allocate-pallets'" label="分配托盘" required>
<el-checkbox-group v-model="form.palletIds"><el-checkbox v-for="pallet in selectablePallets" :key="pallet.id" :value="pallet.id">{{ pallet.palletNo }}({{ pallet.packageCount ?? '未登记' }} 件)</el-checkbox></el-checkbox-group>
<p v-if="!selectablePallets.length">暂无可分配托盘,请刷新核对实收记录。</p>
</el-form-item>
<el-form-item :label="review ? '审核意见' : '操作说明'" :required="active === 'cancel' || (review && form.decision === 'REJECT')"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit /></el-form-item>
<el-form-item :label="review ? '审核意见' : '操作说明'" :required="active === 'cancel' || (review && form.decision === 'REJECT')"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="1000" placeholder="请输入审核意见或操作说明,拒绝或取消时必填" show-word-limit /></el-form-item>
</el-form>
</template>
</AppDialog>
@@ -295,6 +295,14 @@ describe('overseas business mounted pages', () => {
})
it('mounts truck list with enabled create and supported types', async () => { const wrapper = mountPage(TransportOrdersView, { domain: 'truck' }); await flushPromises(); expect(listUsTruckOrders).toHaveBeenCalled(); expect(wrapper.text()).toContain('TK-200'); expect(wrapper.text()).toContain('15拖'); expect(wrapper.text()).toContain('2026-08-01 10:00'); expect(wrapper.findAll('button').find((button) => button.text().includes('新增订单'))?.attributes('disabled')).toBeUndefined() })
it('opens the requested truck review from the list using its customer scope', async () => {
const wrapper = mountPage(TransportOrdersView, { domain: 'truck' }); await flushPromises()
const auth = useAuthStore(); auth.permissions.push('platform:overseas-business:truck:status')
await wrapper.vm.$nextTick()
const review = wrapper.findAll('button').find(button => button.text() === '审核')
expect(review).toBeDefined(); await review!.trigger('click'); await flushPromises()
expect(routing.push).toHaveBeenCalledWith({ name: 'overseas-business-truck-detail', params: { orderId: 'TK-200' }, query: { customerId: '10002' } })
})
it('renders truck load failure without samples', async () => { vi.mocked(listUsTruckOrders).mockRejectedValue(new Error('hidden')); const wrapper = mountPage(TransportOrdersView, { domain: 'truck' }); await flushPromises(); expect(wrapper.text()).toContain('本土卡车订单加载失败'); expect(wrapper.text()).not.toContain('TK-200') })
it('loads and submits pending-review truck edit', async () => { routing.route.params = { orderId: 'TK-200' }; routing.route.query = { customerId: '10002' }; const wrapper = mountPage(TransportOrderFormView, { domain: 'truck' }); await flushPromises(); await wrapper.findAll('button').find((button) => button.text().includes('保存修改'))?.trigger('click'); await flushPromises(); expect(getUsTruckOrder).toHaveBeenCalledWith('TK-200', '10002'); expect(updateUsTruckOrder).toHaveBeenCalledWith('TK-200', '10002', expect.objectContaining({ version: 1, vehicleType: 'PALLET_15', originContactName: 'Bob', destinationContactName: 'Alice', pickupAt: '2026-08-28T09:30:00' })) })
it('keeps truck edit form and version unchanged after a 409', async () => { routing.route.params = { orderId: 'TK-200' }; routing.route.query = { customerId: '10002' }; vi.mocked(updateUsTruckOrder).mockRejectedValue(new ApiResponseError('订单版本冲突', 'B0409', 409)); const wrapper = mountPage(TransportOrderFormView, { domain: 'truck' }); await flushPromises(); const destination = wrapper.find('input[placeholder="城市 / 州"]'); await destination.setValue('Ontario-MARKER-EDIT'); await wrapper.findAll('button').find((button) => button.text().includes('保存修改'))?.trigger('click'); await flushPromises(); expect(updateUsTruckOrder).toHaveBeenCalledWith('TK-200', '10002', expect.objectContaining({ version: 1, destination: 'Ontario-MARKER-EDIT' })); expect((destination.element as HTMLInputElement).value).toBe('Ontario-MARKER-EDIT'); expect(document.body.textContent).toContain('订单版本冲突'); expect(routing.push).not.toHaveBeenCalled() })
@@ -83,7 +83,7 @@ describe('truck review and fulfillment', () => {
await click(wrapper, '登记实收'); Object.assign(formModel(wrapper), { actualPalletCount: 2, packageCount: 20 }); await confirm(wrapper)
await click(wrapper, '分配托盘'); formModel(wrapper).palletIds = current.pallets.map(row => row.id); await confirm(wrapper)
for (const label of ['确认待派车', '确认已派车', '确认发运', '确认完成']) { await click(wrapper, label); await confirm(wrapper) }
expect(wrapper.findComponent(TruckOrderActions).findAll('button')).toHaveLength(0)
expect(wrapper.find('.truck-order-actions').exists()).toBe(false)
expect(current.status).toBe('COMPLETED')
expect(vi.mocked(executeUsTruckCommand).mock.calls.map(call => call[2].payload.version)).toEqual([1, 2, 3, 4, 5, 6, 7])
expect(vi.mocked(executeUsTruckCommand).mock.calls[2][2]).toMatchObject({ command: 'allocate-pallets', payload: { palletIds: ['9007199254741001', '9007199254741002'] } })
@@ -97,16 +97,16 @@ describe('truck review and fulfillment', () => {
vi.mocked(executeUsTruckCommand).mockResolvedValue(order({ status: 'REJECTED', version: 2 }))
await confirm(wrapper)
expect(executeUsTruckCommand).toHaveBeenCalledWith('TR-TEST', '9007199254741999', expect.objectContaining({ command: 'review', payload: expect.objectContaining({ decision: 'REJECT', remark: '取送时间需重新确认', version: 1 }) }))
expect(wrapper.findComponent(TruckOrderActions).findAll('button')).toHaveLength(0)
expect(wrapper.find('.truck-order-actions').exists()).toBe(false)
})
it('preserves one immutable request and retries it after an ambiguous failure, then refreshes the detail', async () => {
vi.mocked(executeUsTruckCommand).mockRejectedValueOnce(new ApiResponseError('订单版本冲突', 'A0409', 409, 'TRUCK-409')).mockResolvedValueOnce(order({ status: 'WAITING_INBOUND', version: 2 }))
vi.mocked(executeUsTruckCommand).mockRejectedValueOnce(new ApiResponseError('订单版本冲突', 'A0409', 409, undefined, 'TRUCK-409')).mockResolvedValueOnce(order({ status: 'WAITING_INBOUND', version: 2 }))
const wrapper = mountDetail(); await flushPromises(); await click(wrapper, '审核')
formModel(wrapper).remark = '核对完成'; await confirm(wrapper)
expect(wrapper.text()).toContain('订单版本冲突')
expect(wrapper.findComponent({ name: 'ElForm' }).props('disabled')).toBe(true)
const original = structuredClone(vi.mocked(executeUsTruckCommand).mock.calls[0][2])
const original = JSON.parse(JSON.stringify(vi.mocked(executeUsTruckCommand).mock.calls[0][2]))
wrapper.findComponent(AppDialog).vm.$emit('update:modelValue', false); await flushPromises()
await click(wrapper, '审核'); await confirm(wrapper)
expect(vi.mocked(executeUsTruckCommand).mock.calls[1][2]).toEqual(original)
@@ -185,7 +185,7 @@ describe('truck review and fulfillment', () => {
})
it('retains error metadata and retries a failed detail read', async () => {
vi.mocked(getUsTruckOrder).mockRejectedValueOnce(new ApiResponseError('读取失败', 'A0409', 409, 'DETAIL-409')).mockResolvedValueOnce(order())
vi.mocked(getUsTruckOrder).mockRejectedValueOnce(new ApiResponseError('读取失败', 'A0409', 409, undefined, 'DETAIL-409')).mockResolvedValueOnce(order())
const wrapper = mountDetail(); await flushPromises()
expect(wrapper.text()).toContain('读取失败'); expect(wrapper.text()).toContain('DETAIL-409')
await wrapper.findComponent({ name: 'LoadErrorState' }).vm.$emit('retry'); await flushPromises()
@@ -18,7 +18,7 @@ import type { LocalTransportDetail, LocalTransportPallet, OverseasBusinessHistor
const route = useRoute(), router = useRouter()
const orderNo = computed(() => typeof route.params.orderId === 'string' ? route.params.orderId : '')
const customerId = computed(() => typeof route.query.customerId === 'string' ? route.query.customerId : '')
const detail = ref<LocalTransportDetail | null>(null), loading = ref(false)
const detail = ref<LocalTransportDetail | null>(null), loading = ref(false), saving = ref(false)
const error = ref<ApiErrorDetails | null>(null)
let requestSequence = 0
const dateTime = (value: string | null | undefined) => value ? value.replace('T', ' ').slice(0, 16) : '—'
@@ -33,7 +33,7 @@ const fields = computed(() => detail.value ? [
['已确认托数', detail.value.palletCount], ['累计实收托数', detail.value.receivedPalletCount], ['已分配托数', detail.value.allocatedPalletCount],
['创建时间', dateTime(detail.value.createdAt)], ['备注', detail.value.remark],
] : [])
const palletLabels: Record<string, string> = { PLANNED: '待实收', RECEIVED: '已实收', ALLOCATED: '已分配', OVERAGE_PENDING_REVIEW: '超托待审', OVERAGE_REJECTED: '超托拒收' }
const palletLabels: Record<string, string> = { EXPECTED: '待实收', PLANNED: '待实收', RECEIVED: '已实收', ALLOCATED: '已分配', OVERAGE_PENDING_REVIEW: '超托待审', OVERAGE_REJECTED: '超托拒收' }
const palletColumns: DataTableColumn[] = [
{ key: 'palletNo', prop: 'palletNo', label: '托盘编号', minWidth: 180 },
{ key: 'status', label: '状态', width: 130, formatter: (row: LocalTransportPallet) => palletLabels[row.status] || row.status },
@@ -49,6 +49,7 @@ const historyColumns: DataTableColumn[] = [
{ key: 'operatorId', prop: 'operatorId', label: '操作人', minWidth: 140 }, { key: 'remark', prop: 'remark', label: '审核意见/操作说明', minWidth: 240 },
]
async function load() {
if (saving.value) return
const sequence = ++requestSequence
detail.value = null; error.value = null
if (!orderNo.value || !customerId.value || customerId.value === '0') {
@@ -64,13 +65,13 @@ watch([orderNo, customerId], load, { immediate: true })
<template>
<AdminPage class="admin-density"><DetailPageLayout aria-label="本土卡车订单详情">
<template #header><div class="truck-detail-header"><el-button @click="router.push({ name: 'overseas-business-trucks' })"><ArrowLeft :size="16" />返回列表</el-button><h1>本土卡车订单 {{ detail?.orderNo || orderNo }}</h1></div></template>
<template #header><div class="truck-detail-header"><el-button :disabled="saving" @click="router.push({ name: 'overseas-business-trucks' })"><ArrowLeft :size="16" />返回列表</el-button><h1>本土卡车订单 {{ detail?.orderNo || orderNo }}</h1></div></template>
<LoadErrorState v-if="error" title="本土卡车订单暂时无法加载" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<el-skeleton v-else-if="loading" :rows="8" animated />
<template v-else-if="detail">
<section class="truck-detail-section"><div class="truck-detail-header"><h2>{{ statusLabel(detail.status) }}</h2><el-button @click="load">刷新订单</el-button></div>
<section class="truck-detail-section"><div class="truck-detail-header"><h2>{{ statusLabel(detail.status) }}</h2><el-button :disabled="saving" @click="load">刷新订单</el-button></div>
<FleetArrangementSummary v-if="detail.fleetArrangement" :arrangement="detail.fleetArrangement" />
<template v-else><p>审核通过 → 实收托盘 → 分配托盘 → 待派车 → 已派车 → 运输中 → 完成</p><TruckOrderActions :detail="detail" @updated="detail = $event" /></template>
<template v-else><p>审核通过 → 实收托盘 → 分配托盘 → 待派车 → 已派车 → 运输中 → 完成</p><TruckOrderActions :detail="detail" @updated="detail = $event" @busy="saving = $event" /></template>
</section>
<section class="truck-detail-section"><h2>订单资料</h2><el-descriptions :column="3" border><el-descriptions-item v-for="[label, value] in fields" :key="String(label)" :label="String(label)">{{ value ?? '—' }}</el-descriptions-item></el-descriptions></section>
<section class="truck-detail-section"><h2>托盘记录</h2><DataTable :data="detail.pallets" :columns="palletColumns" empty-text="暂无托盘记录,独立订单首次实收后生成托盘" /></section>
@@ -131,21 +131,21 @@ export interface RelabelDetailDto {
}
export interface RelabelQuantity {
labelPieceCount: number | null
cartonCount: number | null
skuCount: number
labelPieceCount: number | string | null
cartonCount: number | string | null
skuCount: number | string
duplicateCarton: boolean
quantityUnit: string
sourcePalletCount: number | null
targetPalletCount: number | null
sourcePalletCount: number | string | null
targetPalletCount: number | string | null
}
export interface RelabelQuantityTotals {
orderCount: number
skuCount: number
knownLabelPieceCount: number
unknownPieceOrders: number
knownCartonCount: number
unknownCartonOrders: number
orderCount: number | string
skuCount: number | string
knownLabelPieceCount: number | string
unknownPieceOrders: number | string
knownCartonCount: number | string
unknownCartonOrders: number | string
orders: Record<string, RelabelQuantity>
}
/** 汇总同一筛选下全部单据,不以当前页合计代替。 */
@@ -44,7 +44,26 @@ enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
describe('RelabelOrdersView', () => {
it('uses full-filter totals instead of the visible page and preserves unknown boxes', async () => {
vi.mocked(listRelabelOrders).mockResolvedValue({ records: [{ id: '1', relabelNo: 'RL-PIECE', skuCodes: 'A', totalPackageCount: 100, status: 'PROCESSING' }], page: 1, size: 10, total: 45, pages: 5 })
vi.mocked(getRelabelQuantities).mockResolvedValue({ orderCount: 45, skuCount: 12, knownLabelPieceCount: 300, unknownPieceOrders: 1, knownCartonCount: 8, unknownCartonOrders: 2, orders: { 'RL-PIECE': { labelPieceCount: 100, cartonCount: null, skuCount: 1, duplicateCarton: false, quantityUnit: 'PIECE', sourcePalletCount: null, targetPalletCount: null } } })
const wrapper = mount(RelabelOrdersView, { global: { plugins: [ElementPlus] } })
await flushPromises()
const summary = wrapper.get('[aria-label="全部筛选结果数量汇总"]')
expect(summary.text()).toContain('45 票')
expect(summary.text()).toContain('300 件')
expect(summary.text()).toContain('8 箱')
expect(wrapper.findAll('tbody tr').some(row => row.text().includes('RL-PIECE') && row.text().includes('未提供') && row.text().includes('100'))).toBe(true)
})
it('does not display all-unknown carton quantities as zero boxes', async () => {
vi.mocked(getRelabelQuantities).mockResolvedValue({ orderCount: 1, skuCount: 1, knownLabelPieceCount: 100, unknownPieceOrders: 0, knownCartonCount: 0, unknownCartonOrders: 1, orders: {} })
const wrapper = mount(RelabelOrdersView, { global: { plugins: [ElementPlus] } })
await flushPromises()
expect(wrapper.get('[aria-label="全部筛选结果数量汇总"]').text()).toContain('箱数未提供(1票)')
expect(wrapper.get('[aria-label="全部筛选结果数量汇总"]').text()).not.toContain('0 箱')
})
it('shows every SKU and quantity in the review dialog while keeping one box', async () => {
vi.mocked(getRelabelQuantities).mockResolvedValue({ orderCount: 1, skuCount: 2, knownLabelPieceCount: 15, unknownPieceOrders: 0, knownCartonCount: 1, unknownCartonOrders: 0, orders: { 'RL-MULTI': { labelPieceCount: 15, cartonCount: 1, skuCount: 2, duplicateCarton: false, quantityUnit: 'CARTON', sourcePalletCount: null, targetPalletCount: null } } })
vi.mocked(listRelabelOrders).mockResolvedValueOnce({ records: [{ id: '1', relabelNo: 'RL-MULTI', skuCodes: 'SKU-A,SKU-B', totalPackageCount: 1, status: 'PENDING_REVIEW' }], page: 1, size: 10, total: 1, pages: 1 })
vi.mocked(getRelabelOrder).mockResolvedValueOnce({ application: { id: '1', relabelNo: 'RL-MULTI', totalPackageCount: 1 }, items: [{ id: '1', skuCode: 'SKU-A', newSkuCode: 'NEW-A', skuItems: [{ skuCode: 'SKU-A', newSkuCode: 'NEW-A', quantity: 10 }, { skuCode: 'SKU-B', newSkuCode: 'NEW-B', quantity: 5 }] }], history: [] })
const wrapper = mount(RelabelOrdersView, { attachTo: document.body, global: { plugins: [ElementPlus] } })
@@ -36,8 +36,8 @@ const pageSize = ref(10)
const total = ref(0)
const quantities = ref<RelabelQuantityTotals | null>(null)
const quantityError = ref('')
function dimension(value: number | null | undefined) { return value == null ? '未提供' : String(value) }
function knownDimension(value: number, unknown: number, unit: string) { return Number(value) === 0 && Number(unknown) > 0 ? `${unit === '箱' ? '箱数' : '贴标件数'}未提供(${unknown}票)` : `已知 ${value} ${unit}${Number(unknown) > 0 ? `(${unknown}票未提供)` : ''}` }
function dimension(value: number | string | null | undefined) { return value == null ? '未提供' : String(value) }
function knownDimension(value: number | string, unknown: number | string, unit: string) { return Number(value) === 0 && Number(unknown) > 0 ? `${unit === '箱' ? '箱数' : '贴标件数'}未提供(${unknown}票)` : `已知 ${value} ${unit}${Number(unknown) > 0 ? `(${unknown}票未提供)` : ''}` }
const filters = ref<QueryModel>({ relabelNo: '', sku: '', status: '' })
const fields = computed<QueryField[]>(() => [
{ key: 'relabelNo', type: 'input', label: '换标单号', placeholder: '请输入换标单号', width: 220 },
@@ -66,6 +66,7 @@ const dialogMode = ref<'create' | 'edit'>('create')
const submitting = ref(false)
const detailLoading = ref(false)
const editSourceStatus = ref('')
const auditLabelPieces = ref('未提供')
const form = reactive({ orderNo: '', customerId: '', relatedOrderNo: '', sku: '', productName: '', cartons: undefined as number | undefined, budgetTotal: undefined as number | undefined, status: 'PENDING_PROCESSING', labelRequirement: '', relabelCost: undefined as number | undefined, remark: '', warehouseId: '', labelDeadline: '', contactName: '', contactPhone: '', shipmentNo: '', newSkuCode: '' })
const createIdempotencyKey = ref('')
const dialogError = ref<ApiErrorDetails | null>(null), customersError = ref<ApiErrorDetails | null>(null), warehousesError = ref<ApiErrorDetails | null>(null)
@@ -113,13 +114,14 @@ async function openForm(mode: 'create' | 'edit', source?: object) {
relatedOrderNo: String(detail.application?.lastlegOrderNo || detail.application?.customerOrderNo || row.relatedOrderNo || ''),
sku: [...new Set(detail.items.flatMap(relabelSkuCodes))].join('、') || row.sku || '',
productName: [...new Set(detail.items.map(line => line.productNameZh?.trim()).filter(Boolean))].join('、') || row.productName || '',
cartons: optionalNumber(detail.application?.totalPackageCount ?? row.cartons),
cartons: optionalNumber(quantities.value?.orders[String(row.orderNo)]?.cartonCount),
labelRequirement: [...new Set(detail.items.map(line => line.labelRequirement?.trim()).filter(Boolean))].join(';') || row.labelRequirement || '',
shipmentNo: [...new Set(detail.items.map(line => line.shipmentNo?.trim()).filter(Boolean))].join('、'),
newSkuCode: detail.items.flatMap(line => line.skuItems?.length ? line.skuItems.map(sku => `${sku.newSkuCode} × ${sku.quantity} 件`) : [line.newSkuCode || '']).filter(Boolean).join(';'),
relabelCost: optionalNumber(detail.application?.chargeAmount ?? row.relabelCost),
remark: String(detail.remark || detail.application?.reviewRemark || row.remark || ''),
})
auditLabelPieces.value = dimension(quantities.value?.orders[String(row.orderNo)]?.labelPieceCount)
} catch (error) {
ElMessage.error(getApiErrorDetails(error, '换标单详情加载失败,请重试').message)
dialogOpen.value = false
@@ -295,6 +297,7 @@ watch(dialogOpen, (open) => { if (!open) createIdempotencyKey.value = '' })
<section class="density-dialog-section" data-tone="source" aria-label="换标审核原始资料"><h3 class="density-dialog-section__title">原始资料</h3><table class="density-table"><tbody>
<tr><th scope="row">关联订单</th><td colspan="3">{{ form.relatedOrderNo || '—' }}</td></tr><tr><th scope="row">商品SKU</th><td colspan="3">{{ form.sku || '—' }}</td></tr><tr><th scope="row">中文品名</th><td colspan="3">{{ form.productName || '—' }}</td></tr>
<tr><th scope="row">箱数</th><td>{{ form.cartons ?? '—' }}</td><th scope="row">换标费用(CNY)</th><td>{{ form.relabelCost ?? '—' }}</td></tr>
<tr><th scope="row">贴标件数</th><td colspan="3">{{ auditLabelPieces }}</td></tr>
<tr><th scope="row">贴标要求</th><td colspan="3">{{ form.labelRequirement || '—' }}</td></tr><tr><th scope="row">货件号</th><td colspan="3">{{ form.shipmentNo || '—' }}</td></tr><tr><th scope="row">新SKU</th><td colspan="3">{{ form.newSkuCode || '—' }}</td></tr>
</tbody></table></section>
<section class="density-dialog-section" data-tone="work" aria-label="换标审核结论"><h3 class="density-dialog-section__title">审核结论</h3><table class="density-table density-table--editable relabel-editor-table"><tbody><tr><td colspan="2"><el-form-item label="审核动作"><el-select v-model="form.status" placeholder="请选择审核动作"><el-option v-for="value in allowedStatusOptions" :key="value" :label="relabelAuditStatusLabel(value)" :value="value" /></el-select></el-form-item></td></tr></tbody></table></section>
+1
View File
@@ -48,6 +48,7 @@ const overseasConsumers = [
const overseasBusinessConsumers = [
'overseas-business/pages/PurchaseOrdersView.vue', 'overseas-business/pages/PurchaseOrderDetailView.vue',
'overseas-business/pages/PurchaseOrderEditView.vue', 'overseas-business/pages/TransportOrdersView.vue',
'overseas-business/pages/TruckOrderDetailView.vue',
'overseas-business/pages/TransportOrderFormView.vue', 'overseas-business/components/ProcurementNodeActions.vue', 'overseas-business/components/ProcurementPhysicalFields.vue', 'overseas-business/components/ProcurementTransportFields.vue', 'overseas-business/components/ProcurementFinanceFields.vue', 'overseas-business/components/ProcurementChargeFields.vue', 'overseas-business/components/ProcurementControlFields.vue',
]
const customerServiceConsumers = [