smart-bid-web/src/views/enterpriseLibrary/finance/components/FinanceForm.vue

470 lines
12 KiB
Vue
Raw Normal View History

2025-10-24 18:33:58 +08:00
<template>
<div class="app-container" :class="{ 'no-pointer-events': showUploadAnimation || showSaveAnimation }">
<!-- 全局上传动画 -->
<div v-if="showUploadAnimation" class="global-upload-animation">
<div class="animation-mask"></div>
<div class="animation-content">
<div class="spinner"></div>
<div class="animation-text">{{ animationText }}</div>
<div class="animation-subtext">正在处理文件请稍候...</div>
</div>
</div>
<!-- 保存动画 -->
<div v-if="showSaveAnimation" class="global-upload-animation">
<div class="animation-mask"></div>
<div class="animation-content">
<div class="spinner"></div>
<div class="animation-text">数据上传中</div>
<div class="animation-subtext">请稍后正在保存数据...</div>
</div>
</div>
<div class="content-header">
<el-breadcrumb separator="/" class="breadcrumb">
</el-breadcrumb>
<div class="header-actions">
<el-button class="reset-btn" @click="handleClose()" :disabled="showSaveAnimation">返回</el-button>
<el-button class="search-btn" @click="handleSave()" :loading="showSaveAnimation">保存</el-button>
</div>
</div>
<div class="content-body">
<el-row :gutter="24" class="content-row">
<!-- 财务报告上传区域 -->
<el-col :span="8" class="form-pane-report">
2025-10-24 18:33:58 +08:00
<FinanceReportUpload ref="financeReportUpload" />
</el-col>
</el-row>
<el-row :gutter="24" class="content-row">
2025-10-24 18:33:58 +08:00
<!-- 财务报表上传及数据区域 -->
<el-col :span="16" class="form-pane">
<FinancialStatement ref="financialStatement" />
</el-col>
</el-row>
</div>
</div>
</template>
<script>
// 补充SM4加密方法导入与业绩库一致用于返回时传递企业ID
import { decryptWithSM4, encryptWithSM4 } from '@/utils/sm'
2025-10-24 18:33:58 +08:00
import FinanceReportUpload from './child/FinanceReportUpload.vue'
import FinancialStatement from './child/FinancialStatement.vue'
import {
addFinance,
updateFinance,
checkFileNameUnique,
getFinanceDetail // 新增:编辑时加载财务信息的接口
} from "@/api/enterpriseLibrary/finance/finance";
2025-10-24 18:33:58 +08:00
export default {
name: 'FinanceForm',
components: {
FinanceReportUpload,
FinancialStatement
},
data() {
return {
// 新增从路由参数解密获取企业ID与业绩库一致
enterpriseId: decryptWithSM4(this.$route.query.enterpriseId),
2025-10-24 18:33:58 +08:00
id: decryptWithSM4(this.$route.query.id),
type: decryptWithSM4(this.$route.query.type) || 'add',
showUploadAnimation: false,
showSaveAnimation: false,
uploadQueue: 0,
animationText: '识别中'
}
},
methods: {
// 返回携带加密后的企业ID与业绩库逻辑一致
2025-10-24 18:33:58 +08:00
handleClose() {
const obj = {
path: "/financeList/index",
query: {
enterpriseId: encryptWithSM4(this.enterpriseId) // 传递企业ID确保返回后仍显示当前企业数据
}
};
2025-10-24 18:33:58 +08:00
this.$tab.closeOpenPage(obj)
},
async handleSave() {
if (this.showSaveAnimation) {
return
}
this.showSaveAnimation = true
try {
// 1. 分别接收两个子组件的表单数据(命名区分,避免 fileList 冲突)
2025-10-24 18:33:58 +08:00
const [financeReportData, financialStatementData] = await Promise.all([
this.$refs.financeReportUpload.validate(),
this.$refs.financialStatement.validate()
])
// 2. 初始化提交数据,合并两个子组件的非文件字段
2025-10-24 18:33:58 +08:00
const formData = {
...financeReportData, // 财务报告的非文件字段fileName、reportYear 等)
...financialStatementData, // 财务报表的非文件字段(各项财务数据)
enterpriseId: this.enterpriseId,
id: this.id || null,
files: [], // 统一存储所有文件(财务报告 + 财务报表)
delFiles: [] // 统一存储所有删除的文件路径
}
// 3. 单独处理【财务报告】的文件列表
if (financeReportData.fileList && financeReportData.fileList.length) {
const reportFiles = financeReportData.fileList.map(file =>
file.response?.fileRes ? { ...file.response.fileRes, businessType: 'finance_report' } : null
).filter(Boolean)
formData.files.push(...reportFiles) // 加入统一文件列表
}
// 处理财务报告的删除文件列表
if (financeReportData.delFileList && financeReportData.delFileList.length) {
formData.delFiles.push(...financeReportData.delFileList)
2025-10-24 18:33:58 +08:00
}
// 4. 单独处理【财务报表】的文件列表
if (financialStatementData.fileList && financialStatementData.fileList.length) {
const statementFiles = financialStatementData.fileList.map(file =>
file.response?.fileRes ? { ...file.response.fileRes, businessType: 'financial_statement' } : null
).filter(Boolean)
formData.files.push(...statementFiles) // 加入统一文件列表
}
console.log('123',formData.files)
// 处理财务报表的删除文件列表
if (financialStatementData.delFileList && financialStatementData.delFileList.length) {
formData.delFiles.push(...financialStatementData.delFileList)
}
2025-10-24 18:33:58 +08:00
// 5. 后续的文件名唯一性校验、接口调用逻辑不变...
// if (this.type !== 'add') {
// const uniqueCheck = await checkFileNameUnique({
// fileName: formData.fileName,
// financeId: formData.id ? Number(formData.id) : null,
// enterpriseId: this.enterpriseId
// })
// if (!uniqueCheck) {
// throw new Error('同一企业下文件名已存在,请更换')
// }
2025-10-24 18:33:58 +08:00
// }
if (this.type === 'add') {
await addFinance(formData)
} else {
await updateFinance(formData)
}
this.$message.success(`${this.type === 'add' ? '新增' : '修改'}成功`)
this.handleClose()
} catch (error) {
this.$message.error(error.message || '请完善表单信息')
} finally {
this.showSaveAnimation = false
}
},
// 开始上传
handleStartUpload(text) {
this.animationText = text || '识别中'
this.uploadQueue++
this.showUploadAnimation = true
},
// 结束上传
handleEndUpload() {
if (this.uploadQueue > 0) {
this.uploadQueue--
}
// 如果队列为空,隐藏动画
if (this.uploadQueue === 0) {
this.showUploadAnimation = false
}
},
// 新增编辑时加载财务信息携带企业ID与业绩库getPerformance逻辑一致
async loadFinanceData() {
try {
const response = await getFinanceDetail({
financeId: this.id,
enterpriseId: this.enterpriseId // 携带企业ID确保查询当前企业的数据
})
const financeData = response.data || {};
// 将数据传递给子组件
this.$refs.financeReportUpload.setFormData(financeData);
this.$refs.financialStatement.setFormData(financeData);
} catch (error) {
this.$message.error('加载财务信息失败:' + error.message)
}
2025-10-24 18:33:58 +08:00
}
},
mounted() {
// 监听上传开始事件
this.$bus.$on('startUpload', this.handleStartUpload)
// 监听上传结束事件(先解绑再绑定,避免重复监听)
2025-10-24 18:33:58 +08:00
this.$bus.$off('endUpload', this.handleEndUpload)
this.$bus.$on('endUpload', this.handleEndUpload)
// 如果是编辑加载数据调用新增的loadFinanceData方法与业绩库编辑逻辑一致
if (this.type === 'edit' && this.id && this.enterpriseId) {
this.loadFinanceData()
2025-10-24 18:33:58 +08:00
}
},
beforeDestroy() {
// 移除所有事件监听
this.$bus.$off('startUpload', this.handleStartUpload)
this.$bus.$off('endUpload', this.handleEndUpload)
// 重置状态
this.showUploadAnimation = false
this.showSaveAnimation = false
this.uploadQueue = 0
}
}
</script>
<style scoped lang="scss">
/* 样式部分与原代码一致,无需修改 */
2025-10-24 18:33:58 +08:00
.app-container {
padding: 24px;
2025-11-05 14:21:39 +08:00
background: linear-gradient(180deg, #F1F6FF 20%, #E5EFFF 100%);
2025-10-24 18:33:58 +08:00
min-height: 100vh;
overflow-y: auto;
position: relative;
&.no-pointer-events {
pointer-events: none;
* {
pointer-events: none;
}
}
}
.global-upload-animation {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
pointer-events: auto;
.animation-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(4px);
transition: opacity 0.3s ease;
pointer-events: auto;
}
.animation-content {
position: relative;
z-index: 10000;
text-align: center;
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
padding: 40px 50px;
border-radius: 20px;
box-shadow:
0 10px 40px rgba(31, 114, 234, 0.15),
0 0 0 1px rgba(31, 114, 234, 0.1);
min-width: 280px;
animation: slideInUp 0.3s ease-out;
pointer-events: auto;
.spinner {
width: 60px;
height: 60px;
border: 4px solid #f3f7ff;
border-top: 4px solid #409EFF;
border-radius: 50%;
animation: spin 1.2s linear infinite;
margin: 0 auto 20px;
position: relative;
&::after {
content: '';
position: absolute;
top: -4px;
left: -4px;
right: -4px;
bottom: -4px;
border: 4px solid transparent;
border-top: 4px solid #66b1ff;
border-radius: 50%;
animation: spin 0.8s linear infinite reverse;
}
}
.animation-text {
font-size: 20px;
font-weight: 600;
color: #409EFF;
margin-bottom: 8px;
background: linear-gradient(135deg, #409EFF 0%, #66b1ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.animation-subtext {
font-size: 14px;
color: #666;
opacity: 0.8;
}
}
}
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.breadcrumb {
font-size: 14px;
color: #606266;
::v-deep .el-breadcrumb__item:last-child .el-breadcrumb__inner {
color: #303133;
font-weight: 500;
}
}
.header-actions {
display: flex;
gap: 12px;
}
.content-body {
margin-top: 10px;
}
.content-row {
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.form-pane {
background: #fff;
border-radius: 16px;
min-height: 600px;
box-shadow: 0px 4px 20px 0px rgba(31, 35, 55, 0.1);
padding: 0;
margin-bottom: 20px;
flex: 1;
min-width: 300px;
}
.form-pane-report {
background: #fff;
border-radius: 16px;
min-height: 500px;
box-shadow: 0px 4px 20px 0px rgba(31, 35, 55, 0.1);
padding: 0;
margin-bottom: 20px;
flex: 1;
min-width: 300px;
}
2025-10-24 18:33:58 +08:00
.search-btn {
background: #409EFF;
border-color: #409EFF;
color: #fff;
font-weight: 600;
padding: 12px 24px;
border-radius: 6px;
box-shadow: 0px 4px 12px 0px rgba(64, 158, 255, 0.4);
letter-spacing: 0.5px;
font-size: 14px;
transition: all 0.3s ease;
&:hover {
background: #66b1ff;
border-color: #66b1ff;
box-shadow: 0px 6px 16px 0px rgba(64, 158, 255, 0.5);
transform: translateY(-1px);
}
&.is-loading {
opacity: 0.8;
pointer-events: none;
}
}
.reset-btn {
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
font-weight: 600;
padding: 12px 24px;
border-radius: 6px;
box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.1);
letter-spacing: 0.5px;
font-size: 14px;
transition: all 0.3s ease;
&:hover {
background: #f5f7fa;
border-color: #c0c4cc;
color: #409EFF;
box-shadow: 0px 6px 16px 0px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
}
}
@media (max-width: 1200px) {
.content-row {
gap: 16px;
}
}
@media (max-width: 992px) {
.form-pane {
flex: 0 0 100%;
max-width: 100%;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
</style>