520 lines
17 KiB
Vue
520 lines
17 KiB
Vue
<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> |