Merge branch 'release/test' of http://8.163.30.128:10082/html/admin-web into release/test

This commit is contained in:
2026-10-20 23:49:47 +08:00
40 changed files with 735 additions and 197 deletions
@@ -20,7 +20,7 @@ import LineClampText from '@/shared/components/LineClampText.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import { firstLegBusinessTypeLabel } from '@/shared/utils/firstLegBusinessLabels'
import { listPlatformInbounds } from '../core/api'
import type { InboundOrderSummary } from '../core/types'
import type { InboundOrderSummary, InboundPageResult } from '../core/types'
import InboundMeasurementDialog from '../measurement/InboundMeasurementDialog.vue'
import MeasurementReviewPanel from '../review/MeasurementReviewPanel.vue'
import { approveMeasurement, getMeasurementReviewDetail } from '../review/api'
@@ -34,7 +34,11 @@ type FilterModel = QueryModel & {
expectedArrivalRange: string[]
}
const props = defineProps<{ mode: InboundTaskMode; title?: string }>()
const props = defineProps<{
mode: InboundTaskMode
title?: string
taskLoader?: (pagination: { page: number; size: number }) => Promise<InboundPageResult>
}>()
const emit = defineEmits<{ changed: [] }>()
const router = useRouter()
const access = useAccessPreviewStore()
@@ -144,6 +148,7 @@ function dateParam(value: string | undefined, end = false) {
}
async function loadOptions() {
if (props.taskLoader) return
const serial = ++optionsRequestSerial
customerLoadError.value = null
try {
@@ -170,7 +175,7 @@ async function loadTasks() {
loadError.value = null
const applied = appliedFilters.value
try {
const response = await listPlatformInbounds(applied.customerId || undefined, buildInboundTaskQuery(props.mode, {
const response = props.taskLoader ? await props.taskLoader({ page: page.value, size: pageSize.value }) : await listPlatformInbounds(applied.customerId || undefined, buildInboundTaskQuery(props.mode, {
page: page.value,
size: pageSize.value,
warehouseId: applied.warehouseId || undefined,
@@ -329,6 +334,12 @@ function clearSelectedInbound() {
reviewNavigationLoading.value = false
}
function reload() {
if (page.value === 1) return loadTasks()
page.value = 1
}
defineExpose({ reload })
watch([page, pageSize], () => { void loadTasks() })
onMounted(() => { void loadOptions(); void loadTasks() })
onActivated(() => {
@@ -364,6 +375,7 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
>
<template #toolbar-start><slot name="status-tabs"><h1>{{ title || (isReview ? '测量数据审核' : '订单测量') }}</h1></slot></template>
<template #filters>
<slot name="filters" :loading="loading">
<QueryToolbar
v-model="filters"
:ariaLabel="`${taskLabel}待办筛选`"
@@ -373,6 +385,7 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
@search="applyFilters"
@reset="resetFilters"
/>
</slot>
</template>
<template #notices><template v-if="customerLoadError || loadError">
<div class="state-stack">
+2
View File
@@ -20,3 +20,5 @@ export type { AdjustPublishedInboundFeeRequest, InboundFeeSnapshot } from './fee
export { downloadWeightImage } from './weight/api/weightOcr'
export type { InboundMeasurementDetail } from './measurement/types'
export type { InboundHoldCommand, InboundHoldDetail, InboundHoldEvidenceUpload, InboundHoldHistoryEntry } from './core/types'
export type { InboundOrderSummary, InboundPageResult } from './core/types'
@@ -273,7 +273,7 @@ describe('InboundMeasurementPanel', () => {
expect(text).toContain('客户申报')
expect(text).toContain('平台实测')
expect(text).toContain('差异 +1.5 kg')
expect(text).toContain('差异 -0.2 m³')
expect(text).toContain('差异 -0.200 m³')
expect(text).toContain('平台实测箱规')
expect(text).not.toContain('租户')
expect(text).not.toContain('数据版本')
@@ -297,7 +297,7 @@ describe('InboundMeasurementPanel', () => {
const wrapper = await mountPanel()
const rows = wrapper.findAll('.measurement-comparison tbody tr')
expect(rows.map(row => row.findAll('td').map(cell => cell.text()))).toEqual([
['10', '10', '一致'], ['118.5', '120', '差异 +1.5 kg'], ['0.8', '0.6', '差异 -0.2 m³'],
['10', '10', '一致'], ['118.5', '120', '差异 +1.5 kg'], ['0.800', '0.600', '差异 -0.200 m³'],
])
expect(wrapper.find('.measurement-summary-section .measurement-declaration').exists()).toBe(false)
expect(wrapper.find('.measurement-declaration').element.parentElement).toBe(wrapper.find('.measurement-summary-section').element.parentElement)
@@ -319,8 +319,8 @@ describe('InboundMeasurementPanel', () => {
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ measurementId: null, items: [], weightResults: [] }))
const wrapper = await mountPanel()
expect(wrapper.find('.measurement-summary-header h4').text()).toBe('申报与实测对比')
expect(wrapper.find('.measurement-declaration-header').text()).toContain('DOC-001')
expect(wrapper.find('.measurement-section__heading .measurement-goods-name input').exists()).toBe(true)
expect(wrapper.find('.measurement-declaration-header').text()).not.toContain('DOC-001')
expect(wrapper.find('.measurement-section__heading .measurement-goods-name').exists()).toBe(false)
expect(wrapper.find('.measurement-section__heading .measurement-section-actions').text()).toContain('手动录入箱规')
expect(wrapper.findAll('.el-alert').some(alert => alert.text().includes('请先完成箱规测量'))).toBe(false)
expect(wrapper.find('.measurement-panel__footer').text()).toContain('请先完成箱规测量')
@@ -334,11 +334,11 @@ describe('InboundMeasurementPanel', () => {
expect(comparison.text()).toContain('118.5')
expect(comparison.text()).toContain('0.8')
expect(comparison.findAll('tbody tr')[0].text()).toContain('10')
expect(declaration.text()).toContain('原单据号码')
expect(declaration.text()).toContain('DOC-001')
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(1)
expect(declaration.text()).not.toContain('原单据号码')
expect(declaration.text()).not.toContain('DOC-001')
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(0)
expect(declaration.findAll('input')).toHaveLength(0)
expect(wrapper.find('.measurement-items-section .measurement-goods-name input').exists()).toBe(true)
expect(wrapper.find('.measurement-items-section .measurement-goods-name').exists()).toBe(false)
expect(declaration.find('[aria-label="客户申报箱规明细"]').text()).toContain('蓝牙音箱')
expect(declaration.find('[aria-label="客户申报箱规明细"]').text()).toContain('50 × 40 × 30')
})
@@ -356,13 +356,14 @@ describe('InboundMeasurementPanel', () => {
expect(declaration.find('input').exists()).toBe(false)
expect(declaration.text()).toContain('蓝牙音箱')
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(1)
expect(wrapper.find('.measurement-items-section .measurement-goods-name').text()).toContain('蓝牙音箱')
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(0)
expect(wrapper.find('.measurement-items-section .measurement-goods-name').exists()).toBe(false)
})
it('saves the corrected actual goods name without overriding declaration facts', async () => {
it('preserves the hidden actual goods name without overriding declaration facts', async () => {
const wrapper = await mountPanel()
await wrapper.find('.measurement-goods-name input').setValue('修正后的蓝牙音箱')
expect(wrapper.text()).not.toContain('实际品名')
await wrapper.find('input[aria-label="仓库修改外箱长"]').setValue('55')
const save = wrapper.findAll('button').find((button) => button.text().includes('保存测量数据'))
await save?.trigger('click')
await flushPromises()
@@ -370,7 +371,7 @@ describe('InboundMeasurementPanel', () => {
expect(saveInboundMeasurementDraft).toHaveBeenCalledWith('10001', '7001', expect.objectContaining({
measurementId: '9001',
version: 2,
actualGoodsName: '修正后的蓝牙音箱',
actualGoodsName: '蓝牙音箱',
items: expect.any(Array),
}))
const payload = vi.mocked(saveInboundMeasurementDraft).mock.calls[0]?.[2]
@@ -573,7 +574,7 @@ describe('InboundMeasurementPanel', () => {
const wrapper = await mountPanel()
expect(wrapper.find('.measurement-declaration input').exists()).toBe(false)
expect(wrapper.find('.measurement-items-section .measurement-expected-pallets input').exists()).toBe(true)
expect(wrapper.find('.measurement-items-section .measurement-goods-name input').exists()).toBe(true)
expect(wrapper.find('.measurement-items-section .measurement-goods-name').exists()).toBe(false)
})
it('requires at least one confirmed weight image before submitting for review', async () => {
@@ -124,7 +124,7 @@ const confirmedPackageCount = computed(() => form.items.reduce((sum, item) => su
const comparisonMetrics = computed(() => [
{ label: '箱数', declared: detail.value?.originalPackageCount, actual: form.items.length ? confirmedPackageCount.value : null, digits: 0, unit: '' },
{ label: '毛重(kg)', declared: detail.value?.originalGrossWeightKg, actual: detail.value?.actualGrossWeightKg, digits: 3, unit: ' kg' },
{ label: '总体积(m³)', declared: detail.value?.originalVolumeCbm, actual: detail.value?.actualVolumeCbm, digits: 4, unit: ' m³' },
{ label: '总体积(m³)', declared: detail.value?.originalVolumeCbm, actual: detail.value?.actualVolumeCbm, digits: 3, unit: ' m³' },
])
const expectedPackageCount = computed(() => detail.value?.originalPackageCount ?? null)
@@ -557,9 +557,9 @@ async function submitForReview() {
}
}
function formatNumber(value: number | null | undefined, maximumFractionDigits = 3) {
function formatNumber(value: number | null | undefined, maximumFractionDigits = 3, minimumFractionDigits = 0) {
if (value == null || !Number.isFinite(value)) return '—'
return new Intl.NumberFormat('zh-CN', { maximumFractionDigits }).format(value)
return new Intl.NumberFormat('zh-CN', { maximumFractionDigits, minimumFractionDigits }).format(value)
}
function deviationRatio(actual: number | null | undefined, declared: number | null | undefined) {
@@ -576,7 +576,7 @@ function formatDifference(actual: number | null | undefined, declared: number |
const difference = actual - declared
if (difference === 0) return null
const sign = difference > 0 ? '+' : ''
return `差异 ${sign}${formatNumber(difference, maximumFractionDigits)}${unit}`
return `差异 ${sign}${formatNumber(difference, maximumFractionDigits, unit === ' m³' ? 3 : 0)}${unit}`
}
function releaseImagePreview() {
@@ -728,8 +728,8 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<tbody>
<tr v-for="metric in comparisonMetrics" :key="metric.label">
<th scope="row">{{ metric.label }}</th>
<td>{{ formatNumber(metric.declared, metric.digits) }}</td>
<td>{{ metric.actual == null ? '未测量' : formatNumber(metric.actual, metric.digits) }}</td>
<td>{{ formatNumber(metric.declared, metric.digits, metric.unit === ' m³' ? 3 : 0) }}</td>
<td>{{ metric.actual == null ? '未测量' : formatNumber(metric.actual, metric.digits, metric.unit === ' m³' ? 3 : 0) }}</td>
<td :class="{ 'measurement-comparison__difference': metric.actual != null && metric.declared != null && metric.actual !== metric.declared }">
{{ metric.actual == null ? '待测量' : metric.declared == null ? '—' : formatDifference(metric.actual, metric.declared, metric.digits, metric.unit) || '一致' }}
</td>
@@ -746,7 +746,6 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<section class="measurement-declaration" aria-labelledby="measurement-declaration-title">
<header class="measurement-declaration-header">
<h4 id="measurement-declaration-title">客户申报明细</h4>
<div class="measurement-readonly-field measurement-document-number"><span>原单据号码</span><strong>{{ detail.originalDocumentNo || '—' }}</strong></div>
</header>
<el-table
v-if="detail.latestDeclaration?.items.length"
@@ -770,9 +769,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<div class="measurement-section__heading">
<div><h3>平台实测箱规</h3></div>
<div class="measurement-data-grid measurement-platform-fields">
<label v-if="mayMaintain" class="measurement-goods-name"><span>实际品名</span><el-input v-model="form.actualGoodsName" placeholder="请输入实际品名" maxlength="255" /></label>
<div v-else class="measurement-readonly-field measurement-goods-name"><span>实际品名</span><strong>{{ detail.actualGoodsName || '—' }}</strong></div>
<div v-if="isDropship" class="measurement-data-grid measurement-platform-fields">
<label v-if="isDropship && mayMaintain" class="measurement-expected-pallets"><span>预计托数</span><el-input-number v-model="form.expectedPalletCount" placeholder="请输入预计托数" :min="1" :max="1000" :step="1" step-strictly controls-position="right" /></label>
<div v-else-if="isDropship" class="measurement-readonly-field measurement-expected-pallets"><span>预计托数</span><strong>{{ detail.expectedPalletCount ?? '—' }}</strong></div>
</div>
@@ -800,7 +797,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<div><dt>测量时间</dt><dd>{{ row.measuredAt ? row.measuredAt.replace('T', ' ') : '—' }}</dd></div>
<div><dt>计算方式</dt><dd>{{ calculationModeLabel(row.coefficientMode) }}</dd></div>
<div><dt>计算后外箱</dt><dd>{{ formatNumber(row.calculatedOuterLengthCm) }} × {{ formatNumber(row.calculatedOuterWidthCm) }} × {{ formatNumber(row.calculatedOuterHeightCm) }} cm</dd></div>
<div><dt>单箱体积</dt><dd>{{ formatNumber(row.serverValues.unitVolumeCbm, 4) }} m³</dd></div>
<div><dt>单箱体积</dt><dd>{{ formatNumber(row.serverValues.unitVolumeCbm, 3, 3) }} m³</dd></div>
<div><dt>箱规周长</dt><dd>{{ formatNumber(row.serverValues.girthCm) }} cm</dd></div>
<div><dt>总材积重</dt><dd>{{ formatNumber(row.serverValues.volumeWeightKg) }} kg</dd></div>
<div><dt>计算规则版本</dt><dd>{{ row.calculationRuleVersion || '—' }}</dd></div>
@@ -848,7 +845,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</template>
</el-table-column>
<el-table-column label="总体积(m³)" width="108" align="right">
<template #default="{ row }">{{ formatNumber(row.serverValues.totalVolumeCbm, 4) }}</template>
<template #default="{ row }">{{ formatNumber(row.serverValues.totalVolumeCbm, 3, 3) }}</template>
</el-table-column>
<el-table-column label="图片" width="82" align="center">
<template #default="{ row }">