HTML原生不支持上传文件夹结构,webkitdirectory仅Chrome/Edge支持且返回扁平化FileList,需用webkitRelativePath还原路径或前端ZIP打包上传。

HTML不支持直接上传整个文件夹
浏览器原生 无法真正读取文件夹结构,webkitdirectory 属性只是让用户能“选中文件夹”,但实际返回的 files 列表里只有扁平化文件(无子目录路径信息),且仅 Chrome/Edge 支持,Firefox 和 Safari 完全忽略。所谓“上传文件夹”本质上是上传其中所有文件,需前端自行还原层级或后端约定归档方式。
用 webkitdirectory + FormData 上传文件夹内所有文件
启用 webkitdirectory 后,input.files 是一个包含全部文件(含子目录下)的 FileList,但所有文件的 webkitRelativePath 属性才携带相对路径(如 "docs/report.pdf")。这个属性在 Chrome/Edge 中可用,其他浏览器为 ""。
- 必须监听
change事件,不能靠click或表单提交 -
files是只读类数组,需转为数组才能.map()或.forEach() - 每个
File对象的webkitRelativePath可用于构造服务端接收路径,例如追加为字段:formData.append('path', file.webkitRelativePath) - 若要保留目录结构,可将
file和file.webkitRelativePath一起传,后端按路径创建子目录并存储
示例关键片段:
const input = document.querySelector('input[type="file"][webkitdirectory]');
input.addEventListener('change', (e) => {
const files = Array.from(e.target.files);
const formData = new FormData();
files.forEach(file => {
formData.append('files', file); // 统一字段名,后端收为数组
if (file.webkitRelativePath) {
formData.append('paths', file.webkitRelativePath); // 配套传路径
}
});
fetch('/upload-folder', { method: 'POST', body: formData });
});
后端解析时注意路径字段与文件顺序对齐
前端用两个同名数组字段(files 和 paths)发送,后端必须保证二者索引一一对应——因为 FormData 不保证字段顺序,且部分框架(如 Express + multer)默认不保留多字段顺序。更稳妥的做法是:前端把路径作为文件字段的 filename 元数据传,或改用单字段 + 自定义格式(如 JSON 封装路径+Blob)。
- Node.js/multer 场景下,
req.files是数组,但req.body.paths是字符串数组,需手动校验长度一致 - Flask 中
request.files.getlist('files')和request.form.getlist('paths')也需配对处理 - 若某文件无
webkitRelativePath(如用户拖拽单个文件进来),该位置paths值为空字符串,后端需兼容
真正需要目录结构?得用 ZIP 前端压缩再上传
如果业务强依赖完整目录树(比如上传项目源码、带子模块的文档包),webkitdirectory 仍不可靠。此时应引导用户先压缩为 ZIP,或前端用 JSZip 库在浏览器内打包——读取所有 File 对象,按 webkitRelativePath 构建目录树,生成 Blob,再以 application/zip 类型上传。这样后端只需解压一次,结构 100% 还原,且兼容所有浏览器。
注意点:
-
JSZip对大文件(>500MB)内存压力大,需分块读取或提示限制 - ZIP 文件本身无 MIME 类型校验,
accept属性无法过滤,得靠 JS 检查后缀名 - 上传 ZIP 后,后端不再需要解析
multipart/form-data中的路径元数据,逻辑更简单
复杂点从来不在“怎么点那个按钮”,而在于你是否清楚:浏览器暴露给 JS 的,永远只是文件内容和扁平列表;所谓“文件夹”,是后端按约定重建的幻觉。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











