优化
This commit is contained in:
@@ -31,12 +31,15 @@ import type {
|
||||
SmartLoadingPlan,
|
||||
} from '../types'
|
||||
|
||||
const props = withDefaults(defineProps<{ embedded?: boolean }>(), { embedded: false })
|
||||
|
||||
const access = useAccessPreviewStore()
|
||||
const router = useRouter()
|
||||
const containers = ref<ContainerBookingResource[]>([])
|
||||
const customers = ref<PlatformTenantResource[]>([])
|
||||
const customersLoading = ref(false)
|
||||
const selectedContainerId = ref('')
|
||||
const activeChannelCode = ref('')
|
||||
const plan = ref<SmartLoadingPlan | null>(null)
|
||||
const candidates = ref<SmartLoadingCandidate[]>([])
|
||||
const loading = ref(false)
|
||||
@@ -59,6 +62,21 @@ const canAssign = computed(() => access.can('platform:firstleg:smart-loading:ass
|
||||
const canDelete = computed(() => access.can('platform:firstleg:smart-loading:delete'))
|
||||
const canPush = computed(() => access.can('platform:firstleg:smart-loading:push'))
|
||||
const selectedContainer = computed(() => containers.value.find((item) => item.id === selectedContainerId.value) || null)
|
||||
const channelTabs = computed(() => {
|
||||
const channels = new Map<string, { code: string; name: string; containerCount: number }>()
|
||||
for (const item of containers.value) {
|
||||
if (item.planStatus === 'COMPLETED' || !item.channelCode) continue
|
||||
const current = channels.get(item.channelCode)
|
||||
if (current) current.containerCount += 1
|
||||
else channels.set(item.channelCode, {
|
||||
code: item.channelCode,
|
||||
name: item.channelName || item.channelCode,
|
||||
containerCount: 1,
|
||||
})
|
||||
}
|
||||
return [...channels.values()]
|
||||
})
|
||||
const activeChannel = computed(() => channelTabs.value.find((item) => item.code === activeChannelCode.value) || null)
|
||||
const editable = computed(() => plan.value?.status === 'EDITING' || plan.value?.status === 'LOADING')
|
||||
const selectedPlanStatus = computed(() => selectedContainer.value?.planStatus || null)
|
||||
const assignedDetailIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundDetailId)))
|
||||
@@ -88,6 +106,7 @@ const filteredContainers = computed(() => {
|
||||
const status = String(appliedQuery.value.status || '')
|
||||
return containers.value.filter((item) => {
|
||||
if (item.planStatus === 'COMPLETED') return false
|
||||
if (!activeChannelCode.value || item.channelCode !== activeChannelCode.value) return false
|
||||
if (status && item.planStatus !== status) return false
|
||||
if (!keyword) return true
|
||||
return [item.cabinetNo, item.containerNo, item.bookingNo, item.destinationWarehouseCode, item.destinationWarehouseName]
|
||||
@@ -215,6 +234,9 @@ 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 (!channelTabs.value.some((item) => item.code === activeChannelCode.value)) {
|
||||
activeChannelCode.value = channelTabs.value[0]?.code || ''
|
||||
}
|
||||
if (!filteredContainers.value.some((item) => item.id === selectedContainerId.value)) selectedContainerId.value = preferredContainerId()
|
||||
await loadSelectedData(preserved)
|
||||
} catch (error) {
|
||||
@@ -241,6 +263,13 @@ async function selectContainer(containerId: string) {
|
||||
mutationError.value = null
|
||||
await loadSelectedData()
|
||||
}
|
||||
async function selectChannel(channelCode: string) {
|
||||
if (activeChannelCode.value === channelCode) return
|
||||
activeChannelCode.value = channelCode
|
||||
selectedContainerId.value = preferredContainerId()
|
||||
mutationError.value = null
|
||||
await loadSelectedData()
|
||||
}
|
||||
async function applyFilters(model: QueryModel) {
|
||||
appliedQuery.value = { ...model }
|
||||
if (!filteredContainers.value.some((item) => item.id === selectedContainerId.value)) {
|
||||
@@ -347,8 +376,23 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage>
|
||||
<component :is="props.embedded ? 'div' : AdminPage" :class="{ 'embedded-smart-loading': props.embedded }">
|
||||
<section class="global-loading-page">
|
||||
<nav v-if="channelTabs.length" class="channel-tabs" aria-label="物流渠道" role="tablist">
|
||||
<button
|
||||
v-for="channel in channelTabs"
|
||||
:key="channel.code"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="channel.code === activeChannelCode"
|
||||
:class="{ active: channel.code === activeChannelCode }"
|
||||
@click="selectChannel(channel.code)"
|
||||
>
|
||||
<span>{{ channel.name }}</span>
|
||||
<small>{{ channel.containerCount }} 个柜次</small>
|
||||
</button>
|
||||
</nav>
|
||||
<el-alert v-else-if="containers.length" type="warning" :closable="false" show-icon title="当前柜次缺少渠道快照,暂时不能参与智能装柜" />
|
||||
<QueryToolbar
|
||||
v-model="query"
|
||||
ariaLabel="智能装柜筛选"
|
||||
@@ -376,7 +420,7 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
|
||||
<section class="dispatch-grid" aria-label="智能装柜调度台">
|
||||
<section class="dispatch-pane candidate-pane">
|
||||
<header class="pane-header"><div><strong>候选订单与货物</strong><small>{{ selectedContainer?.warehouseName || '请先选择柜次' }}</small></div></header>
|
||||
<header class="pane-header"><div><strong>候选入库单与货物</strong><small>{{ activeChannel?.name || '未选择渠道' }} · {{ selectedContainer?.warehouseName || '请先选择柜次' }}</small></div></header>
|
||||
<DataTable
|
||||
:data="filteredCandidates"
|
||||
:columns="candidateColumns"
|
||||
@@ -407,7 +451,7 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
</section>
|
||||
|
||||
<aside class="dispatch-pane container-pane" aria-label="货柜列表">
|
||||
<header class="pane-header"><div><strong>货柜</strong><small>待装柜、预装柜中与拣货中</small></div></header>
|
||||
<header class="pane-header"><div><strong>当前渠道柜次</strong><small>待装柜、预装柜中与拣货中</small></div></header>
|
||||
<div v-loading="loading" class="container-list">
|
||||
<el-empty v-if="!filteredContainers.length" description="暂无符合条件的货柜" :image-size="72" />
|
||||
<button
|
||||
@@ -420,6 +464,7 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
>
|
||||
<span class="container-option__top"><strong>{{ item.cabinetNo }}</strong><el-tag size="small" :type="statusType(item.planStatus)">{{ statusLabel(item.planStatus) }}</el-tag></span>
|
||||
<span>{{ item.containerNo || '集装箱号待录入' }} · {{ item.containerType }}</span>
|
||||
<span>{{ item.channelName || item.channelCode || '渠道未设置' }}</span>
|
||||
<span>{{ item.warehouseName || '仓库未设置' }}</span>
|
||||
<span>目的仓 {{ item.destinationWarehouseName || item.destinationWarehouseCode || '不限' }}</span>
|
||||
<span v-if="item.id === selectedContainerId && plan">体积 {{ formatNumber(totalVolume) }} / {{ formatNumber(volumeLimit) }} m³</span>
|
||||
@@ -478,16 +523,23 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
</AppDialog>
|
||||
</template>
|
||||
</section>
|
||||
</AdminPage>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.global-loading-page { display: grid; gap: 16px; min-width: 0; }
|
||||
.embedded-smart-loading { min-width: 0; }
|
||||
.channel-tabs { display: flex; min-height: 48px; overflow-x: auto; border-bottom: 2px solid var(--cc-border); background: #fff; }
|
||||
.channel-tabs button { display: flex; min-width: 150px; min-height: 48px; align-items: center; justify-content: center; gap: 7px; padding: 8px 18px; border: 0; border-bottom: 2px solid transparent; background: transparent; color: var(--cc-text); cursor: pointer; }
|
||||
.channel-tabs button.active { margin-bottom: -2px; border-bottom-color: var(--el-color-primary); color: var(--el-color-primary); font-weight: 600; }
|
||||
.channel-tabs button:focus-visible { outline: 2px solid var(--el-color-primary); outline-offset: -3px; }
|
||||
.channel-tabs small { color: var(--cc-muted); font-weight: 400; white-space: nowrap; }
|
||||
.summary-band { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1px; border: 1px solid var(--cc-border); background: var(--cc-border); }
|
||||
.summary-band > div { display: grid; gap: 4px; padding: 12px 14px; background: #fff; }
|
||||
.summary-band span,.pane-header small,.container-option span,.global-loading-page small { color: var(--cc-muted); font-size: 12px; }
|
||||
.summary-band strong { font-size: 16px; }
|
||||
.dispatch-grid { display: grid; grid-template-columns: minmax(390px, 1.1fr) 270px minmax(460px, 1.35fr); min-height: 620px; overflow-x: auto; border: 1px solid var(--cc-border); background: #fff; }
|
||||
.embedded-smart-loading .dispatch-grid { min-height: 520px; height: min(62vh, 700px); }
|
||||
.dispatch-pane { display: flex; min-width: 0; flex-direction: column; }
|
||||
.dispatch-pane + .dispatch-pane { border-left: 1px solid var(--cc-border); }
|
||||
.pane-header { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border-bottom: 1px solid var(--cc-border); background: #fff; }
|
||||
@@ -522,5 +574,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 button { min-width: 132px; padding-inline: 12px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user