离线表单提交通过service worker拦截post请求并暂存至indexeddb,网络恢复后自动重试(最多3次),配合幂等接口与ui状态提示实现可靠闭环。

拦截表单请求并本地暂存
用户点击提交时,页面脚本不直接发请求,而是通过 fetch 事件让 Service Worker 拦截。重点识别 POST 请求,尤其是 Content-Type 为 application/x-www-form-urlencoded 或 multipart/form-data 的表单。
在 Service Worker 中调用 event.request.clone() 获取可读体,再提取 URL、method、headers 和 body。FormData 类型需提前在主页面转成键值对或 JSON;含文件时,必须在页面中用 FileReader.readAsArrayBuffer() 预处理为 ArrayBuffer,再存入 IndexedDB。
每条记录带唯一 ID(如 UUID v4)、时间戳和重试计数,写入前确保 IndexedDB 已连接。若失败,可降级存到 localStorage(仅限纯文本数据)。
网络恢复后自动重试
Service Worker 无法监听 navigator.onLine,所以不能只靠 online 事件。实际做法是:主页面在 fetch 失败时主动发送 postMessage({type: 'TRY_RESEND'}) 给 Service Worker;也可配合定时轮询或客户端检测到网络可用后通知。
收到消息后,Service Worker 从 IndexedDB 读取 pending 队列,按时间顺序逐条发起 fetch。每次重试都包裹在 event.waitUntil() 中,确保生命周期足够长。
- 成功则删除该条记录,并将结果同步回页面
- 失败保留记录,更新重试次数,最多重试 3 次后停止
- 避免无限重试,服务端需支持幂等(例如用客户端生成的 request-id 去重)
让用户清楚当前状态
提交动作一触发,UI 就显示「已保存至离线队列」,而不是「提交成功」。用户需要知道数据还没真正到达服务器。
通过 MessageChannel 或 client.postMessage 把重试结果(成功/失败/跳过)实时传回页面。关键表单(如订单、留言)可在页面加载时查询待提交数量,用小红点或顶部横幅提示。
提供「查看暂存内容」入口,列出摘要信息(时间、字段概要、状态),支持手动重试或取消。这样用户始终有掌控感,不会因“黑盒重试”而焦虑。
服务端与前端协同闭环
服务端接口必须接受客户端传来的唯一 ID(如 order_id 或 request_id),并基于它做幂等判断。推荐用数据库唯一约束 + UPSERT,或先查后插。
响应格式统一包含:success: boolean、message、server_order_id(如有)。前端收到成功响应后,在 IndexedDB 中补全 server_order_id 和 synced_at 字段,便于后续追溯和查询。
对于文件类字段,服务端需兼容 base64 或 multipart 两种上传方式;若前端用 ArrayBuffer 重建 FormData,服务端应能正确解析 Blob 并保存原始文件。











