是最小可用组合,仅 Chromium 和 Safari 16.4+ 支持,Firefox 不支持;需依赖 webkitRelativePath 还原目录结构,且服务端必须校验路径防遍历。

用 webkitdirectory 属性直接触发文件夹选择
浏览器原生支持文件夹上传,不需要 JS 模拟或拖拽监听。关键就是给 加上 webkitdirectory 和 directory 两个属性——前者是 Chrome/Edge 的事实标准,后者是 Firefox 的等效写法。
常见错误是只写 webkitdirectory,结果在 Firefox 里完全不生效;或者漏掉 multiple,导致选中文件夹后只拿到第一个文件。
-
是最小可用组合 - 不要加
accept,它对文件夹无效,反而可能干扰部分浏览器行为 - 不能用
click()主动触发该 input —— 大多数浏览器会静默拒绝,必须由用户真实点击
为什么 webkitRelativePath 是唯一可靠的路径信息
用户选中一个文件夹后,e.target.files 返回的是扁平的 FileList,但每个 File 对象自带 webkitRelativePath 字符串,比如 "project/src/main.js"。这个字段才是重建原始目录结构的唯一依据。
别试图用 file.name 或 file.path(后者根本不存在)来还原层级;也别依赖 file.lastModified 排序——它和目录顺序无关。
- macOS 用户可能遇到
__MACOSX/隐藏目录,需在遍历时过滤:pathSegments.some(seg => seg.startsWith('__MACOSX') || seg.startsWith('.')) - Windows 路径分隔符是
\,但webkitRelativePath统一返回/,无需额外转换 - 空文件夹不会出现在
files列表里,这是 File API 的限制,前端无法绕过
如何避免“上传按钮点了没反应”的交互陷阱
用户点击上传按钮时,往往以为文件已就绪,但其实 change 事件只在 input 值变化时触发——如果用户重复选择同一个文件夹,change 不会再次执行,files 仍是旧引用。
更隐蔽的问题是:input 元素被隐藏后,用 label 关联点击,但某些移动端 WebView 会丢失 webkitRelativePath(尤其 iOS Safari 16.4 之前版本)。
- 把上传逻辑绑定在
input的change事件上,而不是上传按钮的click;按钮只负责触发 input.click() - 确保 input 未被
display: none或visibility: hidden完全隐藏,可用opacity: 0; position: absolute;替代 - 在 change 回调开头加
if (!e.target.files.length) return;,防止空列表误传
后端接收时最容易忽略的路径注入风险
前端传来的 webkitRelativePath 是用户可控字符串,比如可能包含 ../../../etc/passwd。如果你用它直接拼接服务端文件路径,就会触发路径遍历漏洞。
这不是理论风险——实际测试中,Chrome 128+ 允许用户通过修改 DOM 手动注入恶意路径,哪怕浏览器 UI 上没暴露编辑入口。
- 服务端必须对每个
path做规范化校验:path.startsWith('..') || path.includes('../') || path.startsWith('/') - 用语言内置的路径安全函数解析,例如 Node.js 的
path.relative('', path),Python 的os.path.relpath() - 禁止在路径中出现
..、.、/../等模式,连单个点都不放行(有些系统允许.git但拒绝..git)
文件夹上传真正难的不是选中或递归读取,而是让路径从用户输入到磁盘落盘全程可信。任何环节跳过校验,都可能把整个 uploads 目录变成公开读写区。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











