优化
This commit is contained in:
@@ -273,7 +273,7 @@ defineExpose({ reload: loadFee })
|
||||
</el-alert>
|
||||
|
||||
<el-empty v-else-if="notFound" :image-size="64" description="尚未生成费用快照">
|
||||
<p class="empty-note">测量审核完成后,系统会自动生成并发布费用。</p>
|
||||
<p class="empty-note">测量审核完成后,系统会自动生成初算费用;人工确认后才发布。</p>
|
||||
</el-empty>
|
||||
|
||||
<div v-else-if="snapshot" class="fee-content">
|
||||
|
||||
@@ -2,5 +2,6 @@ export { getInboundMeasurement } from './measurement/api'
|
||||
export { default as InboundMeasurementPanel } from './measurement/InboundMeasurementPanel.vue'
|
||||
export { default as InboundMeasurementDialog } from './measurement/InboundMeasurementDialog.vue'
|
||||
export { default as MeasurementEvidenceThumbnail } from './measurement/components/MeasurementEvidenceThumbnail.vue'
|
||||
export { default as InboundFeePanel } from './fee/InboundFeePanel.vue'
|
||||
export { downloadWeightImage } from './weight/api/weightOcr'
|
||||
export type { InboundMeasurementDetail } from './measurement/types'
|
||||
|
||||
@@ -14,6 +14,9 @@ describe('InboundMeasurementDialog', () => {
|
||||
expect(source).toContain('height: 92vh')
|
||||
expect(source).toContain('<WeightOcrPanel')
|
||||
expect(source).toContain('<InboundMeasurementPanel')
|
||||
expect(source).toContain('class="measurement-dialog-weight"')
|
||||
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)')
|
||||
})
|
||||
|
||||
@@ -72,7 +72,7 @@ function handleSubmitted() {
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<div class="measurement-dialog-workspace">
|
||||
<section class="surface-panel measurement-dialog-weight" aria-label="称重图片、OCR 与托盘核对">
|
||||
<section class="measurement-dialog-weight" aria-label="称重图片、OCR 与托盘核对">
|
||||
<WeightOcrPanel
|
||||
:customer-id="customerId"
|
||||
:inbound-id="inboundId"
|
||||
@@ -113,12 +113,12 @@ function handleSubmitted() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.measurement-dialog-workspace { display: grid; min-width: 0; gap: 20px; }
|
||||
.measurement-dialog-weight { min-width: 0; padding: 20px; }
|
||||
.measurement-dialog-workspace { display: grid; min-width: 0; }
|
||||
.measurement-dialog-weight { min-width: 0; padding: 0 0 16px; border-bottom: 1px solid var(--cc-border); }
|
||||
.measurement-dialog-footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
|
||||
.measurement-dialog-footer :deep(.el-button) { margin-left: 0; }
|
||||
@media (max-width: 720px) {
|
||||
.measurement-dialog-weight { padding: 14px; }
|
||||
.measurement-dialog-weight { padding-bottom: 14px; }
|
||||
.measurement-dialog-footer { display: grid; grid-template-columns: 1fr; }
|
||||
.measurement-dialog-footer :deep(.el-button) { width: 100%; min-height: 44px; }
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ describe('InboundMeasurementPanel', () => {
|
||||
expect(wrapper.text()).not.toContain('LEGACY_DEVICE')
|
||||
})
|
||||
|
||||
it('keeps the first view focused on key totals and moves secondary fields behind disclosure', async () => {
|
||||
it('keeps key totals compact and shows declaration information without another disclosure card', async () => {
|
||||
const wrapper = await mountPanel()
|
||||
const text = wrapper.text()
|
||||
|
||||
@@ -210,12 +210,13 @@ describe('InboundMeasurementPanel', () => {
|
||||
expect(wrapper.find('.measurement-item').exists()).toBe(false)
|
||||
expect(wrapper.find('.measurement-table').text()).not.toContain('周长')
|
||||
const source = await import('./InboundMeasurementPanel.vue?raw').then((module) => module.default)
|
||||
expect(source).toContain('<el-collapse-item name="declaration" title="申报信息">')
|
||||
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-form-grid, .measurement-data-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));')
|
||||
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 })')
|
||||
expect(source).toContain('defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain, maySubmit, saving, submitting, canEdit, canSubmit, isDirty })')
|
||||
})
|
||||
|
||||
it('updates the server weight summary without discarding unsaved measurement edits', async () => {
|
||||
|
||||
@@ -544,17 +544,16 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
|
||||
<div><dt>材积重</dt><dd>{{ formatNumber(detail.volumeWeightKg) }} <small>kg</small></dd></div>
|
||||
</dl>
|
||||
|
||||
<el-collapse class="measurement-more">
|
||||
<el-collapse-item name="declaration" title="申报信息">
|
||||
<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>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<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>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="measurement-section measurement-items-section">
|
||||
@@ -694,31 +693,28 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
|
||||
.measurement-alert ul { margin: 7px 0 0; padding-left: 18px; font-size: 12px; line-height: 1.65; }
|
||||
.measurement-alert .el-button { margin-top: 9px; }
|
||||
.measurement-form fieldset { min-width: 0; margin: 0; padding: 0; border: 0; }
|
||||
.measurement-section { padding: 18px 20px; border-top: 1px solid var(--cc-border); }
|
||||
.measurement-summary-section { display: grid; gap: 16px; border-top: 0; }
|
||||
.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-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-status { display: flex; flex: 0 0 auto; align-items: center; gap: 9px; }
|
||||
.measurement-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); overflow: hidden; border: 1px solid var(--cc-border); border-radius: 6px; background: #fff; }
|
||||
.measurement-metrics > div { display: grid; min-width: 0; min-height: 78px; align-content: center; gap: 6px; padding: 13px 16px; border-right: 1px solid var(--cc-border); }
|
||||
.measurement-metrics > div:last-child { border-right: 0; }
|
||||
.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); }
|
||||
.measurement-metrics > div:first-child { padding-left: 0; border-left: 0; }
|
||||
.measurement-metrics dt { color: var(--cc-muted); font-size: 12px; }
|
||||
.measurement-metrics dd { min-width: 0; margin: 0; color: var(--cc-text); font-size: 19px; font-weight: 700; overflow-wrap: anywhere; }
|
||||
.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: 11px; font-weight: 500; }
|
||||
.measurement-metric--primary { background: #f0f7ff; }
|
||||
.measurement-metric--primary dd { color: var(--cc-primary); font-size: 23px; }
|
||||
.measurement-metric--warning { background: var(--el-color-warning-light-9); }
|
||||
.measurement-metric--primary dd { color: var(--cc-primary); font-size: 20px; }
|
||||
.measurement-metric--warning dd { color: var(--el-color-warning-dark-2); }
|
||||
.measurement-more { border-top: 0; border-bottom: 0; }
|
||||
.measurement-more :deep(.el-collapse-item__header) { height: 38px; color: var(--cc-muted); font-size: 12px; }
|
||||
.measurement-more :deep(.el-collapse-item__wrap) { border-bottom: 0; }
|
||||
.measurement-more :deep(.el-collapse-item__content) { padding: 4px 0 2px; }
|
||||
.measurement-declaration { padding-top: 12px; border-top: 1px solid var(--cc-border); }
|
||||
.measurement-declaration h4 { margin: 0 0 10px; color: #344054; font-size: 12px; font-weight: 700; }
|
||||
.measurement-section__heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 16px; }
|
||||
.measurement-section__heading > div { min-width: 0; }
|
||||
.measurement-form-grid, .measurement-data-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 24px; }
|
||||
.measurement-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 24px; }
|
||||
.measurement-data-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
|
||||
.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; }
|
||||
@@ -747,10 +743,10 @@ defineExpose({ saveDraft, submitForReview, updateActualGrossWeight, mayMaintain,
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.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(2) { border-right: 0; }
|
||||
.measurement-metrics > div:nth-child(-n+2) { border-bottom: 1px solid var(--cc-border); }
|
||||
.measurement-metrics > div:nth-child(3) { padding-left: 0; border-left: 0; }
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
|
||||
@@ -160,6 +160,7 @@ describe('WeightOcrPanel confirmation behavior', () => {
|
||||
const wrapper = mountPanel()
|
||||
await flushPromises()
|
||||
await wrapper.find('.pallet-picker .el-select').trigger('click')
|
||||
expect(document.body.textContent).toContain('木托盘 · 皮重 12.000 KG · 容量 1.2 m³')
|
||||
await new DOMWrapper(document.body).find('.el-select-dropdown__item').trigger('click')
|
||||
const input = wrapper.get('input[type="file"]')
|
||||
const file = new File(['image'], 'weight.jpg', { type: 'image/jpeg' })
|
||||
|
||||
@@ -53,6 +53,13 @@ describe('WeightOcrPanel contract', () => {
|
||||
expect(source).toContain("option.palletType || '未记录类型'")
|
||||
})
|
||||
|
||||
it('keeps pallet type, tare weight, and capacity in one select line', () => {
|
||||
expect(source).toContain(':label="palletOptionLabel(option)"')
|
||||
expect(source).toContain("`${option.palletType || '未记录类型'} · 皮重 ${formatWeight(option.tareWeightKg)} · 容量 ${option.capacityCbm} m³`")
|
||||
expect(source).not.toContain('<small v-if="selectedPallet">')
|
||||
expect(source).toContain('width: clamp(360px, 36vw, 520px)')
|
||||
})
|
||||
|
||||
it('gates every action with the implemented permissions and editable statuses', () => {
|
||||
expect(source).toContain("platform:firstleg:inbound:measure:view")
|
||||
expect(source).toContain("platform:firstleg:inbound:weight:edit")
|
||||
|
||||
@@ -61,7 +61,6 @@ const canConfirm = computed(() => access.can('platform:firstleg:inbound:weight:c
|
||||
const canView = computed(() => access.can('platform:firstleg:inbound:measure:view'))
|
||||
const editable = computed(() => Boolean(workspace.value && editableStatuses.has(workspace.value.processStatus)))
|
||||
const rows = computed(() => workspace.value?.weightResults ?? [])
|
||||
const selectedPallet = computed(() => palletOptions.value.find((option) => option.palletId === selectedPalletId.value) ?? null)
|
||||
const previewResult = computed(() => rows.value.find((row) => row.id === previewResultId.value)
|
||||
|| previewSnapshot.value)
|
||||
const previewConfirmDisabled = computed(() => {
|
||||
@@ -75,6 +74,10 @@ function formatWeight(value: number | null, unit = 'KG') {
|
||||
return value == null ? '—' : `${Number(value).toFixed(3)} ${unit}`
|
||||
}
|
||||
|
||||
function palletOptionLabel(option: WeightPalletOption) {
|
||||
return `${option.palletType || '未记录类型'} · 皮重 ${formatWeight(option.tareWeightKg)} · 容量 ${option.capacityCbm} m³`
|
||||
}
|
||||
|
||||
function actionKey(action: string, id: string) {
|
||||
return `${action}:${id}`
|
||||
}
|
||||
@@ -400,11 +403,10 @@ defineExpose({ reload: loadWorkspace })
|
||||
<el-option
|
||||
v-for="option in palletOptions"
|
||||
:key="option.palletId"
|
||||
:label="option.palletType || '未记录类型'"
|
||||
:label="palletOptionLabel(option)"
|
||||
:value="option.palletId"
|
||||
/>
|
||||
</el-select>
|
||||
<small v-if="selectedPallet">皮重 {{ formatWeight(selectedPallet.tareWeightKg) }} · 容量 {{ selectedPallet.capacityCbm }} m³</small>
|
||||
</div>
|
||||
<input
|
||||
ref="fileInput"
|
||||
@@ -570,15 +572,15 @@ defineExpose({ reload: loadWorkspace })
|
||||
.weight-heading h3 { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 15px; }
|
||||
.weight-heading h3 svg { color: var(--cc-primary); }
|
||||
.weight-heading__actions { display: flex; flex: 0 0 auto; align-items: center; gap: 16px; }
|
||||
.pallet-picker { display: grid; min-width: 260px; gap: 4px; }
|
||||
.pallet-picker small { color: var(--cc-muted); font-size: 11px; line-height: 1.4; }
|
||||
.pallet-picker { width: clamp(360px, 36vw, 520px); min-width: 0; }
|
||||
.pallet-picker :deep(.el-select) { width: 100%; }
|
||||
.weight-summary { display: grid; gap: 2px; text-align: right; }
|
||||
.weight-summary span { color: var(--cc-muted); font-size: 12px; }
|
||||
.weight-summary strong { color: #067647; font-size: 18px; }
|
||||
.visually-hidden-input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
|
||||
.weight-state-note, .weight-action-error { margin-bottom: 14px; }
|
||||
.weight-list { min-height: 0; margin-top: 14px; }
|
||||
.weight-empty { display: flex; min-height: 0; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 4px; background: var(--cc-surface-muted); color: var(--cc-muted); }
|
||||
.weight-empty { display: flex; min-height: 0; align-items: center; gap: 10px; padding: 4px 0 0; color: var(--cc-muted); }
|
||||
.weight-empty strong { color: #344054; font-size: 14px; }
|
||||
.weight-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 14px; }
|
||||
.weight-card { position: relative; min-width: 0; overflow: hidden; border: 1px solid var(--cc-border); border-radius: 6px; background: #fff; }
|
||||
|
||||
Reference in New Issue
Block a user