88 lines
6.8 KiB
Vue
88 lines
6.8 KiB
Vue
<script setup lang="ts">
|
||
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 AdminPage from '@/shared/components/AdminPage.vue'
|
||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||
import { getLocalLooseboardOrder } from '../api/platformOverseasBusiness'
|
||
import BusinessDocumentFees from '../components/BusinessDocumentFees.vue'
|
||
import FleetArrangementSummary from '../components/FleetArrangementSummary.vue'
|
||
import { poolOrderStatusLabels } from '../fleetArrangement'
|
||
import type { LocalLooseboardDetail, OverseasBusinessHistory } from '../types'
|
||
|
||
const route = useRoute(), router = useRouter()
|
||
const orderNo = computed(() => typeof route.params.orderId === 'string' ? route.params.orderId : '')
|
||
const customerId = computed(() => typeof route.query.customerId === 'string' ? route.query.customerId : '')
|
||
const detail = ref<LocalLooseboardDetail | null>(null), loading = ref(false)
|
||
const error = ref<ApiErrorDetails | null>(null)
|
||
let requestSequence = 0
|
||
const dateTime = (value: string | null | undefined) => value ? value.replace('T', ' ').slice(0, 16) : '—'
|
||
const statusLabels: Record<string, string> = { PENDING_ACCEPTANCE: '待接单', DISPATCHED: '已派车', ...poolOrderStatusLabels }
|
||
const statusLabel = (value: string | null) => value ? statusLabels[value] || value : '—'
|
||
const fields = computed(() => detail.value ? [
|
||
['客户编号', detail.value.customerId], ['商品名称', detail.value.productName], ['预计托数', detail.value.estimatedPalletCount],
|
||
['起运地', 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.budgetAmount == null ? '—' : `${detail.value.budgetAmount} ${detail.value.currency}`],
|
||
['创建时间', dateTime(detail.value.createdAt)], ['备注', detail.value.remark],
|
||
] : [])
|
||
const operationLabels: Record<string, string> = { CREATE: '创建', UPDATE: '编辑', REVIEW: '订单审核', STATUS_CHANGE: '状态变更', CANCEL: '取消订单' }
|
||
const historyColumns: DataTableColumn[] = [
|
||
{ key: 'createdAt', label: '操作时间', minWidth: 170, formatter: (row: OverseasBusinessHistory) => dateTime(row.createdAt) },
|
||
{ key: 'operationType', label: '操作', minWidth: 130, formatter: (row: OverseasBusinessHistory) => operationLabels[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 },
|
||
]
|
||
|
||
/** 只用路由中的真实订单和客户查询,切换单据时丢弃旧请求结果。 */
|
||
async function load() {
|
||
const sequence = ++requestSequence
|
||
detail.value = null; error.value = null
|
||
if (!orderNo.value || !customerId.value || customerId.value === '0') {
|
||
loading.value = false; error.value = { message: '缺少有效客户或订单信息,请返回本土散板列表重新打开' }; return
|
||
}
|
||
loading.value = true
|
||
try { const result = await getLocalLooseboardOrder(orderNo.value, customerId.value); if (sequence === requestSequence) detail.value = result }
|
||
catch (cause) { if (sequence === requestSequence) error.value = getApiErrorDetails(cause, '本土散板订单加载失败,请稍后重试') }
|
||
finally { if (sequence === requestSequence) loading.value = false }
|
||
}
|
||
watch([orderNo, customerId], load, { immediate: true })
|
||
</script>
|
||
|
||
<template>
|
||
<AdminPage class="admin-density admin-workbench"><DetailPageLayout aria-label="本土散板订单详情" class="density-detail-layout workbench-panel is-flush is-page-surface">
|
||
<template #header>
|
||
<div class="density-detail-header looseboard-detail-header"><div class="density-detail-heading">
|
||
<el-button link class="density-back" @click="router.push({ name: 'overseas-business-looseboards' })"><ArrowLeft :size="16" />返回列表</el-button>
|
||
<h1 class="density-detail-title">本土散板订单 {{ detail?.orderNo || orderNo }}</h1>
|
||
<StatusBadge v-if="detail" :status="statusLabel(detail.status)" />
|
||
</div><el-button v-if="detail" @click="load">刷新订单</el-button></div>
|
||
</template>
|
||
<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="looseboard-detail-section"><FleetArrangementSummary :arrangement="detail.fleetArrangement" /></section>
|
||
<section class="looseboard-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 class="looseboard-detail-section"><BusinessDocumentFees business-type="LOCAL_LOOSEBOARD" :business-no="detail.orderNo" :customer-id="detail.customerId" :business-id="detail.id" /></section>
|
||
<section class="looseboard-detail-section"><h2>操作记录</h2><div class="workbench-table"><DataTable :data="detail.histories" :columns="historyColumns" empty-text="暂无操作记录" /></div></section>
|
||
</template>
|
||
</DetailPageLayout></AdminPage>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.looseboard-detail-header { flex-wrap: wrap; }
|
||
.looseboard-detail-header .density-detail-heading { flex: 1; }
|
||
.looseboard-detail-section { margin-bottom: var(--admin-space-6); }
|
||
</style>
|