进销存 PWA 离线应用实践
仓库里网络信号差、客户现场没有 Wi-Fi,是进销存移动使用的真实场景。如果断网就开不了单,Web 版进销存就很难取代本地软件。PWA(Progressive Web App)让网页应用具备离线能力:断网时正常开单,联网后自动同步。本文介绍我们为进销存 Web 版实现离线能力的完整方案。
一、整体架构:外壳缓存 + 数据队列
离线能力分两层解决:
- 应用外壳(App Shell)离线:HTML、CSS、JS、字体等静态资源通过 Service Worker 预缓存,断网时应用照常打开。
- 业务数据离线:商品档案、客户档案等基础数据同步到 IndexedDB,开单时本地查询;新开的单据写入本地待同步队列,联网后批量提交。
注意 Service Worker 只能可靠拦截它注册路径下的请求,缓存配额在不同浏览器差异较大(移动端 Safari 尤其紧张),所以离线设计的第一原则是:基础数据要精简。商品档案只同步编码、名称、规格、单位、默认价格几个字段,图片不预同步,用占位图按需加载。
二、Service Worker:预缓存与运行时缓存
静态资源采用"构建时预缓存 + 运行时缓存"双策略。构建工具(Vite 的 workbox 插件或自写脚本)在打包时生成资源清单,Service Worker 安装阶段全部预缓存;运行时对 API 响应按类别设置不同策略:
| 数据类型 | 缓存策略 | 说明 |
|---|---|---|
| 商品/客户档案 | Stale-While-Revalidate | 先返回缓存立即渲染,后台更新缓存,下次生效 |
| 单据提交 | Network Only + 失败入队 | 必须发出去,失败转本地队列 |
| 库存查询 | Network First | 库存必须尽量准,断网才用缓存并标注时间 |
版本更新用"先清后装":新 Service Worker 激活前删除旧版本所有缓存,避免新旧资源混杂。提示用户刷新可以用简单的 postMessage 通知页面弹出"新版本可用"条。
三、IndexedDB:离线单据队列
离线开的单据不能丢,也不能乱序。我们在 IndexedDB 里建两个对象仓库:
- pending_docs:待同步单据队列,按创建时间排序,每行存完整单据快照 + 本地 UUID + 状态(待同步 / 同步中 / 失败)。
- base_data:基础档案缓存,按类型分索引(商品、客户、仓库)。
同步流程:监听 online 事件和服务端可达探测(发一个轻量 ping 接口,因为 online 事件在有局域网但没外网时不准),网络恢复后按队列顺序逐张提交。每张单据带本地 UUID,服务端按 UUID 幂等去重——这是防止"重试导致重复单据"的关键,重复提交网络环境下必须做。
四、冲突处理:先到先得 + 人工干预
离线期间两个人改了同一张单、或者离线开的单与线上库存冲突,是同步绕不开的问题。我们的原则:
- 新单据(离线开的销售单):不冲突,直接入库,但同步后要校验库存是否充足,不足则标记"缺货待审"而不是自动退单。
- 改单据:离线状态下只允许新建不允许编辑已有单据,从流程上消灭大半冲突。
- 库存不足:同步后进入异常清单,通知开单人人工处理。
五、安装与体验
达到可安装标准需要三件套:HTTPS、有效的 Web App Manifest、注册的 Service Worker。Manifest 里配置 display: standalone、主题色、图标(192/512 两档 PNG 加 maskable 版本),用户在手机浏览器菜单里就能"添加到主屏幕",图标和原生应用无异。
最后加一个离线状态指示条:全局监听网络状态,离线时在页面顶部显示"离线模式,单据将在联网后同步"的黄色提示条,让用户对数据去向有确定感。这个小小的提示能大幅降低断网时用户的焦虑和对系统的不信任。
结语
PWA 让 Web 版进销存第一次具备了和本地软件同等的现场可用性。实现要点就三条:外壳预缓存保打开、IndexedDB 队列保单据不丢、UUID 幂等保同步不重。配合 表单设计的键盘流,仓库里一部手机就能完成开单全流程。