84 lines
6.8 KiB
Vue
84 lines
6.8 KiB
Vue
<script setup lang="ts">
|
|
import '@/styles/adminWorkbench.css'
|
|
import InlineHelp from '@/shared/components/InlineHelp.vue'
|
|
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
|
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
import { overseasMasterDataPermissions as permission } from '../permissions'
|
|
import { listVehicleDeliveries, listVehicleAssignmentChanges, type VehicleDelivery, type VehicleAssignmentChange } from '../api/vehicleFulfillment'
|
|
|
|
const props = defineProps<{ vehicleId: string; kind: 'deliveries' | 'changes' }>()
|
|
const access = useAccessPreviewStore()
|
|
const allowed = computed(() => access.can(permission.vehicleList) && access.can(props.kind === 'deliveries' ? 'platform:overseas-warehouse:delivery:list' : permission.tripList))
|
|
const rows = ref<(VehicleDelivery | VehicleAssignmentChange)[]>([]), page = ref(1), size = ref(10), total = ref(0), loading = ref(false)
|
|
const error = ref<ApiErrorDetails | null>(null)
|
|
const title = computed(() => props.kind === 'deliveries' ? '派送订单明细' : '派送更换记录')
|
|
let generation = 0
|
|
const columns = computed<DataTableColumn[]>(() => props.kind === 'deliveries' ? [
|
|
{ key: 'deliveryNo', prop: 'deliveryNo', label: '派送单号', minWidth: 210 },
|
|
{ key: 'source', label: 'BOL / 来源排车', minWidth: 215, slotName: 'source' },
|
|
{ key: 'customerName', label: '客户', minWidth: 160, slotName: 'customer' },
|
|
{ key: 'destination', label: '目的仓 / 地址', minWidth: 180, slotName: 'destination' },
|
|
{ key: 'plannedDeliveryAt', label: '预约日期', minWidth: 165, slotName: 'date' },
|
|
{ key: 'status', label: '派送状态', width: 115, slotName: 'status' },
|
|
{ key: 'relationship', label: '关联关系', minWidth: 220, slotName: 'relationship' },
|
|
] : [
|
|
{ key: 'occurredAt', label: '时间', minWidth: 165, slotName: 'date' },
|
|
{ key: 'tripNo', prop: 'tripNo', label: '车次', minWidth: 210 },
|
|
{ key: 'dispatchNos', label: '关联排车', minWidth: 210, slotName: 'dispatches' },
|
|
{ key: 'relationship', label: '动作', width: 100, slotName: 'relationship' },
|
|
{ key: 'vehicle', label: '原车辆 → 新车辆', minWidth: 230, slotName: 'vehicles' },
|
|
{ key: 'driver', label: '原司机 → 新司机', minWidth: 180, slotName: 'drivers' },
|
|
{ key: 'reason', prop: 'reason', label: '原因', minWidth: 200 },
|
|
{ key: 'operator', label: '操作人', width: 100, slotName: 'operator' },
|
|
])
|
|
const statuses: Record<string, string> = { WAIT_DELIVERY: '待派送', DISPATCHED: '已派送', IN_TRANSIT: '运输中', DELIVERED: '已签收', RETURNED: '已退回', CANCELLED: '已取消', DELIVERY_FAILED: '派送异常' }
|
|
const relations: Record<string, string> = { PLANNED: '当前计划车辆', DEPARTED: '实际发车车辆', REPLACED_OUT: '换出本车', REPLACED_IN: '换入本车', DRIVER_CHANGED: '更换司机' }
|
|
function date(value: string | null | undefined) { return value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—' }
|
|
function rowKey(row: VehicleDelivery | VehicleAssignmentChange) { return 'deliveryNo' in row ? row.deliveryNo : `${row.tripNo}:${row.recordVersion}` }
|
|
async function load() {
|
|
const request = ++generation
|
|
rows.value = []; total.value = 0; error.value = null
|
|
if (!allowed.value || !props.vehicleId) { loading.value = false; return }
|
|
loading.value = true
|
|
try {
|
|
const result = props.kind === 'deliveries'
|
|
? await listVehicleDeliveries(props.vehicleId, page.value, size.value)
|
|
: await listVehicleAssignmentChanges(props.vehicleId, page.value, size.value)
|
|
if (request === generation) { rows.value = result.records; total.value = result.total }
|
|
} catch (cause) { if (request === generation) error.value = getApiErrorDetails(cause, `${title.value}加载失败`) }
|
|
finally { if (request === generation) loading.value = false }
|
|
}
|
|
watch([() => props.vehicleId, () => props.kind, allowed, page, size], (current, prior) => {
|
|
if (prior?.length && (current[0] !== prior[0] || current[1] !== prior[1] || current[4] !== prior[4]) && page.value !== 1) { page.value = 1; return }
|
|
void load()
|
|
}, { immediate: true })
|
|
onBeforeUnmount(() => { generation++ })
|
|
</script>
|
|
<template>
|
|
<section :aria-label="title">
|
|
<p v-if="!allowed" class="fulfillment-note">暂无{{ kind === 'deliveries' ? '派送记录' : '车次历史' }}查看权限。</p>
|
|
<template v-else>
|
|
<div class="fulfillment-heading"><InlineHelp :title="`${title}说明`">{{ kind === 'deliveries' ? '计划中的派送按当前分配车辆展示,实际发车后按发车记录归属车辆。' : '按车次保存更换前后的真实记录;同车次可能关联多张排车单。' }}</InlineHelp></div>
|
|
<LoadErrorState v-if="error" :title="`${title}加载失败`" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
|
<div class="workbench-table"><DataTable v-model:page="page" v-model:page-size="size" :data="rows" :columns="columns" :total="total" :loading="loading" :row-key="rowKey" :empty-text="`暂无${title}`">
|
|
<template #toolbar-actions><el-button :disabled="loading" @click="load">刷新</el-button></template>
|
|
<template #source="{ row }"><div>{{ row.bolNo || '—' }}</div><div>{{ row.dispatchNo || '未关联排车' }}</div></template>
|
|
<template #customer="{ row }">{{ row.customerName || '—' }}</template>
|
|
<template #destination="{ row }">{{ row.destinationType === 'PRIVATE' ? row.deliveryAddress || '—' : row.destinationWarehouseCode || row.destinationWarehouseName || '—' }}</template>
|
|
<template #date="{ row }">{{ date(kind === 'deliveries' ? row.plannedDeliveryAt : row.occurredAt) }}</template>
|
|
<template #status="{ row }">{{ statuses[row.status] || row.status || '—' }}</template>
|
|
<template #relationship="{ row }"><div>{{ relations[row.relationship] || '—' }}</div><div v-if="kind === 'deliveries'">{{ row.tripNo }}</div></template>
|
|
<template #dispatches="{ row }"><div v-for="number in row.dispatchNos" :key="number">{{ number }}</div><span v-if="!row.dispatchNos.length">未关联排车</span></template>
|
|
<template #vehicles="{ row }">{{ row.previousVehiclePlate || '—' }} → {{ row.vehiclePlate || '—' }}</template>
|
|
<template #drivers="{ row }">{{ row.previousDriverName || '—' }} → {{ row.driverName || '—' }}</template>
|
|
<template #operator>—</template>
|
|
</DataTable></div>
|
|
</template>
|
|
</section>
|
|
</template>
|
|
<style scoped>.fulfillment-heading{display:flex;justify-content:flex-end;margin-bottom:var(--admin-space-3)}.fulfillment-note{margin:0 0 var(--admin-space-4);color:var(--workbench-muted)}</style>
|