优化
This commit is contained in:
@@ -19,20 +19,22 @@ describe('InboundTaskList measurement scope', () => {
|
||||
it('uses the inbound tenant isolation key for measurement and review requests', () => {
|
||||
const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8')
|
||||
|
||||
expect(source.match(/:tenant-id="selectedInbound\.tenantId"/g)).toHaveLength(2)
|
||||
expect(source).toContain(':tenant-id="selectedInbound.tenantId"')
|
||||
expect(source).toContain(':customer-id="selectedInbound.tenantId"')
|
||||
expect(source).not.toContain('selectedInbound.customerId || selectedInbound.tenantId')
|
||||
})
|
||||
|
||||
it('embeds the weight OCR workflow in the actual measurement task without closing on weight changes', () => {
|
||||
it('opens the unique shared measurement dialog and refreshes without duplicating its workflow', () => {
|
||||
const source = readFileSync(resolve('src/modules/inbounds/components/InboundTaskList.vue'), 'utf8')
|
||||
|
||||
expect(source).toContain("import WeightOcrPanel from '../weight/components/WeightOcrPanel.vue'")
|
||||
expect(source).toContain('<WeightOcrPanel')
|
||||
expect(source).toContain("import InboundMeasurementDialog from '../measurement/InboundMeasurementDialog.vue'")
|
||||
expect(source).toContain('<InboundMeasurementDialog')
|
||||
expect(source).toContain('v-model="measurementDialogOpen"')
|
||||
expect(source).toContain(':customer-id="selectedInbound.tenantId"')
|
||||
expect(source).toContain('@changed="handleWeightChanged"')
|
||||
expect(source).toContain('@summary-changed="handleWeightSummaryChanged"')
|
||||
expect(source).toContain('measurementPanelRef.value?.updateActualGrossWeight(actualGrossWeightKg)')
|
||||
expect(source).not.toContain('<WeightOcrPanel\n :customer-id="selectedInbound.tenantId"\n :inbound-id="selectedInbound.id"\n @changed="handleTaskChanged"')
|
||||
expect(source).toContain('@updated="handleMeasurementUpdated"')
|
||||
expect(source).toContain('@submitted="handleMeasurementSubmitted"')
|
||||
expect(source).not.toContain('<WeightOcrPanel')
|
||||
expect(source).not.toContain('<InboundMeasurementPanel')
|
||||
})
|
||||
|
||||
it('loads only active customers and exposes a searchable name-only select', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ClipboardCheck, Eye, Ruler, SearchCheck } from '@lucide/vue'
|
||||
import { Eye, Ruler, SearchCheck } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
||||
import { listPlatformWarehouses } from '@/modules/warehouses'
|
||||
@@ -14,11 +14,10 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import { listPlatformInbounds } from '../core/api'
|
||||
import type { InboundOrderSummary } from '../core/types'
|
||||
import InboundMeasurementPanel from '../measurement/InboundMeasurementPanel.vue'
|
||||
import InboundMeasurementDialog from '../measurement/InboundMeasurementDialog.vue'
|
||||
import MeasurementReviewPanel from '../review/MeasurementReviewPanel.vue'
|
||||
import { inboundProcessStatusLabels, inboundReviewStatusLabels, inboundStatusLabel } from '../status'
|
||||
import { buildInboundTaskQuery, loadAllActiveCustomers, type InboundTaskMode } from '../task-list'
|
||||
import WeightOcrPanel from '../weight/components/WeightOcrPanel.vue'
|
||||
|
||||
type FilterModel = QueryModel & {
|
||||
customerId: string
|
||||
@@ -41,8 +40,8 @@ const customers = ref<PlatformTenantResource[]>([])
|
||||
const customerLoadError = ref<ApiErrorDetails | null>(null)
|
||||
const warehouseOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const selectedInbound = ref<InboundOrderSummary | null>(null)
|
||||
const taskDialogOpen = ref(false)
|
||||
const measurementPanelRef = ref<InstanceType<typeof InboundMeasurementPanel> | null>(null)
|
||||
const measurementDialogOpen = ref(false)
|
||||
const reviewDialogOpen = ref(false)
|
||||
let requestSerial = 0
|
||||
let optionsRequestSerial = 0
|
||||
let disposed = false
|
||||
@@ -168,7 +167,8 @@ function resetFilters() {
|
||||
|
||||
function openTask(row: InboundOrderSummary) {
|
||||
selectedInbound.value = row
|
||||
taskDialogOpen.value = true
|
||||
if (isReview.value) reviewDialogOpen.value = true
|
||||
else measurementDialogOpen.value = true
|
||||
}
|
||||
|
||||
function openDetail(row: InboundOrderSummary) {
|
||||
@@ -177,21 +177,21 @@ function openDetail(row: InboundOrderSummary) {
|
||||
}
|
||||
|
||||
function handleTaskChanged() {
|
||||
taskDialogOpen.value = false
|
||||
reviewDialogOpen.value = false
|
||||
selectedInbound.value = null
|
||||
void loadTasks()
|
||||
}
|
||||
|
||||
function handleWeightChanged() {
|
||||
void loadTasks()
|
||||
}
|
||||
|
||||
function handleMeasurementUpdated() {
|
||||
void loadTasks()
|
||||
}
|
||||
|
||||
function handleWeightSummaryChanged(actualGrossWeightKg: number | null) {
|
||||
measurementPanelRef.value?.updateActualGrossWeight(actualGrossWeightKg)
|
||||
function handleMeasurementSubmitted() {
|
||||
void loadTasks()
|
||||
}
|
||||
|
||||
function clearSelectedInbound() {
|
||||
selectedInbound.value = null
|
||||
}
|
||||
|
||||
watch([page, pageSize], () => { void loadTasks() })
|
||||
@@ -280,38 +280,34 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
|
||||
</ListPageLayout>
|
||||
|
||||
<el-dialog
|
||||
v-model="taskDialogOpen"
|
||||
v-if="isReview"
|
||||
v-model="reviewDialogOpen"
|
||||
:title="selectedInbound ? `${taskLabel} · ${selectedInbound.inboundCode || selectedInbound.id}` : taskLabel"
|
||||
width="min(1180px, calc(100vw - 32px))"
|
||||
top="4vh"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
class="inbound-task-dialog"
|
||||
@closed="clearSelectedInbound"
|
||||
>
|
||||
<MeasurementReviewPanel
|
||||
v-if="isReview && selectedInbound"
|
||||
v-if="selectedInbound"
|
||||
:tenant-id="selectedInbound.tenantId"
|
||||
:inbound-id="selectedInbound.id"
|
||||
@changed="handleTaskChanged"
|
||||
/>
|
||||
<div v-else-if="selectedInbound" class="measurement-task-workspace">
|
||||
<section class="surface-panel inbound-task-weight" aria-label="称重图片与 OCR 核对">
|
||||
<WeightOcrPanel
|
||||
:customer-id="selectedInbound.tenantId"
|
||||
:inbound-id="selectedInbound.id"
|
||||
@changed="handleWeightChanged"
|
||||
@summary-changed="handleWeightSummaryChanged"
|
||||
/>
|
||||
</section>
|
||||
<InboundMeasurementPanel
|
||||
ref="measurementPanelRef"
|
||||
:tenant-id="selectedInbound.tenantId"
|
||||
:inbound-id="selectedInbound.id"
|
||||
@changed="handleTaskChanged"
|
||||
@updated="handleMeasurementUpdated"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<InboundMeasurementDialog
|
||||
v-if="!isReview && selectedInbound"
|
||||
v-model="measurementDialogOpen"
|
||||
:customer-id="selectedInbound.tenantId"
|
||||
:inbound-id="selectedInbound.id"
|
||||
:inbound-code="selectedInbound.inboundCode"
|
||||
@updated="handleMeasurementUpdated"
|
||||
@submitted="handleMeasurementSubmitted"
|
||||
@closed="clearSelectedInbound"
|
||||
/>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
@@ -323,12 +319,9 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
|
||||
.primary-cell { display: grid; gap: 3px; min-width: 0; }
|
||||
.primary-cell strong, .primary-cell span { overflow-wrap: anywhere; }
|
||||
.primary-cell span, .muted-action { color: var(--cc-muted); font-size: 12px; }
|
||||
.measurement-task-workspace { display: grid; min-width: 0; gap: 20px; }
|
||||
.inbound-task-weight { min-width: 0; padding: 20px; }
|
||||
@media (max-width: 520px) {
|
||||
.row-actions { align-items: stretch; flex-direction: column; }
|
||||
.row-actions :deep(.el-button) { width: 100%; }
|
||||
.inbound-task-weight { padding: 14px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@ describe('InboundWorkflowWorkspace', () => {
|
||||
it('assembles every phase-two inbound domain behind one server-backed workspace', () => {
|
||||
expect(source).toContain('InboundSummaryPanel')
|
||||
expect(source).toContain('InboundHistoryPanel')
|
||||
expect(source).toContain('InboundMeasurementPanel')
|
||||
expect(source).toContain('WeightOcrPanel')
|
||||
expect(source).toContain('InboundMeasurementDialog')
|
||||
expect(source).toContain('MeasurementReviewPanel')
|
||||
expect(source).toContain('InboundFeePanel')
|
||||
expect(source).toContain('getPlatformInbound')
|
||||
@@ -23,7 +22,7 @@ describe('InboundWorkflowWorkspace', () => {
|
||||
})
|
||||
|
||||
it('opens measurement editing and review in prototype-sized centered dialogs', () => {
|
||||
expect(source).toContain(':title="`测量 · ${title}`"')
|
||||
expect(source).toContain('<InboundMeasurementDialog')
|
||||
expect(source).toContain('width="min(1240px, calc(100vw - 32px))"')
|
||||
expect(source).toContain(':title="`测量审核 · ${title}`"')
|
||||
expect(source).toContain('width="min(1240px, calc(100vw - 32px))"')
|
||||
@@ -37,12 +36,11 @@ describe('InboundWorkflowWorkspace', () => {
|
||||
it('keeps domain writes inside their existing panels and mounts them only in dialog bodies', () => {
|
||||
expect(source).toContain('@click="measurementDialogOpen = true"')
|
||||
expect(source).toContain('@click="reviewDialogOpen = true"')
|
||||
expect(source).toContain(':key="`measurement-dialog-${revision}`"')
|
||||
expect(source).toContain(':key="`review-dialog-${revision}`"')
|
||||
expect(source).toContain('@changed="handleReviewChanged"')
|
||||
expect(source).toContain('<template #footer>')
|
||||
expect(source).toContain('measurementPanelRef?.saveDraft()')
|
||||
expect(source).toContain('measurementPanelRef?.submitForReview()')
|
||||
expect(source).toContain('@updated="handleDomainChanged"')
|
||||
expect(source).toContain('@submitted="handleDomainChanged"')
|
||||
expect(source).toContain("reviewPanelRef?.openReasonDialog('reject')")
|
||||
expect(source).toContain('reviewPanelRef?.confirmApprove()')
|
||||
expect(source).toContain('inbound-operation-dialog__footer')
|
||||
@@ -69,7 +67,7 @@ describe('InboundWorkflowWorkspace', () => {
|
||||
it('uses customer context in the page layer and maps it to compatible domain APIs', () => {
|
||||
expect(source).toContain('customerId: string')
|
||||
expect(source).toContain('getPlatformInbound(props.customerId, props.inboundId)')
|
||||
expect(source).toContain(':tenant-id="customerId"')
|
||||
expect(source).toContain(':customer-id="customerId"')
|
||||
expect(source).toContain('缺少所属企业信息')
|
||||
expect(source).not.toContain('请选择企业')
|
||||
expect(source).not.toContain('租户')
|
||||
|
||||
@@ -10,8 +10,7 @@ import { getPlatformInbound } from '../core/api'
|
||||
import InboundHistoryPanel from '../core/InboundHistoryPanel.vue'
|
||||
import InboundSummaryPanel from '../core/InboundSummaryPanel.vue'
|
||||
import type { InboundOrderDetail } from '../core/types'
|
||||
import { InboundMeasurementPanel } from '../measurement'
|
||||
import { WeightOcrPanel } from '../weight'
|
||||
import { InboundMeasurementDialog } from '../measurement'
|
||||
import { MeasurementReviewPanel } from '../review'
|
||||
import { InboundFeePanel } from '../fee'
|
||||
import { inboundProcessStatusLabels, inboundStatusLabel } from '../status'
|
||||
@@ -29,7 +28,6 @@ const access = useAccessPreviewStore()
|
||||
const activeTab = ref('overview')
|
||||
const measurementDialogOpen = ref(false)
|
||||
const reviewDialogOpen = ref(false)
|
||||
const measurementPanelRef = ref<InstanceType<typeof InboundMeasurementPanel> | null>(null)
|
||||
const reviewPanelRef = ref<InstanceType<typeof MeasurementReviewPanel> | null>(null)
|
||||
const loading = ref(false)
|
||||
const loadError = ref<ApiErrorDetails | null>(null)
|
||||
@@ -136,16 +134,14 @@ onBeforeUnmount(() => {
|
||||
<el-tab-pane label="货品与测量" name="measurement" lazy>
|
||||
<div class="workflow-action-row">
|
||||
<span>核对原单数据、确认扫描箱数,并查看服务端计算的重量和体积结果。</span>
|
||||
<el-button type="primary" @click="measurementDialogOpen = true"><Pencil :size="16" />编辑测量数据</el-button>
|
||||
</div>
|
||||
<div class="measurement-workflow">
|
||||
<WeightOcrPanel
|
||||
:key="`weight-${revision}`"
|
||||
:tenant-id="customerId"
|
||||
:inbound-id="inboundId"
|
||||
@changed="handleDomainChanged"
|
||||
/>
|
||||
<el-button v-if="canViewMeasurement" type="primary" @click="measurementDialogOpen = true"><Pencil :size="16" />编辑测量数据</el-button>
|
||||
</div>
|
||||
<el-alert
|
||||
:title="canViewMeasurement ? '称重图片、OCR、托盘与箱规统一在测量弹框中维护。' : '当前账号缺少测量查看权限。'"
|
||||
:type="canViewMeasurement ? 'info' : 'warning'"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="测量审核" name="review" lazy>
|
||||
<div v-if="canViewMeasurement" class="workflow-action-row">
|
||||
@@ -191,32 +187,14 @@ onBeforeUnmount(() => {
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-dialog
|
||||
<InboundMeasurementDialog
|
||||
v-model="measurementDialogOpen"
|
||||
class="inbound-operation-dialog inbound-measurement-dialog"
|
||||
:title="`测量 · ${title}`"
|
||||
width="min(1240px, calc(100vw - 32px))"
|
||||
top="4vh"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="inbound-operation-dialog__body">
|
||||
<InboundMeasurementPanel
|
||||
ref="measurementPanelRef"
|
||||
:key="`measurement-dialog-${revision}`"
|
||||
:tenant-id="customerId"
|
||||
:inbound-id="inboundId"
|
||||
@changed="handleDomainChanged"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="inbound-operation-dialog__footer">
|
||||
<el-button @click="measurementDialogOpen = false">取消</el-button>
|
||||
<el-button v-if="measurementPanelRef?.canEdit" type="primary" plain :loading="measurementPanelRef?.saving" :disabled="!measurementPanelRef?.mayMaintain" @click="measurementPanelRef?.saveDraft()">保存测量数据</el-button>
|
||||
<el-button v-if="measurementPanelRef?.canSubmit" type="primary" :loading="measurementPanelRef?.submitting" :disabled="!measurementPanelRef?.maySubmit" @click="measurementPanelRef?.submitForReview()">提交审核</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
:customer-id="customerId"
|
||||
:inbound-id="inboundId"
|
||||
:inbound-code="title"
|
||||
@updated="handleDomainChanged"
|
||||
@submitted="handleDomainChanged"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="reviewDialogOpen"
|
||||
@@ -263,7 +241,6 @@ onBeforeUnmount(() => {
|
||||
.workflow-tabs :deep(.el-tabs__active-bar) { height: 2px; background: var(--cc-primary); }
|
||||
.workflow-tabs :deep(.el-tabs__item) { min-height: 44px; padding: 0 18px; color: #475467; font-size: 13px; font-weight: 500; }
|
||||
.workflow-tabs :deep(.el-tabs__item.is-active) { color: var(--cc-primary); font-weight: 650; }
|
||||
.measurement-workflow { display: grid; min-width: 0; gap: 28px; }
|
||||
.workflow-action-row { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: 16px; padding: 0 0 14px; border-bottom: 1px solid var(--cc-border); color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
|
||||
.inbound-operation-dialog__body { min-width: 0; }
|
||||
.inbound-operation-dialog__footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
|
||||
@@ -272,8 +249,6 @@ onBeforeUnmount(() => {
|
||||
:global(.inbound-operation-dialog .el-dialog__header) { flex: 0 0 auto; margin: 0; padding: 18px 24px; border-bottom: 1px solid var(--cc-border); }
|
||||
:global(.inbound-operation-dialog .el-dialog__body) { min-height: 0; flex: 1 1 auto; overflow-y: auto; padding: 20px 24px; }
|
||||
:global(.inbound-operation-dialog .el-dialog__footer) { flex: 0 0 auto; padding: 14px 24px; border-top: 1px solid var(--cc-border); }
|
||||
:global(.inbound-measurement-dialog .measurement-panel) { border: 0; border-radius: 0; box-shadow: none; }
|
||||
:global(.inbound-measurement-dialog .measurement-panel__heading .measurement-panel__actions), :global(.inbound-measurement-dialog .measurement-panel__footer) { display: none; }
|
||||
:global(.inbound-review-dialog .review-actions .action-buttons) { display: none; }
|
||||
@media (max-width: 720px) {
|
||||
.workspace-header { align-items: stretch; flex-direction: column; padding: 16px; }
|
||||
|
||||
Reference in New Issue
Block a user