470 lines
12 KiB
Vue
470 lines
12 KiB
Vue
<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>
|