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

470 lines
12 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="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">
<FinanceReportUpload ref="financeReportUpload" />
</el-col>
</el-row>
<el-row :gutter="24" class="content-row">
<!-- 财务报表上传及数据区域 -->
<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'
import FinanceReportUpload from './child/FinanceReportUpload.vue'
import FinancialStatement from './child/FinancialStatement.vue'
import {
addFinance,
updateFinance,
checkFileNameUnique,
getFinanceDetail // 新增:编辑时加载财务信息的接口
} from "@/api/enterpriseLibrary/finance/finance";
export default {
name: 'FinanceForm',
components: {
FinanceReportUpload,
FinancialStatement
},
data() {
return {
// 新增从路由参数解密获取企业ID与业绩库一致
enterpriseId: decryptWithSM4(this.$route.query.enterpriseId),
id: decryptWithSM4(this.$route.query.id),
type: decryptWithSM4(this.$route.query.type) || 'add',
showUploadAnimation: false,
showSaveAnimation: false,
uploadQueue: 0,
animationText: '识别中'
}
},
methods: {
// 返回携带加密后的企业ID与业绩库逻辑一致
handleClose() {
const obj = {
path: "/financeList/index",
query: {
enterpriseId: encryptWithSM4(this.enterpriseId) // 传递企业ID确保返回后仍显示当前企业数据
}
};
this.$tab.closeOpenPage(obj)
},
async handleSave() {
if (this.showSaveAnimation) {
return
}
this.showSaveAnimation = true
try {
// 1. 分别接收两个子组件的表单数据(命名区分,避免 fileList 冲突)
const [financeReportData, financialStatementData] = await Promise.all([
this.$refs.financeReportUpload.validate(),
this.$refs.financialStatement.validate()
])
// 2. 初始化提交数据,合并两个子组件的非文件字段
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)
}
// 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)
}
// 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('同一企业下文件名已存在,请更换')
// }
// }
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)
}
}
},
mounted() {
// 监听上传开始事件
this.$bus.$on('startUpload', this.handleStartUpload)
// 监听上传结束事件(先解绑再绑定,避免重复监听)
this.$bus.$off('endUpload', this.handleEndUpload)
this.$bus.$on('endUpload', this.handleEndUpload)
// 如果是编辑加载数据调用新增的loadFinanceData方法与业绩库编辑逻辑一致
if (this.type === 'edit' && this.id && this.enterpriseId) {
this.loadFinanceData()
}
},
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">
/* 样式部分与原代码一致,无需修改 */
.app-container {
padding: 24px;
background: linear-gradient(180deg, #F1F6FF 20%, #E5EFFF 100%);
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;
}
.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>