@@ -10,7 +10,7 @@ import type { FleetTrip, TripLoading, TripCapacityPlanning, TripMeasurement } fr
const props = defineProps < { trip : FleetTrip ; editable : boolean ; planning ? : boolean } > ( )
const emit = defineEmits < { saved : [ loading : TripLoading ] ; planned : [ plan : TripCapacityPlanning ] } > ( )
type PalletRow = { warehousePalletId : string ; palletNo : string ; destinations : string ; palletSpaces : number ; weightKg : number | null ; volumeCbm : number | null ; deliveryIndex : number | null }
type PalletRow = { warehousePalletId : string ; palletNo : string ; destinations : string ; destinationKeys : string [ ] ; palletSpaces : number ; weightKg : number | null ; volumeCbm : number | null ; deliveryIndex : number | null }
const info = ref < TripLoading | TripCapacityPlanning | null > ( null ) , rows = ref < PalletRow [ ] > ( [ ] ) , busy = ref ( false ) , loading = ref ( false ) , error = ref < ApiErrorDetails | null > ( null )
let sequence = 0
const columns = computed < DataTableColumn [ ] > ( ( ) => [ { key : 'palletNo' , prop : 'palletNo' , label : '实物托盘' , minWidth : 150 } ,
@@ -20,17 +20,29 @@ const columns = computed<DataTableColumn[]>(() => [{ key: 'palletNo', prop: 'pal
const savedLoad = computed ( ( ) => info . value && ( 'capacityPlan' in info . value ? info . value . capacityPlan : info . value . confirmedLoad ) )
const segments = computed ( ( ) => info . value && 'availableSegments' in info . value ? info . value . availableSegments : savedLoad . value ? . segments || [ ] )
const stalePlan = computed ( ( ) => info . value && 'planValid' in info . value && ! ! info . value . capacityPlan && ! info . value . planValid )
function matchingStops ( row : PalletRow ) {
if ( row . destinationKeys . length !== 1 || ! row . destinationKeys [ 0 ] ) return [ ]
return props . trip . stops . map ( ( stop , index ) => ( { ... stop , index } ) )
. filter ( stop => stop . index > 0 && stop . destinationKey === row . destinationKeys [ 0 ] )
}
const destinationIssue = computed ( ( ) => {
for ( const row of rows . value ) {
if ( row . destinationKeys . length > 1 ) return ` 托盘 ${ row . palletNo } 包含不同目的地,请先拆托。 `
if ( ! matchingStops ( row ) . length ) return ` 托盘 ${ row . palletNo } 的目的地 ${ row . destinations } 尚未匹配车次卸货站;站点同名不代表已匹配。请到“分配与货物”点击“按货物匹配卸货站”,再重新核对容量。 `
}
return ''
} )
function apply ( value : TripLoading | TripCapacityPlanning ) {
info . value = value
const pallets = new Map < string , PalletRow > ( )
for ( const cargo of value . cargo ) {
if ( ! cargo . warehousePalletId ) continue
const existing = pallets . get ( cargo . warehousePalletId )
if ( existing ) { if ( cargo . destinationLabel && ! existing . destinations . split ( ' / ' ) . includes ( cargo . destinationLabel ) ) existing . destinations += ` / ${ cargo . destinationLabel } ` ; continue }
if ( existing ) { if ( ! existing . destinationKeys . includes ( cargo . destinationKey || '' ) ) existing . destinationKeys . push ( cargo . destinationKey || '' ) ; if ( cargo . destinationLabel && ! existing . destinations . split ( ' / ' ) . includes ( cargo . destinationLabel ) ) existing . destinations += ` / ${ cargo . destinationLabel } ` ; continue }
const saved = ( 'capacityPlan' in value ? value . capacityPlan : value . confirmedLoad ) ? . pallets . find ( row => row . warehousePalletId === cargo . warehousePalletId )
const index = props . trip . stops . findIndex ( stop => ! ! cargo . destinationKey && stop . destinationKey === cargo . destinationKey )
pallets . set ( cargo . warehousePalletId , { warehousePalletId : cargo . warehousePalletId , palletNo : cargo . palletNo || '—' , destinations : cargo . destinationLabel || '—' ,
palletSpaces : saved ? . palletSpaces ? ? 1 , weightKg : saved ? . weightKg ? ? null , volumeCbm : saved ? . volumeCbm ? ? null , deliveryIndex : saved ? . deliveryIndex ? ? ( index > 0 ? index : null ) } )
pallets . set ( cargo . warehousePalletId , { warehousePalletId : cargo . warehousePalletId , palletNo : cargo . palletNo || '—' , destinations : cargo . destinationLabel || '—' , destinationKeys : [ cargo . destinationKey || '' ] ,
palletSpaces : saved ? . palletSpaces ? ? 1 , weightKg : saved ? . weightKg ? ? null , volumeCbm : saved ? . volumeCbm ? ? null , deliveryIndex : saved && saved . deliveryIndex > 0 && props . trip . stops [ saved . deliveryIndex ] ? . destinationKey === cargo . destinationKey ? saved . deliveryIndex : ( index > 0 ? index : null ) } )
}
rows . value = [ ... pallets . values ( ) ]
}
@@ -41,7 +53,9 @@ async function load() {
finally { if ( current === sequence ) loading . value = false }
}
async function save ( ) {
if ( busy . value || ! info . value ) return
if ( busy . value || loading . value || ! props . editable || ! info . value ) return
if ( destinationIssue . value ) { ElMessage . warning ( destinationIssue . value ) ; return }
if ( rows . value . some ( row => ! matchingStops ( row ) . some ( stop => stop . index === row . deliveryIndex ) ) ) { ElMessage . warning ( '请选择与货物目的地匹配的卸货站' ) ; return }
if ( ! rows . value . length || rows . value . some ( row => row . weightKg == null || row . volumeCbm == null || row . deliveryIndex == null ) ) { ElMessage . warning ( props . planning ? '请填写每托预计重量、体积和卸货站' : '请填写每托实测重量、体积和卸货站' ) ; return }
const current = sequence , snapshot = info . value
const pallets : TripMeasurement [ ] = rows . value . map ( row => ( { warehousePalletId : row . warehousePalletId , palletSpaces : row . palletSpaces ,
@@ -66,6 +80,7 @@ onBeforeUnmount(() => { sequence++ })
< div v-loading = "loading" >
< LoadErrorState v-if = "error" :title="planning ? '计划容量操作未完成' : '装载操作未完成'" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" / > < / template > < / LoadErrorState >
< p v-if = "planning" > 先核对本车自有货的预计载荷 , 查看每段剩余托位 、 载重和容积 。 实际装载完成后仍需按实测值确认 。 < / p >
< p v-if = "destinationIssue" role="alert" > {{ destinationIssue }} < / p >
< p v-if = "stalePlan" role="status" > 货物明细已变化 , 请重新核对计划容量 。 < / p >
< p v-if = "info?.blockReason" role="status" > {{ info.blockReason }} < / p >
< p v-else-if = "!planning && savedLoad" > 已确认实际装载 ; 车辆 、 司机或计划调整后需要重新确认 。 < / p >
@@ -74,8 +89,8 @@ onBeforeUnmount(() => { sequence++ })
< template # spaces = "{ row }" > < el-input-number v-model = "row.palletSpaces" :min="1" :precision="0" :disabled="!editable || busy" placeholder="占用托位" / > < / template >
< template # weight = "{ row }" > < el-input-number v-model = "row.weightKg" :min="0.001" :precision="3" :disabled="!editable || busy" :placeholder="planning ? '预计重量' : '实际重量'" / > < / template >
< template # volume = "{ row }" > < el-input-number v-model = "row.volumeCbm" :min="0.001" :precision="3" :disabled="!editable || busy" :placeholder="planning ? '预计体积' : '实际体积'" / > < / template >
< template # stop = "{ row }" > < el-select v-model = "row.deliveryIndex" :disabled="!editable || busy" placeholder="选择卸货站 "><el-option v-for="( stop, index) in trip.stops.slice(1 )" :key="index" :value="index + 1 " :label="stop.name" / > < / el-select > < / template >
< template # toolbar -actions > < el-button @click ="load" > {{ planning ? ' 重新读取容量计划 ' : ' 重新读取装载 ' }} < / el -button > < el-button v-if = "editable" type="primary" :loading="busy" :disabled="!!info?.blockReason || !rows.length" @click="save" > {{ planning ? ' 确认计划容量 ' : ' 确认实际装载 ' }} < / el -button > < / template >
< template # stop = "{ row }" > < el-select v-model = "row.deliveryIndex" :disabled="!editable || busy || loading || !matchingStops(row as PalletRow).length " placeholder="请先匹配货物目的地 "><el-option v-for="stop in matchingStops(row as PalletRow )" :key="stop. index" :value="stop. index" :label="stop.name" / > < / el-select > < / template >
< template # toolbar -actions > < el-button @click ="load" > {{ planning ? ' 重新读取容量计划 ' : ' 重新读取装载 ' }} < / el -button > < el-button v-if = "editable" type="primary" :loading="busy" :disabled="loading || !!info?.blockReason || !!destinationIssue || !rows.length" @click="save" > {{ planning ? ' 确认计划容量 ' : ' 确认实际装载 ' }} < / el -button > < / template >
< / DataTable >
< table v-if = "savedLoad && !stalePlan" class="density-table"><thead><tr><th>行驶区间</th><th>占用托位</th><th>重量(kg) </th><th>体积(m³)</th><th v-if="planning">剩余托位 / kg / m³</th></tr></thead><tbody><tr v-for="segment in segments" :key="segment.fromIndex"><td>{{ trip.stops[segment.fromIndex]?.name }} → {{ trip.stops[segment.toIndex]?.name }}</td><td>{{ segment.occupied.palletSpaces }} / {{ trip.maxPalletSpaces }}</td><td>{{ segment.occupied.weightKg }} / {{ trip.maxLoadKg }}</td><td>{{ segment.occupied.volumeCbm }} / {{ trip.maxVolumeCbm }}</td><td v-if="planning" > {{ segment.remaining.palletSpaces }} / {{ segment.remaining.weightKg }} / {{ segment.remaining.volumeCbm }} < / td > < / tr > < / tbody > < / table >
< / div >