149 lines
8.9 KiB
Vue
149 lines
8.9 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { ArrowLeft } from '@lucide/vue'
|
||
import dayjs from 'dayjs'
|
||
import { getDeliveryOrder, type DeliveryOrderDetail, type DeliveryOrderItem } from '../api/deliveryOrders'
|
||
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
|
||
|
||
const props = defineProps<{ deliveryNo: string }>()
|
||
const router = useRouter()
|
||
const loading = ref(false)
|
||
const errorMessage = ref('')
|
||
const detail = ref<DeliveryOrderDetail | null>(null)
|
||
const statusLabels: Record<string, string> = { PENDING: '待派送', IN_TRANSIT: '派送中', DELIVERED: '已送达', SIGNED: '已签收', CANCELLED: '已取消' }
|
||
const deliveryMethodLabels: Record<string, string> = { FBA_PICKUP: 'FBA 自提', CUSTOMER_PICKUP: '客户自提', TRUCK_PALLET: '卡派打托', TRUCK_FLOOR: '卡派地板', EXPRESS: '快递', OUTSOURCED: '外配', DEDICATED_TRUCK: '专车直送', LTL: '零担派送' }
|
||
const pickupMethodLabels: Record<string, string> = { ONE_PICKUP_ONE_DROP: '一提一卸', ONE_PICKUP_MULTI_DROP: '一提多卸', MULTI_PICKUP_ONE_DROP: '多提一卸', MULTI_PICKUP_MULTI_DROP: '多提多卸', WAREHOUSE_PICKUP: '仓库自提' }
|
||
const loadingTypeLabels: Record<string, string> = { PALLET: '托盘', FLOOR: '地板', MIXED: '混装' }
|
||
const vehicleTypeLabels: Record<string, string> = { TRUCK: '卡车', VAN: '厢式货车', BOX_TRUCK: '箱式货车', TRAILER: '拖挂车' }
|
||
const appointmentAccountLabels: Record<string, string> = { SELF: '自有账号', PLATFORM: '平台账号', CARRIER: '承运商账号' }
|
||
const tableValueFormatter = (_row: object, _column: unknown, value: string | number | null | undefined) => display(value)
|
||
const itemColumns: DataTableColumn[] = [
|
||
{ prop: 'palletNo', label: '托盘码', minWidth: 170, formatter: tableValueFormatter }, { prop: 'inboundCode', label: '入仓码', minWidth: 190, formatter: tableValueFormatter },
|
||
{ prop: 'skuCode', label: 'SKU', minWidth: 160, formatter: tableValueFormatter }, { prop: 'productName', label: '品名', minWidth: 180, formatter: tableValueFormatter },
|
||
{ prop: 'packageCount', label: '箱数', width: 90, align: 'right', headerAlign: 'right', formatter: tableValueFormatter },
|
||
{ prop: 'weightKg', label: '重量(kg)', width: 120, align: 'right', headerAlign: 'right', formatter: tableValueFormatter },
|
||
{ prop: 'volumeCbm', label: '体积(m³)', width: 120, align: 'right', headerAlign: 'right', formatter: tableValueFormatter },
|
||
]
|
||
const feeColumns: DataTableColumn[] = [
|
||
{ prop: 'feeType', label: '费用类型', minWidth: 180, formatter: tableValueFormatter }, { prop: 'currency', label: '币种', width: 100, formatter: tableValueFormatter },
|
||
{ prop: 'amount', label: '金额', width: 140, align: 'right', headerAlign: 'right', formatter: tableValueFormatter }, { prop: 'remark', label: '备注', minWidth: 240, formatter: tableValueFormatter },
|
||
]
|
||
|
||
const items = computed<DeliveryOrderItem[]>(() => {
|
||
const value = detail.value?.items
|
||
if (!value) return []
|
||
return Array.isArray(value) ? value : value.records
|
||
})
|
||
|
||
function display(value: string | number | null | undefined) {
|
||
return value === null || value === undefined || value === '' ? '—' : String(value)
|
||
}
|
||
|
||
function formatDateTime(value: string | null | undefined) {
|
||
return value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '—'
|
||
}
|
||
|
||
function formatQuantity(value: number | null | undefined, unit: string) {
|
||
return value === null || value === undefined ? '—' : `${value} ${unit}`
|
||
}
|
||
|
||
function statusLabel(value: string) { return statusLabels[value] ?? display(value) }
|
||
function statusTone(value: string) {
|
||
if (value === 'DELIVERED' || value === 'SIGNED') return 'success'
|
||
if (value === 'IN_TRANSIT') return 'primary'
|
||
if (value === 'CANCELLED') return 'danger'
|
||
return 'warning'
|
||
}
|
||
function mappedLabel(value: string | null | undefined, labels: Record<string, string>) {
|
||
return value ? labels[value] ?? value : '—'
|
||
}
|
||
|
||
async function loadDetail() {
|
||
loading.value = true
|
||
errorMessage.value = ''
|
||
try {
|
||
detail.value = await getDeliveryOrder(props.deliveryNo)
|
||
} catch (error) {
|
||
detail.value = null
|
||
errorMessage.value = error instanceof Error ? error.message : '派送单详情加载失败,请重试'
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
function backToList() {
|
||
void router.push({ name: 'orders-delivery' })
|
||
}
|
||
|
||
onMounted(loadDetail)
|
||
</script>
|
||
|
||
<template>
|
||
<section class="delivery-page" aria-label="派送单详情">
|
||
<div v-if="errorMessage" class="delivery-card delivery-state delivery-state--error" role="alert"><p>{{ errorMessage }}</p><el-button @click="loadDetail">重试</el-button></div>
|
||
<div v-else v-loading="loading" class="delivery-detail-content">
|
||
<template v-if="detail">
|
||
<article class="delivery-detail-surface">
|
||
<header class="delivery-detail-header">
|
||
<div class="delivery-detail-title">
|
||
<el-button link type="primary" @click="backToList"><ArrowLeft :size="16" />返回列表</el-button>
|
||
<div><h2>{{ display(detail.deliveryNo) }}</h2><p>{{ display(detail.overseasWarehouseName) }} · {{ mappedLabel(detail.deliveryMethod, deliveryMethodLabels) }}</p></div>
|
||
</div>
|
||
<span class="client-status" :data-tone="statusTone(detail.status)">{{ statusLabel(detail.status) }}</span>
|
||
</header>
|
||
|
||
<section class="delivery-detail-section">
|
||
<h3>订单概览</h3>
|
||
<div class="delivery-detail-grid">
|
||
<div><span>排车单号</span><strong>{{ display(detail.dispatchNo) }}</strong></div>
|
||
<div><span>出库单号</span><strong>{{ display(detail.outboundNo) }}</strong></div>
|
||
<div><span>海外仓</span><strong>{{ display(detail.overseasWarehouseName) }}</strong></div>
|
||
<div><span>派送方式</span><strong>{{ mappedLabel(detail.deliveryMethod, deliveryMethodLabels) }}</strong></div>
|
||
<div><span>提货方式</span><strong>{{ mappedLabel(detail.pickupMethod, pickupMethodLabels) }}</strong></div>
|
||
<div><span>装载类型</span><strong>{{ mappedLabel(detail.loadingType, loadingTypeLabels) }}</strong></div>
|
||
<div><span>托盘数</span><strong>{{ formatQuantity(detail.palletCount, '托') }}</strong></div>
|
||
<div><span>箱数</span><strong>{{ formatQuantity(detail.packageCount, '箱') }}</strong></div>
|
||
<div><span>总重量</span><strong>{{ formatQuantity(detail.totalWeightKg, 'kg') }}</strong></div>
|
||
<div><span>总体积</span><strong>{{ formatQuantity(detail.totalVolumeCbm, 'm³') }}</strong></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="delivery-detail-section">
|
||
<h3>运输与预约</h3>
|
||
<div class="delivery-detail-grid">
|
||
<div><span>运单号</span><strong>{{ display(detail.waybillNo) }}</strong></div>
|
||
<div><span>承运商</span><strong>{{ display(detail.carrierName) }}</strong></div>
|
||
<div><span>车型</span><strong>{{ mappedLabel(detail.vehicleType, vehicleTypeLabels) }}</strong></div>
|
||
<div><span>车辆尺寸</span><strong>{{ display(detail.vehicleSize) }}</strong></div>
|
||
<div><span>车牌号</span><strong>{{ display(detail.vehiclePlate) }}</strong></div>
|
||
<div><span>司机姓名</span><strong>{{ display(detail.driverName) }}</strong></div>
|
||
<div><span>司机电话</span><strong>{{ display(detail.driverPhone) }}</strong></div>
|
||
<div><span>预约账号</span><strong>{{ mappedLabel(detail.appointmentAccount, appointmentAccountLabels) }}</strong></div>
|
||
<div><span>卖家名称</span><strong>{{ display(detail.sellerName) }}</strong></div>
|
||
<div><span>计划派送</span><strong>{{ formatDateTime(detail.plannedDeliveryAt) }}</strong></div>
|
||
<div><span>预计到达</span><strong>{{ formatDateTime(detail.expectedArrivalAt) }}</strong></div>
|
||
<div><span>签收时间</span><strong>{{ formatDateTime(detail.signedAt) }}</strong></div>
|
||
<div><span>创建时间</span><strong>{{ formatDateTime(detail.createdAt) }}</strong></div>
|
||
<div><span>更新时间</span><strong>{{ formatDateTime(detail.updatedAt) }}</strong></div>
|
||
<div class="delivery-span-all"><span>备注</span><strong>{{ display(detail.remark) }}</strong></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="delivery-detail-section">
|
||
<div class="delivery-detail-section-heading"><h3>货物明细</h3><span>共 {{ items.length }} 条</span></div>
|
||
<DataTable :data="items" :columns="itemColumns" empty-text="暂无货物明细" />
|
||
</section>
|
||
|
||
<section class="delivery-detail-section">
|
||
<div class="delivery-detail-section-heading"><h3>费用明细</h3><span>共 {{ detail.fees.length }} 条</span></div>
|
||
<DataTable :data="detail.fees" :columns="feeColumns" empty-text="暂无费用明细" />
|
||
</section>
|
||
</article>
|
||
</template>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped src="./deliveryOrders.css"></style>
|