当前位置: 首页 > news >正文

Vue3 多文件上传

需求:上传多个文件,且保证上传的excel文件与pdf文件名一致,并按先后顺序排列

弹层页面:

<template> <div> <el-dialog :title="props.title" v-model="dialogInfo.uploadVisible" :close-on-click-modal="false" custom-class="dialogBox" :width="props.dialogWidth" top="10%" :append-to-body="true" :close-on-press-escape="false" > <el-upload v-if="dialogInfo.uploadVisible" class="upload-demo" action="#" :multiple="props.isSingle" :on-remove="handleRemove" :on-change="upPic" :auto-upload="false" :file-list="dialogInfo.fileList" :on-exceed="warningLimit" :limit="props.maxQuantity" > <el-button type="primary" @click="upload">{{ '导入' }}</el-button> <template v-slot:tip> <div class="el-upload__tip">{{ props.hint }}</div> </template> </el-upload> <ul v-if="props.isShowFileList && props.isTrueFileList.length" style="margin-top: 20px"> <li v-for="(item, index) in props.isTrueFileList" :key="index" style="margin-left: 8px; width: 100%" class="el-icon-document"> <a style="margin-left: 5px">{{ item.fileName }}</a> <a class="mc" style="position: absolute; right: 30px" title="删除" @click="delFile(item.id)"><em class="el-icon-close" /></a> </li> </ul> <template v-slot:footer> <span class="dialog-footer"> <el-button @click="(dialogInfo.uploadVisible = false), resetFileLists()"> {{ '取消' }} </el-button> <el-button type="primary" :loading="dialogInfo.btnLoading" @click="importSure">{{ '确定' }}</el-button> </span> </template> </el-dialog> </div> </template>
<script 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 }) </script>
<style lang="scss" scoped> ::v-deep .el-upload-list { height: 150px; overflow: auto; } </style>

使用方法:

//界面引入文件, uploadMoreFile是我给组件的命名 <uploadMoreFile ref="refMoreUploadFile" :is-single="true" :upload-format="moreUploadFormat" @missingPersonUpload="missingPersonMoreUpload" />
<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>

http://www.gsyq.cn/news/1512570.html

相关文章:

  • 如何快速掌握抖音直播数据抓取:DouyinLiveWebFetcher的完整实践指南
  • 别再死记硬背了!用特勒根定理5分钟搞定‘黑匣子’电路分析题
  • 魔兽争霸3终极优化指南:5分钟快速解决游戏兼容性问题
  • 2026年GEO优化服务商排名解析:选型参考与避坑指南 - 信息热点
  • 湖北户外照明新选择:众晨光电全场景灯具应用解析 - 资讯报道
  • OpenClaw + 云数据库运维:自动备份、扩容、迁移 RDS/MySQL 云数据库
  • 编写程序统计节日聚餐饮食数据,计算暴饮暴食肠胃负担,给出餐后调理方案。
  • 2026中级经济师培训机构怎么选?学员真实测评:这类备考模式更靠谱
  • AutoJs6:重新定义Android平台JavaScript自动化工具的未来
  • D3keyHelper终极指南:暗黑3自动化操作一键配置完全教程
  • 2026上海屋顶防水|专业防潮防水补漏公司排名 - 信息热点
  • 终极Windows系统清理工具:专业级EdgeRemover助你彻底卸载Microsoft Edge
  • 《C#2.0宝典》配套WebForms实战源码:24个独立页面+后台逻辑,VS直接运行
  • 河北公路护栏网厂家实测排行:合规性与适配性对比 - 奔跑123
  • 越城区配偶向第三者转账,钱还能要回来吗?本地律师实测排行 - 边虞技术
  • 金盐废料回收公司排行榜:氰化金钾等品类回收资质盘点 - 品牌2026
  • 2026梵克雅宝四叶草系列首饰成都回收,款式热度折价标准一览 - 奢侈品回收评测
  • 有技术团队的AI搜索优化机构内容合规标准算法适配逻辑科普解读 - 信息热点
  • 昆明卡地亚欧米茄回收实测|金价连跌,女表变现哪家更划算 - 奢侈品回收评测
  • 张家口企业想做AI GEO优化找哪家公司靠谱? - 信息热点
  • 固定资产管理系统的发展趋势对企业的数字化转型有哪些影响?
  • MPC7441架构解析:PowerPC与AltiVec技术如何重塑嵌入式高性能计算
  • 2026广州包包回收指南:怎么卖不亏?哪家店报价透明?一篇看懂 - 奢侈品回收评测
  • 制造型企业数据整合:图纸、BOM、订单的AI集成方案
  • AhMyth Android RAT实战指南:从架构解析到渗透测试应用
  • 异构双核MCU+DSP架构解析:以DSP56654为例的设计与编程实战
  • (一)YModbus开篇:为什么工控调试离不开 Modbus?
  • 金盐回收哪家公司价格高?纯度标定与损耗率的计算逻辑 - 品牌2026
  • 从物理波的叠加到数学公式:用Desmos动态演示帮你直观理解sin(α+β)
  • (二)Modbus协议入门:工控调试先把这几个概念搞明白