2026-08-19 16:45:51 +08:00
< script setup lang = "ts" >
2026-10-20 23:34:11 +08:00
import '@/styles/adminWorkbench.css'
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-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-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'
2026-10-27 17:39:19 +08:00
import { getProcurementWorkflow , type ProcurementWorkflow } from '../api/procurementWorkflow'
import ProcurementWorkflowPanel from '../components/ProcurementWorkflowPanel.vue'
import BusinessDocumentFees from '../components/BusinessDocumentFees.vue'
import { statusLabel , money , dateTime , text } from '../procurementPresentation'
import type { PlatformProcurement } from '../procurementTypes'
import { directionLabel } from '../procurementDirection'
2026-10-28 13:59:39 +08:00
import { hasProcurementFeeDetails } from '../procurementOutboundFees'
2026-10-30 11:19:57 +08:00
const route = useRoute (); const router = useRouter (); const auth = useAuthStore ()
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-27 17:39:19 +08:00
const detail = ref < PlatformProcurement | null >( null ), workflow = ref < ProcurementWorkflow | null >( null ), loading = ref ( false )
const loadError = ref < ApiErrorDetails | null >( null ), operationError = ref < ApiErrorDetails | null >( null ), downloading = ref ( '' )
2026-10-10 02:19:39 +08:00
const order = computed (() => detail . value ? . customerView . order )
2026-10-27 17:39:19 +08:00
const feePanel = ref < InstanceType < typeof BusinessDocumentFees > > ()
2026-10-28 13:59:39 +08:00
async function beforeOutbound () : Promise < boolean | null > {
if ( ! feePanel . value ) return null
try { return ! hasProcurementFeeDetails ( await feePanel . value . getFees ()) }
catch { return null }
2026-10-27 17:39:19 +08:00
}
2026-10-10 02:19:39 +08:00
const fields = computed (() => [
2026-10-27 17:39:19 +08:00
[ '客户' , order . value ? . customerName ], [ '采购方向' , directionLabel ( order . value ? . procurementDirection )], [ '收货仓' , order . value ? . actualWarehouseName || order . value ? . plannedWarehouseName ],
[ '采购品类' , order . value ? . purchaseCategory ], [ '商品名称' , order . value ? . productName ], [ '规格' , order . value ? . productSpecification ],
[ '采购数量' , order . value ? . purchaseQuantity ], [ '预算单价' , money ( order . value ? . budgetUnitPrice , order . value ? . currency )], [ '预计到仓' , dateTime ( order . value ? . estimatedArrivalAt )],
[ '收件人' , order . value ? . contactName ], [ '联系电话' , order . value ? . contactPhone ], [ '收货地址' , order . value ? . receivingAddress ],
2026-10-30 11:19:57 +08:00
...( order . value ? . remark ? [[ '采购要求' , order . value . remark ]] : []),
...( detail . value ? . internalRemark || order . value ? . remark ? [[ '采购备注' , detail . value ? . internalRemark || order . value ? . remark ]] : []),
2026-10-27 17:39:19 +08:00
])
let generation = 0 , inFlight : Promise < void > | null = null , inFlightScope = ''
2026-10-10 02:19:39 +08:00
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
2026-10-27 17:39:19 +08:00
detail . value = null ; workflow . value = null ; loadError . value = null ; loading . value = false
2026-10-10 02:19:39 +08:00
if ( ! number || ! customer ) { loadError . value = { message : '缺少采购订单号或客户标识,请从采购订单列表重新进入' }; return Promise . resolve () }
loading . value = true ; inFlightScope = currentScope
2026-10-27 17:39:19 +08:00
const promise = Promise . all ([ getOverseasPurchase ( number , customer ), getProcurementWorkflow ( number , customer )]). then (([ result , process ]) => {
if ( current === generation ) { detail . value = result ; workflow . value = process }
}). catch ( error => { if ( current === generation ) loadError . value = getApiErrorDetails ( error , '采购详情加载失败' ) }). finally (() => {
2026-10-10 02:19:39 +08:00
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-27 17:39:19 +08:00
async function download ( fileId : string ) {
if ( ! order . value || downloading . value ) return
2026-10-10 02:19:39 +08:00
const file = detail . value ? . files . find ( item => item . fileId === fileId )
2026-10-27 17:39:19 +08:00
downloading . value = fileId ; operationError . value = null ; const current = scope . value
2026-09-14 10:02:39 +08:00
try {
2026-10-27 17:39:19 +08:00
const blob = await downloadProcurementFile ( order . value . orderNo , customerId . value , fileId )
2026-10-10 02:19:39 +08:00
if ( scope . value !== current ) return
2026-10-27 17:39:19 +08:00
const url = URL . createObjectURL ( blob ); const link = document . createElement ( 'a' ); link . href = url ; link . download = file ? . fileName || fileId ; link . click (); setTimeout (() => URL . revokeObjectURL ( url ), 1000 )
2026-10-10 02:19:39 +08:00
} catch ( error ) { if ( scope . value === current ) operationError . value = getApiErrorDetails ( error , '文件下载失败' ) }
2026-10-27 17:39:19 +08:00
finally { downloading . value = '' }
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-20 23:34:11 +08:00
< AdminPage class = "admin-density admin-workbench" >< OperationErrorToast :details = "operationError" />
2026-10-27 17:39:19 +08:00
< DetailPageLayout class = "purchase-detail density-detail-layout workbench-panel is-flush is-page-surface" aria -label =" 采购订单详情 " >
2026-10-30 11:19:57 +08:00
< 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 = "workflow" :status="statusLabel(workflow.status)" /></div><div class="density-actions"><el-button :loading="loading" @click="load" > 刷新 </ el -button ></ div ></ div ></ template >
2026-10-10 02:19:39 +08:00
< 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" >
2026-10-27 17:39:19 +08:00
< template v-if = "detail && order && workflow" >
< section >< h3 > 采购信息 </ h3 >< dl class = "density-fields" >< div v-for = "field in fields" :key="String(field[0])" >< dt >{{ field [ 0 ] }}</ dt >< dd >{{ text ( field [ 1 ]) }}</ dd ></ div ></ dl ></ section >
< ProcurementWorkflowPanel :detail = "detail" :workflow = "workflow" :customer-id = "customerId" :downloading = "downloading" :before-outbound = "beforeOutbound" @changed ="load" @download ="download" />
< BusinessDocumentFees ref = "feePanel" business -type =" OVERSEAS_PROCUREMENT " :business-no = "order.orderNo" :customer-id = "customerId" :business-id = "order.id" />
2026-10-10 02:19:39 +08:00
</ 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-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-08-19 16:45:51 +08:00
</ style >