扩展 vueUse 的 useDropZone

614 阅读2分钟

**本文已参与「新人创作礼」活动, 一起开启掘金创作之路。 **

引子

vueUse里有很多实用的功能,有的功能在我的项目里用的还是很多的,比如 useDropZone ,只是可惜只支持文件拖放,不支持文件夹,那么就对它进行改造,首先要感谢 useDropZone 的作者。

官方的例子

微信截图_20220622154704.png

<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

结尾

整体代码完全原创的部分很少,借鉴了网上很多大牛的方案,在这里算是对于我来说的最优解吧。最后附上效果图。

微信截图_20220622161943.png