Files
admin-web/src/modules/overseas-warehouse/pages/InboundOrderDetailView.vue
T

63 lines
8.1 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 { 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 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: '待入库', PDA_PENDING: '待入库', ASSIGNED: '待入库', PICKING: '拣货中', PARTIAL_INBOUND: '部分入库', 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(firstLine.value?.destinationWarehouseName), FBA仓库: text(firstLine.value?.fbaWarehouseCode), 总箱数: detail.value?.expectedPackageCount ?? detail.value?.lines.reduce((sum, line) => sum + line.requiredPackageCount, 0) ?? 0,
应拣货数: detail.value?.expectedPackageCount ?? detail.value?.lines.reduce((sum, line) => sum + line.requiredPackageCount, 0) ?? 0, 实际拣货数: detail.value?.pickedPackageCount ?? detail.value?.allocations.reduce((sum, item) => sum + item.pickedPackageCount, 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 }]
const palletColumns: DataTableColumn[] = [
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 150 },
{ key: 'palletNo', prop: 'palletNo', label: '托盘号', minWidth: 150 },
{ key: 'packageCount', prop: 'packageCount', label: '对应箱数', width: 110, align: 'right' },
]
const palletRows = computed(() => (detail.value?.allocations || []).map((allocation, index) => {
const line = detail.value?.lines.find(item => item.id === allocation.lineId)
const pallet = detail.value?.pallets.find(item => item.id === allocation.palletId)
return {
id: `${allocation.lineId}-${allocation.palletId}-${index}`,
inboundCode: text(line?.inboundCode),
palletNo: text(pallet?.palletNo),
packageCount: Number(allocation.pickedPackageCount ?? allocation.countedPackageCount ?? allocation.plannedPackageCount ?? 0),
}
}))
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="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 pallet-section" aria-labelledby="inbound-pallet-heading"><h2 id="inbound-pallet-heading">入库单托盘明细</h2><p class="pallet-note">PDA 合托后,参与合托的入库单会分别展示同一托盘及本单对应箱数。</p><DataTable :data="palletRows" :columns="palletColumns" column-settings-mode="hide" row-key="id" empty-text="暂无托盘明细" /></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(2, minmax(0, 1fr)); }.detail-group { min-width: 0; padding: 16px 18px 18px; border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }.detail-group:nth-child(2) { border-right: 0; }.detail-group:last-child { grid-column: 1 / -1; border-right: 0; border-bottom: 0; }.detail-group h2,.fee-section h2 { margin: 0 0 12px; color: var(--cc-text); font-size: 15px; }.detail-fields { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); }.detail-group:last-child .detail-fields{grid-template-columns:repeat(4,minmax(0,1fr))}.detail-fields>div { display: grid; min-width: 0; min-height: 46px; grid-template-columns: 110px 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; }
.pallet-section { overflow: hidden; padding-top: 16px; }.pallet-section h2 { margin: 0 18px 4px; color: var(--cc-text); font-size: 15px; }.pallet-note { margin: 0 18px 12px; color: var(--cc-muted); font-size: 12px; }
@media(max-width:1200px){.detail-groups,.detail-fields,.detail-group:last-child .detail-fields{grid-template-columns:1fr}.detail-group:last-child{grid-column:auto}.detail-group{border-right:0}}
</style>