143 lines
5.8 KiB
Markdown
143 lines
5.8 KiB
Markdown
# 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`。
|