381 lines
10 KiB
Vue
381 lines
10 KiB
Vue
|
|
<template>
|
|||
|
|
<el-dialog
|
|||
|
|
v-model="visible"
|
|||
|
|
title="付款记录详情"
|
|||
|
|
width="800px"
|
|||
|
|
:close-on-click-modal="false"
|
|||
|
|
@close="handleClose"
|
|||
|
|
>
|
|||
|
|
<div class="payment-detail-dialog" v-loading="loading">
|
|||
|
|
<div v-if="paymentData" class="detail-content">
|
|||
|
|
<!-- 基本信息 -->
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title">
|
|||
|
|
<span class="border"></span>
|
|||
|
|
<span class="title">基本信息</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-grid">
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>创建日期:</label>
|
|||
|
|
<span>{{ formatDate(paymentData.created_at) }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>付款来源:</label>
|
|||
|
|
<span>{{ paymentData.country_name || '—' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>汇款人:</label>
|
|||
|
|
<span>{{ paymentData.remitter || '—' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>账户:</label>
|
|||
|
|
<span>{{ paymentData.account_name || '—' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>币种:</label>
|
|||
|
|
<span>{{ paymentData.currency_name || '—' }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>金额:</label>
|
|||
|
|
<span class="amount">{{ paymentData.amount }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>付款水单:</label>
|
|||
|
|
<div>
|
|||
|
|
<p v-for="(item, index) in paymentData.voucher_file" :key="index">
|
|||
|
|
<el-image
|
|||
|
|
v-if="
|
|||
|
|
/\.(png|jpe?g|gif|webp|bmp|svg)(\?|$)/i.test(
|
|||
|
|
(item.file_full || '').split('?')[0]
|
|||
|
|
)
|
|||
|
|
"
|
|||
|
|
:src="item.file_full"
|
|||
|
|
:preview-src-list="item.file_full ? [item.file_full] : []"
|
|||
|
|
style="width: 100px; height: 100px"
|
|||
|
|
/>
|
|||
|
|
<a
|
|||
|
|
v-else
|
|||
|
|
:href="item.file_full"
|
|||
|
|
target="_blank"
|
|||
|
|
rel="noopener"
|
|||
|
|
style="
|
|||
|
|
flex: 1;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
width: 200px;
|
|||
|
|
"
|
|||
|
|
>{{ item.file_name }}</a
|
|||
|
|
>
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 关联信息 -->
|
|||
|
|
<div class="section" v-if="paymentData.payment_apply_id">
|
|||
|
|
<div class="section-title">
|
|||
|
|
<span class="border"></span>
|
|||
|
|
<span class="title">关联信息</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-grid">
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>付款申请ID:</label>
|
|||
|
|
<span>{{ paymentData.payment_apply_id }}</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="info-item">
|
|||
|
|
<label>关联状态:</label>
|
|||
|
|
<el-tag :type="getStatusType(paymentData.status)">
|
|||
|
|
{{ getStatusText(paymentData.status) }}
|
|||
|
|
</el-tag>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 收款记录关联 -->
|
|||
|
|
<div
|
|||
|
|
class="section"
|
|||
|
|
v-if="paymentData.receipt_records && paymentData.receipt_records.length"
|
|||
|
|
>
|
|||
|
|
<div class="section-title">
|
|||
|
|
<span class="border"></span>
|
|||
|
|
<span class="title">关联收款记录</span>
|
|||
|
|
</div>
|
|||
|
|
<el-table :data="paymentData.receipt_records" style="margin-top: 12px">
|
|||
|
|
<el-table-column label="收款记录ID" prop="id" min-width="120" />
|
|||
|
|
<el-table-column label="创建日期" prop="created_at" min-width="140">
|
|||
|
|
<template #default="scope">
|
|||
|
|
{{ formatDate(scope.row.created_at) }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="金额" prop="amount" min-width="120">
|
|||
|
|
<template #default="scope">
|
|||
|
|
{{ formatAmount(scope.row.amount, scope.row.currency_name) }}
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="状态" prop="status" min-width="100">
|
|||
|
|
<template #default="scope">
|
|||
|
|
<el-tag :type="getStatusType(scope.row.status)">
|
|||
|
|
{{ getStatusText(scope.row.status) }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 备注信息 -->
|
|||
|
|
<div class="section" v-if="paymentData.remark">
|
|||
|
|
<div class="section-title">
|
|||
|
|
<span class="border"></span>
|
|||
|
|
<span class="title">备注信息</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="remark-content">
|
|||
|
|
{{ paymentData.remark }}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 无数据状态 -->
|
|||
|
|
<div v-else-if="!loading" class="no-data">
|
|||
|
|
<i class="iconfont-sys"></i>
|
|||
|
|
<span>暂无数据</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- <div class="dialog-footer">
|
|||
|
|
<el-button @click="handleClose">关闭</el-button>
|
|||
|
|
</div> -->
|
|||
|
|
</el-dialog>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { financeService } from '@/api/admin/finance'
|
|||
|
|
|
|||
|
|
interface PaymentRecord {
|
|||
|
|
id: string | number
|
|||
|
|
created_at: string
|
|||
|
|
country_name: string
|
|||
|
|
remitter: string
|
|||
|
|
account_name: string
|
|||
|
|
currency_name: string
|
|||
|
|
amount: number
|
|||
|
|
status: string
|
|||
|
|
payment_apply_id?: string | number
|
|||
|
|
receipt_records?: any[]
|
|||
|
|
voucher_file?: any[]
|
|||
|
|
submit_files?: string[]
|
|||
|
|
remark?: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface Props {
|
|||
|
|
modelValue: boolean
|
|||
|
|
paymentId?: string | number
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface Emits {
|
|||
|
|
(e: 'update:modelValue', value: boolean): void
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const props = defineProps<Props>()
|
|||
|
|
const emit = defineEmits<Emits>()
|
|||
|
|
|
|||
|
|
const visible = computed({
|
|||
|
|
get: () => props.modelValue,
|
|||
|
|
set: (value) => emit('update:modelValue', value)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const loading = ref(false)
|
|||
|
|
const paymentData = ref<PaymentRecord | null>(null)
|
|||
|
|
|
|||
|
|
// 获取付款记录详情
|
|||
|
|
const getPaymentDetail = async () => {
|
|||
|
|
if (!props.paymentId) return
|
|||
|
|
|
|||
|
|
loading.value = true
|
|||
|
|
try {
|
|||
|
|
const res = await financeService.getPaymentDetail({ id: props.paymentId })
|
|||
|
|
paymentData.value = res
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取付款记录详情失败:', error)
|
|||
|
|
paymentData.value = null
|
|||
|
|
} finally {
|
|||
|
|
loading.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 关闭对话框
|
|||
|
|
const handleClose = () => {
|
|||
|
|
visible.value = false
|
|||
|
|
paymentData.value = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 打印功能
|
|||
|
|
const handlePrint = () => {
|
|||
|
|
window.print()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 格式化日期
|
|||
|
|
const formatDate = (dateStr: string) => {
|
|||
|
|
if (!dateStr) return '—'
|
|||
|
|
try {
|
|||
|
|
return new Date(dateStr).toLocaleString('zh-CN')
|
|||
|
|
} catch {
|
|||
|
|
return dateStr
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 格式化金额
|
|||
|
|
const formatAmount = (amount: number, currency: string) => {
|
|||
|
|
return `${currency || ''} ${amount?.toLocaleString() || 0}`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取状态类型
|
|||
|
|
const getStatusType = (status: string): 'success' | 'info' | 'warning' | 'danger' | 'primary' => {
|
|||
|
|
const statusMap: Record<string, 'success' | 'info' | 'warning' | 'danger' | 'primary'> = {
|
|||
|
|
available: 'success',
|
|||
|
|
used: 'info',
|
|||
|
|
pending: 'warning',
|
|||
|
|
cancelled: 'danger',
|
|||
|
|
bound: 'success',
|
|||
|
|
unbound: 'info'
|
|||
|
|
}
|
|||
|
|
return statusMap[status] || 'info'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取状态文本
|
|||
|
|
const getStatusText = (status: string) => {
|
|||
|
|
const statusMap: Record<string, string> = {
|
|||
|
|
'available': '可用',
|
|||
|
|
'used': '已使用',
|
|||
|
|
'pending': '待处理',
|
|||
|
|
'cancelled': '已取消',
|
|||
|
|
'bound': '已绑定',
|
|||
|
|
'unbound': '未绑定'
|
|||
|
|
}
|
|||
|
|
return statusMap[status] || status
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 监听对话框显示状态
|
|||
|
|
watch(visible, (newVal) => {
|
|||
|
|
if (newVal && props.paymentId) {
|
|||
|
|
getPaymentDetail()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 监听付款ID变化
|
|||
|
|
watch(() => props.paymentId, (newId) => {
|
|||
|
|
if (visible.value && newId) {
|
|||
|
|
getPaymentDetail()
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped lang="scss">
|
|||
|
|
.payment-detail-dialog {
|
|||
|
|
.detail-content {
|
|||
|
|
.section {
|
|||
|
|
margin-bottom: 24px;
|
|||
|
|
|
|||
|
|
&:last-child {
|
|||
|
|
margin-bottom: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
margin-bottom: 16px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #222;
|
|||
|
|
|
|||
|
|
.border {
|
|||
|
|
width: 4px;
|
|||
|
|
height: 16px;
|
|||
|
|
background: #3d7bff;
|
|||
|
|
margin-right: 8px;
|
|||
|
|
border-radius: 2px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
font-size: 16px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-grid {
|
|||
|
|
display: grid;
|
|||
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|||
|
|
gap: 16px;
|
|||
|
|
|
|||
|
|
.info-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
// padding: 8px 0;
|
|||
|
|
|
|||
|
|
label {
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #666;
|
|||
|
|
min-width: 100px;
|
|||
|
|
margin-right: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
span {
|
|||
|
|
color: #333;
|
|||
|
|
flex: 1;
|
|||
|
|
|
|||
|
|
&.amount {
|
|||
|
|
font-weight: 600;
|
|||
|
|
color: #3d7bff;
|
|||
|
|
font-size: 16px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.remark-content {
|
|||
|
|
padding: 12px;
|
|||
|
|
background: #f8f9fa;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
color: #666;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
min-height: 60px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.no-data {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
padding: 60px 0;
|
|||
|
|
color: #999;
|
|||
|
|
|
|||
|
|
i {
|
|||
|
|
font-size: 48px;
|
|||
|
|
margin-bottom: 12px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.dialog-footer {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: center;
|
|||
|
|
gap: 12px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 打印样式
|
|||
|
|
@media print {
|
|||
|
|
.dialog-footer {
|
|||
|
|
display: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.payment-detail-dialog {
|
|||
|
|
.section-title {
|
|||
|
|
.border {
|
|||
|
|
background: #000 !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|