@@ -8,7 +8,7 @@ import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import { listDispatchCandidates , lockDispatchDraftPallet , preArrangeDispatch , releaseDispatchDraft , unlockDispatchDraftPallet , type DispatchPalletCandidateDto } from '../api/dispatches'
import { createDispatchDraftContainer , deleteDispatchDraftContainer , listDispatchCandidates , listDispatchDrafts , lockDispatchDraftPallet , preArrangeDispatch , releaseDispatchDraft , unlockDispatchDraftPallet , type DispatchPalletCandidateDto } from '../api/dispatches'
import type { InboundOrderListItem } from '../types'
interface LoadingContainer { id : string ; capacity : number ; palletIds : string [ ] }
@@ -21,6 +21,7 @@ const saving = ref(false)
const loadError = ref < ApiErrorDetails | null > ( null )
const mutationError = ref < ApiErrorDetails | null > ( null )
const candidates = ref < DispatchPalletCandidateDto [ ] > ( [ ] )
const palletSnapshots = ref < Record < string , DispatchPalletCandidateDto > > ( { } )
const activeOrderKey = ref ( '' )
const containers = ref < LoadingContainer [ ] > ( [ ] )
const busyPalletIds = ref < string [ ] > ( [ ] )
@@ -57,7 +58,7 @@ const orderGroups = computed(() => {
} )
const activePallets = computed ( ( ) => orderGroups . value . find ( ( item ) => item . key === activeOrderKey . value ) ? . pallets || [ ] )
const assignedIds = computed ( ( ) => containers . value . flatMap ( ( item ) => item . palletIds ) )
const assigned = computed ( ( ) => assignedIds . value . map ( ( id ) => candidates . value . find ( ( item ) => palletId ( item ) === id ) ) . filter ( ( item ) : item is DispatchPalletCandidateDto => Boolean ( item ) ) )
const assigned = computed ( ( ) => assignedIds . value . map ( ( id ) => palletSnapshot ( id ) ) . filter ( ( item ) : item is DispatchPalletCandidateDto => Boolean ( item ) ) )
const assignedPackages = computed ( ( ) => assigned . value . reduce ( ( sum , item ) => sum + Number ( item . packageCount || 0 ) , 0 ) )
const assignedVolume = computed ( ( ) => assigned . value . reduce ( ( sum , item ) => sum + Number ( item . volumeCbm || 0 ) , 0 ) )
@@ -68,16 +69,25 @@ function dateTime(value: unknown) { const raw = String(value || '').trim(); retu
function orderType ( value : unknown ) { return ( { FIRST _LEG : '头程订单' , RELABEL _APPLICATION : '换标订单' , RETURN _RELABEL : '退换标单' , LOCAL _LOOSEBOARD : '本土散板' , WAREHOUSE _TRANSFER : '仓储转运单' , ONE _CLICK _DROPSHIP : '一件代发' , REJECTED : '拒收订单' , EXPRESS : '快递订单' } as Record < string , string > ) [ String ( value || '' ) ] || text ( value ) }
function cartonMarks ( item : DispatchPalletCandidateDto ) { return String ( item . cartonMarks || '' ) . split ( ',' ) . map ( ( value ) => value . trim ( ) ) . filter ( Boolean ) }
function containerFor ( id : string ) { return containers . value . find ( ( item ) => item . palletIds . includes ( id ) ) }
function palletSnapshot ( id : string ) { return palletSnapshots . value [ id ] }
function makeDraftToken ( ) { return globalThis . crypto ? . randomUUID ? . ( ) || ` dispatch- ${ Date . now ( ) } - ${ Math . random ( ) . toString ( 16 ) . slice ( 2 ) } ` }
function ensureDraftToken ( ) {
if ( ! draftToken . value . trim ( ) ) draftToken . value = makeDraftToken ( )
return draftToken . value
}
function rememberPallets ( items : DispatchPalletCandidateDto [ ] ) {
palletSnapshots . value = { ... palletSnapshots . value , ... Object . fromEntries ( items . map ( ( item ) => [ palletId ( item ) , item ] ) ) }
}
async function hydrateDraftPallets ( ) {
const restored = await listDispatchCandidates ( { draftToken : ensureDraftToken ( ) } )
rememberPallets ( restored )
}
async function load ( ) {
loading . value = true ; loadError . value = null
try {
candidates . value = await listDispatchCandidates ( { planIds : planIds . value . length ? planIds . value . join ( ',' ) : undefined , orderNo : query . orderNo . trim ( ) || undefined , fbaWarehouseCode : query . fbaWarehouseCode . trim ( ) || undefined , sourceStage : query . sourceStage || undefined } )
candidates . value = await listDispatchCandidates ( { planIds : planIds . value . length ? planIds . value . join ( ',' ) : undefined , orderNo : query . orderNo . trim ( ) || undefined , fbaWarehouseCode : query . fbaWarehouseCode . trim ( ) || undefined , sourceStage : query . sourceStage || undefined , draftToken : ensureDraftToken ( ) } )
rememberPallets ( candidates . value )
activeOrderKey . value = orderGroups . value [ 0 ] ? . key || ''
} catch ( error ) {
candidates . value = [ ] ; activeOrderKey . value = ''
@@ -92,7 +102,8 @@ async function addContainer() {
confirmButtonText : '确定' , cancelButtonText : '取消' , inputValue : '20' , inputType : 'number' ,
inputValidator : ( value ) => Number . isInteger ( Number ( value ) ) && Number ( value ) > 0 && Number ( value ) <= 500 ? true : '请输入 1 至 500 的整数' ,
} )
containers . value . push ( { id : ` ${ Date . now ( ) } - ${ containers . value . length + 1 } ` , capacity : Number ( result . value ) , palletIds : [ ] } )
const created = await createDispatchDraftContainer ( ensureDraftToken ( ) , Number ( result . value ) )
containers . value . push ( { id : created . id , capacity : created . capacity , palletIds : created . palletIds || [ ] } )
} catch { /* user cancelled */ }
}
@@ -106,13 +117,12 @@ async function movePallet(item: DispatchPalletCandidateDto, containerId: string)
if ( anchor ? . customerId && anchor . customerId !== item . customerId ) { ElMessage . warning ( '一次排车只能装同一客户的托盘' ) ; return }
if ( anchor ? . overseasWarehouseId && anchor . overseasWarehouseId !== item . overseasWarehouseId ) { ElMessage . warning ( '一次排车只能装同一海外仓的托盘' ) ; return }
busyPalletIds . value . push ( id ) ; mutationError . value = null
try { await lockDispatchDraftPallet ( id , ensureDraftToken ( ) ) ; target . palletIds . push ( id ) }
try { await lockDispatchDraftPallet ( id , ensureDraftToken ( ) , target . id ) ; target . palletIds . push ( id ) }
catch ( error ) { mutationError . value = getApiErrorDetails ( error , '托盘锁定失败,请刷新后重试' ) }
finally { busyPalletIds . value = busyPalletIds . value . filter ( ( value ) => value !== id ) }
}
async function removePallet ( item : DispatchPalletCandidateDto ) {
const id = palletId ( item )
async function removePallet ( id : string ) {
const owner = containerFor ( id )
if ( ! owner ) return
busyPalletIds . value . push ( id ) ; mutationError . value = null
@@ -123,10 +133,9 @@ async function removePallet(item: DispatchPalletCandidateDto) {
async function deleteContainer ( container : LoadingContainer ) {
for ( const id of [ ... container . palletIds ] ) {
const item = candidates . value . find ( ( candidate ) => palletId ( candidate ) === id )
if ( item ) await removePallet ( item )
await removePallet ( id )
}
if ( ! container . palletIds . length ) containers . value = containers . value . filter ( ( item ) => item . id !== container . id )
if ( ! container . palletIds . length ) { await deleteDispatchDraftContainer ( ensureDraftToken ( ) , container . id ) ; containers . value = containers . value . filter ( ( item ) => item . id !== container . id ) }
}
function dragStart ( event : DragEvent , item : DispatchPalletCandidateDto ) { event . dataTransfer ? . setData ( 'text/plain' , palletId ( item ) ) }
function dropInto ( event : DragEvent , containerId : string ) {
@@ -156,9 +165,9 @@ async function submit() {
watch ( ( ) => props . modelValue , ( open ) => {
if ( ! open ) return
draftToken . value = makeDraftToken ( ) ; finalized . value = false ; containers . value = [ ]
draftToken . value = makeDraftToken ( ) ; finalized . value = false ; containers . value = [ ] ; palletSnapshots . value = { }
query . orderNo = '' ; query . orderType = '' ; query . sourceStage = 'RECEIVED' ; query . fbaWarehouseCode = '' ; warehouseTab . value = 'ALL' ; mutationError . value = null
void load ( )
void ( async ( ) => { const drafts = await listDispatchDrafts ( ) . catch ( ( ) => [ ] ) ; const draft = drafts [ 0 ] ; if ( draft ? . draftToken ) { draftToken . value = draft . draftToken ; containers . value = ( draft . containers || [ ] ) . map ( ( c ) => ( { id : c . id , capacity : c . capacity , palletIds : c . palletIds || [ ] } ) ) ; await hydrateDraftPallets ( ) . catch ( ( ) => undefined ) } await load ( ) } ) ( )
} , { immediate : true } )
watch ( [ warehouseTab , ( ) => query . orderType ] , ( ) => {
@@ -222,7 +231,7 @@ watch([warehouseTab, () => query.orderType], () => {
< div v-for = "(container, index) in containers" :key="container.id" class="loading-container" @dragover.prevent @drop.prevent="dropInto($event, container.id)" >
< div class = "container-head" > < strong > 装车托盘 { { index + 1 } } < / strong > < span > 托数 < b > { { container . palletIds . length } } < / b > / { { container . capacity } } < / span > < el-button text type = "danger" : aria -label = " ` 删除装车托盘 $ { index + 1 } ` " @click ="deleteContainer(container)" > < Trash2 :size = "15" / > < / el-button > < / div >
< div class = "container-body" >
< div v-for = "id in container.palletIds" :key="id" class="loaded-pallet"><span><strong>{{ text(candidates.find((item) => palletId(item) === id)?.palletNo) }}</strong>{{ text(candidates.find((item) => palletId(item) === id)?.orderNo) }}</span><el-button text type="danger" :loading="busyPalletIds.includes(id)" @click="removePallet(candidates.find((item) => palletId(item) === id)! )" > 移除 < / el -button > < / div >
< div v-for = "id in container.palletIds" :key="id" class="loaded-pallet"><span><strong>{{ text(palletSnapshot( id)?.palletNo || id ) }}</strong>{{ text(palletSnapshot( id)?.orderNo) }}</span><el-button text type="danger" :loading="busyPalletIds.includes(id)" @click="removePallet(id )" > 移除 < / el -button > < / div >
< span v-if = "!container.palletIds.length" class="drop-hint" > 拖入托盘 , 移入后立即锁定 < / span >
< / div >
< / div >