唯一合法起点是文件输入框,它只唤起系统选择框并暴露FileList;后续上传需JS构造请求。表单收不到文件常因后端未解析multipart数据、Flask未用request.files、Nginx限制体大小;fetch设Content-Type会丢失boundary致400;accept不可信,须结合name、type及服务端校验;进度条卡顿因e.lengthComputable为false导致NaN,需防御判断。

为什么直接写 <form method="POST" enctype="multipart/form-data"> 还是收不到文件?
表单提交本身能发文件,但问题常出在后端没解析 multipart 数据。比如 Express 默认不处理 req.file,Flask 用 request.form 读文件字段会为空,Nginx 的 client_max_body_size 默认 1MB 也会静默截断大文件。
前端看似正常,后端却拿不到文件,优先检查这三处:
- Express 是否用了
multer或busboy中间件 - Flask 是否用了
request.files.get('file')(不是request.form) - Nginx 配置里是否加了
client_max_body_size 50M;
fetch 上传时手动设 Content-Type 就失败?
FormData 发送时,浏览器自动设置 Content-Type: multipart/form-data; boundary=...。你只要一显式写 headers: { 'Content-Type': 'multipart/form-data' },boundary 就没了,后端解析器直接拒收整个 body,返回 400。
正确做法:让 fetch 完全自主控制 header —— 不传 headers 字段,也不设 Content-Type。
立即学习“前端免费学习笔记(深入)”;
常见错误代码:
fetch('/upload', {
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' }, // ❌ 错!删掉这整行
body: formData
})怎么校验用户选的是 HTML 文件?
accept=".html,.htm" 只影响文件选择器界面过滤,不能当真。用户切到“所有文件”就能绕过。真正校验得靠 JS:
- 看扩展名:
file.name.toLowerCase().endsWith('.html') - 看 MIME 类型:
file.type === 'text/html'(但部分浏览器对本地 .html 文件返回空字符串) - 更可靠的是读取文件头(前几百字节),比对是否含
<!DOCTYPE html>或<html字符串
别只信 accept,也别只信 file.type,二者要结合,关键逻辑放服务端再验一次。
上传大文件时进度条一直卡在 0% 或 NaN?
因为 XMLHttpRequest.upload.onprogress 的事件对象 e 里,e.lengthComputable 可能为 false(尤其走代理或 Nginx 时),此时 e.total === 0,e.loaded / e.total 就是 NaN。
必须加防御判断:
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total) * 100;
progress.value = Math.round(percent);
} else {
// 无法计算时,用 loaded 增量模拟(如每 100KB 更新一次)
}
};另外注意:fetch 目前完全不支持上传进度事件,别试;form.submit() 更不行——它不返回请求对象,也无法 preventDefault。
实际上传链路很薄:选文件 → 校验 → 构造 FormData → fetch/XHR 发出去。容易卡住的点不在“怎么做”,而在“哪些细节浏览器不帮你兜底”。比如字段名拼错、服务端没开 multipart 解析、进度里忘了判 lengthComputable —— 这些地方一漏,前端看着像没动,其实请求早被后端悄悄丢弃了。



















