企业级表单与文件上传统一管理方案
1.概念
说明:通过父组件统一管理多个子组件表单(包括上传文件表单),子组件通过函数回调暴露异步校验接口,父组件使用 Promise.all 同步校验各表单,整合表单数据和上传文件信息后统一提交接口。
1.子组件
说明:
- 负责文件上传逻辑、显示文件列表、校验文件必填/大小/类型;
- 封装
validateFieldAsync()异步校验方法; - 使用
props.getChildFile将方法暴露给父组件; - 支持事件回调:上传前 (
before-upload-child) 和手动提交 (submit-upload-child)。
<template><div class="upload"><!-- 上传组件容器 --><div class="upload-component"><!-- 表单容器 --><el-form :form="form" ref="form" :model="form"><!-- 表单项 --><el-form-item label="保函扫描件" prop="scanfile" :rules="rules.scanfile"><!-- 上传组件 --><el-uploadref="upload" <!-- 引用,方便在方法里调用 -->drag <!-- 开启拖拽上传 -->multiple <!-- 允许上传多个文件 -->class="upload-file" <!-- 样式类 -->:auto-upload="true" <!-- 自动上传 -->:file-list="fileList" <!-- 上传文件列表 -->:before-upload="beforeUpload" <!-- 上传前钩子 -->action="/api/upload" <!-- 上传接口地址 -->:accept="accept" <!-- 可上传文件类型 -->><!-- 上传图标 --><i class="el-icon-upload" /><div class="el-upload__text">点击或拖拽文件到此处上传<p>只能上传 jpg、png、pdf 格式,单个文件不超过 10M</p></div><!-- 自定义文件列表展示 --><template #file="{ file }"><div class="upload-file-item"><i class="el-icon-document" @click="openFile(file)" /><span class="file-name" @click="openFile(file)">{{ file.name }}</span><i class="el-icon-delete" @click="handleRemove(file)" style="margin-left: 10px; color: #f56565; cursor: pointer;" /></div></template></el-upload></el-form-item></el-form></div></div>
</template><script>
export default {name: 'CustomUpload',props: {// 父组件传递函数,用于获取子组件方法getChildFile:{type:Function,default:()=>{}},// 标题,可配置title: {type: String,default: '保函扫描件'},// 可上传文件类型accept:{type: String,default: '.jpg,.png,.pdf'},// 单文件最大尺寸(MB)maxsize:{type: Number,default: 10}},data() {return {// 表单对象form:{scanfile: [] // 文件列表绑定字段},// 校验规则rules:{scanfile: [{ required: true, message: '请上传保函扫描件', trigger: 'change' },{validator: (rule, value, callback) => {if (!value || value.length === 0) {callback(new Error('请上传扫描件'))} else {callback()}},trigger: 'change'}]},// 上传文件列表fileList: []}},mounted() {// 父组件通过 props 获取子组件方法if(this.getChildFile){this.getChildFile({validateFieldAsync:this.validateFieldAsync // 暴露表单校验方法})}},methods: {// 异步校验方法,返回 Promise,供父组件调用validateFieldAsync(){let self=thisreturn new Promise((resolve, reject) => {self.$refs.form.validate(valid => {if (valid) {resolve(self.form) // 校验通过,返回表单数据}else {reject(false) // 校验失败}})})},// 上传前校验文件大小beforeUpload(file) {const maxSize = this.maxsize * 1024 * 1024if (file.size > maxSize) {this.$message.error(`文件大小不能超过${this.maxsize}MB`)return false}// 添加到 fileListthis.fileList.push(file)this.form.scanfile = this.fileList// 校验表单字段this.$refs.form.validateField('scanfile')// 向父组件派发事件this.$emit('before-upload-child', this.fileList)return false // 阻止自动上传},// 移除文件handleRemove(file) {// 从 fileList 过滤掉删除的文件this.fileList = this.fileList.filter(f => f.uid !== file.uid)this.form.scanfile = this.fileList// 校验表单字段this.$refs.form.validateField('scanfile')},// 上传文件变化(可选,用于同步 fileList)handleChange(file, fileList) {this.fileList = fileList},// 上传成功回调(可选)handleSuccess(response, file, fileList) {this.fileList = fileList},// 打开文件openFile(file) {const url = file.url || (file ? URL.createObjectURL(file) : '')if (url) window.open(url, '_blank')},// 提交上传,手动触发submitUpload() {if (!this.fileList.length) {this.$message.warning('请先选择文件再提交!')return}this.$emit('submit-upload-child',this.fileList)}}
}
</script><style scoped>
.upload-file{width: 100%;
}
:deep(.el-upload.el-upload--text){width: 100%;
}
:deep(.upload-file .el-upload-dragger){width: 100%;height: 100%;
}
</style>
2.父组件
说明:
管理主表单 (
childForm) 和上传表单 (childFile)接收子组件方法,统一调用
validateFieldAsync校验在所有表单通过校验后整合数据提交接口
<template><div><!-- 上传组件 --><custom-upload:get-child-file="getChildFile" <!-- 父组件获取子组件方法 -->@before-upload-child="beforeUploadChild" <!-- 上传前事件 -->@submit-upload-child="submitUploadChild" <!-- 手动提交事件 -->accept=".jpg,.png,.pdf" <!-- 可上传文件类型 -->:maxsize="1" <!-- 单文件最大尺寸(MB) -->/></div>
</template><script>
export default {data() {return {disabled: false, // 控制按钮或交互禁用状态childFile: null, // 存放子组件上传方法对象// 假设还有 childForm 存放普通表单方法}},methods: {// ================================// 父组件接收子组件方法// ================================getChildFile(param) {// 子组件回调过来的方法对象// param = { validateFieldAsync: function }this.childFile = param},// ================================// 上传前事件(可选)// ================================beforeUploadChild(fileList) {// fileList: 当前上传列表console.log('上传前文件列表', fileList)// 可以做额外逻辑:例如限制文件数量、提示等},// ================================// 手动触发上传事件(可选)// ================================submitUploadChild(fileList) {// fileList: 当前上传列表console.log('手动提交文件列表', fileList)},// ================================// 表单提交(整合文件表单 + 主表单)// ================================submitUpload(param) {let self = this;// 同时校验上传表单和主表单Promise.all([this.childFile.validateFieldAsync(), // 子组件上传表单校验this.childForm.validateFieldAsync() // 主表单校验]).then(([fileForm, mainForm]) => {// 处理上传文件信息const files = (fileForm.scanfile || []).map(item => ({name: item.name,url: item.url || "/no", // 如果没有 url 用占位type: item.type}))// 调用提交方法,将主表单 + 上传文件 + 其他信息一起提交self.submit({...mainForm, // 主表单数据letterScanPath: JSON.stringify(files), // 上传文件列表intentionId: self.detailData.intentionId, // 其他必要字段branchId: self.detailData.branchId})}).catch(() => {// 如果任意一个表单校验失败,可以做提示或阻止提交console.log('表单校验未通过')})},// ================================// 真正的提交方法(示例)// ================================submit(data) {console.log('最终提交数据', data)// TODO: 调用接口提交}}
}
</script>
3.效果图

4.总结
父子组件低耦合、表单校验统一、上传文件与表单字段绑定、数据整合集中、可复用和可扩展,适合复杂企业级表单场景。
