文件上传
可以点击/拖拽上传文件🔗element-plus
效果
点击
思路
1. 选择文件
只有 <input type="file">
可以选择文件,但是input
标签是浏览器原生组件,无法修改样式,所以使用<input type="file" hidden>
隐藏,也可以使用 display:none
隐藏
在点击其他元素触发 input 的 change
事件,也有两种方式
1. 使用 label
标签,点击 label 触发 input 的 change 事件
html
<label for="input">
<span>选择文件 </span>
<input id="input" type="file"
@change="changeFileInput"
hidden
:multiple="multiple"
ref="inputRef">
</label>
2. 使用 $refs
获取元素,触发 change 事件
vue
<template>
<div>
<el-button @click="handleClick">选择文件</el-button>
<input id="input" type="file" @change="changeFileInput" hidden :multiple="multiple" ref="inputRef">
</div>
</template>
<script>
const inputRef = ref(null)
const handleClick = () => {
inputRef.value!.click()
}
</script>
2. 获取文件
在 input
的 change
事件中,有一个参数 (e.target as HTMLInputElement).files
代表了所选的文件
对所选的文件进行处理,添加唯一标识 uid(file.uid = Date.now()
),便于以后删除
3. 展示文件
URL.createObjectURL
接收 file/blob
作为参数,返回一个 URL
对象,这个 URL
对象可以作为 <img>
的 src
4. 拖拽上传
使用 @drop.prevent="dragFile"/@dragleave.prevent/@dragover.prevent
事件进行监听是否有文件拖拽到元素上,由于dragleave/dragover
的默认行为是打开文件,所以需要阻止默认行为
在 drop
事件中触发的 dragFile
方法中,使用 event.dataTransfer.files
获取所拖拽的文件,剩下的和 input 流程一样
5. 删除文件
主要是通过唯一标识 uid
进行删除,同时为了避免内存泄漏,需要使用 URL.revokeObjectURL
解除引用
ts
const deleteFile = (delfile: UploadFile) => {
filesRef.value = filesRef.value.filter(file => delfile.uid !== file.uid)
URL.revokeObjectURL(delfile.url)
}
源码
vue
<template>
<div>
<el-switch v-model="isDrag" class="mb-4" active-text="拖拽" inactive-text="点击"></el-switch>
<div class="
drag
" v-if="isDrag" @drop.prevent="dragFile" @dragleave.prevent @dragover.prevent>
<span>+</span>
</div>
<!-- label -->
<div for="input" v-else>
<!-- <span>选择文件 </span> -->
<el-button @click="handleClick">选择文件</el-button>
<input id="input" type="file" @change="changeFileInput" hidden :multiple="multiple" ref="inputRef">
</div>
<transition-group class="list" tag="div">
<div v-for="file in filesRef" :key="file.uid" class="item">
<img :src="file.url" class="img">
<p class="name">{{ file.name }} </p>
<div class="close" @click="deleteFile(file)">
<p>❌</p>
</div>
</div>
</transition-group>
</div>
</template>
<script lang="ts" setup>
import { ref, shallowRef, watch } from 'vue';
export interface IUploadRawFile extends File {
uid: number
}
export interface UploadFile {
name: string
size?: number
uid: number
url: string
raw?: IUploadRawFile
}
const isDrag = ref(false);
const multiple = ref(false);
const limit = ref(Infinity);
const filesRef = ref<UploadFile[]>([])
// 限制 limit 数量
const onExceed = (file: File[], filesRef: UploadFile[]) => {
console.log(file, filesRef)
}
// 上传之前
const onBeforeLoad = (file: IUploadRawFile) => {
console.log(file)
return true
}
// 上传成功
const onSuccess = (file: IUploadRawFile, filesRef: UploadFile[]) => {
console.log(file, filesRef)
}
const dragFile = (e: DragEvent) => {
if (!e.dataTransfer?.files) return;
uploadFiles(Array.from(e.dataTransfer.files))
}
/**
*
* @param e input 选择的 file
*/
const changeFileInput = (e: any) => {
const files = (e.target as HTMLInputElement).files
// 构建自己的对象
if (!files) return
uploadFiles(Array.from(files))
}
/**
* @description 删除操作
* @param delfile 要删除的文件
*/
const deleteFile = (delfile: UploadFile) => {
filesRef.value = filesRef.value.filter(file => delfile.uid !== file.uid)
URL.revokeObjectURL(delfile.url)
}
const uploadFiles = (files: File[]) => {
if (files.length === 0) return;
if (limit && filesRef.value.length + files.length > limit.value) {
return onExceed(files, filesRef.value)
}
if (!multiple) {
files = files.slice(0, 1)
}
for (const file of files) {
const rawFile = file as IUploadRawFile;
// 对原生 file 添加 uid
rawFile.uid = genFileId();
handleStart(rawFile)
// 准备上传
upload(rawFile);
}
}
// 组装 file
const handleStart = (file: IUploadRawFile) => {
let uploadFile: UploadFile = {
size: file.size,
url: URL.createObjectURL(file),
raw: file,
uid: file.uid,
name: file.name
}
filesRef.value = [...filesRef.value, uploadFile]
}
const upload = (rawFile: IUploadRawFile) => {
if (!onBeforeLoad) {
return doUpload(rawFile)
};
let r = true;
if (onBeforeLoad) {
r = onBeforeLoad(rawFile)
if (r) {
doUpload(rawFile)
}
}
}
const doUpload = (file: IUploadRawFile) => {
// 如果是 成功态的话不用 上传
console.log("执行上传")
onSuccess(file, filesRef.value)
}
const inputRef = shallowRef<HTMLInputElement>();
const handleClick = () => {
// 清空 input 的value 值
inputRef.value!.value = ''
inputRef.value!.click()
}
/**
* @return 生成唯一 uid
*/
function genFileId() {
return Date.now()
}
</script>
<style lang="scss" scoped>
.drag {
@apply h-[200px] aspect-square flex rounded border-2 border-dashed border-red-500 items-center justify-center cursor-pointer p-2 text-3xl
}
.list {
@apply flex gap-7 mt-4;
.item {
@apply h-[200px] flex flex-col p-2 relative aspect-square rounded-sm border-dashed border-blue-400;
.img {
@apply object-contain h-full
}
.name {
@apply truncate m-0 text-center text-green-600 mt-auto;
}
.close {
@apply absolute inset-0 z-10 opacity-0 flex items-center justify-center text-4xl transition-opacity duration-300;
p {
@apply hidden cursor-pointer
}
}
&:hover {
@apply bg-white bg-opacity-80 border-red-300;
.img,
.name {
@apply opacity-40;
}
.close {
@apply opacity-100;
p {
@apply block
}
}
}
}
}
</style>