优化
This commit is contained in:
@@ -18,7 +18,15 @@ describe('InboundMeasurementDialog', () => {
|
||||
expect(source).not.toContain('class="surface-panel measurement-dialog-weight"')
|
||||
expect(source).toContain('border-bottom: 1px solid var(--cc-border)')
|
||||
expect(source).toContain('@summary-changed="handleWeightSummaryChanged"')
|
||||
expect(source).toContain('measurementPanelRef.value?.updateActualGrossWeight(actualGrossWeightKg)')
|
||||
expect(source).toContain('measurementPanelRef.value?.updateActualWeights(actualGrossWeightKg, actualNetWeightKg)')
|
||||
})
|
||||
|
||||
it('shows the inbound code once in the dialog title and not as a second body heading', () => {
|
||||
expect(source).toContain('`测量 · ${props.inboundCode?.trim() || props.inboundId}`')
|
||||
expect(source).toContain(':title="title"')
|
||||
expect(panelSource).not.toContain('<span>入仓码</span>')
|
||||
expect(panelSource).not.toContain('<strong>{{ detail.inboundCode')
|
||||
expect(panelSource).toContain('class="measurement-summary-context"')
|
||||
})
|
||||
|
||||
it('is the only measurement dialog opened by both formal list entries', () => {
|
||||
|
||||
@@ -43,8 +43,8 @@ async function beforeClose(done: () => void) {
|
||||
if (await mayClose()) done()
|
||||
}
|
||||
|
||||
function handleWeightSummaryChanged(actualGrossWeightKg: number | null) {
|
||||
measurementPanelRef.value?.updateActualGrossWeight(actualGrossWeightKg)
|
||||
function handleWeightSummaryChanged(actualGrossWeightKg: number | null, actualNetWeightKg: number | null) {
|
||||
measurementPanelRef.value?.updateActualWeights(actualGrossWeightKg, actualNetWeightKg)
|
||||
}
|
||||
|
||||
function handleUpdated() {
|
||||
|
||||
@@ -66,6 +66,7 @@ function measurement(overrides: Partial<InboundMeasurementDetail> = {}): Inbound
|
||||
originalPackageCount: 10,
|
||||
actualPackageCount: 10,
|
||||
actualGrossWeightKg: 120,
|
||||
actualNetWeightKg: 110,
|
||||
actualVolumeCbm: 0.6,
|
||||
volumeWeightKg: 100.2,
|
||||
actualGoodsName: '蓝牙音箱',
|
||||
@@ -216,7 +217,53 @@ describe('InboundMeasurementPanel', () => {
|
||||
expect(source).toContain('.measurement-data-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));')
|
||||
expect(source).toContain('.measurement-table { width: 100%; font-size: 12px; }')
|
||||
expect(source).toContain('.measurement-data-grid :deep(.el-input__wrapper)')
|
||||
expect(source).toContain('defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain, maySubmit, saving, submitting, canEdit, canSubmit, isDirty })')
|
||||
expect(source).toContain('defineExpose({ saveDraft, submitForReview, updateActualWeights, mayMaintain, maySubmit, saving, submitting, canEdit, canSubmit, isDirty })')
|
||||
})
|
||||
|
||||
it('renders customer declaration facts as plain read-only values', async () => {
|
||||
const wrapper = await mountPanel()
|
||||
const declaration = wrapper.find('.measurement-declaration')
|
||||
|
||||
expect(declaration.text()).toContain('申报箱数')
|
||||
expect(declaration.text()).toContain('10')
|
||||
expect(declaration.text()).toContain('申报毛重(kg)')
|
||||
expect(declaration.text()).toContain('118.5')
|
||||
expect(declaration.text()).toContain('申报体积(m³)')
|
||||
expect(declaration.text()).toContain('0.8')
|
||||
expect(declaration.text()).toContain('原单据号码')
|
||||
expect(declaration.text()).toContain('DOC-001')
|
||||
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(4)
|
||||
expect(declaration.findAll('input')).toHaveLength(1)
|
||||
expect(declaration.find('.measurement-goods-name input').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('shows all declaration fields as plain text when measurement is not editable', async () => {
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ processStatus: 'WAIT_SPLIT' }))
|
||||
const wrapper = await mountPanel()
|
||||
const declaration = wrapper.find('.measurement-declaration')
|
||||
|
||||
expect(declaration.find('input').exists()).toBe(false)
|
||||
expect(declaration.text()).toContain('蓝牙音箱')
|
||||
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(5)
|
||||
})
|
||||
|
||||
it('saves the corrected actual goods name without overriding declaration facts', async () => {
|
||||
const wrapper = await mountPanel()
|
||||
await wrapper.find('.measurement-goods-name input').setValue('修正后的蓝牙音箱')
|
||||
const save = wrapper.findAll('button').find((button) => button.text().includes('保存测量数据'))
|
||||
await save?.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(saveInboundMeasurementDraft).toHaveBeenCalledWith('10001', '7001', expect.objectContaining({
|
||||
measurementId: '9001',
|
||||
version: 2,
|
||||
actualGoodsName: '修正后的蓝牙音箱',
|
||||
items: expect.any(Array),
|
||||
}))
|
||||
const payload = vi.mocked(saveInboundMeasurementDraft).mock.calls[0]?.[2]
|
||||
expect(payload).not.toHaveProperty('originalDocumentNo')
|
||||
expect(payload).not.toHaveProperty('originalGrossWeightKg')
|
||||
expect(payload).not.toHaveProperty('originalVolumeCbm')
|
||||
})
|
||||
|
||||
it('updates the server weight summary without discarding unsaved measurement edits', async () => {
|
||||
@@ -224,10 +271,11 @@ describe('InboundMeasurementPanel', () => {
|
||||
const countInput = wrapper.find('.measurement-count-editor input')
|
||||
await countInput.setValue('9')
|
||||
|
||||
wrapper.vm.updateActualGrossWeight(125.75)
|
||||
wrapper.vm.updateActualWeights(125.75, 115.75)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('.measurement-metrics').text()).toContain('125.75')
|
||||
expect(wrapper.find('.measurement-metrics').text()).toContain('115.75')
|
||||
expect((countInput.element as HTMLInputElement).value).toBe('9')
|
||||
})
|
||||
|
||||
@@ -237,6 +285,9 @@ describe('InboundMeasurementPanel', () => {
|
||||
expect(wrapper.classes()).toContain('surface-panel')
|
||||
expect(wrapper.find('.measurement-summary-header').exists()).toBe(true)
|
||||
expect(wrapper.find('.measurement-summary-status').text()).toContain('测量中')
|
||||
expect(wrapper.find('.measurement-summary-header').text()).not.toContain('RK202608040001')
|
||||
expect(wrapper.find('.measurement-summary-header').text()).toContain('测试企业')
|
||||
expect(wrapper.find('.measurement-summary-header').text()).toContain('美西海运')
|
||||
})
|
||||
|
||||
it('saves only the changed confirmed package count with the current measurement version', async () => {
|
||||
@@ -374,7 +425,6 @@ describe('InboundMeasurementPanel', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(getInboundMeasurement).toHaveBeenLastCalledWith('10002', '7002')
|
||||
expect(wrapper.find('.measurement-summary-header').text()).toContain('RK202608040002')
|
||||
expect(wrapper.find('.measurement-summary-header').text()).toContain('第二企业')
|
||||
expect(wrapper.find('.measurement-summary-header').text()).not.toContain('测试企业')
|
||||
})
|
||||
|
||||
@@ -37,9 +37,6 @@ interface EditableMeasurementItem extends SaveInboundMeasurementItemRequest {
|
||||
interface MeasurementFormModel {
|
||||
measurementId: string | null
|
||||
version: number
|
||||
originalDocumentNo: string
|
||||
originalGrossWeightKg: number | null
|
||||
originalVolumeCbm: number | null
|
||||
actualGoodsName: string
|
||||
items: EditableMeasurementItem[]
|
||||
}
|
||||
@@ -79,9 +76,6 @@ let itemSequence = 0
|
||||
const form = reactive<MeasurementFormModel>({
|
||||
measurementId: null,
|
||||
version: 0,
|
||||
originalDocumentNo: '',
|
||||
originalGrossWeightKg: null,
|
||||
originalVolumeCbm: null,
|
||||
actualGoodsName: '',
|
||||
items: [],
|
||||
})
|
||||
@@ -239,9 +233,6 @@ function editableItem(item: InboundMeasurementItem): EditableMeasurementItem {
|
||||
function clearForm() {
|
||||
form.measurementId = null
|
||||
form.version = 0
|
||||
form.originalDocumentNo = ''
|
||||
form.originalGrossWeightKg = null
|
||||
form.originalVolumeCbm = null
|
||||
form.actualGoodsName = ''
|
||||
form.items.splice(0)
|
||||
baseline.value = JSON.stringify(form)
|
||||
@@ -252,9 +243,6 @@ function applyDetail(resource: InboundMeasurementDetail) {
|
||||
detail.value = resource
|
||||
form.measurementId = resource.measurementId == null ? null : String(resource.measurementId)
|
||||
form.version = resource.version ?? 0
|
||||
form.originalDocumentNo = resource.originalDocumentNo || ''
|
||||
form.originalGrossWeightKg = resource.originalGrossWeightKg
|
||||
form.originalVolumeCbm = resource.originalVolumeCbm
|
||||
form.actualGoodsName = resource.actualGoodsName || ''
|
||||
form.items.splice(0, form.items.length, ...resource.items.map(editableItem))
|
||||
baseline.value = JSON.stringify(form)
|
||||
@@ -340,24 +328,13 @@ async function saveDraft() {
|
||||
conflictDetected.value = false
|
||||
try {
|
||||
const originalCounts = new Map((detail.value?.items || []).map((item) => [String(item.id), item.packageCount]))
|
||||
const originalDraft = detail.value
|
||||
? { originalDocumentNo: detail.value.originalDocumentNo || '', originalGrossWeightKg: detail.value.originalGrossWeightKg, originalVolumeCbm: detail.value.originalVolumeCbm, actualGoodsName: detail.value.actualGoodsName || '' }
|
||||
: null
|
||||
const draftChanged = Boolean(originalDraft && (
|
||||
originalDraft.originalDocumentNo !== form.originalDocumentNo
|
||||
|| originalDraft.originalGrossWeightKg !== form.originalGrossWeightKg
|
||||
|| originalDraft.originalVolumeCbm !== form.originalVolumeCbm
|
||||
|| originalDraft.actualGoodsName !== form.actualGoodsName
|
||||
))
|
||||
const draftChanged = Boolean(detail.value && (detail.value.actualGoodsName || '') !== form.actualGoodsName)
|
||||
const changedItems = form.items.filter((item) => originalCounts.get(item.itemId) !== item.packageCount)
|
||||
let version = form.version
|
||||
if (draftChanged) {
|
||||
const saved = await saveInboundMeasurementDraft(customerId.value, props.inboundId, {
|
||||
measurementId: form.measurementId,
|
||||
version,
|
||||
originalDocumentNo: form.originalDocumentNo.trim() || null,
|
||||
originalGrossWeightKg: form.originalGrossWeightKg,
|
||||
originalVolumeCbm: form.originalVolumeCbm,
|
||||
actualGoodsName: form.actualGoodsName.trim() || null,
|
||||
items: form.items.map(({ itemId: _itemId, clientKey: _clientKey, serverValues: _serverValues, ...item }) => item),
|
||||
})
|
||||
@@ -446,9 +423,9 @@ function processStatusLabel(status: string | undefined) {
|
||||
return inboundStatusLabel(status, inboundProcessStatusLabels)
|
||||
}
|
||||
|
||||
function updateActualGrossWeight(actualGrossWeightKg: number | null) {
|
||||
function updateActualWeights(actualGrossWeightKg: number | null, actualNetWeightKg: number | null) {
|
||||
if (!detail.value) return
|
||||
detail.value = { ...detail.value, actualGrossWeightKg }
|
||||
detail.value = { ...detail.value, actualGrossWeightKg, actualNetWeightKg }
|
||||
}
|
||||
|
||||
function measurementRowClassName({ row }: { row: EditableMeasurementItem }) {
|
||||
@@ -472,7 +449,7 @@ watch(
|
||||
|
||||
onBeforeUnmount(releaseImagePreview)
|
||||
|
||||
defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain, maySubmit, saving, submitting, canEdit, canSubmit, isDirty })
|
||||
defineExpose({ saveDraft, submitForReview, updateActualWeights, mayMaintain, maySubmit, saving, submitting, canEdit, canSubmit, isDirty })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -523,10 +500,9 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
|
||||
<legend class="sr-only">测量草稿</legend>
|
||||
<section class="measurement-section measurement-summary-section">
|
||||
<header class="measurement-summary-header">
|
||||
<div>
|
||||
<span>入仓码</span>
|
||||
<strong>{{ detail.inboundCode || '—' }}</strong>
|
||||
<p>{{ detail.companyName || '客户未命名' }} · {{ detail.channelName || detail.channelCode || '渠道未填写' }}</p>
|
||||
<div class="measurement-summary-context">
|
||||
<p><span>客户</span>{{ detail.companyName || '客户未命名' }}</p>
|
||||
<p><span>渠道</span>{{ detail.channelName || detail.channelCode || '渠道未填写' }}</p>
|
||||
</div>
|
||||
<div class="measurement-summary-status">
|
||||
<el-tag effect="light">{{ processStatusLabel(detail.processStatus) }}</el-tag>
|
||||
@@ -539,7 +515,8 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
|
||||
<dt>实测箱数</dt>
|
||||
<dd>{{ formatNumber(confirmedPackageCount, 0) }} <small>/ 申报 {{ formatNumber(expectedPackageCount, 0) }}</small></dd>
|
||||
</div>
|
||||
<div class="measurement-metric--primary"><dt>货物重量</dt><dd>{{ formatNumber(detail.actualGrossWeightKg) }} <small>kg</small></dd></div>
|
||||
<div><dt>实测毛重</dt><dd>{{ formatNumber(detail.actualGrossWeightKg) }} <small>kg</small></dd></div>
|
||||
<div class="measurement-metric--primary"><dt>货物重量</dt><dd>{{ formatNumber(detail.actualNetWeightKg) }} <small>kg</small></dd></div>
|
||||
<div><dt>总体积</dt><dd>{{ formatNumber(detail.actualVolumeCbm, 4) }} <small>m³</small></dd></div>
|
||||
<div><dt>材积重</dt><dd>{{ formatNumber(detail.volumeWeightKg) }} <small>kg</small></dd></div>
|
||||
</dl>
|
||||
@@ -547,11 +524,12 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
|
||||
<section class="measurement-declaration" aria-labelledby="measurement-declaration-title">
|
||||
<h4 id="measurement-declaration-title">申报信息</h4>
|
||||
<div class="measurement-data-grid">
|
||||
<label><span>申报箱数</span><el-input :model-value="formatNumber(expectedPackageCount, 0)" readonly /></label>
|
||||
<label><span>申报毛重(kg)</span><el-input-number v-model="form.originalGrossWeightKg" :min="0" :precision="3" controls-position="right" /></label>
|
||||
<label><span>申报体积(m³)</span><el-input-number v-model="form.originalVolumeCbm" :min="0" :precision="4" controls-position="right" /></label>
|
||||
<label><span>实际品名</span><el-input v-model="form.actualGoodsName" placeholder="请输入实际品名" maxlength="128" /></label>
|
||||
<label><span>原单据号码</span><el-input v-model="form.originalDocumentNo" placeholder="请输入原单据号码" maxlength="128" /></label>
|
||||
<div class="measurement-readonly-field"><span>申报箱数</span><strong>{{ formatNumber(expectedPackageCount, 0) }}</strong></div>
|
||||
<div class="measurement-readonly-field"><span>申报毛重(kg)</span><strong>{{ formatNumber(detail.originalGrossWeightKg) }}</strong></div>
|
||||
<div class="measurement-readonly-field"><span>申报体积(m³)</span><strong>{{ formatNumber(detail.originalVolumeCbm, 4) }}</strong></div>
|
||||
<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 class="measurement-readonly-field"><span>原单据号码</span><strong>{{ detail.originalDocumentNo || '—' }}</strong></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -696,10 +674,9 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
|
||||
.measurement-section { padding: 16px 20px; border-top: 1px solid var(--cc-border); }
|
||||
.measurement-summary-section { display: grid; gap: 12px; border-top: 0; }
|
||||
.measurement-summary-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
|
||||
.measurement-summary-header > div:first-child { display: grid; min-width: 0; gap: 3px; }
|
||||
.measurement-summary-context { display: flex; min-width: 0; flex-wrap: wrap; align-items: center; gap: 8px 20px; }
|
||||
.measurement-summary-header span { color: var(--cc-muted); font-size: 11px; }
|
||||
.measurement-summary-header strong { overflow-wrap: anywhere; color: var(--cc-text); font-size: 20px; line-height: 1.3; }
|
||||
.measurement-summary-header p { margin: 0; color: var(--cc-muted); font-size: 12px; }
|
||||
.measurement-summary-context p { display: inline-flex; min-width: 0; align-items: baseline; gap: 6px; margin: 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: 12px; font-weight: 600; }
|
||||
.measurement-summary-status { display: flex; flex: 0 0 auto; align-items: center; gap: 9px; }
|
||||
.measurement-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 2px 0; }
|
||||
.measurement-metrics > div { display: grid; min-width: 0; min-height: 58px; align-content: center; gap: 4px; padding: 5px 18px; border-left: 1px solid var(--cc-border); }
|
||||
@@ -718,7 +695,10 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
|
||||
.measurement-form label { display: flex; min-width: 0; flex-direction: column; gap: 7px; color: #344054; font-size: 12px; font-weight: 650; }
|
||||
.measurement-form :deep(.el-input-number), .measurement-form :deep(.el-input) { width: 100%; }
|
||||
.measurement-data-grid label { display: flex; min-width: 0; flex-direction: column; gap: 5px; color: #344054; font-size: 12px; font-weight: 600; }
|
||||
.measurement-data-grid :deep(.el-input__wrapper) { border-radius: 3px; background: #f5f5f5; box-shadow: 0 0 0 1px #e5e7eb inset; }
|
||||
.measurement-data-grid :deep(.el-input__wrapper) { border-radius: 3px; }
|
||||
.measurement-readonly-field { display: grid; min-width: 0; align-content: start; gap: 7px; }
|
||||
.measurement-readonly-field span { color: #344054; font-size: 12px; font-weight: 600; }
|
||||
.measurement-readonly-field strong { min-height: 32px; padding: 7px 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: 13px; line-height: 18px; }
|
||||
.measurement-image-preview { display: grid; min-height: 240px; place-items: center; }
|
||||
.measurement-image-preview img { display: block; width: auto; max-width: 100%; max-height: 68vh; object-fit: contain; }
|
||||
.manual-entry-alert { margin-bottom: 16px; }
|
||||
|
||||
@@ -11,9 +11,6 @@ vi.mock('@/shared/api/http', () => ({
|
||||
const draft: SaveInboundMeasurementDraftRequest = {
|
||||
measurementId: '9001',
|
||||
version: 2,
|
||||
originalDocumentNo: 'DOC-01',
|
||||
originalGrossWeightKg: 120,
|
||||
originalVolumeCbm: 1.2,
|
||||
actualGoodsName: '蓝牙音箱',
|
||||
items: [{
|
||||
boxCode: 'RK202608040001#10-1',
|
||||
@@ -62,6 +59,9 @@ describe('inbound measurement API', () => {
|
||||
draft,
|
||||
{ params: { tenantId: '10001' } },
|
||||
)
|
||||
expect(draft).not.toHaveProperty('originalDocumentNo')
|
||||
expect(draft).not.toHaveProperty('originalGrossWeightKg')
|
||||
expect(draft).not.toHaveProperty('originalVolumeCbm')
|
||||
})
|
||||
|
||||
it('submits the server measurement id and optimistic-lock version', async () => {
|
||||
|
||||
@@ -84,6 +84,7 @@ export interface InboundMeasurementDetail {
|
||||
originalPackageCount: number | null
|
||||
actualPackageCount: number | null
|
||||
actualGrossWeightKg: number | null
|
||||
actualNetWeightKg: number | null
|
||||
actualVolumeCbm: number | null
|
||||
volumeWeightKg: number | null
|
||||
actualGoodsName: string | null
|
||||
@@ -113,9 +114,6 @@ export interface SaveInboundMeasurementItemRequest {
|
||||
export interface SaveInboundMeasurementDraftRequest {
|
||||
measurementId: MeasurementIdentifier | null
|
||||
version: number
|
||||
originalDocumentNo: string | null
|
||||
originalGrossWeightKg: number | null
|
||||
originalVolumeCbm: number | null
|
||||
actualGoodsName: string | null
|
||||
items: SaveInboundMeasurementItemRequest[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user