This commit is contained in:
2026-08-20 18:02:52 +08:00
parent 4ae5e7c672
commit 80947cd9ce
65 changed files with 1386 additions and 1083 deletions
@@ -1,62 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { getInboundPlan, type InboundPlanDetailDto } from '../api/inboundPlans'
import OverseasDetailShell from '../components/OverseasDetailShell.vue'
const route = useRoute()
const router = useRouter()
const orderId = computed(() => String(route.params.orderId || '—'))
const route = useRoute(); const router = useRouter()
const planNo = computed(() => String(route.params.orderId || ''))
const customerId = computed(() => String(route.query.customerId || ''))
const detail = ref<InboundPlanDetailDto | null>(null); const loading = ref(false); const loadError = ref<ApiErrorDetails | null>(null)
function text(value: unknown) { return String(value ?? '').trim() || '—' }
function status(value: unknown) { const labels: Record<string, string> = { PENDING_ASSIGNMENT: '待分配', COMPLETED: '已完成' }; return labels[String(value || '')] || text(value) }
const firstLine = computed(() => detail.value?.lines[0])
const fieldValues = computed<Record<string, string | number>>(() => ({
入库单号: text(detail.value?.planNo), 入仓码: text(firstLine.value?.inboundCode), 类型: '—', 货主: '—', 关联订单: text(detail.value?.sourceOrderId),
海外仓: text(firstLine.value?.destinationWarehouseName), FBA仓库: text(firstLine.value?.fbaWarehouseCode), 总箱数: detail.value?.lines.reduce((sum, line) => sum + line.requiredPackageCount, 0) || 0,
'计费体积(m³)': Number(detail.value?.totalVolumeCbm || 0), 总托盘数: Number(detail.value?.palletCount || 0), 状态: status(detail.value?.status),
预约状态: '—', 预约送仓时间: '—', 预计装车时间: '—', 排车状态: '—', 创建时间: '—', 更新时间: '—', 备注: '—',
}))
const groups = [
{ title: '单据信息', fields: ['入库单号', '入仓码', '类型', '货主', '关联订单'] },
{ title: '仓储信息', fields: ['海外仓', 'FBA仓库', '总箱数', '计费体积(m³)', '总托盘数'] },
{ title: '预约与排车', fields: ['状态', '预约状态', '预约送仓时间', '预计装车时间', '排车状态', '创建时间', '更新时间', '备注'] },
]
const feeColumns: DataTableColumn[] = [
{ key: 'index', label: '序号', width: 70, align: 'center' },
{ key: 'name', label: '费用项目', minWidth: 180 },
{ key: 'currency', label: '币种', width: 100 },
{ key: 'amount', label: '金额', width: 140, align: 'right' },
{ key: 'remark', label: '备注', minWidth: 200 },
]
function back() {
void router.push({ name: 'overseas-inbound-orders' })
}
const feeColumns: DataTableColumn[] = [{ key: 'index', label: '序号', width: 70, align: 'center' }, { key: 'name', label: '费用项目', minWidth: 180 }, { key: 'currency', label: '币种', width: 100 }, { key: 'amount', label: '金额', width: 140, align: 'right' }, { key: 'remark', label: '备注', minWidth: 200 }]
async function load() { if (!planNo.value || !customerId.value) { loadError.value = { message: '详情缺少客户范围,请从入库单列表重新进入' }; return }; loading.value = true; loadError.value = null; try { detail.value = await getInboundPlan(planNo.value, customerId.value) } catch (error) { loadError.value = getApiErrorDetails(error, '入库单详情加载失败,请重试') } finally { loading.value = false } }
function back() { void router.push({ name: 'overseas-inbound-orders' }) }
onMounted(load)
</script>
<template>
<OverseasDetailShell title="入库单详情" reference-label="入库单号" :reference-value="orderId" back-label="返回入库单" domain="入库单详情" @back="back">
<section class="detail-section" aria-label="入库单基本信息">
<div class="detail-groups">
<section v-for="group in groups" :key="group.title" class="detail-group" :aria-label="group.title">
<h2>{{ group.title }}</h2>
<dl class="detail-fields"><div v-for="label in group.fields" :key="label"><dt>{{ label }}</dt><dd>—</dd></div></dl>
</section>
</div>
</section>
<section class="detail-section fee-section" aria-labelledby="inbound-fees-heading">
<h2 id="inbound-fees-heading">应收费用明细</h2>
<DataTable :data="[]" :columns="feeColumns" column-settings-mode="hide" empty-text="海外区域应收费用契约待接入" />
<footer class="fee-total"><span>应收费用合计(USD)</span><strong>—</strong></footer>
</section>
</OverseasDetailShell>
</template>
<template><OverseasDetailShell title="入库单详情" reference-label="入库单号" :reference-value="detail?.planNo || planNo" back-label="返回入库单" domain="入库单详情" :pending="false" :status-text="detail ? status(detail.status) : '加载中'" @back="back">
<LoadErrorState v-if="loadError" title="入库单详情加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
<template v-else><section v-loading="loading" class="detail-section" aria-label="入库单基本信息"><div class="detail-groups"><section v-for="group in groups" :key="group.title" class="detail-group" :aria-label="group.title"><h2>{{ group.title }}</h2><dl class="detail-fields"><div v-for="label in group.fields" :key="label"><dt>{{ label }}</dt><dd>{{ fieldValues[label] }}</dd></div></dl></section></div></section>
<section class="detail-section fee-section" aria-labelledby="inbound-fees-heading"><h2 id="inbound-fees-heading">应收费用明细</h2><DataTable :data="[]" :columns="feeColumns" column-settings-mode="hide" empty-text="当前入库计划详情接口未提供应收费用" /><footer class="fee-total"><span>应收费用合计(USD)</span><strong>—</strong></footer></section></template>
</OverseasDetailShell></template>
<style scoped>
.detail-section { min-width: 0; border: 1px solid var(--cc-border); background: #fff; }
.detail-groups { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.detail-group { min-width: 0; padding: 16px 18px 18px; border-right: 1px solid var(--cc-border); }
.detail-group:last-child { border-right: 0; }
.detail-group h2, .fee-section h2 { margin: 0 0 12px; color: var(--cc-text); font-size: 15px; }
.detail-fields { margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); }
.detail-fields > div { display: grid; min-width: 0; min-height: 46px; grid-template-columns: 120px minmax(0, 1fr); border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }
.detail-fields dt, .detail-fields dd { display: flex; min-width: 0; align-items: center; padding: 8px 10px; }
.detail-fields dt { background: #f8fafc; color: var(--cc-muted); font-size: 12px; }
.detail-fields dd { margin: 0; color: var(--cc-text); overflow-wrap: anywhere; }
.fee-section { overflow: hidden; padding-top: 16px; }
.fee-section h2 { margin-right: 18px; margin-left: 18px; }
.fee-total { display: flex; min-height: 48px; align-items: center; justify-content: flex-end; gap: 40px; padding: 10px 18px; border-top: 1px solid var(--cc-border); color: var(--cc-text); }
.fee-total strong { min-width: 120px; font-size: 16px; }
.detail-section { min-width: 0; border: 1px solid var(--cc-border); background: #fff; }.detail-groups { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }.detail-group { min-width: 0; padding: 16px 18px 18px; border-right: 1px solid var(--cc-border); }.detail-group:last-child { border-right: 0; }.detail-group h2,.fee-section h2 { margin: 0 0 12px; color: var(--cc-text); font-size: 15px; }.detail-fields { margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); }.detail-fields>div { display: grid; min-width: 0; min-height: 46px; grid-template-columns: 120px minmax(0,1fr); border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }.detail-fields dt,.detail-fields dd { display:flex; min-width:0; align-items:center; padding:8px 10px; }.detail-fields dt { background:#f8fafc; color:var(--cc-muted); font-size:12px; }.detail-fields dd { margin:0; color:var(--cc-text); overflow-wrap:anywhere; }.fee-section { overflow:hidden; padding-top:16px; }.fee-section h2 { margin-right:18px; margin-left:18px; }.fee-total { display:flex; min-height:48px; align-items:center; justify-content:flex-end; gap:40px; padding:10px 18px; border-top:1px solid var(--cc-border); }.fee-total strong { min-width:120px; font-size:16px; }
</style>