From c527af0b1d24ed54dd8eb43ce603cda2df33fde4 Mon Sep 17 00:00:00 2001 From: wuzilong <704541015@qq.com> Date: Fri, 16 Oct 2026 13:19:02 +0800 Subject: [PATCH] docs: record web-admin force-refresh design for stale open tabs. Co-authored-by: Cursor --- ...-10-16-web-admin-version-refresh-demo.html | 561 ++++++++++++++++++ ...-10-16-web-admin-version-refresh-design.md | 142 +++++ 2 files changed, 703 insertions(+) create mode 100644 docs/superpowers/demos/2026-10-16-web-admin-version-refresh-demo.html create mode 100644 docs/superpowers/specs/2026-10-16-web-admin-version-refresh-design.md diff --git a/docs/superpowers/demos/2026-10-16-web-admin-version-refresh-demo.html b/docs/superpowers/demos/2026-10-16-web-admin-version-refresh-demo.html new file mode 100644 index 000000000..9e02a0050 --- /dev/null +++ b/docs/superpowers/demos/2026-10-16-web-admin-version-refresh-demo.html @@ -0,0 +1,561 @@ + + + + + + web-admin · 发版后提示刷新 Demo + + + + + + +
+

web-admin · 发版后「请刷新」交互 Demo

+

+ 模拟客户长时间不关标签页、仍跑旧前端的场景。下面三种提示样式可切换对比; + Token 对齐 Fresh Mint(#10b981)。 +

+
+ +
+ + + + +
+ +
+ + + +
+ +
+
+ 工作台 / 订单列表 + + 页面 v3.0.2 +    + 张三 + +
+
+
+

订单列表

+
+ 已选 0 项 + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
订单号客户状态金额
SO20261016001华中物流配送中¥12,800
SO20261016002海河化工已完成¥6,420
SO20261015088金桥供应链待发货¥9,150
+
+
+
+
+ + + + + +
+
+ +
+

有可用更新

+

新版本 v3.0.3 已上线,刷新即可使用最新功能。

+
+
+
+ + +
+
+
+ +

+ 对比要点: + A 强制弹窗不可稍后(与小程序强制更新一致,适合破坏性发版); + B 顶部横幅不打断当前填表,适合非紧急; + C 右下角最轻,容易被忽略。 + 检测侧通常对比本地 __APP_VERSION__ / 构建产物 hash 与服务器上的 version.json。 +

+ + + + diff --git a/docs/superpowers/specs/2026-10-16-web-admin-version-refresh-design.md b/docs/superpowers/specs/2026-10-16-web-admin-version-refresh-design.md new file mode 100644 index 000000000..612376b72 --- /dev/null +++ b/docs/superpowers/specs/2026-10-16-web-admin-version-refresh-design.md @@ -0,0 +1,142 @@ +# 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`。