Files
client-web/src/modules/order-management/views/DeliveryOrderDetailView.vue
T

149 lines
8.9 KiB
Vue
Raw Normal View History

2026-09-01 14:24:00 +08:00
<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'
2026-09-18 16:48:35 +08:00
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
2026-09-01 14:24:00 +08:00
const props = defineProps<{ deliveryNo: string }>()
const router = useRouter()
const loading = ref(false)
const errorMessage = ref('')
const detail = ref<DeliveryOrderDetail | null>(null)
2026-09-18 16:48:35 +08:00
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 },
]
2026-09-01 14:24:00 +08:00
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}`
}
2026-09-18 16:48:35 +08:00
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 : '—'
}
2026-09-01 14:24:00 +08:00
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>
2026-09-18 16:48:35 +08:00
<section class="delivery-page" aria-label="派送单详情">
2026-09-01 14:24:00 +08:00
<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">
2026-09-18 16:48:35 +08:00
<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>
2026-09-01 14:24:00 +08:00
<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>
2026-09-18 16:48:35 +08:00
<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>
2026-09-01 14:24:00 +08:00
<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>
2026-09-18 16:48:35 +08:00
<section class="delivery-detail-section">
2026-09-01 14:24:00 +08:00
<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>
2026-09-18 16:48:35 +08:00
<div><span>车型</span><strong>{{ mappedLabel(detail.vehicleType, vehicleTypeLabels) }}</strong></div>
2026-09-01 14:24:00 +08:00
<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>
2026-09-18 16:48:35 +08:00
<div><span>预约账号</span><strong>{{ mappedLabel(detail.appointmentAccount, appointmentAccountLabels) }}</strong></div>
2026-09-01 14:24:00 +08:00
<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>
2026-09-18 16:48:35 +08:00
<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="暂无货物明细" />
2026-09-01 14:24:00 +08:00
</section>
2026-09-18 16:48:35 +08:00
<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="暂无费用明细" />
2026-09-01 14:24:00 +08:00
</section>
2026-09-18 16:48:35 +08:00
</article>
2026-09-01 14:24:00 +08:00
</template>
</div>
</section>
</template>
<style scoped src="./deliveryOrders.css"></style>