**本文已参与「新人创作礼」活动, 一起开启掘金创作之路。 **
引子
vueUse里有很多实用的功能,有的功能在我的项目里用的还是很多的,比如 useDropZone ,只是可惜只支持文件拖放,不支持文件夹,那么就对它进行改造,首先要感谢 useDropZone 的作者。
官方的例子
<script setup lang="ts">
import { useDropZone } from '@vueuse/core'
const dropZoneRef = ref(null)
function onDrop(dropZoneRef, files: File[] | null) {
// Trigger an event when file(s) is drop on zone
}
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
</script>
<template>
<div ref="dropZoneRef">
Drop files here
</div>
</template>
使用方法很简单,引用,拖入文件后返回结果在 onDrop 中查看。
开始改造
找到源代码的位置,每个人都有自己习惯的风格,我多少也有点,需要改动一些地方。 先上改造前的代码
export function useDropZone(target: MaybeRef<HTMLElement | null>, onDrop: (files: File[] | null) => void): UseDropZoneReturn {
const isOverDropZone = ref(false)
let counter = 0
if (isClient) {
useEventListener<DragEvent>(target, 'dragenter', (event) => {
event.preventDefault()
counter += 1
isOverDropZone.value = true
})
useEventListener<DragEvent>(target, 'dragover', (event) => {
event.preventDefault()
})
useEventListener<DragEvent>(target, 'dragleave', (event) => {
event.preventDefault()
counter -= 1
if (counter === 0)
isOverDropZone.value = false
})
useEventListener<DragEvent>(target, 'drop', (event) => {
event.preventDefault()
counter = 0
isOverDropZone.value = false
const files = Array.from(event.dataTransfer?.files ?? [])
if (files.length === 0) {
onDrop(null)
return
}
onDrop(files)
})
}
return {
isOverDropZone,
}
}
而需要添加的代码如下:
const addFilesFormDirectory = (directory: any | null, path: string) => {
const dirReader = directory.createReader()
dirReader.readEntries(function (entries: FileSystemEntry[]) {
entries.forEach(function (entry: any) {
if (entry.isFile) {
// 如果是文件
entry.file((file: any) => {
file.fullPath = path + '/' + file.name;
// @ts-ignore
uploadFiles.value.push(file)
})
} else if (entry.isDirectory) {
// 递归处理
addFilesFormDirectory(entry, path + '/' + entry.name);
}
});
}
)
}
const addFilesItems = (items: DataTransferItemList) => {
Object.values(items).map(item => {
let entry: any
if (item.webkitGetAsEntry && (entry = item.webkitGetAsEntry())) {
if (entry.isFile) {
entry.file((file: any) => {
file.fullPath = file.name;
// @ts-ignore
uploadFiles.value.push(file)
})
} else if (entry.isDirectory) {
addFilesFormDirectory(entry, entry.name);
}
}
})
}
原理就是判断是文件还是文件夹,如果是文件夹则读取里面的文件,原则上可以读取多层,文件夹下的文件名称会变为文件夹/文件名,如 文件夹/图片.jpg。后端需要特殊处理,拆解一下即可。下面整体代码奉上。
export interface DropReturn {
isDrop: Ref<boolean>
}
const dropUpload = (target: MaybeRef<HTMLElement | null>, onDrop: (files: File[] | null) => void): DropReturn => {
const uploadFiles = ref([])
const isDrop = ref(false)
let counter = 0
/**
* 拖拽进入
**/
const onDragEnter = (e: MouseEvent) => {
e.preventDefault()
isDrop.value = true
counter += 1
}
const onDragOver = (e: MouseEvent) => {
e.preventDefault()
}
/**
* 拖拽离开
**/
const onDragOut = (e: MouseEvent) => {
e.preventDefault()
counter -= 1
if (counter === 0) {
isDrop.value = false
}
}
/**
* 拖拽松开
**/
const uploadFunc = (e: any) => {
e.preventDefault()
onDragOut(e)
counter = 0
if (e.type === 'drop') {
if (e.dataTransfer.files) {
let items = e.dataTransfer.items;
if (items && items.length && items[0].webkitGetAsEntry != null) {
addFilesItems(items)
}
}
}
onDrop(uploadFiles.value)
isDrop.value = false
}
const addFilesFormDirectory = (directory: any | null, path: string) => {
const dirReader = directory.createReader()
dirReader.readEntries(function (entries: FileSystemEntry[]) {
entries.forEach(function (entry: any) {
if (entry.isFile) {
// 如果是文件
entry.file((file: any) => {
file.fullPath = path + '/' + file.name;
// @ts-ignore
uploadFiles.value.push(file)
})
} else if (entry.isDirectory) {
// 递归处理
addFilesFormDirectory(entry, path + '/' + entry.name);
}
});
}
)
}
const addFilesItems = (items: DataTransferItemList) => {
Object.values(items).map(item => {
let entry: any
if (item.webkitGetAsEntry && (entry = item.webkitGetAsEntry())) {
if (entry.isFile) {
entry.file((file: any) => {
file.fullPath = file.name;
// @ts-ignore
uploadFiles.value.push(file)
})
} else if (entry.isDirectory) {
addFilesFormDirectory(entry, entry.name);
}
}
})
}
if (target) {
useEventListener(target, 'dragenter', onDragEnter)
useEventListener(target, 'dragover', onDragOver)
useEventListener(target, 'dragleave', onDragOut)
useEventListener(target, 'drop', uploadFunc)
}
return {
isDrop
}
}
export default dropUpload
结尾
整体代码完全原创的部分很少,借鉴了网上很多大牛的方案,在这里算是对于我来说的最优解吧。最后附上效果图。