2025-04-24 18:29:19 +08:00

520 lines
17 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="transaction-detail-container" v-loading="loading">
<!-- 详情主体 -->
<el-tabs v-model="activeTab">
<!-- 总体信息 -->
<el-tab-pane label="总体信息" name="overall">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>总体信息</span>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="对方单位名称">{{ detailData.totalInfo && detailData.totalInfo.unitName || '-' }}</el-descriptions-item>
<el-descriptions-item label="总金额">
<span class="amount">{{ formatAmount(detailData.totalInfo && detailData.totalInfo.totalAmount) }}</span>
</el-descriptions-item>
<el-descriptions-item label="已匹配金额">
<span class="amount matched">{{ formatAmount(detailData.totalInfo && detailData.totalInfo.matchedAmount) }}</span>
</el-descriptions-item>
<el-descriptions-item label="未匹配金额">
<span class="amount unmatched">{{ formatAmount(detailData.totalInfo && detailData.totalInfo.unmatchedAmount) }}</span>
</el-descriptions-item>
</el-descriptions>
</el-card>
</el-tab-pane>
<!-- 流水信息 -->
<el-tab-pane label="流水信息" name="transaction">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>流水信息</span>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="借贷标">
<el-tag :type="(detailData.transaction && detailData.transaction.dcFlagCode === '0') ? 'danger' : 'success'">
{{ (detailData.transaction && detailData.transaction.dcFlagCode === '0') ? '借(支出)' : '贷(收入)' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="发生额">
<span class="amount">{{ formatAmount(detailData.transaction && detailData.transaction.occuAmt) }}</span>
</el-descriptions-item>
<el-descriptions-item label="入账日期">{{ detailData.transaction && detailData.transaction.inaccDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="对方单位名称">{{ detailData.transaction && detailData.transaction.unitName || '-' }}</el-descriptions-item>
<el-descriptions-item label="支付方式">{{ getPaymentModeName(detailData.transaction && detailData.transaction.payModeName) }}</el-descriptions-item>
<el-descriptions-item label="对方账号">{{ detailData.transaction && detailData.transaction.opsAccname || '-' }}</el-descriptions-item>
<el-descriptions-item label="凭证号">{{ detailData.transaction && detailData.transaction.txVchrNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="收据编号">{{ detailData.transaction && detailData.transaction.receiptNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系人">{{ detailData.transaction && detailData.transaction.conterName || '-' }}</el-descriptions-item>
<el-descriptions-item label="流水账户">{{ detailData.transaction && detailData.transaction.openaccBankName || '-' }}</el-descriptions-item>
<el-descriptions-item label="摘要">{{ detailData.transaction && detailData.transaction.summ || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ detailData.transaction && detailData.transaction.remark || '-' }}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-tab-pane>
<!-- 匹配账单(新增的独立标签页) -->
<el-tab-pane label="匹配账单" name="matchBills">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>匹配账单</span>
</div>
<el-table :data="detailData.relatedBills || []" border style="width: 100%">
<el-table-column prop="unitName" label="对方名称" min-width="120" show-overflow-tooltip />
<el-table-column prop="feeTypeName" label="费用类型" min-width="120" show-overflow-tooltip />
<el-table-column label="费用周期" min-width="200">
<template slot-scope="scope">
{{ scope.row.billingStartDate || '-' }} 至 {{ scope.row.billingEndDate || '-' }}
</template>
</el-table-column>
<el-table-column prop="receivableAmount" label="应收金额" min-width="120" align="right">
<template slot-scope="scope">
{{ formatAmount(scope.row.receivableAmount) }}
</template>
</el-table-column>
</el-table>
<div v-if="!detailData.relatedBills || detailData.relatedBills.length === 0" class="empty-data">
暂无匹配账单
</div>
</el-card>
</el-tab-pane>
<!-- 附件信息 -->
<el-tab-pane label="附件信息" name="attachment">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>附件信息</span>
<el-button style="float: right;" type="primary" size="small" @click="handleAddAttachment">
<i class="el-icon-plus"></i> 添加附件
</el-button>
</div>
<el-table :data="detailData.attachments || []" border style="width: 100%">
<el-table-column prop="fileName" label="文件名" min-width="250" show-overflow-tooltip />
<el-table-column prop="createUserName" label="操作人" width="150" />
<el-table-column prop="createTime" label="操作时间" width="180" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="previewFile(scope.row)">预览</el-button>
<el-button size="mini" type="text" @click="downloadFile(scope.row)">下载</el-button>
<el-button size="mini" type="text" class="delete-btn" @click="deleteFile(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!detailData.attachments || detailData.attachments.length === 0" class="empty-data">
暂无附件
</div>
</el-card>
<!-- 文件上传对话框 -->
<el-dialog title="上传附件" :visible.sync="uploadDialogVisible" width="500px" append-to-body>
<el-upload
class="upload-area"
action="#"
:http-request="uploadFile"
:file-list="uploadFileList"
:before-upload="beforeUpload"
multiple
:limit="10">
<el-button type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png/pdf文件且不超过5MB</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button @click="uploadDialogVisible = false"> </el-button>
<el-button type="primary" @click="confirmUpload"> </el-button>
</div>
</el-dialog>
</el-tab-pane>
<!-- 操作记录 -->
<el-tab-pane label="操作记录" name="logs">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>操作记录</span>
</div>
<el-table :data="detailData.operationRecords || []" border style="width: 100%" v-loading="logsLoading">
<el-table-column prop="oprPersonNm" label="操作人" width="150" />
<el-table-column prop="oprContent" label="操作内容" min-width="250" show-overflow-tooltip />
<el-table-column prop="createTime" label="操作时间" width="180" />
</el-table>
<div v-if="!detailData.operationRecords || detailData.operationRecords.length === 0" class="empty-data">
暂无操作记录
</div>
</el-card>
</el-tab-pane>
</el-tabs>
<!-- 文件预览对话框 -->
<el-dialog title="附件预览" :visible.sync="previewDialogVisible" width="800px" append-to-body>
<div class="preview-container" v-loading="previewLoading">
<div v-if="previewUrl && isPdf" class="preview-iframe">
<iframe :src="previewUrl" width="100%" height="500px" frameborder="0"></iframe>
</div>
<div v-else-if="previewUrl && isImage" class="preview-image">
<img :src="previewUrl" style="max-width: 100%;" />
</div>
<div v-else-if="!previewLoading" class="preview-error">
<i class="el-icon-warning"></i>
<p>预览加载失败请尝试下载后查看</p>
<div class="preview-actions">
<el-button type="primary" @click="downloadCurrentPreview">下载文件</el-button>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="previewDialogVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
getTransactionDetail,
uploadTransactionAttachment,
deleteTransactionAttachment,
getTransactionOperationLogs,
previewAttachment,
downloadAttachment,
addTransactionOperation
} from '@/api/finance'
export default {
name: 'TransactionDetail',
props: {
transactionId: {
type: [String, Number],
required: true
}
},
data() {
return {
loading: false,
activeTab: 'overall',
detailData: {},
// 附件相关
uploadDialogVisible: false,
uploadFileList: [],
// 预览相关
previewDialogVisible: false,
previewLoading: false,
previewUrl: '',
previewBlob: null,
currentPreviewName: '',
isPdf: false,
isImage: false,
// 操作记录相关
logsLoading: false,
logsTotal: 0,
logsQuery: {
pageNum: 1,
pageSize: 10,
transactionId: undefined
}
}
},
created() {
this.getDetail()
},
methods: {
// 获取流水详情
getDetail() {
this.loading = true
getTransactionDetail(this.transactionId).then(response => {
this.detailData = response.data || {}
this.loading = false
}).catch(() => {
this.loading = false
})
},
// 获取操作记录
getOperationLogs() {
this.logsLoading = true
this.logsQuery.transactionId = this.transactionId
getTransactionOperationLogs(this.logsQuery).then(response => {
if (this.detailData.operationRecords) {
this.detailData.operationRecords = response.data.data || []
}
this.logsTotal = response.data.total
this.logsLoading = false
}).catch(() => {
this.logsLoading = false
})
},
// 操作记录分页大小改变
handleLogsSizeChange(val) {
this.logsQuery.pageSize = val
this.getOperationLogs()
},
// 操作记录分页页码改变
handleLogsCurrentChange(val) {
this.logsQuery.pageNum = val
this.getOperationLogs()
},
// 格式化金额
formatAmount(amount) {
if (amount === undefined || amount === null) {
return '0.00'
}
return parseFloat(amount).toFixed(2)
},
// 处理添加附件
handleAddAttachment() {
this.uploadDialogVisible = true
this.uploadFileList = []
},
// 上传前检查
beforeUpload(file) {
const isValidType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.type)
const isLt5M = file.size / 1024 / 1024 < 5
if (!isValidType) {
this.$message.error('只能上传JPG/PNG/PDF格式的文件!')
}
if (!isLt5M) {
this.$message.error('文件大小不能超过5MB!')
}
return isValidType && isLt5M
},
// 文件上传
uploadFile(options) {
// 先将文件加入上传列表,等确认后再统一上传
const file = options.file
// 创建临时URL供预览
const tempUrl = URL.createObjectURL(file)
// 向上传列表中添加文件
this.uploadFileList.push({
name: file.name,
file: file,
url: tempUrl
})
},
// 确认上传
confirmUpload() {
if (this.uploadFileList.length === 0) {
this.$message.warning('请选择要上传的文件')
return
}
const userId = this.$store.getters.userId || ''
const userName = this.$store.getters.name || ''
const uploadPromises = this.uploadFileList.map(fileItem => {
return uploadTransactionAttachment(this.transactionId, fileItem.file, userId, userName)
})
this.loading = true
Promise.all(uploadPromises)
.then(() => {
this.$message.success('附件上传成功')
this.uploadDialogVisible = false
// 重新获取详情以更新附件列表
this.getDetail()
this.loading = false
})
.catch(() => {
this.$message.error('部分附件上传失败,请重试')
this.loading = false
})
},
// 预览文件
previewFile(file) {
this.previewLoading = true
this.previewDialogVisible = true
this.currentPreviewName = file.fileName || '附件'
this.previewUrl = ''
this.isPdf = false
this.isImage = false
previewAttachment(file.fileId).then(response => {
// 判断文件类型
const contentType = response.headers['content-type']
// 创建blob对象
const blob = new Blob([response.data], { type: contentType })
this.previewBlob = blob
// 判断文件类型并设置标志
this.isPdf = contentType === 'application/pdf'
this.isImage = contentType.startsWith('image/')
// 创建URL用于预览
this.previewUrl = URL.createObjectURL(blob)
this.previewLoading = false
}).catch(error => {
console.error('预览失败', error)
this.$message.error('获取预览数据失败')
this.previewLoading = false
})
},
// 下载当前预览的文件
downloadCurrentPreview() {
if (!this.previewBlob) {
this.$message.error('没有可下载的文件')
return
}
const url = URL.createObjectURL(this.previewBlob)
const link = document.createElement('a')
link.href = url
link.setAttribute('download', this.currentPreviewName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
},
// 下载文件
downloadFile(file) {
downloadAttachment(file.fileId, file.fileName).then(response => {
// 创建下载链接
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = file.fileName
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(link.href)
}).catch(() => {
this.$message.warning('无法下载该文件')
})
},
// 删除文件
deleteFile(index, file) {
this.$confirm('确认删除该附件?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const userId = this.$store.getters.userId || ''
deleteTransactionAttachment(file.fileId, userId).then(() => {
this.$message.success('删除成功')
if (this.detailData.attachments) {
this.detailData.attachments.splice(index, 1)
}
// 重新获取详情以更新附件列表
this.getDetail()
})
}).catch(() => {})
},
// 添加支付方式显示方法
getPaymentModeName(code) {
const paymentModeMap = {
'1': '现金',
'2': '网银转账',
'3': 'POS机',
'4': '支付宝',
'5': '微信',
'6': '转账支票',
'7': '其他方式',
'8': '线上支付'
}
return paymentModeMap[code] || code || '-'
}
}
}
</script>
<style lang="scss" scoped>
.transaction-detail-container {
padding: 0 20px;
.amount {
font-weight: bold;
color: #409EFF;
&.matched {
color: #67C23A;
}
&.unmatched {
color: #E6A23C;
}
}
.box-card {
margin-bottom: 20px;
}
.upload-area {
text-align: center;
padding: 20px 0;
}
.empty-data {
color: #909399;
text-align: center;
padding: 30px 0;
}
.delete-btn {
color: #F56C6C;
}
.preview-container {
min-height: 500px;
max-height: 700px;
overflow: auto;
.preview-iframe {
width: 100%;
height: 500px;
border: none;
}
.preview-image {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
.preview-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
color: #909399;
i {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 16px;
}
.preview-actions {
margin-top: 20px;
}
}
}
}
</style>