优化
This commit is contained in:
@@ -33,6 +33,7 @@ describe('adminNavigation', () => {
|
||||
'订单测量',
|
||||
'测量数据审核',
|
||||
'库位管理',
|
||||
'托盘管理',
|
||||
'整柜业务',
|
||||
])
|
||||
expect(adminNavigation.find((item) => item.key === 'customer-service')?.children?.map((item) => item.title)).toEqual(['异常订单', '客户回访'])
|
||||
|
||||
@@ -68,6 +68,7 @@ export const adminNavigation: AdminNavigationNode[] = [
|
||||
{ key: 'inbound-dedup', title: '订单测量', icon: ClipboardList, path: '/inbounds/measurements', permission: 'platform:firstleg:inbound:measure:view' },
|
||||
{ key: 'inbound-measure', title: '测量数据审核', icon: FileCheck2, path: '/inbounds/measurement-reviews', permission: 'firstleg:inbound:measurement-review' },
|
||||
{ key: 'locations', title: '库位管理', icon: WarehouseIcon, path: '/master-data/locations', permission: 'platform:location:list' },
|
||||
{ key: 'pallets', title: '托盘管理', icon: WarehouseIcon, path: '/master-data/pallets', permission: 'platform:pallet:list' },
|
||||
{ key: 'domestic-containers', title: '整柜业务', icon: WarehouseIcon, path: '/domestic-containers', permission: 'platform:domestic-container:list' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -93,6 +93,18 @@ describe('inbound weight OCR API', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('passes an abort signal without changing the frozen pallet endpoint parameters', async () => {
|
||||
vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: [] })
|
||||
const controller = new AbortController()
|
||||
|
||||
await getWeightPalletOptions('10001', '20001', controller.signal)
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/platform/v1/first-leg/inbounds/20001/weight-pallet-options',
|
||||
{ params: { tenantId: '10001' }, signal: controller.signal },
|
||||
)
|
||||
})
|
||||
|
||||
it('registers, corrects, and confirms a weight result using the implemented contracts', async () => {
|
||||
vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '1' } })
|
||||
vi.mocked(http.put).mockResolvedValue({ code: '200', message: 'success', data: { id: '1', version: 1 } })
|
||||
|
||||
@@ -60,10 +60,10 @@ export async function getWeightWorkspace(tenantId: string, inboundId: string) {
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function getWeightPalletOptions(tenantId: string, inboundId: string) {
|
||||
export async function getWeightPalletOptions(tenantId: string, inboundId: string, signal?: AbortSignal) {
|
||||
const response = await http.get<ApiEnvelope<WeightPalletOption[]>, ApiEnvelope<WeightPalletOption[]>>(
|
||||
`${measurementBasePath}/${segment(inboundId)}/weight-pallet-options`,
|
||||
{ params: { tenantId } },
|
||||
{ params: { tenantId }, ...(signal ? { signal } : {}) },
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
@@ -352,6 +352,64 @@ describe('WeightOcrPanel confirmation behavior', () => {
|
||||
expect(getWeightPalletOptions).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps historical pallet snapshots visible without requesting candidates for a read-only operator', async () => {
|
||||
const wrapper = mountPanel(['platform:firstleg:inbound:measure:view'])
|
||||
await flushPromises()
|
||||
|
||||
expect(getWeightWorkspace).toHaveBeenCalledOnce()
|
||||
expect(getWeightPalletOptions).not.toHaveBeenCalled()
|
||||
expect(wrapper.find('.pallet-picker .el-select').exists()).toBe(false)
|
||||
expect(wrapper.text()).toContain('木托盘')
|
||||
expect(wrapper.text()).toContain('12.000 KG')
|
||||
})
|
||||
|
||||
it('does not request pallet candidates when the inbound status is not editable', async () => {
|
||||
vi.mocked(getWeightWorkspace).mockResolvedValue({ ...workspace, processStatus: 'MEASURE_PENDING' })
|
||||
|
||||
mountPanel()
|
||||
await flushPromises()
|
||||
|
||||
expect(getWeightPalletOptions).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('virtualizes one thousand pallet candidates instead of rendering one thousand option nodes', async () => {
|
||||
vi.mocked(getWeightPalletOptions).mockResolvedValue(Array.from({ length: 1_000 }, (_, index) => ({
|
||||
palletId: String(60_000 + index),
|
||||
palletCode: `TP-${index}`,
|
||||
palletType: `托盘类型 ${index}`,
|
||||
capacityCbm: 1.2,
|
||||
tareWeightKg: 12,
|
||||
})))
|
||||
const wrapper = mountPanel()
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.find('.pallet-picker .el-select').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
const renderedOptions = document.body.querySelectorAll('.el-select-dropdown__item')
|
||||
expect(renderedOptions.length).toBeGreaterThan(0)
|
||||
expect(renderedOptions.length).toBeLessThan(100)
|
||||
})
|
||||
|
||||
it('aborts an in-flight pallet request and ignores its late result when the panel closes', async () => {
|
||||
let resolveOptions: ((value: Awaited<ReturnType<typeof getWeightPalletOptions>>) => void) | undefined
|
||||
vi.mocked(getWeightPalletOptions).mockImplementationOnce(() => new Promise((resolve) => {
|
||||
resolveOptions = resolve
|
||||
}))
|
||||
const wrapper = mountPanel()
|
||||
await flushPromises()
|
||||
const signal = vi.mocked(getWeightPalletOptions).mock.calls[0]?.[2]
|
||||
|
||||
wrapper.unmount()
|
||||
resolveOptions?.([{
|
||||
palletId: 'late', palletCode: 'TP-LATE', palletType: '过期托盘', capacityCbm: 1, tareWeightKg: 10,
|
||||
}])
|
||||
await flushPromises()
|
||||
|
||||
expect(signal?.aborted).toBe(true)
|
||||
expect(document.body.textContent).not.toContain('过期托盘')
|
||||
})
|
||||
|
||||
it('keeps a low-confidence OCR result focused on the editable weight', async () => {
|
||||
vi.mocked(getWeightWorkspace).mockResolvedValue({
|
||||
...workspace,
|
||||
|
||||
@@ -55,8 +55,10 @@ describe('WeightOcrPanel contract', () => {
|
||||
expect(source).toContain("option.palletType || '未记录类型'")
|
||||
})
|
||||
|
||||
it('keeps pallet type, tare weight, and capacity in one select line', () => {
|
||||
expect(source).toContain(':label="palletOptionLabel(option)"')
|
||||
it('keeps pallet type, tare weight, and capacity in one virtualized select line', () => {
|
||||
expect(source).toContain('<el-select-v2')
|
||||
expect(source).toContain(':options="virtualPalletOptions"')
|
||||
expect(source).toContain('label: palletOptionLabel(option)')
|
||||
expect(source).toContain("`${option.palletType || '未记录类型'} · 皮重 ${formatWeight(option.tareWeightKg)} · 容量 ${option.capacityCbm} m³`")
|
||||
expect(source).not.toContain('<small v-if="selectedPallet">')
|
||||
expect(source).toContain('width: clamp(360px, 36vw, 520px)')
|
||||
@@ -67,6 +69,9 @@ describe('WeightOcrPanel contract', () => {
|
||||
expect(source).toContain("platform:firstleg:inbound:weight:edit")
|
||||
expect(source).toContain("platform:firstleg:inbound:weight:confirm")
|
||||
expect(source).toContain("new Set(['WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'])")
|
||||
expect(source).toContain('&& canEdit.value')
|
||||
expect(source).toContain('&& editable.value')
|
||||
expect(source).toContain('cancelPalletOptionsLoad()')
|
||||
expect(source).toContain("workspace.value?.processStatus === 'MEASURE_PENDING'")
|
||||
expect(source).toContain('v-if="canEdit && !reviewMode"')
|
||||
expect(source).toContain('v-if="canEdit && editable && !reviewMode"')
|
||||
|
||||
@@ -59,6 +59,8 @@ const previewResultId = ref('')
|
||||
const previewSnapshot = ref<WeightResultResource | null>(null)
|
||||
const weightDrafts = ref<Record<string, { value: number | null; unit: 'KG' | 'LB'; reason: string }>>({})
|
||||
let loadSequence = 0
|
||||
let palletLoadSequence = 0
|
||||
let palletAbortController: AbortController | null = null
|
||||
|
||||
const editableStatuses = new Set(['WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'])
|
||||
const minimumWeight = { KG: 0.001, LB: 0.002 } as const
|
||||
@@ -68,6 +70,18 @@ const canEdit = computed(() => access.can('platform:firstleg:inbound:weight:edit
|
||||
const canConfirm = computed(() => access.can('platform:firstleg:inbound:weight:confirm'))
|
||||
const canView = computed(() => access.can('platform:firstleg:inbound:measure:view'))
|
||||
const editable = computed(() => Boolean(workspace.value && editableStatuses.has(workspace.value.processStatus)))
|
||||
const canLoadPalletOptions = computed(() => Boolean(
|
||||
!props.reviewMode
|
||||
&& customerId.value
|
||||
&& props.inboundId
|
||||
&& canView.value
|
||||
&& canEdit.value
|
||||
&& editable.value,
|
||||
))
|
||||
const virtualPalletOptions = computed(() => palletOptions.value.map((option) => ({
|
||||
label: palletOptionLabel(option),
|
||||
value: option.palletId,
|
||||
})))
|
||||
const reviewCorrectionEnabled = computed(() => Boolean(
|
||||
props.reviewMode
|
||||
&& props.correctionMode
|
||||
@@ -212,32 +226,44 @@ async function loadWorkspace() {
|
||||
}
|
||||
|
||||
async function loadPalletOptions() {
|
||||
if (props.reviewMode) {
|
||||
if (!canLoadPalletOptions.value) {
|
||||
cancelPalletOptionsLoad()
|
||||
palletOptions.value = []
|
||||
selectedPalletId.value = ''
|
||||
palletError.value = null
|
||||
return
|
||||
}
|
||||
if (!customerId.value || !props.inboundId || !canView.value) {
|
||||
palletOptions.value = []
|
||||
selectedPalletId.value = ''
|
||||
return
|
||||
}
|
||||
cancelPalletOptionsLoad()
|
||||
const sequence = ++palletLoadSequence
|
||||
const controller = new AbortController()
|
||||
palletAbortController = controller
|
||||
palletLoading.value = true
|
||||
palletError.value = null
|
||||
try {
|
||||
const result = await getWeightPalletOptions(customerId.value, props.inboundId)
|
||||
const result = await getWeightPalletOptions(customerId.value, props.inboundId, controller.signal)
|
||||
if (sequence !== palletLoadSequence || controller.signal.aborted) return
|
||||
palletOptions.value = result
|
||||
if (!result.some((option) => option.palletId === selectedPalletId.value)) selectedPalletId.value = ''
|
||||
} catch (error) {
|
||||
if (sequence !== palletLoadSequence || controller.signal.aborted) return
|
||||
palletOptions.value = []
|
||||
selectedPalletId.value = ''
|
||||
palletError.value = getApiErrorDetails(error, '可选托盘加载失败,请稍后重试')
|
||||
} finally {
|
||||
palletLoading.value = false
|
||||
if (sequence === palletLoadSequence) {
|
||||
palletLoading.value = false
|
||||
if (palletAbortController === controller) palletAbortController = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cancelPalletOptionsLoad() {
|
||||
++palletLoadSequence
|
||||
palletAbortController?.abort()
|
||||
palletAbortController = null
|
||||
palletLoading.value = false
|
||||
}
|
||||
|
||||
function chooseFile() {
|
||||
if (!editable.value) {
|
||||
ElMessage.warning('当前入库状态不允许上传称重图片')
|
||||
@@ -397,17 +423,32 @@ watch(
|
||||
previewOpen.value = false
|
||||
releasePreview()
|
||||
void loadWorkspace()
|
||||
void loadPalletOptions()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => [customerId.value, props.inboundId, canLoadPalletOptions.value] as const,
|
||||
() => {
|
||||
cancelPalletOptionsLoad()
|
||||
palletOptions.value = []
|
||||
selectedPalletId.value = ''
|
||||
palletError.value = null
|
||||
if (canLoadPalletOptions.value) void loadPalletOptions()
|
||||
},
|
||||
)
|
||||
|
||||
watch(previewOpen, (open) => {
|
||||
if (!open) {
|
||||
releasePreview()
|
||||
}
|
||||
})
|
||||
onBeforeUnmount(releasePreview)
|
||||
onBeforeUnmount(() => {
|
||||
cancelPalletOptionsLoad()
|
||||
palletOptions.value = []
|
||||
selectedPalletId.value = ''
|
||||
releasePreview()
|
||||
})
|
||||
|
||||
defineExpose({ reload: loadWorkspace })
|
||||
</script>
|
||||
@@ -424,22 +465,16 @@ defineExpose({ reload: loadWorkspace })
|
||||
<div class="weight-summary"><span>货物重量</span><strong>{{ formatWeight(workspace.actualNetWeightKg) }}</strong></div>
|
||||
</div>
|
||||
<div class="pallet-picker">
|
||||
<el-select
|
||||
v-if="!reviewMode"
|
||||
<el-select-v2
|
||||
v-if="canEdit && !reviewMode"
|
||||
v-model="selectedPalletId"
|
||||
:options="virtualPalletOptions"
|
||||
aria-label="选择称重托盘"
|
||||
placeholder="选择称重托盘"
|
||||
filterable
|
||||
:loading="palletLoading"
|
||||
:disabled="loading || uploading || !editable || !canEdit || Boolean(palletError)"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in palletOptions"
|
||||
:key="option.palletId"
|
||||
:label="palletOptionLabel(option)"
|
||||
:value="option.palletId"
|
||||
/>
|
||||
</el-select>
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-if="!reviewMode"
|
||||
|
||||
Reference in New Issue
Block a user