Files
shanxia2.0/docs/superpowers/specs/2026-10-16-web-admin-version-refresh-design.md
T

143 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# web-admin 发版后强制刷新
> 日期:2026-10-16
> 适用范围:`web-admin` 生产构建与运行时
> 交互参考:`docs/superpowers/demos/2026-10-16-web-admin-version-refresh-demo.html`(方案 A · 强制弹窗)
> 决策:静态 `version.json` + 强制弹窗,确认后刷新页面
---
## 1. 目标与已确认决策
### 1.1 目标
1. 管理端发版后,仍停留在旧标签页的用户能得知已有新版本。
2. 用户只能通过「立即刷新」进入新包,不能关掉提示继续用旧功能。
3. 不依赖后端接口;随静态资源部署即生效。
### 1.2 决策摘要
| 项 | 约定 |
|----|------|
| 方案 | **A**:Vite 构建写出 `dist/version.json`,前端比对 `buildId` |
| 交互 | 强制弹窗,无「稍后 / 取消 / 关闭」;确认后 `location.reload()` |
| 未保存表单 | 不另做二次确认,直接弹刷新提示 |
| 展示版本 | 现有 `VITE_VERSION`(注入 `__APP_VERSION__`) |
| 比对指纹 | `buildId` = 短 git hash + 构建时间戳(注入 `__APP_BUILD_ID__`) |
| 轮询 | 生产环境启动后立即查一次,之后每 **10 分钟** |
| 回前台 | `document.visibilityState === 'visible'` 时立刻再查;距上次请求不足 **30 秒**则跳过 |
| 开发环境 | `pnpm dev` 不检查 |
### 1.3 非目标
- 后端版本接口、按发版类型切换「可稍后」
- 小程序更新(已有 `miniprogram/utils/mpUpdate.js`)
- 未保存表单的草稿保护或刷新前二次确认
- 修改 `VITE_VERSION` 的人工发版流程(展示号可不变,`buildId` 每次构建都会变)
---
## 2. 背景
`web-admin` 是 Vite 打包的 SPA(hash 路由)。用户长时间不关标签页时,内存里仍是旧的 JS。现有 `__APP_VERSION__`(来自 `VITE_VERSION`,当前 `3.0.2`)只用于本地存储键隔离(`web-admin/src/utils/storage/storage-config.ts`),发版后不会提示刷新。若发版时忘记改 `VITE_VERSION`,仅比对该字段还会漏报。因此展示用版本号,是否更新用每次构建都变化的 `buildId`。
---
## 3. 构建产物
### 3.1 `dist/version.json`
构建结束时写入,与 `index.html` 同目录(受 `VITE_BASE_URL` 影响时,文件仍在 `outDir` 根上,由静态服务器按 base 提供):
```json
{
"version": "3.0.2",
"buildId": "a1b2c3d-1730000000000"
}
```
| 字段 | 来源 | 用途 |
|------|------|------|
| `version` | `VITE_VERSION` | 弹窗展示 |
| `buildId` | `git rev-parse --short HEAD`,失败时用 `nogit`;再拼接 `-` + 构建时刻毫秒时间戳 | 是否为新包的唯一比对键 |
同一 commit 再次 `pnpm build`,时间戳不同,`buildId` 必须变化。
### 3.2 包内常量
在现有 `define.__APP_VERSION__` 旁增加 `__APP_BUILD_ID__`,值与当次 `version.json` 的 `buildId` 相同。类型声明写在 `web-admin/src/env.d.ts`。
### 3.3 插件位置
在 `web-admin/vite.config.ts` 的 `plugins` 中增加一个仅 `apply: 'build'` 的插件:`closeBundle`(或等价的构建结束钩子)写文件,并在 `config` 阶段把 `buildId` 配进 `define`。开发服务器不写该文件、不注入会触发检查的生产逻辑。
---
## 4. 运行时
### 4.1 模块
新增 `web-admin/src/utils/check-app-update.ts`,由 `web-admin/src/main.ts` 在 `app.mount` 之后调用 `startAppUpdateCheck()`。
职责:
1. `import.meta.env.PROD` 为假时直接返回。
2. 请求 `${import.meta.env.BASE_URL}version.json?t=${Date.now()}`,`cache: 'no-store'`。
3. 解析 JSON。`buildId` 为非空字符串且与 `__APP_BUILD_ID__` 不同时弹窗。
4. 弹窗已打开或已经弹过则不再弹。弹出时清除定时器并移除 `visibilitychange` 监听,不再继续检查。
### 4.2 调度
- 启动后立即检查一次。
- `setInterval` 10 分钟。
- `visibilitychange`:变为 `visible` 时检查;若距上次**发起请求**不足 30 秒则跳过。
- 页面卸载时移除监听并清除定时器。
### 4.3 弹窗
使用 Element Plus `ElMessageBox.alert`(自动导入,不手写 import):
| 项 | 值 |
|----|-----|
| 标题 | 发现新版本 |
| 正文 | 系统已发布更新,当前页面仍在使用旧功能。请刷新后继续操作。 |
| 版本行 | 远端 `version` 为非空字符串时追加:`当前 v{__APP_VERSION__} / 最新 v{远端 version}` |
| 确认按钮 | 立即刷新 |
| `showClose` | `false` |
| `closeOnClickModal` | `false` |
| `closeOnPressEscape` | `false` |
| `showCancelButton` | `false` |
用户点击「立即刷新」后执行一次 `location.reload()`(模块内标志位保证只触发一次)。遮罩无背景模糊,与现有弹窗一致。
多个标签页各自检查、各自弹窗。
---
## 5. 失败处理
| 情况 | 行为 |
|------|------|
| 网络失败、非 2xx、JSON 无法解析、缺少 `buildId` | 不提示,保留定时器,下一轮再试 |
| `buildId` 相同 | 不提示 |
| `buildId` 不同 | 弹一次窗并停止后续检查 |
| 远端无 `version` | 仍按 `buildId` 弹窗,正文不带版本行 |
---
## 6. 部署注意
前端请求已绕过 HTTP 缓存。静态服务器仍应为 `index.html` 与 `version.json` 设置短缓存或 `no-cache`。二者若被长期强缓存,用户点刷新后仍可能拿到旧的 `index.html`。带 hash 的 JS/CSS 可继续长期缓存。
本设计不新增 Nginx 配置文件;上线时由现有发布方式保证这两份入口文件可被重新验证。
---
## 7. 验证
1. 连续构建两次,两次 `dist/version.json` 的 `buildId` 不同。
2. 用第一版打开页面,再换成第二版静态资源:等待轮询,或切走标签页超过 30 秒后再切回,出现不可关闭的「发现新版本」。
3. 点击「立即刷新」后加载第二版,不再弹窗。
4. 删除或无法访问 `version.json` 时,业务页面仍可操作,控制台不把该失败当成未捕获异常打断应用。
5. `pnpm dev` 不请求 `version.json`。