一.Input
项目一开始的需求定的是拖动单个文件夹到页面中收集文件。
使用的是input解决
<input type="file" ref="updateFile" multiple webkitdirectory @change="updateFile" />
//multiple 允许多文件
//webkitdirectory 选择目录
updateFile(e) {
console.log(e.target.files)
}
\
这样我们拿到了文件夹里的文件信息,并且根据webkitRelativePath属性里的数据也能还原这些文件原本的层级关系和文件夹的名称
但是!需求变了!需要支持多个文件夹一块拖入。
试了下input多文件夹拖入后获取的文件只读取了第一个文件夹的文件,其余文件夹文件并没有读取,查阅了很多文章都没有找到解决办法,所以只能换一种思路。
二.DragEvent
查看了MDN的drag and drop API后,发现可以实现这个需求,过程:
drop(e) {
// 1.获取DataTransfer对象。
// 2.DataTransfer对象的items属性,提供一个包含所有拖动数据列表的 DataTransferItemList 对象。
let DataTransferItemList = e.dataTransfer.items
// 3.DataTransferItemList 对象是一组代表被拖动项的DataTransferItem 对象的列表。
for (let index = 0; index < DataTransferItemList.length; index++) {
const DataTransferItem = DataTransferItemList[index]
// 4.DataTransferItem的kind属性可以判断拖拽项的种类,string 或是 file。
// 判断文件
if (DataTransferItem.kind === 'file') {
// 5.文件返回FileSystemFileEntry,文件夹返回FileSystemDirectoryEntry
// 上面两个entry具有的属性{filesystem,fullPath,isDirectory,isFile,name}
let entry = DataTransferItem.webkitGetAsEntry()
// 业务需求是拖进文件夹,所以判断下
if (entry.isDirectory) {
this.readDirectory(entry)
}
}
}
},
readDirectory(directory) {
let _this = this
// 6.调用createReader方法 创建一个reader
let dirReader = directory.createReader()
let getEntries = function () {
// 7.readEntries返回一个数组,文件返回FileSystemFileEntry,文件夹返回FileSystemDirectoryEntry。
dirReader.readEntries(
results => {
if (results.length) {
results.forEach(item => {
// 判断是否为文件 FileSystemFileEntry
if (item.isFile) {
// 8.FileSystemFileEntry.file()读取file
item.file(
file => {
_this.fileList.push(file)
},
error => {}
)
}
})
getEntries()
}
},
error => {
/* handle error — error is a FileError object */
}
)
}
getEntries()
}
这样就从用户拖拽进来的文件夹里获取到文件的file对象。当然根据业务需求需要进行一些改造。比如要判断什么时候读取完,就可以包一层promise;要判断文件夹层级,封装的方法可以添加层级参数,然后递归等等。
常见问题
1.阻止默认事件。
不想打开新页面,不仅要在ondrop上添加阻止,还要在ondragover上添加
<div class="drag_box" @drop.prevent="drop" @dragover.prevent></div>
2.异步。
readEntries(),file()这几个都是异步的,所以要判断是否读取完成时需要注意。
而且safari中reader读取文件完成的顺序是乱的,比如同样的代码和文件,如果是判断最后一个文件读取结束,在谷歌中确实是都结束了,但是safari中有的文件还没有读取完成。所以如果要判断所有文件是否完成也要注意这一点---不要以数组最后一个文件读取是否完成来判断所有文件的读取状态。
3.文件数量太多时(>100),文件缺失
let dirReader = directory.createReader()
let getEntries = function () {
dirReader.readEntries(
results => {
if (results.length) {
getEntries()
}
},
error => {
/* handle error — error is a FileError object */
}
)
}
getEntries()
这个是MDN文档中的写法,一定要封装个函数判断递归!直到返回的数组中再也没有文件为止!
经过测试,返回的数组长度最大为100
兼容性
MDN和caniuse网站提供数据显示:
| api | 不兼容的浏览器 |
|---|---|
| DataTransfer.items | IE |
| FileSystemDirectoryEntry.createReader() | IE,Opera |
| FileSystemFileEntry.file() | IE,Opera |
但是我测试了Opera(版本号89.0.4447.71)可以正常使用,数据正常
所以兼容性:完美