This commit is contained in:
2026-10-20 23:34:11 +08:00
parent f155976f52
commit 13e4aa56ec
321 changed files with 6756 additions and 2110 deletions
@@ -13,7 +13,7 @@ describe('InboundMeasurementDialog', () => {
it('owns the complete measurement workspace behind one centered dialog', () => {
expect(source).toContain("import AppDialog from '@/shared/components/AppDialog.vue'")
expect(source).toContain('<AppDialog')
expect(source).toContain('class="inbound-measurement-workspace-dialog admin-density-dialog admin-density-workspace inbound-density"')
expect(source).toContain('class="inbound-measurement-workspace-dialog admin-density-dialog admin-density-workspace inbound-density admin-workbench is-dialog"')
expect(source).toContain('width="min(1560px, calc(100vw - 48px))"')
expect(source).toContain('height: 92vh')
expect(densitySource).toContain('.admin-density-dialog.el-dialog .el-dialog__header')
@@ -24,7 +24,7 @@ describe('InboundMeasurementDialog', () => {
expect(source).toContain(':customer-name="customerName"')
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('border-bottom: 1px solid var(--workbench-border)')
expect(source).toContain('@summary-changed="handleWeightSummaryChanged"')
expect(source).toContain('measurementPanelRef.value?.updateWeightEvidence(actualGrossWeightKg, actualNetWeightKg, hasConfirmedWeight)')
expect(source).toContain('measurementPanelRef?.submissionBlockReason')
@@ -1,5 +1,6 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import '@/styles/adminWorkbench.css'
import './measurementDensity.css'
import { computed, ref } from 'vue'
import { ElMessageBox } from 'element-plus'
@@ -69,7 +70,7 @@ function handleSubmitted() {
<template>
<AppDialog
:model-value="modelValue"
class="inbound-measurement-workspace-dialog admin-density-dialog admin-density-workspace inbound-density"
class="inbound-measurement-workspace-dialog admin-density-dialog admin-density-workspace inbound-density admin-workbench is-dialog"
:title="title"
width="min(1560px, calc(100vw - 48px))"
top="4vh"
@@ -130,7 +131,7 @@ function handleSubmitted() {
<style scoped>
.measurement-dialog-workspace { display: grid; min-width: 0; }
.measurement-dialog-weight { min-width: 0; padding: 0 0 var(--admin-space-6, 16px); border-bottom: 1px solid var(--cc-border); }
.measurement-dialog-weight { min-width: 0; padding: 0 0 var(--admin-space-6, 16px); border-bottom: 1px solid var(--workbench-border); }
.measurement-dialog-footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--admin-space-4, 10px); }
.measurement-dialog-footer__hint { margin-right: auto; color: var(--el-color-warning-dark-2); font-size: var(--admin-font-caption, 12px); }
.measurement-dialog-footer :deep(.el-button) { margin-left: 0; }
@@ -1,5 +1,6 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import '@/styles/adminWorkbench.css'
import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
import { AlertTriangle, Image, Plus, Save, Send, Upload } from '@lucide/vue'
@@ -643,7 +644,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</script>
<template>
<section class="surface-panel measurement-panel" aria-label="入库测量草稿">
<section class="surface-panel measurement-panel admin-workbench" aria-label="入库测量草稿">
<el-empty v-if="!hasContext" description="请选择所属企业和入库单后维护测量数据" />
<LoadErrorState
@@ -722,7 +723,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<p v-for="warning in measurementDeviationWarnings" :key="warning.key">{{ warning.text }}</p>
</el-alert>
<table class="measurement-comparison" aria-label="客户申报与平台实测对比">
<div class="workbench-evidence"><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">
@@ -734,7 +735,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</td>
</tr>
</tbody>
</table>
</table></div>
<div class="measurement-extra-facts">
<span>货物净重 <strong>{{ formatNumber(detail.actualNetWeightKg) }}</strong> kg</span>
<span>材积重 <strong>{{ formatNumber(detail.volumeWeightKg) }}</strong> kg</span>
@@ -874,7 +875,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</div>
</footer>
<AppDialog class="admin-density-dialog" v-model="imagePreviewOpen" :title="detail?.inboundCode ? `当前有效测量图片 · ${detail.inboundCode}` : '当前有效测量图片'" width="min(720px, 92vw)" fullscreenable :show-footer="false" :close-on-click-modal="true" destroy-on-close append-to-body @closed="releaseImagePreview">
<AppDialog class="admin-density-dialog admin-workbench is-dialog" v-model="imagePreviewOpen" :title="detail?.inboundCode ? `当前有效测量图片 · ${detail.inboundCode}` : '当前有效测量图片'" width="min(720px, 92vw)" fullscreenable :show-footer="false" :close-on-click-modal="true" destroy-on-close append-to-body @closed="releaseImagePreview">
<div class="measurement-image-preview">
<el-skeleton v-if="imagePreviewLoading" :rows="5" animated />
<LoadErrorState v-else-if="imagePreviewError" title="图片加载失败" :message="imagePreviewError.message" :show-retry="false">
@@ -885,12 +886,12 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</div>
</AppDialog>
<el-dialog class="admin-density-dialog" v-model="manualEntryOpen" :title="detail?.inboundCode ? `手动录入箱规 · ${detail.inboundCode}` : '手动录入箱规'" width="min(1000px, 92vw)" destroy-on-close append-to-body>
<el-dialog class="admin-density-dialog admin-workbench is-dialog" v-model="manualEntryOpen" :title="detail?.inboundCode ? `手动录入箱规 · ${detail.inboundCode}` : '手动录入箱规'" width="min(1000px, 92vw)" destroy-on-close append-to-body>
<el-alert v-if="manualEntryErrors.length" type="error" :closable="false" show-icon class="manual-entry-alert" title="请修正以下问题">
<ul><li v-for="error in manualEntryErrors" :key="error">{{ error }}</li></ul>
</el-alert>
<el-form class="manual-entry-form" @submit.prevent="submitManualEntry">
<table class="density-table density-table--editable manual-entry-table" aria-label="手动录入箱规">
<div class="workbench-evidence"><table class="density-table density-table--editable manual-entry-table" aria-label="手动录入箱规">
<colgroup><col class="manual-entry-label-column" /><col /><col class="manual-entry-label-column" /><col /></colgroup>
<tbody>
<tr>
@@ -916,7 +917,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</el-upload>
</td></tr>
</tbody>
</table>
</table></div>
</el-form>
<template #footer>
<el-button :disabled="manualEntrySubmitting" @click="manualEntryOpen = false">取消</el-button>
@@ -929,20 +930,20 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
<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 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: #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-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(--cc-muted); font-size: var(--admin-font-caption, 12px); }
.measurement-extra-facts strong { color: var(--cc-text); }
.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-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); }
.measurement-panel h2, .measurement-panel h3 { margin: 0; color: var(--cc-text); }
.measurement-panel__icon { display: grid; width: 34px; height: 34px; flex: 0 0 34px; place-items: center; border-radius: 8px; background: #eef4ff; color: var(--workbench-primary); }
.measurement-panel h2, .measurement-panel h3 { margin: 0; color: var(--workbench-text); }
.measurement-panel h2 { font-size: var(--admin-font-title, 16px); }
.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__heading p, .measurement-section__heading p { margin: var(--admin-space-1, 4px) 0 0; color: var(--workbench-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); }
@@ -953,22 +954,22 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
.measurement-alert ul { margin: 7px 0 0; padding-left: 18px; font-size: var(--admin-font-caption, 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: var(--admin-space-6, 16px) 0px; border-top: 1px solid var(--cc-border); }
.measurement-section { padding: var(--admin-space-6, 16px) 0px; border-top: 1px solid var(--workbench-border); }
.measurement-summary-section { display: grid; gap: var(--admin-space-5, 12px); border-top: 0; }
.measurement-summary-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
.measurement-summary-context { display: flex; min-width: 0; flex-wrap: wrap; align-items: center; gap: var(--admin-space-3, 8px) 20px; }
.measurement-summary-header span { color: var(--cc-muted); font-size: var(--admin-font-caption, 11px); }
.measurement-summary-header span { color: var(--workbench-muted); font-size: var(--admin-font-caption, 11px); }
.measurement-deviation-alert { --el-color-warning: var(--el-color-danger-dark-2); --el-color-warning-dark-2: var(--el-color-danger-dark-2); margin: 0; width: auto; }
.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-context p { display: inline-flex; min-width: 0; align-items: baseline; gap: var(--admin-space-2, 6px); margin: 0; overflow-wrap: anywhere; color: var(--workbench-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-subheading { margin: 0; color: #344054; font-size: var(--admin-font-caption, 12px); font-weight: 700; }
.measurement-declaration { padding-top: var(--admin-space-5, 12px); border-top: 1px solid var(--cc-border); }
.measurement-declaration { padding-top: var(--admin-space-5, 12px); border-top: 1px solid var(--workbench-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); }
.measurement-section__heading > div { min-width: 0; }
.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; }
.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(--workbench-border); border-radius: 6px; }.suggestion-preview div { padding: var(--admin-space-5, 12px); border-right: 1px solid var(--workbench-border); }.suggestion-preview div:last-child { border-right: 0; }.suggestion-preview dt { color: var(--workbench-muted); font-size: var(--admin-font-caption, 11px); }.suggestion-preview dd { margin: 5px 0 0; color: var(--workbench-text); font-size: var(--admin-font-body, 13px); font-weight: 650; }
.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%; }
@@ -976,7 +977,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
.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: var(--admin-font-caption, 12px); font-weight: 600; }
.measurement-readonly-field strong { min-height: var(--admin-control-height, 32px); padding: 7px 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: var(--admin-font-body, 13px); line-height: 18px; }
.measurement-readonly-field strong { min-height: var(--admin-control-height, 32px); padding: 7px 0; overflow-wrap: anywhere; color: var(--workbench-text); font-size: var(--admin-font-body, 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: var(--admin-space-6, 16px); }
@@ -990,14 +991,14 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
.measurement-table :deep(.el-table__cell) { padding: 7px 0; }
.measurement-table :deep(.cell) { line-height: 1.35; }
.measurement-table :deep(.measurement-table__row--error > td.el-table__cell) { background: var(--el-color-danger-light-9); }
.warehouse-suggestion-table { display: grid; gap: var(--admin-space-4, 10px); margin-top: 18px; }.warehouse-suggestion-table h3 { margin: 0; color: var(--cc-text); font-size: var(--admin-font-section, 14px); }
.warehouse-suggestion-table { display: grid; gap: var(--admin-space-4, 10px); margin-top: 18px; }.warehouse-suggestion-table h3 { margin: 0; color: var(--workbench-text); font-size: var(--admin-font-section, 14px); }
.measurement-box-code { font-weight: 650; }
.measurement-count-editor { width: 104px !important; }
.measurement-row-details { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--admin-space-3, 8px) 20px; margin: 0; padding: var(--admin-space-1, 4px) 18px var(--admin-space-3, 8px) 62px; }
.measurement-row-details dt { color: var(--cc-muted); font-size: var(--admin-font-caption, 11px); }
.measurement-row-details dd { margin: 3px 0 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: var(--admin-font-caption, 12px); font-weight: 600; }
.measurement-panel__footer { position: sticky; bottom: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-6, 16px); padding: var(--admin-space-5, 12px) 20px; border-top: 1px solid var(--cc-border); background: var(--admin-surface, #fff); }
.measurement-panel__footer > span { display: inline-flex; align-items: center; gap: var(--admin-space-2, 6px); color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); }
.measurement-row-details dt { color: var(--workbench-muted); font-size: var(--admin-font-caption, 11px); }
.measurement-row-details dd { margin: 3px 0 0; overflow-wrap: anywhere; color: var(--workbench-text); font-size: var(--admin-font-caption, 12px); font-weight: 600; }
.measurement-panel__footer { position: sticky; bottom: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-6, 16px); padding: var(--admin-space-5, 12px) 20px; border-top: 1px solid var(--workbench-border); background: var(--admin-surface, #fff); }
.measurement-panel__footer > span { display: inline-flex; align-items: center; gap: var(--admin-space-2, 6px); color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); }
.measurement-panel__footer > span:has(svg) { color: #b54708; font-weight: 650; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
@@ -1,4 +1,6 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import '@/styles/adminWorkbench.css'
import { onBeforeUnmount, ref, watch } from 'vue'
import { ImageOff } from '@lucide/vue'
import { downloadWeightThumbnail } from '../../weight/api/weightOcr'
@@ -55,7 +57,7 @@ onBeforeUnmount(() => {
<template>
<button
class="measurement-evidence-thumb"
class="measurement-evidence-thumb admin-workbench"
type="button"
:disabled="!fileId"
:aria-label="fileId ? `查看${alt}` : emptyText"
@@ -70,10 +72,10 @@ onBeforeUnmount(() => {
</template>
<style scoped>
.measurement-evidence-thumb { display: block; width: 100%; min-width: 96px; aspect-ratio: 4 / 3; overflow: hidden; padding: 0; border: 1px solid var(--cc-border); border-radius: 6px; background: #f2f4f7; cursor: zoom-in; }
.measurement-evidence-thumb { display: block; width: 100%; min-width: 96px; aspect-ratio: 4 / 3; overflow: hidden; padding: 0; border: 1px solid var(--workbench-border); border-radius: 6px; background: var(--workbench-surface-soft); cursor: zoom-in; }
.measurement-evidence-thumb:disabled { cursor: default; }
.measurement-evidence-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.measurement-evidence-thumb__state { display: grid; width: 100%; height: 100%; place-items: center; align-content: center; gap: 7px; padding: 8px; color: #667085; font-size: 12px; }
.measurement-evidence-thumb__state.is-loading { color: #98a2b3; }
.measurement-evidence-thumb:focus-visible { outline: 2px solid var(--cc-primary); outline-offset: 2px; }
.measurement-evidence-thumb__state { display: grid; width: 100%; height: 100%; place-items: center; align-content: center; gap: 7px; padding: 8px; color: var(--workbench-muted); font-size: 12px; }
.measurement-evidence-thumb__state.is-loading { color: var(--workbench-muted); }
.measurement-evidence-thumb:focus-visible { outline: 2px solid var(--workbench-primary); outline-offset: 2px; }
</style>