HTML 文件夹上传需 JavaScript 遍历+后端还原路径,因 input[type="file"] 不支持原生文件夹结构;webkitdirectory 兼容差、不可靠;现代方案用 showDirectoryPicker() 跨平台读取空目录,但需 HTTPS 和用户授权,并配合后端校验路径、递归建目录或直传云存储。

HTML 文件夹不能直接“上传到云存储”——浏览器原生不支持 input[type="file"] 选择整个文件夹并保留结构,必须靠 JavaScript 主动遍历 + 后端配合还原路径。所有所谓“拖拽上传文件夹”的功能,底层都是递归读取 webkitdirectory 或 FileSystem API(已废弃),再逐个构造带相对路径的请求。
为什么 input[type="file"] 加 webkitdirectory 不可靠
Chrome/Edge 支持 webkitdirectory 属性,能选中文件夹并读取其下所有文件,但存在几个硬伤:
- Firefox、Safari 完全不支持,
webkit前缀本身就是 Chrome 系专属 - 返回的
FileList中每个File对象的webkitRelativePath字段只在 Chrome 有效,且路径分隔符是/,Windows 用户本地路径本是\,容易出错 - 无法读取空子文件夹(浏览器根本不把空目录当“文件”暴露)
- 大文件夹(如含 500+ 文件)会卡死 UI,因为 JS 是单线程同步遍历
用 showDirectoryPicker() 替代(现代方案)
这是目前唯一跨平台、标准、可读取空目录的 API,但仅限 HTTPS 环境或 localhost,且需用户主动授权:
- 调用
navigator.storage.getDirectory()或showDirectoryPicker()获取FileSystemDirectoryHandle - 用
handle.keys()和handle.getFileHandle()递归遍历,拿到每个文件的完整相对路径(包括空文件夹的 handle) - 对每个文件调用
fileHandle.getFile()得到File实例,再用File.name和你维护的路径拼出服务端所需路径,如css/style.css - 注意:该 API 返回的是
FileSystemFileHandle,不是传统File,需转成 blob 后再封装进FormData
示例关键片段:
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
立即学习“前端免费学习笔记(深入)”;
const dir = await showDirectoryPicker();
async function uploadDir(handle, basePath = '') {
for await (const entry of handle.values()) {
if (entry.kind === 'file') {
const file = await entry.getFile();
const fullPath = basePath ? `${basePath}/${file.name}` : file.name;
// 构造 FormData,附带 fullPath 字段供后端存路径
const fd = new FormData();
fd.append('file', file);
fd.append('path', fullPath);
await fetch('/api/upload', { method: 'POST', body: fd });
} else if (entry.kind === 'directory') {
const subPath = basePath ? `${basePath}/${entry.name}` : entry.name;
await uploadDir(entry, subPath);
}
}
}
后端必须按路径建目录并写入
前端传来的 path 字段(如 js/main.js)不能简单拼到根目录下,否则会被覆盖或权限拒绝。后端要:
- 校验
path是否含../等路径穿越字符,直接拒收 - 用
path.dirname()提取父目录,递归创建(如 Node.js 用fs.promises.mkdir(p, { recursive: true })) - 用
path.basename()取文件名,写入对应位置,确保层级和本地一致 - 若用云存储(OSS/COS/OBS),则把
path直接作为对象 key,无需本地建目录——这才是直传价值所在
别忽略空文件夹和权限提示
用户拖一个空 images/ 进来,showDirectoryPicker() 能拿到该目录 handle,但里面没 getFile() 可调。你要决定是否主动向后端发一条“创建空目录”请求(比如 POST {op: "mkdir", path: "images/"})。另外,首次调用 showDirectoryPicker() 会弹系统级权限框,用户点“取消”就彻底失败——必须加 fallback 提示,比如:“请改用 ZIP 压缩后上传,我们自动解压”。这个交互兜底,90% 的线上项目都漏掉。


















