fix(admin): simplify booking documents and refine measurement review
This commit is contained in:
@@ -62,13 +62,19 @@ describe('MeasurementReviewPanel', () => {
|
||||
vi.mocked(listMeasurementReviewHistory).mockResolvedValue({ records: [], page: 1, size: 20, total: 0, pages: 0 })
|
||||
})
|
||||
|
||||
it('keeps customer context and pending actions in one sticky toolbar while reviewing long content', async () => {
|
||||
it('keeps context in a scrolling toolbar and moves weight actions and totals to the weight details', async () => {
|
||||
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
const toolbar = wrapper.get('.review-toolbar')
|
||||
expect(toolbar.text()).toContain('待审核')
|
||||
expect(toolbar.text()).toContain('修改重量')
|
||||
expect(toolbar.text()).not.toContain('修改重量')
|
||||
const weightHeading = wrapper.get('.review-weight-items header')
|
||||
expect(weightHeading.text()).toContain('修改重量')
|
||||
expect(weightHeading.text()).toContain('实测毛重 120 kg')
|
||||
expect(weightHeading.text()).toContain('货物重量 110 kg')
|
||||
expect(weightHeading.find('.lucide-scale').exists()).toBe(true)
|
||||
expect(weightHeading.find('.lucide-pencil').exists()).toBe(true)
|
||||
expect(toolbar.findAll('button').map(button => button.text())).toContain('通过')
|
||||
expect(toolbar.text()).toContain('刷新')
|
||||
expect(toolbar.text()).toContain('客户:柏悦客户')
|
||||
@@ -83,7 +89,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).toContain('position: sticky')
|
||||
expect(source).not.toContain('position: sticky')
|
||||
expect(source).toContain('review-toolbar__context')
|
||||
expect(source.indexOf('class="review-toolbar"')).toBeLessThan(source.indexOf('<WeightOcrPanel'))
|
||||
expect(source).not.toContain('reasonDialog')
|
||||
@@ -193,7 +199,6 @@ describe('MeasurementReviewPanel', () => {
|
||||
|
||||
const weightPanel = wrapper.getComponent({ name: 'WeightOcrPanel' })
|
||||
expect(weightPanel.props('reviewMode')).toBe(true)
|
||||
expect(weightPanel.props('tableMode')).toBe(true)
|
||||
expect(weightPanel.props('correctionMode')).toBe(false)
|
||||
await wrapper.findAll('button').find((button) => button.text().includes('修改重量'))?.trigger('click')
|
||||
expect(weightPanel.props('correctionMode')).toBe(true)
|
||||
@@ -229,7 +234,7 @@ describe('MeasurementReviewPanel', () => {
|
||||
expect(wrapper.find('.review-toolbar__actions').exists()).toBe(false)
|
||||
expect(wrapper.get('.review-toolbar__context').text()).toContain('客户:柏悦客户')
|
||||
expect(wrapper.vm.approvalButtonLabel).toBe('通过')
|
||||
wrapper.vm.enableWeightCorrection()
|
||||
await wrapper.get('.review-weight-summary button').trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.getComponent({ name: 'WeightOcrPanel' }).props('correctionMode')).toBe(true)
|
||||
await wrapper.vm.confirmApprove()
|
||||
|
||||
@@ -3,7 +3,7 @@ import '@/styles/adminDensity.css'
|
||||
import '@/styles/adminWorkbench.css'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Check, Pencil, RotateCcw } from '@lucide/vue'
|
||||
import { Check, Pencil, RotateCcw, Ruler, Scale } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
@@ -13,6 +13,7 @@ import { downloadInboundMeasurementImage } from '../measurement/api'
|
||||
import MeasurementEvidenceThumbnail from '../measurement/components/MeasurementEvidenceThumbnail.vue'
|
||||
import { inboundProcessStatusLabels, inboundReviewStatusLabels, inboundStatusLabel } from '../status'
|
||||
import WeightOcrPanel from '../weight/components/WeightOcrPanel.vue'
|
||||
import type { WeightResultResource } from '../weight/types'
|
||||
import { approveMeasurement, getMeasurementReviewDetail } from './api'
|
||||
import type { MeasurementReviewDetail } from './types'
|
||||
|
||||
@@ -38,6 +39,7 @@ const loading = ref(false)
|
||||
const actionLoading = ref('')
|
||||
const loadError = ref<ApiErrorDetails | null>(null)
|
||||
const correctionMode = ref(false)
|
||||
const weightPanelRef = ref<InstanceType<typeof WeightOcrPanel> | null>(null)
|
||||
const requestSerial = ref(0)
|
||||
const imagePreviewOpen = ref(false)
|
||||
const imagePreviewTitle = ref('测量图片')
|
||||
@@ -125,7 +127,7 @@ async function loadReview() {
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
await loadReview()
|
||||
await Promise.all([loadReview(), weightPanelRef.value?.reload()])
|
||||
}
|
||||
|
||||
async function handleWeightChanged() {
|
||||
@@ -175,7 +177,7 @@ onMounted(() => {
|
||||
})
|
||||
onBeforeUnmount(releaseImagePreview)
|
||||
|
||||
defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPending, canReview, actionLoading })
|
||||
defineExpose({ confirmApprove, approvalButtonLabel, isPending, canReview, actionLoading })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -183,7 +185,6 @@ defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPe
|
||||
<h2 id="measurement-review-title" class="sr-only">测量审核内容</h2>
|
||||
<div class="review-toolbar" aria-label="测量审核操作">
|
||||
<div v-if="showActions" class="review-toolbar__actions">
|
||||
<el-button v-if="isPending && canReview" type="primary" plain @click="enableWeightCorrection"><Pencil :size="16" />修改重量</el-button>
|
||||
<el-button v-if="isPending && canReview" type="primary" :loading="actionLoading === 'approve' || postApproveLoading" :disabled="approvalCompleted" @click="confirmApprove"><Check :size="16" />{{ approvalCompleted ? '审核已通过' : approvalButtonLabel }}</el-button>
|
||||
</div>
|
||||
<div class="review-toolbar__status">
|
||||
@@ -220,18 +221,13 @@ defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPe
|
||||
|
||||
<section class="review-items density-dialog-section" data-tone="source" aria-labelledby="review-items-title">
|
||||
<header class="review-section-heading density-dialog-section__title">
|
||||
<div>
|
||||
<h3 id="review-items-title">箱规测量明细</h3>
|
||||
</div>
|
||||
<h3 id="review-items-title" class="review-detail-heading"><Ruler :size="18" aria-hidden="true" />箱规测量明细</h3>
|
||||
<span>{{ measurementCount }} 条</span>
|
||||
</header>
|
||||
<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="箱号" width="160" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.boxCode || row.itemNo || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="测量图片" width="98" align="center">
|
||||
<el-table-column label="测量图片" width="112" align="center">
|
||||
<template #default="{ row }">
|
||||
<MeasurementEvidenceThumbnail
|
||||
:customer-id="customerId"
|
||||
@@ -243,6 +239,9 @@ defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPe
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="箱号" width="160" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.boxCode || row.itemNo || '—' }}</template>
|
||||
</el-table-column>
|
||||
<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>
|
||||
@@ -258,7 +257,7 @@ defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPe
|
||||
<el-table-column label="体积重(kg)" width="96" align="right">
|
||||
<template #default="{ row }">{{ formatNumber(row.volumeWeightKg) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="测量时间" width="190" class-name="review-time-cell">
|
||||
<el-table-column label="测量时间" width="220" class-name="review-time-cell">
|
||||
<template #default="{ row }">{{ formatDate(row.deviceMeasuredAt) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -267,34 +266,45 @@ defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPe
|
||||
</section>
|
||||
|
||||
<section class="review-items review-weight-items density-dialog-section" data-tone="source" aria-labelledby="review-weight-title">
|
||||
<header class="review-section-heading density-dialog-section__title"><h3 id="review-weight-title">称重明细</h3><span>{{ weightCount }} 条</span></header>
|
||||
<div v-if="weightCount" class="review-table-scroll">
|
||||
<el-table :data="detail.weightResults" border stripe table-layout="fixed" class="review-table review-weight-table">
|
||||
<el-table-column type="index" label="#" width="44" align="center" />
|
||||
<el-table-column label="称重图片" width="112" align="center"><template #default="{ row }"><MeasurementEvidenceThumbnail :customer-id="customerId" :inbound-id="inboundId" :file-id="row.fileId" :alt="`${row.palletCode || '托盘'}称重图片`" empty-text="无图片" @open="openImagePreview(row.fileId, row.palletCode || '托盘')" /></template></el-table-column>
|
||||
<el-table-column prop="palletCode" label="托盘编码" min-width="120" />
|
||||
<el-table-column prop="palletType" label="托盘类型" min-width="120" />
|
||||
<el-table-column label="库位" min-width="130"><template #default="{ row }"><span class="review-location">{{ row.locationCode || '—' }}<template v-if="row.locationCode"> · {{ row.slotStatus === 'BOUND' ? '待上架' : row.slotStatus || '待上架' }}</template></span></template></el-table-column>
|
||||
<el-table-column label="毛重(KG)" min-width="120" align="right"><template #default="{ row }">{{ formatNumber(row.grossWeightKg, 3) }}</template></el-table-column>
|
||||
<el-table-column label="托盘皮重(kg)" min-width="120" align="right"><template #default="{ row }">{{ formatNumber(row.palletTareWeightKg, 3) }}</template></el-table-column>
|
||||
<el-table-column label="货物重量(kg)" min-width="120" align="right"><template #default="{ row }"><strong class="review-weight-value">{{ formatNumber(row.netWeightKg, 3) }}</strong></template></el-table-column>
|
||||
<el-table-column label="称重时间" min-width="150"><template #default="{ row }">{{ formatDate(row.confirmedAt || row.adjustedAt) }}</template></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-empty v-else description="暂无称重明细" :image-size="48" />
|
||||
<header class="review-section-heading density-dialog-section__title">
|
||||
<h3 id="review-weight-title" class="review-detail-heading"><Scale :size="18" aria-hidden="true" />称重明细</h3>
|
||||
<div class="review-weight-summary">
|
||||
<span>实测毛重 <strong>{{ formatNumber(detail.actualGrossWeightKg) }}</strong> kg</span>
|
||||
<span>货物重量 <strong>{{ formatNumber(detail.actualNetWeightKg) }}</strong> kg</span>
|
||||
<el-button v-if="isPending && canReview" type="primary" plain @click="enableWeightCorrection"><Pencil :size="16" />修改重量</el-button>
|
||||
<span>{{ weightCount }} 条</span>
|
||||
</div>
|
||||
</header>
|
||||
<WeightOcrPanel
|
||||
ref="weightPanelRef"
|
||||
review-mode
|
||||
:correction-mode="correctionMode && isPending && canReview"
|
||||
:customer-id="customerId"
|
||||
:inbound-id="inboundId"
|
||||
@changed="handleWeightChanged"
|
||||
>
|
||||
<template #heading><span id="weight-ocr-title" class="sr-only">称重图片预览与修正</span></template>
|
||||
<template #results="{ rows, openPreview, canCorrect, busy }">
|
||||
<div v-if="rows.length" class="review-table-scroll">
|
||||
<el-table :data="rows" border stripe table-layout="fixed" class="review-table review-weight-table">
|
||||
<el-table-column v-if="canCorrect" label="操作" width="100">
|
||||
<template #default="{ row, $index }"><el-button v-if="row.recognitionStatus === 'CONFIRMED'" link type="primary" :aria-label="`修改重量 ${$index + 1}`" :disabled="busy" @click="openPreview(row as WeightResultResource)">修改重量</el-button><span v-else>—</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column type="index" label="#" width="44" align="center" />
|
||||
<el-table-column label="称重图片" width="112" align="center"><template #default="{ row }"><MeasurementEvidenceThumbnail :customer-id="customerId" :inbound-id="inboundId" :file-id="row.fileId" :alt="`${row.palletCode || '托盘'}称重图片`" empty-text="无图片" @open="openPreview(row as WeightResultResource)" /></template></el-table-column>
|
||||
<el-table-column prop="palletCode" label="托盘编码" width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="palletType" label="托盘类型" min-width="120" />
|
||||
<el-table-column label="库位" min-width="130"><template #default="{ row }"><span class="review-location">{{ row.locationCode || '—' }}<template v-if="row.locationCode"> · {{ row.slotStatus === 'BOUND' ? '待上架' : row.slotStatus || '待上架' }}</template></span></template></el-table-column>
|
||||
<el-table-column label="毛重(KG)" min-width="120" align="right"><template #default="{ row }">{{ formatNumber(row.grossWeightKg, 3) }}</template></el-table-column>
|
||||
<el-table-column label="托盘皮重(kg)" min-width="120" align="right"><template #default="{ row }">{{ formatNumber(row.palletTareWeightKg, 3) }}</template></el-table-column>
|
||||
<el-table-column label="货物重量(kg)" min-width="120" align="right"><template #default="{ row }"><strong class="review-weight-value">{{ formatNumber(row.netWeightKg, 3) }}</strong></template></el-table-column>
|
||||
<el-table-column label="称重时间" width="220" class-name="review-time-cell"><template #default="{ row }">{{ formatDate(row.confirmedAt || row.adjustedAt) }}</template></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-empty v-else-if="!busy" description="暂无称重明细" :image-size="48" />
|
||||
</template>
|
||||
</WeightOcrPanel>
|
||||
</section>
|
||||
|
||||
<WeightOcrPanel
|
||||
class="density-dialog-section"
|
||||
data-tone="source"
|
||||
review-mode
|
||||
table-mode
|
||||
:correction-mode="correctionMode"
|
||||
:customer-id="customerId"
|
||||
:inbound-id="inboundId"
|
||||
@changed="handleWeightChanged"
|
||||
:class="{ 'review-weight-editor--hidden': !correctionMode }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<AppDialog class="admin-density-dialog admin-workbench is-dialog" v-model="imagePreviewOpen" :title="imagePreviewTitle" width="min(760px, 92vw)" fullscreenable :show-footer="false" :close-on-click-modal="true" destroy-on-close append-to-body @closed="releaseImagePreview">
|
||||
@@ -313,7 +323,7 @@ defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPe
|
||||
|
||||
<style scoped>
|
||||
.review-panel { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--admin-space-5, 12px); color: var(--workbench-text); }
|
||||
.review-toolbar { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5, 12px); position: sticky; top: 0; z-index: 3; flex-wrap: wrap; padding: var(--admin-space-3, 8px) 0; border-bottom: 1px solid var(--workbench-border); background: var(--admin-surface, #fff); }
|
||||
.review-toolbar { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5, 12px); flex-wrap: wrap; padding: var(--admin-space-3, 8px) 0; border-bottom: 1px solid var(--workbench-border); background: var(--admin-surface, #fff); }
|
||||
.review-toolbar__status, .review-toolbar__actions { display: flex; min-width: 0; align-items: center; flex-wrap: wrap; gap: var(--admin-space-3, 8px); }
|
||||
.review-toolbar__status > span { color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); }
|
||||
.review-toolbar__actions { justify-content: flex-end; }
|
||||
@@ -325,6 +335,8 @@ defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPe
|
||||
.review-section-heading > div { min-width: 0; }
|
||||
.review-section-heading h3 { margin: 0; color: var(--workbench-text); font-size: var(--admin-font-section, 14px); line-height: 1.35; }
|
||||
.review-section-heading > span { color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); white-space: nowrap; }
|
||||
.review-items > .review-section-heading { min-height: var(--admin-control-height, 32px); }
|
||||
.review-weight-items :deep(.weight-list) { margin-top: 0; }
|
||||
.review-comparison { table-layout: fixed; }
|
||||
.review-extra-facts { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--admin-space-5, 12px); margin-top: var(--admin-space-3, 8px); color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); }
|
||||
.review-extra-facts strong { color: var(--workbench-text); }
|
||||
@@ -334,7 +346,10 @@ defineExpose({ confirmApprove, enableWeightCorrection, approvalButtonLabel, isPe
|
||||
.review-table :deep(.measurement-evidence-thumb) { min-width: 72px; border-radius: var(--admin-radius, 4px); }
|
||||
.review-weight-value { color: var(--el-color-success-dark-2); font-weight: 600; }
|
||||
.review-location { white-space: nowrap; }
|
||||
.review-weight-editor--hidden { display: none; }
|
||||
.review-detail-heading, .review-weight-summary { display: flex; align-items: center; gap: var(--admin-space-5, 12px); }
|
||||
.review-detail-heading > svg { flex-shrink: 0; }
|
||||
.review-weight-summary { flex-wrap: wrap; color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); }
|
||||
.review-weight-summary strong { color: var(--el-color-success-dark-2); }
|
||||
.review-image-preview { display: grid; min-height: 240px; place-items: center; }
|
||||
.review-image-preview img { display: block; width: auto; max-width: 100%; max-height: 70vh; object-fit: contain; }
|
||||
.review-image-preview__error { display: grid; justify-items: center; gap: var(--admin-space-3, 8px); color: var(--el-color-danger); }
|
||||
|
||||
Reference in New Issue
Block a user