Merge branch 'release/test' of http://8.163.30.128:10082/html/admin-web into release/test
This commit is contained in:
@@ -32,7 +32,7 @@ type FilterModel = QueryModel & {
|
|||||||
expectedArrivalRange: string[]
|
expectedArrivalRange: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{ mode: InboundTaskMode }>()
|
const props = defineProps<{ mode: InboundTaskMode; title?: string }>()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const access = useAccessPreviewStore()
|
const access = useAccessPreviewStore()
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
@@ -356,8 +356,9 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
|
|||||||
:aria-label="`${taskLabel}待办列表`"
|
:aria-label="`${taskLabel}待办列表`"
|
||||||
@selection-change="handleReviewSelectionChange"
|
@selection-change="handleReviewSelectionChange"
|
||||||
>
|
>
|
||||||
<template #toolbar-start><h1>{{ isReview ? '测量数据审核' : '订单测量' }}</h1></template>
|
<template #toolbar-start><h1>{{ title || (isReview ? '测量数据审核' : '订单测量') }}</h1></template>
|
||||||
<template #filters>
|
<template #filters>
|
||||||
|
<slot name="status-tabs" />
|
||||||
<QueryToolbar
|
<QueryToolbar
|
||||||
v-model="filters"
|
v-model="filters"
|
||||||
:ariaLabel="`${taskLabel}待办筛选`"
|
:ariaLabel="`${taskLabel}待办筛选`"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export { createInboundHoldIdempotencyKey, downloadInboundCartonLabelsBatch, down
|
|||||||
export { default as DropshipPalletPrintPanel } from './core/DropshipPalletPrintPanel.vue'
|
export { default as DropshipPalletPrintPanel } from './core/DropshipPalletPrintPanel.vue'
|
||||||
export { default as InboundMeasurementPanel } from './measurement/InboundMeasurementPanel.vue'
|
export { default as InboundMeasurementPanel } from './measurement/InboundMeasurementPanel.vue'
|
||||||
export { default as InboundMeasurementDialog } from './measurement/InboundMeasurementDialog.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 MeasurementEvidenceThumbnail } from './measurement/components/MeasurementEvidenceThumbnail.vue'
|
||||||
export { default as InboundFeePanel } from './fee/InboundFeePanel.vue'
|
export { default as InboundFeePanel } from './fee/InboundFeePanel.vue'
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
import { getOutbound } from '../api/outbounds'
|
import { getOutbound } from '../api/outbounds'
|
||||||
import OutboundDetailView from './OutboundDetailView.vue'
|
import OutboundDetailView from './OutboundDetailView.vue'
|
||||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||||
|
import { getPlatformWarehouse } from '@/modules/warehouses'
|
||||||
|
|
||||||
const canPermission = vi.hoisted(() => vi.fn((_code: string) => false))
|
const canPermission = vi.hoisted(() => vi.fn((_code: string) => false))
|
||||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) }))
|
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) }))
|
||||||
vi.mock('vue-router', () => ({ useRoute: () => ({ params: { outboundNo: 'OUT/1' } }), useRouter: () => ({ push: vi.fn() }) }))
|
vi.mock('vue-router', () => ({ useRoute: () => ({ params: { outboundNo: 'OUT/1' } }), useRouter: () => ({ push: vi.fn() }) }))
|
||||||
vi.mock('../api/outbounds', () => ({ getOutbound: vi.fn(), cancelOutbound: vi.fn() }))
|
vi.mock('../api/outbounds', () => ({ getOutbound: vi.fn(), cancelOutbound: vi.fn() }))
|
||||||
|
vi.mock('@/modules/warehouses', () => ({ getPlatformWarehouse: vi.fn() }))
|
||||||
|
|
||||||
describe('OutboundDetailView', () => {
|
describe('OutboundDetailView', () => {
|
||||||
it.each([
|
it.each([
|
||||||
@@ -25,6 +27,7 @@ describe('OutboundDetailView', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.mocked(getPlatformWarehouse).mockReset().mockRejectedValue(new Error('Warehouse unavailable'))
|
||||||
canPermission.mockReset().mockReturnValue(false)
|
canPermission.mockReset().mockReturnValue(false)
|
||||||
vi.mocked(getOutbound).mockReset().mockResolvedValue({
|
vi.mocked(getOutbound).mockReset().mockResolvedValue({
|
||||||
outbound: { id: '1', outboundNo: 'OUT/1', dispatchNo: 'PC-1', status: 'PICKED', palletCount: 2, packageCount: 20, createdAt: '2026-09-01T10:00:00' },
|
outbound: { id: '1', outboundNo: 'OUT/1', dispatchNo: 'PC-1', status: 'PICKED', palletCount: 2, packageCount: 20, createdAt: '2026-09-01T10:00:00' },
|
||||||
@@ -87,6 +90,24 @@ describe('OutboundDetailView', () => {
|
|||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('resolves the warehouse for a discard outbound without deliveries', async () => {
|
||||||
|
vi.mocked(getOutbound).mockResolvedValueOnce({
|
||||||
|
outbound: { id: '1', outboundNo: 'OB-WH-20261019-151681', outboundType: 'DISCARD_OUTBOUND', overseasWarehouseId: 'warehouse-internal-id', packageCount: 1 },
|
||||||
|
deliveries: [],
|
||||||
|
})
|
||||||
|
vi.mocked(getPlatformWarehouse).mockResolvedValueOnce({
|
||||||
|
warehouse: { id: 'warehouse-internal-id', tenantId: '0', warehouseCode: 'LAX', internalCode: null, warehouseName: '洛杉矶仓', warehouseType: 'OVERSEAS', countryCode: 'US', timezone: 'America/Los_Angeles', contactName: null, contactPhone: null, contactEmail: null, status: 'ACTIVE', remark: null },
|
||||||
|
addresses: [], capabilities: [],
|
||||||
|
})
|
||||||
|
const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } })
|
||||||
|
await flushPromises()
|
||||||
|
expect(getPlatformWarehouse).toHaveBeenCalledWith('warehouse-internal-id')
|
||||||
|
const label = wrapper.findAll('.el-descriptions__label').find(item => item.text() === '海外仓')!
|
||||||
|
expect(label.element.nextElementSibling?.textContent?.trim()).toBe('洛杉矶仓')
|
||||||
|
expect(wrapper.text()).not.toContain('warehouse-internal-id')
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['warehouse-internal-id', '洛杉矶仓', '洛杉矶仓'],
|
['warehouse-internal-id', '洛杉矶仓', '洛杉矶仓'],
|
||||||
['warehouse-internal-id', null, '—'],
|
['warehouse-internal-id', null, '—'],
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|||||||
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
|
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
|
||||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||||
|
import { getPlatformWarehouse } from '@/modules/warehouses'
|
||||||
import { cancelOutbound, getOutbound, type OutboundDetail } from '../api/outbounds'
|
import { cancelOutbound, getOutbound, type OutboundDetail } from '../api/outbounds'
|
||||||
import { formatOverseasDateTime } from '../lastMilePresentation'
|
import { formatOverseasDateTime } from '../lastMilePresentation'
|
||||||
import { overseasWarehousePermissions as permission } from '../permissions'
|
import { overseasWarehousePermissions as permission } from '../permissions'
|
||||||
@@ -21,6 +22,7 @@ const route = useRoute(); const router = useRouter(); const access = useAccessPr
|
|||||||
const outboundNo = computed(() => String(route.params.outboundNo || '').trim())
|
const outboundNo = computed(() => String(route.params.outboundNo || '').trim())
|
||||||
const detail = ref<OutboundDetail | null>(null); const loading = ref(false); const cancelling = ref(false)
|
const detail = ref<OutboundDetail | null>(null); const loading = ref(false); const cancelling = ref(false)
|
||||||
const loadError = ref<ApiErrorDetails | null>(null); const mutationError = ref<ApiErrorDetails | null>(null)
|
const loadError = ref<ApiErrorDetails | null>(null); const mutationError = ref<ApiErrorDetails | null>(null)
|
||||||
|
const warehouseName = ref('')
|
||||||
const statusLabels: Record<string, string> = { WAIT_PICK: '待拣货', WAIT_OUTBOUND: '待出库', PICKING: '拣货中', PICKED: '已拣货', OUTBOUNDED: '已出库', CANCELLED: '已取消' }
|
const statusLabels: Record<string, string> = { WAIT_PICK: '待拣货', WAIT_OUTBOUND: '待出库', PICKING: '拣货中', PICKED: '已拣货', OUTBOUNDED: '已出库', CANCELLED: '已取消' }
|
||||||
const palletColumns: DataTableColumn[] = [
|
const palletColumns: DataTableColumn[] = [
|
||||||
{ key: 'palletNo', label: '托盘码', width: 220 },
|
{ key: 'palletNo', label: '托盘码', width: 220 },
|
||||||
@@ -40,8 +42,23 @@ function show(value: unknown) { const text = String(value ?? '').trim(); return
|
|||||||
// A warehouse ID is only a join key; never render it as a warehouse name.
|
// A warehouse ID is only a join key; never render it as a warehouse name.
|
||||||
const warehouseLabel = computed(() => {
|
const warehouseLabel = computed(() => {
|
||||||
const outboundWarehouseId = detail.value?.outbound.overseasWarehouseId
|
const outboundWarehouseId = detail.value?.outbound.overseasWarehouseId
|
||||||
|
if (!outboundWarehouseId) return '—'
|
||||||
const delivery = detail.value?.deliveries?.find(item => item.overseasWarehouseId === outboundWarehouseId)
|
const delivery = detail.value?.deliveries?.find(item => item.overseasWarehouseId === outboundWarehouseId)
|
||||||
return outboundWarehouseId && delivery?.overseasWarehouseId === outboundWarehouseId ? show(delivery.overseasWarehouseName) : '—'
|
return show(delivery?.overseasWarehouseName?.trim() || warehouseName.value)
|
||||||
|
})
|
||||||
|
// 废弃等出库没有关联派送单,直接从仓库主档补充名称。
|
||||||
|
watch(() => detail.value, async (value, _previous, onCleanup) => {
|
||||||
|
warehouseName.value = ''
|
||||||
|
let current = true
|
||||||
|
onCleanup(() => { current = false })
|
||||||
|
const warehouseId = value?.outbound.overseasWarehouseId
|
||||||
|
if (!warehouseId || value?.deliveries?.some(item => item.overseasWarehouseId === warehouseId && item.overseasWarehouseName?.trim())) return
|
||||||
|
try {
|
||||||
|
const result = await getPlatformWarehouse(warehouseId)
|
||||||
|
if (current && String(result.warehouse.id) === String(warehouseId)) warehouseName.value = result.warehouse.warehouseName
|
||||||
|
} catch {
|
||||||
|
// 主档不可用时保留占位,出库详情仍可查看,不展示内部 ID。
|
||||||
|
}
|
||||||
})
|
})
|
||||||
function statusLabel(value: unknown) { const code = String(value || ''); return statusLabels[code] || show(code) }
|
function statusLabel(value: unknown) { const code = String(value || ''); return statusLabels[code] || show(code) }
|
||||||
function outboundTypeLabel(value?: string | null) { return value === 'RELABEL_OUTBOUND' ? '贴标出库' : value === 'DISCARD_OUTBOUND' ? '废弃出库' : value === 'WAREHOUSE_TRANSFER_OUTBOUND' ? '仓储转运出库' : value === 'ONE_CLICK_DROPSHIP' ? '一件代发出库' : value === 'RETURN_OUTBOUND' ? '退货出库' : value === 'DISPATCH_DELIVERY' ? '派送出库' : show(value) }
|
function outboundTypeLabel(value?: string | null) { return value === 'RELABEL_OUTBOUND' ? '贴标出库' : value === 'DISCARD_OUTBOUND' ? '废弃出库' : value === 'WAREHOUSE_TRANSFER_OUTBOUND' ? '仓储转运出库' : value === 'ONE_CLICK_DROPSHIP' ? '一件代发出库' : value === 'RETURN_OUTBOUND' ? '退货出库' : value === 'DISPATCH_DELIVERY' ? '派送出库' : show(value) }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export type UnifiedOrderMainStatus =
|
|||||||
export const unifiedOrderStatusOptions: Array<{ value: UnifiedOrderMainStatus; label: string }> = [
|
export const unifiedOrderStatusOptions: Array<{ value: UnifiedOrderMainStatus; label: string }> = [
|
||||||
{ value: 'PENDING_REVIEW', label: '待审核' },
|
{ value: 'PENDING_REVIEW', label: '待审核' },
|
||||||
{ value: 'WAIT_MEASURE', label: '待测量数据' },
|
{ value: 'WAIT_MEASURE', label: '待测量数据' },
|
||||||
{ value: 'WAIT_PALLETIZE', label: '待一件代发组托' },
|
{ value: 'WAIT_PALLETIZE', label: '一件代发打托' },
|
||||||
{ value: 'WAIT_SPLIT', label: '待分柜' },
|
{ value: 'WAIT_SPLIT', label: '待分柜' },
|
||||||
{ value: 'LOADING', label: '装柜中' },
|
{ value: 'LOADING', label: '装柜中' },
|
||||||
{ value: 'LOADED', label: '装柜完成' },
|
{ value: 'LOADED', label: '装柜完成' },
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ vi.mock('vue-router', () => ({
|
|||||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
|
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/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/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('@/modules/downloads', () => ({ createPlatformExportTask: vi.fn() }))
|
||||||
vi.mock('@/shared/components/exportTaskCreatedDialog', () => ({ showExportTaskCreatedDialog: vi.fn() }))
|
vi.mock('@/shared/components/exportTaskCreatedDialog', () => ({ showExportTaskCreatedDialog: vi.fn() }))
|
||||||
vi.mock('@/modules/unified-orders/workflows', () => ({ UnifiedOrderWorkflowPanel: { template: '<div />' } }))
|
vi.mock('@/modules/unified-orders/workflows', () => ({ UnifiedOrderWorkflowPanel: { template: '<div />' } }))
|
||||||
@@ -51,6 +54,7 @@ function mountView() {
|
|||||||
directives: { loading: () => undefined },
|
directives: { loading: () => undefined },
|
||||||
stubs: {
|
stubs: {
|
||||||
AdminPage: { template: '<main><slot /></main>' },
|
AdminPage: { template: '<main><slot /></main>' },
|
||||||
|
InboundTaskList: false,
|
||||||
ListPageLayout: { template: '<section><slot name="toolbar" /><slot name="state" /><slot /></section>' },
|
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" />' },
|
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>' },
|
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)
|
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 () => {
|
it('does not apply draft filters to pagination until query is submitted', async () => {
|
||||||
const wrapper = mountView()
|
const wrapper = mountView()
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ describe('UnifiedOrdersView contract', () => {
|
|||||||
|
|
||||||
it('exposes the product main statuses including dropship palletizing', () => {
|
it('exposes the product main statuses including dropship palletizing', () => {
|
||||||
expect(unifiedOrderStatusOptions.map((item) => item.label)).toEqual([
|
expect(unifiedOrderStatusOptions.map((item) => item.label)).toEqual([
|
||||||
'待审核', '待测量数据', '待一件代发组托', '待分柜', '装柜中', '装柜完成', '派送完成', '已完成',
|
'待审核', '待测量数据', '一件代发打托', '待分柜', '装柜中', '装柜完成', '派送完成', '已完成',
|
||||||
])
|
])
|
||||||
for (const legacyLabel of ['待客户确认', '差异处理中', '派送中', '进入分柜']) {
|
for (const legacyLabel of ['待客户确认', '差异处理中', '派送中', '进入分柜']) {
|
||||||
expect(source).not.toContain(legacyLabel)
|
expect(source).not.toContain(legacyLabel)
|
||||||
@@ -72,7 +72,7 @@ describe('UnifiedOrdersView contract', () => {
|
|||||||
it('renders accessible current-status tabs with keyboard navigation', () => {
|
it('renders accessible current-status tabs with keyboard navigation', () => {
|
||||||
expect(source).toContain('role="tablist"')
|
expect(source).toContain('role="tablist"')
|
||||||
expect(source).toContain('role="tab"')
|
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 === 'ArrowRight'")
|
||||||
expect(source).toContain("event.key === 'ArrowLeft'")
|
expect(source).toContain("event.key === 'ArrowLeft'")
|
||||||
expect(source).toContain("event.key === 'Home'")
|
expect(source).toContain("event.key === 'Home'")
|
||||||
@@ -237,7 +237,7 @@ describe('UnifiedOrdersView contract', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('keeps the title, search, errors and results in the integrated list', () => {
|
it('keeps the title, search, errors and results in the integrated list', () => {
|
||||||
expect(source).toContain('<ListPageLayout ariaLabel="统一订单列表">')
|
expect(source).toContain('<ListPageLayout v-else ariaLabel="统一订单列表">')
|
||||||
expect(source).toContain('<template #filters>')
|
expect(source).toContain('<template #filters>')
|
||||||
expect(source).toContain('<template #notices>')
|
expect(source).toContain('<template #notices>')
|
||||||
expect(source).toContain('<template #default>')
|
expect(source).toContain('<template #default>')
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ import {
|
|||||||
updateMeasurementOuterDimensions,
|
updateMeasurementOuterDimensions,
|
||||||
InboundFeePanel,
|
InboundFeePanel,
|
||||||
InboundMeasurementDialog,
|
InboundMeasurementDialog,
|
||||||
|
InboundTaskList,
|
||||||
MeasurementEvidenceThumbnail,
|
MeasurementEvidenceThumbnail,
|
||||||
DropshipPalletPrintPanel,
|
DropshipPalletPrintPanel,
|
||||||
sendInboundFee,
|
sendInboundFee,
|
||||||
@@ -322,6 +323,9 @@ const showAdvancedFilters = ref(false)
|
|||||||
const filters = ref<FilterModel>(emptyFilters())
|
const filters = ref<FilterModel>(emptyFilters())
|
||||||
const appliedFilters = ref<FilterModel>(emptyFilters())
|
const appliedFilters = ref<FilterModel>(emptyFilters())
|
||||||
const selectedStatus = ref<UnifiedOrderMainStatus | ''>('')
|
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'
|
const businessInboundCode = computed(() => route.name === 'unified-order-detail-by-inbound-code' && typeof route.params.inboundCode === 'string'
|
||||||
? route.params.inboundCode.trim()
|
? route.params.inboundCode.trim()
|
||||||
: '')
|
: '')
|
||||||
@@ -584,7 +588,9 @@ async function waitForSavedMeasurementVersion(tenantId: string, inboundId: strin
|
|||||||
|
|
||||||
const statusTabs = computed(() => [
|
const statusTabs = computed(() => [
|
||||||
{ value: '' as const, label: '全部' },
|
{ 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) => ({
|
const warehouseOptions = computed(() => warehouses.value.map((warehouse) => ({
|
||||||
@@ -706,8 +712,11 @@ function optionalBoolean(value: string | null | undefined) {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectStatus(status: UnifiedOrderMainStatus | '') {
|
function selectStatus(status: UnifiedOrderMainStatus | '' | 'MEASUREMENT_REVIEW') {
|
||||||
if (selectedStatus.value === status) return
|
if (selectedTab.value === status) return
|
||||||
|
measurementReviewSelected.value = status === 'MEASUREMENT_REVIEW'
|
||||||
|
if (measurementReviewSelected.value) selectedOrders.value = []
|
||||||
|
if (status === 'MEASUREMENT_REVIEW') return
|
||||||
selectedStatus.value = status
|
selectedStatus.value = status
|
||||||
if (page.value === 1) void loadOrders()
|
if (page.value === 1) void loadOrders()
|
||||||
else page.value = 1
|
else page.value = 1
|
||||||
@@ -724,8 +733,9 @@ function handleTabKeydown(event: KeyboardEvent, index: number) {
|
|||||||
const tab = statusTabs.value[nextIndex]
|
const tab = statusTabs.value[nextIndex]
|
||||||
if (!tab) return
|
if (!tab) return
|
||||||
selectStatus(tab.value)
|
selectStatus(tab.value)
|
||||||
const buttons = (event.currentTarget as HTMLElement).parentElement?.querySelectorAll<HTMLElement>('[role="tab"]')
|
void nextTick(() => {
|
||||||
buttons?.[nextIndex]?.focus()
|
statusTabList.value?.querySelectorAll<HTMLElement>('[role="tab"]')[nextIndex]?.focus()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function can(permission: string) {
|
function can(permission: string) {
|
||||||
@@ -1574,6 +1584,7 @@ async function loadWarehouses() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadOrders() {
|
async function loadOrders() {
|
||||||
|
if (measurementReviewSelected.value) return
|
||||||
selectedApprovalOrders.value = []
|
selectedApprovalOrders.value = []
|
||||||
selectedOrders.value = []
|
selectedOrders.value = []
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -2691,7 +2702,27 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<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 #toolbar-start><h1>订单列表</h1></template>
|
||||||
<template #filters>
|
<template #filters>
|
||||||
<div class="orders-query-panel integrated-query-group">
|
<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
|
<button
|
||||||
v-for="(tab, index) in statusTabs"
|
v-for="(tab, index) in statusTabs"
|
||||||
:key="tab.value || 'ALL'"
|
:key="tab.value || 'ALL'"
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
:aria-selected="selectedStatus === tab.value"
|
:aria-selected="selectedTab === tab.value"
|
||||||
aria-controls="unified-orders-results"
|
aria-controls="unified-orders-results"
|
||||||
:tabindex="selectedStatus === tab.value ? 0 : -1"
|
:tabindex="selectedTab === tab.value ? 0 : -1"
|
||||||
:class="['status-tab', { 'is-active': selectedStatus === tab.value }]"
|
:class="['status-tab', { 'is-active': selectedTab === tab.value }]"
|
||||||
@click="selectStatus(tab.value)"
|
@click="selectStatus(tab.value)"
|
||||||
@keydown="handleTabKeydown($event, index)"
|
@keydown="handleTabKeydown($event, index)"
|
||||||
>{{ tab.label }}</button>
|
>{{ tab.label }}</button>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
|
|
||||||
describe('unified order status labels', () => {
|
describe('unified order status labels', () => {
|
||||||
it('maps order, review and loading statuses to domain-specific Chinese', () => {
|
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(unifiedOrderStatusLabel('WAIT_SPLIT')).toBe('待分柜')
|
||||||
expect(unifiedOrderReviewStatusLabel('APPROVED')).toBe('审核通过')
|
expect(unifiedOrderReviewStatusLabel('APPROVED')).toBe('审核通过')
|
||||||
expect(unifiedOrderLoadingStatusLabel('LOADED')).toBe('已配载')
|
expect(unifiedOrderLoadingStatusLabel('LOADED')).toBe('已配载')
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const orderStatusLabels: Record<string, string> = {
|
|||||||
CANCELLED: '已取消',
|
CANCELLED: '已取消',
|
||||||
PENDING_REVIEW: '待审核',
|
PENDING_REVIEW: '待审核',
|
||||||
WAIT_MEASURE: '待测量数据',
|
WAIT_MEASURE: '待测量数据',
|
||||||
WAIT_PALLETIZE: '待一件代发组托',
|
WAIT_PALLETIZE: '一件代发打托',
|
||||||
WAIT_SPLIT: '待分柜',
|
WAIT_SPLIT: '待分柜',
|
||||||
LOADING: '装柜中',
|
LOADING: '装柜中',
|
||||||
LOADED: '装柜完成',
|
LOADED: '装柜完成',
|
||||||
|
|||||||
Reference in New Issue
Block a user