必须监听 form 的 submit 事件而非 button 的 click,因为回车、form.requestsubmit()、校验插件等均绕过 click;只有 submit 是所有提交路径的必经出口,需在其中统一禁用全部 type="submit" 元素。

连击导致多次提交,本质是 submit 事件没被统一拦截——只禁用一个按钮、只监听 click、或把 disabled = true 写在异步回调里,全都不够用。
为什么必须监听 form 的 submit 事件而不是 button 的 click
用户按回车、调用 form.requestSubmit()、甚至某些校验插件自动触发提交,都会绕过 click。只有 submit 是所有提交路径的必经出口。
- 在
form.addEventListener('submit', ...)中第一行就遍历禁用所有type="submit"元素,才能覆盖全部入口 - 别写
button.onclick = () => { btn.disabled = true; fetch(...) }—— 回车提交时完全失效 - 如果用了 jQuery Validate 或其他表单插件,确认它是否已阻止默认行为;若你在
submitHandler里再手动调用form.submit(),等于双触发
禁用按钮的正确时机和范围
button.disabled = true 必须出现在 fetch 或 XMLHttpRequest 调用之前,且要作用于所有提交控件,不能只锁一个 ID。
- 执行顺序必须是:
event.preventDefault()→form.checkValidity()(校验失败时不锁)→ 遍历form.querySelectorAll('button[type="submit"], input[type="submit"]')并设disabled = true - 成功提交后通常跳转或清空表单,不用恢复按钮;但失败或取消时,必须在
catch和abort分支里手动恢复disabled = false - 如果页面有
form.reset(),它会重置disabled状态——要么改用 JS 手动清空字段,要么监听reset事件后立即重设禁用
服务端幂等校验不可省略
前端禁用只是防手抖,刷新、F5、开发者工具都能绕过。没有服务端校验,重复请求照样落库。
- 客户端每次提交必须带唯一
idempotency-key(如 UUID),服务端用 Redis 缓存该 key 并设 5–10 分钟过期 - 数据库写操作加唯一约束(如订单号唯一),配合
INSERT ... ON CONFLICT DO NOTHING - 返回状态码要区分:首次成功用
201 Created,重复提交用409 Conflict或422 Unprocessable Entity -
submit_token字段必须由后端生成并绑定 session,前端不能自己Math.random()拼,也不能复用旧 token
最容易被忽略的是 submit_token 的生命周期管理——它得随表单加载动态注入,且每次新表单请求都换新值;否则 token 复用或过期,前端锁得再严也没意义。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











