您现在的位置是:首页 > 诗句大全

前端如何直接上传文件夹

作者:淼淼时间:2024-04-20 12:40:56分类:诗句大全

简介  文章浏览阅读765次。我们经常看到一些网站支持直接选择整个文件夹上传,例如:宝塔面板、cloudflare托管、对象存储网站等等需要模拟文件路径存储文件的场景。那是怎么实现的呢?_前端上传文件夹

点击全文阅读

前面写了一篇仿写el-upload组件,彻底搞懂文件上传,实现了选择/拖拽文件上传,我们经常看到一些网站支持直接选择整个文件夹上传,例如:宝塔面板、cloudflare托管、对象存储网站等等需要模拟文件路径存储文件的场景。那是怎么实现的呢?

依然从两方面来说:

input选择文件夹拖拽文件夹

input选择文件夹

在props.js中加一个属性,upload-folder是否支持上传文件夹

export default {  // 前面的省略了...  // 是否支持选择文件夹  'upload-folder': {    type: Boolean,    default: false  }}

改一下input标签,依然是根据props的值动态判断是否支持上传文件夹。主要是webkitdirectory这个属性,由于不是一个标准属性,需要加浏览器前缀。

<input   type="file"   id="file"   :multiple="multiple"  :accept="accept"  :webkitdirectory="uploadFolder"  :mozdirectory="uploadFolder"  :odirectory="uploadFolder"  @change="handleChange">

注意:支持选择文件夹时就只能选择文件夹,无法选择文件。

那么如何获取选择的文件夹呢?其实我们最终要上传的依然是文件,也就是file对象,文件夹也是一个特殊的文件。

依然是通过inputonchange事件回调拿到上传的event

或者直接获取input这个dom对象,然后拿到files属性,结果是一样的。

// input选择文件回调const handleChange = (event) => {  console.log('[ files ] >', event.target.files)  const inputDom = document.querySelector('#file')  console.log('[ files ] >', inputDom.files)}

可以看到,比选择单个文件时,多了一个webkitRelativePath属性,并且它是递归选择的文件夹,拿到这个文件夹及其子文件夹下所有的文件,我们可以通过这个属性拿到上传时文件所在的文件夹名称路径

拖拽文件夹

上篇文章讲过拖拽如何拿到文件,首先要准备一个用于拖拽放置的区域。
调用upload组件时,传入drag=true

<div   class="drag-box"   @dragover="handleDragOver"  @dragleave="handleDragLeave"  @drop="handleDrop">  将文件拖到此处,或<span>点击上传</span></div>
// 拖放进入目标区域const handleDragOver = (event) => {  event.preventDefault()}// 拖拽放置const handleDrop = (event) => {  event.preventDefault()  console.log('[ event ] >', event)}

注意:和input上传不同,拖拽时,是可以同时拖拽文件和文件夹的。

因为可以同时拖拽文件和文件夹,我们就不能直接使用event.dataTransfer.files,如果刚好拖拽进来的是一个文件,那可以这么获取,如果是个文件夹呢?那就不行了。

同时拖拽一个文件和一个文件夹

这时候就要用到event.dataTransfer.items

// 拖拽放置const handleDrop = (event) => {  event.preventDefault()  console.log(event.dataTransfer.items)}

打印一下看看:

得到一个List类型的数据,里面是两个DataTransferItem,控制台无法直接查看它到底是个什么玩意儿。

看MDN,也看不出它具体是个啥。既然是List,遍历一下看看:

const handleDrop = (event) => {  event.preventDefault()  console.log(event.dataTransfer.items)  for (const item of event.dataTransfer.items) {    console.log('[ item ] >', item)  }}


可以看到不管是文件还是文件夹,都被识别成了file,只不过图片是直接能识别出type为image/png

查看MDN,https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransferItem

点击查看itemPrototype,发现里面有个webkitGetAsEntry方法,执行它就能拿到item的具体信息。

看方法名,带了个webkit,但是这个方法除了Android Firefox浏览器以外都可以用。

for (const item of event.dataTransfer.items) {  const entry = item.webkitGetAsEntry()  console.log(entry)}

依然拖动上面那个图片文件和一个文件夹:


可以看出,文件夹里面还有文件和文件夹,但是只显示了一个文件和一个文件夹,看来拖拽和input上传不一样,它不会自动的把里面所有的文件递归列出来。

通过isDirectory属性,就能区分是文件还是文件夹。除了这些基础属性以外,继续查看Prototype,可以看到还有一系列方法:

先看怎么拿到文件

entry是一个文件时,它有两个方法:createWriter()file(),查看MDN,https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileEntry/createWriter

createWriter()已经废弃了,而且也不是我们今天要用的。

file()才是我们要找的。


这不就是我们熟悉的file对象吗,跟input上传拿到的一毛一样。

再看怎么拿到文件夹

查看MDN的Drop API webkitGetAsEntry()方法,https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransferItem/webkitGetAsEntry可得,如果是文件夹,可以通过createReader方法创建一个文件目录阅读器,然后通过readEntries方法,重新拿到每个item,这就是event.dataTransfer.items里面的每个item

我们写一下试试

依然是之前那个图片和文件夹

只打印出了跟目录下一级的一个文件和一个文件夹,那下面还有一个文件怎么办呢?递归呀!

写一个递归读文件的方法。

const readFiles = async (item) => {  if (item.isDirectory) {    // 是一个文件夹    console.log('=======文件夹=======');    const directoryReader = item.createReader();    // readEntries是一个异步方法    const entries = await new Promise((resolve, reject) => {      directoryReader.readEntries(resolve, reject);    });    let files = [];    for (const entry of entries) {      const resultFiles = await readFiles(entry);      files = files.concat(resultFiles);    }    return files;  } else {    // 是一个文件    console.log('=======文件=======');    // file也是一个异步方法    const file = await new Promise((resolve, reject) => {      item.file(resolve, reject);    });    console.log('[ file ] >', file);    return [file];  }}

handleDrop方法也要改一下

// 拖拽放置const handleDrop = async (event) => {  event.preventDefault()  console.log(event.dataTransfer.items)  const files = [];  const promises = [];  for (const item of event.dataTransfer.items) {    const entry = item.webkitGetAsEntry();    console.log('[ entry ] >', entry);    promises.push(readFiles(entry));  }  const resultFilesArrays = await Promise.all(promises);  const allFiles = resultFilesArrays.flat();  console.log('[ All files ] >', allFiles);}

再次拖拽上传看看

三个文件我们都拿到了。

总结

上传文件夹,还是直接使用input比较简单,使用它能直接拿到文件夹下所有的文件,以及每个文件在本地的路径,代码量也少很多。

拖拽的好处是文件和文件夹能一起上传。

点击全文阅读

郑重声明:

本站所有活动均为互联网所得,如有侵权请联系本站删除处理

我来说两句