2026-08-19 16:45:51 +08:00
< script setup lang = "ts" >
2026-09-15 09:58:15 +08:00
import '@/styles/adminDensity.css'
2026-10-10 02:19:39 +08:00
import { computed , onActivated , onBeforeUnmount , ref , watch } from 'vue'
2026-08-19 16:45:51 +08:00
import { useRoute , useRouter } from 'vue-router'
2026-10-10 02:19:39 +08:00
import { ArrowLeft } from '@lucide/vue'
import { useAuthStore } from '@/core/auth/auth'
2026-09-05 22:26:37 +08:00
import { useAccessPreviewStore } from '@/core/access/accessPreview'
2026-08-19 16:45:51 +08:00
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'
2026-09-15 09:58:15 +08:00
import StatusBadge from '@/shared/components/StatusBadge.vue'
2026-08-19 16:45:51 +08:00
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
2026-08-20 09:10:36 +08:00
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
2026-10-10 02:19:39 +08:00
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
2026-08-20 09:10:36 +08:00
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
2026-10-10 02:19:39 +08:00
import { getOverseasPurchase } from '../api/platformOverseasBusiness'
import { downloadProcurementFile } from '../api/procurementCommands'
import ProcurementNodeActions from '../components/ProcurementNodeActions.vue'
import { remainingLabels , decisionLabels } from '../procurementControlForm'
2026-09-05 22:26:37 +08:00
import { overseasBusinessPermissions } from '../permissions'
2026-10-10 02:19:39 +08:00
import { statusLabel , money , dateTime , text , measurement , feeStatus , feePurposes } from '../procurementPresentation'
import type { PlatformProcurement , ProcurementFile , ProcurementFee , ProcurementReceipt , ProcurementCarton , ProcurementShipment } from '../procurementTypes'
2026-08-19 16:45:51 +08:00
2026-10-10 02:19:39 +08:00
const route = useRoute (); const router = useRouter (); const auth = useAuthStore (); const access = useAccessPreviewStore ()
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 : '' )
2026-10-10 02:19:39 +08:00
const detail = ref < PlatformProcurement | null >( null ); const loading = ref ( false )
const loadError = ref < ApiErrorDetails | null >( null ); const operationError = ref < ApiErrorDetails | null >( null )
const downloading = ref < string | null >( null )
const order = computed (() => detail . value ? . customerView . order )
const fields = computed (() => [
[ '采购品类' , order . value ? . purchaseCategory ], [ '商品名称' , order . value ? . productName ], [ '商品规格' , order . value ? . productSpecification ], [ '采购数量' , order . value ? . purchaseQuantity ],
[ '预算单价' , money ( order . value ? . budgetUnitPrice , order . value ? . currency )], [ '有效预算总额' , money ( detail . value ? . customerView . budget . totalAmount , detail . value ? . customerView . budget . currency )],
[ '预计到海外仓' , dateTime ( order . value ? . estimatedArrivalAt )], [ '实际收货仓' , order . value ? . actualWarehouseName ],
[ '收件人' , order . value ? . contactName ], [ '联系电话' , order . value ? . contactPhone ], [ '下单收货地址' , order . value ? . receivingAddress ], [ '内部备注' , detail . value ? . internalRemark ],
] as const )
const purchaseFields = computed (() => [
[ '实际购买时间' , dateTime ( detail . value ? . purchaseFacts . purchased_at )], [ '实购商品' , detail . value ? . purchaseFacts . purchased_product_name ],
[ '实购数量' , detail . value ? . purchaseFacts . purchased_quantity ], [ '原登记实付总额' , money ( detail . value ? . purchaseFacts . purchase_amount , detail . value ? . purchaseFacts . purchase_currency )],
[ '货款核定时间' , dateTime ( detail . value ? . purchaseFacts . purchase_confirmed_at )], [ '待处理事项' , detail . value ? . purchaseFacts . purchase_issue ],
] as const )
const receiptColumns : DataTableColumn [] = [
{ key : 'receiptNo' , prop : 'receiptNo' , label : '实收编号' , minWidth : 180 },
{ key : 'leg' , label : '收货段' , width : 100 , formatter : ( row : ProcurementReceipt ) => row . leg === 'OVERSEAS' ? '海外' : '国内' },
{ key : 'source' , label : '来源箱' , minWidth : 160 , formatter : ( row : ProcurementReceipt ) => detail . value ? . physical . cartons . find ( box => box . id === row . sourceCartonId ) ? . cartonNo || '—' },
{ key : 'goodQuantity' , prop : 'goodQuantity' , label : '合格件数' , width : 100 }, { key : 'damagedQuantity' , prop : 'damagedQuantity' , label : '破损件数' , width : 100 }, { key : 'extraQuantity' , prop : 'extraQuantity' , label : '多货件数' , width : 100 },
{ key : 'receivedAt' , label : '实收时间' , minWidth : 160 , formatter : ( row : ProcurementReceipt ) => dateTime ( row . receivedAt ) },
{ key : 'active' , label : '记录状态' , width : 100 , formatter : ( row : ProcurementReceipt ) => row . active ? '有效' : '已更正' },
2026-08-19 16:45:51 +08:00
]
2026-10-10 02:19:39 +08:00
const dispositionColumns : DataTableColumn [] = [
{ key : 'leg' , label : '发生段' , width : 100 , formatter : ( row : PlatformProcurement [ 'physical' ][ 'dispositions' ][ number ]) => row . leg === 'OVERSEAS' ? '海外' : '国内' },
{ key : 'source' , label : '实收或原箱来源' , minWidth : 180 , formatter : ( row : PlatformProcurement [ 'physical' ][ 'dispositions' ][ number ]) => ( row . receipt_id ? detail . value ? . physical . receipts . find ( receipt => receipt . id === row . receipt_id ) ? . receiptNo : row . source_carton_id ? detail . value ? . physical . cartons . find ( box => box . id === row . source_carton_id ) ? . cartonNo : order . value ? . orderNo ) || '—' },
{ key : 'kind' , label : '差异' , width : 100 , formatter : ( row : PlatformProcurement [ 'physical' ][ 'dispositions' ][ number ]) => ({ SHORTAGE : '短少' , DAMAGED : '破损' , EXTRA : '多货' })[ row . kind ] },
{ key : 'quantity' , prop : 'quantity' , label : '处置件数' , width : 100 }, { key : 'reason' , prop : 'reason' , label : '处置依据' , minWidth : 220 },
{ key : 'active' , label : '状态' , width : 110 , formatter : ( row : PlatformProcurement [ 'physical' ][ 'dispositions' ][ number ]) => row . active ? '有效' : '已撤销或更正' },
]
const cartonColumns : DataTableColumn [] = [
{ key : 'cartonNo' , prop : 'cartonNo' , label : '箱号' , minWidth : 160 }, { key : 'quantity' , prop : 'quantity' , label : '件数' , width : 85 },
{ key : 'measurements' , label : '长 × 宽 × 高(cm) ' , minWidth : 220 , formatter : ( row : ProcurementCarton ) => [ row . lengthCm , row . widthCm , row . heightCm ]. map ( value => measurement ( value , 2 )). join ( ' × ' ) },
{ key : 'weight' , label : '毛重(kg) ' , width : 120 , formatter : ( row : ProcurementCarton ) => measurement ( row . grossWeightKg , 3 ) },
{ key : 'sources' , label : '装货来源' , minWidth : 240 , formatter : ( row : ProcurementCarton ) => row . sources . map ( source => ` ${ detail . value ? . physical . receipts . find ( receipt => receipt . id === source . receiptId ) ? . receiptNo || '未关联实收' } : ${ source . quantity } 件` ). join ( '; ' ) },
]
const shipmentColumns : DataTableColumn [] = [
{ key : 'shipmentNo' , prop : 'shipmentNo' , label : '交寄编号' , minWidth : 180 }, { key : 'leg' , label : '运输段' , width : 110 , formatter : ( row : ProcurementShipment ) => row . leg === 'OVERSEAS' ? '国际交寄' : '寄客户' },
{ key : 'cartonNo' , prop : 'cartonNo' , label : '箱号' , minWidth : 150 }, { key : 'quantity' , prop : 'quantity' , label : '件数' , width : 85 }, { key : 'carrier' , prop : 'carrier' , label : '承运商' , minWidth : 100 }, { key : 'trackingNo' , prop : 'trackingNo' , label : '实际运单' , minWidth : 180 },
{ key : 'shippedAt' , label : '交寄时间' , minWidth : 160 , formatter : ( row : ProcurementShipment ) => dateTime ( row . shippedAt ) },
]
const feeColumns : DataTableColumn [] = [
{ key : 'feeNo' , prop : 'feeNo' , label : '费用编号' , minWidth : 170 }, { key : 'purpose' , label : '用途' , minWidth : 140 , formatter : ( row : ProcurementFee ) => feePurposes [ row . purpose ] || '待核对' },
{ key : 'name' , prop : 'name' , label : '费用项目' , minWidth : 120 }, { key : 'original' , label : '原币金额' , minWidth : 140 , formatter : ( row : ProcurementFee ) => money ( row . originalAmount , row . originalCurrency ) },
{ key : 'amount' , label : '人民币金额' , minWidth : 140 , formatter : ( row : ProcurementFee ) => money ( row . amount , row . currency ) },
{ key : 'rate' , label : '汇率 / 月份' , minWidth : 150 , formatter : ( row : ProcurementFee ) => ` ${ text ( row . exchangeRate ) } / ${ text ( row . exchangeRateMonth ) } ` },
{ key : 'walletStatus' , label : '资金状态' , minWidth : 150 , formatter : ( row : ProcurementFee ) => feeStatus ( row ) },
{ key : 'sourceLinked' , label : '费用来源关联' , width : 130 , formatter : ( row : ProcurementFee ) => row . purpose === 'INTERNATIONAL_ESTIMATE' ? '预估不入池' : row . purpose === 'REFUND' ? '退款不入应收' : [ 'ZERO' , 'NOT_APPLICABLE' ]. includes ( row . determination ) ? '无需入池' : row . sourceLinked ? '已完成' : '待完成' },
{ key : 'active' , label : '版本状态' , width : 100 , formatter : ( row : ProcurementFee ) => row . active ? '有效' : '历史版本' },
]
const labelGroups = computed (() => detail . value ? [
{ leg : 'OVERSEAS' , title : '国际段' , value : detail . value . transport . overseasLabels },
{ leg : 'DOMESTIC' , title : '寄客户段' , value : detail . value . transport . domesticLabels },
]. filter ( group => group . value ) : [])
const labelColumns : DataTableColumn [] = [
{ key : 'cartonId' , label : '原箱' , minWidth : 150 , formatter : ( row : { cartonId : string }) => detail . value ? . physical . cartons . find ( box => box . id === row . cartonId ) ? . cartonNo || '未关联箱' },
{ key : 'fileId' , label : '面单文件' , minWidth : 200 , formatter : ( row : { fileId : string }) => detail . value ? . files . find ( file => file . fileId === row . fileId ) ? . fileName || '未关联文件' },
{ key : 'page' , prop : 'page' , label : '页码' , width : 75 }, { key : 'carrier' , prop : 'carrier' , label : '承运商' , minWidth : 120 }, { key : 'trackingNo' , prop : 'trackingNo' , label : '运单号' , minWidth : 180 },
{ key : 'appliedAt' , label : '实际贴单时间' , minWidth : 160 , formatter : ( row : { appliedAt : string | null }) => dateTime ( row . appliedAt ) },
]
const fileLabels : Record < string , string > = { PURCHASE : '购买凭证' , BUDGET : '预算授权' , LABEL : '快递面单' , EXCEPTION : '异常证据' , RECEIPT : '收货凭证' , VOUCHER : '费用凭证' }
const noteColumns : DataTableColumn [] = [
{ key : 'note_no' , prop : 'note_no' , label : '说明编号' , minWidth : 180 },
{ key : 'target' , label : '关联事实' , minWidth : 180 , formatter : ( row : PlatformProcurement [ 'controls' ][ 'notes' ][ number ]) => row . target_type === 'PURCHASE' ? order . value ? . orderNo || '—' : row . target_type === 'WAREHOUSE' ? row . original_value_json ? . name || '实际仓记录' : row . target_type === 'RECEIPT' ? detail . value ? . physical . receipts . find ( item => item . id === row . target_id ) ? . receiptNo || '未关联实收' : detail . value ? . transport . shipments . find ( item => item . id === row . target_id ) ? . shipmentNo || '未关联交寄' },
{ key : 'reason' , prop : 'reason' , label : '内部事实说明' , minWidth : 260 },
{ key : 'warehouseCorrection' , label : '仓库更正留痕' , minWidth : 190 , formatter : ( row : PlatformProcurement [ 'controls' ][ 'notes' ][ number ]) => row . corrected_value_json ? ` ${ row . original_value_json ? . name || '—' } → ${ row . corrected_value_json . warehouseName || '—' } ` : '—' },
{ key : 'block' , label : '阻断状态' , width : 140 , formatter : ( row : PlatformProcurement [ 'controls' ][ 'notes' ][ number ]) => ! row . blocking ? '仅说明' : row . resolved_at ? '已解除阻断' : '待处理阻断' },
{ key : 'resolution_reason' , prop : 'resolution_reason' , label : '解除依据' , minWidth : 220 },
{ key : 'client_summary' , prop : 'client_summary' , label : '客户可见摘要' , minWidth : 220 },
]
const remainingColumns : DataTableColumn [] = [{ key : 'businessNo' , prop : 'businessNo' , label : '货物来源' , minWidth : 180 }, { key : 'kind' , label : '剩余类型' , minWidth : 130 , formatter : ( row : PlatformProcurement [ 'controls' ][ 'remaining' ][ number ]) => remainingLabels [ row . kind ] }, { key : 'quantity' , prop : 'quantity' , label : '待处理件数' , width : 130 }]
const closureColumns : DataTableColumn [] = [{ key : 'closure_no' , prop : 'closure_no' , label : '关闭编号' , minWidth : 180 }, { key : 'financial_conclusion' , prop : 'financial_conclusion' , label : '内部财务结论' , minWidth : 250 }, { key : 'reason' , prop : 'reason' , label : '内部关闭原因' , minWidth : 220 }, { key : 'client_summary' , prop : 'client_summary' , label : '客户可见摘要' , minWidth : 250 }, { key : 'created_at' , label : '关闭时间' , minWidth : 170 , formatter : ( row : PlatformProcurement [ 'controls' ][ 'closures' ][ number ]) => dateTime ( row . created_at ) }]
const closureItemColumns : DataTableColumn [] = [
{ key : 'source' , label : '处置来源' , minWidth : 180 , formatter : ( row : PlatformProcurement [ 'controls' ][ 'closureItems' ][ number ]) => row . source_kind === 'PURCHASE_REMAINDER' ? order . value ? . orderNo || '—' : row . source_kind === 'TRANSIT_REMAINDER' ? detail . value ? . physical . cartons . find ( item => item . id === row . source_id ) ? . cartonNo || '未关联原箱' : detail . value ? . physical . receipts . find ( item => item . id === row . source_id ) ? . receiptNo || '未关联实收' },
{ key : 'source_kind' , label : '来源类型' , width : 130 , formatter : ( row : PlatformProcurement [ 'controls' ][ 'closureItems' ][ number ]) => remainingLabels [ row . source_kind ] },
{ key : 'quantity' , prop : 'quantity' , label : '件数' , width : 90 }, { key : 'decision' , label : '实际处置' , minWidth : 150 , formatter : ( row : PlatformProcurement [ 'controls' ][ 'closureItems' ][ number ]) => decisionLabels [ row . decision ] || '待核对' }, { key : 'destination' , prop : 'destination' , label : '实际去向与接收方' , minWidth : 260 },
]
const correctionColumns : DataTableColumn [] = [
{ key : 'correctionNo' , prop : 'correctionNo' , label : '更正编号' , minWidth : 190 }, { key : 'rootFeeNo' , prop : 'rootFeeNo' , label : '原费用编号' , minWidth : 170 },
{ key : 'targetOriginalAmount' , label : '正确原币总额' , minWidth : 150 , formatter : ( row : PlatformProcurement [ 'feeCorrections' ][ number ]) => money ( row . targetOriginalAmount , row . originalCurrency ) },
{ key : 'targetAmount' , label : '目标人民币' , minWidth : 140 , formatter : ( row : PlatformProcurement [ 'feeCorrections' ][ number ]) => money ( row . targetAmount , 'CNY' ) },
{ key : 'previousNetAmount' , label : '更正前净实扣' , minWidth : 140 , formatter : ( row : PlatformProcurement [ 'feeCorrections' ][ number ]) => money ( row . previousNetAmount , 'CNY' ) },
{ key : 'differenceAmount' , label : '本次差额' , minWidth : 140 , formatter : ( row : PlatformProcurement [ 'feeCorrections' ][ number ]) => money ( row . differenceAmount , 'CNY' ) },
{ key : 'feeNos' , label : '关联实际费用或调整' , minWidth : 240 , formatter : ( row : PlatformProcurement [ 'feeCorrections' ][ number ]) => row . feeNos . join ( '; ' ) || '无资金差额' },
]
const fileColumns : DataTableColumn [] = [
{ key : 'actions' , label : '操作' , width : 90 , fixed : 'left' }, { key : 'fileName' , prop : 'fileName' , label : '文件名' , minWidth : 250 },
{ key : 'category' , label : '用途' , width : 120 , formatter : ( row : ProcurementFile ) => fileLabels [ row . category ] || '待核对' },
{ key : 'createdAt' , label : '上传时间' , minWidth : 160 , formatter : ( row : ProcurementFile ) => dateTime ( row . createdAt ) },
]
const historyColumns : DataTableColumn [] = [{ key : 'createdAt' , label : '时间' , minWidth : 160 , formatter : ( row : PlatformProcurement [ 'reviewHistory' ][ number ]) => dateTime ( row . createdAt ) }, { key : 'operation' , label : '操作' , width : 120 , formatter : ( row : PlatformProcurement [ 'reviewHistory' ][ number ]) => ({ CREATE : '创建' , REVIEW : '审核' , UPDATE : '编辑备注' } as Record < string , string >)[ row . operationType || '' ] || '采购变更' }, { key : 'remark' , prop : 'remark' , label : '说明' , minWidth : 250 }]
const commandColumns : DataTableColumn [] = [{ key : 'created_at' , label : '时间' , minWidth : 160 , formatter : ( row : PlatformProcurement [ 'operations' ][ number ]) => dateTime ( row . created_at ) }, { key : 'reason' , prop : 'reason' , label : '履约操作说明' , minWidth : 400 }]
let generation = 0 ; let inFlight : Promise < void > | null = null ; let inFlightScope = ''
const scope = computed (() => ` ${ auth . user ? . id || '' } : ${ customerId . value } : ${ purchaseId . value } ` )
onBeforeUnmount (() => { generation ++ })
function load () {
const currentScope = scope . value
if ( inFlight && inFlightScope === currentScope ) return inFlight
const current = ++ generation ; const number = purchaseId . value ; const customer = customerId . value
detail . value = null ; loadError . value = null ; loading . value = false
if ( ! number || ! customer ) { loadError . value = { message : '缺少采购订单号或客户标识,请从采购订单列表重新进入' }; return Promise . resolve () }
loading . value = true ; inFlightScope = currentScope
const promise = getOverseasPurchase ( number , customer ). then ( result => { if ( current === generation ) detail . value = result }). catch ( error => { if ( current === generation ) loadError . value = getApiErrorDetails ( error , '采购详情加载失败' ) }). finally (() => {
if ( current === generation ) loading . value = false
if ( inFlight === promise ) inFlight = null
})
inFlight = promise ; return promise
2026-09-14 10:02:39 +08:00
}
2026-10-10 02:19:39 +08:00
async function download ( fileId : unknown ) {
const file = detail . value ? . files . find ( item => item . fileId === fileId )
if ( ! file || ! order . value || downloading . value ) return
downloading . value = file . fileId ; operationError . value = null ; const current = scope . value
2026-09-14 10:02:39 +08:00
try {
2026-10-10 02:19:39 +08:00
const blob = await downloadProcurementFile ( order . value . orderNo , customerId . value , file . fileId )
if ( scope . value !== current ) return
const url = URL . createObjectURL ( blob ); const link = document . createElement ( 'a' ); link . href = url ; link . download = file . fileName ; link . click (); setTimeout (() => URL . revokeObjectURL ( url ), 1000 )
} catch ( error ) { if ( scope . value === current ) operationError . value = getApiErrorDetails ( error , '文件下载失败' ) }
finally { downloading . value = null }
2026-08-20 09:10:36 +08:00
}
2026-09-14 10:02:39 +08:00
let activatedOnce = false
2026-10-10 02:19:39 +08:00
watch ( scope , () => { void load () }, { immediate : true })
2026-09-14 10:02:39 +08:00
onActivated (() => { if ( activatedOnce ) void load (); else activatedOnce = true })
2026-08-19 16:45:51 +08:00
</ script >
< template >
2026-10-10 02:19:39 +08:00
< AdminPage class = "admin-density" >< OperationErrorToast :details = "operationError" />
< DetailPageLayout class = "purchase-detail density-detail-layout" aria -label =" 采购订单履约详情 " >
< template # header >< div class = "density-detail-header" >< div class = "density-detail-heading" >< el-button link class = "density-back" @click ="router.push({ name: 'overseas-business-purchases' })" >< ArrowLeft :size = "16" /> 返回列表 </ el-button >< h1 class = "density-detail-title" >{{ order ? . orderNo || '—' }}</ h1 >< StatusBadge v-if = "order" :status="statusLabel(order.status)" /></div><div class="density-actions"><el-button :loading="loading" @click="load">刷新</el-button><el-button v-if="order && access.can(overseasBusinessPermissions.purchaseEdit)" @click="router.push({ name: 'overseas-business-purchase-edit', params: { purchaseId: order.orderNo }, query: { customerId } })" > 编辑内部备注 </ el -button ></ div ></ div ></ template >
< template v-if = "order" #summary><dl class="density-detail-overview" aria-label="采购订单概览" >< div >< dt > 客户 </ dt >< dd >{{ text ( order . customerName ) }}</ dd ></ div >< div >< dt > 计划采购仓 </ dt >< dd >{{ text ( order . plannedWarehouseName ) }}</ dd ></ div >< div >< dt > 采购数量 </ dt >< dd >{{ order . purchaseQuantity }}</ dd ></ div ></ dl ></ 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-grid" >
< template v-if = "detail" >
< ProcurementNodeActions :detail = "detail" :customer-id = "customerId" @changed ="load" />
< section >< h3 > 订单及预算 </ h3 >< dl class = "density-fields" >< div v-for = "field in fields" :key="field[0]" :class="{ wide: ['下单收货地址', '内部备注'].includes(field[0]) }" >< dt >{{ field [ 0 ] }}</ dt >< dd >{{ text ( field [ 1 ]) }}</ dd ></ div ></ dl ></ section >
< section >< h3 > 购买事实 </ h3 >< dl class = "density-fields" >< div v-for = "field in purchaseFields" :key="field[0]" >< dt >{{ field [ 0 ] }}</ dt >< dd >{{ text ( field [ 1 ]) }}</ dd ></ div ></ dl ></ section >
< section >< h3 > 履约进度 </ h3 >< dl class = "density-fields" >< div v-for = "step in detail.customerView.progress" :key="step.key" >< dt >{{ step . title }}</ dt >< dd >{{ step . completedAt ? dateTime ( step . completedAt ) : '未完成' }}</ dd ></ div ></ dl ></ section >
< section >< h3 > 分次实收记录 </ h3 >< DataTable :data = "detail.physical.receipts" :columns = "receiptColumns" row -key =" id " empty -text =" 暂无实收记录 " /></ section >
< section >< h3 > 差异处置记录 </ h3 >< DataTable :data = "detail.physical.dispositions" :columns = "dispositionColumns" row -key =" id " empty -text =" 暂无差异处置 " /></ section >
< section >< h3 > 逐箱装货清单 · {{ detail . physical . packingStatus === 'CONFIRMED' ? '已确认' : detail . physical . packingStatus === 'DRAFT' ? '草稿' : '未建立' }}< span v-if = "detail.physical.packingVersion">(第 {{ detail.physical.packingVersion }} 版)</span></h3><DataTable :data="detail.physical.cartons" :columns="cartonColumns" row-key="id" empty-text="暂无箱明细" /></ section >
< section v-if = "detail.transport.domesticCargo.length"><h3>国内包装与可寄货物核对</h3><DataTable :data="detail.transport.domesticCargo" :columns="cartonColumns" row-key="cartonId" /></ section >
< section v-for = "group in labelGroups" :key="group.leg"><h3>{{ group.title }}最近保存面单 · 第 {{ group.value?.version }} 版</h3><p>包装或实收变化后须重新核对面单;是否可交寄以节点校验结果为准。</p><dl v-if="group.value" class="density-fields"><div class="wide"><dt>寄件信息</dt><dd>{{ group.value.sender.name }} · {{ group.value.sender.phone }} · {{ group.value.sender.address }}</dd></div><div class="wide"><dt>收件信息</dt><dd>{{ group.value.receiver.name }} · {{ group.value.receiver.phone }} · {{ group.value.receiver.address }}</dd></div></dl><DataTable :data="group.value?.labels || []" :columns="labelColumns" row-key="cartonId" /></ section >
< section >< h3 > 两段实际交寄 </ h3 >< DataTable :data = "detail.customerView.shipments" :columns = "shipmentColumns" empty -text =" 暂无实际交寄记录 " /></ section >
< section >< h3 > 费用与资金结果 </ h3 >< p > 样品货款与实际费用独立结算 ; 预估国际运费仅冻结 。 资金结果和费用来源关联分别核对 。 </ p >< DataTable :data = "detail.customerView.fees" :columns = "feeColumns" row -key =" feeNo " empty -text =" 暂无核定费用 " /></ section >
< section >< h3 > 费用更正记录 </ h3 >< p > 本表记录核定目标 、 差额及未实扣费用的替代版本 ; 实际扣款 、 退款结果见费用与资金明细 。 </ p >< DataTable :data = "detail.feeCorrections" :columns = "correctionColumns" row -key =" correctionNo " empty -text =" 暂无费用目标更正 " /></ section >
< section >< h3 > 事实说明与阻断 </ h3 >< DataTable :data = "detail.controls.notes" :columns = "noteColumns" row -key =" id " empty -text =" 暂无事实说明或阻断 " /></ section >
< section >< h3 > 剩余货物去向核对 </ h3 >< DataTable :data = "detail.controls.remaining" :columns = "remainingColumns" empty -text =" 当前无待处置的剩余货物 " /></ section >
< section v-if = "detail.controls.closures.length"><h3>异常关闭记录</h3><DataTable :data="detail.controls.closures" :columns="closureColumns" row-key="id" /><DataTable :data="detail.controls.closureItems" :columns="closureItemColumns" row-key="id" empty-text="关闭时无剩余实物" /></ section >
< section >< h3 > 本单文件 </ h3 >< DataTable :data = "detail.files" :columns = "fileColumns" row -key =" fileId " empty -text =" 暂无文件 " >< template # actions = "{ row }" >< el-button class = "density-action" : loading = "downloading === row.fileId" :disabled = "!!downloading" @click ="download(row.fileId)" > 下载 </ el -button ></ template ></ DataTable ></ section >
< section >< h3 > 操作流水 </ h3 >< DataTable :data = "detail.reviewHistory" :columns = "historyColumns" empty -text =" 暂无基础操作记录 " />< DataTable :data = "detail.operations" :columns = "commandColumns" empty -text =" 暂无履约操作记录 " /></ section >
</ template >
2026-09-15 09:58:15 +08:00
</ div >
2026-10-10 02:19:39 +08:00
</ DetailPageLayout >
</ AdminPage >
2026-08-19 16:45:51 +08:00
</ template >
< style scoped >
2026-10-10 02:19:39 +08:00
. purchase - detail { border : 1 px solid var ( -- cc - border ); border - radius : var ( -- admin - radius ); background : var ( -- admin - surface ); }
2026-09-15 09:58:15 +08:00
. detail - grid { display : grid ; min - width : 0 ; gap : var ( -- admin - space - 5 ); }
. detail - grid section { min - width : 0 ; }
. detail - grid h3 { margin : 0 0 var ( -- admin - space - 3 ); font - size : var ( -- admin - font - section ); }
2026-10-10 02:19:39 +08:00
. detail - grid p { color : var ( -- cc - muted ); font - size : var ( -- admin - font - caption ); }
. density - fields > . wide { grid - column : span 2 ; }
2026-08-19 16:45:51 +08:00
</ style >