Vue3 多文件上传
需求上传多个文件且保证上传的excel文件与pdf文件名一致并按先后顺序排列弹层页面template div el-dialog :titleprops.title v-modeldialogInfo.uploadVisible :close-on-click-modalfalse custom-classdialogBox :widthprops.dialogWidth top10% :append-to-bodytrue :close-on-press-escapefalse el-upload v-ifdialogInfo.uploadVisible classupload-demo action# :multipleprops.isSingle :on-removehandleRemove :on-changeupPic :auto-uploadfalse :file-listdialogInfo.fileList :on-exceedwarningLimit :limitprops.maxQuantity el-button typeprimary clickupload{{ 导入 }}/el-button template v-slot:tip div classel-upload__tip{{ props.hint }}/div /template /el-upload ul v-ifprops.isShowFileList props.isTrueFileList.length stylemargin-top: 20px li v-for(item, index) in props.isTrueFileList :keyindex stylemargin-left: 8px; width: 100% classel-icon-document a stylemargin-left: 5px{{ item.fileName }}/a a classmc styleposition: absolute; right: 30px title删除 clickdelFile(item.id)em classel-icon-close //a /li /ul template v-slot:footer span classdialog-footer el-button click(dialogInfo.uploadVisible false), resetFileLists() {{ 取消 }} /el-button el-button typeprimary :loadingdialogInfo.btnLoading clickimportSure{{ 确定 }}/el-button /span /template /el-dialog /div /templatescript setup import { defineProps, reactive } from vue import { uploadUsingPost1 } from /api/global import { ElMessage } from element-plus const emit defineEmits([delFile, missingPersonUpload]) const props defineProps({ // 最大上传数量 maxQuantity: { type: Number, default: 20 }, // 提示信息 hint: { type: String, default: 选择需要上传的文件 }, // 文件名限制 astrict: { type: String, default: }, // 是否显示已上传文件列表 isShowFileList: { type: Boolean, default: false }, // 已上传文件列表 isTrueFileList: { type: Array, default: () [] }, // 对话框宽度 dialogWidth: { type: String, default: 35% }, // 是否单选 isSingle: { type: Boolean, default: true }, // 文件格式 uploadFormat: { type: Array, default: () [] }, title: { type: String, default: 上传附件 }, // 是否通过公用方法上传至服务器 isUpload: { type: Boolean, default: true }, // 上传完毕是否立即关闭对话框 isUploadVisible: { type: Boolean, default: false }, // 自定义 formData 中 文件 key 值 customKey: { type: String, default: files } }) const dialogInfo reactive({ uploadVisible: false, file: {}, fileData: {}, // 文件列表 fileList: [], // 确定loading btnLoading: false, formatErrorNotified: false }) /** * description: * 打开对话框 * return { * } */ const open () { // 当可上传多份文件时,可不置空文件列表 dialogInfo.file {} dialogInfo.fileList [] dialogInfo.formatErrorNotified false dialogInfo.uploadVisible true } /** * description: * 删除文件的回调 * return { * } */ const handleRemove (file, fileList) { dialogInfo.fileList fileList } /** * description: * 删除已上传文件 * return { * } */ const delFile async (id) { await emit(delFile, id) } /** * description: * 上传文件的回调 * return { * } */ const upPic (files, fileList) { const getExt (filename) { const lastDotIndex filename.lastIndexOf(.) return lastDotIndex -1 ? : filename.slice(lastDotIndex 1).toLowerCase() } const fileExt getExt(files.name) if (props.uploadFormat.length !props.uploadFormat.map((f) f.toLowerCase()).includes(fileExt)) { // 从 fileList 中移除当前非法文件 const index fileList.findIndex((item) item.uid files.uid) if (index ! -1) { fileList.splice(index, 1) } dialogInfo.file files.raw dialogInfo.fileList sortFileList(fileList) // 只提示一次 if (!dialogInfo.formatErrorNotified) { ElMessage.error(上传文件只能是 ${props.uploadFormat.join(、)} 格式) dialogInfo.formatErrorNotified true // 可选延迟重置避免同一批上传再次触发比如 2 秒后 setTimeout(() { dialogInfo.formatErrorNotified false }, 2000) } return } const isValidSize files.size / 1024 / 1024 100 // 限制文件大小不超过100MB if (!isValidSize) { ElMessage.error(当前上传的文件过大建议不超过100MB) for (let i 0; i fileList.length; i) { const ele fileList[i] if (files.uid ele.uid) { fileList.splice(i, 1) break } } dialogInfo.file files.raw // 对剩余文件排序 dialogInfo.fileList sortFileList(fileList) return } dialogInfo.file files.raw // 对最新 fileList 排序 dialogInfo.fileList sortFileList(fileList) } /** * description: 按主文件名去扩展名 去空格排序使同名 xlsx/pdf 相邻 */ const sortFileList (list) { return [...list].sort((a, b) { const baseNameA a.name.replace(/\.[^/.]$/, ).trim() const baseNameB b.name.replace(/\.[^/.]$/, ).trim() if (baseNameA baseNameB) return -1 if (baseNameA baseNameB) return 1 // 主文件名相同时按扩展名排序 const extA a.name.split(.).pop().toLowerCase() const extB b.name.split(.).pop().toLowerCase() return extB.localeCompare(extA) }) } /** * description: * 文件提示 * param {} * */ const warningLimit () { ElMessage.warning(文件超出最大上传数量) } /** * description: * 限制单选多选 * param {e} 默认行为 * return { * } */ const upload (e) { if (props.maxQuantity 1 dialogInfo.fileList.length) { ElMessage.error(只能上传单个文件) e.stopPropagation() } } /** * description: * 重置所有文件列表 * return { * } */ const resetFileLists () { dialogInfo.file {} dialogInfo.secondFile {} } /** * description: * 确认上传 - 处理两个文件列表 * return { * } */ const importSure () { // 首先把dialogInfo.fileList中的文件按照每一项的name值进行分类 分成xlsxList和pdfList两组 因为name包含了文件名和后缀 // 所以先把按后缀把dialogInfo.fileList分成两部分然后判断两部分的数量是否一样例如xlsxList.length要等于pdfLits.length // 再然后 判断xlsxList中的文件名与pdfList的文件名是否一样如果去空和去掉后缀的名字不一样那么进行提示 // 非空校验 - 检查两个文件列表 const hasFirstFile dialogInfo.fileList.length 0 // 校验两个上传区域都必须有文件 if (!hasFirstFile) { return ElMessage.warning(请上传文件!) } //按扩展名分类 const xlsxList [] const pdfList [] dialogInfo.fileList.forEach((file) { const ext file.name.split(.).pop().toLowerCase() if (ext xlsx || ext xls) { xlsxList.push(file) } else if (ext pdf) { pdfList.push(file) } }) // 校验数量是否相等 if (xlsxList.length ! pdfList.length) { return ElMessage.warning(EXCEL文件与PDF文件数量不相等) } // 校验文件名是否一致去空格去扩展名 const getBaseName (filename) { return filename.replace(/\.[^/.]$/, ).trim() } // 创建主文件名集合用于快速查找 const pdfBaseNames new Set(pdfList.map((file) getBaseName(file.name))) for (const xlsxFile of xlsxList) { const xlsxBaseName getBaseName(xlsxFile.name) if (!pdfBaseNames.has(xlsxBaseName)) { return ElMessage.warning(找不到与EXCEL文件 ${xlsxFile.name} 对应的PDF文件) } } console.log(xlsxList, xlsxList, pdfList, pdfList) // 触发上传事件 emit(missingPersonUpload, { firstFiles: xlsxList, // EXCEL文件 secondFiles: pdfList // PDF文件 }) dialogInfo.uploadVisible props.isUploadVisible } // 抛出去的方法 open defineExpose({ open, delFile }) /scriptstyle langscss scoped ::v-deep .el-upload-list { height: 150px; overflow: auto; } /style使用方法//界面引入文件 uploadMoreFile是我给组件的命名 uploadMoreFile refrefMoreUploadFile :is-singletrue :upload-formatmoreUploadFormat missingPersonUploadmissingPersonMoreUpload /script setup import { reactive, onMounted, ref } from vue import uploadMoreFile from workspace/views/aaa/components/uploadMoreFile.vue const moreUploadFormat ref([xlsx, pdf]) const refMoreUploadFile ref(null) const refErrorText ref(null) //处理上传逻辑的,这里写自己的逻辑 const missingPersonMoreUpload (list) { const fileExcels list.firstFiles const filePdfs list.secondFiles } /script