优化
This commit is contained in:
@@ -32,13 +32,10 @@ import { groupSmartLoadingItems, type SmartLoadingInboundItem } from '../smartLo
|
||||
import { loadSmartLoadingPlanCoLoadGroups, type SmartLoadingPlanCoLoadGroup } from '../smartLoadingPlanGroups'
|
||||
import {
|
||||
SMART_LOADING_CHANNEL_GROUPS,
|
||||
SMART_LOADING_WAREHOUSE_GROUPS,
|
||||
resolveSmartLoadingChannelGroup,
|
||||
resolveSmartLoadingWarehouseGroup,
|
||||
} from '../smartLoadingChannelGroups'
|
||||
|
||||
const props = withDefaults(defineProps<{ embedded?: boolean }>(), { embedded: false })
|
||||
const activeWarehouseCode = defineModel<string>('warehouseCode', { default: '' })
|
||||
|
||||
const access = useAccessPreviewStore()
|
||||
const router = useRouter()
|
||||
@@ -46,7 +43,7 @@ const containers = ref<ContainerBookingResource[]>([])
|
||||
const customers = ref<PlatformTenantResource[]>([])
|
||||
const customersLoading = ref(false)
|
||||
const selectedContainerId = ref('')
|
||||
const activeChannelCode = ref('')
|
||||
const selectedChannelCodes = ref<string[]>([])
|
||||
const plan = ref<SmartLoadingPlan | null>(null)
|
||||
const candidates = ref<SmartLoadingCandidate[]>([])
|
||||
const loading = ref(false)
|
||||
@@ -61,7 +58,7 @@ const selectedCandidate = ref<SmartLoadingCandidateRow | null>(null)
|
||||
const assignForm = reactive({ packageCount: 1 })
|
||||
const quantityDrafts = reactive<Record<string, number>>({})
|
||||
const planGroupsByInboundId = ref(new Map<string, SmartLoadingPlanCoLoadGroup>())
|
||||
const query = ref<QueryModel>({ customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
|
||||
const query = ref<QueryModel>({ warehouseId: '', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
|
||||
const appliedQuery = ref<QueryModel>({ ...query.value })
|
||||
const candidatePage = ref(1)
|
||||
const candidatePageSize = ref(20)
|
||||
@@ -77,16 +74,16 @@ const unmappedChannelContainerCount = computed(() => orchestratableContainers.va
|
||||
item.channelCode && !resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)
|
||||
)).length)
|
||||
const mappedWorkbenchContainerCount = computed(() => orchestratableContainers.value.filter((item) => (
|
||||
resolveSmartLoadingWarehouseGroup(item.warehouseName)
|
||||
&& resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)
|
||||
resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)
|
||||
)).length)
|
||||
const unmappedWarehouseContainerCount = computed(() => orchestratableContainers.value.filter((item) => (
|
||||
!resolveSmartLoadingWarehouseGroup(item.warehouseName)
|
||||
)).length)
|
||||
const warehouseTabs = computed(() => SMART_LOADING_WAREHOUSE_GROUPS)
|
||||
const channelTabs = computed(() => SMART_LOADING_CHANNEL_GROUPS)
|
||||
const activeWarehouse = computed(() => warehouseTabs.value.find((item) => item.code === activeWarehouseCode.value) || null)
|
||||
const activeChannel = computed(() => channelTabs.value.find((item) => item.code === activeChannelCode.value) || null)
|
||||
const channelOptions = computed(() => SMART_LOADING_CHANNEL_GROUPS)
|
||||
const warehouseOptions = computed(() => {
|
||||
const options = new Map<string, string>()
|
||||
for (const item of orchestratableContainers.value) {
|
||||
if (item.warehouseId && !options.has(item.warehouseId)) options.set(item.warehouseId, item.warehouseName?.trim() || '未命名发货仓')
|
||||
}
|
||||
return [...options].map(([value, label]) => ({ value, label }))
|
||||
})
|
||||
const editable = computed(() => plan.value?.canEdit === true)
|
||||
const assignable = computed(() => plan.value?.canAssign === true)
|
||||
const selectedPlanStatus = computed(() => selectedContainer.value?.planStatus || null)
|
||||
@@ -195,6 +192,7 @@ const candidateOrderStatusOptions = computed(() => [...new Set(candidates.value
|
||||
.filter(Boolean))]
|
||||
.map((status) => ({ value: status, label: candidateOrderStatusLabel(status) })))
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{ key: 'warehouseId', type: 'select', label: '发货仓', placeholder: '全部发货仓', width: 180, options: warehouseOptions.value, props: { filterable: true } },
|
||||
{ key: 'customerId', type: 'select', label: '客户', placeholder: '全部客户', width: 220, options: customers.value.filter((customer) => customer.status === 'ACTIVE').map((customer) => ({ label: customer.tenantName, value: String(customer.id) })), props: { filterable: true, loading: customersLoading.value } },
|
||||
{ key: 'inboundCode', type: 'input', label: '入仓码', placeholder: '请输入入仓码', width: 240 },
|
||||
{ key: 'expectedArrivalRange', type: 'daterange', label: '预计到货时间', placeholder: '请选择预计到货时间范围', width: 300 },
|
||||
@@ -203,9 +201,8 @@ const queryFields = computed<QueryField[]>(() => [
|
||||
|
||||
const filteredContainers = computed(() => containers.value.filter((item) => (
|
||||
['WAIT_LOADING', 'EDITING', 'LOADING'].includes(item.planStatus)
|
||||
&& resolveSmartLoadingWarehouseGroup(item.warehouseName)?.code === activeWarehouseCode.value
|
||||
&& Boolean(activeChannelCode.value)
|
||||
&& resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)?.code === activeChannelCode.value
|
||||
&& (!String(appliedQuery.value.warehouseId || '') || item.warehouseId === String(appliedQuery.value.warehouseId))
|
||||
&& selectedChannelCodes.value.includes(resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)?.code || '')
|
||||
)))
|
||||
const filteredCandidates = computed(() => {
|
||||
const customerId = String(appliedQuery.value.customerId || '')
|
||||
@@ -298,12 +295,6 @@ function acceptPlan(next: SmartLoadingPlan, preserved: Record<string, number> =
|
||||
}
|
||||
}
|
||||
|
||||
function preferredChannelCode(warehouseCode: string) {
|
||||
return channelTabs.value.find((group) => orchestratableContainers.value.some((item) => (
|
||||
resolveSmartLoadingWarehouseGroup(item.warehouseName)?.code === warehouseCode
|
||||
&& resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)?.code === group.code
|
||||
)))?.code || channelTabs.value[0]?.code || ''
|
||||
}
|
||||
function clearSelectionData() {
|
||||
plan.value = null
|
||||
candidates.value = []
|
||||
@@ -363,13 +354,12 @@ async function loadAll(preserved: Record<string, number> = {}) {
|
||||
? await Promise.all(Array.from({ length: firstPage.pages - 1 }, (_, index) => listContainers({ page: index + 2, size: 100 })))
|
||||
: []
|
||||
containers.value = [firstPage, ...remainingPages].flatMap((page) => page.records)
|
||||
if (!warehouseTabs.value.some((item) => item.code === activeWarehouseCode.value)) {
|
||||
activeWarehouseCode.value = warehouseTabs.value.find((group) => orchestratableContainers.value.some((item) => (
|
||||
resolveSmartLoadingWarehouseGroup(item.warehouseName)?.code === group.code
|
||||
)))?.code || warehouseTabs.value[0]?.code || ''
|
||||
}
|
||||
if (!channelTabs.value.some((item) => item.code === activeChannelCode.value)) {
|
||||
activeChannelCode.value = preferredChannelCode(activeWarehouseCode.value)
|
||||
const availableChannelCodes = new Set(channelOptions.value.map((item) => item.code))
|
||||
selectedChannelCodes.value = selectedChannelCodes.value.filter((code) => availableChannelCodes.has(code))
|
||||
if (!selectedChannelCodes.value.length) {
|
||||
selectedChannelCodes.value = [channelOptions.value.find((channel) => orchestratableContainers.value.some((item) => (
|
||||
resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)?.code === channel.code
|
||||
)))?.code || channelOptions.value[0]?.code || '']
|
||||
}
|
||||
if (!filteredContainers.value.some((item) => item.id === selectedContainerId.value)) selectedContainerId.value = preferredContainerId()
|
||||
await loadSelectedData(preserved)
|
||||
@@ -396,16 +386,12 @@ async function selectContainer(containerId: string) {
|
||||
selectedContainerId.value = containerId
|
||||
await loadSelectedData()
|
||||
}
|
||||
async function selectWarehouse(warehouseCode: string) {
|
||||
if (activeWarehouseCode.value === warehouseCode) return
|
||||
activeWarehouseCode.value = warehouseCode
|
||||
selectedContainerId.value = preferredContainerId()
|
||||
await loadSelectedData()
|
||||
}
|
||||
defineExpose({ selectWarehouse })
|
||||
async function selectChannel(channelCode: string) {
|
||||
if (activeChannelCode.value === channelCode) return
|
||||
activeChannelCode.value = channelCode
|
||||
async function selectChannels(codes: string[]) {
|
||||
if (!codes.length) {
|
||||
ElMessage.warning('至少选择一个渠道')
|
||||
return
|
||||
}
|
||||
selectedChannelCodes.value = codes
|
||||
selectedContainerId.value = preferredContainerId()
|
||||
await loadSelectedData()
|
||||
}
|
||||
@@ -575,13 +561,14 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
<template>
|
||||
<component :is="props.embedded ? 'div' : AdminPage" :class="{ 'embedded-smart-loading': props.embedded }">
|
||||
<section class="global-loading-page">
|
||||
<div v-if="channelTabs.length" class="channel-summary-row">
|
||||
<el-tabs :model-value="activeChannelCode" class="channel-tabs" aria-label="物流渠道" @tab-change="selectChannel(String($event))">
|
||||
<el-tab-pane v-for="channel in channelTabs" :key="channel.code" :label="channel.name" :name="channel.code" />
|
||||
</el-tabs>
|
||||
<dl class="compact-summary" aria-label="当前渠道装柜统计">
|
||||
<div v-if="channelOptions.length" class="channel-summary-row">
|
||||
<el-checkbox-group :model-value="selectedChannelCodes" class="channel-tags" aria-label="渠道筛选,至少选择一项" @change="selectChannels($event as string[])">
|
||||
<span class="channel-tags__label">渠道</span>
|
||||
<el-checkbox-button v-for="channel in channelOptions" :key="channel.code" :label="channel.code" :value="channel.code">{{ channel.name }}</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
<dl class="compact-summary" aria-label="已选渠道装柜统计">
|
||||
<div><dt>候选明细</dt><dd><strong>{{ filteredCandidates.length }}</strong> 条</dd></div>
|
||||
<div><dt>柜次</dt><dd><strong>{{ filteredContainers.length }}</strong> 个</dd></div>
|
||||
<div><dt>柜子数量</dt><dd><strong>{{ filteredContainers.length }}</strong> 个</dd></div>
|
||||
<div><dt>已装</dt><dd><strong>{{ totalPackages }}</strong> 箱</dd></div>
|
||||
<div><dt>总重量</dt><dd><strong>{{ formatNumber(totalWeight, 1) }}</strong> kg</dd></div>
|
||||
<div><dt>总体积</dt><dd><strong>{{ formatNumber(totalVolume) }}</strong> m³</dd></div>
|
||||
@@ -589,7 +576,6 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
</div>
|
||||
<el-alert v-if="missingChannelContainerCount && !mappedWorkbenchContainerCount" type="warning" :closable="false" show-icon :title="`${missingChannelContainerCount} 个可编排柜次缺少渠道快照,已隔离;请补齐后再参与智能装柜`" />
|
||||
<el-alert v-if="unmappedChannelContainerCount && !mappedWorkbenchContainerCount" type="warning" :closable="false" show-icon :title="`${unmappedChannelContainerCount} 个可编排柜次的渠道未纳入固定 9 类,已隔离`" />
|
||||
<el-alert v-if="unmappedWarehouseContainerCount && !mappedWorkbenchContainerCount" type="warning" :closable="false" show-icon :title="`${unmappedWarehouseContainerCount} 个可编排柜次不属于义乌仓或东莞仓,已隔离`" />
|
||||
<el-alert v-if="completedContainerCount && !orchestratableContainers.length" type="success" :closable="false" show-icon title="当前柜次已完成装柜,不可继续编辑" />
|
||||
<QueryToolbar
|
||||
v-model="query"
|
||||
@@ -599,6 +585,7 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
preference-key="global-smart-loading-query-v2"
|
||||
:search-on-reset="false"
|
||||
@search="applyFilters"
|
||||
@reset="applyFilters"
|
||||
/>
|
||||
|
||||
<LoadErrorState v-if="loadError" title="智能装柜工作台加载失败" :message="loadError.message" :loading="loading" @retry="load">
|
||||
@@ -610,7 +597,7 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
</LoadErrorState>
|
||||
<section class="dispatch-grid" aria-label="智能装柜调度台">
|
||||
<section class="dispatch-pane candidate-pane">
|
||||
<header class="pane-header"><div><strong>候选装柜明细</strong><small>{{ activeWarehouse?.name || '未选择仓库' }} · {{ activeChannel?.name || '未选择渠道' }}</small></div></header>
|
||||
<header class="pane-header"><div><strong>候选装柜明细</strong><small>{{ selectedContainer?.warehouseName || '未选择发货仓' }} · {{ resolveSmartLoadingChannelGroup(selectedContainer?.channelCode, selectedContainer?.channelName)?.name || '未选择渠道' }}</small></div></header>
|
||||
<DataTable
|
||||
v-model:page="candidatePage"
|
||||
v-model:page-size="candidatePageSize"
|
||||
@@ -642,7 +629,7 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
</section>
|
||||
|
||||
<aside class="dispatch-pane container-pane" aria-label="货柜列表">
|
||||
<header class="pane-header"><div><strong>当前渠道柜次</strong></div></header>
|
||||
<header class="pane-header"><div><strong>已选渠道柜次</strong></div></header>
|
||||
<div v-loading="loading" class="container-list">
|
||||
<el-empty v-if="!filteredContainers.length" description="暂无符合条件的货柜" :image-size="72" />
|
||||
<button
|
||||
@@ -741,13 +728,12 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
<style scoped>
|
||||
.global-loading-page { display: grid; gap: 12px; min-width: 0; }
|
||||
.embedded-smart-loading { min-width: 0; }
|
||||
.channel-summary-row { display: grid; grid-template-columns: minmax(360px, 1fr) auto; min-width: 0; align-items: end; border-bottom: 2px solid var(--cc-border); background: #fff; }
|
||||
.channel-tabs { min-width: 0; }
|
||||
.channel-tabs :deep(.el-tabs__header) { margin: 0 0 -2px; }
|
||||
.channel-tabs :deep(.el-tabs__nav-wrap::after) { display: none; }
|
||||
.channel-tabs :deep(.el-tabs__item) { overflow: hidden; max-width: 240px; height: 48px; padding-inline: 18px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.channel-tabs :deep(.el-tabs__content) { display: none; }
|
||||
.compact-summary { display: flex; min-height: 48px; align-items: center; justify-content: flex-end; gap: 16px; margin: 0; padding: 6px 12px; white-space: nowrap; }
|
||||
.channel-summary-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; min-width: 0; align-items: center; border-bottom: 2px solid var(--cc-border); background: #fff; }
|
||||
.channel-tags { display: flex; min-width: 0; align-items: center; flex-wrap: nowrap; gap: 6px; padding: 6px 0; overflow-x: auto; }
|
||||
.channel-tags__label { flex: 0 0 auto; margin-right: 2px; color: var(--cc-text); font-size: 13px; font-weight: 600; }
|
||||
.channel-tags :deep(.el-checkbox-button) { flex: 0 0 auto; }
|
||||
.channel-tags :deep(.el-checkbox-button__inner) { height: 34px; padding: 0 10px; border: 1px solid var(--cc-border) !important; border-radius: 6px !important; box-shadow: none !important; font-size: 13px; line-height: 32px; }
|
||||
.compact-summary { display: flex; min-height: 46px; align-items: center; justify-content: flex-end; gap: 12px; margin: 0; padding: 5px 8px; white-space: nowrap; }
|
||||
.compact-summary > div { display: flex; align-items: baseline; gap: 4px; }
|
||||
.compact-summary dt { color: var(--cc-muted); font-size: 12px; }
|
||||
.compact-summary dd { margin: 0; color: var(--cc-text); font-size: 12px; }
|
||||
@@ -803,6 +789,6 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
.pane-header,.plan-actions,.meter-title { align-items: stretch; flex-direction: column; }
|
||||
.plan-actions :deep(.el-button) { width: 100%; min-height: 40px; margin-left: 0; }
|
||||
.container-list { grid-template-columns: 1fr; }
|
||||
.channel-tabs :deep(.el-tabs__item) { max-width: 180px; padding-inline: 12px; }
|
||||
.channel-tags { padding-inline: 8px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user