This commit is contained in:
2026-09-14 15:33:35 +08:00
parent 23d956ba15
commit b0de9c026f
30 changed files with 270 additions and 240 deletions
@@ -1,4 +1,6 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import '../measurement/measurementDensity.css'
import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
@@ -326,7 +328,7 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
</script>
<template>
<AdminPage as="div" class="inbound-task-page">
<AdminPage as="div" class="inbound-task-page admin-density">
<ListPageLayout :aria-label="`${taskLabel}待办`">
<template #toolbar>
<QueryToolbar
@@ -408,13 +410,13 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
v-if="isReview"
v-model="reviewDialogOpen"
:title="selectedInbound ? `${taskLabel} · ${selectedInbound.inboundCode || '入仓码待补充'}` : taskLabel"
width="min(1180px, calc(100vw - 32px))"
width="min(1560px, calc(100vw - 48px))"
top="4vh"
destroy-on-close
append-to-body
:close-on-click-modal="!reviewNavigationLoading"
:close-on-press-escape="!reviewNavigationLoading"
class="inbound-task-dialog"
class="inbound-task-dialog admin-density-dialog admin-density-workspace inbound-density"
@closed="clearSelectedInbound"
>
<el-alert v-if="reviewNavigationError" type="error" :closable="false" show-icon class="review-navigation-error">
@@ -466,8 +468,8 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
</style>
<style>
.inbound-task-dialog { max-height: 92vh; overflow: hidden; }
.inbound-task-dialog .el-dialog__body { max-height: calc(92vh - 62px); overflow: auto; padding: 20px 24px 24px; }
.inbound-task-dialog.admin-density-workspace.el-dialog:not(.is-fullscreen) { height: auto; max-height: 92vh; overflow: hidden; }
.inbound-task-dialog .el-dialog__body { max-height: calc(92vh - 62px); overflow: auto; padding: 12px 16px; }
@media (max-width: 520px) {
.inbound-task-dialog .el-dialog__body { padding: 14px; }
}
@@ -11,8 +11,8 @@ 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"')
expect(source).toContain('width="min(1240px, calc(100vw - 32px))"')
expect(source).toContain('class="inbound-measurement-workspace-dialog admin-density-dialog admin-density-workspace inbound-density"')
expect(source).toContain('width="min(1560px, calc(100vw - 48px))"')
expect(source).toContain('height: 92vh')
expect(source).toContain('padding: 10px 20px')
expect(source).toContain('<WeightOcrPanel')
@@ -1,4 +1,6 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import './measurementDensity.css'
import { computed, ref } from 'vue'
import { ElMessageBox } from 'element-plus'
import AppDialog from '@/shared/components/AppDialog.vue'
@@ -66,9 +68,9 @@ function handleSubmitted() {
<template>
<AppDialog
:model-value="modelValue"
class="inbound-measurement-workspace-dialog"
class="inbound-measurement-workspace-dialog admin-density-dialog admin-density-workspace inbound-density"
:title="title"
width="min(1240px, calc(100vw - 32px))"
width="min(1560px, calc(100vw - 48px))"
top="4vh"
fullscreenable
destroy-on-close
@@ -139,8 +141,8 @@ function handleSubmitted() {
<style>
.inbound-measurement-workspace-dialog.el-dialog { display: flex; max-width: calc(100vw - 32px); height: 92vh; max-height: 92vh; margin-bottom: 0; flex-direction: column; overflow: hidden; }
.inbound-measurement-workspace-dialog .el-dialog__header { flex: 0 0 auto; margin: 0; padding: 10px 20px; border-bottom: 1px solid var(--cc-border); }
.inbound-measurement-workspace-dialog .el-dialog__body { min-height: 0; flex: 1 1 auto; overflow-y: auto; padding: 20px 24px; }
.inbound-measurement-workspace-dialog .el-dialog__footer { flex: 0 0 auto; padding: 14px 24px; border-top: 1px solid var(--cc-border); }
.inbound-measurement-workspace-dialog .el-dialog__body { min-height: 0; flex: 1 1 auto; overflow-y: auto; padding: 12px 16px; }
.inbound-measurement-workspace-dialog .el-dialog__footer { flex: 0 0 auto; padding: 8px 16px; border-top: 1px solid var(--cc-border); }
.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; }
@@ -1,4 +1,5 @@
<script setup lang="ts">
import '@/styles/adminDensity.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'
@@ -860,7 +861,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</div>
</footer>
<el-dialog v-model="imagePreviewOpen" title="当前有效测量图片" width="min(720px, 92vw)" destroy-on-close @closed="releaseImagePreview">
<el-dialog class="admin-density-dialog" v-model="imagePreviewOpen" title="当前有效测量图片" width="min(720px, 92vw)" destroy-on-close @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">
@@ -871,7 +872,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</div>
</el-dialog>
<el-dialog v-model="manualEntryOpen" title="手动录入箱规" width="min(680px, 92vw)" destroy-on-close>
<el-dialog class="admin-density-dialog" v-model="manualEntryOpen" title="手动录入箱规" width="min(680px, 92vw)" destroy-on-close>
<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>
@@ -0,0 +1,30 @@
@media (min-width: 961px) {
.inbound-density.admin-density-dialog .measurement-dialog-weight { padding-bottom: 10px; }
.inbound-density.admin-density-dialog .measurement-section { padding: 10px 0; }
.inbound-density.admin-density-dialog .measurement-summary-section { gap: 8px; }
.inbound-density.admin-density-dialog .measurement-section__heading { align-items: center; margin-bottom: 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: 36px; padding: 6px 10px; display: flex; align-items: baseline; gap: 8px; }
.inbound-density.admin-density-dialog .measurement-metrics dd { font-size: 14px; }
.inbound-density.admin-density-dialog .measurement-metrics small { font-size: 12px; }
.inbound-density.admin-density-dialog .measurement-data-grid { gap: 8px 12px; }
.inbound-density.admin-density-dialog .measurement-declaration { padding-top: 8px; }
.inbound-density.admin-density-dialog .measurement-declaration h4 { margin-bottom: 6px; font-size: 13px; }
.inbound-density.admin-density-dialog .measurement-panel > .el-alert { margin: 8px 0; }
.inbound-density.admin-density-dialog .measurement-summary-header span { font-size: 12px; }
.inbound-density.admin-density-dialog .measurement-table .el-table__header th { padding: 4px 0; font-size: 13px; }
.inbound-density.admin-density-dialog .weight-heading { flex-wrap: wrap; gap: 8px; padding-bottom: 8px; }
.inbound-density.admin-density-dialog .weight-heading__actions { flex-wrap: wrap; gap: 12px; }
.inbound-density.admin-density-dialog .pallet-picker { width: 340px; }
.inbound-density.admin-density-dialog .weight-list { margin-top: 8px; }
.inbound-density.admin-density-dialog .weight-grid { grid-template-columns: repeat(auto-fill, minmax(420px, 1fr)); gap: 8px; }
.inbound-density.admin-density-dialog .weight-card { display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: 10px; align-items: start; padding: 8px; border-radius: 2px; }
.inbound-density.admin-density-dialog .weight-card .weight-thumb { width: 120px; border-radius: 2px; }
.inbound-density.admin-density-dialog .weight-card__body { min-width: 0; padding: 0; text-align: left; }
.inbound-density.admin-density-dialog .weight-card__value { min-height: 28px; padding-right: 32px; text-align: left; }
.inbound-density.admin-density-dialog .weight-card__value strong { font-size: 14px; }
.inbound-density.admin-density-dialog .weight-card__facts { gap: 3px; margin-top: 4px; padding-top: 4px; }
.inbound-density.admin-density-dialog .weight-card__facts div { font-size: 12px; }
.inbound-density.admin-density-dialog .weight-card__correct { width: auto; margin-top: 6px; }
}
@@ -62,7 +62,7 @@ describe('MeasurementReviewPanel', () => {
vi.mocked(listMeasurementReviewHistory).mockResolvedValue({ records: [], page: 1, size: 20, total: 0, pages: 0 })
})
it('keeps customer context and pending actions in one non-sticky first-screen toolbar', async () => {
it('keeps customer context and pending actions in one sticky toolbar while reviewing long content', async () => {
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
await flushPromises()
@@ -83,7 +83,7 @@ describe('MeasurementReviewPanel', () => {
expect(wrapper.text()).not.toContain('审核历史')
expect(listMeasurementReviewHistory).not.toHaveBeenCalled()
const source = await import('./MeasurementReviewPanel.vue?raw').then((module) => module.default)
expect(source).not.toContain('position: sticky')
expect(source).toContain('position: sticky')
expect(source).toContain('review-toolbar__context')
expect(source.indexOf('class="review-toolbar"')).toBeLessThan(source.indexOf('<WeightOcrPanel'))
expect(source).not.toContain('reasonDialog')
@@ -1,4 +1,5 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Check, Pencil, RotateCcw } from '@lucide/vue'
@@ -139,7 +140,7 @@ async function confirmApprove() {
await ElMessageBox.confirm(
`审核通过后,入库单将进入${approvalTargetLabel.value}。系统会尝试生成初算费用,但初算失败不影响审核通过,后续可由平台人员补充调整。是否继续?`,
'确认测量审核通过',
{ confirmButtonText: approvalButtonLabel.value, cancelButtonText: '返回', type: 'warning' },
{ customClass: 'admin-density-confirm', confirmButtonText: approvalButtonLabel.value, cancelButtonText: '返回', type: 'warning' },
)
} catch {
return
@@ -242,7 +243,7 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
<div v-if="detail.items.length" class="review-table-scroll">
<el-table :data="detail.items" border stripe table-layout="fixed" class="review-table">
<el-table-column type="index" label="#" width="44" align="center" />
<el-table-column label="箱号" min-width="100" show-overflow-tooltip>
<el-table-column label="箱号" width="240" show-overflow-tooltip>
<template #default="{ row }">{{ row.boxCode || row.itemNo || '—' }}</template>
</el-table-column>
<el-table-column label="测量图片" width="98" align="center">
@@ -257,7 +258,7 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
/>
</template>
</el-table-column>
<el-table-column label="尺寸 长×宽×高(cm)" width="132" align="center">
<el-table-column label="尺寸 长×宽×高(cm)" min-width="200" align="center">
<template #default="{ row }">{{ formatNumber(row.lengthCm) }} × {{ formatNumber(row.widthCm) }} × {{ formatNumber(row.heightCm) }}</template>
</el-table-column>
<el-table-column prop="packageCount" label="箱数" width="54" align="right">
@@ -272,7 +273,7 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
<el-table-column label="体积重(kg)" width="96" align="right">
<template #default="{ row }">{{ formatNumber(row.volumeWeightKg) }}</template>
</el-table-column>
<el-table-column label="测量时间" width="152" class-name="review-time-cell">
<el-table-column label="测量时间" width="190" class-name="review-time-cell">
<template #default="{ row }">{{ formatDate(row.deviceMeasuredAt) }}</template>
</el-table-column>
</el-table>
@@ -289,7 +290,7 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
/>
</template>
<el-dialog v-model="imagePreviewOpen" :title="imagePreviewTitle" width="min(760px, 92vw)" destroy-on-close append-to-body @closed="releaseImagePreview">
<el-dialog class="admin-density-dialog" v-model="imagePreviewOpen" :title="imagePreviewTitle" width="min(760px, 92vw)" destroy-on-close append-to-body @closed="releaseImagePreview">
<div class="review-image-preview">
<el-skeleton v-if="imagePreviewLoading" :rows="5" animated />
<div v-else-if="imagePreviewError" class="review-image-preview__error">
@@ -304,8 +305,8 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
</template>
<style scoped>
.review-panel { display: grid; gap: 16px; color: var(--cc-text); }
.review-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin: -20px -24px 0; padding: 14px 24px; border-bottom: 1px solid var(--cc-border); background: #fff; }
.review-panel { display: grid; gap: 12px; color: var(--cc-text); }
.review-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 14px; position: sticky; top: -12px; z-index: 3; flex-wrap: wrap; margin: -12px -16px 0; padding: 8px 16px; border-bottom: 1px solid var(--cc-border); background: #fff; }
.review-toolbar__status, .review-toolbar__actions { display: flex; min-width: 0; align-items: center; flex-wrap: wrap; gap: 8px; }
.review-toolbar__status > span { color: var(--cc-muted); font-size: 12px; }
.review-toolbar__actions { justify-content: flex-end; }
@@ -324,14 +325,14 @@ defineExpose({ confirmApprove, isPending, canReview, actionLoading })
.review-metrics, .review-declaration { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px 16px; min-width: 0; margin: 0; }
.review-declaration { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.review-metrics > div, .review-declaration > div { display: flex; min-width: 0; align-items: baseline; gap: 6px; }
.review-metrics dt, .review-declaration dt { color: var(--cc-muted); font-size: 11px; line-height: 1.4; }
.review-metrics dd { margin: 0; overflow-wrap: anywhere; color: #172033; font-size: 15px; font-weight: 700; line-height: 1.35; }
.review-metrics dd small, .review-declaration dd small { color: var(--cc-muted); font-size: 11px; font-weight: 500; }
.review-metrics dt, .review-declaration dt { color: var(--cc-muted); font-size: 12px; line-height: 1.4; }
.review-metrics dd { margin: 0; overflow-wrap: anywhere; color: #172033; font-size: 14px; font-weight: 600; line-height: 1.35; }
.review-metrics dd small, .review-declaration dd small { color: var(--cc-muted); font-size: 12px; font-weight: 500; }
.review-metrics .review-metric--primary dd { color: #067647; }
.review-declaration dd { margin: 0; overflow-wrap: anywhere; color: var(--cc-text); font-size: 13px; font-weight: 650; line-height: 1.4; }
.review-table-scroll { width: 100%; min-width: 0; overflow: hidden; }
.review-table { width: 100%; font-size: 12px; }
.review-table :deep(.el-table__header th) { padding: 8px 0; background: #f6f8fa; color: #475467; font-size: 11px; font-weight: 700; }
.review-table :deep(.el-table__header th) { padding: 8px 0; background: #f6f8fa; color: #475467; font-size: 12px; font-weight: 700; }
.review-table :deep(.el-table__header .cell) { white-space: nowrap; }
.review-table :deep(.el-table__cell) { padding: 7px 0; }
.review-table :deep(.review-time-cell .cell) { white-space: nowrap; }
@@ -1,4 +1,5 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
@@ -625,7 +626,7 @@ defineExpose({ reload: loadWorkspace })
</template>
</template>
<AppDialog
<AppDialog class="admin-density-dialog"
v-model="previewOpen"
title="确认称重图片"
:loading="previewLoading"
@@ -711,10 +712,10 @@ defineExpose({ reload: loadWorkspace })
.weight-card__facts dd { min-width: 0; margin: 0; color: #344054; overflow-wrap: anywhere; text-align: right; }
.weight-card__correct { width: 100%; margin: 12px 0 0; }
.weight-preview { display: block; width: 100%; max-height: min(50vh, 460px); object-fit: contain; border-radius: 6px; background: #f2f4f7; }
.weight-key-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; margin-top: 14px; padding: 14px 0; border-block: 1px solid var(--cc-border); }
.weight-key-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 12px; margin-top: 8px; padding: 8px 0; border-block: 1px solid var(--cc-border); }
.weight-key-facts div { display: grid; min-width: 0; gap: 4px; }
.weight-key-facts span { color: var(--cc-muted); font-size: 12px; }
.weight-key-facts strong { color: #101828; font-size: 18px; }
.weight-key-facts strong { color: #101828; font-size: 14px; }
.preview-weight { display: grid; grid-template-columns: minmax(0, 1fr) 120px; align-items: end; gap: 12px; margin-top: 14px; }
.preview-weight label { display: grid; gap: 6px; color: #475467; font-size: 12px; }
.preview-weight :deep(.el-input-number), .preview-weight :deep(.el-segmented) { width: 100%; }