应监听 submit 事件而非 click,统一禁用提交按钮并更新文案,上传完成后仅在明确失败时恢复按钮,服务端必须校验幂等性并使用一次性 token 和唯一约束。

监听 submit 事件而非 click
文件上传通常走表单提交,但用户快速连点 <input type="file"> 本身不会触发重复上传——真正危险的是点击“确认上传”按钮(type="submit" 或带 onclick 的 <button></button>)。只监听 click 会漏掉回车提交、form.submit() 调用等路径。必须统一收口到 form.addEventListener('submit', ...)。
- 在事件回调第一行禁用所有提交按钮:
form.querySelectorAll('button[type="submit"], input[type="submit"]') - 禁用后立即更新文案,比如设为
btn.textContent = '上传中…',避免用户因无反馈而狂点 - 不要用
event.preventDefault()后手动 fetch —— 若仍走原生表单提交,disabled属性能被浏览器正确识别并阻止后续 submit 触发
禁用后别自动恢复按钮状态
文件上传耗时长、失败率高,自动在 setTimeout 或 finally 里恢复按钮极易误导用户:上传失败了但按钮已可用,用户可能再次点击,导致重复请求;或者上传成功了但页面没跳转,用户误以为失败又点一次。
- 仅在明确知道结果时操作按钮:上传成功(
200或201)后跳转/清空表单,不恢复;失败(4xx/5xx)后才调用btn.disabled = false并提示错误 - 若用
fetch提交,务必用AbortController处理用户中途离开页面的场景,否则disabled状态可能永远卡住 - 警惕
form.reset():它会把disabled状态重置为初始值(通常是false),防重立即失效;改用 JS 手动清空字段 + 显式控制按钮状态
服务端必须校验幂等性,不能只靠前端
前端禁用只是防止垃圾请求打爆接口,对真实重复提交毫无防御力。F5 刷新、开发者工具删掉 disabled、抓包重放,都能绕过。
- 每次渲染上传表单时,后端生成一次性
submit_token,签名 + 绑定 session + 绑定文件类型(如avatar_upload),有效期建议 10 分钟 - 前端通过隐藏域提交:
<input type="hidden" name="submit_token" value="a1b2c3...">;服务端收到后先校验签名、时效、未消费,再标记为“已使用” - 数据库层加唯一约束,例如用文件哈希(
sha256(file_bytes))或客户端生成的idempotency-key做唯一索引,配合INSERT ... ON CONFLICT DO NOTHING
input[type="file"] 本身不需要防连点,但要防重复选同个文件
<input type="file"> 元素连续点击不会触发多次 change 事件——浏览器对相同路径文件有去重机制。但用户换文件再换回来,或清空后重选,change 仍会触发。这不是连点问题,而是状态管理疏漏。
- 不要依赖
input.files.length是否为 0 来判断是否“已选”,而应监听change事件后立即将input.value = ''清空,强制下一次选择视为新动作 - 若需支持多选且防重复,可在 JS 层维护已上传文件名/哈希集合,每次
change后过滤掉已存在项 - 注意:Safari 对
input.value = ''有兼容性问题,需用input.type = 'text'; input.type = 'file'临时 hack
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











