前言

上一篇写完之后我就迫不及待写第二篇。我也想大家能够赶紧看到这个东东啊。

实现思路

直奔主题。说一下实现这个功能的思路。 跟后端同学对接的时候我们要拿到两个接口。一个是创建文件夹的接口,一个是上传图片的接口。所有我们组件要传这两个参数。其次有个问题 文件夹下面的图片我们上传到服务器的时候要带上之前创建的文件夹成功之后返回的id参数。不然我们不知道上传到哪个文件夹下面。所以第一步就是先递归上传所有的文件夹,然后再上传图片。总结如下

  • 第一步 通过dropzone的API 依次读取每个文件的信息 来组装一个文件夹的树形结构
  • 第二步 等到所有的文件夹的树形结构组装完成之后 开始递归上传文件夹
  • 第三步 开始放开队列里面的文件 开始一个个图片进行上传到指定的文件夹下(带上对应文件夹的id就可以知道是哪个文件夹下的图片)
  • 第四步 上传完成之后 暴露一个上传完成的回调 当然中途可以暴露 上传成功 上传失败 上传进度等等

这就是整体的思路。

具体实现

首先你要熟悉一下dropzone的API。有很多都是通过调用它实例的方法实现的。

第一步 文件夹树形结构组装

在上传图片之前我们要先保证文件夹创建完毕才能上传图片不然我不知道把图片上传到何处。所以组件传参数的时候 给dropzone传options下的autoProcessQueue属性为false,表示文件会被添加到上传队列里面但是不会自动上传,需要等到所有的文件夹创建之后才开始上传。树形结构的组装我说一下思路 代码在github上会有。

组装文件夹树形结构思路

  • 首先根据路径来拆分开 比如( 添加文件API里面会有个file参数,file里面有个filePath。例如 '文件夹A/文件夹B/图片.png'。这样我们就可以拆开成['文件夹A', '文件夹B', '图片.png'] ) 画个图说明一下。就是类似下面的结构


大概的结构就是这样

[
  // 第一层
  {
      name: '文件夹A',
      level: 0, // 第一层
      isDir: true, // 是否是文件夹 图片就是false 图片也没有children属性
      children: [
        {
          name: '文件夹B',
          level: 1, // 第二层
          isDir: true, // 是否是文件夹
          children: [
            {
                name: '图片',
                level: 2, // 第三层
                isDir: false
            }
          ]
        }
      ]
  }
]
    
复制代码

递归组装函数实现思路就是 根据上面的 ['文件夹A', '文件夹B', '图片.png'] 值来判断是文件还是文件夹。如果是文件夹就判断是不是第一次添加 因为如果已经有该层有这个文件夹 就不能在添加这个文件夹了 所以要分开处理。当已经有文件夹之后 就循环对比名字是否相同 下面贴出我的实现 如果有更好的方法麻烦赐教啊

/**
     * @param { newPath 文件路径拆开的数组 }
     * @param { level 层级 表示是第几层 }
     * @param { folder 传进来的文件夹树形结构 首次为空数组 }
     */
    singleFolder(newPath, level, folder) {
      // 计数 如果循环完毕没有相等的我们才添加在这一层 接着继续递归
      let sum = 0;
      
      // newPath[level] 不能跟folder名字相同
      if (!newPath[level].includes('.')) {
        // 如果不是空文件夹 
        if (folder.length) {
          // 如果里面有就添加到当前下面 没有就添加到这一层
          for (let i = 0; i < folder.length; i++) {
            if (newPath[level] == folder[i].name) {
              if (newPath[level + 1]) {
                folder[i].children = this.singleFolder(newPath, level + 1, folder[i].children);
                return folder;
              }
            } else {
              sum++;
            }
          }
          // 如果能走到这里就表示不相等
          if (sum == folder.length) {
            const node = {
              children: [],
              isDir: true,
              name: newPath[level],
              level
            };
            folder.push(node);
            if (newPath[level + 1]) {
              node.children = this.singleFolder(newPath, level + 1, node.children);
            }
          }
        } else {
          const node = {
            children: [],
            isDir: true,
            name: newPath[level],
            level
          };
          folder.push(node);
          // 看后面还有没有元素
          if (newPath[level + 1]) {
            node.children = this.singleFolder(newPath, level + 1, node.children);
          }
        }
      } else {
          const node = {
            isDir: false,
            name: newPath[level],
            level
          };
          folder.push(node);
      }
      return folder;
    },

复制代码
  • 第二步 等组装好文件夹树形结构之后开始调用创建文件夹的接口。传刚才创建好的树形结构 以及要传在哪个文件夹的id。这里还有个要处理的 就是上传成功返回的id我们要添加到对应的树形结构下。之前的level我们就可以使用了。
deepUpload(folder, returnParentId) {
      // 如果存在文件夹
      folder.forEach(item => {
        if (item.isDir) {
          // 这里加id 
          this.uploadFolder[item.level] = this.uploadFolder[item.level] || [];
            const id = item.parentId ? item.parentId : returnParentId;
            // 不存在就开始上传
            this.axios.get(`xxx`).then(res => {
                if (res.data) {
                  if (res.data.errorCode === ERR_OK)) {
                  // 成功之后返回的 parentId 添加到 
                    const returnParentId = res.data.result.id;
                    item.parentId = returnParentId;
                    if (item.children && item.children.length) {
                        this.deepUpload(item.children, returnParentId);
                    }
                  } else if (res.data.errorCode === 1) {
                    this.$message.error('已存在该文件夹!');
                    this.removeAllFiles(true);
                    return;
                  }
                }
            });
        }
      });
    }

复制代码
  • 第三步 文件夹遍历创建之后 开始启动上传图片了

等文件夹创建成功之后。就要开始上传文件了。这时候使用dropzone的send方法。里面有个回调函数 函数有个formData参数。我们这里通过append方法加上id即可。

this.dropzone.on('sending', (file, xhr, formData) => {
      // 判断是文件夹下的图片还是外层的图片
      if (file.fullPath) {
        const pathArr = file.fullPath.split('/');
        const LEN = pathArr.length - 2;
        const id = this.findId(this.folder, pathArr, 0, LEN);
        // 这里加上id
        formData.append('parentId', id);
      } else {
        // 直接点击上传或者拖动图片上传
        formData.append('parentId', this.parentId);
      }
    });

复制代码

文件夹下Id查找函数也贴出来。

/** 查找id
     * @param list 树形结构
     * @param arr 文件的路径
     * @param 文件的层级
     * @param 路径的length
     * return 返回的是 这个图片是哪个文件夹下对应的id
     */
    findId(list, arr, level, len) {
      let id;
      let i = 0;
      while(i < list.length) {
        if (list[i].name === arr[level]) {
          if (level === len) {
            id = list[i].parentId;
            return id;
          }
          if (arr[level + 1] && !arr[level + 1].includes('.')) {
            id = this.findId(list[i].children, arr, level + 1, len);
          }
        }
        i++;
      }
      return id;
    }

复制代码

到这里最主要的功能就基本上完了。至于一些上传成功、失败、进度的回调可以去看看dropzone的官方文档。

注意点

不同的业务需求有的地方的代码也需要微调。比如之前传的图片不能再次上传。这里要加一点判断。 大家可以根据自己的需求去看dropzone文档,来做调整。