使用ant-design-vue的upload进行文件或者文件夹的拖拽上传

1,278 阅读4分钟

使用场景

系统中上传文件和文件夹的情况数不胜数,但是这次我们需要加上拖拽文件和文件夹的上传功能,目前文件和文件夹的拖拽需要用户手动选择,如图:

image.png

如果已经有上传好的文件,则还会增加这样的点击上传入口

image.png

实现

我们借助ant-design-vueUpload组件来进行封装 3x.antdv.com/components/…

由于我们需要先实现一个既能拖拽又能点击上传的功能,所以这里我直接使用upload-dragger组件吧

<a-upload-dragger
    ref={uploadDragger}
    directory={curType.value === UploadType.FOLDER}
    name="file"
    multiple={true}
    showUploadList={false}
    before-upload={beforeUpload}
    customRequest={customRequest}
>
    <div class={[style["upload-dragger-box"], style["upload-dragger-box-hidden"]]}>
      <p class={style["upload-dragger-icon"]}>
        <SvgIcon class={style["icon"]} name="drawing-add-icon" width={20} height={20} />
      </p>
      <p class={style["upload-dragger-text"]}>点击或拖拽文件到此处上传</p>
      <p class={style["upload-dragger-hint"]}>文件大小不超过 500 M</p>
    </div>
</a-upload-dragger>

这样我们就有一个上传的容器了,当然,如果你不想使用组件库,你也可以自己写原生的input type=file来进行文件选择的触发,这里我们就不展开讲了,我们着重实现基于组件库的功能

拖拽功能

使用原生的拖拽api实现拖拽绑定 拖拽api

这里我们为什么要自己绑定呢?因为ant-design-vue的upload组件,只要有文件往里头拖拽,那就会触发上传,不管你是Upload还是UploadDragger组件,只要到那个范围内,就会发起拖拽

这个很好验证,大家可以直接去官网尝试把文件拖拽进去,就会看到效果了,当然,我说的是3.x版本哈!

所以这里我决定自己写一个容器遮住upload,同时绑定拖拽方法;在页面初始化的时候,我们就需要对容器进行拖拽的绑定

upload组件上遮罩上我们自己绑定的div,这样不会触发upload自带的拖拽方法了

{/* 上传区域 */}
  <div
    ref={dragBox}
    class={[
      style["upload-dragger-box"],
      style["upload-wrapper-no-file"],
      props.disabled ? style["upload-dragger-box-disabled"] : "",
    ]}
    onClick={clickUpload}
  >
    <p class={style["upload-dragger-icon"]}>
      <SvgIcon class={style["icon"]} name="drawing-add-icon" width={20} height={20} />
    </p>
    <p class={style["upload-dragger-text"]}>点击或拖拽文件到此处上传</p>
    <p class={style["upload-dragger-hint"]}>文件大小不超过 500 M</p>
  </div>

页面初始化的时候进行拖拽事件绑定

const handleDragLeave = (e) => {
    if (props.disabled) {
      return;
    }
    e.preventDefault();
    e.stopPropagation();
    // 去掉拖拽区域选中样式
    dragBox.value.style.border = "1px dashed #E5E6EB";
 };
 const handleDragOver = (e) => {
    if (props.disabled) {
      return;
    }
    e.preventDefault();
    e.stopPropagation();
    // 添加拖拽区域选中样式
    dragBox.value.style.border = "1px dashed #08a86d";
 };
 const handleDrop = (e) => {
    if (props.disabled) {
      return;
    }
    e.preventDefault();
    e.stopPropagation();
    dragBox.value.style.border = "1px dashed #E5E6EB";
    // 触发文件解析,进行上传数据准备
    dropHandle(e.dataTransfer.items);
 };
const registerDrag = () => {
    if (!dragBox.value) {
      return;
    }
    dragBox.value.addEventListener("dragleave", handleDragLeave);
    dragBox.value.addEventListener("dragover", handleDragOver);
    dragBox.value.addEventListener("drop", handleDrop);
};
onMounted(() => {
    nextTick(() => {
      registerDrag();
    });
});

如上代码所示,当用户拖拽文件进入区域并且松开鼠标的时候,也就是触发drop事件,则我们可以获取到当前的文件相关内容e.dataTransfer.items,我们可以在这个数据中知道当前是文件还是文件夹,如果是文件,我们可以读取文件,如果是文件夹,我们需要递归i进行文件的读取

// 拖拽事件
  const dropHandle = async (items) => {
    const dragList = ref<FileItem[]>([]);
    const traverseFileTree = async (entry) => {
      curDragType.value = entry.isFile ? UploadType.FILE : UploadType.FOLDER;
      if (curDragType.value === UploadType.FILE) {
        const file = await readFileEntries(entry);
        dragList.value.push(file as any);
      } else {
        // ! 递归获取文件
        const entries = await readDirEntries(entry as FileSystemDirectoryEntry);
        for (const item of entries) {
          // 递归获取文件
          await traverseFileTree(item);
        }
      }
    };
    const arr: Promise<void>[] = [];
    // 根据传入的文件进行循环,依次判断文件类型
    for (let i = 0; i < items.length; i++) {
      const filItem = items[i];
      if (filItem.kind == "file") {
        // ! 检查是否可以使用拖拽文件读取
        if (!filItem.webkitGetAsEntry) {
          message.error("当前浏览器不支持拖拽文件读取,更换浏览器或者点击上传");
          break;
        }
        arr.push(traverseFileTree(filItem.webkitGetAsEntry()));
      }
    }
    Promise.all(arr).then(() => {
      console.log("拖拽进来了咯", dragList.value, dragList.value.length);
      // 触发自定义上传事件
      customRequest(dragList.value);
    });
  };

需要注意的是,webkitGetAsEntry 这个方法目前还不是标准方法,但是我看了ant-design-vue的upload源码,里面也是采用这个方法进行读取,所以我们只要做好兼容就没问题了,如果不支持这个方法,我们就给用户一个提示即可

点击上传

由于我们是在真正的upload上面遮罩了一个div,所以当用户点击上传的时候,需要通过这个divclick事件来手动触发upload文件的点击事件,这样系统就可以帮我们调起文件选择的弹窗了

  // 拖拽内容区域
  const uploadDragger = ref();
    // 触发上传
  const clickUpload = () => {
    if (props.disabled) {
      return;
    }
    uploadDragger.value.$el.querySelector("input").click();
  };
  con

上面我看代码已经绑定了beforeUpload方法,因为我们想要把用户一次性选中的文件做一个集中上传,这样也是方便做文件队列

  // 上传前钩子
  const beforeUpload = (file, fileList) => {
    // ! 自定义上传,同时将多个文件一次性进行接口上传,方便进行队列操作
    temList.value.push(file);
    if (temList.value.length === fileList.length) {
      customRequest(temList.value);
    }
    return false;
  };
  const customRequest = async (file) => {
    // 文件上传相关逻辑  
  })

以上,也就完成了拖拽和点击上传的闭环流程了