前端实现拖拽上传文件和文件夹(webkit内核)

5,703 阅读1分钟

目前组件库中关于桌面拖拽文件上传基本都是开箱即用的,这里就不在给大家讲解,但是拖拽文件夹上传实现是比较少的,原因是拖拽拿到的fils中的仅仅对文件夹的描述,至于文件夹内部信息是不会像使用input标签上传文件夹那样,将内部文件信息全部拍平汇入files中为一个fileList,所以需要进行特殊处理,将内部数据拿到。

一、使用input标签上传文件夹

<input
      multiple="false"
      type="file"
      ref="folderUploader"
      webkitdirectory
      class="file-input"
      @change="uploadHandler($event, 'folder')"
      title="文件与文件夹无法同时上传,单次仅支持上传一个文件夹"
  />

在uploadHandler的event中拿到如下信息:

image.png

二、使用拖拽上传文件夹

<!-- 容器组件-->
<FileDragWrap
          v-show="!!draggingStatus"
          :draggingStatus="draggingStatus"
          :uploadExts="uploadExts"
          :uploadHint="uploadHint"
          @change="(val) => (draggingStatus = val)"
          @success="handleDragSuccess"
          @error="handleDragError"
 />
<!-- 拖拽区域组件FileDragWrap-->
 <div
    class="cme-drop-file-hint__wrap"
    @dragover="handleDragWrapDragOver"
    @drop="handleDragWrapDrop"
  >
    <div class="cme-drop-file-hint__icon">
      <i class="icon-cme icon-cme-upload"></i>
    </div>
    <p class="cme-drop-file-hint__text">释放鼠标上传文件</p>
  </div>

在handleDragWrapDrop的event中拿到的信息:

image.png

我们发现没有文件夹内部信息描述,所以我们需要使用chrome特有的方法webkitGetAsEntry 去递归拿到所有的信息进行拍平,转换为可上传使用的File对象。

 /**
   * @description 拖拽事件处理
   * @param 拖拽事件体
   */
   
 async handleDragWrapDrop(event: DragEvent) {
    const { dataTransfer } = event
    if (!dataTransfer) return
    const { files } = dataTransfer
    // 检查类型
    const allowFile = find(files, (item) => {
      return some(this.uploadExts, (ext) => {
        return item.name.toLowerCase().endsWith(`.${ext.toLowerCase()}`) || item.type === ''
      })
    })
    if (allowFile) {
      if (files.length === 1) {
        const { items, files } = dataTransfer
        const item = items[0].webkitGetAsEntry()// 获取当前文件夹的Entry(webkit内核特有),然后去递归Entry
        if (item) {
          // 说明是文件夹
          if (item.isDirectory) {
            const filesList: any[] = []
            await this.scanFiles(item, filesList)
            const copyEvent: any = { dataTransfer: { files: filesList } }
            this.$emit('success', { event: copyEvent, msg: '', type: 'folder' })
          } else {
            this.$emit('success', { event, msg: '', type: 'file' })
          }
        }
      } else {
        this.$emit('success', { event: {}, msg: '单次拖拽仅支持单个文件上传' })
      }
    } else {
      this.$emit('error', { event: {}, msg: '上传文件格式错误' })
    }
  }

递归扫描文件:

/**
   * @description 扫描文件夹中所有的文件夹子和文件,将数据拍平为可上传使用的File对象
   * @param  item  FileSystemDirectoryEntry 对象实例(目录实体)
   */
   
  scanFiles(entry: any, filesList: any[]) {
    return new Promise((resolve, reject) => {
      if (entry.isDirectory) {
        const directoryReader = entry.createReader()
        directoryReader.readEntries(
          async (entries: any[]) => {
            entries.forEach(async (entry: any, index: number) => {
              await this.scanFiles(entry, filesList)
              if (index === entries.length - 1) {
                resolve(1)
              }
            })
          },
          (e: any) => {
            reject(e)
          }
        )
      } else {
        entry.file(
          async (file: any) => {
            const path = entry.fullPath.substring(1)
             /**修改webkitRelativePath 是核心操作,原因是拖拽会的事件体中webkitRelativePath是空的,而且webkitRelativePath 是只读属性,普通赋值是不行的。所以目前只能使用这种方法将entry.fullPath 赋值给webkitRelativePath**/
            const newFile = Object.defineProperty(file, 'webkitRelativePath', {
              value: path,
            })
            filesList.push(newFile)
            resolve(1)
          },
          (e: any) => {
            reject(e)
          }
        )
      }
    })
  }

最后我们通过这种scanFiles方式获得filesList 给到上传接口就可以进行上传文件夹中的内容,当然上面的组件,文件和文件夹都是支持的,希望能够帮到您~