@@ -18,10 +18,12 @@ import { listLooseboardDemands, listCapacityOffers, listCapacityInquiries, getCa
import { offerStatusLabels , inquiryStatusLabels , offerTime , loadText , type LooseboardDemand , type CapacityOffer , type CapacityInquiry , type InquiryResult , type OfferQuery , type InquiryQuery } from '../capacityOfferTypes'
import { inquirySourceQuery , inquiryOrderTarget } from '../inquiryNavigation'
import { useCapacityInquiryCommands } from '../useCapacityInquiryCommands'
import { false } from '../capacityOfferFeatures'
const router = useRouter ( ) , access = useAccessPreviewStore ( )
const manage = computed ( ( ) => access . can ( 'platform:fleet:capacity-offer:manage' ) )
const tab = ref < 'offers' | 'inquiries' | 'demands' > ( manage . value ? 'inquiries' : 'offers' )
const filters = ref < QueryModel > ( manage . value ? { scope : 'TODO' } : { } ) , applied = ref < QueryModel > ( { ... filters . value } )
// 当前只展示余位需求池;登记、原单匹配及履约模板直接隐藏,原实现保留。
const tab = ref < 'offers' | 'inquiries' | 'demands' > ( 'offers' )
const filters = ref < QueryModel > ( { } ) , applied = ref < QueryModel > ( { ... filters . value } )
const page = ref ( 1 ) , size = ref ( 20 ) , total = ref ( 0 ) , loading = ref ( false ) , error = ref < ApiErrorDetails | null > ( null )
const offers = ref < CapacityOffer [ ] > ( [ ] ) , inquiries = ref < CapacityInquiry [ ] > ( [ ] )
const editorOpen = ref ( false ) , editing = ref < CapacityInquiry | null > ( null ) , selectedOffer = ref < CapacityOffer | null > ( null ) , readonly = ref ( true )
@@ -56,8 +58,8 @@ let sequence = 0, editorSequence = 0
const fields = computed < QueryField [ ] > ( ( ) => tab . value === 'offers' ? [
{ key : 'origin' , label : '起运仓' , type : 'input' , placeholder : '起运仓名称' , width : 170 } , { key : 'destination' , label : '目的站' , type : 'input' , placeholder : '目的站名称' , width : 170 } ,
{ key : 'departureFrom' , label : '发车起日' , type : 'date' , placeholder : '发车起日' , width : 170 } , { key : 'departureTo' , label : '发车止日' , type : 'date' , placeholder : '发车止日' , width : 170 } ,
{ key : 'canConfirm' , label : '接单资格 ' , type : 'select' , placeholder : '全部接单资格 ' , width : 150 , options : [ { value : 'true' , label : '可确认承运 ' } , { value : 'false' , label : '暂不可确认 ' } ] } ,
... ( manage . value ? [ { key : 'scope' , label : '发布范围' , type : 'select' , placeholder : '可登记意向 ' , width : 180 , options : [ { value : 'AVAILABLE' , label : '可登记意向 ' } , { value : 'ALL' , label : '全部发布(含历史)' } ] } as QueryField , { key : 'status' , label : '发布状态' , type : 'select' , placeholder : '全部发布状态' , width : 140 , options : Object . entries ( offerStatusLabels ) . map ( ( [ value , label ] ) => ( { value , label } ) ) } as QueryField ] : [ ] ) ,
{ key : 'canConfirm' , label : '余位状态 ' , type : 'select' , placeholder : '全部状态 ' , width : 150 , options : [ { value : 'true' , label : '余位已核对 ' } , { value : 'false' , label : '待核对或不可承运 ' } ] } ,
... ( manage . value ? [ { key : 'scope' , label : '发布范围' , type : 'select' , placeholder : '当前发布 ' , width : 180 , options : [ { value : 'AVAILABLE' , label : '当前发布 ' } , { value : 'ALL' , label : '全部发布(含历史)' } ] } as QueryField , { key : 'status' , label : '发布状态' , type : 'select' , placeholder : '全部发布状态' , width : 140 , options : Object . entries ( offerStatusLabels ) . map ( ( [ value , label ] ) => ( { value , label } ) ) } as QueryField ] : [ ] ) ,
] : tab . value === 'demands' ? [ { key : 'keyword' , label : '需求关键词' , type : 'input' , placeholder : '原单号 / 起运地 / 目的地' , width : 280 } ] : [
{ key : 'keyword' , label : '意向关键词' , type : 'input' , placeholder : '预订号 / 客户 / 车次' , width : 260 } ,
{ key : 'status' , label : '意向状态' , type : 'select' , placeholder : '全部状态' , width : 150 , options : Object . entries ( inquiryStatusLabels ) . map ( ( [ value , label ] ) => ( { value , label } ) ) } ,
@@ -68,7 +70,7 @@ const columns = computed<DataTableColumn[]>(() => [
{ key : 'warehouseName' , label : '起运仓' , minWidth : 155 } , { key : 'destinations' , label : '目的站与可接余位' , minWidth : 310 , slotName : 'destinations' , singleLine : false } ,
{ key : 'plannedDepartureAt' , label : '发车 / 最晚送仓' , minWidth : 195 , slotName : 'times' } ,
... ( manage . value ? [ { key : 'segments' , label : '分段计划占用 / 外接预留' , minWidth : 320 , slotName : 'loads' , singleLine : false } ] : [ ] ) ,
{ key : 'status' , label : '接单 状态' , minWidth : 170 , slotName : 'status' , singleLine : false } , { key : 'contactName' , label : '调度联系信息' , minWidth : 180 , slotName : 'contact' } ,
{ key : 'status' , label : '发布 状态' , minWidth : 170 , slotName : 'status' , singleLine : false } , { key : 'contactName' , label : '调度联系信息' , minWidth : 180 , slotName : 'contact' } ,
] )
const canCreate = computed ( ( ) => manage . value || access . can ( 'platform:fleet:capacity-offer:list' ) && access . can ( 'platform:fleet:capacity-offer:inquiry-create' ) )
async function create ( row : CapacityOffer ) {
@@ -77,7 +79,7 @@ async function create(row: CapacityOffer) {
try { const value = await getCapacityOffer ( row . offerNo ) ; if ( current !== editorSequence ) return ; selectedOffer . value = value . offer ; confirmMode . value = false ; editing . value = null ; readonly . value = false ; editorOpen . value = true }
catch ( cause ) { if ( current === editorSequence ) error . value = getApiErrorDetails ( cause , '登记信息读取失败' ) }
}
function switchTab ( value : 'offers' | 'inquiries' | 'demands' ) { if ( tab . value === value ) return ; filters . value = { } ; applied . value = { } ; page . value = 1 ; total . value = 0 ; tab . value = value }
function switchTab ( value : 'offers' | 'inquiries' | 'demands' ) { if ( value !== 'offers' || tab . value === value ) return ; filters . value = { } ; applied . value = { } ; page . value = 1 ; total . value = 0 ; tab . value = value }
async function load ( ) {
const current = ++ sequence ; loading . value = true ; error . value = null
try {
@@ -104,6 +106,7 @@ function accept(value: InquiryResult) {
}
const commands = useCapacityInquiryCommands ( 'capacity-pool-command' , accept )
async function inquiryAction ( key : string , row : CapacityInquiry ) {
if ( ! false ) return
followupSequence ++
if ( openFulfillment ( key , row ) ) return
if ( key === 'cancel' ) { await commands . run ( row , key ) ; return }
@@ -129,30 +132,30 @@ watch([tab, page, size], load); onMounted(load); onBeforeUnmount(() => { followu
< / script >
< template > < AdminPage class = "admin-density admin-workbench" > < ListPageLayout aria -label = " 散板需求 " >
< p v-if = "selectedDemand && tab === 'offers'">正在匹配 {{ selectedDemand.orderNo }}: {{ selectedDemand.origin }} → {{ selectedDemand.destination }}。请选择合适车次,保存登记后尚不保位。<el-button link @click="selectedDemand = null" > 取消选择 < / el -button > < / p >
< div v-if = "tab === 'demands'" class="workbench-table is-page-surface"><DataTable appearance="integrated" preference-key="fleet-looseboard-demands" :results-visible="!error" v-model:page="page" v-model:page-size="size" :data="demands" :columns="demandColumns" :loading="loading" :total="total" row-key="orderNo" empty-text="暂无待匹配散板需求" >
< template # toolbar -start > < div class = "density-status-tabs workbench-list-tabs" role = "tablist" aria -label = " 需求来源 " > < button v-if = "canCreate" role="tab" aria-selected="true" class="is-active" @click="switchTab('demands')">待匹配散板</button><button role="tab" aria-selected="false" @click="switchTab('offers')">可拼车次</button><button role="tab" aria-selected="false" @click="switchTab('inquiries')" > 我的登记 < / button > < / div > < / template >
< div v-if = "false && tab === 'demands'" class="workbench-table is-page-surface"><DataTable appearance="integrated" preference-key="fleet-looseboard-demands" :results-visible="!error" v-model:page="page" v-model:page-size="size" :data="demands" :columns="demandColumns" :loading="loading" :total="total" row-key="orderNo" empty-text="暂无待匹配散板需求" >
< template # toolbar -start > < div class = "density-status-tabs workbench-list-tabs" role = "tablist" aria -label = " 需求来源 " > < button v-if = "false && canCreate" role="tab" aria-selected="true" class="is-active" @click="switchTab('demands')">待匹配散板</button><button role="tab" aria-selected="false" @click="switchTab('offers')">可拼车次</button><button role="tab" aria-selected="false" @click="switchTab('inquiries')" > 我的登记 < / button > < / div > < / template >
< template # filters > < QueryToolbar v-model = "filters" :fields="fields" ariaLabel="待匹配散板查询" :loading="loading" @search="search" / > < / template >
< template # notices > < LoadErrorState v-if = "error" title="需求读取失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" / > < / template > < / LoadErrorState > < / template >
< template # actions = "{ row }" > < div class = "row-actions density-actions admin-text-actions" > < el-button class = "density-action" v-if = "canCreate" @click="matchDemand(row as LooseboardDemand)" > 匹配车次 < / el -button > < / div > < / template >
< template # actions = "{ row }" > < div class = "row-actions density-actions admin-text-actions" > < el-button class = "density-action" v-if = "false && canCreate" @click="matchDemand(row as LooseboardDemand)" > 匹配车次 < / el -button > < / div > < / template >
< / DataTable > < / div >
< div v-if = "tab === 'offers'" class="workbench-table is-page-surface"><DataTable appearance="integrated" preference-key="fleet-capacity-offers" column-settings-mode="show" :results-visible="!error" v-model:page="page" v-model:page-size="size" :data="offers" :columns="columns" :loading="loading" :total="total" row-key="offerNo" :empty-text="Object.values(applied).some(Boolean) ? '当前筛选条件下暂无运力发布' : '暂无获准查看的运力发布'" >
< template # toolbar -start > < div class = "density-status-tabs workbench-list-tabs" role = "tablist" aria -label = " 需求来源 " > < button v-if = " canCreate" role="tab" aria-selected="false" @click="switchTab('demands')">待匹配散板</button><button role="tab" aria-selected="true" class="is-active" @click="switchTab('offers')">可拼车次</button><button role="tab" aria-selected="false" @click="switchTab('inquiries')">我的登记</button></div></template><template #toolbar-end><el-button v-if="manage" @click="switchTab('inquiries'); filters = { scope: 'TODO' }; applied = { scope: 'TODO' }">待我处理</el-button><el-button v-if="manage && access.can('platform:fleet:trip:list')" @click="router.push({ name: 'overseas-master-data-trips' })"> 选择车次发布余位 < / el -button > < / template >
< template # toolbar -start > < h1 > 散板需求 < / h1 > < div v-if = "false" class=" density-status-tabs workbench-list-tabs" role= "tablist" aria-label="需求来源"><button v-if="false && canCreate" role="tab" aria-selected="false" @click="switchTab('demands')">待匹配散板</button><button role="tab" aria-selected="true" class="is-active" @click="switchTab('offers')">可拼车次</button><button role="tab" aria-selected="false" @click="switchTab('inquiries')">我的登记</button></div></template><template #toolbar-end><el-button v-if="false && manage" @click="switchTab('inquiries'); filters = { scope: 'TODO' }; applied = { scope: 'TODO' }">待我处理</el-button><el-button v-if="manage && access.can('platform:fleet:trip:list')" @click="router.push({ name: 'overseas-master-data-trips' })" > 选择车次发布余位 < / el -button > < / template >
< template # filters >
< div class = "integrated-query-group" >
< QueryToolbar v-model = "filters" :fields="fields" ariaLabel="可拼车次查询" :loading="loading" @search="search" / >
< / div >
< / template >
< template # notices > < LoadErrorState v-if = "error" title="运力列表读取失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" / > < / template > < / LoadErrorState > < / template >
< template # actions = "{ row }" > < div class = "row-actions density-actions admin-text-actions" > < el-button class = "density-action" v-if = "canCreate" :disabled="!row.canCreateInquiry" @click="create(row as CapacityOffer)">登记客户货物</el-button><el-button class="density-action" @click="router.push({ name: 'overseas-master-data-capacity-offer-detail', params: { offerNo: row.offerNo } })" > 详情 < / el -button > < / div > < / template >
< template # actions = "{ row }" > < div class = "row-actions density-actions admin-text-actions" > < el-button class = "density-action" v-if = "false && canCreate" :disabled="!row.canCreateInquiry" @click="create(row as CapacityOffer)">登记客户货物</el-button><el-button class="density-action" @click="router.push({ name: 'overseas-master-data-capacity-offer-detail', params: { offerNo: row.offerNo } })" > 详情 < / el -button > < / div > < / template >
< template # identity = "{ row }" > < div > { { row . offerNo } } < / div > < div > { { row . tripNo } } < / div > < / template >
< template # destinations = "{ row }" > < div v-for = "stop in (row as CapacityOffer).destinations" :key="stop.destinationKey" > {{ stop.name }} · {{ row.capacityKnown ? loadText ( stop.remaining ) : ' 待调度核对 ' }} < / div > < / template >
< template # times = "{ row }" > < div > 发车 { { offerTime ( row . plannedDepartureAt ) } } < / div > < div > 最晚送仓 { { offerTime ( row . cutoffAt ) } } < / div > < / template >
< template # loads = "{ row }" > < span v-if = "!row.segments.length">{{ row.capacityKnown ? '—' : '待调度核对' }}</span><div v-for="segment in (row as CapacityOffer).segments" :key="segment.fromIndex" > {{ segment.fromName }} → {{ segment.toName }} < br > 计划 { { loadText ( segment . planned ) } } < br > 预留 { { loadText ( segment . reserved ) } } < / div > < / template >
< template # status = "{ row }" > { { offerStatusLabels [ ( row as CapacityOffer ) . status ] } } · { { row . canConfirm ? '可登记,确认后保位' : row . canCreateInquiry ? '可先登记,待调度核对,尚不保位' : '暂不可登记' } } < div v-if = "row.blockReason" > {{ row.blockReason }} < / div > < / template >
< template # status = "{ row }" > { { offerStatusLabels [ ( row as CapacityOffer ) . status ] } } < template v-if = "false"> · {{ row.canConfirm ? '可登记,确认后保位' : row.canCreateInquiry ? '可先登记,待调度核对,尚不保位' : '暂不可登记' }}</template><div v-if= "row.blockReason"> {{ row.blockReason }} < / div > < / template >
< template # contact = "{ row }" > < div > { { row . contactName || '—' } } < / div > < div > { { row . contactPhone || '—' } } < / div > < / template >
< / DataTable > < / div >
< CapacityInquiryTable v-else-if = "tab === 'inquiries'" integrated class="is-page-surface" v-model:page="page" v-model:size="size" :rows="inquiries" :total="total" :loading="loading" :failed="!!error" :busy="commands.busy.value" @action="inquiryAction" >
< template # toolbar -start > < div class = "density-status-tabs workbench-list-tabs" role = "tablist" aria -label = " 需求来源 " > < button v-if = "canCreate" role="tab" aria-selected="false" @click="switchTab('demands')">待匹配散板</button><button role="tab" aria-selected="false" @click="switchTab('offers')">可拼车次</button><button role="tab" aria-selected="true" class="is-active" @click="switchTab('inquiries')">我的登记</button></div><span v-if="applied.scope === 'TODO' || applied.scope === 'ALL'" class="workbench-badge" > {{ applied.scope = = = ' TODO ' ? ' 待我处理 ' : ' 全部登记 ' }} < / span > < / template >
< CapacityInquiryTable v-else-if = "false && tab === 'inquiries'" integrated class="is-page-surface" v-model:page="page" v-model:size="size" :rows="inquiries" :total="total" :loading="loading" :failed="!!error" :busy="commands.busy.value" @action="inquiryAction" >
< template # toolbar -start > < div class = "density-status-tabs workbench-list-tabs" role = "tablist" aria -label = " 需求来源 " > < button v-if = "false && canCreate" role="tab" aria-selected="false" @click="switchTab('demands')">待匹配散板</button><button role="tab" aria-selected="false" @click="switchTab('offers')">可拼车次</button><button role="tab" aria-selected="true" class="is-active" @click="switchTab('inquiries')">我的登记</button></div><span v-if="applied.scope === 'TODO' || applied.scope === 'ALL'" class="workbench-badge" > {{ applied.scope = = = ' TODO ' ? ' 待我处理 ' : ' 全部登记 ' }} < / span > < / template >
< template # filters >
< div class = "integrated-query-group" >
< QueryToolbar v-model = "filters" :fields="fields" ariaLabel="拼车意向查询" :loading="loading" @search="search" / >
@@ -160,6 +163,6 @@ watch([tab, page, size], load); onMounted(load); onBeforeUnmount(() => { followu
< / template >
< template # notices > < LoadErrorState v-if = "error || commands.error.value" title="意向操作未完成" :message="(error || commands.error.value)?.message" @retry="commands.state.pending ? commands.retry() : load()"><template #details><ApiErrorMeta :details="(error || commands.error.value)!" /></template></LoadErrorState><el-button v-if="commands.state.pending" :loading="commands.busy.value" @click="commands.retry" > 重试原操作 < / el -button > < / template >
< / CapacityInquiryTable >
< CapacityInquiryFulfillment v-model = "fulfillmentOpen" :inquiry="fulfillmentRow" :mode="fulfillmentMode" @saved="fulfillmentSaved" / >
< CapacityInquiryDialog v-model = "editorOpen" :offer="selectedOffer" :source-demand="editing ? null : selectedDemand" :inquiry="editing" :readonly="readonly" :confirm-mode="confirmMode" @action="inquiryAction" @saved="accept" @refresh="recheckEditor" / >
< CapacityInquiryFulfillment v-if = "false" v-model=" fulfillmentOpen" :inquiry="fulfillmentRow" :mode="fulfillmentMode" @saved="fulfillmentSaved" / >
< CapacityInquiryDialog v-if = "false" v-model=" editorOpen" :offer="selectedOffer" :source-demand="editing ? null : selectedDemand" :inquiry="editing" :readonly="readonly" :confirm-mode="confirmMode" @action="inquiryAction" @saved="accept" @refresh="recheckEditor" / >
< / ListPageLayout > < / AdminPage > < / template >