feat(web): Web 装到桌面(PWA),带安装引导与发版提示
CI / backend (push) Canceled after 0s

用 vite-plugin-pwa 生成 manifest 与 Service Worker,不自己写缓存逻辑。

- 装到桌面:manifest 名字/主题色/三个图标(any + maskable + apple-touch-icon),
  图标和页头、安卓桌面是同一枚;首页底部给安装引导,能装就弹「安装」,
  iOS 给「分享 → 添加到主屏幕」,关掉记在 localStorage
- 发版提示:registerType=prompt,收到新版本只在底部提示,点「刷新」才换。
  故意不自动刷新——Vditor、图表是懒加载分片,后台换 chunk 会让面板 404
- 缓存边界:只预缓存页面骨架(约 0.8MB),/api/ 一律 NetworkOnly,
  离线宁可报错也不端旧数据;/vditor/ 走 CacheFirst,不拖慢首次安装
- 顺手修:UpdatePrompt 居中改用 flex 包一层,animate-slide-up 的 keyframes
  自带 transform,会和 -translate-x-1/2 抢属性,手机窄屏会把卡片推出屏幕

验证:web/e2e/pwa.mjs 19 项全绿(manifest/SW 激活/预缓存/安装引导/iOS 文案/
发版提示/断网仍能打开);tsc 通过、vite build 通过。

已知限制:Service Worker 只在 https 或 localhost 注册,生产入口还是 http,
所以内网 IP 上装不了——需要域名 + 证书反代或隧道,已写进 DEPLOY.md 第 6 节。
This commit is contained in:
Steven
2026-10-31 04:33:43 +08:00
parent 370d8923c8
commit 483b554c3c
18 changed files with 4798 additions and 26 deletions
+40
View File
@@ -501,3 +501,43 @@
### 7.6 没做的
- 安卓端登录页仍只有验证码,账号页也没有——下一步对齐(密码登录 + 改昵称 + 退出)
- 手机号换绑、头像上传未做(头像现在只有首字母)
## 8. Web 装到桌面(PWA,2026-10-31)
### 8.1 要解决的问题
"web版本帮我封装成PWA,这样方便安装到手机桌面和电脑桌面。"(用户原话)
现在每次用都要敲地址、开浏览器、找标签页;手机上还给不了"点了图标就进来"的感觉。
### 8.2 决定(写进注释了,别以后翻案)
- 用 `vite-plugin-pwa`(本就在 devDependencies 里,2.0.0)生成 SW 与 manifest,不自己写
- `registerType: "prompt"` + `injectRegister: null`:**有新版只提示,点「刷新」才换**。
理由:Vditor、图表是懒加载分片,`autoUpdate` 会在用户开着页面时把 chunk 换掉,
下一次点开面板就是 404(编辑器白屏)
- 预缓存只放构建产物与图标(约 0.8MB);`/api/` 一律 NetworkOnly——离线宁可报错,也不端旧数据
- 图标复用 Web Logo 的 32 单位坐标生成,和页头、安卓桌面图标同一枚
### 8.3 实现
| 位置 | 内容 |
|---|---|
| `web/vite.config.ts` | manifest(standalone、主题色 `#2f6054`、三个图标)+ workbox(`navigateFallback` 回退 `index.html`,`/vditor/` 走 CacheFirst,`/api/` NetworkOnly) |
| `web/index.html` | favicon 改成同款 Logo(内联 SVG)、`theme-color`、`apple-touch-icon`、`viewport-fit=cover` |
| `web/src/lib/pwa.ts` | 生产环境注册 SW、`dadao:pwa-update-ready` 事件、`applyServiceWorkerUpdate()`、`isStandalone()` |
| `web/src/components/UpdatePrompt.tsx` | 底部卡片「有新版了」+ 稍后 / 刷新(**居中用 flex 包一层**:`animate-slide-up` 的 keyframes 自带 transform,会和 `-translate-x-1/2` 抢属性,手机窄屏上卡片会被推出屏幕右侧) |
| `web/src/components/InstallHint.tsx` | 首页底部安装引导:能装就给「安装」按钮(拦 `beforeinstallprompt`),iOS 给"分享 → 添加到主屏幕",关掉记在 localStorage |
| `web/public/icons/` | `icon-192` / `icon-512`(any)、`maskable-512`、`apple-touch-icon`(180,iOS 不吃透明) |
### 8.4 验证
- `web/e2e/pwa.mjs`(**19 项全绿**):manifest 名字/standalone/三个图标 → 图标都能下载 200 →
SW 已 `activated` 且作用域是整站 → 页面骨架已进预缓存 → 主题色 → 桌面尺寸控制台零报错 →
窄屏有安装引导 → iOS 文案是「分享 → 添加到主屏幕」→ 关掉后刷新不再出现 →
有新版会提示 → 点「稍后」收起 → **断网后页面骨架仍能打开**
- 跑法:后端用 `WEB_DIST_DIR=<repo>/web/dist` 起一个同构站点(生产同形),
然后 `node web/e2e/pwa.mjs`;`BASE` 可换环境
- 截图:`web/e2e/artifacts/pwa-install-hint.png`、`pwa-update-prompt.png`、`pwa-offline.png`
### 8.5 还没解开的
- **生产入口还是 http**:Service Worker 只在 https / localhost 注册,所以内网 IP 上装不了、
也看不到安装提示(桌面用 `127.0.0.1` 打开可以)。要么加域名 + 证书反代(`443 → 127.0.0.1:8200`),
要么用 Cloudflare Tunnel 之类拿个 https 域名;应用代码不用改。已写进 `DEPLOY.md` 第 6 节
- 安卓端还在走自己的 OTA(原生包),和 PWA 是两条路;要不要让安卓也改成加载 Web,等产品定