@@ -0,0 +1,95 @@
< script setup lang = "ts" >
import { computed , ref , watch } from 'vue'
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import { getLoadingAssociationSummary , getOverseasInboundCodeRelationship , getPlatformInboundCodeAssociation , type LoadingAssociationSummary , type OverseasInboundCodeRelationship , type PlatformInboundCodeAssociation } from '@/shared/api/inboundCodeRelations'
import ApiErrorMeta from './ApiErrorMeta.vue'
import AppDrawer from './AppDrawer.vue'
import LoadErrorState from './LoadErrorState.vue'
import StatusBadge from './StatusBadge.vue'
const props = defineProps < { modelValue : boolean ; inboundCode : string ; canReadBase : boolean ; canReadLoading : boolean ; canReadOverseas : boolean } > ( )
const emit = defineEmits < { 'update:modelValue' : [ value : boolean ] } > ( )
const baseAssociation = ref < PlatformInboundCodeAssociation | null > ( null )
const overseas = ref < OverseasInboundCodeRelationship | null > ( null )
const loadingAssociation = ref < LoadingAssociationSummary | null > ( null )
const baseError = ref < ApiErrorDetails | null > ( null )
const overseasError = ref < ApiErrorDetails | null > ( null )
const loadingAssociationError = ref < ApiErrorDetails | null > ( null )
const loading = ref ( false )
let serial = 0
const canReadBase = computed ( ( ) => props . canReadBase )
const canReadLoading = computed ( ( ) => props . canReadLoading )
const canReadOverseas = computed ( ( ) => props . canReadOverseas )
const normalizedCode = computed ( ( ) => props . inboundCode . trim ( ) )
function display ( value : unknown , fallback = '—' ) { return String ( value ? ? '' ) . trim ( ) || fallback }
function close ( value : boolean ) { emit ( 'update:modelValue' , value ) }
async function load ( ) {
const code = normalizedCode . value
const request = ++ serial
baseAssociation . value = null ; overseas . value = null ; loadingAssociation . value = null ; baseError . value = null ; overseasError . value = null ; loadingAssociationError . value = null
if ( ! code || ( ! canReadBase . value && ! canReadLoading . value && ! canReadOverseas . value ) ) return
loading . value = true
const tasks : Promise < void > [ ] = [ ]
if ( canReadBase . value ) tasks . push ( getPlatformInboundCodeAssociation ( code ) . then ( ( value ) => { if ( request === serial ) baseAssociation . value = value } ) . catch ( ( cause ) => { if ( request === serial ) baseError . value = getApiErrorDetails ( cause , '订单基础关联数据加载失败' ) } ) )
if ( canReadLoading . value ) tasks . push ( getLoadingAssociationSummary ( code ) . then ( ( value ) => { if ( request === serial ) loadingAssociation . value = value } ) . catch ( ( cause ) => { if ( request === serial ) loadingAssociationError . value = getApiErrorDetails ( cause , '装柜与PDA关联数据加载失败' ) } ) )
if ( canReadOverseas . value ) tasks . push ( getOverseasInboundCodeRelationship ( code ) . then ( ( value ) => { if ( request === serial ) overseas . value = value } ) . catch ( ( cause ) => { if ( request === serial ) overseasError . value = getApiErrorDetails ( cause , '海外入仓关联数据加载失败' ) } ) )
await Promise . all ( tasks )
if ( request === serial ) loading . value = false
}
watch ( ( ) => [ props . modelValue , props . inboundCode , props . canReadBase , props . canReadLoading , props . canReadOverseas ] as const , ( [ open ] ) => {
if ( open ) void load ( )
else serial += 1
} )
< / script >
< template >
< AppDrawer :model-value = "modelValue" : title = "`入仓码关联数据 · ${normalizedCode || '未提供'}`" size = "min(860px, 94vw)" :loading = "loading" :show-footer = "false" destroy -on -close @ update :model-value = "close" >
< el-alert v-if = "!normalizedCode" type="warning" :closable="false" show-icon title="缺少入仓码,已停止查询关联数据。" / >
< el-alert v-else-if = "!canReadBase && !canReadLoading && !canReadOverseas" type="warning" :closable="false" show-icon title="当前账号无入仓关联数据查看权限。" / >
< div v-else class = "relationship-sections" >
< section v-if = "canReadBase" class="relationship-section" aria-label="订单基础关联" >
< header > < h3 > 订单基础信息 < / h3 > < StatusBadge v-if = "baseAssociation?.orderStatus" :status="baseAssociation.orderStatus" / > < / header >
< LoadErrorState v-if = "baseError" title="订单基础关联加载失败" :message="baseError.message" @retry="load"><template #details><ApiErrorMeta :details="baseError" / > < / template > < / LoadErrorState >
< dl v-else-if = "baseAssociation" class="compact-facts" >
< div > < dt > 入仓码 < / dt > < dd :title = "display(baseAssociation.inboundCode)" > { { display ( baseAssociation . inboundCode ) } } < / dd > < / div > < div > < dt > 订单编码 < / dt > < dd : title = "display(baseAssociation.orderNo, '订单编码待补充')" > { { display ( baseAssociation . orderNo , '订单编码待补充' ) } } < / dd > < / div >
< div > < dt > 客户 < / dt > < dd : title = "display(baseAssociation.customerName, '客户名称待补充')" > { { display ( baseAssociation . customerName , '客户名称待补充' ) } } < / dd > < / div > < div > < dt > 目的类型 < / dt > < dd > { { display ( baseAssociation . destinationType ) } } < / dd > < / div >
< div > < dt > 目的国家 < / dt > < dd > { { display ( baseAssociation . destinationCountryCode ) } } < / dd > < / div > < div > < dt > 目的仓 < / dt > < dd > { { display ( baseAssociation . destinationWarehouseName , '目的仓名称待补充' ) } } < small v-if = "baseAssociation.destinationWarehouseCode" > {{ baseAssociation.destinationWarehouseCode }} < / small > < / dd > < / div >
< / dl >
< / section >
< section v-if = "canReadLoading" class="relationship-section" aria-label="装柜与PDA关联" >
< header > < h3 > 装柜与PDA < / h3 > < StatusBadge v-if = "loadingAssociation?.loadingStatus" :status="loadingAssociation.loadingStatus" / > < / header >
< LoadErrorState v-if = "loadingAssociationError" title="装柜关联加载失败" :message="loadingAssociationError.message" @retry="load"><template #details><ApiErrorMeta :details="loadingAssociationError" / > < / template > < / LoadErrorState >
< dl v-else-if = "loadingAssociation" class="compact-facts" >
< div > < dt > 订单编码 < / dt > < dd : title = "display(loadingAssociation.orderNo, '订单编码待补充')" > { { display ( loadingAssociation . orderNo , '订单编码待补充' ) } } < / dd > < / div > < div > < dt > 装柜计划 < / dt > < dd : title = "display(loadingAssociation.loadingPlanNo, '尚未进入装柜')" > { { display ( loadingAssociation . loadingPlanNo , '尚未进入装柜' ) } } < / dd > < / div >
< div > < dt > 柜号 < / dt > < dd : title = "display(loadingAssociation.containerNo || loadingAssociation.cabinetNo, '尚未关联柜号')" > { { display ( loadingAssociation . containerNo || loadingAssociation . cabinetNo , '尚未关联柜号' ) } } < / dd > < / div > < div > < dt > 同柜组 < / dt > < dd :title = "display(loadingAssociation.coLoadGroupNo)" > { { display ( loadingAssociation . coLoadGroupNo ) } } < / dd > < / div >
< div > < dt > 装柜状态 < / dt > < dd > { { display ( loadingAssociation . loadingStatus ) } } < / dd > < / div > < div > < dt > PDA任务状态 < / dt > < dd > { { display ( loadingAssociation . pdaTaskStatus , '尚未生成PDA任务' ) } } < / dd > < / div >
< / dl >
< / section >
< section v-if = "canReadOverseas" class="relationship-section" aria-label="海外入仓关联" >
< header > < h3 > 海外入仓与库存 < / h3 > < StatusBadge v-if = "overseas?.overseasReceivingStatus" :status="overseas.overseasReceivingStatus" / > < / header >
< LoadErrorState v-if = "overseasError" title="海外关联加载失败" :message="overseasError.message" @retry="load"><template #details><ApiErrorMeta :details="overseasError" / > < / template > < / LoadErrorState >
< el-empty v-else-if = "overseas && !overseas.found" description="服务端未找到该入仓码的海外关联数据" :image-size="56" / >
< template v-else-if = "overseas" >
< dl class = "compact-facts" > < div > < dt > 客户 < / dt > < dd > { { display ( overseas . customer ? . customerName , '客户名称待补充' ) } } < / dd > < / div > < div > < dt > 海外仓 < / dt > < dd > { { display ( overseas . overseasWarehouse ? . warehouseName , '仓库名称待补充' ) } } < small v-if = "overseas.overseasWarehouse?.warehouseCode" > {{ overseas.overseasWarehouse.warehouseCode }} < / small > < / dd > < / div > < / dl >
< div class = "compact-list" > < strong > 尾程订单 < / strong > < span : title = "overseas.taillegOrders.map((item) => item.orderNo).join('、')" > { { overseas . taillegOrders . map ( ( item ) => ` ${ item . orderNo } ${ item . orderStatus ? ` ( ${ item . orderStatus } ) ` : '' } ` ) . filter ( Boolean ) . join ( '、' ) || '—' } } < / span > < / div >
< div class = "compact-list" > < strong > 入库计划 < / strong > < span : title = "overseas.inboundPlans.map((item) => item.planNo).join('、')" > { { overseas . inboundPlans . map ( ( item ) => ` ${ item . planNo } ${ item . planStatus ? ` ( ${ item . planStatus } ) ` : '' } ${ item . receivingStatus ? ` · ${ item . receivingStatus } ` : '' } ` ) . join ( '、' ) || '—' } } < / span > < / div >
< div class = "compact-list" > < strong > 托盘号 < / strong > < span :title = "overseas.palletCodes.join('、')" > { { overseas . palletCodes . join ( '、' ) || '—' } } < / span > < / div >
< el-table v-if = "overseas.inventory.length" :data="overseas.inventory" size="small" border class="inventory-table" >
< el -table -column prop = "skuCode" label = "SKU编码" min -width = " 150 " / > < el-table-column label = "产品名称" min -width = " 190 " > < template # default = "{ row }" > { { display ( row . productNameZh || row . productNameEn ) } } < / template > < / el-table-column > < el-table-column prop = "packageCount" label = "库存箱数" width = "110" align = "right" / >
< / el-table >
< / template >
< / section >
< / div >
< / AppDrawer >
< / template >
< style scoped >
. relationship - sections { display : grid ; gap : 14 px } . relationship - section { border : 1 px solid var ( -- cc - border ) ; border - radius : 10 px ; padding : 14 px ; background : var ( -- cc - surface ) } . relationship - section > header { display : flex ; align - items : center ; justify - content : space - between ; gap : 12 px ; margin - bottom : 12 px } . relationship - section h3 { margin : 0 ; font - size : 15 px } . compact - facts { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; gap : 1 px ; margin : 0 ; background : var ( -- cc - border ) } . compact - facts > div { display : grid ; grid - template - columns : 92 px minmax ( 0 , 1 fr ) ; gap : 8 px ; padding : 9 px 10 px ; background : var ( -- cc - surface ) } . compact - facts dt { color : var ( -- cc - muted ) } . compact - facts dd { min - width : 0 ; margin : 0 ; overflow : hidden ; text - overflow : ellipsis ; white - space : nowrap } . compact - facts small { display : block ; overflow : hidden ; color : var ( -- cc - muted ) ; margin - top : 2 px ; text - overflow : ellipsis ; white - space : nowrap } . compact - list { display : grid ; grid - template - columns : 92 px minmax ( 0 , 1 fr ) ; gap : 8 px ; padding : 9 px 10 px ; border - top : 1 px solid var ( -- cc - border ) } . compact - list span { min - width : 0 ; overflow : hidden ; text - overflow : ellipsis ; white - space : nowrap } . inventory - table { margin - top : 10 px } @ media ( max - width : 720 px ) { . compact - facts { grid - template - columns : 1 fr } }
< / style >