2026-10-10 02:19:39 +08:00
< script setup lang = "ts" >
2026-10-27 17:39:28 +08:00
import { computed , onBeforeUnmount , ref } from 'vue'
import { getProcurementFile , type ProcurementDetail , type ProcurementFee , type ProcurementWorkflow } from '../api/procurement'
import { directionLabel , dateTime , feeStatus , feeTone , feeTotals , money , productHref , statusLabel , text } from '../procurementPresentation'
const props = defineProps < { detail : ProcurementDetail ; workflow : ProcurementWorkflow } > ()
const previewUrl = ref ( '' ), previewName = ref ( '' ), previewImage = ref ( false ), fileLoading = ref ( '' ), fileError = ref ( '' )
let active = true
function clearPreview () { if ( previewUrl . value ) URL . revokeObjectURL ( previewUrl . value ); previewUrl . value = '' }
function fileName ( fileId : string , index : string | number ) { return props . workflow . files . find ( file => file . fileId === fileId ) ? . fileName || '附件 ' + ( Number ( index ) + 1 ) }
async function openFile ( fileId : string ) {
if ( fileLoading . value ) return
fileLoading . value = fileId ; fileError . value = ''
try {
const blob = await getProcurementFile ( props . workflow . orderNo , fileId )
if ( ! active ) return
clearPreview (); previewUrl . value = URL . createObjectURL ( blob )
previewName . value = props . workflow . files . find ( file => file . fileId === fileId ) ? . fileName || '附件'
previewImage . value = /^image\/(png|jpeg|gif|webp|bmp)$/i . test ( blob . type )
} catch ( cause ) { if ( active ) fileError . value = cause instanceof Error ? cause . message : '附件加载失败' }
finally { fileLoading . value = '' }
}
onBeforeUnmount (() => { active = false ; clearPreview () })
2026-10-10 02:19:39 +08:00
const totals = computed (() => feeTotals ( props . detail . fees ))
</ script >
< template >
2026-10-27 17:39:28 +08:00
< el-alert v-if = "fileError" :title="fileError" type="error" :closable="false" />
2026-10-29 13:37:28 +08:00
< section class = "procurement-section" aria -label =" 采购数量统计 " >
2026-10-10 02:19:39 +08:00
< dl class = "procurement-grid money-grid" >
2026-10-27 17:39:28 +08:00
< div >< dt > 累计收货 </ dt >< dd >{{ workflow . receivedQuantity }} 件 </ dd ></ div >
< div >< dt > 采购库存 </ dt >< dd >{{ workflow . stockQuantity }} 件 </ dd ></ div >
< div >< dt > 出库处理中 </ dt >< dd >{{ workflow . reservedQuantity }} 件 </ dd ></ div >
< div >< dt > 可出库 </ dt >< dd >{{ workflow . availableQuantity }} 件 </ dd ></ div >
2026-10-10 02:19:39 +08:00
</ dl >
2026-10-29 13:37:28 +08:00
</ section >
< section class = "procurement-section" aria -label =" 分批收货 " >
< h3 > 收货记录 </ h3 >
2026-10-27 17:39:28 +08:00
< el-table :data = "workflow.receipts" border stripe empty -text =" 暂无收货记录 " row -key =" id " >
< el-table-column label = "批次" width = "80" align = "center" >< template # default = "{ $index }" >{{ $index + 1 }}</ template ></ el-table-column >
< el-table-column prop = "quantity" label = "收货数量(件)" width = "145" align = "right" />
< el-table-column label = "收货时间" width = "185" >< template # default = "{ row }" >{{ dateTime ( row . receivedAt ) }}</ template ></ el-table-column >
< el-table-column label = "收货照片" min -width =" 160 " >< template # default = "{ row }" >< span v-if = "!row.fileIds.length">—</span><el-button v-for="(fileId, index) in row.fileIds" :key="fileId" link type="primary" :loading="fileLoading === fileId" :disabled="!!fileLoading" @click="openFile(fileId)" >{{ fileName ( fileId , index ) }} </ el -button ></ template ></ el-table-column >
2026-10-10 02:19:39 +08:00
</ el-table >
</ section >
2026-10-27 17:39:28 +08:00
< section class = "procurement-section" aria -label =" 分批出库 " >
< h3 > 出库记录 </ h3 >
2026-10-28 14:45:17 +08:00
< el-table : data = "workflow.outbounds.filter(row => row.status !== 'IN_TRANSIT' && row.status !== 'CANCELLED')" border stripe empty -text =" 暂无出库记录 " row -key =" id " >
2026-10-27 17:39:28 +08:00
< el-table-column prop = "outboundNo" label = "出库单号" min -width =" 180 " />
< el-table-column prop = "quantity" label = "出库数量(件)" width = "145" align = "right" />
< el-table-column prop = "trackingNo" label = "物流跟踪号" min -width =" 160 " />
< el-table-column label = "状态" width = "100" align = "center" >< template # default = "{ row }" >{{ statusLabel ( row . status ) }}</ template ></ el-table-column >
< el-table-column label = "创建时间" width = "185" >< template # default = "{ row }" >{{ dateTime ( row . createdAt ) }}</ template ></ el-table-column >
< el-table-column label = "签收时间" width = "185" >< template # default = "{ row }" >{{ dateTime ( row . completedAt ) }}</ template ></ el-table-column >
< el-table-column label = "签收凭证" min -width =" 220 " >< template # default = "{ row }" >< span v-if = "!row.fileIds.length">—</span><el-button v-for="(fileId, index) in row.fileIds" :key="fileId" link type="primary" :loading="fileLoading === fileId" :disabled="!!fileLoading" @click="openFile(fileId)" >{{ fileName ( fileId , index ) }} </ el -button ></ template ></ el-table-column >
</ el-table >
2026-10-10 02:19:39 +08:00
</ section >
2026-10-27 17:39:28 +08:00
< section class = "procurement-section" aria -label =" 费用明细 " >
< h3 > 费用明细 < span class = "fee-total" > 已扣款 {{ money ( totals ? . net ) }}</ span ></ h3 >
< el-table :data = "detail.fees" border stripe empty -text =" 暂无费用 " row -key =" feeNo " >
< el-table-column prop = "name" label = "费用名称" min -width =" 180 " />
< el-table-column label = "原币金额" min -width =" 145 " align = "right" >< template # default = "{ row }" >{{ money ( row . originalAmount , row . originalCurrency ) }}</ template ></ el-table-column >
< el-table-column label = "人民币金额" min -width =" 145 " align = "right" >< template # default = "{ row }" >{{ money ( row . amount ) }}</ template ></ el-table-column >
< el-table-column label = "扣款结果" min -width =" 160 " align = "center" >< template # default = "{ row }" >< el-tag : type = "feeTone(row as ProcurementFee)" size = "small" >{{ feeStatus ( row as ProcurementFee ) }}</ el-tag ></ template ></ el-table-column >
</ el-table >
</ section >
2026-10-29 13:37:28 +08:00
< section class = "procurement-section" aria -label =" 商品与收件信息 " >
< h3 > 商品与收件信息 </ h3 >
< dl class = "procurement-grid" >
< div >< dt > 采购方向 </ dt >< dd >{{ directionLabel ( detail . order . procurementDirection ) }}</ dd ></ div >
< div >< dt > 商品名称 </ dt >< dd >{{ detail . order . productName }}</ dd ></ div >
< div >< dt > 商品规格 </ dt >< dd >{{ text ( detail . order . productSpecification ) }}</ dd ></ div >
< div >< dt > 采购品类 </ dt >< dd >{{ text ( detail . order . purchaseCategory ) }}</ dd ></ div >
< div >< dt > 采购数量 </ dt >< dd >{{ detail . order . purchaseQuantity }} 件 </ dd ></ div >
< div >< dt > 预算单价 </ dt >< dd >{{ money ( detail . order . budgetUnitPrice , detail . order . currency ) }}</ dd ></ div >
< div >< dt > 预算合计 </ dt >< dd >{{ money ( detail . budget . totalAmount , detail . budget . currency ) }}</ dd ></ div >
< div >< dt > 采购仓 </ dt >< dd >{{ detail . order . actualWarehouseName || detail . order . plannedWarehouseName || '待审核指定' }}< span v-if = "detail.order.actualWarehouseCode" > ( {{ detail.order.actualWarehouseCode }} ) </ span ></ dd ></ div >
< div >< dt > 收件人 </ dt >< dd >{{ detail . order . contactName }}</ dd ></ div >
< div >< dt > 联系电话 </ dt >< dd >{{ detail . order . contactPhone }}</ dd ></ div >
< div class = "wide" >< dt > 收货地址 </ dt >< dd >{{ detail . order . receivingAddress }}</ dd ></ div >
< div v-if = "detail.order.productUrl" class="full"><dt>商品链接</dt><dd><a v-if="productHref(detail.order.productUrl)" :href="productHref(detail.order.productUrl)" target="_blank" rel="noopener noreferrer" > 查看商品 </ a >< span v-else >{{ text ( detail.order.productUrl ) }}</ span ></ dd ></ div >
</ dl >
</ section >
2026-10-27 17:39:28 +08:00
< el-dialog :model-value = "!!previewUrl" :title = "previewName" width = "800px" append -to-body @close ="clearPreview" >
< img v-if = "previewImage" :src="previewUrl" :alt="previewName" style="display: block; max-width: 100%; max-height: 70vh; margin: auto" />
< a v-else :href = "previewUrl" :download = "previewName" > 下载 {{ previewName }}</ a >
</ el-dialog >
2026-10-10 02:19:39 +08:00
</ template >
< style scoped >
. procurement - section { margin - top : 18 px ; min - width : 0 ; }
2026-10-27 17:39:28 +08:00
. fee - total { margin - left : 14 px ; font - weight : 400 ; font - size : 12 px ; color : var ( -- muted ); }
2026-10-10 02:19:39 +08:00
h3 { margin : 0 0 10 px ; padding - bottom : 7 px ; border - bottom : 1 px solid var ( -- border ); color : var ( -- navy ); font - size : 14 px ; }
. procurement - grid { display : grid ; grid - template - columns : repeat ( 4 , minmax ( 0 , 1 fr )); margin : 0 ; border - left : 1 px solid var ( -- border ); border - top : 1 px solid var ( -- border ); }
. procurement - grid > div { display : grid ; grid - template - columns : 110 px minmax ( 0 , 1 fr ); align - items : center ; gap : 8 px ; padding : 9 px 12 px ; border - right : 1 px solid var ( -- border ); border - bottom : 1 px solid var ( -- border ); min - width : 0 ; }
. procurement - grid dt { font - size : 12 px ; color : var ( -- muted ); }. procurement - grid dd { margin : 0 ; font - size : 13 px ; line - height : 1.6 ; color : var ( -- navy ); overflow - wrap : anywhere ; }
2026-10-29 13:37:28 +08:00
. procurement - grid . wide { grid - column : span 2 ; }. money - grid { gap : 16 px ; border : 0 ; }. money - grid > div { grid - template - columns : minmax ( 0 , 1 fr ); padding : 18 px ; border : 1 px solid var ( -- border ); border - radius : 8 px ; background : # f8fbff ; }. money - grid dd { font - size : 16 px ; font - weight : 600 ; }
2026-10-27 17:39:28 +08:00
. procurement - grid . full { grid - column : 1 / - 1 ; }
. procurement - section : deep (. el - table . cell ) { overflow - wrap : anywhere ; font - variant - numeric : tabular - nums ; }
. procurement - section : deep (. el - table . el - button . is - link ) { display : flex ; justify - content : flex - start ; max - width : 100 % ; height : auto ; margin : 0 ; padding : 3 px 0 ; white - space : normal ; text - align : left ; }
. procurement - section : deep (. el - table . el - button . is - link > span ) { min - width : 0 ; overflow - wrap : anywhere ; }
a { color : var ( -- client - action ); }
2026-10-10 02:19:39 +08:00
@ media ( max - width : 767 px ) {
2026-10-27 17:39:28 +08:00
. procurement - grid { grid - template - columns : 1 fr ; }. procurement - grid . wide { grid - column : auto ; }
2026-10-10 02:19:39 +08:00
}
</ style >