前端如何直接上传文件夹

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

依然从两方面来说:

  1. input选择文件夹
  2. 拖拽文件夹

input选择文件夹

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

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

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

html 复制代码
<input 
  type="file" 
  id="file" 
  :multiple="multiple"
  :accept="accept"
  :webkitdirectory="uploadFolder"
  :mozdirectory="uploadFolder"
  :odirectory="uploadFolder"
  @change="handleChange"
>

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

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

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

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

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

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

拖拽文件夹

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

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

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

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

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

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

打印一下看看: 得到一个List 类型的数据,里面是两个DataTransferItem ,控制台无法直接查看它到底是个什么玩意儿。 看MDN,也看不出它具体是个啥。既然是List,遍历一下看看:

js 复制代码
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,developer.mozilla.org/zh-CN/docs/...

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

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

js 复制代码
for (const item of event.dataTransfer.items) {
  const entry = item.webkitGetAsEntry()
  console.log(entry)
}

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

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

先看怎么拿到文件

entry是一个文件时,它有两个方法:createWriter()file(),查看MDN,developer.mozilla.org/en-US/docs/... createWriter()已经废弃了,而且也不是我们今天要用的。 file()才是我们要找的。 这不就是我们熟悉的file对象吗,跟input上传拿到的一毛一样。

再看怎么拿到文件夹

查看MDN的Drop API webkitGetAsEntry()方法,developer.mozilla.org/zh-CN/docs/... 可得,如果是文件夹,可以通过createReader方法创建一个文件目录阅读器,然后通过readEntries方法,重新拿到每个item,这就是event.dataTransfer.items里面的每个item 我们写一下试试 依然是之前那个图片和文件夹 只打印出了跟目录下一级的一个文件和一个文件夹,那下面还有一个文件怎么办呢?

递归呀!

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

js 复制代码
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方法也要改一下

js 复制代码
// 拖拽放置
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比较简单,使用它能直接拿到文件夹下所有的文件,以及每个文件在本地的路径,代码量也少很多。

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

相关推荐
C语言魔术师2 分钟前
【小游戏篇】三子棋游戏
前端·算法·游戏
匹马夕阳1 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?1 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
桂月二二7 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062069 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb9 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角9 小时前
CSS 颜色
前端·css
九酒9 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔10 小时前
HTML5 新表单属性详解
前端·html·html5
lee57610 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm