一、如何选择文件夹进行上传?
在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;
},因为文件夹可能是嵌套的关系,我们不知道一个文件夹的内部是否嵌套了多层的文件夹,所以这里需要利用递归的方式,对文件夹内部的文件进行读取然后平铺出来,当然如果大家有更好的方法可以提出来,这里的代码写的有点粗糙了。