优化
This commit is contained in:
@@ -30,6 +30,7 @@ export interface InboundOrderSummaryDto {
|
||||
fbaWarehouseCode?: string | null
|
||||
customerName?: string | null
|
||||
packageCount?: number | null
|
||||
pickedPackageCount?: number | null
|
||||
volumeCbm?: number | null
|
||||
palletCount?: number | null
|
||||
status?: string | null
|
||||
|
||||
@@ -51,6 +51,8 @@ export interface InboundPlanDetailDto {
|
||||
lineCount: number
|
||||
palletCount: number
|
||||
totalVolumeCbm: number
|
||||
expectedPackageCount?: number
|
||||
pickedPackageCount?: number
|
||||
lines: Array<{ id: string; orderId: string; inboundCode?: string | null; fbaWarehouseCode?: string | null; destinationWarehouseId?: string | null; destinationWarehouseCode?: string | null; destinationWarehouseName?: string | null; volumeCbm?: number | null; requiredPackageCount: number; plannedPalletCount: number }>
|
||||
pallets: Array<{ id: string; palletNo?: string | null; destinationWarehouseId?: string | null; destinationWarehouseCode?: string | null; destinationWarehouseName?: string | null; capacityCbm?: number | null; allocatedVolumeCbm?: number | null; sequenceNo: number; warehousePalletId?: string | null; warehouseLocationId?: string | null; warehouseResourceLockId?: string | null; assignmentStatus?: string | null; lockedAt?: string | null; actualVolumeCbm?: number | null; actualPackageCount: number }>
|
||||
allocations: Array<{ lineId: string; palletId: string; allocatedVolumeCbm?: number | null; plannedPackageCount: number; countedPackageCount: number; pickedPackageCount: number; actualVolumeCbm?: number | null; status?: string | null; version: number }>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { ArrowRight, GripVertical, Plus, Trash2 } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
@@ -11,7 +11,8 @@ import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
|
||||
import { createDispatchDraftContainer, deleteDispatchDraftContainer, listDispatchCandidates, listDispatchDrafts, lockDispatchDraftPallet, preArrangeDispatch, releaseDispatchDraft, unlockDispatchDraftPallet, type DispatchPalletCandidateDto } from '../api/dispatches'
|
||||
import type { InboundOrderListItem } from '../types'
|
||||
|
||||
interface LoadingContainer { id: string; capacity: number; palletIds: string[] }
|
||||
type VehicleSpec = { value: string; label: string; capacity: number; maxWeightLb: number }
|
||||
interface LoadingContainer { id: string; capacity: number; maxWeightLb: number; vehicleLabel: string; palletIds: string[] }
|
||||
|
||||
const props = defineProps<{ modelValue: boolean; orders: InboundOrderListItem[] }>()
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: boolean]; created: [] }>()
|
||||
@@ -27,6 +28,8 @@ const containers = ref<LoadingContainer[]>([])
|
||||
const busyPalletIds = ref<string[]>([])
|
||||
const draftToken = ref('')
|
||||
const finalized = ref(false)
|
||||
const vehicleDialogOpen = ref(false)
|
||||
const selectedVehicle = ref('')
|
||||
const query = reactive({ orderNo: '', orderType: '', sourceStage: 'RECEIVED' as '' | 'PLAN' | 'RECEIVED', fbaWarehouseCode: '' })
|
||||
const warehouseTab = ref<'ALL' | 'LAX' | 'NYC'>('ALL')
|
||||
|
||||
@@ -61,6 +64,15 @@ const assignedIds = computed(() => containers.value.flatMap((item) => item.palle
|
||||
const assigned = computed(() => assignedIds.value.map((id) => palletSnapshot(id)).filter((item): item is DispatchPalletCandidateDto => Boolean(item)))
|
||||
const assignedPackages = computed(() => assigned.value.reduce((sum, item) => sum + Number(item.packageCount || 0), 0))
|
||||
const assignedVolume = computed(() => assigned.value.reduce((sum, item) => sum + Number(item.volumeCbm || 0), 0))
|
||||
const vehicleSpecs: VehicleSpec[] = [
|
||||
{ value: 'LARGE_30', label: '大车(30托)', capacity: 30, maxWeightLb: 40000 },
|
||||
{ value: 'SMALL_15', label: '小车(15托)', capacity: 15, maxWeightLb: 10000 },
|
||||
{ value: 'SMALL_14', label: '小车(14托)', capacity: 14, maxWeightLb: 10000 },
|
||||
{ value: 'SMALL_12', label: '小车(12托)', capacity: 12, maxWeightLb: 10000 },
|
||||
]
|
||||
const selectedVehicleSpec = computed(() => vehicleSpecs.find((item) => item.value === selectedVehicle.value))
|
||||
const weightLb = (item?: DispatchPalletCandidateDto) => Number(item?.volumeWeightKg || 0) * 2.2046226218
|
||||
const containerWeightLb = (container: LoadingContainer) => container.palletIds.reduce((sum, id) => sum + weightLb(palletSnapshot(id)), 0)
|
||||
|
||||
function palletId(item: DispatchPalletCandidateDto) { return String(item.planPalletId) }
|
||||
function text(value: unknown) { const normalized = String(value ?? '').trim(); return normalized || '—' }
|
||||
@@ -96,15 +108,15 @@ async function load() {
|
||||
}
|
||||
function reset() { query.orderNo = ''; query.orderType = ''; query.sourceStage = 'RECEIVED'; query.fbaWarehouseCode = ''; warehouseTab.value = 'ALL'; void load() }
|
||||
|
||||
async function addContainer() {
|
||||
function addContainer() { selectedVehicle.value = ''; vehicleDialogOpen.value = true }
|
||||
async function confirmAddContainer() {
|
||||
const spec = selectedVehicleSpec.value
|
||||
if (!spec) { ElMessage.warning('请选择车型'); return }
|
||||
try {
|
||||
const result = await ElMessageBox.prompt('请输入该装车托盘容器可装的托数', '添加装车托盘', {
|
||||
confirmButtonText: '确定', cancelButtonText: '取消', inputValue: '20', inputType: 'number',
|
||||
inputValidator: (value) => Number.isInteger(Number(value)) && Number(value) > 0 && Number(value) <= 500 ? true : '请输入 1 至 500 的整数',
|
||||
})
|
||||
const created = await createDispatchDraftContainer(ensureDraftToken(), Number(result.value))
|
||||
containers.value.push({ id: created.id, capacity: created.capacity, palletIds: created.palletIds || [] })
|
||||
} catch { /* user cancelled */ }
|
||||
const created = await createDispatchDraftContainer(ensureDraftToken(), spec.capacity)
|
||||
containers.value.push({ id: created.id, capacity: created.capacity, maxWeightLb: spec.maxWeightLb, vehicleLabel: spec.label, palletIds: created.palletIds || [] })
|
||||
vehicleDialogOpen.value = false
|
||||
} catch (error) { mutationError.value = getApiErrorDetails(error, '添加装车车型失败,请重试') }
|
||||
}
|
||||
|
||||
async function movePallet(item: DispatchPalletCandidateDto, containerId: string) {
|
||||
@@ -113,6 +125,9 @@ async function movePallet(item: DispatchPalletCandidateDto, containerId: string)
|
||||
if (!target || target.palletIds.includes(id)) return
|
||||
if (containerFor(id)) { ElMessage.warning('该托盘已在其他装车托盘中'); return }
|
||||
if (target.palletIds.length >= target.capacity) { ElMessage.warning('该装车托盘已达到设置的托数'); return }
|
||||
const nextWeight = containerWeightLb(target) + weightLb(item)
|
||||
if (nextWeight > target.maxWeightLb * 1.1) { ElMessage.warning(`${target.vehicleLabel}重量将达到 ${nextWeight.toFixed(0)}LB,超过限重10%,无法装入`); return }
|
||||
if (nextWeight > target.maxWeightLb) ElMessage.warning(`${target.vehicleLabel}当前重量 ${nextWeight.toFixed(0)}LB,已超过额定限重 ${target.maxWeightLb}LB`)
|
||||
const anchor = assigned.value[0]
|
||||
if (anchor?.customerId && anchor.customerId !== item.customerId) { ElMessage.warning('一次排车只能装同一客户的托盘'); return }
|
||||
if (anchor?.overseasWarehouseId && anchor.overseasWarehouseId !== item.overseasWarehouseId) { ElMessage.warning('一次排车只能装同一海外仓的托盘'); return }
|
||||
@@ -167,7 +182,7 @@ watch(() => props.modelValue, (open) => {
|
||||
if (!open) return
|
||||
draftToken.value = makeDraftToken(); finalized.value = false; containers.value = []; palletSnapshots.value = {}
|
||||
query.orderNo = ''; query.orderType = ''; query.sourceStage = 'RECEIVED'; query.fbaWarehouseCode = ''; warehouseTab.value = 'ALL'; mutationError.value = null
|
||||
void (async () => { const drafts = await listDispatchDrafts().catch(() => []); const draft = drafts[0]; if (draft?.draftToken) { draftToken.value = draft.draftToken; containers.value = (draft.containers || []).map((c) => ({ id: c.id, capacity: c.capacity, palletIds: c.palletIds || [] })); await hydrateDraftPallets().catch(() => undefined) } await load() })()
|
||||
void (async () => { const drafts = await listDispatchDrafts().catch(() => []); const draft = drafts[0]; if (draft?.draftToken) { draftToken.value = draft.draftToken; containers.value = (draft.containers || []).map((c) => { const spec = vehicleSpecs.find((item) => item.capacity === c.capacity) || vehicleSpecs[0]!; return { id: c.id, capacity: c.capacity, maxWeightLb: spec.maxWeightLb, vehicleLabel: spec.label, palletIds: c.palletIds || [] } }); await hydrateDraftPallets().catch(() => undefined) } await load() })()
|
||||
}, { immediate: true })
|
||||
|
||||
watch([warehouseTab, () => query.orderType], () => {
|
||||
@@ -198,7 +213,7 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
<el-table :data="orderGroups" border height="520" highlight-current-row row-key="key" @current-change="activeOrderKey = $event?.key || ''">
|
||||
<el-table-column label="订单号" min-width="150"><template #default="{ row }">{{ text(row.first.orderNo) }}</template></el-table-column>
|
||||
<el-table-column label="订单类型" width="100"><template #default="{ row }">{{ orderType(row.first.orderType) }}</template></el-table-column>
|
||||
<el-table-column label="重量(kg)" width="100" align="right"><template #default="{ row }">{{ number(row.first.volumeWeightKg, 2) }}</template></el-table-column>
|
||||
<el-table-column label="重量(lb)" width="100" align="right"><template #default="{ row }">{{ number(Number(row.first.volumeWeightKg || 0) * 2.2046226218, 0) }}LB</template></el-table-column>
|
||||
<el-table-column label="托盘数量" width="90" align="right"><template #default="{ row }">{{ row.pallets.length }}</template></el-table-column>
|
||||
<el-table-column label="目的地FBA" width="110"><template #default="{ row }">{{ text(row.first.fbaWarehouseCode) }}</template></el-table-column>
|
||||
<el-table-column label="体积(m³)" width="100" align="right"><template #default="{ row }">{{ number(row.pallets.reduce((sum: number, item: DispatchPalletCandidateDto) => sum + Number(item.volumeCbm || 0), 0), 2) }}</template></el-table-column>
|
||||
@@ -214,7 +229,7 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
<div class="pallet-list">
|
||||
<div v-for="item in activePallets" :key="palletId(item)" class="pallet-row" :class="{ assigned: containerFor(palletId(item)) }" draggable="true" @dragstart="dragStart($event, item)">
|
||||
<GripVertical :size="16" class="drag-handle" />
|
||||
<div class="pallet-copy"><strong>{{ text(item.palletNo) }}</strong><span>{{ Number(item.packageCount || 0) }}箱 · {{ number(item.volumeCbm, 2) }}m³</span><div class="carton-marks"><el-tag v-for="mark in cartonMarks(item).slice(0, 4)" :key="mark" size="small" effect="plain">{{ mark }}</el-tag><span v-if="cartonMarks(item).length > 4">+{{ cartonMarks(item).length - 4 }}</span><span v-if="!cartonMarks(item).length">暂无箱号</span></div></div>
|
||||
<div class="pallet-copy"><strong>{{ text(item.palletNo) }}</strong><span>{{ Number(item.packageCount || 0) }}箱 · {{ number(weightLb(item), 0) }}LB</span><div class="carton-marks"><el-tag v-for="mark in cartonMarks(item).slice(0, 4)" :key="mark" size="small" effect="plain">{{ mark }}</el-tag><span v-if="cartonMarks(item).length > 4">+{{ cartonMarks(item).length - 4 }}</span><span v-if="!cartonMarks(item).length">暂无箱号</span></div></div>
|
||||
<el-tag v-if="containerFor(palletId(item))" type="success" size="small">已锁定</el-tag>
|
||||
<el-dropdown v-else trigger="click" :disabled="!containers.length || busyPalletIds.includes(palletId(item))" @command="movePallet(item, String($event))">
|
||||
<el-button text type="primary" :loading="busyPalletIds.includes(palletId(item))">移入 <ArrowRight :size="14" /></el-button>
|
||||
@@ -229,7 +244,7 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
<header><div><h3>装车托盘</h3><p>托盘 {{ assigned.length }} · 箱数 {{ assignedPackages }} · 立方 {{ assignedVolume.toFixed(2) }}</p></div><el-button type="primary" plain @click="addContainer"><Plus :size="15" />添加装车托盘</el-button></header>
|
||||
<div class="container-list">
|
||||
<div v-for="(container, index) in containers" :key="container.id" class="loading-container" @dragover.prevent @drop.prevent="dropInto($event, container.id)">
|
||||
<div class="container-head"><strong>装车托盘 {{ index + 1 }}</strong><span>托数 <b>{{ container.palletIds.length }}</b>/{{ container.capacity }}</span><el-button text type="danger" :aria-label="`删除装车托盘${index + 1}`" @click="deleteContainer(container)"><Trash2 :size="15" /></el-button></div>
|
||||
<div class="container-head"><strong>{{ container.vehicleLabel }}</strong><span>托数 <b>{{ container.palletIds.length }}</b>/{{ container.capacity }} · 重量 <b>{{ number(containerWeightLb(container), 0) }}LB</b>/{{ container.maxWeightLb }}LB</span><el-button text type="danger" :aria-label="`删除装车车型${index + 1}`" @click="deleteContainer(container)"><Trash2 :size="15" /></el-button></div>
|
||||
<div class="container-body">
|
||||
<div v-for="id in container.palletIds" :key="id" class="loaded-pallet"><span><strong>{{ text(palletSnapshot(id)?.palletNo || id) }}</strong>{{ text(palletSnapshot(id)?.orderNo) }}</span><el-button text type="danger" :loading="busyPalletIds.includes(id)" @click="removePallet(id)">移除</el-button></div>
|
||||
<span v-if="!container.palletIds.length" class="drop-hint">拖入托盘,移入后立即锁定</span>
|
||||
@@ -240,8 +255,14 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<footer class="dialog-footer"><span>托盘移入即锁定,移除或关闭弹窗即解锁。</span><div><el-button :disabled="saving" @click="close(false)">取消</el-button><el-button type="primary" :loading="saving" :disabled="!assignedIds.length" @click="submit">预排车</el-button></div></footer>
|
||||
<footer class="dialog-footer"><span>托盘移入即锁定,重量按 1kg = 2.2046lb 换算。</span><div><el-button :disabled="saving" @click="close(false)">取消</el-button><el-button type="primary" :loading="saving" :disabled="!assignedIds.length" @click="submit">预排车</el-button></div></footer>
|
||||
</AppDialog>
|
||||
<el-dialog v-model="vehicleDialogOpen" title="选择车型" width="360px" append-to-body>
|
||||
<el-radio-group v-model="selectedVehicle" class="vehicle-options">
|
||||
<el-radio v-for="spec in vehicleSpecs" :key="spec.value" :value="spec.value">{{ spec.label }} · 限重 {{ spec.maxWeightLb }}LB</el-radio>
|
||||
</el-radio-group>
|
||||
<template #footer><el-button @click="vehicleDialogOpen = false">取消</el-button><el-button type="primary" @click="confirmAddContainer">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -275,5 +296,6 @@ h3, p { margin: 0; } h3 { font-size: 16px; } h3 span, header > span, p { color:
|
||||
.loaded-pallet > span { display: flex; min-width: 0; flex-direction: column; color: var(--cc-muted); font-size: 12px; }.loaded-pallet strong { color: var(--cc-text); font-size: 13px; }
|
||||
.dialog-footer { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 16px -20px -20px; padding: 14px 20px; border-top: 1px solid var(--cc-border); background: #fafbfc; }
|
||||
.dialog-footer > span { color: var(--cc-muted); font-size: 12px; }
|
||||
.vehicle-options { display: grid; gap: 14px; }
|
||||
@media (max-width: 1280px) { .dispatch-workbench { grid-template-columns: minmax(500px, 1.3fr) minmax(320px, 1fr); }.container-pane { grid-column: 1 / -1; border-left: 0; padding-left: 0; }.container-list { height: 300px; } }
|
||||
</style>
|
||||
|
||||
@@ -106,7 +106,7 @@ const relatedOrders = computed<RelatedOrderRow[]>(() => {
|
||||
|
||||
const orderColumns: DataTableColumn[] = [
|
||||
{ key: 'orderNo', prop: 'orderNo', label: '订单号', minWidth: 210 },
|
||||
{ key: 'weight', prop: 'weight', label: '重量(kg)', minWidth: 130, align: 'right' },
|
||||
{ key: 'weight', prop: 'weight', label: '重量(lb)', minWidth: 130, align: 'right' },
|
||||
{ key: 'palletCount', prop: 'palletCount', label: '托数', minWidth: 100, align: 'right' },
|
||||
{ key: 'fbaWarehouse', prop: 'fbaWarehouse', label: '目的FBA仓', minWidth: 150 },
|
||||
{ key: 'volume', prop: 'volume', label: '体积(m³)', minWidth: 130, align: 'right' },
|
||||
|
||||
@@ -13,20 +13,36 @@ 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) }
|
||||
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(detail.value?.sourceOrderId),
|
||||
海外仓: text(firstLine.value?.destinationWarehouseName), FBA仓库: text(firstLine.value?.fbaWarehouseCode), 总箱数: detail.value?.lines.reduce((sum, line) => sum + line.requiredPackageCount, 0) || 0,
|
||||
海外仓: 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: ['海外仓', '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)
|
||||
@@ -35,9 +51,11 @@ onMounted(load)
|
||||
<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(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; }
|
||||
.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; }
|
||||
</style>
|
||||
|
||||
@@ -34,7 +34,7 @@ const filters = ref<QueryModel>({ orderNo: '', inboundCode: '', status: '', ware
|
||||
const fields = computed<QueryField[]>(() => [
|
||||
{ key: 'orderNo', type: 'input', label: '入库单号', width: 165, placeholder: '入库单号' },
|
||||
{ key: 'inboundCode', type: 'input', label: '入仓码', width: 145, placeholder: '入仓码' },
|
||||
{ key: 'status', type: 'select', label: '状态', width: 130, placeholder: '全部状态', options: [{ label: '待分配', value: 'PENDING_ASSIGNMENT' }, { label: '入库中', value: 'IN_PROGRESS' }, { label: '已完成', value: 'COMPLETED' }] },
|
||||
{ key: 'status', type: 'select', label: '状态', width: 130, placeholder: '全部状态', options: [{ label: '待入库', value: 'PENDING_ASSIGNMENT' }, { label: '拣货中', value: 'PICKING' }, { label: '全部入库', value: 'COMPLETED' }, { label: '部分入库', value: 'PARTIAL_INBOUND' }] },
|
||||
{ key: 'warehouse', type: 'input', label: '海外仓', width: 155, placeholder: '全部海外仓' },
|
||||
{ key: 'fbaWarehouse', type: 'input', label: 'FBA仓库', width: 145, placeholder: '全部FBA仓库' },
|
||||
{ key: 'appointmentStatus', type: 'select', label: '预约状态', width: 145, placeholder: '全部预约状态', options: [{ label: '未预约', value: 'UNAPPOINTED' }, { label: '预约中', value: 'PENDING' }, { label: '预约成功', value: 'SUCCESS' }, { label: '预约失败', value: 'FAILED' }, { label: '已取消', value: 'CANCELLED' }] },
|
||||
@@ -69,7 +69,8 @@ const columns: DataTableColumn[] = [
|
||||
{ key: 'warehouse', prop: 'warehouse', label: '海外仓', minWidth: 170 },
|
||||
{ key: 'fbaWarehouse', prop: 'fbaWarehouse', label: 'FBA仓库', minWidth: 130 },
|
||||
{ key: 'owner', prop: 'owner', label: '货主', minWidth: 150 },
|
||||
{ key: 'cartons', prop: 'cartons', label: '总箱数', width: 90, align: 'right' },
|
||||
{ key: 'cartons', prop: 'cartons', label: '应拣货数', width: 100, align: 'right' },
|
||||
{ key: 'pickedCartons', prop: 'pickedCartons', label: '实际拣货数', width: 110, align: 'right' },
|
||||
{ key: 'volume', prop: 'volume', label: '计费体积(m³)', width: 125, align: 'right' },
|
||||
{ key: 'pallets', prop: 'pallets', label: '总托盘数', width: 100, align: 'right' },
|
||||
{ key: 'status', prop: 'status', label: '状态', width: 100 },
|
||||
@@ -81,7 +82,7 @@ const columns: DataTableColumn[] = [
|
||||
function text(value: unknown) { const normalized = String(value ?? '').trim(); return normalized || '—' }
|
||||
function number(value: unknown) { const normalized = Number(value); return Number.isFinite(normalized) ? normalized : 0 }
|
||||
function typeLabel(value: unknown) { return lastMileOrderTypeLabel(value) }
|
||||
function planStatusLabel(value: unknown) { return ({ PENDING_ASSIGNMENT: '待分配', IN_PROGRESS: '入库中', COMPLETED: '已完成' } as Record<string, string>)[String(value || '')] || text(value) }
|
||||
function planStatusLabel(value: unknown) { return ({ PENDING_ASSIGNMENT: '待入库', ASSIGNED: '待入库', PDA_PENDING: '待入库', PICKING: '拣货中', COMPLETED: '全部入库', PARTIAL_INBOUND: '部分入库' } as Record<string, string>)[String(value || '')] || text(value) }
|
||||
function appointmentStatusLabel(value: unknown) { return !value ? '未预约' : ({ PENDING: '预约中', SUCCESS: '预约成功', FAILED: '预约失败', CANCELLED: '已取消' } as Record<string, string>)[String(value)] || text(value) }
|
||||
|
||||
function toListItem(dto: InboundOrderSummaryDto): InboundOrderListItem {
|
||||
@@ -89,7 +90,7 @@ function toListItem(dto: InboundOrderSummaryDto): InboundOrderListItem {
|
||||
lineId: String(dto.lineId), planId: String(dto.planId), customerId: String(dto.customerId), orderId: String(dto.orderId),
|
||||
warehouseId: text(dto.warehouseId) === '—' ? '' : String(dto.warehouseId), destinationWarehouseCode: text(dto.warehouseCode),
|
||||
orderNo: text(dto.planNo), inboundCode: text(dto.inboundCode), type: typeLabel(dto.orderType), warehouse: text(dto.warehouseName || dto.warehouseCode),
|
||||
fbaWarehouse: text(dto.fbaWarehouseCode), owner: text(dto.customerName), cartons: number(dto.packageCount), volume: number(dto.volumeCbm), pallets: number(dto.palletCount),
|
||||
fbaWarehouse: text(dto.fbaWarehouseCode), owner: text(dto.customerName), cartons: number(dto.packageCount), pickedCartons: number(dto.pickedPackageCount), volume: number(dto.volumeCbm), pallets: number(dto.palletCount),
|
||||
status: planStatusLabel(dto.status), appointmentNo: text(dto.appointmentNo), appointmentStatus: appointmentStatusLabel(dto.appointmentStatus),
|
||||
appointmentAt: text(dto.appointmentAt), expectedLoadingAt: text(dto.expectedLoadingAt), dispatchStatus: dto.dispatchStatus === 'SCHEDULED' ? '已排车' : '未排车',
|
||||
createdAt: formatOverseasDateTime(dto.createdAt),
|
||||
|
||||
@@ -325,7 +325,7 @@ describe('overseas warehouse first-batch prototype contract', () => {
|
||||
'createdAt', 'internalRemark', 'customerRemark', 'warehouseRemark',
|
||||
])
|
||||
expect(columnKeys('InboundOrdersView.vue')).toEqual([
|
||||
'actions', 'selection', 'orderNo', 'inboundCode', 'type', 'warehouse', 'fbaWarehouse', 'owner', 'cartons', 'volume',
|
||||
'actions', 'selection', 'orderNo', 'inboundCode', 'type', 'warehouse', 'fbaWarehouse', 'owner', 'cartons', 'pickedCartons', 'volume',
|
||||
'pallets', 'status', 'appointmentStatus', 'dispatchStatus', 'createdAt',
|
||||
])
|
||||
expect(columnKeys('InventoryView.vue')).toEqual([
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface InboundOrderListItem {
|
||||
fbaWarehouse: string
|
||||
owner: string
|
||||
cartons: number
|
||||
pickedCartons: number
|
||||
volume: number
|
||||
pallets: number
|
||||
status: string
|
||||
|
||||
Reference in New Issue
Block a user