必须用 javascript 拦截表单提交并阻止默认行为,离线时序列化数据存 localstorage(文件仅存元信息),联网后通过 request_id 幂等重发并清理本地存储。

不能靠 HTML 自身实现网络恢复后自动提交,必须用 JavaScript 拦截表单、捕获离线状态、持久化数据,并在检测到网络可用时主动触发重发。
submit 事件里必须调 event.preventDefault()
原生表单提交会刷新页面或跳转,所有 JS 逻辑立即中断。不阻止默认行为,后续任何重试、存储、监听都无效。
- 漏掉这一步,
localStorage写入可能成功,但页面已跳走,用户根本看不到“已保存”提示 - 移动端 Safari 对
beforeunload限制极严,无法靠它兜底;唯一可靠入口就是 submit 事件本身 - 建议统一用
form.addEventListener('submit', handler),避免内联onsubmit不易维护
离线时把表单数据存进 localStorage,但别直接存 FormData
FormData 对象无法被 JSON.stringify 序列化,直接存会变成空对象 {}。必须先转成可序列化的结构。
- 用
Object.fromEntries(new FormData(form))提取键值对,再JSON.stringify存储 - 文件类字段(
<input type="file">)不能存二进制内容,只存文件名、大小、类型等元信息,上传动作留待联网后重新触发FileReader或分片上传 - 为避免多表单冲突,key 名建议带时间戳或哈希,例如
pendingForm_${Date.now()}
监听 navigator.onLine 并配合定时探测,别信它返回 true 就真能通
navigator.onLine 仅反映浏览器是否认为自己在线,Wi-Fi 已连但网关不通、DNS 故障、防火墙拦截时它仍返回 true。
- 建议组合使用:
if (navigator.onLine) await fetch('/health', { method: 'HEAD', cache: 'no-store' })做轻量探测 - 失败时不要立刻重试,加个 3–5 秒延迟,避免高频轮询打爆本地 DNS 缓存
- 首次联网成功后,只触发一次重发;不要持续监听,防止重复提交
重发前检查服务端是否已处理过该请求
表单重发不是简单地再 POST 一遍,重复下单、重复扣款的风险真实存在。
- 客户端生成唯一
request_id(如crypto.randomUUID()),随表单一起提交并存入localStorage - 服务端收到后先查该
request_id是否已存在,若存在则直接返回成功响应,不重复执行业务逻辑 - 重发成功后,务必从
localStorage中removeItem对应条目,否则下次打开页面又会再发一次
真正难的不是存和发,而是判断“这次到底要不要发”——网络恢复只是必要条件,不是充分条件。请求幂等性、服务端去重、客户端状态清理,三者缺一不可。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











