优化
This commit is contained in:
@@ -1,61 +0,0 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import ElementPlus from 'element-plus'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { createMemoryHistory, createRouter } from 'vue-router'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import InboundOrderDetailView from './InboundOrderDetailView.vue'
|
||||
|
||||
const source = readFileSync(resolve(process.cwd(), 'src/modules/inbounds/pages/InboundOrderDetailView.vue'), 'utf8')
|
||||
|
||||
describe('InboundOrderDetailView', () => {
|
||||
it('maps the compatible route parameter to customer context without a detail-page selector', async () => {
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', name: 'dashboard', component: { template: '<div>首页</div>' } },
|
||||
{ path: '/inbounds', name: 'inbound-orders', component: { template: '<div>入库单列表</div>' } },
|
||||
{ path: '/inbounds/:inboundId', name: 'inbound-order-detail', component: InboundOrderDetailView },
|
||||
],
|
||||
})
|
||||
await router.push('/inbounds/19001?tenantId=10002')
|
||||
await router.isReady()
|
||||
const wrapper = mount(InboundOrderDetailView, {
|
||||
global: {
|
||||
plugins: [router, ElementPlus],
|
||||
stubs: {
|
||||
InboundWorkflowWorkspace: {
|
||||
props: ['customerId', 'inboundId'],
|
||||
template: '<div data-testid="workspace">{{ customerId }}:{{ inboundId }}</div>',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.get('[aria-label="面包屑"]').text()).toContain('首页入库单列表入库单详情')
|
||||
expect(wrapper.get('[data-testid="workspace"]').text()).toBe('10002:19001')
|
||||
expect(wrapper.findAll('button').filter((button) => button.text().includes('返回入库单列表'))).toHaveLength(1)
|
||||
await wrapper.findAll('button').filter((button) => button.text().includes('返回入库单列表')).at(-1)?.trigger('click')
|
||||
await flushPromises()
|
||||
expect(router.currentRoute.value.name).toBe('inbound-orders')
|
||||
expect(router.currentRoute.value.query.tenantId).toBe('10002')
|
||||
|
||||
expect(source).toContain('route.query.tenantId')
|
||||
expect(source).toContain('const customerId = computed')
|
||||
expect(source).toContain("name: 'inbound-orders'")
|
||||
expect(source).toContain(':customer-id="customerId"')
|
||||
expect(source).toContain(':inbound-id="inboundId"')
|
||||
expect(source).toContain('缺少所属企业信息,请从入库单列表重新打开')
|
||||
expect(source).not.toContain('PlatformTenantSelector')
|
||||
expect(source).not.toContain('租户')
|
||||
expect(source).toContain('<PageToolbar title="入库单详情"')
|
||||
expect(source).toContain('货品与测量、费用信息、状态历史及其他记录')
|
||||
expect(source).toContain('class="detail-page"')
|
||||
expect(source).toContain('class="detail-page-actions"')
|
||||
expect(source).toContain('返回入库单列表')
|
||||
expect(source).toContain('class="detail-breadcrumb"')
|
||||
expect(source).not.toContain('class="detail-page-footer"')
|
||||
expect(source).toContain('@media (max-width: 720px)')
|
||||
expect(source).toContain('@media (max-width: 360px)')
|
||||
})
|
||||
})
|
||||
@@ -1,75 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft, ChevronRight, Home } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import PageToolbar from '@/shared/components/PageToolbar.vue'
|
||||
import InboundWorkflowWorkspace from '../components/InboundWorkflowWorkspace.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
function routeValue(value: unknown) {
|
||||
if (Array.isArray(value)) return String(value[0] || '')
|
||||
return value === null || value === undefined ? '' : String(value)
|
||||
}
|
||||
|
||||
const inboundId = computed(() => routeValue(route.params.inboundId))
|
||||
const customerId = computed(() => routeValue(route.query.tenantId))
|
||||
|
||||
function backToList() {
|
||||
void router.push({ name: 'inbound-orders', query: customerId.value ? { tenantId: customerId.value } : undefined })
|
||||
}
|
||||
|
||||
function backToHome() {
|
||||
void router.push({ name: 'dashboard' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage as="div" class="business-page inbound-detail-page">
|
||||
<nav class="detail-breadcrumb" aria-label="面包屑">
|
||||
<button type="button" @click="backToHome"><Home :size="14" />首页</button>
|
||||
<ChevronRight :size="14" aria-hidden="true" />
|
||||
<button type="button" @click="backToList">入库单列表</button>
|
||||
<ChevronRight :size="14" aria-hidden="true" />
|
||||
<span aria-current="page">入库单详情</span>
|
||||
</nav>
|
||||
<DetailPageLayout class="detail-page" aria-label="入库单详情">
|
||||
<template #header>
|
||||
<PageToolbar title="入库单详情" description="查看基本信息、货品与测量、费用信息、状态历史及其他记录。">
|
||||
<div class="detail-page-actions">
|
||||
<el-button @click="backToList"><ArrowLeft :size="16" />返回入库单列表</el-button>
|
||||
</div>
|
||||
</PageToolbar>
|
||||
</template>
|
||||
|
||||
<InboundWorkflowWorkspace
|
||||
v-if="inboundId && customerId"
|
||||
:customer-id="customerId"
|
||||
:inbound-id="inboundId"
|
||||
/>
|
||||
<el-empty
|
||||
v-else
|
||||
:description="inboundId ? '缺少所属企业信息,请从入库单列表重新打开' : '缺少有效的入库单标识'"
|
||||
/>
|
||||
</DetailPageLayout>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.inbound-detail-page { min-width: 0; }
|
||||
.detail-breadcrumb { display: flex; min-width: 0; align-items: center; flex-wrap: wrap; gap: 6px; padding: 0 2px; color: var(--cc-muted); font-size: 13px; }
|
||||
.detail-breadcrumb button { display: inline-flex; min-height: 32px; align-items: center; gap: 5px; padding: 4px 6px; border: 0; border-radius: 4px; background: transparent; color: var(--cc-primary); cursor: pointer; font: inherit; }
|
||||
.detail-breadcrumb button:hover { background: #f0f7ff; }
|
||||
.detail-breadcrumb button:focus-visible { outline: 3px solid rgba(102, 126, 234, .2); outline-offset: 1px; }
|
||||
.detail-page { min-width: 0; overflow: hidden; padding: 20px 24px; }
|
||||
.detail-page-actions { display: flex; min-width: 0; align-items: center; justify-content: flex-end; gap: 12px; }
|
||||
@media (max-width: 720px) {
|
||||
.detail-page { padding: 16px; }
|
||||
.detail-page-actions { width: 100%; align-items: stretch; flex-direction: column; }
|
||||
.detail-page-actions :deep(.el-button) { width: 100%; min-height: 44px; }
|
||||
}
|
||||
@media (max-width: 360px) { .detail-page { padding: 12px; } }
|
||||
</style>
|
||||
@@ -169,7 +169,7 @@ describe('InboundOrdersView', () => {
|
||||
}))
|
||||
})
|
||||
|
||||
it('passes the selected backend tab and routes view to the independent detail page', async () => {
|
||||
it('passes the selected backend tab and routes view to the unified order detail page', async () => {
|
||||
const wrapper = mountPage()
|
||||
const toolbar = await selectCustomer(wrapper)
|
||||
toolbar.vm.$emit('search')
|
||||
@@ -182,8 +182,8 @@ describe('InboundOrdersView', () => {
|
||||
;[...document.querySelectorAll<HTMLButtonElement>('button')].filter((button) => button.textContent?.trim() === '查看').at(-1)?.click()
|
||||
await flushPromises()
|
||||
expect(routerPush).toHaveBeenCalledWith({
|
||||
name: 'inbound-order-detail',
|
||||
params: { inboundId: '19001' },
|
||||
name: 'unified-order-detail',
|
||||
params: { orderId: '18001' },
|
||||
query: { tenantId: '10002' },
|
||||
})
|
||||
expect(wrapper.find('[aria-label="入库单详情抽屉"]').exists()).toBe(false)
|
||||
@@ -334,7 +334,8 @@ describe('InboundOrdersView', () => {
|
||||
expect(source).toContain('preference-key="inbound-orders-table"')
|
||||
expect(columnSource.match(/defaultVisible: false/g)?.length).toBeGreaterThanOrEqual(25)
|
||||
expect(toolbarSource).toMatch(/@media \(max-width: 720px\)[\s\S]*width: 100%/)
|
||||
expect(source).toContain('<el-button size="small" type="primary" @click="openDetail')
|
||||
expect(source).toContain('name: \'unified-order-detail\'')
|
||||
expect(source).toContain(':disabled="!(row as InboundOrderSummary).orderId"')
|
||||
expect(source).toContain('<ClipboardCheck :size="15" />处理')
|
||||
expect(source).toContain('<el-button size="small" plain type="primary"')
|
||||
expect(source).not.toContain('<el-button link type="primary" @click="openDetail(row as InboundOrderSummary)">')
|
||||
|
||||
@@ -283,7 +283,11 @@ function applyFilters() {
|
||||
}
|
||||
|
||||
function openDetail(row: InboundOrderSummary) {
|
||||
void router.push({ name: 'inbound-order-detail', params: { inboundId: row.id }, query: { tenantId: row.tenantId } })
|
||||
if (!row.orderId) {
|
||||
ElMessage.warning('该入库单尚未关联订单,暂时无法查看订单详情')
|
||||
return
|
||||
}
|
||||
void router.push({ name: 'unified-order-detail', params: { orderId: row.orderId }, query: { tenantId: row.tenantId } })
|
||||
}
|
||||
async function printCartonLabels(row: InboundOrderSummary) {
|
||||
if (printingInboundId.value) return
|
||||
@@ -411,7 +415,7 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; customerRequestId +
|
||||
</header>
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="columns" preference-key="inbound-orders-table" :loading="loading" :total="result.total" :page-sizes="[20, 50, 100]" row-key="id" :empty-text="listEmptyText" aria-label="入库单列表">
|
||||
<template #pagination-left>当前显示 {{ currentPageCount }} 条 · 共 {{ result.total }} 条</template>
|
||||
<template #actions="{ row }"><div v-if="access.can('platform:firstleg:inbound:view')" class="row-actions"><el-button size="small" type="primary" @click="openDetail(row as InboundOrderSummary)"><ClipboardCheck :size="15" />处理</el-button><el-button size="small" plain type="primary" :loading="printingInboundId === (row as InboundOrderSummary).id" :disabled="Boolean(printingInboundId)" @click="printCartonLabels(row as InboundOrderSummary)"><Printer :size="15" />打印箱唛</el-button><el-button size="small" plain type="primary" @click="openDetail(row as InboundOrderSummary)"><Eye :size="15" />查看</el-button></div><span v-else class="muted-action">无查看权限</span></template>
|
||||
<template #actions="{ row }"><div v-if="access.can('platform:firstleg:inbound:view')" class="row-actions"><el-button size="small" type="primary" :disabled="!(row as InboundOrderSummary).orderId" @click="openDetail(row as InboundOrderSummary)"><ClipboardCheck :size="15" />处理</el-button><el-button size="small" plain type="primary" :loading="printingInboundId === (row as InboundOrderSummary).id" :disabled="Boolean(printingInboundId)" @click="printCartonLabels(row as InboundOrderSummary)"><Printer :size="15" />打印箱唛</el-button><el-button size="small" plain type="primary" :disabled="!(row as InboundOrderSummary).orderId" @click="openDetail(row as InboundOrderSummary)"><Eye :size="15" />查看</el-button></div><span v-else class="muted-action">无查看权限</span></template>
|
||||
<template #inbound="{ row }"><div class="primary-cell"><strong>{{ text((row as InboundOrderSummary).inboundCode) }}</strong><span>订单 {{ text((row as InboundOrderSummary).orderNo) }}</span></div></template>
|
||||
<template #processStatus="{ row }"><el-tag :type="statusType((row as InboundOrderSummary).processStatus)" effect="light">{{ statusLabel((row as InboundOrderSummary).processStatus) }}</el-tag></template>
|
||||
<template #orderStatus="{ row }">{{ codeLabel((row as InboundOrderSummary).orderStatusSnapshot, inboundOrderStatusLabels) }}</template>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useAuthStore } from '@/core/auth/auth'
|
||||
import DataTable from '@/shared/components/DataTable.vue'
|
||||
import { approveMeasurement, getMeasurementReviewDetail, listMeasurementReviewHistory } from './api'
|
||||
import { approveMeasurement, getMeasurementReviewDetail, listMeasurementReviewHistory, rejectMeasurement } from './api'
|
||||
import MeasurementReviewPanel from './MeasurementReviewPanel.vue'
|
||||
|
||||
vi.mock('./api', () => ({
|
||||
@@ -19,7 +19,7 @@ vi.mock('./api', () => ({
|
||||
const detail = {
|
||||
inboundId: '90001', inboundCode: 'RK90001', companyName: '柏悦客户', channelCode: 'SEA', channelName: '海运',
|
||||
processStatus: 'MEASURE_PENDING', measureReviewStatus: 'PENDING', recommendedPalletType: 'STANDARD', reviewReason: null, reviewerId: null, reviewerName: null,
|
||||
measurementId: '70001', sourceType: 'DEVICE', measurementStatus: 'SUBMITTED', version: 4,
|
||||
measurementId: '70001', sourceType: 'DEVICE', measurementStatus: 'SUBMITTED', inboundVersion: 7, version: 4,
|
||||
originalDocumentNo: 'ORD-001', originalGrossWeightKg: 100, originalVolumeCbm: 2, originalPackageCount: 10,
|
||||
actualPackageCount: 10, actualGrossWeightKg: 110, actualVolumeCbm: 2.2, volumeWeightKg: 367.4, actualGoodsName: '测试货物',
|
||||
items: [{ id: '1', boxCode: 'RK90001#10-1', itemNo: '001', boxSpecification: '60×40×30', packageCount: 10, lengthCm: 60, widthCm: 40, heightCm: 30, girthCm: 240, unitWeightKg: 11, unitVolumeCbm: 0.072, totalWeightKg: 110, totalVolumeCbm: 0.72, volumeWeightKg: 120.24, intelligentImageFileId: '30001', machineNo: 'MJ20210327', measuredAt: '2026-08-04T10:30:00+08:00', overLimitWarning: '周长超过渠道限制,请人工核验尺寸后再作出审核决定', outerLengthCm: 61, outerWidthCm: 41, outerHeightCm: 31, remark: '外箱存在轻微变形' }],
|
||||
@@ -92,7 +92,26 @@ describe('MeasurementReviewPanel', () => {
|
||||
'确认测量审核通过',
|
||||
expect.objectContaining({ confirmButtonText: '通过并完成入库' }),
|
||||
)
|
||||
expect(approveMeasurement).toHaveBeenCalledWith('10001', '90001', { measurementId: '70001', expectedVersion: 4 })
|
||||
expect(approveMeasurement).toHaveBeenCalledWith('10001', '90001', { measurementId: '70001', expectedVersion: 7 })
|
||||
expect(wrapper.emitted('changed')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('rejects with the inbound optimistic-lock version instead of the measurement version', async () => {
|
||||
vi.mocked(rejectMeasurement).mockResolvedValue(undefined)
|
||||
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
const reject = wrapper.findAll('button').find((button) => button.text().includes('驳回'))
|
||||
await reject?.trigger('click')
|
||||
await wrapper.find('textarea').setValue('重量图片需要重新核对')
|
||||
const confirm = wrapper.findAll('button').find((button) => button.text().includes('确认驳回'))
|
||||
await confirm?.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(rejectMeasurement).toHaveBeenCalledWith('10001', '90001', {
|
||||
measurementId: '70001',
|
||||
expectedVersion: 7,
|
||||
reason: '重量图片需要重新核对',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -156,7 +156,7 @@ async function confirmApprove() {
|
||||
try {
|
||||
await approveMeasurement(customerId.value, props.inboundId, {
|
||||
measurementId: detail.value.measurementId,
|
||||
expectedVersion: detail.value.version,
|
||||
expectedVersion: detail.value.inboundVersion,
|
||||
})
|
||||
ElMessage.success('审核已通过,费用发布和入库正在完成')
|
||||
await reload()
|
||||
@@ -187,14 +187,14 @@ async function submitReason() {
|
||||
if (kind === 'reject') {
|
||||
await rejectMeasurement(customerId.value, props.inboundId, {
|
||||
measurementId: detail.value.measurementId,
|
||||
expectedVersion: detail.value.version,
|
||||
expectedVersion: detail.value.inboundVersion,
|
||||
reason: value,
|
||||
})
|
||||
ElMessage.success('测量已驳回,已退回测量修正')
|
||||
} else {
|
||||
await releaseRejectedMeasurement(customerId.value, props.inboundId, {
|
||||
measurementId: detail.value.measurementId,
|
||||
expectedVersion: detail.value.version,
|
||||
expectedVersion: detail.value.inboundVersion,
|
||||
reason: value,
|
||||
})
|
||||
ElMessage.success('异常放行已提交,后续计费入库将继续处理')
|
||||
|
||||
@@ -60,6 +60,7 @@ export interface MeasurementReviewDetail {
|
||||
measurementId: string
|
||||
sourceType: string | null
|
||||
measurementStatus: string | null
|
||||
inboundVersion: number
|
||||
version: number
|
||||
originalDocumentNo: string | null
|
||||
originalGrossWeightKg: number | null
|
||||
|
||||
@@ -31,14 +31,4 @@ export const inboundRoutes = [
|
||||
permission: 'platform:firstleg:inbound:list',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'inbounds/:inboundId',
|
||||
name: 'inbound-order-detail',
|
||||
component: () => import('./pages/InboundOrderDetailView.vue'),
|
||||
meta: {
|
||||
title: '入库作业详情',
|
||||
description: '处理测量、称重、审核并查看费用和托盘库位结果',
|
||||
permission: 'platform:firstleg:inbound:view',
|
||||
},
|
||||
},
|
||||
] satisfies RouteRecordRaw[]
|
||||
|
||||
Reference in New Issue
Block a user