优化 #6
@@ -41,7 +41,7 @@ describe('adminNavigation', () => {
|
|||||||
expect(adminNavigation.find((item) => item.key === 'container-management')?.children?.map((item) => item.title)).toEqual([
|
expect(adminNavigation.find((item) => item.key === 'container-management')?.children?.map((item) => item.title)).toEqual([
|
||||||
'订舱列表',
|
'订舱列表',
|
||||||
'拖车列表',
|
'拖车列表',
|
||||||
'送柜报关',
|
'货柜列表',
|
||||||
'清关提柜',
|
'清关提柜',
|
||||||
])
|
])
|
||||||
expect(adminNavigation.find((item) => item.key === 'overseas-inventory')?.children?.map((item) => item.title)).toEqual([
|
expect(adminNavigation.find((item) => item.key === 'overseas-inventory')?.children?.map((item) => item.title)).toEqual([
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const adminNavigation: AdminNavigationNode[] = [
|
|||||||
children: [
|
children: [
|
||||||
{ key: 'booking-list', title: '订舱列表', icon: ClipboardList, path: '/ocean-bookings', permission: 'platform:firstleg:ocean-booking:list' },
|
{ key: 'booking-list', title: '订舱列表', icon: ClipboardList, path: '/ocean-bookings', permission: 'platform:firstleg:ocean-booking:list' },
|
||||||
{ key: 'truck-list', title: '拖车列表', icon: Truck, path: '/trucking', permission: 'platform:firstleg:truck-appointment:list' },
|
{ key: 'truck-list', title: '拖车列表', icon: Truck, path: '/trucking', permission: 'platform:firstleg:truck-appointment:list' },
|
||||||
{ key: 'domestic-container-workbench', title: '送柜报关', icon: WarehouseIcon, path: '/container-loading/domestic', permission: 'platform:firstleg:container:list' },
|
{ key: 'domestic-container-workbench', title: '货柜列表', icon: WarehouseIcon, path: '/container-loading/domestic', permission: 'platform:firstleg:container:list' },
|
||||||
{ key: 'overseas-container-workbench', title: '清关提柜', icon: ClipboardList, path: '/container-loading/overseas', permission: 'platform:firstleg:terminal-drayage:list' },
|
{ key: 'overseas-container-workbench', title: '清关提柜', icon: ClipboardList, path: '/container-loading/overseas', permission: 'platform:firstleg:terminal-drayage:list' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const containerLoadingRoutes = [
|
|||||||
path: 'container-loading/domestic',
|
path: 'container-loading/domestic',
|
||||||
name: 'domestic-container-workbench',
|
name: 'domestic-container-workbench',
|
||||||
component: () => import('./pages/DomesticContainersView.vue'),
|
component: () => import('./pages/DomesticContainersView.vue'),
|
||||||
meta: { title: '送柜报关', description: '管理国内送柜、装柜、出口报关与出仓', permission: 'platform:firstleg:container:list' },
|
meta: { title: '货柜列表', description: '管理国内送柜、装柜、出口报关与出仓', permission: 'platform:firstleg:container:list' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'container-loading/domestic/:containerId',
|
path: 'container-loading/domestic/:containerId',
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function mountView() {
|
|||||||
AdminPage: { template: '<main><slot /></main>' },
|
AdminPage: { template: '<main><slot /></main>' },
|
||||||
ListPageLayout: { template: '<section><slot name="toolbar" /><slot name="state" /><slot /></section>' },
|
ListPageLayout: { template: '<section><slot name="toolbar" /><slot name="state" /><slot /></section>' },
|
||||||
QueryToolbar: { template: '<div />' },
|
QueryToolbar: { template: '<div />' },
|
||||||
DataTable: { name: 'DataTable', props: ['data', 'columns'], emits: ['selection-change'], template: '<section><slot name="toolbar-actions" /><button data-testid="select" @click="$emit(\'selection-change\', data)">勾选两票</button><template v-for="row in data" :key="row.orderId"><slot name="actions" :row="row" /><slot name="inboundCode" :row="row" /></template></section>' },
|
DataTable: { name: 'DataTable', props: ['data', 'columns', 'rowClassName'], emits: ['selection-change'], template: '<section><slot name="toolbar-actions" /><button data-testid="select" @click="$emit(\'selection-change\', data)">勾选两票</button><template v-for="row in data" :key="row.orderId"><slot name="actions" :row="row" /><slot name="inboundCode" :row="row" /></template></section>' },
|
||||||
AppDialog: { name: 'AppDialog', props: ['modelValue', 'confirmText', 'confirmDisabled', 'confirmLoading'], emits: ['confirm', 'update:modelValue'], methods: { close() { this.$emit('update:modelValue', false) } }, template: '<section v-if="modelValue"><slot /><slot name="footer" :close="close" :submitting="confirmLoading"><button data-testid="dialog-confirm" :disabled="confirmDisabled" @click="$emit(\'confirm\')">{{ confirmText }}</button></slot></section>' },
|
AppDialog: { name: 'AppDialog', props: ['modelValue', 'confirmText', 'confirmDisabled', 'confirmLoading'], emits: ['confirm', 'update:modelValue'], methods: { close() { this.$emit('update:modelValue', false) } }, template: '<section v-if="modelValue"><slot /><slot name="footer" :close="close" :submitting="confirmLoading"><button data-testid="dialog-confirm" :disabled="confirmDisabled" @click="$emit(\'confirm\')">{{ confirmText }}</button></slot></section>' },
|
||||||
ApiErrorMeta: { props: ['details'], template: '<div>{{ details.code }} {{ details.requestId }} {{ details.traceId }}</div>' },
|
ApiErrorMeta: { props: ['details'], template: '<div>{{ details.code }} {{ details.requestId }} {{ details.traceId }}</div>' },
|
||||||
CopyButton: { props: ['value', 'label'], template: '<button :data-copy="value">复制 ID</button>' },
|
CopyButton: { props: ['value', 'label'], template: '<button :data-copy="value">复制 ID</button>' },
|
||||||
@@ -199,6 +199,19 @@ describe('UnifiedOrdersView co-load group behavior', () => {
|
|||||||
expect(wrapper.text()).not.toContain('5001')
|
expect(wrapper.text()).not.toContain('5001')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('highlights rows in a current co-load group but not cancelled groups', async () => {
|
||||||
|
const activeRow = { ...rows[0], coLoadGroup: { status: 'ACTIVE', memberCount: 2, memberInboundCodes: ['IN-1', 'IN-2'] } }
|
||||||
|
const cancelledRow = { ...rows[1], coLoadGroup: { status: 'CANCELLED', memberCount: 2, memberInboundCodes: ['IN-1', 'IN-2'] } }
|
||||||
|
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: [activeRow, cancelledRow], page: 1, size: 20, total: 2, pages: 1 })
|
||||||
|
const wrapper = mountView()
|
||||||
|
await flushPromises()
|
||||||
|
const rowClassName = wrapper.findComponent({ name: 'DataTable' }).props('rowClassName') as (scope: { row: UnifiedOrderResource }) => string
|
||||||
|
|
||||||
|
expect(rowClassName({ row: activeRow })).toBe('is-coload-group-row')
|
||||||
|
expect(rowClassName({ row: cancelledRow })).toBe('')
|
||||||
|
expect(rowClassName({ row: rows[0] })).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
it('cancels an ACTIVE group by the clicked member inbound code and preserves dialog error evidence', async () => {
|
it('cancels an ACTIVE group by the clicked member inbound code and preserves dialog error evidence', async () => {
|
||||||
const rowsWithGroup = rows.map((row) => ({ ...row, coLoadGroup: { status: 'ACTIVE', memberCount: 2, memberInboundCodes: ['IN-1', 'IN-2'] } }))
|
const rowsWithGroup = rows.map((row) => ({ ...row, coLoadGroup: { status: 'ACTIVE', memberCount: 2, memberInboundCodes: ['IN-1', 'IN-2'] } }))
|
||||||
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rowsWithGroup, page: 1, size: 20, total: 2, pages: 1 })
|
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rowsWithGroup, page: 1, size: 20, total: 2, pages: 1 })
|
||||||
|
|||||||
@@ -232,6 +232,8 @@ describe('UnifiedOrdersView contract', () => {
|
|||||||
expect(source).toContain("{ key: 'detail', label: '详情', priority: 500 }")
|
expect(source).toContain("{ key: 'detail', label: '详情', priority: 500 }")
|
||||||
expect(source).toContain('...unifiedOrderSecondaryActions(row).map')
|
expect(source).toContain('...unifiedOrderSecondaryActions(row).map')
|
||||||
expect(source).toContain('<RowActions :actions="unifiedOrderRowActions(row as UnifiedOrderResource)"')
|
expect(source).toContain('<RowActions :actions="unifiedOrderRowActions(row as UnifiedOrderResource)"')
|
||||||
|
expect(source).toContain("if (action === 'approve') return '审核'")
|
||||||
|
expect(source).not.toContain("if (action === 'approve') return '通过'")
|
||||||
})
|
})
|
||||||
|
|
||||||
it('opens smart loading from the order table toolbar with its own permission', () => {
|
it('opens smart loading from the order table toolbar with its own permission', () => {
|
||||||
|
|||||||
@@ -1811,6 +1811,10 @@ function coLoadGroup(row: UnifiedOrderResource) {
|
|||||||
return row.coLoadGroup || null
|
return row.coLoadGroup || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unifiedOrderRowClassName({ row }: { row: UnifiedOrderResource }) {
|
||||||
|
return row.coLoadGroup && row.coLoadGroup.status !== 'CANCELLED' ? 'is-coload-group-row' : ''
|
||||||
|
}
|
||||||
|
|
||||||
function coLoadGroupMemberLabel(group: CoLoadGroup) {
|
function coLoadGroupMemberLabel(group: CoLoadGroup) {
|
||||||
const orderCount = new Set(group.members.map((member) => member.orderId)).size
|
const orderCount = new Set(group.members.map((member) => member.orderId)).size
|
||||||
return group.members.length === orderCount
|
return group.members.length === orderCount
|
||||||
@@ -2009,7 +2013,7 @@ function moreUnifiedOrderActionsLabel(row: UnifiedOrderResource) {
|
|||||||
}
|
}
|
||||||
function unifiedOrderActionLabel(action: UnifiedOrderRowAction) {
|
function unifiedOrderActionLabel(action: UnifiedOrderRowAction) {
|
||||||
if (action === 'note') return '备注'
|
if (action === 'note') return '备注'
|
||||||
if (action === 'approve') return '通过'
|
if (action === 'approve') return '审核'
|
||||||
return '测量'
|
return '测量'
|
||||||
}
|
}
|
||||||
function unifiedOrderRowActions(row: UnifiedOrderResource): RowActionItem[] {
|
function unifiedOrderRowActions(row: UnifiedOrderResource): RowActionItem[] {
|
||||||
@@ -2648,7 +2652,7 @@ onBeforeUnmount(() => {
|
|||||||
<template #default>
|
<template #default>
|
||||||
<div v-if="!loadError" id="unified-orders-results" class="surface-panel table-panel table-card" role="tabpanel">
|
<div v-if="!loadError" id="unified-orders-results" class="surface-panel table-panel table-card" role="tabpanel">
|
||||||
<div v-if="coLoadError" class="coload-error" role="alert"><span>{{ coLoadError.message }}</span><ApiErrorMeta :details="coLoadError" /></div>
|
<div v-if="coLoadError" class="coload-error" role="alert"><span>{{ coLoadError.message }}</span><ApiErrorMeta :details="coLoadError" /></div>
|
||||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="records" :columns="columns" preference-key="unified-orders-table-v2" :loading="loading" :total="total" :page-sizes="[20, 50, 100]" row-key="orderId" border stripe empty-text="暂无符合条件的订单" aria-label="统一订单列表" @selection-change="handleOrderSelectionChange">
|
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="records" :columns="columns" preference-key="unified-orders-table-v2" :loading="loading" :total="total" :page-sizes="[20, 50, 100]" :row-class-name="unifiedOrderRowClassName" row-key="orderId" border stripe empty-text="暂无符合条件的订单" aria-label="统一订单列表" @selection-change="handleOrderSelectionChange">
|
||||||
<template #toolbar-actions>
|
<template #toolbar-actions>
|
||||||
<el-button v-if="can('platform:order:export') && can('platform:download:list')" :loading="exportSubmitting" :disabled="loading || exportSubmitting" @click="exportOrders"><Download :size="16" />导出<span v-if="selectedOrders.length">(已选 {{ selectedOrders.length }})</span></el-button>
|
<el-button v-if="can('platform:order:export') && can('platform:download:list')" :loading="exportSubmitting" :disabled="loading || exportSubmitting" @click="exportOrders"><Download :size="16" />导出<span v-if="selectedOrders.length">(已选 {{ selectedOrders.length }})</span></el-button>
|
||||||
<el-button v-if="can('platform:order:approve')" type="success" :loading="batchApprovalLoading || batchApprovalSaving" :disabled="loading || !selectedApprovalOrders.length" @click="openBatchApproval"><CircleCheck :size="16" />批量审核通过<span v-if="selectedApprovalOrders.length">({{ selectedApprovalOrders.length }})</span></el-button>
|
<el-button v-if="can('platform:order:approve')" type="success" :loading="batchApprovalLoading || batchApprovalSaving" :disabled="loading || !selectedApprovalOrders.length" @click="openBatchApproval"><CircleCheck :size="16" />批量审核通过<span v-if="selectedApprovalOrders.length">({{ selectedApprovalOrders.length }})</span></el-button>
|
||||||
@@ -2884,6 +2888,9 @@ onBeforeUnmount(() => {
|
|||||||
.order-code-cell { display: grid; justify-items: start; gap: var(--admin-space-1, 4px); }
|
.order-code-cell { display: grid; justify-items: start; gap: var(--admin-space-1, 4px); }
|
||||||
.coload-tag-trigger { margin: 0; padding: 0; border: 0; background: transparent; cursor: pointer; }
|
.coload-tag-trigger { margin: 0; padding: 0; border: 0; background: transparent; cursor: pointer; }
|
||||||
.coload-tag-trigger:focus-visible { outline: 2px solid var(--el-color-primary); outline-offset: 2px; border-radius: 3px; }
|
.coload-tag-trigger:focus-visible { outline: 2px solid var(--el-color-primary); outline-offset: 2px; border-radius: 3px; }
|
||||||
|
.table-card :deep(.el-table__row.is-coload-group-row > .el-table__cell) { background: #fff6df; color: #7a4b00; }
|
||||||
|
.table-card :deep(.el-table__row.is-coload-group-row:hover > .el-table__cell) { background: #ffedbd; }
|
||||||
|
.table-card :deep(.el-table__row.is-coload-group-row > .el-table__cell:first-child) { box-shadow: inset 3px 0 0 #e6a23c; }
|
||||||
.coload-selection { display: flex; flex-wrap: wrap; gap: var(--admin-space-2, 6px); margin-top: var(--admin-space-5, 12px); }
|
.coload-selection { display: flex; flex-wrap: wrap; gap: var(--admin-space-2, 6px); margin-top: var(--admin-space-5, 12px); }
|
||||||
.coload-selection > span { padding: var(--admin-space-1, 4px) 7px; border: 1px solid var(--cc-border); border-radius: var(--admin-radius, 4px); background: var(--cc-surface-muted); font-size: var(--admin-font-caption, 12px); }
|
.coload-selection > span { padding: var(--admin-space-1, 4px) 7px; border: 1px solid var(--cc-border); border-radius: var(--admin-radius, 4px); background: var(--cc-surface-muted); font-size: var(--admin-font-caption, 12px); }
|
||||||
.coload-detail-summary { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5, 12px); margin-bottom: var(--admin-space-4, 10px); }
|
.coload-detail-summary { display: flex; align-items: center; justify-content: space-between; gap: var(--admin-space-5, 12px); margin-bottom: var(--admin-space-4, 10px); }
|
||||||
|
|||||||
Reference in New Issue
Block a user