Files
client-web/src/modules/order-management/views/DeliveryOrderDetailView.vue
T
2026-09-18 16:48:35 +08:00

149 lines
8.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>