发布:将已验 TEST 功能更新至既有生产灰度(2026-10-23) #21

Merged
admin merged 100 commits from release/test into main 2026-10-23 10:51:28 +08:00
8 changed files with 74 additions and 18 deletions
Showing only changes of commit 29f887496a - Show all commits
@@ -32,7 +32,7 @@ type FilterModel = QueryModel & {
expectedArrivalRange: string[]
}
const props = defineProps<{ mode: InboundTaskMode }>()
const props = defineProps<{ mode: InboundTaskMode; title?: string }>()
const router = useRouter()
const access = useAccessPreviewStore()
const page = ref(1)
@@ -356,8 +356,9 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
:aria-label="`${taskLabel}待办列表`"
@selection-change="handleReviewSelectionChange"
>
<template #toolbar-start><h1>{{ isReview ? '测量数据审核' : '订单测量' }}</h1></template>
<template #toolbar-start><h1>{{ title || (isReview ? '测量数据审核' : '订单测量') }}</h1></template>
<template #filters>
<slot name="status-tabs" />
<QueryToolbar
v-model="filters"
:ariaLabel="`${taskLabel}待办筛选`"
+1
View File
@@ -3,6 +3,7 @@ export { createInboundHoldIdempotencyKey, downloadInboundCartonLabelsBatch, down
export { default as DropshipPalletPrintPanel } from './core/DropshipPalletPrintPanel.vue'
export { default as InboundMeasurementPanel } from './measurement/InboundMeasurementPanel.vue'
export { default as InboundMeasurementDialog } from './measurement/InboundMeasurementDialog.vue'
export { default as InboundTaskList } from './components/InboundTaskList.vue'
export { default as MeasurementEvidenceThumbnail } from './measurement/components/MeasurementEvidenceThumbnail.vue'
export { default as InboundFeePanel } from './fee/InboundFeePanel.vue'
export {
@@ -15,7 +15,7 @@ export type UnifiedOrderMainStatus =
export const unifiedOrderStatusOptions: Array<{ value: UnifiedOrderMainStatus; label: string }> = [
{ value: 'PENDING_REVIEW', label: '待审核' },
{ value: 'WAIT_MEASURE', label: '待测量数据' },
{ value: 'WAIT_PALLETIZE', label: '待一件代发组托' },
{ value: 'WAIT_PALLETIZE', label: '一件代发打托' },
{ value: 'WAIT_SPLIT', label: '待分柜' },
{ value: 'LOADING', label: '装柜中' },
{ value: 'LOADED', label: '装柜完成' },
@@ -14,7 +14,10 @@ vi.mock('vue-router', () => ({
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) }))
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mockResolvedValue([]) }))
vi.mock('@/modules/inbounds', () => ({ downloadWeightImage: vi.fn(), getInboundMeasurement: vi.fn() }))
vi.mock('@/modules/inbounds', () => ({
downloadWeightImage: vi.fn(), getInboundMeasurement: vi.fn(),
InboundTaskList: { name: 'InboundTaskList', props: ['mode', 'title'], template: '<section><slot name="status-tabs" /></section>' },
}))
vi.mock('@/modules/downloads', () => ({ createPlatformExportTask: vi.fn() }))
vi.mock('@/shared/components/exportTaskCreatedDialog', () => ({ showExportTaskCreatedDialog: vi.fn() }))
vi.mock('@/modules/unified-orders/workflows', () => ({ UnifiedOrderWorkflowPanel: { template: '<div />' } }))
@@ -51,6 +54,7 @@ function mountView() {
directives: { loading: () => undefined },
stubs: {
AdminPage: { template: '<main><slot /></main>' },
InboundTaskList: false,
ListPageLayout: { template: '<section><slot name="toolbar" /><slot name="state" /><slot /></section>' },
QueryToolbar: { name: 'QueryToolbar', props: ['modelValue'], emits: ['update:modelValue', 'search', 'reset'], template: '<div data-testid="query-toolbar" />' },
DataTable: { name: 'DataTable', props: ['page', 'pageSize', 'data'], emits: ['update:page', 'update:pageSize', 'selection-change'], template: '<div data-testid="data-table"><slot name="toolbar-start" /><slot name="toolbar-actions" /><slot name="filters" /><slot name="notices" /><div v-for="row in data" :key="row.orderId" class="test-row"><slot name="estimatedVolume" :row="row" /><slot name="expectedArrival" :row="row" /></div></div>' },
@@ -94,6 +98,25 @@ describe('UnifiedOrdersView list request coordination', () => {
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(5)
})
it('opens the existing measurement review list beside measurement and returns to order queries', async () => {
const wrapper = mountView()
await flushPromises()
const labels = wrapper.findAll('[role="tab"]').map((tab) => tab.text())
expect(labels.indexOf('测量数据审核')).toBe(labels.indexOf('待测量数据') + 1)
await wrapper.findAll('[role="tab"]').find((tab) => tab.text() === '测量数据审核')!.trigger('click')
await flushPromises()
expect(wrapper.getComponent({ name: 'InboundTaskList' }).props()).toMatchObject({ mode: 'review', title: '订单列表' })
expect(wrapper.get('[aria-selected="true"]').text()).toBe('测量数据审核')
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(1)
await wrapper.findAll('[role="tab"]').find((tab) => tab.text() === '待测量数据')!.trigger('click')
await flushPromises()
expect(wrapper.findComponent({ name: 'InboundTaskList' }).exists()).toBe(false)
expect(listPlatformUnifiedOrders).toHaveBeenLastCalledWith(expect.objectContaining({ mainStatus: 'WAIT_MEASURE' }))
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(2)
})
it('does not apply draft filters to pagination until query is submitted', async () => {
const wrapper = mountView()
await flushPromises()
@@ -62,7 +62,7 @@ describe('UnifiedOrdersView contract', () => {
it('exposes the product main statuses including dropship palletizing', () => {
expect(unifiedOrderStatusOptions.map((item) => item.label)).toEqual([
'待审核', '待测量数据', '待一件代发组托', '待分柜', '装柜中', '装柜完成', '派送完成', '已完成',
'待审核', '待测量数据', '一件代发打托', '待分柜', '装柜中', '装柜完成', '派送完成', '已完成',
])
for (const legacyLabel of ['待客户确认', '差异处理中', '派送中', '进入分柜']) {
expect(source).not.toContain(legacyLabel)
@@ -72,7 +72,7 @@ describe('UnifiedOrdersView contract', () => {
it('renders accessible current-status tabs with keyboard navigation', () => {
expect(source).toContain('role="tablist"')
expect(source).toContain('role="tab"')
expect(source).toContain(':aria-selected="selectedStatus === tab.value"')
expect(source).toContain(':aria-selected="selectedTab === tab.value"')
expect(source).toContain("event.key === 'ArrowRight'")
expect(source).toContain("event.key === 'ArrowLeft'")
expect(source).toContain("event.key === 'Home'")
@@ -82,6 +82,7 @@ import {
updateMeasurementOuterDimensions,
InboundFeePanel,
InboundMeasurementDialog,
InboundTaskList,
MeasurementEvidenceThumbnail,
DropshipPalletPrintPanel,
sendInboundFee,
@@ -322,6 +323,9 @@ const showAdvancedFilters = ref(false)
const filters = ref<FilterModel>(emptyFilters())
const appliedFilters = ref<FilterModel>(emptyFilters())
const selectedStatus = ref<UnifiedOrderMainStatus | ''>('')
const measurementReviewSelected = ref(false)
const selectedTab = computed(() => measurementReviewSelected.value ? 'MEASUREMENT_REVIEW' : selectedStatus.value)
const statusTabList = ref<HTMLElement | null>(null)
const businessInboundCode = computed(() => route.name === 'unified-order-detail-by-inbound-code' && typeof route.params.inboundCode === 'string'
? route.params.inboundCode.trim()
: '')
@@ -584,7 +588,9 @@ async function waitForSavedMeasurementVersion(tenantId: string, inboundId: strin
const statusTabs = computed(() => [
{ value: '' as const, label: '全部' },
...unifiedOrderStatusOptions,
...unifiedOrderStatusOptions.flatMap((tab) => tab.value === 'WAIT_MEASURE' && can('firstleg:inbound:measurement-review')
? [tab, { value: 'MEASUREMENT_REVIEW' as const, label: '测量数据审核' }]
: [tab]),
])
const warehouseOptions = computed(() => warehouses.value.map((warehouse) => ({
@@ -706,8 +712,11 @@ function optionalBoolean(value: string | null | undefined) {
return undefined
}
function selectStatus(status: UnifiedOrderMainStatus | '') {
if (selectedStatus.value === status) return
function selectStatus(status: UnifiedOrderMainStatus | '' | 'MEASUREMENT_REVIEW') {
if (selectedTab.value === status) return
measurementReviewSelected.value = status === 'MEASUREMENT_REVIEW'
if (measurementReviewSelected.value) selectedOrders.value = []
if (status === 'MEASUREMENT_REVIEW') return
selectedStatus.value = status
if (page.value === 1) void loadOrders()
else page.value = 1
@@ -724,8 +733,9 @@ function handleTabKeydown(event: KeyboardEvent, index: number) {
const tab = statusTabs.value[nextIndex]
if (!tab) return
selectStatus(tab.value)
const buttons = (event.currentTarget as HTMLElement).parentElement?.querySelectorAll<HTMLElement>('[role="tab"]')
buttons?.[nextIndex]?.focus()
void nextTick(() => {
statusTabList.value?.querySelectorAll<HTMLElement>('[role="tab"]')[nextIndex]?.focus()
})
}
function can(permission: string) {
@@ -1574,6 +1584,7 @@ async function loadWarehouses() {
}
async function loadOrders() {
if (measurementReviewSelected.value) return
selectedApprovalOrders.value = []
selectedOrders.value = []
loading.value = true
@@ -2691,7 +2702,27 @@ onBeforeUnmount(() => {
</template>
<template v-else>
<ListPageLayout ariaLabel="统一订单列表">
<div v-if="measurementReviewSelected" id="unified-orders-results" role="tabpanel">
<InboundTaskList mode="review" title="订单列表">
<template #status-tabs>
<div ref="statusTabList" class="status-tabs density-status-tabs" role="tablist" aria-label="订单主状态">
<button
v-for="(tab, index) in statusTabs"
:key="tab.value || 'ALL'"
type="button"
role="tab"
:aria-selected="selectedTab === tab.value"
aria-controls="unified-orders-results"
:tabindex="selectedTab === tab.value ? 0 : -1"
:class="['status-tab', { 'is-active': selectedTab === tab.value }]"
@click="selectStatus(tab.value)"
@keydown="handleTabKeydown($event, index)"
>{{ tab.label }}</button>
</div>
</template>
</InboundTaskList>
</div>
<ListPageLayout v-else ariaLabel="统一订单列表">
@@ -2701,16 +2732,16 @@ onBeforeUnmount(() => {
<template #toolbar-start><h1>订单列表</h1></template>
<template #filters>
<div class="orders-query-panel integrated-query-group">
<div class="status-tabs density-status-tabs" role="tablist" aria-label="订单主状态">
<div ref="statusTabList" class="status-tabs density-status-tabs" role="tablist" aria-label="订单主状态">
<button
v-for="(tab, index) in statusTabs"
:key="tab.value || 'ALL'"
type="button"
role="tab"
:aria-selected="selectedStatus === tab.value"
:aria-selected="selectedTab === tab.value"
aria-controls="unified-orders-results"
:tabindex="selectedStatus === tab.value ? 0 : -1"
:class="['status-tab', { 'is-active': selectedStatus === tab.value }]"
:tabindex="selectedTab === tab.value ? 0 : -1"
:class="['status-tab', { 'is-active': selectedTab === tab.value }]"
@click="selectStatus(tab.value)"
@keydown="handleTabKeydown($event, index)"
>{{ tab.label }}</button>
+1 -1
View File
@@ -10,7 +10,7 @@ import {
describe('unified order status labels', () => {
it('maps order, review and loading statuses to domain-specific Chinese', () => {
expect(unifiedOrderStatusLabel('WAIT_PALLETIZE')).toBe('待一件代发组托')
expect(unifiedOrderStatusLabel('WAIT_PALLETIZE')).toBe('一件代发打托')
expect(unifiedOrderStatusLabel('WAIT_SPLIT')).toBe('待分柜')
expect(unifiedOrderReviewStatusLabel('APPROVED')).toBe('审核通过')
expect(unifiedOrderLoadingStatusLabel('LOADED')).toBe('已配载')
+1 -1
View File
@@ -9,7 +9,7 @@ const orderStatusLabels: Record<string, string> = {
CANCELLED: '已取消',
PENDING_REVIEW: '待审核',
WAIT_MEASURE: '待测量数据',
WAIT_PALLETIZE: '待一件代发组托',
WAIT_PALLETIZE: '一件代发打托',
WAIT_SPLIT: '待分柜',
LOADING: '装柜中',
LOADED: '装柜完成',