This commit is contained in:
2026-09-18 16:48:40 +08:00
parent e10d0534ae
commit 24fa3d1c9d
19 changed files with 286 additions and 210 deletions
@@ -178,7 +178,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
[[], ['查看'], 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 })
@@ -1,7 +1,7 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { Download, Pencil, MoreHorizontal } from '@lucide/vue'
import { Download, Pencil } from '@lucide/vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
@@ -14,6 +14,7 @@ import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import RowActions, { type RowActionItem } from '@/shared/components/RowActions.vue'
import { changeContainerReceivingWarehouse, convertLastMileContainerToInboundPlans, createManualInboundPlan, downloadLastMileOrders, getContainerInboundPlanPreview, getContainerPlanProgress, getLastMileOrderManualNodes, listContainerPlanProgress, listLastMileOrders, transferLastMileOrders, updateLastMileOrderHold, updateLastMileOrderManualNodes, type ContainerInboundPlanPreviewDto, type ContainerInboundPlanPreviewGroupDto, type ContainerInboundPlanPreviewOrderDto, type ContainerPlanProgressDetailDto, type ContainerPlanProgressSummaryDto, type ManualNodeCode } from '../api/lastMileOrders'
import OrderReferenceText from '../components/OrderReferenceText.vue'
import WarehouseTransferReviewDialog from '../components/WarehouseTransferReviewDialog.vue'
@@ -775,21 +776,27 @@ watch(containerConvertOpen, (open) => {
containerConversionIdempotencyKey = ''
})
function rowActionCount(source: object) {
const row = source as LastMileOrderListItem
const count = 1
+ Number(access.can(permission.warehouseTransferApprove) && row.orderTypeCode === 'WAREHOUSE_TRANSFER' && row.currentStatusCode === 'PENDING_REVIEW')
+ Number(access.can(permission.inboundOrderCreate))
+ (access.can(permission.lastMileOrderTransfer) ? 2 : 0)
+ Number(access.can(permission.relabelOrderStatus) && Boolean(row.relabelOrderNo) && canConfirmRelabel(row))
+ Number(access.can(permission.relabelOrderStatus) && Boolean(row.relabelOrderNo) && canReworkRelabel(row))
return hasRelabelActions(row) ? Math.max(4, count) : count
}
function canConfirmRelabel(row: { relabelStatusCode?: string }) { return row.relabelStatusCode === 'CUSTOMER_CONFIRM_PENDING' }
function canReworkRelabel(row: { relabelStatusCode?: string }) { return ['CUSTOMER_CONFIRM_PENDING', 'PROCESSING'].includes(row.relabelStatusCode ?? '') }
function hasRelabelActions(source: object) {
const row = source as LastMileOrderListItem
return access.can(permission.relabelOrderStatus) && Boolean(row.relabelOrderNo) && (canConfirmRelabel(row) || canReworkRelabel(row))
function lastMileRowActions(row: LastMileOrderListItem): RowActionItem[] {
return [
{ key: 'detail', label: '查看', priority: 500 },
{ 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: '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) },
]
}
function handleLastMileRowAction(command: string, row: LastMileOrderListItem) {
if (command === 'detail') openDetail(row)
else if (command === 'review') openTransferReview(row)
else if (command === 'inbound-plan') openInboundPlan(row)
else if (command === 'hold') openHold(row)
else if (command === 'manual-nodes') void openManualNodes(row)
else if (command === 'confirm-relabel') void confirmRelabel(row)
else if (command === 'rework-relabel') void reworkRelabel(row)
}
async function confirmRelabel(row: { relabelOrderNo?: string }) {
const relabelNo = row.relabelOrderNo
@@ -884,7 +891,7 @@ onMounted(() => { void load() })
</section>
<section v-else-if="!loadError" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-last-mile-orders" row-key="id" empty-text="暂无尾程订单" @selection-change="onSelectionChange" @update:page="load()" @update:page-size="page = 1; load()">
<template #toolbar-actions><el-button v-if="access.can(permission.lastMileOrderExport)" plain :loading="exportLoading" @click="exportOrders"><Download :size="16" />导出订单</el-button><el-button v-if="access.can(permission.lastMileOrderTransfer)" plain :disabled="!selectedRows.length || actionLoading" @click="openTransfer">转仓</el-button><el-button v-if="access.can(permission.inboundOrderCreate)" type="primary" plain :disabled="selectedRows.length !== 1 || actionLoading" @click="openInboundPlan()">生成入库计划</el-button><el-button v-if="statusTab === '待分配' && access.can(permission.inboundOrderCreate)" type="primary" :disabled="actionLoading" @click="openContainerConversion">按柜批量生成</el-button></template>
<template #actions="{ row }"><div class="row-actions density-actions"><el-button class="density-action" data-tone="neutral" size="small" @click="openDetail(row)">查看</el-button><el-button class="density-action" data-tone="warning" v-if="access.can(permission.warehouseTransferApprove) && row.orderTypeCode === 'WAREHOUSE_TRANSFER' && row.currentStatusCode === 'PENDING_REVIEW'" size="small" @click="openTransferReview(row)">审核</el-button><el-button class="density-action" data-tone="neutral" v-if="access.can(permission.inboundOrderCreate)" size="small" @click="openInboundPlan(row)">入库计划</el-button><template v-if="rowActionCount(row) <= 3 && access.can(permission.lastMileOrderTransfer)"><el-button class="density-action" data-tone="warning" :disabled="!row.detained && row.currentStatusCode !== 'INBOUND_COMPLETED'" size="small" @click="openHold(row)">{{ row.detained ? '解除暂扣' : '暂扣订单' }}</el-button><el-button class="density-action" data-tone="neutral" size="small" @click="openManualNodes(row)">人工修改节点</el-button></template><el-dropdown v-if="rowActionCount(row) > 3" trigger="click"><el-button class="density-action" :aria-label="`更多操作 · ${row.orderNo || row.dispatchNo || '当前记录'}`" title="更多操作" data-tone="neutral" size="small" ><MoreHorizontal aria-hidden="true" :size="15" /></el-button><template #dropdown><el-dropdown-menu><el-dropdown-item v-if="access.can(permission.lastMileOrderTransfer)" :disabled="!row.detained && row.currentStatusCode !== 'INBOUND_COMPLETED'" @click="openHold(row)">{{ row.detained ? '解除暂扣' : '暂扣订单' }}</el-dropdown-item><el-dropdown-item v-if="access.can(permission.lastMileOrderTransfer)" @click="openManualNodes(row)">人工修改节点</el-dropdown-item><el-dropdown-item v-if="access.can(permission.relabelOrderStatus) && row.relabelOrderNo && canConfirmRelabel(row)" @click="confirmRelabel(row)">换标完成确认</el-dropdown-item><el-dropdown-item v-if="access.can(permission.relabelOrderStatus) && row.relabelOrderNo && canReworkRelabel(row)" @click="reworkRelabel(row)">重新换标</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div></template>
<template #actions="{ row }"><RowActions :actions="lastMileRowActions(row as LastMileOrderListItem)" :more-label="`更多尾程订单操作 · ${(row as LastMileOrderListItem).orderNo || '当前记录'}`" @action="handleLastMileRowAction($event, row as LastMileOrderListItem)" /></template>
<template #orderNo="{ row }"><OrderReferenceText :value="String(row.orderNo || '')" copyable /></template>
<template #cartonDifference="{ row }"><span :class="{ 'package-difference--nonzero': typeof row.cartonDifference === 'number' && row.cartonDifference !== 0 }">{{ packageDifferenceText(row.cartonDifference) }}</span></template>
<template #pagination-left>共 {{ total }} 条尾程订单 · 已选 {{ selectedRows.length }} 条</template>