2026-08-19 16:45:51 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-08-20 09:10:36 +08:00
|
|
|
|
import { computed, ref, watch } from 'vue'
|
2026-08-19 16:45:51 +08:00
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
|
import { ArrowLeft } from '@lucide/vue'
|
|
|
|
|
|
import AdminPage from '@/shared/components/AdminPage.vue'
|
2026-08-20 09:10:36 +08:00
|
|
|
|
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
2026-08-19 16:45:51 +08:00
|
|
|
|
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
|
|
|
|
|
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
2026-08-20 09:10:36 +08:00
|
|
|
|
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
|
|
|
|
|
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
|
|
|
|
|
import { getOverseasPurchase } from '../api/platformOverseasBusiness'
|
2026-08-20 18:02:52 +08:00
|
|
|
|
import type { OverseasBusinessHistory, OverseasPurchaseDetail, OverseasPurchaseStatus } from '../types'
|
2026-08-19 16:45:51 +08:00
|
|
|
|
|
2026-08-20 09:10:36 +08:00
|
|
|
|
const route = useRoute(); const router = useRouter()
|
2026-08-19 16:45:51 +08:00
|
|
|
|
const purchaseId = computed(() => typeof route.params.purchaseId === 'string' ? route.params.purchaseId : '')
|
2026-08-20 09:10:36 +08:00
|
|
|
|
const customerId = computed(() => typeof route.query.customerId === 'string' ? route.query.customerId : '')
|
|
|
|
|
|
const detail = ref<OverseasPurchaseDetail | null>(null); const loading = ref(false); const loadError = ref<ApiErrorDetails | null>(null)
|
|
|
|
|
|
const statusLabels: Record<OverseasPurchaseStatus, string> = { PENDING_REVIEW: '待审核', APPROVED: '已通过', REJECTED: '已拒绝' }
|
|
|
|
|
|
const productFields = computed(() => [['客户名称', detail.value?.customerName], ['采购仓库', detail.value?.purchaseWarehouseName], ['采购品类', detail.value?.purchaseCategory], ['商品名称', detail.value?.productName], ['商品规格', detail.value?.productSpecification], ['商品链接', detail.value?.productUrl]] as const)
|
2026-08-20 18:02:52 +08:00
|
|
|
|
const stockFields = computed(() => [['入库量', detail.value?.inboundQuantity], ['库存量', detail.value?.inventoryQuantity], ['库存状态', detail.value ? (detail.value.inventoryQuantity > 0 ? '在库' : '无库存') : null]] as const)
|
2026-08-20 09:10:36 +08:00
|
|
|
|
const orderFields = computed(() => [['订单编号', detail.value?.orderNo], ['采购数量', detail.value?.purchaseQuantity], ['预算单价', detail.value ? formatAmount(detail.value.budgetUnitPrice, detail.value.currency) : null], ['订单状态', detail.value ? statusLabels[detail.value.status] : null], ['联系人', detail.value?.contactName], ['联系电话', detail.value?.contactPhone], ['收货地址', detail.value?.receivingAddress], ['订单备注', detail.value?.remark]] as const)
|
|
|
|
|
|
const courierFields = computed(() => [['国内承运商', detail.value?.domesticCarrier], ['国内运单号', detail.value?.domesticTrackingNo], ['海外承运商', detail.value?.overseasCarrier], ['海外运单号', detail.value?.overseasTrackingNo], ['预计到达时间', formatDateTime(detail.value?.estimatedArrivalAt)]] as const)
|
2026-08-20 18:02:52 +08:00
|
|
|
|
const historyColumns: DataTableColumn[] = [
|
|
|
|
|
|
{ key: 'createdAt', label: '时间', minWidth: 155, formatter: (row: OverseasBusinessHistory) => formatDateTime(row.createdAt) },
|
|
|
|
|
|
{ key: 'operationType', label: '操作类型', width: 110, formatter: (row: OverseasBusinessHistory) => operationLabels[row.operationType || ''] || row.operationType || '—' },
|
|
|
|
|
|
{ key: 'status', label: '状态变化', minWidth: 150, formatter: (row: OverseasBusinessHistory) => row.fromStatus ? `${statusLabel(row.fromStatus)} → ${statusLabel(row.toStatus)}` : statusLabel(row.toStatus) },
|
|
|
|
|
|
{ key: 'changeDetail', prop: 'changeDetail', label: '变更明细', minWidth: 260 },
|
|
|
|
|
|
{ key: 'operatorId', prop: 'operatorId', label: '操作人', minWidth: 120 },
|
2026-08-19 16:45:51 +08:00
|
|
|
|
{ key: 'remark', prop: 'remark', label: '备注', minWidth: 180 },
|
|
|
|
|
|
]
|
2026-08-20 18:02:52 +08:00
|
|
|
|
const operationLabels: Record<string, string> = { CREATE: '创建', REVIEW: '审核', UPDATE: '编辑' }
|
|
|
|
|
|
function statusLabel(status?: string | null) { return status ? (statusLabels[status as OverseasPurchaseStatus] || status) : '—' }
|
2026-08-20 09:10:36 +08:00
|
|
|
|
function display(value: unknown) { return value === null || value === undefined || value === '' ? '—' : String(value) }
|
|
|
|
|
|
function formatDateTime(value?: string | null) { return value ? value.replace('T', ' ').slice(0, 16) : '—' }
|
|
|
|
|
|
function formatAmount(value: string | null, currency: string) { return value == null ? '—' : `${Number(value).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ${currency}` }
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
detail.value = null; loadError.value = null
|
|
|
|
|
|
if (!purchaseId.value || !customerId.value) { loadError.value = { message: '缺少采购订单号或客户标识,请从采购订单列表重新进入' }; return }
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
try { detail.value = await getOverseasPurchase(purchaseId.value, customerId.value) } catch (error) { loadError.value = getApiErrorDetails(error, '海外采购订单详情加载失败') } finally { loading.value = false }
|
|
|
|
|
|
}
|
|
|
|
|
|
watch([purchaseId, customerId], () => { void load() }, { immediate: true })
|
2026-08-19 16:45:51 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<AdminPage><DetailPageLayout aria-label="海外采购订单详情">
|
2026-08-20 09:10:36 +08:00
|
|
|
|
<template #header><div class="detail-header"><div><h2>海外采购订单详情</h2><span>订单编号:{{ detail?.orderNo || purchaseId || '—' }}</span></div><el-button @click="router.push({ name: 'overseas-business-purchases' })"><ArrowLeft :size="16" />返回</el-button></div></template>
|
|
|
|
|
|
<LoadErrorState v-if="loadError" title="采购订单详情暂时无法加载" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
|
|
|
|
|
|
<div v-else v-loading="loading" class="detail-surface surface-panel">
|
2026-08-19 16:45:51 +08:00
|
|
|
|
<div class="detail-grid">
|
2026-08-20 09:10:36 +08:00
|
|
|
|
<section><h3>商品信息</h3><dl><div v-for="field in productFields" :key="field[0]"><dt>{{ field[0] }}</dt><dd>{{ display(field[1]) }}</dd></div></dl></section>
|
|
|
|
|
|
<section><h3>库存信息</h3><dl><div v-for="field in stockFields" :key="field[0]"><dt>{{ field[0] }}</dt><dd>{{ display(field[1]) }}</dd></div></dl></section>
|
|
|
|
|
|
<section><h3>订单信息</h3><dl><div v-for="field in orderFields" :key="field[0]"><dt>{{ field[0] }}</dt><dd>{{ display(field[1]) }}</dd></div></dl></section>
|
|
|
|
|
|
<section><h3>快递信息</h3><dl><div v-for="field in courierFields" :key="field[0]"><dt>{{ field[0] }}</dt><dd>{{ display(field[1]) }}</dd></div></dl></section>
|
2026-08-20 18:02:52 +08:00
|
|
|
|
<section class="full"><div class="section-heading"><h3>操作流水</h3><span>展示创建、审核和编辑变更;四期不承载采购出库。</span></div><DataTable :data="detail?.histories || []" :columns="historyColumns" row-key="createdAt" empty-text="暂无操作流水" /></section>
|
2026-08-19 16:45:51 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</DetailPageLayout></AdminPage>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-08-20 09:10:36 +08:00
|
|
|
|
.detail-header { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:14px 18px; border:1px solid var(--cc-border); border-radius:8px; background:#fff; }.detail-header h2 { margin:0; font-size:16px; }.detail-header span { display:block; margin-top:4px; color:var(--cc-muted); font-size:12px; }.detail-surface { min-height:240px; padding:20px; }.detail-grid { display:grid; grid-template-columns:1fr 1fr; gap:0 24px; }.detail-grid section { padding:0 0 18px; border-bottom:1px solid var(--cc-border); }.detail-grid section:nth-child(3), .detail-grid section:nth-child(4) { padding-top:18px; border-bottom:0; }.detail-grid h3 { margin:0 0 12px; font-size:14px; }.detail-grid .full { grid-column:1 / -1; padding-top:18px; border-top:1px solid var(--cc-border); border-bottom:0; }.detail-grid dl { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px 18px; margin:0; }.detail-grid dl div { min-width:0; }.detail-grid dt { color:var(--cc-muted); font-size:12px; }.detail-grid dd { margin:4px 0 0; overflow-wrap:anywhere; color:var(--cc-text); font-size:13px; }.section-heading { display:flex; align-items:baseline; justify-content:space-between; gap:16px; }.section-heading span { color:var(--cc-muted); font-size:12px; }
|
2026-08-19 16:45:51 +08:00
|
|
|
|
</style>
|