优化
This commit is contained in:
@@ -104,7 +104,7 @@ function handleSubmitted() {
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="measurement-dialog-footer">
|
||||
<span v-if="measurementPanelRef?.submissionBlockReason" class="measurement-dialog-footer__hint">
|
||||
<span v-if="measurementPanelRef?.submissionBlockReason" class="measurement-dialog-footer__hint" role="status">
|
||||
{{ measurementPanelRef.submissionBlockReason }}
|
||||
</span>
|
||||
<el-button :disabled="Boolean(measurementPanelRef?.saving || measurementPanelRef?.submitting)" @click="requestClose">取消</el-button>
|
||||
@@ -147,15 +147,6 @@ function handleSubmitted() {
|
||||
.inbound-measurement-workspace-dialog .measurement-panel { border: 0; border-radius: 0; box-shadow: none; }
|
||||
.inbound-measurement-workspace-dialog .measurement-panel__heading .measurement-panel__actions,
|
||||
.inbound-measurement-workspace-dialog .measurement-panel__footer { display: none; }
|
||||
.inbound-measurement-workspace-dialog .measurement-summary-section { margin: 0 -12px; padding: 10px 12px 13px; border: 1px solid #cce8df; border-left: 4px solid #20a477; border-radius: 8px; background: #f4fbf8; }
|
||||
.inbound-measurement-workspace-dialog .measurement-summary-context { min-height: auto; }
|
||||
.inbound-measurement-workspace-dialog .measurement-subheading { color: #18795d; font-size: 15px; }
|
||||
.inbound-measurement-workspace-dialog .measurement-metrics { padding: 0; border: 1px solid #cce8df; border-radius: 8px; background: #fff; overflow: hidden; }
|
||||
.inbound-measurement-workspace-dialog .measurement-metrics > div { min-height: 58px; padding: 9px 14px; border-left: 1px solid #e8ecf2; }
|
||||
.inbound-measurement-workspace-dialog .measurement-metrics > div:first-child { border-left: 0; }
|
||||
.inbound-measurement-workspace-dialog .measurement-declaration { margin-top: 12px; padding: 10px 12px 0; border: 1px solid #d9e2fa; border-left: 4px solid #647fec; border-radius: 8px; background: #f7f9ff; }
|
||||
.inbound-measurement-workspace-dialog .measurement-items-section { margin-top: 12px; padding: 10px 12px 13px; border: 1px solid #eadfc9; border-left: 4px solid #c79643; border-radius: 8px; background: #fffbf4; }
|
||||
.inbound-measurement-workspace-dialog .measurement-items-section .measurement-section__heading { margin-bottom: 8px; }
|
||||
@media (max-width: 720px) {
|
||||
.inbound-measurement-workspace-dialog.el-dialog { width: calc(100vw - 20px) !important; max-width: calc(100vw - 20px); }
|
||||
.inbound-measurement-workspace-dialog .el-dialog__header { padding: 16px; }
|
||||
|
||||
@@ -266,7 +266,7 @@ describe('InboundMeasurementPanel', () => {
|
||||
const wrapper = await mountPanel()
|
||||
const text = wrapper.text()
|
||||
|
||||
expect(text).toContain('实测箱数')
|
||||
expect(text).toContain('箱数')
|
||||
expect(text).toContain('货物净重')
|
||||
expect(text).toContain('总体积')
|
||||
expect(text).toContain('材积重')
|
||||
@@ -274,7 +274,7 @@ describe('InboundMeasurementPanel', () => {
|
||||
expect(text).toContain('平台实测')
|
||||
expect(text).toContain('差异 +1.5 kg')
|
||||
expect(text).toContain('差异 -0.2 m³')
|
||||
expect(text).toContain('箱规明细')
|
||||
expect(text).toContain('平台实测箱规')
|
||||
expect(text).not.toContain('租户')
|
||||
expect(text).not.toContain('数据版本')
|
||||
|
||||
@@ -287,27 +287,58 @@ describe('InboundMeasurementPanel', () => {
|
||||
expect(source).toContain('<section class="measurement-declaration"')
|
||||
expect(source).not.toContain('<el-collapse-item name="declaration"')
|
||||
expect(source).toContain('grid-template-columns: repeat(4, minmax(0, 1fr))')
|
||||
expect(source).toContain('.measurement-data-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));')
|
||||
expect(source).toContain('.measurement-data-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));')
|
||||
expect(source).toContain('.measurement-table { width: 100%; font-size: var(--admin-font-caption, 12px); }')
|
||||
expect(source).toContain('.measurement-data-grid :deep(.el-input__wrapper)')
|
||||
expect(source).toContain('defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlockReason, mayMaintain, maySubmit, saving, submitting, canEdit, canSubmit, isDirty })')
|
||||
})
|
||||
|
||||
it('aligns declared and measured values and keeps the declaration outside the measured section', async () => {
|
||||
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³'],
|
||||
])
|
||||
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)
|
||||
})
|
||||
|
||||
it('shows pending comparison rather than a negative count when no boxes have been measured', async () => {
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({
|
||||
items: [], actualPackageCount: null, actualGrossWeightKg: null, actualVolumeCbm: null,
|
||||
}))
|
||||
const wrapper = await mountPanel()
|
||||
const rows = wrapper.findAll('.measurement-comparison tbody tr')
|
||||
for (const row of rows) {
|
||||
expect(row.findAll('td')[1].text()).toBe('未测量')
|
||||
expect(row.findAll('td')[2].text()).toBe('待测量')
|
||||
}
|
||||
})
|
||||
|
||||
it('groups titles and related fields into shared header rows without duplicating submission warnings', async () => {
|
||||
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-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('请先完成箱规测量')
|
||||
})
|
||||
|
||||
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')
|
||||
const comparison = wrapper.find('.measurement-comparison')
|
||||
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(4)
|
||||
expect(declaration.findAll('input')).toHaveLength(1)
|
||||
expect(declaration.find('.measurement-goods-name input').exists()).toBe(true)
|
||||
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(1)
|
||||
expect(declaration.findAll('input')).toHaveLength(0)
|
||||
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('50 × 40 × 30')
|
||||
})
|
||||
@@ -315,7 +346,7 @@ describe('InboundMeasurementPanel', () => {
|
||||
it('does not render differences when the corresponding declaration is unavailable', async () => {
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ originalGrossWeightKg: null, originalVolumeCbm: null }))
|
||||
const wrapper = await mountPanel()
|
||||
expect(wrapper.find('.measurement-metrics').text()).not.toContain('差异')
|
||||
expect(wrapper.findAll('.measurement-comparison tbody tr').slice(1).map(row => row.findAll('td')[2].text())).toEqual(['—', '—'])
|
||||
})
|
||||
|
||||
it('shows all declaration fields as plain text when measurement is not editable', async () => {
|
||||
@@ -325,7 +356,8 @@ describe('InboundMeasurementPanel', () => {
|
||||
|
||||
expect(declaration.find('input').exists()).toBe(false)
|
||||
expect(declaration.text()).toContain('蓝牙音箱')
|
||||
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(5)
|
||||
expect(declaration.findAll('.measurement-readonly-field')).toHaveLength(1)
|
||||
expect(wrapper.find('.measurement-items-section .measurement-goods-name').text()).toContain('蓝牙音箱')
|
||||
})
|
||||
|
||||
it('saves the corrected actual goods name without overriding declaration facts', async () => {
|
||||
@@ -369,8 +401,8 @@ describe('InboundMeasurementPanel', () => {
|
||||
wrapper.vm.updateWeightEvidence(125.75, 115.75, true)
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('.measurement-metrics').text()).toContain('125.75')
|
||||
expect(wrapper.find('.measurement-metrics').text()).toContain('115.75')
|
||||
expect(wrapper.find('.measurement-summary-section').text()).toContain('125.75')
|
||||
expect(wrapper.find('.measurement-summary-section').text()).toContain('115.75')
|
||||
expect((countInput.element as HTMLInputElement).value).toBe('9')
|
||||
})
|
||||
|
||||
@@ -515,6 +547,35 @@ describe('InboundMeasurementPanel', () => {
|
||||
expect(wrapper.emitted('changed')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('explains missing measurement prerequisites and avoids duplicate empty source status', async () => {
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({
|
||||
measurementId: null, items: [], sourceType: null, weightResults: [],
|
||||
}))
|
||||
const wrapper = await mountPanel()
|
||||
expect(wrapper.vm.maySubmit).toBe(false)
|
||||
expect(wrapper.vm.submissionBlockReason).toContain('请先完成箱规测量')
|
||||
expect(wrapper.vm.submissionBlockReason).toContain('称重图片')
|
||||
expect(wrapper.find('.measurement-summary-status').text()).not.toContain('尚未测量')
|
||||
expect(wrapper.find('.measurement-panel__footer [role="status"]').text()).toContain('请先完成箱规测量')
|
||||
})
|
||||
|
||||
it('explains order review and submitted-state blockers before measurement prerequisites', async () => {
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ processStatus: 'PREPARING', measurementId: null }))
|
||||
const pendingOrder = await mountPanel()
|
||||
expect(pendingOrder.vm.submissionBlockReason).toContain('订单尚待客服审核')
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ processStatus: 'MEASURE_PENDING' }))
|
||||
const pendingMeasurement = await mountPanel()
|
||||
expect(pendingMeasurement.vm.submissionBlockReason).toContain('不能提交测量审核')
|
||||
})
|
||||
|
||||
it('keeps dropship pallet edits in the platform section and declaration facts read-only', async () => {
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ fulfillmentType: 'ONE_CLICK_DROPSHIP', expectedPalletCount: 2 }))
|
||||
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)
|
||||
})
|
||||
|
||||
it('requires at least one confirmed weight image before submitting for review', async () => {
|
||||
const messageSpy = vi.spyOn(ElMessage, 'warning')
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({ weightResults: [] }))
|
||||
|
||||
@@ -119,6 +119,13 @@ const mayMaintain = computed(() => canEdit.value && countEditableProcess.value &
|
||||
const manualEntryEditableProcess = computed(() => ['PREPARING', 'WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'].includes(detail.value?.processStatus || ''))
|
||||
const mayCreateManualEntry = computed(() => canManualEntry.value && manualEntryEditableProcess.value && !loading.value && !saving.value && !submitting.value && !isDirty.value)
|
||||
const confirmedPackageCount = computed(() => form.items.reduce((sum, item) => sum + (Number.isFinite(item.packageCount) ? item.packageCount : 0), 0))
|
||||
// No scan rows means no measured count yet, rather than a measured zero.
|
||||
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³' },
|
||||
])
|
||||
|
||||
const expectedPackageCount = computed(() => detail.value?.originalPackageCount ?? null)
|
||||
const packageCountDifference = computed(() => expectedPackageCount.value == null ? null : confirmedPackageCount.value - expectedPackageCount.value)
|
||||
const packageCountMatched = computed(() => packageCountDifference.value === null || packageCountDifference.value === 0)
|
||||
@@ -127,7 +134,10 @@ const isDropship = computed(() => detail.value?.fulfillmentType === 'ONE_CLICK_D
|
||||
const expectedPalletCountValid = computed(() => !isDropship.value || (Number.isInteger(form.expectedPalletCount) && Number(form.expectedPalletCount) >= 1))
|
||||
const maySubmit = computed(() => canSubmit.value && submitEditableProcess.value && Boolean(form.measurementId) && packageCountMatched.value && hasConfirmedWeightResult.value && expectedPalletCountValid.value && !loading.value && !saving.value)
|
||||
const submissionBlockReason = computed(() => {
|
||||
if (!canSubmit.value || !submitEditableProcess.value || !form.measurementId) return ''
|
||||
if (!canSubmit.value || !detail.value || loading.value) return ''
|
||||
if (detail.value.processStatus === 'PREPARING') return '订单尚待客服审核,审核通过后才能提交测量审核'
|
||||
if (!submitEditableProcess.value) return `当前${processStatusLabel(detail.value.processStatus)},不能提交测量审核`
|
||||
if (!form.measurementId) return hasConfirmedWeightResult.value ? '请先完成箱规测量并保存测量数据' : '请先完成箱规测量,并上传确认至少一张称重图片'
|
||||
if (!hasConfirmedWeightResult.value) return '即使只有一箱,也必须上传并确认至少一张称重图片后才能提交审核'
|
||||
if (!packageCountMatched.value) return '确认箱数合计必须与订单总箱数一致后才能提交审核'
|
||||
if (!expectedPalletCountValid.value) return '请先填写并保存有效的预计托数后再提交审核'
|
||||
@@ -675,15 +685,6 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
|
||||
<el-alert v-else-if="!canEdit" type="info" :closable="false" show-icon title="当前账号仅可查看测量数据" />
|
||||
|
||||
<el-alert
|
||||
v-if="submissionBlockReason && !hasConfirmedWeightResult"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="measurement-alert"
|
||||
:title="submissionBlockReason"
|
||||
/>
|
||||
|
||||
<el-alert v-if="conflictDetected" type="warning" :closable="false" show-icon class="measurement-alert">
|
||||
<template #title>数据版本已变化,当前修改尚未丢失</template>
|
||||
<p>{{ operationError?.message }}</p>
|
||||
@@ -699,13 +700,14 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
<legend class="sr-only">测量草稿</legend>
|
||||
<section class="measurement-section measurement-summary-section">
|
||||
<header class="measurement-summary-header">
|
||||
<h4 class="measurement-subheading">申报与实测对比</h4>
|
||||
<div class="measurement-summary-context">
|
||||
<p><span>客户</span>{{ displayCustomerName }}</p>
|
||||
<p><span>渠道</span>{{ detail.channelName || detail.channelCode || '渠道未填写' }}</p>
|
||||
</div>
|
||||
<div class="measurement-summary-status">
|
||||
<el-tag effect="light">{{ processStatusLabel(detail.processStatus) }}</el-tag>
|
||||
<span>{{ sourceLabel }}</span>
|
||||
<span v-if="detail.sourceType && form.items.length">{{ sourceLabel }}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -720,57 +722,66 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
<p v-for="warning in measurementDeviationWarnings" :key="warning.key">{{ warning.text }}</p>
|
||||
</el-alert>
|
||||
|
||||
<h4 class="measurement-subheading">平台实测</h4>
|
||||
<dl class="measurement-metrics" aria-label="平台实测结果汇总">
|
||||
<div :class="{ 'measurement-metric--warning': !packageCountMatched }">
|
||||
<dt>实测箱数</dt>
|
||||
<dd>{{ formatNumber(confirmedPackageCount, 0) }} <small v-if="formatDifference(confirmedPackageCount, expectedPackageCount, 0)">/ {{ formatDifference(confirmedPackageCount, expectedPackageCount, 0) }}</small></dd>
|
||||
</div>
|
||||
<div><dt>实测毛重</dt><dd>{{ formatNumber(detail.actualGrossWeightKg) }} <small>kg<span v-if="formatDifference(detail.actualGrossWeightKg, detail.originalGrossWeightKg, 3, ' kg')"> · {{ formatDifference(detail.actualGrossWeightKg, detail.originalGrossWeightKg, 3, ' kg') }}</span></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³<span v-if="formatDifference(detail.actualVolumeCbm, detail.originalVolumeCbm, 4, ' m³')"> · {{ formatDifference(detail.actualVolumeCbm, detail.originalVolumeCbm, 4, ' m³') }}</span></small></dd></div>
|
||||
<div><dt>材积重</dt><dd>{{ formatNumber(detail.volumeWeightKg) }} <small>kg</small></dd></div>
|
||||
</dl>
|
||||
<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>
|
||||
<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 :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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="measurement-extra-facts">
|
||||
<span>货物净重 <strong>{{ formatNumber(detail.actualNetWeightKg) }}</strong> kg</span>
|
||||
<span>材积重 <strong>{{ formatNumber(detail.volumeWeightKg) }}</strong> kg</span>
|
||||
<el-tooltip content="净重、材积重为平台数据,不与客户申报毛重比较"><span tabindex="0">平台补充数据</span></el-tooltip>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="measurement-declaration" aria-labelledby="measurement-declaration-title">
|
||||
<h4 id="measurement-declaration-title">客户申报</h4>
|
||||
<div class="measurement-data-grid">
|
||||
<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>
|
||||
<label v-if="isDropship && mayMaintain"><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"><span>预计托数</span><strong>{{ detail.expectedPalletCount ?? '—' }}</strong></div>
|
||||
<div class="measurement-readonly-field"><span>原单据号码</span><strong>{{ detail.originalDocumentNo || '—' }}</strong></div>
|
||||
</div>
|
||||
<el-table
|
||||
v-if="detail.latestDeclaration?.items.length"
|
||||
:data="detail.latestDeclaration.items"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
class="measurement-declaration-table"
|
||||
aria-label="客户申报箱规明细"
|
||||
>
|
||||
<el-table-column prop="itemNo" label="#" width="70" />
|
||||
<el-table-column prop="cargoName" label="货物品名" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="packageCount" label="申报箱数" width="100" />
|
||||
<el-table-column label="申报尺寸 长×宽×高(cm)" min-width="190">
|
||||
<template #default="{ row }">{{ formatNumber(row.lengthCm) }} × {{ formatNumber(row.widthCm) }} × {{ formatNumber(row.heightCm) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
<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"
|
||||
:data="detail.latestDeclaration.items"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
class="measurement-declaration-table"
|
||||
aria-label="客户申报箱规明细"
|
||||
>
|
||||
<el-table-column prop="itemNo" label="#" width="70" />
|
||||
<el-table-column prop="cargoName" label="货物品名" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="packageCount" label="申报箱数" width="100" />
|
||||
<el-table-column label="申报尺寸 长×宽×高(cm)" min-width="190">
|
||||
<template #default="{ row }">{{ formatNumber(row.lengthCm) }} × {{ formatNumber(row.widthCm) }} × {{ formatNumber(row.heightCm) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="measurement-section measurement-items-section">
|
||||
<div class="measurement-section__heading">
|
||||
<div><h3>箱规明细</h3></div>
|
||||
<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>
|
||||
<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>
|
||||
<div class="measurement-section-actions">
|
||||
<el-button v-if="canManualEntry" type="primary" plain :disabled="!mayCreateManualEntry" @click="openManualEntry"><Plus :size="15" />手动录入箱规</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<el-table
|
||||
v-if="form.items.length"
|
||||
:data="form.items"
|
||||
@@ -854,7 +865,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
</form>
|
||||
|
||||
<footer v-if="canEdit || canSubmit" class="measurement-panel__footer">
|
||||
<span v-if="!hasConfirmedWeightResult"><AlertTriangle :size="15" />至少上传并确认一张称重图片后才能提交审核</span>
|
||||
<span v-if="submissionBlockReason" role="status"><AlertTriangle :size="15" />{{ submissionBlockReason }}</span>
|
||||
<span v-else-if="isDirty"><AlertTriangle :size="15" />有尚未保存的修改</span>
|
||||
<span v-else>所有修改已保存</span>
|
||||
<div>
|
||||
@@ -906,6 +917,14 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
</template>
|
||||
|
||||
<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 th, .measurement-comparison td { padding: var(--admin-space-3, 8px) var(--admin-space-5, 12px); border: 1px solid var(--cc-border); text-align: left; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
|
||||
.measurement-comparison thead th { background: #f8fafc; }
|
||||
.measurement-comparison thead th:nth-child(2) { background: #f7f9ff; color: #465fc4; }
|
||||
.measurement-comparison thead th:nth-child(3) { background: #f4fbf8; color: #18795d; }
|
||||
.measurement-comparison__difference { color: var(--el-color-warning-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(--cc-muted); font-size: var(--admin-font-caption, 12px); }
|
||||
.measurement-extra-facts strong { color: var(--cc-text); }
|
||||
.measurement-panel { overflow: hidden; }
|
||||
.measurement-panel__heading > div:first-child { display: flex; min-width: 0; align-items: flex-start; gap: 11px; }
|
||||
.measurement-panel__icon { display: grid; width: 34px; height: 34px; flex: 0 0 34px; place-items: center; border-radius: 8px; background: #eef4ff; color: var(--cc-primary); }
|
||||
@@ -914,6 +933,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
.measurement-panel h3 { font-size: var(--admin-font-section, 14px); }
|
||||
.measurement-panel__heading p, .measurement-section__heading p { margin: var(--admin-space-1, 4px) 0 0; color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); line-height: 1.55; }
|
||||
.measurement-panel__actions, .measurement-panel__footer > div { display: flex; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; gap: var(--admin-space-3, 8px); }
|
||||
.measurement-platform-fields { margin-bottom: var(--admin-space-5, 12px); }
|
||||
.measurement-declaration-table { margin-top: var(--admin-space-5, 12px); }
|
||||
.measurement-panel__actions :deep(.el-button), .measurement-panel__footer :deep(.el-button) { margin-left: 0; }
|
||||
.measurement-skeleton { padding: var(--admin-space-7, 24px); }
|
||||
@@ -931,15 +951,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
.measurement-deviation-alert p { margin: 3px 0 0; line-height: 1.55; }
|
||||
.measurement-summary-context p { display: inline-flex; min-width: 0; align-items: baseline; gap: var(--admin-space-2, 6px); margin: 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: var(--admin-font-caption, 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-subheading { margin: 0; color: #344054; font-size: var(--admin-font-caption, 12px); font-weight: 700; }
|
||||
.measurement-metrics > div { display: grid; min-width: 0; min-height: 58px; align-content: center; gap: var(--admin-space-1, 4px); padding: 5px 18px; border-left: 1px solid var(--cc-border); }
|
||||
.measurement-metrics > div:first-child { padding-left: 0; border-left: 0; }
|
||||
.measurement-metrics dt { color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); }
|
||||
.measurement-metrics dd { min-width: 0; margin: 0; color: var(--cc-text); font-size: 18px; font-weight: 700; overflow-wrap: anywhere; }
|
||||
.measurement-metrics small { color: var(--cc-muted); font-size: var(--admin-font-caption, 11px); font-weight: 500; }
|
||||
.measurement-metric--primary dd { color: var(--cc-primary); font-size: 20px; }
|
||||
.measurement-metric--warning dd { color: var(--el-color-warning-dark-2); }
|
||||
.measurement-declaration { padding-top: var(--admin-space-5, 12px); border-top: 1px solid var(--cc-border); }
|
||||
.measurement-declaration h4 { margin: 0 0 var(--admin-space-4, 10px); color: #344054; font-size: var(--admin-font-caption, 12px); font-weight: 700; }
|
||||
.measurement-section__heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: var(--admin-space-6, 16px); }
|
||||
@@ -947,7 +959,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
.measurement-section-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--admin-space-3, 8px); }.measurement-section-actions :deep(.el-button) { margin-left: 0; }
|
||||
.warehouse-suggestion-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--admin-space-6, 16px); }.warehouse-suggestion-form :deep(.el-select),.warehouse-suggestion-form :deep(.el-input-number) { width: 100%; }.warehouse-suggestion-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }.suggestion-preview { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: var(--admin-space-1, 4px) 0 0; border: 1px solid var(--cc-border); border-radius: 6px; }.suggestion-preview div { padding: var(--admin-space-5, 12px); border-right: 1px solid var(--cc-border); }.suggestion-preview div:last-child { border-right: 0; }.suggestion-preview dt { color: var(--cc-muted); font-size: var(--admin-font-caption, 11px); }.suggestion-preview dd { margin: 5px 0 0; color: var(--cc-text); font-size: var(--admin-font-body, 13px); font-weight: 650; }
|
||||
.measurement-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--admin-space-6, 16px); }
|
||||
.measurement-data-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--admin-space-5, 12px); }
|
||||
.measurement-data-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--admin-space-5, 12px); }
|
||||
.measurement-form label { display: flex; min-width: 0; flex-direction: column; gap: 7px; color: #344054; font-size: var(--admin-font-caption, 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: var(--admin-font-caption, 12px); font-weight: 600; }
|
||||
@@ -982,8 +994,6 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
|
||||
.measurement-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.measurement-data-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.measurement-row-details { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.measurement-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.measurement-metrics > div:nth-child(3) { padding-left: 0; border-left: 0; }
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
|
||||
@@ -1,15 +1,38 @@
|
||||
/* Measurement workspace: keep all sections inside the panel's content bounds. */
|
||||
.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 #cce8df; border-left: 4px solid #20a477; border-radius: 8px; background: #f4fbf8; }
|
||||
.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 p > span { flex-shrink: 0; }
|
||||
.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 #eadfc9; border-left: 4px solid #c79643; border-radius: 8px; background: #fffbf4; }
|
||||
|
||||
.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 #d9e2fa; border-left: 4px solid #647fec; border-radius: 8px; background: #f7f9ff; }
|
||||
|
||||
@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-section { padding: var(--admin-space-4, 10px) 0; }
|
||||
.inbound-density.admin-density-dialog .measurement-section { padding-block: var(--admin-space-4, 10px); }
|
||||
.inbound-density.admin-density-dialog .measurement-summary-section { gap: var(--admin-space-3, 8px); }
|
||||
.inbound-density.admin-density-dialog .measurement-section__heading { align-items: center; margin-bottom: var(--admin-space-3, 8px); }
|
||||
.inbound-density.admin-density-dialog .measurement-metrics { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||
.inbound-density.admin-density-dialog .measurement-metrics > div { min-height: var(--admin-row-height, 36px); padding: var(--admin-space-2, 6px) var(--admin-space-4, 10px); display: flex; align-items: baseline; gap: var(--admin-space-3, 8px); }
|
||||
.inbound-density.admin-density-dialog .measurement-metrics dd { font-size: var(--admin-font-section, 14px); }
|
||||
.inbound-density.admin-density-dialog .measurement-metrics small { font-size: var(--admin-font-caption, 12px); }
|
||||
.inbound-density.admin-density-dialog .measurement-data-grid { gap: var(--admin-space-3, 8px) var(--admin-space-5, 12px); }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-summary-header h4,
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-declaration-header h4 { flex-shrink: 0; margin: 0; }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-declaration-header { display: flex; align-items: baseline; gap: var(--admin-space-6, 16px); }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-document-number { display: flex; flex: 1; align-items: baseline; gap: var(--admin-space-3, 8px); }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-document-number > span { flex-shrink: 0; }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-document-number strong { min-width: 0; min-height: 0; padding: 0; }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-items-section .measurement-section__heading { flex-wrap: wrap; gap: var(--admin-space-3, 8px) var(--admin-space-6, 16px); }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-platform-fields { display: flex; flex: 1 1 520px; align-items: center; flex-wrap: wrap; margin: 0; gap: var(--admin-space-5, 12px); }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-platform-fields > label,
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-platform-fields > .measurement-readonly-field { display: flex; flex-direction: row; align-items: center; gap: var(--admin-space-3, 8px); }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-platform-fields > .measurement-goods-name { flex: 1 1 340px; }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-platform-fields > .measurement-expected-pallets { flex: 0 0 180px; }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-platform-fields > * > span { flex-shrink: 0; }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-platform-fields .el-input,
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-platform-fields .el-input-number { min-width: 0; flex: 1; width: auto; }
|
||||
.inbound-measurement-workspace-dialog.inbound-density .measurement-section-actions { margin-left: auto; }
|
||||
.inbound-density.admin-density-dialog .measurement-declaration { padding-top: var(--admin-space-3, 8px); }
|
||||
.inbound-density.admin-density-dialog .measurement-declaration h4 { margin-bottom: var(--admin-space-2, 6px); font-size: var(--admin-font-body, 13px); }
|
||||
.inbound-density.admin-density-dialog .measurement-declaration h4 { font-size: var(--admin-font-body, 13px); }
|
||||
.inbound-density.admin-density-dialog .measurement-panel > .el-alert { margin: var(--admin-space-3, 8px) 0; }
|
||||
.inbound-density.admin-density-dialog .measurement-summary-header span { font-size: var(--admin-font-caption, 12px); }
|
||||
.inbound-density.admin-density-dialog .measurement-table .el-table__header th { padding: var(--admin-space-1, 4px) 0; font-size: var(--admin-font-body, 13px); }
|
||||
|
||||
Reference in New Issue
Block a user