2026-09-04 12:41:58 +08:00
< script setup lang = "ts" >
2026-09-18 16:48:35 +08:00
import { computed , onMounted , reactive , ref , watch } from 'vue'
import { useRoute , useRouter } from 'vue-router'
2026-10-19 15:50:39 +08:00
import { ElMessage , type FormInstance } from 'element-plus'
import TransportDemandForm from './TransportDemandForm.vue'
import { createTransportDemandForm , transportDemandError , buildTransportDemand , type TransportDemandForm as DemandFormModel } from '../transportDemandForm'
2026-09-18 16:48:35 +08:00
import DataTable , { type DataTableColumn } from '@/components/common/DataTable.vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/components/common/QueryToolbar.vue'
import type { TableAction } from '@/components/common/tableActions'
2026-09-05 22:26:37 +08:00
import { useAuthStore } from '@/modules/auth/public'
2026-09-04 12:41:58 +08:00
import {
createTransport ,
getBusinessOrder ,
listBusinessOrders ,
type CrossBorderBusinessKind ,
type TransportDetail ,
type TransportSummary ,
} from '../api/businessOrders'
2026-10-07 16:46:58 +08:00
import { poolOrderStatusLabels } from '../fleetArrangement'
2026-09-04 12:41:58 +08:00
2026-09-18 16:48:35 +08:00
const props = withDefaults ( defineProps < { kind : CrossBorderBusinessKind ; createPage ?: boolean ; detailPage ?: boolean } > (), { createPage : false , detailPage : false })
2026-09-05 22:26:37 +08:00
const auth = useAuthStore ()
2026-09-18 16:48:35 +08:00
const router = useRouter ()
const route = useRoute ()
const detailOrderNo = computed (() => String ( route . params . orderNo || '' ))
2026-09-17 18:25:08 +08:00
const canCreate = computed (() => auth . hasPermission (({
trucking : 'merchant:cross-border:domestic-trucking:create' ,
looseboard : 'merchant:cross-border:pallet-shipping:create' ,
} as Record < CrossBorderBusinessKind , string >)[ props . kind ]))
2026-09-04 12:41:58 +08:00
2026-10-10 02:19:39 +08:00
type BusinessRow = TransportSummary
type BusinessDetail = TransportDetail
2026-09-04 12:41:58 +08:00
type PageMode = 'list' | 'create' | 'detail'
interface ColumnDefinition { key : string ; label : string ; width ?: number }
2026-10-19 15:50:39 +08:00
type BusinessForm = DemandFormModel
2026-09-04 12:41:58 +08:00
const configuration = computed (() => ({
trucking : {
title : '美国本土卡' ,
description : '查询并管理美国本土卡订单' ,
columns : transportColumns ( false ),
},
looseboard : {
title : '美本土散板' ,
description : '查询并管理美本土散板订单' ,
columns : transportColumns ( true ),
},
})[ props . kind ])
function transportColumns ( looseboard : boolean ) : ColumnDefinition [] {
return [
{ key : 'orderNo' , label : '订单编号' , width : 180 },
2026-10-19 15:11:15 +08:00
...( ! looseboard ? [{ key : 'vehicleType' , label : '车辆类型' , width : 110 }] : []),
2026-09-04 12:41:58 +08:00
{ key : 'origin' , label : '起运地' , width : 150 },
{ key : 'destination' , label : '目的地' , width : 150 },
2026-10-19 15:11:15 +08:00
...( looseboard ? [{ key : 'productName' , label : '商品名称' , width : 150 }, { key : 'estimatedPalletCount' , label : '预计托数' , width : 100 }] : []),
2026-09-04 12:41:58 +08:00
{ key : 'totalWeightKg' , label : '总重量(kg)' , width : 120 },
{ key : 'budgetAmount' , label : '预算总价' , width : 110 },
2026-10-19 15:11:15 +08:00
...([
2026-10-19 14:37:03 +08:00
{ key : 'pickupAt' , label : '取货时间' , width : 165 }, { key : 'deliveryAt' , label : '送货时间' , width : 165 },
]),
2026-09-04 12:41:58 +08:00
{ key : 'status' , label : '状态' , width : 130 },
2026-10-07 16:46:58 +08:00
{ key : 'fleetArrangement' , label : '承运安排' , width : 190 },
2026-09-04 12:41:58 +08:00
]
}
2026-09-18 16:48:35 +08:00
const mode = ref < PageMode >( props . detailPage ? 'detail' : props . createPage ? 'create' : 'list' )
2026-09-04 12:41:58 +08:00
const loading = ref ( false )
const submitting = ref ( false )
const submitKey = ref ( '' )
const error = ref ( '' )
const rows = ref < BusinessRow [] >([])
const detail = ref < BusinessDetail | null >( null )
2026-10-10 02:19:39 +08:00
const fleetArrangement = computed (() => detail . value ? ( detail . value as TransportDetail ). summary . fleetArrangement : null )
2026-09-04 12:41:58 +08:00
const formRef = ref < FormInstance >()
2026-10-10 02:19:39 +08:00
const query = reactive ({ page : 1 , size : 20 , orderNo : '' , routeKeyword : '' , status : '' })
2026-09-04 12:41:58 +08:00
const total = ref ( 0 )
2026-09-18 16:48:35 +08:00
const queryModel = computed < QueryModel >({
get : () => ({
orderNo : query . orderNo ,
routeKeyword : query . routeKeyword ,
status : query . status ,
}),
set : ( value ) => {
query . orderNo = String ( value . orderNo || '' )
query . routeKeyword = String ( value . routeKeyword || '' )
query . status = String ( value . status || '' )
},
})
const queryFields = computed < QueryField [] >(() => [
{ key : 'orderNo' , label : '订单号' , type : 'input' , placeholder : '请输入订单号' },
2026-10-10 02:19:39 +08:00
{ key : 'routeKeyword' , label : '运输路线' , type : 'input' , placeholder : '请输入起运地或目的地' },
2026-09-18 16:48:35 +08:00
{
key : 'status' ,
label : '状态' ,
type : 'select' ,
placeholder : '全部' ,
options : [
{ label : '待审核' , value : 'PENDING_REVIEW' },
{ label : '已通过' , value : 'APPROVED' },
{ label : '已拒绝' , value : 'REJECTED' },
{ label : '待接单' , value : 'PENDING_ACCEPTANCE' },
2026-10-10 02:19:39 +08:00
... Object . entries ( poolOrderStatusLabels ). filter (([ value ]) => ! [ 'IN_TRANSIT' , 'COMPLETED' , 'CANCELLED' ]. includes ( value )). map (([ value , label ]) => ({ value , label })),
2026-09-18 16:48:35 +08:00
{ label : '处理中' , value : 'PROCESSING' },
{ label : '运输中' , value : 'IN_TRANSIT' },
{ label : '已完成' , value : 'COMPLETED' },
{ label : '已取消' , value : 'CANCELLED' },
],
},
])
2026-09-04 12:41:58 +08:00
const form = reactive < BusinessForm >( emptyForm ())
const detailFields = computed (() => {
if ( ! detail . value ) return []
const summary = detail . value . summary
const current = detail . value as TransportDetail
return [
2026-10-19 14:37:03 +08:00
...( props . kind === 'looseboard' ? [
2026-10-19 15:11:15 +08:00
[ '商品名称' , summary . productName ], [ '预计托数' , summary . estimatedPalletCount ],
2026-10-19 14:37:03 +08:00
] : [[ '车辆类型' , vehicleTypeLabel ( summary . vehicleType )]]),
2026-09-04 12:41:58 +08:00
[ '起运地' , read ( summary , 'origin' )], [ '起运地址' , current . originAddress ],
[ '目的地' , read ( summary , 'destination' )], [ '目的地址' , current . destinationAddress ],
[ '件数' , read ( summary , 'packageCount' )], [ '总重量(kg)' , read ( summary , 'totalWeightKg' )],
[ '总体积(m³)' , read ( summary , 'totalVolumeCbm' )], [ '预算总价' , money ( read ( summary , 'budgetAmount' ), read ( summary , 'currency' ))],
2026-10-19 15:11:15 +08:00
[ '起运地联系人' , summary . originContactName ], [ '起运地联系电话' , summary . originContactPhone ],
[ '目的地联系人' , summary . destinationContactName ], [ '目的地联系电话' , summary . destinationContactPhone ],
[ '取货时间' , localMinute ( summary . pickupAt )], [ '送货时间' , localMinute ( summary . deliveryAt )], [ '备注' , current . remark ],
2026-09-04 12:41:58 +08:00
]
})
2026-09-18 16:48:35 +08:00
const tableColumns = computed < DataTableColumn [] >(() => [
{ key : 'actions' , label : '操作' , width : 90 , fixed : 'left' , slotName : 'actions' },
... configuration . value . columns . map (( column ) => ({
... column ,
prop : column . key ,
2026-10-07 16:46:58 +08:00
slotName : [ 'orderNo' , 'status' , 'fleetArrangement' ]. includes ( column . key ) ? column . key : undefined ,
2026-09-18 16:48:35 +08:00
formatter : ( row : object ) => displayCell ( row , column . key ),
})),
])
const detailStatus = computed (() => detail . value ? statusLabel ( detail . value . summary . status ) : '—' )
2026-10-19 15:50:39 +08:00
function emptyForm () : BusinessForm { return createTransportDemandForm () }
2026-09-04 12:41:58 +08:00
function resetForm () { Object . assign ( form , emptyForm ()); submitKey . value = '' ; formRef . value ? . clearValidate () }
function read ( source : object , key : string ) { return Reflect . get ( source , key ) as unknown }
function text ( value : unknown ) { return value === null || value === undefined || value === '' ? '—' : String ( value ) }
function money ( amount : unknown , currency : unknown ) { return amount === null || amount === undefined ? '—' : ` ${ text ( currency ) } ${ Number ( amount ). toFixed ( 2 ) } ` }
function dateTime ( value : unknown ) { return value ? new Date ( String ( value )). toLocaleString ( 'zh-CN' , { hour12 : false }) : '—' }
function yesNo ( value : unknown ) { return value === 'YES' ? '是' : value === 'NO' ? '否' : text ( value ) }
2026-10-19 14:37:03 +08:00
function vehicleTypeLabel ( value : unknown ) { return ({ PALLET_15 : '15拖' , PALLET_30 : '30拖' } as Record < string , string >)[ String ( value )] || text ( value ) }
function localMinute ( value : unknown ) { return value ? String ( value ). replace ( 'T' , ' ' ). slice ( 0 , 16 ) : '—' }
2026-09-04 12:41:58 +08:00
function transportTypeLabel ( value : unknown ) { return ({ PORT_PICKUP : '港口提柜' , WAREHOUSE_TO_WAREHOUSE : '仓到仓' , WAREHOUSE_TO_DOOR : '仓到门' } as Record < string , string >)[ String ( value )] || text ( value ) }
function statusLabel ( value : unknown ) {
2026-10-07 16:46:58 +08:00
if ( poolOrderStatusLabels [ String ( value )]) return poolOrderStatusLabels [ String ( value )]
2026-10-10 02:19:39 +08:00
return ({ PENDING_REVIEW : '待审核' , APPROVED : '已通过' , REJECTED : '已拒绝' , PENDING_ACCEPTANCE : '待接单' , PROCESSING : '处理中' , IN_TRANSIT : '运输中' , COMPLETED : '已完成' , CANCELLED : '已取消' } as Record < string , string >)[ String ( value )] || text ( value )
2026-09-04 12:41:58 +08:00
}
2026-09-18 16:48:35 +08:00
function statusTone ( value : unknown ) {
2026-10-10 02:19:39 +08:00
return ({ APPROVED : 'success' , COMPLETED : 'success' , REJECTED : 'danger' , CANCELLED : 'info' , PENDING_REVIEW : 'warning' , PENDING_ACCEPTANCE : 'warning' , PROCESSING : 'primary' , IN_TRANSIT : 'primary' } as Record < string , ' success ' | ' warning ' | ' danger ' | ' info ' | ' primary ' >)[ String ( value )] || 'info'
2026-09-18 16:48:35 +08:00
}
2026-09-04 12:41:58 +08:00
function displayCell ( candidate : unknown , key : string ) {
const row = candidate as BusinessRow
const value = read ( row , key )
if ( key === 'status' ) return statusLabel ( value )
2026-10-19 14:37:03 +08:00
if ( key === 'vehicleType' ) return vehicleTypeLabel ( value )
if ( key === 'pickupAt' || key === 'deliveryAt' ) return localMinute ( value )
2026-09-04 12:41:58 +08:00
if ( key === 'transportType' ) return transportTypeLabel ( value )
if ( key === 'palletRequired' ) return yesNo ( value )
if ( key === 'createdAt' ) return dateTime ( value )
2026-10-10 02:19:39 +08:00
if ( key === 'budgetAmount' ) return money ( value , read ( row , 'currency' ))
2026-09-04 12:41:58 +08:00
return text ( value )
}
function idempotencyKey () { return globalThis . crypto ? . randomUUID ? .() || `client- ${ Date . now () } - ${ Math . random (). toString ( 16 ). slice ( 2 ) } ` }
function errorMessage ( cause : unknown ) { return cause instanceof Error ? cause . message : '请求失败,请稍后重试' }
async function load () {
loading . value = true
error . value = ''
try {
const params = {
page : query . page , size : query . size , orderNo : query . orderNo || undefined , status : query . status || undefined ,
2026-10-10 02:19:39 +08:00
routeKeyword : query . routeKeyword || undefined ,
2026-09-04 12:41:58 +08:00
}
2026-10-10 02:19:39 +08:00
const result = await listBusinessOrders < TransportSummary >( props . kind , params )
2026-09-04 12:41:58 +08:00
rows . value = result . records
total . value = result . total
} catch ( cause ) {
error . value = errorMessage ( cause )
rows . value = []
total . value = 0
} finally { loading . value = false }
}
function resetQuery () {
2026-10-07 16:46:58 +08:00
const reloadDirectly = query . page === 1
2026-10-10 02:19:39 +08:00
Object . assign ( query , { page : 1 , orderNo : '' , routeKeyword : '' , status : '' })
2026-10-07 16:46:58 +08:00
if ( reloadDirectly ) void load ()
2026-09-04 12:41:58 +08:00
}
2026-09-18 16:48:35 +08:00
function searchOrders () {
2026-10-07 16:46:58 +08:00
if ( query . page !== 1 ) query . page = 1
else void load ()
2026-09-18 16:48:35 +08:00
}
function getRowActions ( candidate : object ) : TableAction [] {
return [{ key : 'detail' , label : '详情' , run : () => openDetail ( candidate ) }]
}
function detailFieldClass ( label : unknown ) {
2026-10-10 02:19:39 +08:00
return [ '备注' , '起运地址' , '目的地址' ]. includes ( String ( label )) ? 'is-wide' : ''
2026-09-18 16:48:35 +08:00
}
const listRouteName = computed (() => ({
trucking : 'cross-border-domestic-trucking' ,
looseboard : 'cross-border-pallet-shipping' ,
} as Record < CrossBorderBusinessKind , string >)[ props . kind ])
const detailRouteName = computed (() => ({
trucking : 'cross-border-domestic-trucking-detail' ,
looseboard : 'cross-border-pallet-shipping-detail' ,
} as Record < CrossBorderBusinessKind , string >)[ props . kind ])
function returnToList () {
if ( props . createPage || props . detailPage ) {
void router . push ({ name : listRouteName . value })
return
}
mode . value = 'list'
}
function openDetail ( candidate : unknown ) {
2026-09-04 12:41:58 +08:00
const row = candidate as BusinessRow
2026-09-18 16:48:35 +08:00
void router . push ({ name : detailRouteName . value , params : { orderNo : row . orderNo } })
}
async function loadDetail () {
if ( ! detailOrderNo . value ) return
2026-09-04 12:41:58 +08:00
loading . value = true
error . value = ''
try {
2026-10-10 02:19:39 +08:00
detail . value = await getBusinessOrder < TransportDetail >( props . kind , detailOrderNo . value )
2026-09-04 12:41:58 +08:00
mode . value = 'detail'
2026-09-18 16:48:35 +08:00
} catch ( cause ) { detail . value = null ; error . value = errorMessage ( cause ); ElMessage . error ( error . value ) }
2026-09-04 12:41:58 +08:00
finally { loading . value = false }
}
async function submit () {
2026-10-19 15:50:39 +08:00
const failure = transportDemandError ( props . kind === 'trucking' ? 'truck' : 'looseboard' , form )
if ( failure ) { ElMessage . error ( failure ); return }
2026-09-04 12:41:58 +08:00
if ( ! await formRef . value ? . validate (). catch (() => false )) return
submitting . value = true
const stableSubmitKey = submitKey . value || idempotencyKey ()
submitKey . value = stableSubmitKey
try {
2026-10-19 15:50:39 +08:00
if ( props . kind === 'trucking' ) await createTransport ( 'trucking' , { ... buildTransportDemand ( 'truck' , form ), idempotencyKey : stableSubmitKey })
else await createTransport ( 'looseboard' , { ... buildTransportDemand ( 'looseboard' , form ), idempotencyKey : stableSubmitKey })
2026-09-04 12:41:58 +08:00
ElMessage . success ( '订单提交成功' )
resetForm ()
2026-09-18 16:48:35 +08:00
if ( props . createPage ) await router . push ({ name : listRouteName . value })
else { mode . value = 'list' ; await load () }
2026-09-04 12:41:58 +08:00
} catch ( cause ) { ElMessage . error ( errorMessage ( cause )) }
finally { submitting . value = false }
}
2026-09-18 16:48:35 +08:00
watch (() => [ query . page , query . size ], () => { if ( ! props . createPage && mode . value === 'list' ) void load () })
2026-10-19 15:11:15 +08:00
onMounted (() => { if ( props . detailPage ) void loadDetail (); else if ( ! props . createPage ) void load () })
2026-09-04 12:41:58 +08:00
</ script >
< template >
2026-09-18 16:48:35 +08:00
< section v-loading = "loading" class="business-orders client-ui" :aria-busy="loading" >
2026-10-19 14:53:57 +08:00
< header v-if = "mode === 'create'" class="page-head" :class="{ 'looseboard-page-head': props.kind === 'looseboard' }" >
2026-09-04 12:41:58 +08:00
< div >
2026-09-18 16:48:35 +08:00
< h2 >{{ configuration . title }} 下单 </ h2 >
< p > 请填写订单信息 , 带 * 项为必填项 </ p >
2026-09-04 12:41:58 +08:00
</ div >
2026-09-18 16:48:35 +08:00
< el-button @click ="returnToList" > 返回列表 </ el -button >
2026-09-04 12:41:58 +08:00
</ header >
< template v-if = "mode === 'list'" >
2026-09-18 16:48:35 +08:00
< QueryToolbar v-model = "queryModel" layout="grid" ariaLabel="订单查询条件" :fields="queryFields" :search-on-reset="false" @search="searchOrders" @reset="resetQuery" />
< div class = "business-table-card" >
< DataTable v -model:page = "query.page" v -model:page-size = "query.size" :data = "rows" :columns = "tableColumns" :loading = "loading" :total = "total" : page -sizes ="[ 10 , 20 , 50 , 100 ]" :row-actions = "getRowActions" row -key =" orderNo " empty -text =" 暂无订单 " >
< template # toolbar -actions >< el-button v-if = "canCreate" type="primary" @click="resetForm(); mode = 'create'" > 新增下单 </ el -button ></ template >
< template v-if = "error" #error><div class="business-load-error" role="alert"><span>{{ error }}</span><el-button size="small" @click="load" > 重新加载 </ el -button ></ div ></ template >
< template # orderNo = "{ row }" >< button class = "business-order-link" type = "button" @click ="openDetail(row)" >{{ ( row as BusinessRow ) .orderNo }}</ button ></ template >
< template # status = "{ row }" >< el-tag : type = "statusTone((row as BusinessRow).status)" effect = "light" size = "small" >{{ statusLabel (( row as BusinessRow ). status ) }}</ el-tag ></ template >
2026-10-07 16:46:58 +08:00
< template # fleetArrangement = "{ row }" >< template v-if = "(row as TransportSummary).fleetArrangement" >< div >{{ poolOrderStatusLabels [( row as TransportSummary ). fleetArrangement ! . fulfillmentStage ] || '待核对履约进度' }}</ div >< small >{{ dateTime (( row as TransportSummary ). fleetArrangement ! . plannedDepartureAt ) }}</ small ></ template >< span v-else > — </ span ></ template >
2026-09-18 16:48:35 +08:00
</ DataTable >
</ div >
2026-09-04 12:41:58 +08:00
</ template >
2026-09-18 16:48:35 +08:00
< section v-else-if = "mode === 'detail'" class="business-detail-page" aria-label="订单详情" >
< header class = "business-detail-heading" >
< div class = "business-detail-heading-main" >< el-button link type = "primary" @click ="returnToList" > ← 返回列表 </ el -button >< div >< h2 >{{ detail ? . summary . orderNo || '订单详情' }}</ h2 >< p >{{ configuration . title }} 订单 </ p ></ div ></ div >
< el-tag :type = "statusTone(detail?.summary.status)" effect = "light" >{{ detailStatus }}</ el-tag >
</ header >
< div v-if = "error" class="business-detail-error" role="alert"><strong>{{ error }}</strong><el-button size="small" @click="loadDetail" > 重新加载 </ el -button ></ div >
< section v-else class = "business-detail-section" >
< h3 > 订单信息 </ h3 >
2026-10-19 14:37:03 +08:00
< dl class = "detail-grid" : class = "{ 'trucking-detail-grid': kind === 'trucking' }" >
2026-09-18 16:48:35 +08:00
< div v-for = "field in detailFields" :key="String(field[0])" :class="detailFieldClass(field[0])" >< dt >{{ field [ 0 ] }}</ dt >< dd >{{ text ( field [ 1 ]) }}</ dd ></ div >
</ dl >
</ section >
2026-10-07 16:46:58 +08:00
< section v-if = "!error && fleetArrangement" class="business-detail-section" aria-label="承运安排" >
< h3 > 承运安排 </ h3 >< p >{{ fleetArrangement . readOnlyReason }}</ p >
2026-10-07 20:59:34 +08:00
< dl class = "detail-grid arrangement-grid" >
2026-10-07 16:46:58 +08:00
< div >< dt > 预订号 </ dt >< dd >{{ fleetArrangement . bookingNo }}</ dd ></ div >
< div >< dt > 计划发车 </ dt >< dd >{{ dateTime ( fleetArrangement . plannedDepartureAt ) }}</ dd ></ div >
< div >< dt > 起运仓 </ dt >< dd >{{ text ( fleetArrangement . originWarehouseName ) }}</ dd ></ div >
< div >< dt > 目的地 </ dt >< dd >{{ text ( fleetArrangement . destinationLabel ) }}</ dd ></ div >
< div >< dt > 交货方式 </ dt >< dd >{{ fleetArrangement . deliveryMode === 'SELF_DELIVERY' ? '自行送至起运仓' : '线下安排提货至起运仓' }}</ dd ></ div >
< div >< dt > 履约进度 </ dt >< dd >{{ poolOrderStatusLabels [ fleetArrangement . fulfillmentStage ] || '待核对履约进度' }}</ dd ></ div >
< div >< dt > 更新时间 </ dt >< dd >{{ dateTime ( fleetArrangement . stageOccurredAt ) }}</ dd ></ div >
< div >< dt > 约定运费 ( 非实收 ) </ dt >< dd >{{ money ( fleetArrangement . agreedFreightAmount , fleetArrangement . currency ) }}</ dd ></ div >
< div >< dt > 来货单号 </ dt >< dd >{{ text ( fleetArrangement . incomingNo ) }}</ dd ></ div >
< div >< dt > 派送单号 </ dt >< dd >{{ text ( fleetArrangement . deliveryNo ) }}</ dd ></ div >
< div >< dt > 已签 / 未签 / 退回箱数 </ dt >< dd >{{ fleetArrangement . signedPackages ?? '—' }} / {{ fleetArrangement . unsignedPackages ?? '—' }} / {{ fleetArrangement . returnedPackages ?? '—' }}</ dd ></ div >
< div class = "is-wide" >< dt > 交货说明 </ dt >< dd >{{ text ( fleetArrangement . deliveryInstructions ) }}</ dd ></ div >
< div v-if = "fleetArrangement.exceptionSummary" class="is-wide" >< dt > 履约说明 </ dt >< dd >{{ fleetArrangement . exceptionSummary }}</ dd ></ div >
</ dl >
</ section >
2026-09-18 16:48:35 +08:00
</ section >
2026-09-04 12:41:58 +08:00
< el-card v-else shadow = "never" class = "form-card" >
2026-10-19 15:50:39 +08:00
< TransportDemandForm ref = "formRef" :model = "form" : kind = "kind === 'trucking' ? 'truck' : 'looseboard'" :submitting = "submitting" :can-submit = "canCreate" @submit ="submit" @cancel ="returnToList" />
2026-09-04 12:41:58 +08:00
</ el-card >
</ section >
</ template >
< style scoped >
2026-09-18 16:48:35 +08:00
. business - orders { display : grid ; gap : var ( -- client - section - gap ); }
2026-09-04 12:41:58 +08:00
. page - head { display : flex ; align - items : center ; justify - content : space - between ; gap : 24 px ; }
2026-10-19 14:53:57 +08:00
. looseboard - page - head { max - width : 1180 px ; padding - right : 30 px ; box - sizing : border - box ; }
2026-09-04 12:41:58 +08:00
. page - head h2 { margin : 0 ; color : # 111827 ; font - size : 26 px ; }
. page - head p { margin : 7 px 0 0 ; color : # 64748 b ; font - size : 14 px ; }
2026-09-18 16:48:35 +08:00
. business - table - card { overflow : hidden ; border : 1 px solid var ( -- client - border ); border - radius : var ( -- client - radius ); background : # fff ; }
. business - order - link { max - width : 100 % ; overflow : hidden ; padding : 0 ; border : 0 ; background : transparent ; color : var ( -- client - action ); cursor : pointer ; font : inherit ; font - weight : 650 ; text - align : left ; text - overflow : ellipsis ; white - space : nowrap ; }
. business - order - link : hover { text - decoration : underline ; text - underline - offset : 3 px ; }
. business - load - error { display : flex ; min - height : 84 px ; align - items : center ; justify - content : center ; gap : 12 px ; padding : 14 px ; color : # b42318 ; }
. business - detail - error { display : flex ; min - height : 180 px ; align - items : center ; justify - content : center ; gap : 12 px ; color : # b42318 ; }
. business - detail - page { min - width : 0 ; padding : 10 px 14 px 20 px ; overflow : hidden ; border : 1 px solid var ( -- client - border ); border - radius : var ( -- client - radius ); background : # fff ; }
. business - detail - heading { display : flex ; min - height : 50 px ; align - items : center ; justify - content : space - between ; gap : 16 px ; padding : 5 px 0 ; border - bottom : 1 px solid var ( -- border ); }
. business - detail - heading - main { display : flex ; min - width : 0 ; align - items : center ; gap : 12 px ; }
. business - detail - heading - main > div { min - width : 0 ; }
. business - detail - heading h2 { margin : 0 ; overflow : hidden ; color : var ( -- navy ); font - size : 18 px ; text - overflow : ellipsis ; white - space : nowrap ; }
. business - detail - heading p { margin : 3 px 0 0 ; color : var ( -- muted ); font - size : 11 px ; }
. business - detail - section { margin - top : 10 px ; }
. business - detail - section h3 { margin : 0 ; padding : 0 0 5 px ; border - bottom : 1 px solid # eaf0f7 ; color : var ( -- navy ); font - size : 13 px ; }
. detail - grid { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr )); margin : 0 ; border - top : 1 px solid var ( -- border ); border - left : 1 px solid var ( -- border ); }
. detail - grid div { display : grid ; min - width : 0 ; min - height : 34 px ; grid - template - columns : 76 px minmax ( 0 , 1 fr ); align - items : center ; gap : 7 px ; padding : 3 px 8 px ; border - right : 1 px solid var ( -- border ); border - bottom : 1 px solid var ( -- border ); }
2026-10-19 14:37:03 +08:00
. trucking - detail - grid div { grid - template - columns : 112 px minmax ( 0 , 1 fr ); }
2026-09-18 16:48:35 +08:00
. detail - grid div . is - wide { grid - column : span 2 ; }
. detail - grid dt { color : var ( -- muted ); font - size : 10 px ; white - space : nowrap ; }
2026-10-07 20:59:34 +08:00
. arrangement - grid div { grid - template - columns : 124 px minmax ( 0 , 1 fr ); }
. arrangement - grid dt { white - space : normal ; overflow - wrap : anywhere ; }
2026-09-18 16:48:35 +08:00
. detail - grid dd { min - width : 0 ; margin : 0 ; overflow - wrap : anywhere ; color : var ( -- navy ); font - size : 12 px ; font - weight : 600 ; line - height : 1.4 ; }
2026-10-19 15:50:39 +08:00
. form - card : deep (. el - card__body ) { padding : 0 ; }
2026-10-19 14:53:57 +08:00
2026-09-14 10:02:37 +08:00
@ media ( max - width : 767 px ) {
. business - orders { gap : 14 px ; }
. page - head { align - items : flex - start ; gap : 12 px ; }
. page - head h2 { font - size : 24 px ; }
. page - head > div { min - width : 0 ; }
. detail - grid { grid - template - columns : 1 fr ; }
2026-09-18 16:48:35 +08:00
. detail - grid div , . detail - grid div . is - wide { grid - column : auto ; }
2026-09-14 10:02:37 +08:00
. detail - grid dd { overflow - wrap : break - word ; word - break : normal ; }
2026-09-18 16:48:35 +08:00
. business - detail - heading { align - items : flex - start ; flex - direction : column ; }
2026-09-14 10:02:37 +08:00
. form - card : deep (. el - form - item ) { display : block ; }
. form - card : deep (. el - form - item__label ) {
width : auto ! important ;
height : auto ;
margin - bottom : 7 px ;
line - height : 1.4 ;
justify - content : flex - start ;
}
. form - card : deep (. el - form - item__content ) { width : 100 % ; margin - left : 0 ! important ; }
. form - card : deep (. el - input - number ) { width : 100 % ; }
. form - card . budget - form - item : deep (. el - form - item__content ) { display : flex ; flex - wrap : nowrap ; }
. form - card . budget - form - item : deep (. el - input - number ) { width : auto ; min - width : 0 ; flex : 1 1 auto ; }
. form - card . currency { width : 96 px ; flex : 0 0 96 px ; margin - left : 8 px ; }
}
2026-09-04 12:41:58 +08:00
</ style >