smart-bid-web/src/views/template/templateInfo/components/TemplateForm.vue

487 lines
13 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="handleBack()" :disabled="showSaveAnimation">返回</el-button>
<el-button class="search-btn" @click="handleSave()" :loading="showSaveAnimation">
{{ type === 'add' ? '新增' : '保存' }}
</el-button>
</div>
</div>
<div class="content-body">
<el-row :gutter="24" class="content-row">
<el-col :span="24" class="form-pane">
<BasicInfo
ref="basicInfoRef"
:industry-type-options="industryTypeOptions"
:type="type"
/>
</el-col>
</el-row>
<el-row :gutter="24" class="content-row">
<el-col :span="24" class="form-pane">
<ProjectFile
ref="projectFileRef"
title="项目文件"
:is-edit-mode="type === 'edit'"
/>
</el-col>
</el-row>
<el-row :gutter="24" class="content-row">
<el-col :span="24" class="form-pane">
<SectionFile
ref="sectionFileRef"
title="标段/标包文件"
:is-edit-mode="type === 'edit'"
/>
</el-col>
</el-row>
<el-row :gutter="24" class="content-row">
<el-col :span="8" class="form-pane">
<AnalysisLabel
ref="analysisLabelRef"
title="解析标签组"
/>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import { decryptWithSM4, encryptWithSM4 } from '@/utils/sm'
import BasicInfo from './child/BasicInfo.vue'
import ProjectFile from './child/ProjectFile.vue'
import SectionFile from './child/SectionFile.vue'
import AnalysisLabel from './child/AnalysisLabel.vue'
import {
addTemplateInfo,
updateTemplateInfo,
checkTemplateNameUnique,
getTemplateInfoDetail
} from "@/api/template/templateInfo/templateInfo";
export default {
name: 'TemplateForm',
components: {
BasicInfo,
ProjectFile,
SectionFile,
AnalysisLabel
},
props: {
visible: {
type: Boolean,
default: false
},
industryTypeOptions: {
type: Array,
default: () => []
}
},
data() {
return {
type: 'add',
templateId: null,
showUploadAnimation: false,
showSaveAnimation: false,
uploadQueue: 0,
animationText: '识别中'
}
},
methods: {
handleBack() {
const obj = { path: "/templateInfo/index" };
this.$tab.closeOpenPage(obj);
},
// 保存模板信息
async handleSave() {
if (this.showSaveAnimation) return;
this.showSaveAnimation = true;
try {
// 1. 校验并获取所有子组件数据
const [basicData, projectFileData, sectionFileData, analysisLabelData] = await Promise.all([
this.$refs.basicInfoRef.validate(),
this.$refs.projectFileRef.validate(),
this.$refs.sectionFileRef.validate(),
this.$refs.analysisLabelRef.validate()
]);
// 2. 初始化提交数据
const formData = {
...basicData,
...analysisLabelData,
templateId: this.type === 'edit' ? this.templateId : null,
useState: '0',
publishedStatus: '1',
files: [], // 统一存储所有待上传/已上传的文件
delFiles: [] // 统一存储所有需要删除的文件路径
};
// --- 新增逻辑:处理文件和删除列表 ---
// 3. 处理项目文件 (ProjectFile)
projectFileData.forEach(tab => {
// 3.1 处理上传的文件,为每个文件添加 compositionType
if (tab.fileList && tab.fileList.length > 0) {
const processedFiles = tab.fileList.map(file => {
// 提取 fileRes 对象
const fileRes = file.response?.fileRes || {};
// 返回一个合并后的新对象,确保 compositionType 被正确添加
return {
...fileRes,
businessType: 'project_file',
compositionName: file.compositionName,
compositionFileType: file.compositionFileType,
compositionType: tab.compositionType, // <-- 核心:添加 Tab 的 compositionType
};
}).filter(Boolean); // 过滤掉可能的无效数据
formData.files.push(...processedFiles);
}
// 3.2 处理删除的文件路径
if (tab.delFileList && tab.delFileList.length > 0) {
formData.delFiles.push(...tab.delFileList);
}
});
// 4. 处理标段/标包文件 (SectionFile)
sectionFileData.forEach(tab => {
// 4.1 处理上传的文件,为每个文件添加 compositionType
if (tab.fileList && tab.fileList.length > 0) {
const processedFiles = tab.fileList.map(file => {
const fileRes = file.response?.fileRes || {};
return {
...fileRes,
businessType: 'section_file',
compositionName: file.compositionName,
compositionFileType: file.compositionFileType,
compositionType: tab.compositionType, // <-- 核心:添加 Tab 的 compositionType
};
}).filter(Boolean);
console.log('123123123',processedFiles)
formData.files.push(...processedFiles);
}
// 4.2 处理删除的文件路径
if (tab.delFileList && tab.delFileList.length > 0) {
formData.delFiles.push(...tab.delFileList);
}
});
// --- 新增逻辑结束 ---
// 5. 校验模板名称唯一性(仅编辑时)
// if (this.type === 'edit') {
// const uniqueRes = await checkTemplateNameUnique({
// templateName: formData.templateName,
// templateId: formData.templateId
// });
// if (uniqueRes.data > 0) {
// throw new Error('同一行业下模板名称已存在,请更换');
// }
// }
// 6. 调用接口保存
if (this.type === 'add') {
await addTemplateInfo(formData);
} else {
console.log('12345678',formData)
await updateTemplateInfo(formData);
}
this.$message.success(`${this.type === 'add' ? '新增' : '编辑'}模板成功`);
this.$emit('save-success');
this.handleBack();
} catch (error) {
this.$message.error(error.message || '请完善表单信息');
} finally {
this.showSaveAnimation = false;
}
},
// 上传动画控制
handleStartUpload(text) {
this.animationText = text || '处理中';
this.uploadQueue++;
this.showUploadAnimation = true;
},
handleEndUpload() {
this.uploadQueue--;
if (this.uploadQueue <= 0) {
this.showUploadAnimation = false;
this.uploadQueue = 0;
}
},
// 加载模板详情(编辑模式)
async loadTemplateData() {
if (!this.templateId) return;
try {
const res = await getTemplateInfoDetail({ templateId: this.templateId });
const detail = res.data || {};
console.log('detail',detail)
// 向子组件传递数据
this.$refs.basicInfoRef.setFormData(detail);
this.$refs.projectFileRef.setFormData(detail.projectFiles || []);
this.$refs.sectionFileRef.setFormData(detail.sectionFiles || []);
this.$refs.analysisLabelRef.setFormData(detail);
} 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);
// 从路由参数中解析type和templateId
const { type: encryptedType, templateId: encryptedTemplateId } = this.$route.query;
if (encryptedType) {
try {
const decryptedType = decryptWithSM4(encryptedType);
this.type = ['add', 'edit'].includes(decryptedType) ? decryptedType : 'add';
} catch (e) {
console.error('解析type失败', e);
this.type = 'add';
}
}
if (encryptedTemplateId) {
try {
this.templateId = decryptWithSM4(encryptedTemplateId);
} catch (e) {
console.error('解析templateId失败', e);
this.templateId = null;
}
}
// 编辑模式且有有效ID时加载详情数据
if (this.type === 'edit' && this.templateId) {
this.loadTemplateData();
}
},
beforeDestroy() {
// 移除事件监听,避免内存泄漏
this.$bus.$off('startUpload', this.handleStartUpload);
this.$bus.$off('endUpload', this.handleEndUpload);
}
}
</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);
}
.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;
.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;
}
.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;
box-shadow: 0px 4px 20px 0px rgba(31, 35, 55, 0.1);
padding: 20px;
margin-bottom: 20px;
flex: 1;
min-width: 300px;
}
.search-btn {
background: #409EFF;
border-color: #409EFF;
color: #fff;
padding: 12px 24px;
border-radius: 6px;
box-shadow: 0px 4px 12px 0px rgba(64, 158, 255, 0.4);
transition: all 0.3s ease;
&:hover {
background: #66b1ff;
box-shadow: 0px 6px 16px 0px rgba(64, 158, 255, 0.5);
}
}
.reset-btn {
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
padding: 12px 24px;
border-radius: 6px;
box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
background: #f5f7fa;
color: #409EFF;
}
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 992px) {
.content-row {
gap: 16px;
}
.form-pane {
flex: 0 0 100%;
max-width: 100%;
}
}
</style>