封装文件上传
更新: 2025/3/20 11:11:44 字数: 0 字
需求分析
我们的前端使用的是 element-plus
组件库,他有自己的上传组件 el-upload
,但是它有缺陷,也就是与我们的需求不符。
功能需求:
- 可以点击打开资源文件管理器,选择文件上传,也可以使用拖拽到指定位置进行文件上传。
- 当文件上传到指定个数后,隐藏上传框,显示已上传文件列表。
- 上传文件列表可以点击文件名或者点击下载按钮进行下载,可以点击删除按钮进行删除。
实现结果展示
功能实现效果:
- 可以点击打开资源文件管理器,选择文件上传。
- 可以将要上传的文件拖拽到对应的区域进行文件上传。
- 可以点击文件名或者点击下载按钮进行下载。
- 可以点击删除按钮进行删除。

封装文件上传组件
props 传值
vue
<script setup>
import {useVModel} from "@vueuse/core";
const $emit = defineEmits(['update:modelValue', 'update:disabled'])
const props = defineProps({
// fileList: [ {"name": "sql专题.docx", "diskName": "20250320103839_28.docx", "raw": "[object File]", "uid": 20473, "fileId": 20473 }],
// url: 'xxxx'
fileData: {
type: Object,
default: () => {
}
},
disabled: {
type: Boolean,
default: false
},
limit: {
type: Number,
default: 1
},
// 允许上传的文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array || String,
default: '*'
},
fileSize: {
type: Number,
default: 10,
},
})
const modelValue = useVModel(props, "fileData", $emit)
</script>
展示结构
vue
<template>
<div class="my-upload">
<!--上传区域 -->
<div
ref="uploadRef"
class="upload-input"
:class="{ 'dragging': isDragging, 'maskLayer': disabled }"
v-show="showUpload"
>
<input
ref="fileInputRef"
type="file"
id="file-input"
style="display: none"
:accept="fileAccept"
@change="onImageAdded"
:disabled="disabled"
/>
<div
class="card upload-card"
@click="openFileDialog"
>
<!--文件上传的 svg + 描述文字 -->
<el-icon class="svg-icon">
<upload-filled/>
</el-icon>
<div class="el-upload__text">
拖拽文件到此处 或者 <em :class="{'strikethrough':disabled, 'link-type':!disabled}">点击上传</em>
</div>
</div>
</div>
<!--提示信息 -->
<span v-show="showUpload" style="font-size: 12px; margin-left: 5px;">请上传 {{ fileTypeName }} 类型的文件,且大小不要超过 {{
fileSize
}} MB </span>
<!--文件展示文件列表 -->
<div>
<div class="list-item" v-for="(item, index) in modelValue.fileList" :key="index">
<div class="item-left link-type" :title="item.name" @click="handleOnlineView(item)">
<el-icon style="margin-right: 3px">
<Document/>
</el-icon>
{{ item.name }}
</div>
<el-icon class="link-type" @click="handlePreview(item)">
<Download/>
</el-icon>
<el-button :disabled="disabled" type="text" size="small" @click="handleRemove(item)">删除</el-button>
</div>
</div>
<!-- 图片预览 -->
<el-image-viewer
v-if="showImagePreview"
:zoom-rate="1.2"
@close="closePreview"
:url-list="imgPreviewList"
/>
</div>
</template>
点击进行文件上传
点击上传框会触发 openFileDialog
方法,然后调用 fileInputRef.click()
方法,打开资源文件管理器,选择文件上传。
js
// 打开文件选择框
function openFileDialog() {
fileInputRef.value.click();
}
当我们选择完成文件后,会触发 input
上传的 change
事件,会执行 onImageAdded
方法,然后调用 uploadFile
方法,进行文件上传。
- 判断文件是否为空,并且文件数量是否超过限制,如果为 0 或者文件数量超过限制,直接返回.
- 判断文件后缀是否在允许的文件类型中.
- 判断文件大小是否符合要求.
js
// 上传文件
function onImageAdded() {
let fileInput = fileInputRef.value;
// 1. 判断文件是否为空,并且文件数量是否超过限制,如果为 0 或者文件数量超过限制,直接返回
if (fileInput.files.length == 0 || (modelValue.value.fileList && modelValue.value.fileList.length >= props.limit)) {
return;
}
let file = fileInput.files[0];
// 2. 判断文件后缀是否在允许的文件类型中
let fileExtension = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase();
console.log(fileExtension)
// 判断文件后缀是不是在允许的文件类型
if (props.fileType !== '*' && props.fileType.indexOf(fileExtension) == -1) {
ElMessage.error("文件格式不正确!");
// 清除 input 中的文件
fileInput.value = '';
return;
}
// 3. 判断文件大小
if (file.size / 1024 / 1024 > props.fileSize) {
ElMessage.error("文件大小不能超过 " + props.fileSize + "MB!");
// 清除 input 中的文件
fileInput.value = '';
return;
}
upload(file);
}
符合要求后,调用 upload
方法,进行文件上传。
下面是文件上传的逻辑,主要解决两个问题:
- 文件上传成功后,会返回一个文件 id,然后保存到
modelValue.value.fileId
中,用于后续的删除操作。 - 上传成功后如果是
js
// 文件上传
function upload(file) {
// 上传图片
let param = new FormData(); // 创建form对象
param.append("file", file, file.name); // 通过append向form对象添加数据
console.log("fileLenth", file.name.length); //
if (file.name.length > 50) {
ElMessage.error("文件名称长度不能超过 50 个字符!")
return;
}
let config = {
headers: {"Content-Type": "multipart/form-data"}
};
proxy.$modal.loading("文件上传中...")
// 添加请求头
service.post(modelValue.value.url, param, config)
.then(req => {
modelValue.value.fileList.push({
name: req.filename,
diskName: req.target,
raw: file,
uid: req.id,
fileId: req.id,
onlineUrl: req.onlineUrl
})
setTimeout(() => {
isProgressVisible.value = false;
isSuccessLabelVisible.value = true;
}, 200);
}).catch((err) => {
isProgressVisible.value = false;
// 清除 input 中的文件
fileInputRef.value.value = '';
}).finally(() => {
proxy.$modal.closeLoading()
})
}
删除文件
js
/**
* 删除文件
* @param file
*/
function handleRemove(item) {
if (item.fileId) {
deleteFile(item.fileId, item.diskName).then(res => {
modelValue.value.fileList = modelValue.value.fileList.filter(v => {
return v.fileId !== file.fileId
})
ElMessage.success(res.msg)
})
}
}
文件预览 (仅限于图片)
js
const handleOnlineView = (file) => {
// 取出文件后缀
const fileType = file.name.split('.').pop();
console.log("fileType", fileType)
// 判断文件类型是不是允许的图片类型
if (['jpg', 'jpeg', 'png', 'gif'].includes(fileType) && file.name.url) {
const url = import.meta.env.VITE_APP_ADDR + file.name.url
showImage(url, file.name)
} else {
handlePreview(file)
}
}
拖拽上传文件
js
const handleDragenter = (e) => {
e.preventDefault()
if(!isDragging.value) isDragging.value = true
}
const handleDragleave = (e) => {
e.preventDefault()
if(isDragging.value) isDragging.value = false
}
const handleDrop = (event) => {
event.preventDefault();
isDragging.value = false;
const file = event.dataTransfer.files[0];
// 触发文件上传 ———— 记得要校验哦
upload(file);
}
onMounted(() => {
// 当用户将拖拽的文件或元素释放(松开鼠标)到目标区域时触发。
uploadRef.value.addEventListener('drop', handleDrop)
// 当用户拖拽的文件或元素离开目标区域时触发。
uploadRef.value.addEventListener('dragleave', handleDragleave)
// 当用户拖拽的文件或元素首次进入目标区域时触发。
uploadRef.value.addEventListener('dragenter', handleDragenter)
// 当用户拖拽的文件或元素在目标区域内移动时触发。
uploadRef.value.addEventListener('dragover', handleDragenter)
})
onUnmounted(() => {
if (uploadRef.value) {
uploadRef.value.removeEventListener('drop', handleDrop)
uploadRef.value.removeEventListener('dragleave', handleDragleave)
uploadRef.value.removeEventListener('dragenter', handleDragenter)
uploadRef.value.removeEventListener('dragover', handleDragenter)
}
})