优化
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { Download, Eye } from '@lucide/vue'
|
||||
import { Download, Eye, Pencil } from '@lucide/vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
|
||||
@@ -13,7 +13,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 { convertLastMileContainerToInboundPlans, convertLastMileOrderToInboundPlan, downloadLastMileOrders, getContainerPlanProgress, listContainerPlanProgress, listLastMileOrders, transferLastMileOrders, type ContainerPlanProgressDetailDto, type ContainerPlanProgressSummaryDto } from '../api/lastMileOrders'
|
||||
import { changeContainerReceivingWarehouse, convertLastMileContainerToInboundPlans, convertLastMileOrderToInboundPlan, downloadLastMileOrders, getContainerPlanProgress, listContainerPlanProgress, listLastMileOrders, transferLastMileOrders, type ContainerPlanProgressDetailDto, type ContainerPlanProgressSummaryDto } from '../api/lastMileOrders'
|
||||
import OrderReferenceText from '../components/OrderReferenceText.vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { overseasWarehousePermissions as permission } from '../permissions'
|
||||
@@ -50,6 +50,14 @@ const containerOptions = ref<ContainerPlanProgressSummaryDto[]>([])
|
||||
const containerOptionsLoading = ref(false)
|
||||
const containerLoadError = ref<ApiErrorDetails | null>(null)
|
||||
const selectedContainerBookingId = ref('')
|
||||
const receivingWarehouseOpen = ref(false)
|
||||
const receivingWarehouseLoading = ref(false)
|
||||
const receivingWarehouseError = ref<ApiErrorDetails | null>(null)
|
||||
const receivingWarehouseActionError = ref<ApiErrorDetails | null>(null)
|
||||
const receivingWarehouseOptions = ref<TransferWarehouseOption[]>([])
|
||||
const receivingWarehouseTarget = ref<ContainerPlanProgressSummaryDto | null>(null)
|
||||
const receivingWarehouseForm = reactive({ warehouseId: '', remark: '' })
|
||||
let receivingWarehouseIdempotencyKey = ''
|
||||
let containerOptionRequest = 0
|
||||
const statusTab = ref<'全部' | '待分配' | '柜子'>('全部')
|
||||
const filters = ref<QueryModel>({ orderNo: '', orderType: '', warehouse: '', inboundCode: '', lxCode: '', sourceOrderNo: '', containerNo: '', status: '', transferWarehouseCode: '', abnormal: '', detained: '', createdAt: [] })
|
||||
@@ -112,6 +120,17 @@ function toTransferWarehouseOption(warehouse: PlatformWarehouseSummaryResource):
|
||||
label: [warehouseCode, warehouseName, region].filter(Boolean).join(' · '),
|
||||
}
|
||||
}
|
||||
|
||||
function receivingWarehouseLabel(container: ContainerPlanProgressSummaryDto | null | undefined) {
|
||||
if (!container) return '—'
|
||||
const code = String(container.overseasWarehouseCode || '').trim()
|
||||
const name = String(container.overseasWarehouseName || '').trim()
|
||||
return [code, name].filter(Boolean).join(' · ') || '—'
|
||||
}
|
||||
|
||||
function newReceivingWarehouseIdempotencyKey() {
|
||||
return `container-receiving-warehouse-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
|
||||
}
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 270, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'selection', type: 'selection', width: 48, fixed: 'left' },
|
||||
@@ -256,6 +275,65 @@ async function openContainerDetail(row: ContainerPlanProgressSummaryDto) {
|
||||
containerDetailLoading.value = false
|
||||
}
|
||||
}
|
||||
async function loadReceivingWarehouseOptions() {
|
||||
receivingWarehouseLoading.value = true
|
||||
receivingWarehouseError.value = null
|
||||
receivingWarehouseOptions.value = []
|
||||
try {
|
||||
const warehouses = await listAllPlatformWarehouses({ warehouseType: 'OVERSEAS', status: 'ACTIVE' })
|
||||
receivingWarehouseOptions.value = warehouses.map(toTransferWarehouseOption)
|
||||
} catch (error) {
|
||||
receivingWarehouseError.value = getApiErrorDetails(error, '海外接柜仓候选加载失败,请重试')
|
||||
} finally {
|
||||
receivingWarehouseLoading.value = false
|
||||
}
|
||||
}
|
||||
function openReceivingWarehouseChange(container: ContainerPlanProgressSummaryDto) {
|
||||
if (!container.canChangeReceivingWarehouse) return
|
||||
receivingWarehouseTarget.value = container
|
||||
Object.assign(receivingWarehouseForm, { warehouseId: container.overseasWarehouseId || '', remark: '' })
|
||||
receivingWarehouseIdempotencyKey = newReceivingWarehouseIdempotencyKey()
|
||||
receivingWarehouseOptions.value = []
|
||||
receivingWarehouseError.value = null
|
||||
receivingWarehouseActionError.value = null
|
||||
actionError.value = null
|
||||
receivingWarehouseOpen.value = true
|
||||
void loadReceivingWarehouseOptions()
|
||||
}
|
||||
async function submitReceivingWarehouseChange() {
|
||||
const target = receivingWarehouseTarget.value
|
||||
const selected = receivingWarehouseOptions.value.find((warehouse) => warehouse.warehouseId === receivingWarehouseForm.warehouseId)
|
||||
if (!target || !selected) {
|
||||
ElMessage.warning('请选择有效的海外接柜仓')
|
||||
return
|
||||
}
|
||||
actionLoading.value = true
|
||||
actionError.value = null
|
||||
try {
|
||||
await changeContainerReceivingWarehouse(target.sourceContainerBookingId, {
|
||||
overseasWarehouseId: selected.warehouseId,
|
||||
expectedVersion: target.assignmentVersion,
|
||||
remark: receivingWarehouseForm.remark.trim() || undefined,
|
||||
}, receivingWarehouseIdempotencyKey)
|
||||
receivingWarehouseOpen.value = false
|
||||
receivingWarehouseTarget.value = null
|
||||
ElMessage.success('海外接柜仓修改成功')
|
||||
await load()
|
||||
} catch (error) {
|
||||
const details = getApiErrorDetails(error, '海外接柜仓修改失败,请重试')
|
||||
receivingWarehouseActionError.value = details.code === 'A0409'
|
||||
? { ...details, message: '接柜仓信息已被其他操作更新,请刷新后重试' }
|
||||
: details
|
||||
actionError.value = receivingWarehouseActionError.value
|
||||
if (details.code === 'A0409') {
|
||||
receivingWarehouseOpen.value = false
|
||||
receivingWarehouseTarget.value = null
|
||||
await load()
|
||||
}
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
function onSelectionChange(selection: object[]) { selectedRows.value = selection as LastMileOrderListItem[] }
|
||||
async function loadTransferWarehouseOptions() {
|
||||
transferWarehouseLoading.value = true
|
||||
@@ -418,6 +496,10 @@ onMounted(() => { void load() })
|
||||
<div v-if="containerRows.length" class="container-workbench__grid">
|
||||
<article v-for="container in containerRows" :key="container.sourceContainerBookingId" class="container-workbench-card">
|
||||
<span class="container-workbench-card__top"><strong>{{ container.containerNo || '柜号待补充' }}</strong></span>
|
||||
<span class="container-workbench-card__warehouse">
|
||||
<small>海外接柜仓</small><strong>{{ receivingWarehouseLabel(container) }}</strong>
|
||||
<small v-if="!container.canChangeReceivingWarehouse && container.receivingWarehouseLockReason" class="is-locked">{{ container.receivingWarehouseLockReason }}</small>
|
||||
</span>
|
||||
<span class="container-workbench-card__arrival"><small>计划到仓</small><strong>{{ formatDateTime(container.plannedArrivalAt) }}</strong></span>
|
||||
<span class="container-workbench-card__pending">
|
||||
<strong>{{ pendingInboundCodeCount(container) }}</strong><span>个入仓码待生成计划</span>
|
||||
@@ -427,6 +509,7 @@ onMounted(() => { void load() })
|
||||
</span>
|
||||
<span class="container-workbench-card__actions">
|
||||
<el-button class="container-workbench-card__detail" plain @click="openContainerDetail(container)">查看详情</el-button>
|
||||
<el-button v-if="access.can(permission.lastMileOrderTransfer) && container.canChangeReceivingWarehouse" plain @click="openReceivingWarehouseChange(container)"><Pencil :size="15" />修改接柜仓</el-button>
|
||||
<el-button v-if="access.can(permission.inboundOrderCreate)" type="primary" :disabled="actionLoading || !canGenerateContainerPlans(container)" @click="openContainerConversionFor(container)">生成入库计划</el-button>
|
||||
</span>
|
||||
</article>
|
||||
@@ -479,16 +562,36 @@ onMounted(() => { void load() })
|
||||
<template v-else-if="containerDetail">
|
||||
<div class="container-detail-summary">
|
||||
<span>柜号:{{ containerDetail.summary.containerNo || '—' }}</span>
|
||||
<span>海外接柜仓:{{ receivingWarehouseLabel(containerDetail.summary) }}</span>
|
||||
<span>计划到仓:{{ formatDateTime(containerDetail.summary.plannedArrivalAt) }}</span>
|
||||
<span>订单:{{ containerDetail.summary.totalOrderCount }}</span>
|
||||
<span>入仓码:{{ containerDetail.summary.inboundCodeCount }}</span>
|
||||
<span>入库计划:{{ containerDetail.summary.planCount }},待生成 {{ pendingInboundCodeCount(containerDetail.summary) }}</span>
|
||||
<span v-if="!containerDetail.summary.canChangeReceivingWarehouse">修改状态:{{ containerDetail.summary.receivingWarehouseLockReason || '当前已锁定' }}</span>
|
||||
</div>
|
||||
<DataTable :data="containerDetail.orders" :columns="containerDetailColumns" :loading="containerDetailLoading" preference-key="overseas-last-mile-container-detail" row-key="orderId" :total="undefined" empty-text="该柜暂无尾程订单">
|
||||
<template #plannedArrivalAt="{ row }">{{ formatDateTime((row as ContainerPlanProgressDetailDto['orders'][number]).plannedArrivalAt) }}</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
</AppDialog>
|
||||
<AppDialog v-model="receivingWarehouseOpen" title="修改海外接柜仓" width="min(600px, calc(100vw - 28px))" confirm-text="确认修改" :loading="receivingWarehouseLoading" :confirm-loading="actionLoading" :confirm-disabled="!receivingWarehouseForm.warehouseId || Boolean(receivingWarehouseError)" @confirm="submitReceivingWarehouseChange">
|
||||
<LoadErrorState v-if="receivingWarehouseError" title="海外接柜仓候选加载失败" :message="receivingWarehouseError.message" compact @retry="loadReceivingWarehouseOptions">
|
||||
<template #details><ApiErrorMeta :details="receivingWarehouseError" /></template>
|
||||
</LoadErrorState>
|
||||
<el-form v-else label-position="top">
|
||||
<el-form-item label="当前海外接柜仓"><el-input :model-value="receivingWarehouseLabel(receivingWarehouseTarget)" readonly /></el-form-item>
|
||||
<el-form-item label="新的海外接柜仓" required>
|
||||
<el-select v-model="receivingWarehouseForm.warehouseId" filterable clearable placeholder="请选择 ACTIVE 海外接柜仓">
|
||||
<el-option v-for="warehouse in receivingWarehouseOptions" :key="warehouse.warehouseId" :label="warehouse.label" :value="warehouse.warehouseId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-alert v-if="!receivingWarehouseLoading && !receivingWarehouseOptions.length" title="暂无可用的 ACTIVE 海外接柜仓" type="info" :closable="false" show-icon />
|
||||
<el-form-item label="备注"><el-input v-model="receivingWarehouseForm.remark" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入修改备注(选填)" /></el-form-item>
|
||||
<LoadErrorState v-if="receivingWarehouseActionError" title="海外接柜仓修改失败" :message="receivingWarehouseActionError.message" compact>
|
||||
<template #details><ApiErrorMeta :details="receivingWarehouseActionError" /></template>
|
||||
</LoadErrorState>
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -510,6 +613,10 @@ onMounted(() => { void load() })
|
||||
.container-workbench-card__arrival { display: grid; gap: 4px; }
|
||||
.container-workbench-card__arrival small { color: var(--cc-muted); font-size: 12px; }
|
||||
.container-workbench-card__arrival strong { color: var(--cc-text); font-size: 15px; }
|
||||
.container-workbench-card__warehouse { display: grid; gap: 4px; }
|
||||
.container-workbench-card__warehouse small { color: var(--cc-muted); font-size: 12px; }
|
||||
.container-workbench-card__warehouse strong { overflow-wrap: anywhere; color: var(--cc-text); font-size: 14px; }
|
||||
.container-workbench-card__warehouse .is-locked { color: #c2410c; }
|
||||
.container-workbench-card__pending { display: flex; align-items: baseline; gap: 8px; padding: 12px 14px; border-left: 3px solid #f59e0b; background: #fffaf0; }
|
||||
.container-workbench-card__pending strong { color: #b45309; font-size: 28px; line-height: 1; }
|
||||
.container-workbench-card__pending span { color: #92400e; font-size: 13px; }
|
||||
|
||||
Reference in New Issue
Block a user