进销存 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 里建两个对象仓库:

  1. pending_docs:待同步单据队列,按创建时间排序,每行存完整单据快照 + 本地 UUID + 状态(待同步 / 同步中 / 失败)。
  2. base_data:基础档案缓存,按类型分索引(商品、客户、仓库)。

同步流程:监听 online 事件和服务端可达探测(发一个轻量 ping 接口,因为 online 事件在有局域网但没外网时不准),网络恢复后按队列顺序逐张提交。每张单据带本地 UUID,服务端按 UUID 幂等去重——这是防止"重试导致重复单据"的关键,重复提交网络环境下必须做。

四、冲突处理:先到先得 + 人工干预

离线期间两个人改了同一张单、或者离线开的单与线上库存冲突,是同步绕不开的问题。我们的原则:

  • 新单据(离线开的销售单):不冲突,直接入库,但同步后要校验库存是否充足,不足则标记"缺货待审"而不是自动退单。
  • 改单据:离线状态下只允许新建不允许编辑已有单据,从流程上消灭大半冲突。
  • 库存不足:同步后进入异常清单,通知开单人人工处理。

五、安装与体验

达到可安装标准需要三件套:HTTPS、有效的 Web App Manifest、注册的 Service Worker。Manifest 里配置 display: standalone、主题色、图标(192/512 两档 PNG 加 maskable 版本),用户在手机浏览器菜单里就能"添加到主屏幕",图标和原生应用无异。

最后加一个离线状态指示条:全局监听网络状态,离线时在页面顶部显示"离线模式,单据将在联网后同步"的黄色提示条,让用户对数据去向有确定感。这个小小的提示能大幅降低断网时用户的焦虑和对系统的不信任。

结语

PWA 让 Web 版进销存第一次具备了和本地软件同等的现场可用性。实现要点就三条:外壳预缓存保打开、IndexedDB 队列保单据不丢、UUID 幂等保同步不重。配合 表单设计的键盘流,仓库里一部手机就能完成开单全流程。