最近公司框架改用vue3了,项目多数都是后台管理系统,所以在网上各种找着有没开源的上传组件支持多类型多文件单次上传,找了好久都无果,所以只好自己改了一个并分享一下成果
依赖方面的话使用了:element-ui、v-viewer、viewerjs
npm install v-viewer viewerjs
代码方面就比较粗糙+粗暴了,组件还有很多可以升级改造的地方,只不过我这切图仔还有很多业务代码要写,所以有优化跟bug的地方请大方指出,我会虚心接受
<script setup>
import { fileUpload,fileDownload } from '@/api/common';
import { reactive, ref, onMounted, watch, defineEmits, defineProps } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { api as viewerApi } from "v-viewer";
import doc from './images/doc.png';
import pdf from './images/pdf.png';
import txt from './images/txt.png';
import xls from './images/xls.png';
import rar from './images/rar.png';
import zip from './images/zip.png';
import other from './images/other.png';
import video from './images/video.png';
import audio from './images/audio.png';
import "viewerjs/dist/viewer.css";
// 定义组件的 props
const props = defineProps({
name: { type: String, required: false, default: 'files' }, // 上传字段的名称
images: { type: Array, required: false, default: () => [] }, // 图片数组
size: { type: Number, required: false, default: 100 }, // 文件大小
imageUrlKey: { type: String, required: false, default: () => "url" }, // 预览文件的 key
accept: { type: String, required: false, default: () => "" } // 上传文件的接受类型
});
// 定义事件
const emit = defineEmits(['uploadSuccess']);
const upload = (data)=>{
emit("uploadSuccess",data)
}
// 上传文件相关的一些状态和方法
const iconSize = ref('20'); // 图标大小
const iconColor = ref('#fff'); // 图标颜色
const uploadId = ref(Math.random().toString(36).substr(2).toLocaleUpperCase()); // 上传组件的唯一标识
const fileList = ref([]); // 文件列表
const fm = ref(new FormData()); // FormData 对象
const uploadFiles = ref([]); // 上传的文件列表
const fileTotal = ref(0); // 文件总数
const Uploader = ref(null); // 上传组件的引用
const $viewer = ref(null); // Viewer 实例
// 文件预览相关状态和方法
const videoEle = ref(null); // 视频元素
const audioEle = ref(null); // 音频元素
const mediaTitle = ref(''); // 媒体标题
const mediaUrl = ref(''); // 媒体 URL
const dialogVideoVisible = ref(false); // 视频预览弹窗是否可见
const dialogAudioVisible = ref(false); // 音频预览弹窗是否可见
/**
* 监听文件初始化
*/
watch(() => props.images,(nvl,avl)=>{
fileList.value = nvl.map(item => item);
},{ deep: true, immediate: true })
/**
* 视频预览弹窗关闭
*/
const videoClose = (file) =>{
videoEle.value.pause()
dialogVideoVisible.value = false
}
/**
* 音频预览弹窗关闭
*/
const audioClose = (file) =>{
audioEle.value.pause()
dialogAudioVisible.value = false
}
/**
* 视频预览
*/
const handleVideoPreview = (file) =>{
mediaTitle.value = file.originalName
mediaUrl.value = file.domainUrl + file.link.replace(/^\/{2}/, '')
dialogVideoVisible.value = true
}
/**
* 音频预览
*/
const handleAudioPreview = (file) =>{
mediaTitle.value = file.originalName
mediaUrl.value = file.domainUrl + file.link.replace(/^\/{2}/, '')
dialogAudioVisible.value = true
}
/**
* 图片预览
*/
const handlePictureCardPreview = (index) => {
// 存在多类型的文件,过滤出图片做预览
const imageList = fileList.value.filter(item=>suffix.value.image.includes(item.extension));
// 取到点击预览的文件的id (可自行调整)
const fileIndexsId = fileList.value[index].id
// 取到过滤后的预览的文件的索引 (可自行调整)
const fileIndexs = imageList.findIndex(item=>item?.id === fileIndexsId)
if (!imageList || imageList.length === 0) {
console.error("请传入正确的图片数组");
return;
}
const previewConfig = {
options: {
toolbar: true,
initialViewIndex: fileIndexs
},
images: imageList
};
if (imageList[0][props.imageUrlKey]) {
previewConfig.options.url = props.imageUrlKey;
}
$viewer.value = viewerApi(previewConfig);
}
const fileHandlers = {
// 业务图片是根据后台返回的ip跟相对地址组成,可自行更改
image: file => file.domainUrl + file.link.replace(/^\/{2}/, ''),
xls: () => window.location.origin + xls,
doc: () => window.location.origin + doc,
pdf: () => window.location.origin + pdf,
txt: () => window.location.origin + txt,
zip: () => window.location.origin + zip,
rar: () => window.location.origin + rar,
video: () => window.location.origin + video,
audio: () => window.location.origin + audio,
};
const suffix = ref({
image: ['jpg', 'jpeg', 'png', 'gif'], // 图片类型
xls: ['xls', 'xlsx'], // Excel 类型
doc: ['doc', 'docx'], // Word 类型
pdf: ['pdf'], // PDF 类型
txt: ['txt'], // 文本类型
zip: ['zip'], // 压缩包类型
rar: ['rar'], // RAR 类型
video: ['avid', 'mp4', 'ogv', 'webm', 'mov'], // 视频类型
audio: ['mp3', 'midi', 'cd'], // 音频类型
})
/**
* 文件类型处理方法,根据文件类型返回对应的处理 URL
*/
const checkSuffix = (file) => {
let fileType
for (let handlerType in suffix.value) {
if (suffix.value[handlerType].includes(file.extension)) {
fileType = fileHandlers[handlerType](file);
break
}
}
return fileType || ( window.location.origin + other )
};
/**
* 文件上传处理
*/
const httpRequest = async (file) => {
fm.value.append(props.name, file.file);
//当fm getall的数组长度与filetotal的长度一致,文件准备完成
if (fm.value.getAll(props.name).length === fileTotal.value) {
try {
const { data } = await fileUpload(fm.value);
fileList.value.push(...data.data.map(item => ({ ...item, url: checkSuffix(item) })));
upload(fileList.value);
} catch (error) {
console.log(`上传文件出错`, error);
} finally {
//无论成功与失败都要清空文件列表!!
uploadFiles.value = [];
fm.value.delete(props.name);
}
}
}
/**
* 上传前回调
*/
const handleBeforeUpload = (file) => {
const isSize = file.size / 1024 / 1024 < props.size;
if (!isSize) {
ElMessage({
message: "上传图片大小不能超过 4MB!",
type: 'error',
});
}
return isSize;
}
/**
* 文件上传change
*/
const handleChange = (file,) => {
//获取饿了么添加文件进来时的状态
(file.status == 'ready') && uploadFiles.value.push(file.raw);
//获取原始文件的个数
fileTotal.value = document.getElementsByName(uploadId.value)[0].files.length;
//如果原始文件和上传个数相同的时候就说明已经全部添加完成
if (uploadFiles.value.length === fileTotal.value) {
const ele = Uploader.value;
ele.submit();
}
}
/**
* 文件下载
* 后台返回的为文件流的处理
*/
const handleDownload = async (file) => {
try {
const res = await fileDownload(file.id);
const fileName = res.headers['content-disposition']
? res.headers['content-disposition']
.split(';')[1]
.split('=')[1]
.replace('"', '')
.replace('"', '')
: new Date().getTime() + '.' + file.extension;
const blob = res.data;
const url = URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', decodeURIComponent(fileName));
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
console.error(`下载文件出错:`, error);
}
};
/**
* 移除图片回调
*/
const handleRemove = (file) => {
const index = fileList.value.findIndex(item => item.id === file.id);
if (index !== -1) fileList.value.splice(index, 1);
upload(fileList.value);
}
需要拿源码的自取吧 :
github地址:GitHub - PaiDaXing-xuan/u-upload
gitee地址:u-upload: Element Plus上传组件二次封装,支持多类型多文件单次上传
如果觉得这文章分享的代码觉得对你有帮助的话,记得留下你的赞哦~谢谢