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

This commit is contained in:
2026-11-05 16:03:06 +08:00
4 changed files with 44 additions and 25 deletions
@@ -381,8 +381,8 @@ describe('InboundMeasurementPanel', () => {
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ measurementId: null, items: [], weightResults: [] })) vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ measurementId: null, items: [], weightResults: [] }))
const wrapper = await mountPanel() const wrapper = await mountPanel()
expect(wrapper.find('.measurement-summary-header h4').text()).toBe('申报与实测对比') expect(wrapper.find('.measurement-summary-header h4').text()).toBe('申报与实测对比')
expect(wrapper.find('.measurement-declaration-header').text()).not.toContain('DOC-001') expect(wrapper.find('.measurement-declaration-header').text()).toContain('DOC-001')
expect(wrapper.find('.measurement-section__heading .measurement-goods-name').exists()).toBe(false) expect(wrapper.find('.measurement-section__heading .measurement-goods-name input').exists()).toBe(true)
expect(wrapper.find('.measurement-section__heading .measurement-section-actions').text()).toContain('手动录入箱规') 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.findAll('.el-alert').some(alert => alert.text().includes('请先完成箱规测量'))).toBe(false)
expect(wrapper.find('.measurement-panel__footer').text()).toContain('请先完成箱规测量') expect(wrapper.find('.measurement-panel__footer').text()).toContain('请先完成箱规测量')
@@ -396,11 +396,11 @@ describe('InboundMeasurementPanel', () => {
expect(comparison.text()).toContain('118.5') expect(comparison.text()).toContain('118.5')
expect(comparison.text()).toContain('0.8') expect(comparison.text()).toContain('0.8')
expect(comparison.findAll('tbody tr')[0].text()).toContain('10') expect(comparison.findAll('tbody tr')[0].text()).toContain('10')
expect(declaration.text()).not.toContain('原单据号码') expect(declaration.text()).toContain('原单据号码')
expect(declaration.text()).not.toContain('DOC-001') expect(declaration.text()).toContain('DOC-001')
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(0) expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(1)
expect(declaration.findAll('input')).toHaveLength(0) expect(declaration.findAll('input')).toHaveLength(0)
expect(wrapper.find('.measurement-items-section .measurement-goods-name').exists()).toBe(false) expect(wrapper.find('.measurement-items-section .measurement-goods-name input').exists()).toBe(true)
expect(declaration.find('[aria-label="客户申报箱规明细"]').text()).toContain('蓝牙音箱') expect(declaration.find('[aria-label="客户申报箱规明细"]').text()).toContain('蓝牙音箱')
expect(declaration.find('[aria-label="客户申报箱规明细"]').text()).toContain('50 × 40 × 30') expect(declaration.find('[aria-label="客户申报箱规明细"]').text()).toContain('50 × 40 × 30')
}) })
@@ -418,14 +418,18 @@ describe('InboundMeasurementPanel', () => {
expect(declaration.find('input').exists()).toBe(false) expect(declaration.find('input').exists()).toBe(false)
expect(declaration.text()).toContain('蓝牙音箱') expect(declaration.text()).toContain('蓝牙音箱')
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(0) expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(1)
expect(wrapper.find('.measurement-items-section .measurement-goods-name').exists()).toBe(false) const actualGoods = wrapper.get('.measurement-items-section .measurement-goods-name')
expect(actualGoods.text()).toContain('蓝牙音箱')
expect(actualGoods.find('input').exists()).toBe(false)
}) })
it('preserves the hidden actual goods name without overriding declaration facts', async () => { it('saves the edited actual goods name without overriding declaration facts', async () => {
const wrapper = await mountPanel() const wrapper = await mountPanel()
expect(wrapper.text()).not.toContain('实际品名') expect(wrapper.get<HTMLInputElement>('input[aria-label="实际品名"]').element.value).toBe('蓝牙音箱')
await wrapper.find('input[aria-label="仓库修改外箱长"]').setValue('55') await wrapper.get('input[aria-label="实际品名"]').setValue(' 实测蓝牙音箱 ')
expect(wrapper.get('.measurement-declaration').text()).toContain('蓝牙音箱')
expect(wrapper.get('.measurement-declaration').text()).not.toContain('实测蓝牙音箱')
const save = wrapper.findAll('button').find((button) => button.text().includes('保存测量数据')) const save = wrapper.findAll('button').find((button) => button.text().includes('保存测量数据'))
await save?.trigger('click') await save?.trigger('click')
await flushPromises() await flushPromises()
@@ -433,7 +437,7 @@ describe('InboundMeasurementPanel', () => {
expect(saveInboundMeasurementDraft).toHaveBeenCalledWith('10001', '7001', expect.objectContaining({ expect(saveInboundMeasurementDraft).toHaveBeenCalledWith('10001', '7001', expect.objectContaining({
measurementId: '9001', measurementId: '9001',
version: 2, version: 2,
actualGoodsName: '蓝牙音箱', actualGoodsName: '实测蓝牙音箱',
items: expect.any(Array), items: expect.any(Array),
})) }))
const payload = vi.mocked(saveInboundMeasurementDraft).mock.calls[0]?.[2] const payload = vi.mocked(saveInboundMeasurementDraft).mock.calls[0]?.[2]
@@ -630,7 +634,7 @@ describe('InboundMeasurementPanel', () => {
const wrapper = await mountPanel() const wrapper = await mountPanel()
expect(wrapper.find('.measurement-declaration input').exists()).toBe(false) 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-expected-pallets input').exists()).toBe(true)
expect(wrapper.find('.measurement-items-section .measurement-goods-name').exists()).toBe(false) expect(wrapper.find('.measurement-items-section .measurement-goods-name input').exists()).toBe(true)
}) })
it('requires at least one confirmed weight image before submitting for review', async () => { it('requires at least one confirmed weight image before submitting for review', async () => {
@@ -736,7 +736,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<p v-for="warning in measurementDeviationWarnings" :key="warning.key">{{ warning.text }}</p> <p v-for="warning in measurementDeviationWarnings" :key="warning.key">{{ warning.text }}</p>
</el-alert> </el-alert>
<div class="workbench-evidence"><table class="measurement-comparison" aria-label="客户申报与平台实测对比"> <table class="measurement-comparison" aria-label="客户申报与平台实测对比">
<thead><tr><th scope="col">指标</th><th scope="col">客户申报</th><th scope="col">平台实测</th><th scope="col">差异(实测 − 申报)</th></tr></thead> <thead><tr><th scope="col">指标</th><th scope="col">客户申报</th><th scope="col">平台实测</th><th scope="col">差异(实测 − 申报)</th></tr></thead>
<tbody> <tbody>
<tr v-for="metric in comparisonMetrics" :key="metric.label"> <tr v-for="metric in comparisonMetrics" :key="metric.label">
@@ -748,7 +748,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table></div> </table>
<div class="measurement-extra-facts"> <div class="measurement-extra-facts">
<span>货物净重 <strong>{{ formatNumber(detail.actualNetWeightKg) }}</strong> kg</span> <span>货物净重 <strong>{{ formatNumber(detail.actualNetWeightKg) }}</strong> kg</span>
<span>计费材积重 <strong>{{ formatNumber(detail.volumeWeightKg) }}</strong> kg</span> <span>计费材积重 <strong>{{ formatNumber(detail.volumeWeightKg) }}</strong> kg</span>
@@ -759,6 +759,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<section class="measurement-declaration" aria-labelledby="measurement-declaration-title"> <section class="measurement-declaration" aria-labelledby="measurement-declaration-title">
<header class="measurement-declaration-header"> <header class="measurement-declaration-header">
<h4 id="measurement-declaration-title">客户申报明细</h4> <h4 id="measurement-declaration-title">客户申报明细</h4>
<div class="measurement-readonly-field measurement-document-number"><span>原单据号码</span><strong>{{ detail.originalDocumentNo || '—' }}</strong></div>
</header> </header>
<el-table <el-table
v-if="detail.latestDeclaration?.items.length" v-if="detail.latestDeclaration?.items.length"
@@ -782,7 +783,9 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<div class="measurement-section__heading"> <div class="measurement-section__heading">
<div><h3>平台实测箱规</h3></div> <div><h3>平台实测箱规</h3></div>
<div v-if="isDropship" class="measurement-data-grid measurement-platform-fields"> <div class="measurement-data-grid measurement-platform-fields">
<label v-if="mayMaintain" class="measurement-goods-name"><span>实际品名</span><el-input v-model="form.actualGoodsName" aria-label="实际品名" placeholder="请输入实际品名" maxlength="255" /></label>
<div v-else class="measurement-readonly-field measurement-goods-name"><span>实际品名</span><strong>{{ detail.actualGoodsName || '—' }}</strong></div>
<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> <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 v-else-if="isDropship" class="measurement-readonly-field measurement-expected-pallets"><span>预计托数</span><strong>{{ detail.expectedPalletCount ?? '—' }}</strong></div>
</div> </div>
@@ -927,9 +930,9 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<style scoped> <style scoped>
.measurement-comparison { width: 100%; border-collapse: collapse; table-layout: fixed; background: var(--admin-surface, #fff); font-size: var(--admin-font-body, 13px); } .measurement-comparison { width: 100%; border-collapse: collapse; table-layout: fixed; background: var(--admin-surface, #fff); font-size: var(--admin-font-body, 13px); }
.measurement-comparison th, .measurement-comparison td { padding: var(--admin-space-3, 8px) var(--admin-space-5, 12px); border: 1px solid var(--workbench-border); text-align: left; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; } .measurement-comparison th, .measurement-comparison td { padding: var(--admin-space-3, 8px) var(--admin-space-5, 12px); border: 1px solid var(--workbench-border); text-align: left; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
.measurement-comparison thead th { background: var(--cc-list-table-header-bg); color: var(--cc-list-table-header-text); } .measurement-comparison thead th { background: var(--admin-label-bg); color: var(--workbench-text); }
.measurement-comparison thead th:nth-child(2) { background: #f7f9ff; color: #465fc4; } .measurement-comparison thead th:nth-child(2) { background: var(--admin-dialog-source-bg); color: #465fc4; }
.measurement-comparison thead th:nth-child(3) { background: var(--admin-surface, #fff); color: #18795d; } .measurement-comparison thead th:nth-child(3) { background: var(--admin-dialog-summary-bg); color: #18795d; }
.measurement-comparison__difference { color: var(--el-color-danger-dark-2); font-weight: 600; } .measurement-comparison__difference { color: var(--el-color-danger-dark-2); font-weight: 600; }
.measurement-extra-facts { display: flex; flex-wrap: wrap; gap: var(--admin-space-3, 8px) var(--admin-space-6, 16px); color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); } .measurement-extra-facts { display: flex; flex-wrap: wrap; gap: var(--admin-space-3, 8px) var(--admin-space-6, 16px); color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); }
.measurement-extra-facts strong { color: var(--workbench-text); } .measurement-extra-facts strong { color: var(--workbench-text); }
@@ -1,14 +1,15 @@
/* Measurement workspace: keep all sections inside the panel's content bounds. */ /* Approved measurement-only palette; shared dialog styling must preserve these sections and gutters. */
.inbound-measurement-workspace-dialog.inbound-density .measurement-summary-section { min-width: 0; margin: 0; padding: var(--admin-space-4, 10px) var(--admin-space-5, 12px); border: 1px solid var(--cc-border); border-radius: var(--admin-dialog-section-radius); background: var(--admin-surface, #fff); } .inbound-measurement-workspace-dialog.inbound-density .measurement-summary-section { min-width: 0; margin: 0; padding: var(--admin-space-4, 10px) var(--admin-space-5, 12px); border: 1px solid var(--admin-dialog-summary-border); border-left: 4px solid #20a477; border-radius: var(--admin-dialog-section-radius); background: var(--admin-dialog-summary-bg); }
.inbound-measurement-workspace-dialog.inbound-density .measurement-summary-header { flex-wrap: wrap; align-items: center; gap: var(--admin-space-3, 8px) var(--admin-space-6, 16px); } .inbound-measurement-workspace-dialog.inbound-density .measurement-summary-header { flex-wrap: wrap; align-items: center; gap: var(--admin-space-3, 8px) var(--admin-space-6, 16px); }
.inbound-measurement-workspace-dialog.inbound-density .measurement-summary-context { flex: 1 1 auto; } .inbound-measurement-workspace-dialog.inbound-density .measurement-summary-context { flex: 1 1 auto; }
.inbound-measurement-workspace-dialog.inbound-density .measurement-summary-context p > span { flex-shrink: 0; } .inbound-measurement-workspace-dialog.inbound-density .measurement-summary-context p > span { flex-shrink: 0; }
.inbound-measurement-workspace-dialog.inbound-density .measurement-summary-status { flex-wrap: wrap; } .inbound-measurement-workspace-dialog.inbound-density .measurement-summary-status { flex-wrap: wrap; }
.inbound-measurement-workspace-dialog.inbound-density .measurement-items-section { min-width: 0; margin: var(--admin-space-5, 12px) 0 0; padding: var(--admin-space-4, 10px) var(--admin-space-5, 12px); border: 1px solid var(--cc-border); border-radius: var(--admin-dialog-section-radius); background: var(--admin-surface, #fff); } .inbound-measurement-workspace-dialog.inbound-density .measurement-items-section { min-width: 0; margin: var(--admin-space-5, 12px) 0 0; padding: var(--admin-space-4, 10px) var(--admin-space-5, 12px); border: 1px solid var(--admin-dialog-work-border); border-left: 4px solid #c79643; border-radius: var(--admin-dialog-section-radius); background: var(--admin-dialog-work-bg); }
.inbound-measurement-workspace-dialog.inbound-density .measurement-deviation-alert { background: var(--admin-surface, #fff); } .inbound-measurement-workspace-dialog.inbound-density .measurement-deviation-alert { background: var(--admin-surface, #fff); }
.inbound-measurement-workspace-dialog.inbound-density .measurement-subheading { color: #18795d; font-size: var(--admin-font-section, 14px); } .inbound-measurement-workspace-dialog.inbound-density .measurement-subheading { color: #18795d; font-size: var(--admin-font-section, 14px); }
.inbound-measurement-workspace-dialog.inbound-density .measurement-declaration { min-width: 0; margin-top: var(--admin-space-3, 8px); padding: var(--admin-space-4, 10px) var(--admin-space-5, 12px); border: 1px solid var(--admin-dialog-source-border); border-radius: var(--admin-dialog-section-radius); background: var(--admin-dialog-source-bg); } .inbound-measurement-workspace-dialog.inbound-density .measurement-declaration { min-width: 0; margin-top: var(--admin-space-3, 8px); padding: var(--admin-space-4, 10px) var(--admin-space-5, 12px); border: 1px solid var(--admin-dialog-source-border); border-left: 4px solid #647fec; border-radius: var(--admin-dialog-section-radius); background: var(--admin-dialog-source-bg); }
.inbound-measurement-workspace-dialog.inbound-density .measurement-declaration-table th.el-table__cell { background: var(--admin-label-bg); color: var(--workbench-muted); }
@media (min-width: 961px) { @media (min-width: 961px) {
.inbound-density.admin-density-dialog .measurement-dialog-weight { padding-bottom: var(--admin-space-4, 10px); } .inbound-density.admin-density-dialog .measurement-dialog-weight { padding-bottom: var(--admin-space-4, 10px); }
+13 -2
View File
@@ -431,7 +431,11 @@ describe('approved Admin density ownership', () => {
expect(rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable .el-form-item.is-error')).toMatchObject({ 'margin-bottom': 'var(--admin-space-5)' }) expect(rules.get(':is(.admin-density, .admin-density-dialog) .density-table--editable .el-form-item.is-error')).toMatchObject({ 'margin-bottom': 'var(--admin-space-5)' })
for (const selector of rules.keys()) if (selector.includes('density-dialog-section')) expect(selector.startsWith(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) ')).toBe(true) for (const selector of rules.keys()) if (selector.includes('density-dialog-section')) expect(selector.startsWith(':is(.admin-density-dialog, .admin-density-drawer:where(.admin-density-drawer--detail, .admin-density-drawer--editor)) ')).toBe(true)
const measured = read('modules/inbounds/measurement/measurementDensity.css') const measured = read('modules/inbounds/measurement/measurementDensity.css')
expect(measured).not.toMatch(/border-left(?:-color|-width)?\s*:/) cssRoot(measured).walkRules(rule => {
rule.walkDecls(/^border-left/, () => {
expect(rule.selector).toMatch(/^\.inbound-measurement-workspace-dialog\.inbound-density \.measurement-(summary-section|declaration|items-section)$/)
})
})
for (const color of ['#f4fbf8', '#f7f9ff', '#fffbf4']) expect(measured).not.toContain(color) for (const color of ['#f4fbf8', '#f7f9ff', '#fffbf4']) expect(measured).not.toContain(color)
for (const file of ['unified-orders/pages/UnifiedOrdersView.vue', 'unified-orders/components/OrderDeclarationEditorDialog.vue', 'unified-orders/components/InboundFeeDetailManager.vue', 'inbounds/measurement/InboundMeasurementPanel.vue', 'inbounds/weight/components/WeightOcrPanel.vue', 'domestic-containers/pages/DomesticContainersView.vue', 'sailing-calendar/pages/SailingCalendarView.vue', 'ocean-bookings/components/OceanBookingPrintDialog.vue', 'ocean-bookings/pages/OceanBookingDetailView.vue', 'trucking/components/TruckAttachmentDialog.vue', 'container-loading/pages/ContainersView.vue', 'container-loading/pages/ContainerDetailView.vue', 'container-loading/components/OceanBookingLinkDialog.vue']) { for (const file of ['unified-orders/pages/UnifiedOrdersView.vue', 'unified-orders/components/OrderDeclarationEditorDialog.vue', 'unified-orders/components/InboundFeeDetailManager.vue', 'inbounds/measurement/InboundMeasurementPanel.vue', 'inbounds/weight/components/WeightOcrPanel.vue', 'domestic-containers/pages/DomesticContainersView.vue', 'sailing-calendar/pages/SailingCalendarView.vue', 'ocean-bookings/components/OceanBookingPrintDialog.vue', 'ocean-bookings/pages/OceanBookingDetailView.vue', 'trucking/components/TruckAttachmentDialog.vue', 'container-loading/pages/ContainersView.vue', 'container-loading/pages/ContainerDetailView.vue', 'container-loading/components/OceanBookingLinkDialog.vue']) {
const source = read('modules/' + file) const source = read('modules/' + file)
@@ -1302,13 +1306,20 @@ describe('approved Admin density ownership', () => {
sectionStyles.set(rule.selector, declarations) sectionStyles.set(rule.selector, declarations)
}) })
for (const [section, background] of [ for (const [section, background] of [
['summary-section', 'var(--admin-surface, #fff)'], ['declaration', 'var(--admin-dialog-source-bg)'], ['items-section', 'var(--admin-surface, #fff)'], ['summary-section', 'var(--admin-dialog-summary-bg)'], ['declaration', 'var(--admin-dialog-source-bg)'], ['items-section', 'var(--admin-dialog-work-bg)'],
]) { ]) {
expect(sectionStyles.get(`.inbound-measurement-workspace-dialog.inbound-density .measurement-${section}`)).toMatchObject({ expect(sectionStyles.get(`.inbound-measurement-workspace-dialog.inbound-density .measurement-${section}`)).toMatchObject({
padding: 'var(--admin-space-4, 10px) var(--admin-space-5, 12px)', padding: 'var(--admin-space-4, 10px) var(--admin-space-5, 12px)',
background, background,
}) })
} }
for (const [section, accent] of [['summary-section', '#20a477'], ['declaration', '#647fec'], ['items-section', '#c79643']]) {
expect(sectionStyles.get(`.inbound-measurement-workspace-dialog.inbound-density .measurement-${section}`)?.['border-left']).toBe(`4px solid ${accent}`)
}
expect(differenceStyles.get('.measurement-comparison thead th')).toMatchObject({ background: 'var(--admin-label-bg)' })
expect(differenceStyles.get('.measurement-comparison thead th:nth-child(2)')).toMatchObject({ background: 'var(--admin-dialog-source-bg)' })
expect(differenceStyles.get('.measurement-comparison thead th:nth-child(3)')).toMatchObject({ background: 'var(--admin-dialog-summary-bg)' })
expect(sectionStyles.get('.inbound-measurement-workspace-dialog.inbound-density .measurement-declaration-table th.el-table__cell')).toMatchObject({ background: 'var(--admin-label-bg)', color: 'var(--workbench-muted)' })
expect(sectionStyles.get('.inbound-measurement-workspace-dialog.inbound-density .measurement-deviation-alert')?.background).toBe('var(--admin-surface, #fff)') expect(sectionStyles.get('.inbound-measurement-workspace-dialog.inbound-density .measurement-deviation-alert')?.background).toBe('var(--admin-surface, #fff)')
// Shared density must not reset the sections' horizontal gutters. // Shared density must not reset the sections' horizontal gutters.
expect(sectionStyles.get('.inbound-density.admin-density-dialog .measurement-section')).toEqual({ expect(sectionStyles.get('.inbound-density.admin-density-dialog .measurement-section')).toEqual({