367 lines
10 KiB
Vue
367 lines
10 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="file-container">
|
|||
|
|
<div class="basic-info-title">
|
|||
|
|
<div class="title-content">
|
|||
|
|
<img src="@/assets/enterpriseLibrary/basic-info.png" alt="标段文件">
|
|||
|
|
<span>{{ title }}</span>
|
|||
|
|
</div>
|
|||
|
|
<el-button
|
|||
|
|
type="text"
|
|||
|
|
icon="el-icon-plus"
|
|||
|
|
class="add-tab-btn"
|
|||
|
|
@click="handleAddTab"
|
|||
|
|
:disabled="fileTabs.length >= 10"
|
|||
|
|
>
|
|||
|
|
添加文件
|
|||
|
|
</el-button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<el-tabs
|
|||
|
|
v-model="activeTabIndex"
|
|||
|
|
type="card"
|
|||
|
|
class="file-tabs"
|
|||
|
|
@tab-remove="handleRemoveTab"
|
|||
|
|
>
|
|||
|
|
<el-tab-pane
|
|||
|
|
v-for="(tab, index) in fileTabs"
|
|||
|
|
:key="index"
|
|||
|
|
:label="'文件 ' + (index + 1)"
|
|||
|
|
:name="index.toString()"
|
|||
|
|
closable
|
|||
|
|
>
|
|||
|
|
<el-form
|
|||
|
|
:model="tab.formData"
|
|||
|
|
:rules="rules"
|
|||
|
|
:ref="'sectionFileForm_' + index"
|
|||
|
|
label-width="120px"
|
|||
|
|
label-position="top"
|
|||
|
|
class="file-form"
|
|||
|
|
>
|
|||
|
|
<el-form-item label="组成文件名称" prop="fileName">
|
|||
|
|
<el-input
|
|||
|
|
v-model="tab.formData.fileName"
|
|||
|
|
placeholder="请输入"
|
|||
|
|
maxlength="128"
|
|||
|
|
@input="handleFileNameChange(index)"
|
|||
|
|
@change="handleFileNameChange(index)"
|
|||
|
|
></el-input>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="文件类型" prop="compositionFileType">
|
|||
|
|
<el-select
|
|||
|
|
v-model="tab.formData.compositionFileType"
|
|||
|
|
placeholder="请选择文件类型"
|
|||
|
|
clearable
|
|||
|
|
@change="handleTypeChange(index)"
|
|||
|
|
>
|
|||
|
|
<el-option
|
|||
|
|
v-for="item in compositionFileTypeOptions"
|
|||
|
|
:key="item.value"
|
|||
|
|
:label="item.label"
|
|||
|
|
:value="item.value"
|
|||
|
|
></el-option>
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
|
|||
|
|
<el-form-item label="文件" prop="fileList">
|
|||
|
|
<UploadFile
|
|||
|
|
:fileList="formatFileList(tab.formData.fileList, index)"
|
|||
|
|
:fileUploadRule="fileUploadRule"
|
|||
|
|
@file-change="(files, type) => handleFileChange(files, index)"
|
|||
|
|
@del-file="(file) => handleDelFile(file, index)"
|
|||
|
|
:uploadType="uploadType"
|
|||
|
|
:maxFileTips="maxFileTips"
|
|||
|
|
type="section_file"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
</el-tab-pane>
|
|||
|
|
</el-tabs>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import UploadFile from '@/views/common/UploadFile.vue'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: 'SectionFile',
|
|||
|
|
components: { UploadFile },
|
|||
|
|
props: {
|
|||
|
|
title: {
|
|||
|
|
type: String,
|
|||
|
|
default: '模板组成-标段/标包文件'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
uploadType: 'doc、docx、pdf、xls、xlsx、wps',
|
|||
|
|
maxFileTips: '50MB',
|
|||
|
|
fileUploadRule: { fileUploadType: 'section_file', suffix: 'template' },
|
|||
|
|
activeTabIndex: '0',
|
|||
|
|
fileTabs: [
|
|||
|
|
{
|
|||
|
|
formData: {
|
|||
|
|
compositionType: '2', // 标段文件类型标识
|
|||
|
|
fileName: '',
|
|||
|
|
compositionFileType: '',
|
|||
|
|
fileList: [], // 存储当前页签的文件列表
|
|||
|
|
delFileList: []
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
rules: {
|
|||
|
|
fileName: [
|
|||
|
|
{ required: true, message: '请输入组成文件名称', trigger: 'blur' }
|
|||
|
|
],
|
|||
|
|
compositionFileType: [
|
|||
|
|
{ required: true, message: '请选择文件类型', trigger: 'change' }
|
|||
|
|
],
|
|||
|
|
fileList: [
|
|||
|
|
{ required: true, message: '请上传文件', trigger: 'change' }
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
compositionFileTypeOptions: []
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
created() {
|
|||
|
|
this.loadFileTypeDict()
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
methods: {
|
|||
|
|
loadFileTypeDict() {
|
|||
|
|
this.getDicts('file_type').then(response => {
|
|||
|
|
this.compositionFileTypeOptions = response.data.map(item => ({
|
|||
|
|
label: item.dictLabel,
|
|||
|
|
value: item.dictLabel
|
|||
|
|
}))
|
|||
|
|
}).catch(error => {
|
|||
|
|
console.error('加载文件类型字典失败:', error)
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
handleAddTab() {
|
|||
|
|
this.fileTabs.push({
|
|||
|
|
formData: {
|
|||
|
|
compositionType: '2',
|
|||
|
|
fileName: '',
|
|||
|
|
compositionFileType: '',
|
|||
|
|
fileList: [],
|
|||
|
|
delFileList: []
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
this.activeTabIndex = (this.fileTabs.length - 1).toString()
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
handleRemoveTab(index) {
|
|||
|
|
if (this.fileTabs.length <= 1) {
|
|||
|
|
this.$message.warning('至少保留一个文件')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
this.fileTabs.splice(index, 1)
|
|||
|
|
this.activeTabIndex = '0'
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 格式化文件列表,适配UploadFile组件
|
|||
|
|
formatFileList(fileList, index) {
|
|||
|
|
return (fileList || []).map((file, fileIndex) => {
|
|||
|
|
const fileExt = file.fileName
|
|||
|
|
? file.fileName.split('.').pop().toLowerCase()
|
|||
|
|
: (file.name || '').split('.').pop().toLowerCase() || '';
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
...file,
|
|||
|
|
uid: file.uid || file.filePath || `${Date.now()}-${index}-${fileIndex}`,
|
|||
|
|
name: file.name || file.fileName || `未命名文件.${fileExt}`,
|
|||
|
|
status: file.status || 'success',
|
|||
|
|
percentage: file.percentage || 100,
|
|||
|
|
url: file.lsFilePath || file.url,
|
|||
|
|
lsFilePath: file.lsFilePath || file.url,
|
|||
|
|
filePath: file.filePath || '',
|
|||
|
|
fileType: file.fileType || '2'
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理文件上传/变更
|
|||
|
|
handleFileChange(files, index) {
|
|||
|
|
|
|||
|
|
const currentType = this.fileTabs[index].formData.compositionFileType
|
|||
|
|
const compositionName = this.fileTabs[index].formData.fileName // 取当前fileName
|
|||
|
|
this.fileTabs[index].formData.fileList = files.map(file => ({
|
|||
|
|
...file,
|
|||
|
|
businessType: 'section_file',
|
|||
|
|
compositionName: compositionName, // 绑定fileName
|
|||
|
|
compositionFileType: currentType,
|
|||
|
|
compositionType: '2',
|
|||
|
|
filePath: file.response?.fileRes?.uploadPath || file.filePath || '',
|
|||
|
|
lsFilePath: file.response?.fileRes?.url || file.lsFilePath || file.url
|
|||
|
|
}))
|
|||
|
|
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理文件类型变更
|
|||
|
|
handleTypeChange(index) {
|
|||
|
|
const currentType = this.fileTabs[index].formData.compositionFileType
|
|||
|
|
const compositionName = this.fileTabs[index].formData.fileName // 取当前fileName
|
|||
|
|
this.fileTabs[index].formData.fileList = this.fileTabs[index].formData.fileList.map(file => ({
|
|||
|
|
...file,
|
|||
|
|
compositionFileType: currentType,
|
|||
|
|
compositionName: compositionName // 同步fileName
|
|||
|
|
}))
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 新增:监听fileName变化,同步更新当前页签所有文件的compositionName
|
|||
|
|
handleFileNameChange(index) {
|
|||
|
|
const currentFileName = this.fileTabs[index].formData.fileName;
|
|||
|
|
// 遍历当前页签的所有文件,更新compositionName
|
|||
|
|
this.fileTabs[index].formData.fileList = this.fileTabs[index].formData.fileList.map(file => ({
|
|||
|
|
...file,
|
|||
|
|
compositionName: currentFileName // 用最新的fileName赋值
|
|||
|
|
}));
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理文件删除
|
|||
|
|
handleDelFile(file, index) {
|
|||
|
|
const filePath = file.filePath || file.response?.fileRes?.uploadPath || ''
|
|||
|
|
if (filePath && !this.fileTabs[index].formData.delFileList.includes(filePath)) {
|
|||
|
|
this.fileTabs[index].formData.delFileList.push(filePath)
|
|||
|
|
}
|
|||
|
|
this.fileTabs[index].formData.fileList = this.fileTabs[index].formData.fileList.filter(
|
|||
|
|
item => item.filePath !== filePath
|
|||
|
|
)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 回显数据
|
|||
|
|
setFormData(sectionFiles) {
|
|||
|
|
this.fileTabs = []
|
|||
|
|
|
|||
|
|
if (!sectionFiles || sectionFiles.length === 0) {
|
|||
|
|
this.fileTabs.push({
|
|||
|
|
formData: {
|
|||
|
|
compositionType: '2',
|
|||
|
|
fileName: '',
|
|||
|
|
compositionFileType: '',
|
|||
|
|
fileList: [],
|
|||
|
|
delFileList: []
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
this.activeTabIndex = '0'
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sectionFiles.forEach((file, index) => {
|
|||
|
|
const tabFileName = file.compositionName || file.fileName || '';
|
|||
|
|
this.fileTabs.push({
|
|||
|
|
formData: {
|
|||
|
|
compositionType: '2',
|
|||
|
|
fileName: tabFileName,
|
|||
|
|
compositionFileType: file.compositionFileType || '',
|
|||
|
|
fileList: [{
|
|||
|
|
...file,
|
|||
|
|
businessType: 'section_file',
|
|||
|
|
compositionType: '2',
|
|||
|
|
lsFilePath: file.lsFilePath,
|
|||
|
|
url: file.lsFilePath,
|
|||
|
|
filePath: file.filePath,
|
|||
|
|
name: file.fileName || file.name,
|
|||
|
|
fileName: file.fileName || file.name,
|
|||
|
|
status: 'success',
|
|||
|
|
percentage: 100,
|
|||
|
|
compositionName: tabFileName // 初始化时绑定fileName
|
|||
|
|
}],
|
|||
|
|
delFileList: []
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.activeTabIndex = '0'
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 表单验证
|
|||
|
|
validate() {
|
|||
|
|
return new Promise(async (resolve, reject) => {
|
|||
|
|
try {
|
|||
|
|
const allFormData = []
|
|||
|
|
for (let i = 0; i < this.fileTabs.length; i++) {
|
|||
|
|
const formRef = this.$refs[`sectionFileForm_${i}`]?.[0]
|
|||
|
|
if (!formRef) continue
|
|||
|
|
|
|||
|
|
await new Promise((validResolve, validReject) => {
|
|||
|
|
formRef.validate((valid) => {
|
|||
|
|
if (valid) {
|
|||
|
|
validResolve()
|
|||
|
|
} else {
|
|||
|
|
this.activeTabIndex = i.toString()
|
|||
|
|
validReject(new Error(`文件 ${i + 1} 信息填写不完整`))
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
allFormData.push({ ...this.fileTabs[i].formData })
|
|||
|
|
}
|
|||
|
|
resolve(allFormData)
|
|||
|
|
} catch (error) {
|
|||
|
|
reject(error)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped lang="scss">
|
|||
|
|
.file-container {
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.basic-info-title {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
margin: 10px 0 20px;
|
|||
|
|
|
|||
|
|
.title-content {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
span {
|
|||
|
|
margin-left: 8px;
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: #1d2129;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
img {
|
|||
|
|
width: 20px;
|
|||
|
|
height: 20px;
|
|||
|
|
object-fit: contain;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.file-tabs {
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.add-tab-btn {
|
|||
|
|
color: #409EFF;
|
|||
|
|
border: 1px dashed #409EFF;
|
|||
|
|
padding: 6px 12px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.file-form {
|
|||
|
|
padding: 20px 10px 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
::v-deep .el-tabs__item {
|
|||
|
|
padding: 0 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
::v-deep .el-tabs__close-btn {
|
|||
|
|
margin-left: 8px;
|
|||
|
|
}
|
|||
|
|
</style>
|