2026-08-08 13:32:27 +08:00
< script setup lang = "ts" >
import { computed , onMounted , ref } from 'vue'
import { useRouter } from 'vue-router'
2026-08-10 16:24:54 +08:00
import { Eye , Pencil , Plus } from '@lucide/vue'
2026-08-08 13:32:27 +08:00
import DataTable , { type DataTableColumn } from '@/shared/components/DataTable.vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/shared/components/QueryToolbar.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
2026-08-10 16:24:54 +08:00
import AdminPage from '@/shared/components/AdminPage.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
2026-08-08 13:32:27 +08:00
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listTruckAppointments } from '../api/platformTrucking'
2026-08-11 18:43:56 +08:00
import { TRUCK_APPOINTMENT_STATUSES , truckAppointmentStatusLabel , truckAppointmentStatusLabels , type TruckAppointmentQuery , type TruckAppointmentResource , type TruckAppointmentStatus } from '../types'
2026-08-08 13:32:27 +08:00
2026-08-10 16:24:54 +08:00
const router = useRouter (); const rows = ref < TruckAppointmentResource [] >([]); const loading = ref ( false ); const page = ref ( 1 ); const pageSize = ref ( 20 ); const total = ref ( 0 ); const loadError = ref < ApiErrorDetails | null >( null )
2026-08-08 13:32:27 +08:00
const access = useAccessPreviewStore ()
2026-08-08 15:05:37 +08:00
const filters = ref ({ keyword : '' , status : '' as TruckAppointmentStatus | '' , containerBookingId : '' , containerType : '' }); const applied = ref ({ ... filters . value })
2026-08-08 13:32:27 +08:00
const fields = computed < QueryField [] >(() => [
2026-08-08 15:05:37 +08:00
{ key : 'keyword' , type : 'input' , label : '关键词' , placeholder : '委托单位、派车单号、车牌号' },
2026-08-08 13:32:27 +08:00
{ key : 'status' , type : 'select' , label : '状态' , placeholder : '全部状态' , options : TRUCK_APPOINTMENT_STATUSES . map (( value ) => ({ value , label : truckAppointmentStatusLabels [ value ] })) },
2026-08-08 15:05:37 +08:00
{ key : 'containerType' , type : 'select' , label : '柜型' , placeholder : '全部柜型' , options : [
{ value : '20GP' , label : '20GP' }, { value : '40GP' , label : '40GP' }, { value : '40HC' , label : '40HC' },
] },
2026-08-08 13:32:27 +08:00
])
const columns : DataTableColumn [] = [
{ key : 'actions' , label : '操作' , width : 150 , fixed : 'left' , slotName : 'actions' },
2026-08-08 15:05:37 +08:00
{ key : 'appointmentNo' , prop : 'appointmentNo' , label : '派车单号' , minWidth : 170 },
{ key : 'containerBookingId' , prop : 'containerBookingId' , label : '关联柜体' , minWidth : 130 , formatter : ( row : TruckAppointmentResource ) => row . containerBookingId || '—' },
{ key : 'company' , label : '运输公司' , minWidth : 160 , slotName : 'company' },
{ key : 'driver' , label : '司机' , minWidth : 130 , slotName : 'driver' },
{ key : 'vehicleNo' , prop : 'vehicleNo' , label : '车头号' , minWidth : 120 , formatter : ( row : TruckAppointmentResource ) => row . vehicleNo || '—' },
{ key : 'store' , label : '门店' , minWidth : 140 , slotName : 'store' },
2026-08-08 13:32:27 +08:00
{ key : 'payableAmount' , label : '应付拖车费' , minWidth : 130 , formatter : ( row : TruckAppointmentResource ) => row . payableAmount ? `¥ ${ row . payableAmount } ` : '—' },
{ key : 'receivableAmount' , label : '应收拖车费' , minWidth : 130 , formatter : ( row : TruckAppointmentResource ) => row . receivableAmount ? `¥ ${ row . receivableAmount } ` : '—' },
2026-08-08 15:05:37 +08:00
{ key : 'plannedLoadingAt' , label : '预计装箱日期' , minWidth : 170 , formatter : ( row : TruckAppointmentResource ) => formatDateTime ( row . plannedLoadingAt ) },
2026-08-08 13:32:27 +08:00
{ key : 'status' , label : '状态' , width : 130 , slotName : 'status' },
{ key : 'attachments' , label : '附件' , width : 90 , formatter : ( row : TruckAppointmentResource ) => String ( row . attachments ? . length || 0 ) },
]
2026-08-08 14:13:32 +08:00
function formatDateTime ( value ?: string | null ) { return value ? value . replace ( 'T' , ' ' ). slice ( 0 , 16 ) : '—' }
2026-08-10 17:42:17 +08:00
function appointmentActionLabel ( action : '查看' | '编辑' , row : TruckAppointmentResource ) {
const identifier = [ row . appointmentNo , row . id ]
. map (( value ) => String ( value ?? '' ). trim ())
. find ( Boolean )
return identifier ? ` ${ action } ${ identifier } ` : ` ${ action } 拖车预约详情`
}
2026-08-08 13:32:27 +08:00
function statusLabel ( value : unknown ) {
2026-08-11 18:43:56 +08:00
return truckAppointmentStatusLabel ( typeof value === 'string' ? value : null )
2026-08-08 13:32:27 +08:00
}
2026-08-10 16:24:54 +08:00
async function load () { loading . value = true ; loadError . value = null ; try { const result = await listTruckAppointments ({ page : page . value , size : pageSize . value , ... applied . value } as TruckAppointmentQuery ); rows . value = result . records ; total . value = result . total } catch ( error ) { rows . value = []; total . value = 0 ; loadError . value = getApiErrorDetails ( error , '拖车预约加载失败,请稍后重试' ) } finally { loading . value = false } }
2026-08-10 18:12:23 +08:00
function search ( model : QueryModel ) { applied . value = { keyword : String ( model . keyword || '' ), status : ( model . status || '' ) as TruckAppointmentStatus | '' , containerBookingId : '' , containerType : String ( model . containerType || '' ) }; if ( page . value === 1 ) void load (); else page . value = 1 }
2026-08-08 13:32:27 +08:00
onMounted ( load )
2026-08-08 21:14:45 +08:00
function openDetail ( row : TruckAppointmentResource , edit = false ) { router . push ({ name : 'trucking-detail' , params : { id : String ( row . id ) }, query : edit ? { edit : '1' } : undefined }) }
2026-08-08 13:32:27 +08:00
</ script >
< template >
2026-08-10 16:24:54 +08:00
< AdminPage as = "div" class = "trucking-page" >
< ListPageLayout aria -label =" 拖车预约列表 " >
2026-08-10 18:12:23 +08:00
< template # toolbar >< QueryToolbar v-model = "filters" class="surface-panel" ariaLabel="拖车预约查询" :fields="fields" preference-key="trucking-query" @search="search" /></ template >
2026-08-10 16:24:54 +08:00
< template v-if = "loadError" #state><LoadErrorState title="拖车预约列表暂时无法加载" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></ template ></ LoadErrorState ></ template >
< section v-if = "!loadError" class="surface-panel table-panel" :aria-busy="loading" >
2026-08-08 14:13:32 +08:00
< DataTable v -model:page = "page" v -model:page-size = "pageSize" :data = "rows" :columns = "columns" :loading = "loading" :total = "total" preference -key =" trucking -appointments " aria -label =" 拖车预约列表 " empty -text =" 暂无符合筛选条件的拖车预约 " >
< template # toolbar -actions >< el-button v-if = "access.can('platform:firstleg:truck-appointment:create')" type="primary" :disabled="loading" @click="router.push({ name: 'trucking-new' })"><Plus :size="16" /> 新增拖车 </ el-button ></ template >
2026-08-08 13:32:27 +08:00
< template # company = "{ row }" >{{ row . carrierName || row . carrierId || '—' }}</ template >
2026-08-08 15:05:37 +08:00
< template # driver = "{ row }" >{{ row . driverName || '—' }}< small >{{ row . driverPhone || '—' }}</ small ></ template >
2026-08-08 13:32:27 +08:00
< template # store = "{ row }" >{{ row . storeName || '—' }}</ template >
< template # status = "{ row }" >< StatusBadge :status = "statusLabel(row.status)" /></ template >
2026-08-10 17:42:17 +08:00
< template # actions = "{ row }" >< div class = "table-actions" >< el-button link type = "primary" : aria -label =" appointmentActionLabel (' 查看 ', row as TruckAppointmentResource )" @click ="openDetail(row as TruckAppointmentResource)" >< Eye :size = "15" /> 详情 </ el-button >< el-button v-if = "access.can('platform:firstleg:truck-appointment:edit')" link type="primary" :aria-label="appointmentActionLabel('编辑', row as TruckAppointmentResource)" @click="openDetail(row as TruckAppointmentResource, true)"><Pencil :size="15" /> 编辑 </ el-button ></ div ></ template >
2026-08-08 13:32:27 +08:00
</ DataTable >
2026-08-10 16:24:54 +08:00
</ section >
</ ListPageLayout >
</ AdminPage >
2026-08-08 13:32:27 +08:00
</ template >
< style scoped >
2026-08-08 15:15:04 +08:00
. trucking - page { min - width : 0 ; }
2026-08-10 16:24:54 +08:00
. table - panel { overflow : hidden ; }
2026-08-08 13:32:27 +08:00
small { display : block ; color : var ( -- el - text - color - secondary ); margin - top : 3 px ; }
</ style >