关键是以IndexedDB暂存编辑操作、Service Worker后台同步、服务端幂等接口处理冲突,Storage事件仅作多标签页UI状态广播。

实现网页版协作文档的离线操作与本地同步,关键不是让 Storage 事件“驱动同步”,而是用它作为多标签页间状态广播的轻量信使——真正可靠的数据暂存、冲突协调和最终上行,必须由 IndexedDB + Service Worker + 服务端幂等接口共同完成。Storage 事件只负责 UI 层的即时感知和一致性刷新。
用 IndexedDB 暂存所有离线编辑动作
LocalStorage 容量小、无事务、无法结构化查询,不适合存文档变更记录。应使用 IndexedDB 创建专用 objectStore(如 doc_edits),每条记录包含:
- docId:文档唯一标识(服务端下发)
- operation:类型(insert / update / delete / replace)
- content:变更内容(富文本片段、光标位置、delta 操作等)
- timestamp:客户端生成的毫秒级时间戳(用于冲突排序)
- clientId:当前设备/会话唯一 ID(避免同设备多标签重复提交)
- status:pending / synced / failed
每次用户输入、粘贴、格式调整,都封装为一条原子操作写入 IndexedDB 事务中,不直接改 localStorage。
Storage 事件仅用于跨页 UI 同步与状态提示
当用户在 A 标签页点击「保存草稿」或「网络恢复自动提交」后,可向 localStorage 写一个轻量控制键,触发其他页面响应:
- 写入 localStorage.setItem('doc_sync_status', JSON.stringify({ docId, status: 'syncing' }))
- 所有同源页面监听 storage 事件,仅当 e.key === 'doc_sync_status' 时解析并更新 UI(如显示「正在同步…」图标)
- 注意:该事件不会在触发页自身执行,因此主操作页需同步更新自身 UI,其他页靠事件补全
- 不存实际文档内容到 localStorage,仅作状态广播通道
Service Worker 管理后台同步与冲突回传
离线期间的编辑记录,不能依赖用户手动刷新来上传。应启用 Background Sync:
- 页面加载后注册:navigator.serviceWorker.ready.then(sw => sw.sync.register('sync-doc-' + docId))
- 在 Service Worker 的 sync 事件中,从 IndexedDB 查询对应 docId 的 pending 记录,按 timestamp 排序逐条 POST 到 /api/docs/{id}/edits
- 服务端响应需返回 conflict: true/false 和 serverVersion;若冲突,将服务端最新 content + version 写回 IndexedDB,并标记该条为 conflicted
- 前端收到 conflict 响应后,弹出合并提示,或自动采用 OT/CRDT 策略计算合并结果再重试
服务端必须提供版本控制与幂等写入能力
前端的任何同步机制,离开服务端配合就无法闭环。后端接口至少需支持:
- 接收 clientId + timestamp + operation,并校验是否已处理过相同 clientId+timestamp 组合(防重放)
- 对同一 docId 的多次 update,按 timestamp 合并或拒绝旧时间戳请求
- 响应中返回当前文档的 latestVersion 和完整 contentHash,供前端比对本地缓存是否过期
- 提供 /api/docs/{id}/diff?since=xxx 接口,供页面重连后拉取增量变更,而非全量覆盖
不复杂但容易忽略:Storage 事件只是表层信号,真正的离线协同能力藏在 IndexedDB 的事务写入、Service Worker 的后台调度、以及服务端的版本决策逻辑里。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











