需求:上传多个文件,且保证上传的excel文件与pdf文件名一致,并按先后顺序排列
弹层页面:
html
<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>
javascript
<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>
css
<style lang="scss" scoped>
::v-deep .el-upload-list {
height: 150px;
overflow: auto;
}
</style>
使用方法:
html
//界面引入文件, uploadMoreFile是我给组件的命名
<uploadMoreFile ref="refMoreUploadFile" :is-single="true" :upload-format="moreUploadFormat" @missingPersonUpload="missingPersonMoreUpload" />
javascript
<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>
