fix(admin): align loading history and pallet lookup

This commit is contained in:
2026-09-01 11:10:22 +08:00
parent 4711bcc4e6
commit e3ecba7133
8 changed files with 136 additions and 20 deletions
@@ -93,26 +93,26 @@ describe('inbound weight OCR API', () => {
data: { records, page: '1', size: '20', total: '2', pages: '1' },
})
await expect(getWeightPalletOptions('10001', '20001')).resolves.toEqual({
await expect(getWeightPalletOptions('10001', '20001', { page: 1, size: 50 })).resolves.toEqual({
records, page: 1, size: 20, total: 2, pages: 1,
})
expect(http.get).toHaveBeenCalledWith(
'/platform/v1/first-leg/inbounds/20001/weight-pallet-options',
{ params: { tenantId: '10001' } },
{ params: { tenantId: '10001', page: 1, size: 50, keyword: undefined } },
)
})
it('passes an abort signal without changing the frozen pallet endpoint parameters', async () => {
it('passes remote page, keyword and abort signal through the frozen pallet endpoint', async () => {
vi.mocked(http.get).mockResolvedValue({
code: '200', message: 'success', data: { records: [], page: '1', size: '20', total: '0', pages: '0' },
})
const controller = new AbortController()
await getWeightPalletOptions('10001', '20001', controller.signal)
await getWeightPalletOptions('10001', '20001', { page: 2, size: 50, keyword: ' 木托盘 ', signal: controller.signal })
expect(http.get).toHaveBeenCalledWith(
'/platform/v1/first-leg/inbounds/20001/weight-pallet-options',
{ params: { tenantId: '10001' }, signal: controller.signal },
{ params: { tenantId: '10001', page: 2, size: 50, keyword: '木托盘' }, signal: controller.signal },
)
})
+11 -2
View File
@@ -74,10 +74,19 @@ export async function getWeightWorkspace(tenantId: string, inboundId: string) {
return unwrapApiResponse(response)
}
export async function getWeightPalletOptions(tenantId: string, inboundId: string, signal?: AbortSignal) {
export async function getWeightPalletOptions(tenantId: string, inboundId: string, query: {
page?: number
size?: number
keyword?: string
signal?: AbortSignal
} = {}) {
const keyword = query.keyword?.trim()
const response = await http.get<ApiEnvelope<WeightPalletOptionPageResponse>, ApiEnvelope<WeightPalletOptionPageResponse>>(
`${measurementBasePath}/${segment(inboundId)}/weight-pallet-options`,
{ params: { tenantId }, ...(signal ? { signal } : {}) },
{
params: { tenantId, page: query.page ?? 1, size: query.size ?? 50, keyword: keyword || undefined },
...(query.signal ? { signal: query.signal } : {}),
},
)
const page = unwrapApiResponse(response)
return {
@@ -1,5 +1,6 @@
import ElementPlus from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { AxiosError } from 'axios'
import { createPinia, setActivePinia } from 'pinia'
import { DOMWrapper, enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -221,7 +222,7 @@ describe('WeightOcrPanel confirmation behavior', () => {
await flushPromises()
await openWeightDialog(wrapper)
const dialogText = document.body.textContent || ''
const dialogText = document.body.querySelector('.el-dialog')?.textContent || ''
expect(dialogText).toContain('当前托盘皮重')
expect(dialogText).toContain('12.000 KG')
expect(dialogText).toContain('OCR 识别毛重')
@@ -360,6 +361,24 @@ describe('WeightOcrPanel confirmation behavior', () => {
expect(wrapper.text()).not.toContain('可选托盘加载失败')
})
it('remote search keyword uses page 1 and size 50', async () => {
const wrapper = mountPanel()
await flushPromises()
vi.useFakeTimers()
try {
await wrapper.find('.pallet-picker .el-select').trigger('click')
await wrapper.find('.pallet-picker input').setValue(' 木托盘 ')
await vi.advanceTimersByTimeAsync(350)
await flushPromises()
expect(getWeightPalletOptions).toHaveBeenLastCalledWith('10001', '20001', expect.objectContaining({
page: 1, size: 50, keyword: '木托盘', signal: expect.any(AbortSignal),
}))
} finally {
vi.useRealTimers()
}
})
it('shows pallet loading failures without hiding existing weight results', async () => {
vi.mocked(getWeightPalletOptions).mockRejectedValueOnce(new ApiResponseError('托盘服务暂不可用', 'A0500', 503))
const wrapper = mountPanel()
@@ -369,6 +388,17 @@ describe('WeightOcrPanel confirmation behavior', () => {
expect(wrapper.findAll('.weight-card')).toHaveLength(1)
})
it('shows a network error as a pallet candidate load failure without hiding historical snapshots', async () => {
vi.mocked(getWeightPalletOptions).mockRejectedValueOnce(new AxiosError('Network Error', 'ERR_NETWORK'))
const wrapper = mountPanel()
await flushPromises()
expect(wrapper.text()).toContain('可选托盘加载失败')
expect(wrapper.findAll('.weight-card')).toHaveLength(1)
expect(wrapper.text()).toContain('木托盘')
expect(wrapper.text()).toContain('12.000 KG')
})
it('does not load weight or pallet data without measurement view permission', async () => {
const wrapper = mountPanel([])
await flushPromises()
@@ -378,13 +408,14 @@ describe('WeightOcrPanel confirmation behavior', () => {
expect(getWeightPalletOptions).not.toHaveBeenCalled()
})
it('keeps historical pallet snapshots visible without requesting candidates for a read-only operator', async () => {
it('keeps historical palletCode snapshots visible 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('TP-001')
expect(wrapper.text()).toContain('木托盘')
expect(wrapper.text()).toContain('12.000 KG')
})
@@ -424,7 +455,7 @@ describe('WeightOcrPanel confirmation behavior', () => {
}))
const wrapper = mountPanel()
await flushPromises()
const signal = vi.mocked(getWeightPalletOptions).mock.calls[0]?.[2]
const signal = vi.mocked(getWeightPalletOptions).mock.calls[0]?.[2]?.signal
wrapper.unmount()
resolveOptions?.(palletPage([{
@@ -45,6 +45,7 @@ const fileInput = ref<HTMLInputElement>()
const workspace = ref<WeightWorkspaceResource | null>(null)
const palletOptions = ref<WeightPalletOption[]>([])
const selectedPalletId = ref('')
const palletKeyword = ref('')
const palletLoading = ref(false)
const palletError = ref<ApiErrorDetails | null>(null)
const loading = ref(false)
@@ -61,6 +62,7 @@ const weightDrafts = ref<Record<string, { value: number | null; unit: 'KG' | 'LB
let loadSequence = 0
let palletLoadSequence = 0
let palletAbortController: AbortController | null = null
const PALLET_OPTION_PAGE_SIZE = 50
const editableStatuses = new Set(['WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'])
const minimumWeight = { KG: 0.001, LB: 0.002 } as const
@@ -225,7 +227,7 @@ async function loadWorkspace() {
}
}
async function loadPalletOptions() {
async function loadPalletOptions(keyword = palletKeyword.value) {
if (!canLoadPalletOptions.value) {
cancelPalletOptionsLoad()
palletOptions.value = []
@@ -237,10 +239,16 @@ async function loadPalletOptions() {
const sequence = ++palletLoadSequence
const controller = new AbortController()
palletAbortController = controller
palletKeyword.value = keyword.trim()
palletLoading.value = true
palletError.value = null
try {
const result = await getWeightPalletOptions(customerId.value, props.inboundId, controller.signal)
const result = await getWeightPalletOptions(customerId.value, props.inboundId, {
page: 1,
size: PALLET_OPTION_PAGE_SIZE,
keyword: palletKeyword.value || undefined,
signal: controller.signal,
})
if (sequence !== palletLoadSequence || controller.signal.aborted) return
palletOptions.value = result.records
if (!result.records.some((option) => option.palletId === selectedPalletId.value)) selectedPalletId.value = ''
@@ -257,6 +265,14 @@ async function loadPalletOptions() {
}
}
function searchPalletOptions(keyword: string) {
void loadPalletOptions(keyword)
}
function reloadPalletOptions() {
void loadPalletOptions()
}
function cancelPalletOptionsLoad() {
++palletLoadSequence
palletAbortController?.abort()
@@ -417,6 +433,7 @@ watch(
workspace.value = null
palletOptions.value = []
selectedPalletId.value = ''
palletKeyword.value = ''
palletError.value = null
weightDrafts.value = {}
loadError.value = null
@@ -433,6 +450,7 @@ watch(
cancelPalletOptionsLoad()
palletOptions.value = []
selectedPalletId.value = ''
palletKeyword.value = ''
palletError.value = null
if (canLoadPalletOptions.value) void loadPalletOptions()
},
@@ -472,6 +490,8 @@ defineExpose({ reload: loadWorkspace })
aria-label="选择称重托盘"
placeholder="选择称重托盘"
filterable
remote
:remote-method="searchPalletOptions"
:loading="palletLoading"
:disabled="loading || uploading || !editable || !canEdit || Boolean(palletError)"
/>
@@ -519,7 +539,7 @@ defineExpose({ reload: loadWorkspace })
:loading="palletLoading"
compact
class="weight-action-error"
@retry="loadPalletOptions"
@retry="reloadPalletOptions"
>
<template #details><ApiErrorMeta :details="palletError" /></template>
</LoadErrorState>
@@ -531,7 +551,7 @@ defineExpose({ reload: loadWorkspace })
class="weight-action-error"
title="当前仓库暂无可选托盘,暂时不能上传称重图片。"
>
<el-button size="small" @click="loadPalletOptions"><RefreshCw :size="14" />重新加载</el-button>
<el-button size="small" @click="reloadPalletOptions"><RefreshCw :size="14" />重新加载</el-button>
</el-alert>
<div v-loading="loading" class="weight-list" aria-live="polite">
<div v-if="!loading && rows.length === 0" class="weight-empty">
@@ -566,6 +586,7 @@ defineExpose({ reload: loadWorkspace })
@click="openPreview(row)"
><strong>{{ cardWeight(row) }}</strong></button>
<dl class="weight-card__facts">
<div><dt>托盘编码</dt><dd :title="row.palletCode">{{ row.palletCode || '未记录编码' }}</dd></div>
<div><dt>托盘类型</dt><dd>{{ row.palletType || '未记录类型' }}</dd></div>
<div><dt>服务端确认毛重</dt><dd>{{ formatWeight(row.grossWeightKg) }}</dd></div>
<div><dt>托盘皮重</dt><dd>{{ formatWeight(row.palletTareWeightKg) }}</dd></div>