JS 拖拽文件夹上传

9,143 阅读4分钟

一、如何选择文件夹进行上传?

       在input:file中,有一个属性,用于控制在点击上传文件按钮时,用于展示文件还是文件夹的属性,这个属性就是webkitdirectory。

      webkitdirectory是H5新增的文件选择标签的属性,允许脚本访问本地文件夹,所以当input:file 标签带有这个属性的时候,上传选择时就只能选择文件夹,是看不到文件的。当没有这个属性的时候,在选择文件框中,就只能够选择文件,不能选择文件夹。代码如下:

<input type="file" :accept="accepts" webkitdirectory multiple />

可以看到,上面的代码中,有webkitdirectory 这个属性,此时,我们就可以选择文件夹进行上传了,实际效果如下:


      webkitdirectory属性的兼容性:


       可以看到,谷歌,火狐,edge对这个属性的支持还是很好地,其余浏览就不太支持这个属性。

       注意1:当你需要进行文件夹的上传的时候,就可以用到这个属性,之后就是可以进行文件夹上传了,但是需要注意的一个点就是,当你增加这个属性之后,就只能看到文件夹了,当你又需要可以上传文件的时候,就需要去掉这个属性,才能够看到文件,所以需要动态的加入和取消这个属性,才可以在文件和文件夹之间进行切换。

       注意2:文件夹的上传不是,直接就将整个文件夹进行上传了,在选中文件夹之后,浏览器会把文件夹之内的文件读取出来,无论里面嵌套的层数是有多少层的文件夹,都会将所有的文件都平铺出来,然后把所有的文件进行上传。

二、如何拖拽文件夹进行上传

       我们实现的主要就是两点:

             1.如何拖拽,拖拽后如何读取拖拽到的内容?

             2.如何将读取到的内容,转换成一个个的文件?

实现1:

      拖拽功能,现在都有很多拖拽的方法,我这里用到的就是H5新增的拖放API。

  因为我的方法是写在vue中的,所以看到如下代码:

 var dragNow = this.$refs.dragArea; 
    
 dragNow.ondragenter = function(e) {        
     e.preventDefault();      
 }; 
     
 dragNow.ondragover = function(e) {
     e.preventDefault();     
 };      

 dragNow.ondragleave = function(e) {     
    e.preventDefault();    
 };     

 dragNow.ondrop = e => {      
    e.preventDefault();       
    this.dealWithFolders(e.dataTransfer.items);   
 };

dargNow: 是$refs获取到的节点信息,这个节点是用于放置拖放文件或者文件夹的区域 

下面的ondragenter, ondragover, ondragleave, ondrop。就是H5新增的拖放api,从意思中就可以看出来:

   ondragenter:拖放刚进入拖放区域。

   ondragover: 拖放文件在拖放区域上方。

ondragleave: 拖放文件离开拖放区域。

ondrop: 放置文件到区域内,松手离开。

注意: e.preventDefault(); 每个动作内部必须,写入这个,防止默认事件的产生。

如果拖放的是一个文件 去读取e.dataTransfer.files就可以了 就直接可以达到文件,然后进行上传就可以了; 但是如果是一个文件夹就不能拿这个属性里面的值了,因为此时你拿到的是一个文件夹的信息,而不是你希望拿到的文件夹内部所有文件的信息。

        此时,我们需要去拿的是e.dataTransfer.items 这个属性,但是需要注意的是,这个属性也不是直接可以拿来用的,需要另外的方法,将文件夹里面的属性读取之后,才能够使用。

实现2: 那我们如何把e.dataTransfer.items中的所有的文件读取出来呢?这个时候就需要用到这几个属性

      webkitGetAsEntry: 获取到的每一个文件夹自带的方法,用于获取对应的内容。

      createReader: 读取文件夹。

      dirReader.readEntries: 读取和生成文件

       isDirectory:是否是一个文件夹

  dealWithFolders(items) { 
     if (items.length > 1) {     
   this.loading = false;      
  return this.$message.info("一次只允许上传一个文件夹");  
   }  
    var item = items[0].webkitGetAsEntry();   
   if (item) {    
    this.checkFolders(item);    
  }  
  },   
 // 判断是否为文件夹  
  checkFolders(item) {    
  if (item.isDirectory) {  
      let result = this.traverseFileTree(item);   
     setTimeout(() => {   
       this.transferFiles(result);    
    }, 3 * 1000);   
   } else {     
   this.loading = false;   
     this.$message.info("只支持上传文件夹");   
   }   
 },   
 traverseFileTree(item) {   
   let res = [];    
  var internalProces = (item, path, res) => {     
   if (item.isFile) {       
   item.file(file => {          
  file.path = path + file.name;         
   var newFile = new File([file], file.path, { type: file.type });      
      res.push(newFile);      
    });      
  } else if (item.isDirectory) {         
 var dirReader = item.createReader();    
      dirReader.readEntries(      
      entries => {      
        for (let i = 0; i < entries.length; i++) {      
          internalProces(entries[i], path + item.name + "/", res);     
         }      
      },      
      function(e) {      
        console.log(e);      
      }    
      );    
    }   
   };   
   internalProces(item, "", res);   
   return res;  
  },


因为文件夹可能是嵌套的关系,我们不知道一个文件夹的内部是否嵌套了多层的文件夹,所以这里需要利用递归的方式,对文件夹内部的文件进行读取然后平铺出来,当然如果大家有更好的方法可以提出来,这里的代码写的有点粗糙了。