From 1cc8ae44c5eead3783083712d6d31e45ec57e71a Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sun, 20 Sep 2026 21:52:12 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/iam/pages/RolesView.test.ts | 1 + src/modules/iam/pages/RolesView.vue | 3 +- .../pages/LastMileOrdersView.behavior.test.ts | 26 ++++++-- .../pages/LastMileOrdersView.vue | 22 ++++++- .../pages/PdaReleasesView.test.ts | 8 +++ .../pda-releases/pages/PdaReleasesView.vue | 64 +++++++++---------- 6 files changed, 82 insertions(+), 42 deletions(-) diff --git a/src/modules/iam/pages/RolesView.test.ts b/src/modules/iam/pages/RolesView.test.ts index eb805e05..d1472728 100644 --- a/src/modules/iam/pages/RolesView.test.ts +++ b/src/modules/iam/pages/RolesView.test.ts @@ -104,6 +104,7 @@ describe('RolesView', () => { it('restores saved permission ids after the dialog tree has mounted', () => { expect(rolesSource).toContain('@opened="initializePermissionSelection"') expect(rolesSource).toContain('treeRef.value?.setCheckedKeys([...form.permissionIds])') + expect(rolesSource).not.toContain('{{ data.code }}') expect(rolesSource).not.toContain('nextTick(() =>') }) diff --git a/src/modules/iam/pages/RolesView.vue b/src/modules/iam/pages/RolesView.vue index 3300bfad..26efde9d 100644 --- a/src/modules/iam/pages/RolesView.vue +++ b/src/modules/iam/pages/RolesView.vue @@ -266,7 +266,7 @@ onMounted(loadRoles) - + @@ -279,7 +279,6 @@ onMounted(loadRoles) .role-editor-layout > .role-editor-permissions.density-form-section { min-width: 0; padding: 0 0 0 var(--admin-space-5); border-left: 1px solid var(--cc-border); } .permission-tree { max-height: 380px; overflow-x: hidden; overflow-y: auto; } .permission-node span { min-width: 0; } -.permission-node code { max-width: 58%; white-space: normal; text-align: right; } .permission-node.is-navigation-group span { font-weight: 650; } :global(.role-editor-dialog) { max-height: min(760px, calc(100vh - 32px)); display: flex; flex-direction: column; } :global(.role-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; } diff --git a/src/modules/overseas-warehouse/pages/LastMileOrdersView.behavior.test.ts b/src/modules/overseas-warehouse/pages/LastMileOrdersView.behavior.test.ts index 8181f3f3..49206ad4 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrdersView.behavior.test.ts +++ b/src/modules/overseas-warehouse/pages/LastMileOrdersView.behavior.test.ts @@ -176,9 +176,9 @@ describe('LastMileOrdersView pending order and container conversion', () => { it.each([ [[], ['查看'], false], - [['inbound-order:create'], ['查看', '入库计划'], false], + [['inbound-order:create'], ['查看', '生成入库计划'], false], [['last-mile-order:edit'], ['查看', '暂扣订单', '人工修改节点'], false], - [['inbound-order:create', 'last-mile-order:edit'], ['查看', '入库计划', '暂扣订单', ''], true], + [['inbound-order:create', 'last-mile-order:edit'], ['查看', '生成入库计划', '暂扣订单', ''], true], ])('only collapses row actions above three for %j', async (allowed, labels, overflow) => { canAccess.mockImplementation(code => allowed.some(action => code === 'platform:overseas-warehouse:' + action)) vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{ id: 'order-1', customerId, orderNo: 'LM-1', currentStatus: 'PENDING_ASSIGNMENT', orderType: 'FIRST_LEG' }], page: 1, size: 10, total: 1, pages: 1 }) @@ -227,6 +227,22 @@ describe('LastMileOrdersView pending order and container conversion', () => { expect((holdButton.element as HTMLButtonElement).disabled).toBe(disabled) }) + it('removes generation and hold actions after an inbound plan has been submitted', async () => { + canAccess.mockReturnValue(true) + vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{ + id: 'order-planned', customerId, orderNo: 'LM-PLANNED', currentStatus: 'INBOUND_PLANNED', orderType: 'FIRST_LEG', detained: false, + }], page: 1, size: 10, total: 1, pages: 1 }) + + const wrapper = await mountView() + const actions = wrapper.get('.el-table__body-wrapper .row-actions') + expect(actions.text()).toContain('查看') + expect(actions.text()).not.toContain('生成入库计划') + expect(actions.text()).not.toContain('暂扣订单') + + await selectRows(wrapper, [(wrapper.findComponent(DataTable).props('data') as object[])[0]]) + expect((button(wrapper, '生成入库计划')!.element as HTMLButtonElement).disabled).toBe(true) + }) + it('keeps the original pending order table and does not render a view switch or container table', async () => { const wrapper = await openPending() expect(wrapper.text()).toContain('尾程单号') @@ -342,7 +358,7 @@ describe('LastMileOrdersView pending order and container conversion', () => { expect(payload).not.toHaveProperty('plannedWarehouseName') }) - it('allows a historically held order to generate its inbound plan', async () => { + it('blocks a held order from generating its inbound plan', async () => { const wrapper = await openPending() await selectRows(wrapper, [selectedRow({ detained: true, @@ -352,10 +368,10 @@ describe('LastMileOrdersView pending order and container conversion', () => { await button(wrapper, '生成入库计划')!.trigger('click') await flushPromises() - expect(getContainerInboundPlanPreview).toHaveBeenCalledWith(bookingId) + expect(getContainerInboundPlanPreview).not.toHaveBeenCalled() const confirm = [...document.body.querySelectorAll('button')] .find((item) => item.textContent?.includes('保存并生成入库单')) as HTMLButtonElement - expect(confirm.disabled).toBe(false) + expect(confirm).toBeUndefined() }) it('blocks manual plan creation when the cabinet has no frozen expected arrival time', async () => { diff --git a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue index 3e985e36..8b4a1d16 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue @@ -445,6 +445,7 @@ function isRejectedReturn(source: object) { return row.orderTypeCode === 'RETURN_RELABEL' && row.currentStatusCode === 'REJECTED' } function hasSelectedRejectedReturn() { return selectedRows.value.some(isRejectedReturn) } +function hasSelectedInboundPlan() { return selectedRows.value.some(hasInboundPlan) } async function loadTransferWarehouseOptions() { transferWarehouseLoading.value = true transferWarehouseError.value = null @@ -500,6 +501,7 @@ function openInboundPlan(source?: object) { const target = source ? source as LastMileOrderListItem : (selectedRows.value.length === 1 ? selectedRows.value[0] : null) if (!target) { ElMessage.warning('生成入库计划时请选择 1 条尾程订单'); return } if (isRejectedReturn(target)) { ElMessage.warning('已驳回的退货预报只能查看详情'); return } + if (hasInboundPlan(target)) { ElMessage.warning('当前订单已提交入库计划,不能重复生成'); return } if (target.detained) { ElMessage.warning('订单处于暂扣状态,解除暂扣后才能生成入库计划'); return } actionTarget.value = target const volumeMinimum = typeof target.volume === 'number' && target.volume > 0 ? Math.ceil(target.volume / 2) : 1 @@ -811,13 +813,27 @@ function hasRelabelActions(row: LastMileOrderListItem) { && Boolean(row.relabelOrderNo) && (canConfirmRelabel(row) || canReworkRelabel(row)) } +const inboundPlanCreatedStatuses = new Set([ + 'INBOUND_PLANNED', + 'PENDING_RECEIVING', + 'WAIT_PDA_RECEIVING', + 'INBOUND_MEASURING', + 'WAIT_CUSTOMER_CONFIRMATION', + 'PARTIALLY_RECEIVED', + 'INBOUND_COMPLETED', +]) +function hasInboundPlan(row: LastMileOrderListItem) { + return inboundPlanCreatedStatuses.has(row.currentStatusCode) + || Boolean(row.inboundOrderNo && row.inboundOrderNo !== '—') +} function lastMileRowActions(row: LastMileOrderListItem): RowActionItem[] { + const inboundPlanCreated = hasInboundPlan(row) return [ { key: 'detail', label: '查看', priority: 500 }, { key: 'review-return', label: '审核', priority: 460, tone: 'warning', visible: access.can(permission.returnOrderReview) && row.orderTypeCode === 'RETURN_RELABEL' && row.currentStatusCode === 'PENDING_REVIEW' }, { key: 'review', label: '审核', priority: 450, tone: 'warning', visible: access.can(permission.warehouseTransferApprove) && row.orderTypeCode === 'WAREHOUSE_TRANSFER' && row.currentStatusCode === 'PENDING_REVIEW' }, - { key: 'inbound-plan', label: '入库计划', priority: 400, visible: access.can(permission.inboundOrderCreate) }, - { key: 'hold', label: row.detained ? '解除暂扣' : '暂扣订单', priority: 300, tone: 'warning', visible: access.can(permission.lastMileOrderTransfer), disabled: !row.detained && row.currentStatusCode !== 'INBOUND_COMPLETED' }, + { key: 'inbound-plan', label: '生成入库计划', priority: 400, visible: access.can(permission.inboundOrderCreate) && !inboundPlanCreated }, + { key: 'hold', label: row.detained ? '解除暂扣' : '暂扣订单', priority: 300, tone: 'warning', visible: access.can(permission.lastMileOrderTransfer) && (row.detained || !inboundPlanCreated || row.currentStatusCode === 'INBOUND_COMPLETED'), disabled: !row.detained && row.currentStatusCode !== 'INBOUND_COMPLETED' }, { key: 'manual-nodes', label: '人工修改节点', priority: 200, visible: access.can(permission.lastMileOrderTransfer) }, { key: 'confirm-relabel', label: '换标完成确认', priority: 150, visible: access.can(permission.relabelOrderStatus) && Boolean(row.relabelOrderNo) && canConfirmRelabel(row) }, { key: 'rework-relabel', label: '重新换标', priority: 100, tone: 'danger', divided: true, visible: access.can(permission.relabelOrderStatus) && Boolean(row.relabelOrderNo) && canReworkRelabel(row) }, @@ -925,7 +941,7 @@ onMounted(() => { void load() })
- + diff --git a/src/modules/pda-releases/pages/PdaReleasesView.test.ts b/src/modules/pda-releases/pages/PdaReleasesView.test.ts index 25766476..43cd6411 100644 --- a/src/modules/pda-releases/pages/PdaReleasesView.test.ts +++ b/src/modules/pda-releases/pages/PdaReleasesView.test.ts @@ -24,4 +24,12 @@ describe('PDA release workbench', () => { expect(page).toContain('row.current') expect(page).toContain('ElMessageBox.confirm') }) + + it('uses the standard dense list workspace without repeating the route title', () => { + expect(page).toContain('class="surface-panel table-panel pda-release-panel"') + expect(page).toContain('