Files
art-design/src/views/admin/stock/show_admin.vue
T
2025-09-15 17:14:38 +08:00

671 lines
22 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.
<template>
<div class="stocking-show-page">
<div class="header boxshadow">
<div class="header-left">
<div class="title">合塑实业·汽车事业部-备货单(客户下定)</div>
<div class="sub">
<span class="mr40">备货单编号:{{ header.order_no }}</span>
<span>业务员:{{ header.salesman }}</span>
</div>
</div>
<ElButton type="primary" plain @click="printCurrent">打印整单</ElButton>
</div>
<!-- 基础信息 -->
<div class="boxshadow">
<div class="form-title"><span class="border"></span><span class="title">基础信息</span></div>
<ElRow :gutter="12">
<ElCol :span="5">
<ElFormItem label="阿里订单号/PI号">
<ElInput v-model="base.pn_no" disabled />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="日期">
<ElInput v-model="base.date" disabled />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="清关目的国">
<ElInput v-model="base.close_date" disabled />
</ElFormItem>
</ElCol>
<ElCol :span="3">
<ElFormItem label="客户来源">
<ElInput v-model="base.channel" disabled />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="客户名称">
<ElInput v-model="base.customer_name" disabled />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="客户所在国">
<ElInput v-model="base.client_country" disabled />
</ElFormItem>
</ElCol>
</ElRow>
</div>
<!-- 订单详情 -->
<div class="boxshadow">
<div class="form-title"><span class="border"></span><span class="title">订单详情</span></div>
<div class="items-wrap">
<div class="item-row" v-for="(item, idx) in items" :key="idx">
<ElRow :gutter="12">
<ElCol :span="8">
<ElFormItem label="车型" label-width="64px">
<ElInput v-model="item.model" disabled />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="颜色/外" label-width="72px">
<ElInput v-model="item.color_out" disabled />
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="内饰" label-width="52px">
<ElInput v-model="item.color_in" disabled />
</ElFormItem>
</ElCol>
<ElCol :span="8">
<ElFormItem label="改装" label-width="52px">
<ElInput v-model="item.modify" disabled />
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="12">
<ElCol :span="4">
<ElFormItem label="单价" label-width="52px">
<ElInput v-model="item.unit_price" disabled>
<template #append>{{ item.unit_price_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="总价" label-width="52px">
<ElInput v-model="item.total_price" disabled>
<template #append>{{ item.total_price_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="改装费" label-width="60px">
<ElInput v-model="item.modify_fee" disabled>
<template #append>{{ item.modify_fee_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="FOB段运费" label-width="84px">
<ElInput v-model="item.fob_fee" disabled>
<template #append>{{ item.fob_fee_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="手续费" label-width="60px">
<ElInput v-model="item.helper_fee" disabled>
<template #append>{{ item.helper_fee_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="港杂费" label-width="60px">
<ElInput v-model="item.port_fee" disabled>
<template #append>{{ item.port_fee_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
</ElRow>
<ElRow :gutter="12">
<ElCol :span="4">
<ElFormItem label="里程" label-width="52px">
<ElInput v-model="item.mileage" disabled />
</ElFormItem>
</ElCol>
<template v-if="(item.mileage ?? 0) <= 0">
<ElCol :span="4">
<ElFormItem label="官方指导价" label-width="85px">
<ElInput v-model="item.guide_price" disabled>
<template #append>{{ item.guide_price_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="优惠" label-width="52px">
<ElInput v-model="item.discount" disabled>
<template #append>{{ item.discount_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
</template>
<template v-else>
<ElCol :span="4">
<ElFormItem label="挂牌价" label-width="55px">
<ElInput v-model="item.listing_price" disabled>
<template #append>{{ item.listing_price_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
<ElCol :span="4">
<ElFormItem label="二手减价" label-width="72px">
<ElInput v-model="item.reduction_price" disabled>
<template #append>{{ item.reduction_price_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
</template>
<ElCol :span="8">
<ElFormItem label="利润" label-width="52px">
<ElInput v-model="item.profit" disabled>
<template #append>{{ item.profit_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
</ElRow>
</div>
<ElRow :gutter="12">
<ElCol :span="6">
<ElFormItem label="合计到岸价" label-width="85px">
<ElInput v-model="base.total_cif" disabled>
<template #append>{{ base.total_cif_currency }}</template>
</ElInput>
</ElFormItem>
</ElCol>
<ElCol :span="6">
<ElFormItem label="合同汇率" label-width="70px">
<ElInput v-model="base.contract_rate" disabled />
</ElFormItem>
</ElCol>
</ElRow>
</div>
</div>
<!-- 运输详情 -->
<div class="boxshadow">
<div class="form-title"><span class="border"></span><span class="title">运输详情</span></div>
<ElRow :gutter="12">
<ElCol :span="4"
><ElFormItem label="清关人"><ElInput v-model="ship.customs" disabled /></ElFormItem
></ElCol>
<ElCol :span="4"
><ElFormItem label="预计发送日"><ElInput v-model="ship.plan" disabled /></ElFormItem
></ElCol>
<ElCol :span="4"
><ElFormItem label="运输方式"><ElInput v-model="ship.method" disabled /></ElFormItem
></ElCol>
<ElCol :span="4"
><ElFormItem label="国内收货港"
><ElInput v-model="ship.domestic_port" disabled /></ElFormItem
></ElCol>
<ElCol :span="8"
><ElFormItem label="贸易条款/清关目的港"
><ElInput v-model="ship.terms" disabled /></ElFormItem
></ElCol>
</ElRow>
<ElRow :gutter="12">
<ElCol :span="6"
><ElFormItem label="最迟到港时间"
><ElInput v-model="ship.latest_arrival" disabled /></ElFormItem
></ElCol>
</ElRow>
</div>
<!-- 计划收款信息 -->
<div class="boxshadow">
<div class="form-title"
><span class="border"></span><span class="title">计划收款信息</span></div
>
<div v-for="(p, idx) in payments" :key="idx">
<ElRow :gutter="12">
<ElCol :span="4"
><ElFormItem label="汇款国"><ElInput v-model="p.remit_country" disabled /></ElFormItem
></ElCol>
<ElCol :span="4"
><ElFormItem label="日期"><ElInput v-model="p.date" disabled /></ElFormItem
></ElCol>
<ElCol :span="4"
><ElFormItem label="币种"><ElInput v-model="p.currency" disabled /></ElFormItem
></ElCol>
<ElCol :span="4"
><ElFormItem label="账户"><ElInput v-model="p.account" disabled /></ElFormItem
></ElCol>
<ElCol :span="4"
><ElFormItem label="汇款人"><ElInput v-model="p.payer" disabled /></ElFormItem
></ElCol>
<ElCol :span="4"
><ElFormItem label="金额"><ElInput v-model="p.amount" disabled /></ElFormItem
></ElCol>
</ElRow>
</div>
</div>
<!-- 上传合同 -->
<div class="boxshadow">
<div class="form-title">
<span class="border"></span>
<span class="title">上传合同</span>
</div>
<div class="file-preview">
<div v-if="contractFile" class="contract-file">
<div v-if="isImageFile(contractFile)" class="image-preview">
<img
:src="contractFile.url"
:alt="contractFile.name"
class="preview-image"
@click="previewImage(contractFile)"
/>
<div class="file-info">
<span class="file-name">{{ contractFile.name }}</span>
<el-button type="text" size="small" @click="previewImage(contractFile)">
预览
</el-button>
</div>
</div>
<div v-else class="document-preview">
<div class="file-icon">
<i class="iconfont-sys">&#xe814;</i>
</div>
<div class="file-info">
<span class="file-name">{{ contractFile.name }}</span>
<el-button type="primary" size="small" @click="downloadFile(contractFile)">
下载
</el-button>
</div>
</div>
</div>
<div v-else class="no-file">
<i class="iconfont-sys">&#xe60c;</i>
<span>暂无合同文件</span>
</div>
</div>
</div>
<!-- PDF 预览弹窗 -->
<el-dialog v-model="pdf.visible" title="打印预览" width="80%" top="40px" :close-on-click-modal="false">
<div style="height: 70vh; display: flex; flex-direction: column; gap: 12px;">
<div style="display:flex; justify-content:flex-end; gap: 8px;">
<!-- <el-button type="primary" @click="print()">打印</el-button> -->
<el-button @click="download">下载</el-button>
</div>
<iframe
v-if="pdf.blobUrl || pdf.url"
ref="frameRef"
:src="pdf.blobUrl || pdf.url"
style="flex:1; width: 100%; border: 1px solid #eee; border-radius: 6px;"
/>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { manageService } from '@/api/adminApi'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import { usePdfPreview } from '@/composables/usePdfPreview'
const { pdf, frameRef, openWithResponse, download, print, preparePreview } = usePdfPreview()
defineOptions({ name: 'AdminStockingDetail' })
const printCurrent = async () => {
const res = await manageService.printStock({ id: stocking_id })
openWithResponse(res)
preparePreview()
}
const route = useRoute()
const stocking_id = route.query.id
const stockingInfo = ref<any>({})
// 回显视图模型,字段与模板一致
const header = reactive({ order_no: '', salesman: '' })
const base = reactive({
pn_no: '',
date: '',
close_date: '',
channel: '',
customer_name: '',
client_country: '',
total_cif: '',
total_cif_currency: '',
contract_rate: ''
})
const items = ref<any[]>([])
const ship = reactive({
customs: '',
plan: '',
method: '',
domestic_port: '',
terms: '',
latest_arrival: ''
})
const payments = ref<any[]>([])
// 合同文件
const contractFile = ref<{ name: string; url: string; file_name: string } | null>(null)
const toMoneyView = (m: any) => ({
amount: m?.amount ?? '',
currency: m?.currency ?? ''
})
//获取详情并映射到回显模型
const getdetilfn = async () => {
const res = await manageService.getStockDetail({ id: stocking_id })
stockingInfo.value = res
// 头部
header.order_no = res?.order_no || res?.pi_no || ''
header.salesman = res?.salesperson_snapshot || res?.salesman || res?.salesman_name || res?.user?.name || ''
// 基础信息
base.pn_no = res?.pi_no || ''
base.date = res?.biz_date || ''
base.close_date = res?.clearance_country_snapshot || ''
base.channel = res?.customer_source_snapshot || res?.customer_source || ''
base.customer_name = res?.customer_snapshot || res?.customer_name || ''
base.client_country = res?.customer_country_snapshot || res?.customer_country || ''
// 订单明细
const list = Array.isArray(res?.veh_list) ? res.veh_list : []
items.value = list.map((it: any) => {
const unit = toMoneyView(it?.unit_price)
const total = toMoneyView(it?.total_price)
const guide = toMoneyView(it?.official_price)
const discount = toMoneyView(it?.discount_price)
const helper = toMoneyView(it?.handling_fee)
const fob = toMoneyView(it?.fob_transport_fee)
const port = toMoneyView(it?.port_misc_fee)
const profit = toMoneyView(it?.profit)
return {
model: it?.model_snapshot || '',
mileage: it?.mileage ?? 0,
color_out: it?.color_exterior_snapshot || '',
color_in: it?.color_interior_snapshot || '',
modify: (Array.isArray(it?.modify_list) ? it.modify_list : [])
.map((m: any) => m?.option_name_snapshot)
.filter(Boolean)
.join('、'),
modify_fee: it?.retrofit_fee?.amount ?? '',
modify_fee_currency: it?.retrofit_fee?.currency ?? '',
fob_fee: fob.amount,
fob_fee_currency: fob.currency,
unit_price: unit.amount,
unit_price_currency: unit.currency,
total_price: total.amount,
total_price_currency: total.currency,
guide_price: guide.amount,
guide_price_currency: guide.currency,
discount: discount.amount,
discount_currency: discount.currency,
listing_price: it?.listing_price?.amount ?? '',
listing_price_currency: it?.listing_price?.currency ?? '',
reduction_price: it?.reduction_price?.amount ?? '',
reduction_price_currency: it?.reduction_price?.currency ?? '',
helper_fee: helper.amount,
helper_fee_currency: helper.currency,
domestic_freight: it?.domestic_freight?.amount ?? '',
domestic_freight_currency: it?.domestic_freight?.currency ?? '',
port_fee: port.amount,
port_fee_currency: port.currency,
profit: profit.amount,
profit_currency: profit.currency
}
})
// 运输详情
ship.customs = res?.declarant_name || ''
ship.plan = res?.eta_ship_date || ''
ship.method = res?.shipping_method_snapshot || ''
ship.domestic_port = res?.domestic_port_snapshot || ''
ship.terms = `${res?.trade_term_snapshot || ''} / ${res?.clearance_port_snapshot || ''}`.replace(
/^\s*\/\s*$/,
''
)
ship.latest_arrival = res?.latest_arrival_date || ''
// 合计与汇率
base.total_cif = res?.cif_total_amount?.amount ?? ''
base.total_cif_currency = res?.cif_total_amount?.currency || ''
base.contract_rate = res?.contract_exchange_rate || res?.contract_rate || ''
// 计划收款(全部展示)
payments.value = (Array.isArray(res?.account_list) ? res.account_list : []).map((p: any) => ({
remit_country: p?.remit_country_name_snapshot || '',
date: p?.remit_date || '',
currency: p?.currency_name_snapshot || p?.currency_code || '',
account: p?.account_name_snapshot || '',
payer: p?.remitter_name || '',
amount: p?.amount ?? ''
}))
// 合同文件
if (res?.contract) {
contractFile.value = {
name: '合同文件',
url: res.contract_url || res.contract,
file_name: res.contract
}
}
}
// 判断是否为图片文件
const isImageFile = (file: { name: string; url: string; file_name: string }) => {
// 检查文件名
if (file.name && /\.(jpg|jpeg|png|gif|webp)$/i.test(file.name)) {
return true
}
// 检查file_name字段
if (file.file_name && file.file_name.startsWith('image/')) {
return true
}
// 检查URL中的文件扩展名(去除查询参数)
if (file.url) {
const urlWithoutParams = file.url.split('?')[0] // 移除查询参数
if (/\.(jpg|jpeg|png|gif|webp)$/i.test(urlWithoutParams)) {
return true
}
}
return false
}
// 预览图片
const previewImage = (file: { name: string; url: string; file_name: string }) => {
// 创建图片预览弹窗
const imagePreview = document.createElement('div')
imagePreview.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
cursor: pointer;
`
const image = document.createElement('img')
image.src = file.url
image.alt = file.name
image.style.cssText = `
max-width: 90%;
max-height: 90%;
object-fit: contain;
border-radius: 4px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
`
const closeBtn = document.createElement('div')
closeBtn.innerHTML = '×'
closeBtn.style.cssText = `
position: absolute;
top: 20px;
right: 30px;
color: white;
font-size: 30px;
cursor: pointer;
z-index: 10000;
`
imagePreview.appendChild(image)
imagePreview.appendChild(closeBtn)
document.body.appendChild(imagePreview)
// 点击关闭
const closePreview = () => {
document.body.removeChild(imagePreview)
}
imagePreview.addEventListener('click', closePreview)
closeBtn.addEventListener('click', (e) => {
e.stopPropagation()
closePreview()
})
// ESC键关闭
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
closePreview()
document.removeEventListener('keydown', handleKeydown)
}
}
document.addEventListener('keydown', handleKeydown)
}
// 下载文件
const downloadFile = (file: { name: string; url: string; file_name: string }) => {
try {
const link = document.createElement('a')
link.href = file.url
link.download = file.name
link.target = '_blank'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
} catch (error) {
console.error('下载失败:', error)
ElMessage.error('下载失败,请稍后重试')
}
}
onMounted(() => {
getdetilfn()
})
</script>
<style scoped lang="scss">
.stocking-show-page { min-height: 100vh; }
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
margin-bottom: 12px;
color: #333333;
.header-left {
flex: 1;
text-align: center;
}
.title {
font-size: 18px;
font-weight: 600;
margin-bottom: 15px;
}
.sub {
color: #333333;
}
.mr40 { margin-right: 40px; }
}
.form-title { font-weight: bold; color: #222; margin-bottom: 15px; }
.form-title .title { margin-left: 6px; }
.items-wrap { margin-top: 6px; }
.item-row { padding: 8px 0; }
:deep(.el-input.is-disabled .el-input__inner) { color: var(--el-text-color-regular); }
.file-preview {
padding: 20px;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.contract-file {
width: 100%;
max-width: 400px;
}
.image-preview {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.preview-image {
max-width: 200px;
max-height: 200px;
object-fit: contain;
border: 1px solid #e4e7ed;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: transform 0.2s ease;
}
.preview-image:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.document-preview {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
border: 1px solid #e4e7ed;
border-radius: 8px;
background: #fafafa;
}
.file-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: #409eff;
border-radius: 8px;
color: white;
font-size: 24px;
}
.file-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.file-name {
font-size: 14px;
color: #333;
word-break: break-all;
}
.no-file {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
color: #999;
font-size: 14px;
}
.no-file i {
font-size: 48px;
color: #ddd;
}
</style>