优化
This commit is contained in:
@@ -3,7 +3,7 @@ import '@/styles/adminWorkbench.css'
|
||||
import '@/styles/adminDensity.css'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ArrowLeft } from '@lucide/vue'
|
||||
import { ArrowLeft, Check, MapPin } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||
@@ -16,7 +16,7 @@ import FleetArrangementSummary from '../components/FleetArrangementSummary.vue'
|
||||
import TruckOrderActions from '../components/TruckOrderActions.vue'
|
||||
import BusinessDocumentFees from '../components/BusinessDocumentFees.vue'
|
||||
import { truckStatusLabels } from '../truckWorkflow'
|
||||
import type { LocalTransportDetail, LocalTransportPallet, OverseasBusinessHistory } from '../types'
|
||||
import type { LocalTransportDetail, OverseasBusinessHistory } from '../types'
|
||||
|
||||
const route = useRoute(), router = useRouter()
|
||||
const orderNo = computed(() => typeof route.params.orderId === 'string' ? route.params.orderId : '')
|
||||
@@ -24,33 +24,43 @@ const customerId = computed(() => typeof route.query.customerId === 'string' ? r
|
||||
const detail = ref<LocalTransportDetail | null>(null), loading = ref(false), saving = ref(false)
|
||||
const error = ref<ApiErrorDetails | null>(null)
|
||||
let requestSequence = 0
|
||||
|
||||
const flowSteps = ['待审核', '排车审核', '取货', '派送中', '送达']
|
||||
const dateTime = (value: string | null | undefined) => value ? value.replace('T', ' ').slice(0, 16) : '—'
|
||||
const statusLabel = (value: string | null) => value ? truckStatusLabels[value as keyof typeof truckStatusLabels] || value : '—'
|
||||
const flowIndex = computed(() => {
|
||||
const status = detail.value?.status
|
||||
if (status === 'PENDING_REVIEW') return 0
|
||||
if (status === 'IN_TRANSIT') return 3
|
||||
if (status === 'COMPLETED') return 4
|
||||
if (status === 'CANCELLED' || status === 'REJECTED') return 0
|
||||
return 1
|
||||
})
|
||||
const fields = computed(() => detail.value ? [
|
||||
['客户编号', detail.value.customerId], ['车辆类型', detail.value.vehicleType ? ({ PALLET_15: '15拖', PALLET_30: '30拖' })[detail.value.vehicleType] : '—'],
|
||||
['起运地', detail.value.origin], ['起运地详细地址', detail.value.originAddress], ['起运地联系人', detail.value.originContactName], ['起运地联系电话', detail.value.originContactPhone],
|
||||
['目的地', detail.value.destination], ['目的地详细地址', detail.value.destinationAddress], ['目的地联系人', detail.value.destinationContactName], ['目的地联系电话', detail.value.destinationContactPhone],
|
||||
['取货时间', dateTime(detail.value.pickupAt)], ['送货时间', dateTime(detail.value.deliveryAt)],
|
||||
['申报件数', detail.value.packageCount], ['总重量(kg)', detail.value.totalWeightKg], ['总体积(m³)', detail.value.totalVolumeCbm],
|
||||
['车辆类型', detail.value.vehicleType ? ({ PALLET_15: '15拖', PALLET_30: '30拖' })[detail.value.vehicleType] : '—'],
|
||||
['取货地', detail.value.origin], ['取货地址', detail.value.originAddress],
|
||||
['取货联系人', detail.value.originContactName], ['取货联系电话', detail.value.originContactPhone],
|
||||
['送达地', detail.value.destination], ['送达地址', detail.value.destinationAddress],
|
||||
['送达联系人', detail.value.destinationContactName], ['送达联系电话', detail.value.destinationContactPhone],
|
||||
['件数', detail.value.packageCount], ['重量(kg)', detail.value.totalWeightKg], ['体积(m³)', detail.value.totalVolumeCbm],
|
||||
['预算总价', detail.value.budgetAmount == null ? '—' : `${detail.value.budgetAmount} ${detail.value.currency}`],
|
||||
['已确认托数', detail.value.palletCount], ['累计实收托数', detail.value.receivedPalletCount], ['已分配托数', detail.value.allocatedPalletCount],
|
||||
['取货时间', dateTime(detail.value.pickupAt)], ['送货时间', dateTime(detail.value.deliveryAt)],
|
||||
['创建时间', dateTime(detail.value.createdAt)], ['备注', detail.value.remark],
|
||||
] : [])
|
||||
const palletLabels: Record<string, string> = { EXPECTED: '待实收', PLANNED: '待实收', RECEIVED: '已实收', ALLOCATED: '已分配', OVERAGE_PENDING_REVIEW: '超托待审', OVERAGE_REJECTED: '超托拒收' }
|
||||
const palletColumns: DataTableColumn[] = [
|
||||
{ key: 'palletNo', prop: 'palletNo', label: '托盘编号', minWidth: 180 },
|
||||
{ key: 'status', label: '状态', width: 130, formatter: (row: LocalTransportPallet) => palletLabels[row.status] || row.status },
|
||||
{ key: 'packageCount', prop: 'packageCount', label: '实收件数', width: 120 },
|
||||
{ key: 'receivedAt', label: '实收时间', minWidth: 170, formatter: (row: LocalTransportPallet) => dateTime(row.receivedAt) },
|
||||
]
|
||||
const operations: Record<string, string> = { CREATE: '创建', UPDATE: '编辑', REVIEW: '订单审核', RECEIVE_PALLETS: '登记实收', REVIEW_PALLET_OVERAGE: '超托审核', ALLOCATE_PALLETS: '分配托盘', PREPARE_DISPATCH: '确认待派车', DISPATCH: '确认已派车', START_TRANSIT: '确认发运', COMPLETE: '确认完成', CANCEL: '取消订单' }
|
||||
const operations: Record<string, string> = {
|
||||
CREATE: '创建', UPDATE: '编辑', REVIEW: '审核', TRANSPORT_ASSIGN: '接入排车',
|
||||
RECEIVE_PALLETS: '取货', START_TRANSIT: '派送中', COMPLETE: '送达', LOCAL_TRUCK_DEPART: '取货完成', LOCAL_TRUCK_COMPLETE: '送达', CANCEL: '取消',
|
||||
DISPATCH: '接入排车', PREPARE_DISPATCH: '接入排车',
|
||||
}
|
||||
const historyColumns: DataTableColumn[] = [
|
||||
{ key: 'createdAt', label: '操作时间', minWidth: 170, formatter: (row: OverseasBusinessHistory) => dateTime(row.createdAt) },
|
||||
{ key: 'operationType', label: '操作', minWidth: 130, formatter: (row: OverseasBusinessHistory) => operations[row.operationType || ''] || row.operationType || '—' },
|
||||
{ key: 'fromStatus', label: '原状态', width: 130, formatter: (row: OverseasBusinessHistory) => statusLabel(row.fromStatus) },
|
||||
{ key: 'toStatus', label: '新状态', width: 130, formatter: (row: OverseasBusinessHistory) => statusLabel(row.toStatus) },
|
||||
{ key: 'operatorId', prop: 'operatorId', label: '操作人', minWidth: 140 }, { key: 'remark', prop: 'remark', label: '审核意见/操作说明', minWidth: 240 },
|
||||
{ key: 'createdAt', label: '时间', minWidth: 170, formatter: (row: OverseasBusinessHistory) => dateTime(row.createdAt) },
|
||||
{ key: 'operationType', label: '操作', minWidth: 120, formatter: (row: OverseasBusinessHistory) => operations[row.operationType || ''] || row.operationType || '—' },
|
||||
{ key: 'fromStatus', label: '原状态', width: 120, formatter: (row: OverseasBusinessHistory) => statusLabel(row.fromStatus) },
|
||||
{ key: 'toStatus', label: '新状态', width: 120, formatter: (row: OverseasBusinessHistory) => statusLabel(row.toStatus) },
|
||||
{ key: 'operatorId', prop: 'operatorId', label: '操作人', minWidth: 120 },
|
||||
{ key: 'remark', prop: 'remark', label: '说明', minWidth: 220 },
|
||||
]
|
||||
|
||||
async function load() {
|
||||
if (saving.value) return
|
||||
const sequence = ++requestSequence
|
||||
@@ -84,11 +94,21 @@ watch([orderNo, customerId], load, { immediate: true })
|
||||
<LoadErrorState v-if="error" title="本土卡车订单暂时无法加载" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||||
<el-skeleton v-else-if="loading" :rows="8" animated />
|
||||
<template v-else-if="detail">
|
||||
<section v-if="detail.fleetArrangement" class="truck-detail-section"><FleetArrangementSummary :arrangement="detail.fleetArrangement" /></section>
|
||||
<section class="truck-detail-section"><h2>订单资料</h2><el-descriptions class="density-descriptions" :column="3" border><el-descriptions-item v-for="[label, value] in fields" :key="String(label)" :label="String(label)">{{ value ?? '—' }}</el-descriptions-item></el-descriptions></section>
|
||||
<section v-if="detail.fleetArrangement" class="truck-detail-section"><FleetArrangementSummary :arrangement="detail.fleetArrangement" /><p class="truck-detail-note">取货和送达照片请在排车任务中上传。</p></section>
|
||||
|
||||
<section class="truck-detail-section truck-flow-section" aria-label="运输流程">
|
||||
<div class="truck-section-heading"><h2>运输流程</h2><span class="truck-flow-caption">审核通过后由排车单推进取货、派送和送达</span></div>
|
||||
<ol class="truck-flow">
|
||||
<li v-for="(step, index) in flowSteps" :key="step" :class="{ active: index === flowIndex, done: index < flowIndex || detail.status === 'COMPLETED' }">
|
||||
<span class="truck-flow-marker"><Check v-if="index < flowIndex || detail.status === 'COMPLETED'" :size="14" /><MapPin v-else-if="index === 2 || index === 4" :size="14" /><span v-else>{{ index + 1 }}</span></span>
|
||||
<span class="truck-flow-label">{{ step }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="truck-detail-section"><div class="truck-section-heading"><h2>订单资料</h2><span class="truck-flow-caption">基础信息与货量</span></div><dl class="truck-detail-grid"><div v-for="[label, value] in fields" :key="String(label)" class="truck-detail-fact"><dt>{{ label }}</dt><dd>{{ value ?? '—' }}</dd></div></dl></section>
|
||||
<section class="truck-detail-section"><BusinessDocumentFees business-type="US_TRUCK" :business-no="detail.orderNo" :customer-id="detail.customerId" :business-id="detail.id" /></section>
|
||||
<section class="truck-detail-section"><h2>托盘记录</h2><div class="workbench-table"><DataTable :data="detail.pallets" :columns="palletColumns" empty-text="暂无托盘记录,独立订单首次实收后生成托盘" /></div></section>
|
||||
<section class="truck-detail-section"><h2>审核与操作记录</h2><div class="workbench-table"><DataTable :data="detail.histories" :columns="historyColumns" empty-text="暂无操作记录" /></div></section>
|
||||
<section class="truck-detail-section"><div class="truck-section-heading"><h2>审核与操作记录</h2><span class="truck-flow-caption">保留关键节点</span></div><div class="workbench-table"><DataTable :data="detail.histories" :columns="historyColumns" empty-text="暂无操作记录" /></div></section>
|
||||
</template>
|
||||
</DetailPageLayout></AdminPage>
|
||||
</template>
|
||||
@@ -99,4 +119,25 @@ watch([orderNo, customerId], load, { immediate: true })
|
||||
.truck-detail-header .status-badge { flex-shrink: 0; }
|
||||
.truck-detail-actions { flex-wrap: wrap; }
|
||||
.truck-detail-section { margin-bottom: var(--admin-space-6); }
|
||||
.truck-section-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
|
||||
.truck-section-heading h2 { margin: 0; font-size: 16px; color: var(--cc-text); }
|
||||
.truck-flow-caption, .truck-detail-note { color: var(--cc-muted); font-size: 12px; }
|
||||
.truck-detail-note { margin: 10px 0 0; }
|
||||
.truck-flow { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 0; margin: 0; padding: 0; list-style: none; }
|
||||
.truck-flow li { position: relative; display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: 0; color: var(--cc-muted); text-align: center; }
|
||||
.truck-flow li::before { content: ''; position: absolute; top: 15px; left: -50%; width: 100%; height: 1px; background: var(--cc-border); }
|
||||
.truck-flow li:first-child::before { display: none; }
|
||||
.truck-flow li.done::before, .truck-flow li.active::before { background: var(--el-color-primary); }
|
||||
.truck-flow-marker { position: relative; z-index: 1; display: grid; place-items: center; width: 30px; height: 30px; border: 1px solid var(--cc-border); border-radius: 50%; background: var(--cc-surface); color: var(--cc-muted); font-size: 12px; }
|
||||
.truck-flow li.done .truck-flow-marker, .truck-flow li.active .truck-flow-marker { border-color: var(--el-color-primary); color: var(--el-color-primary); }
|
||||
.truck-flow li.active .truck-flow-marker { background: var(--el-color-primary); color: #fff; box-shadow: 0 0 0 4px color-mix(in srgb, var(--el-color-primary) 14%, transparent); }
|
||||
.truck-flow-label { font-size: 12px; white-space: nowrap; }
|
||||
.truck-flow li.active .truck-flow-label { color: var(--el-color-primary); font-weight: 600; }
|
||||
.truck-detail-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; margin: 0; border: 1px solid var(--cc-border); border-radius: var(--cc-radius-lg); overflow: hidden; background: var(--cc-border); }
|
||||
.truck-detail-fact { min-width: 0; display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 10px; padding: 11px 14px; background: var(--cc-surface); }
|
||||
.truck-detail-fact dt { color: var(--cc-muted); font-size: 12px; }
|
||||
.truck-detail-fact dd { min-width: 0; margin: 0; color: var(--cc-text); overflow-wrap: anywhere; font-size: 13px; }
|
||||
.truck-detail-fact:last-child { grid-column: span 2; }
|
||||
@media (max-width: 1100px) { .truck-detail-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
@media (max-width: 760px) { .truck-flow { overflow-x: auto; grid-template-columns: repeat(5, 100px); padding-bottom: 4px; } .truck-detail-grid { grid-template-columns: 1fr; } .truck-detail-fact:last-child { grid-column: auto; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user