我这里是封装的i一个vue2上传文件的组件,因为项目需求 需要上传各种类型的文件以及文件夹,所以采用原生HTML5实现,废话不多说下面直接上代码,喜欢的宝子们记得点赞加关注哦~
对了Jacob的博客-优快云博客这也是我,这上面会有一些平时我发现的问题,感兴趣的可以去看一看
实现效果:
代码:
<template>
<div>
<div
ref="fileContainer"
class="file-container"
:style="{ height: height }"
@click="handlUpload"
>
<img src="@/assets/image/add-icon.png" class="icon" alt="">
<div class="text">将文件{{ isFolder ? "或文件夹" : "" }}拖拽至此,进行上传上传格式</div>
<div class="text">仅支持:{{ getType() }}{{ isFolder ? "、文件夹" : "" }}</div>
<input
v-if="isFolder"
ref="inputFile"
type="file"
style="display: none"
webkitdirectory
mozdirectory
odirectory
@change="inputChange"
>
<input
v-else
ref="inputFile"
type="file"
:multiple="multiple"
style="display: none"
@change="inputChange"
>
</div>
<div>
<div v-for="item in files" :key="item.fileId" class="file-item">
<div>{{ item.fileName }}</div>
<i class="el-icon-close item-icon" @click="deleteFile(item)" />
</div>
</div>
</div>
</template>
<script>
import { upload } from "@/api/public"
export default {
props: {
height: {
type: String,
default: "155px"
},
// 上传文件类型
accept: {
type: Array,
default: () => [
"doc",
"docx",
"pdf",
"xls",
"xlsx",
"jpg",
"jpeg",
"png",
"gif",
"dwg",
"dxf"
]
},
/** 点击上传时 是否选择文件夹 为true multiple属性不生效 注意HTML5目前不支持同时选择文件夹和文件 */
isFolder: {
type: Boolean,
default: true
},
// 点击上传非文件夹时 支持多选 当isFolder为true此属性不生效
multiple: {
type: Boolean,
default: true
},
parentFiles: {
type: Array,
default: () => []
}
},
data() {
return {
files: []
}
},
watch: {
parentFiles: {
handler(newValue, oldValue) {
// 当parentFiles发生变化时,这里会被调用
this.files = newValue
},
deep: true // 开启深度监听
}
},
mounted() {
// 进入时触发
this.$refs.fileContainer.addEventListener("dragenter", this.dragenter)
// 悬停时触发
this.$refs.fileContainer.addEventListener("dragover", this.dragover)
// 拖拽放下时触发
this.$refs.fileContainer.addEventListener("drop", this.drop)
},
destroyed() {
// 页面销毁时移除监听的事件
this.$refs.fileContainer?.removeEventListener("dragenter", this.dragenter)
this.$refs.fileContainer?.removeEventListener("dragover", this.dragover)
this.$refs.fileContainer?.removeEventListener("drop", this.drop)
},
methods: {
getType() {
const fileType = {
doc: "word",
docx: "word",
pdf: "PDF",
xls: "Excel",
xlsx: "Excel",
jpg: "图片",
jpeg: "图片",
png: "图片",
gif: "GIF",
dwg: "CAD",
dxf: "CAD"
}
const accepts = []
this.accept.forEach((item) => {
if (!accepts.includes(fileType[item])) {
accepts.push(fileType[item])
}
})
return accepts.join("、")
},
async uploadFile(file) {
try {
//上传文件接口 替换为自己的即可
const res = await upload(file)
this.files.push({
fileName: file.name,
size: file.size,
fileId: res.data
})
} catch (error) {
console.log("err====", error)
}
},
handlUpload() {
this.$refs.inputFile.click()
},
// 点击触发上传
async inputChange(e) {
if (!this.multiple && this.files.length > 0) {
this.$message.error(`只能上传单个文件!`)
return
}
const fileList = e.target.files
const promises = []
for (const file of fileList) {
const isAccept = this.accept.includes(this.getFileType(file.name))
if (!isAccept) {
this.$message.error(`文件${file.name}格式错误,`)
this.files = []
return false
}
// 需要使用fullPath替代webkitRelativePath 因为通过移入上传时拿不到 webkitRelativePath
file.fullPath = file.webkitRelativePath
promises.push(this.uploadFile(file))
}
e.target.value = ""
await Promise.all(promises)
this.$emit("change", this.files)
},
dragenter(e) {
e.preventDefault()
},
dragover(e) {
e.preventDefault()
},
// 文件后缀名
getFileType(name) {
const fileType = name.split(".")
return fileType[fileType.length - 1]
},
/**
* 判断是否为文件夹
*/
async isDirectory(entry) {
return new Promise((resolve, reject) => {
// 为文件夹时
if (entry.isDirectory) {
if (!this.isFolder) {
this.$message.error(`只能选择文件,而不是文件夹`)
return reject(false)
}
// 为文件夹时 创建读取器读取文件夹里面得内容
const reader = entry.createReader()
reader.readEntries((entries) => {
for (const entrie of entries) {
if (entrie.isDirectory) {
this.isDirectory(entrie)
} else {
entrie.file(async (file) => {
const isAccept = this.accept.includes(this.getFileType(file.name))
if (!isAccept) {
this.$message.error(`文件${file.name}格式错误,`)
this.files = []
return reject(false)
}
// 需要使用fullPath替代webkitRelativePath 这里拿不到webkitRelativePath
file.fullPath = entrie.fullPath
await this.uploadFile(file)
return resolve(true)
})
}
}
})
} else {
entry.file(async (file) => {
const isAccept = this.accept.includes(this.getFileType(file.name))
if (!isAccept) {
this.$message.error(`文件${file.name}格式错误,`)
this.files = []
return reject(false)
}
file.fullPath = entry.fullPath
await this.uploadFile(file)
return resolve(true)
})
}
})
},
// 文件移入上传
async drop(e) {
e.preventDefault()
// this.files = []
const promises = []
const items = e.dataTransfer.items
if (!this.multiple && (this.files.length > 0 || Object.keys(items).length > 1)) {
this.$message.error(`只能选择单个文件`)
return
}
for (const item of items) {
const entry = item.webkitGetAsEntry()
promises.push(this.isDirectory(entry))
// await this.isDirectory(entry)
}
// 注意读取文件属于IO操作,也就是异步的,所以这里我用promises包了一层
await Promise.all(promises)
this.$emit("change", this.files)
},
/**
* 删除
* @param file
*/
deleteFile(file) {
this.$confirm("您确定删除该文件吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.files = this.files.filter((item) => item.fileId != file.fileId)
this.$emit("change", this.files)
})
.catch(() => {})
}
}
}
</script>
<style lang="scss" scoped>
.file-container {
width: 100%;
background: #ffffff;
border-radius: 4px 4px 4px 4;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-weight: 400;
font-size: 12px;
color: #a0a6ae;
cursor: pointer;
&:hover {
border-color: #44a5ff;
}
.icon {
width: 19px;
height: 24px;
display: block;
margin-bottom: 10px;
}
.text {
line-height: 20px;
}
}
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
color: #333333;
cursor: pointer;
.item-icon {
opacity: 0;
}
&:hover {
color: #44a5ff;
}
&:hover .item-icon {
opacity: 1;
}
}
</style>