Преглед изворни кода

feat(web-admin): force a refresh when a newer build is deployed.

Co-authored-by: Cursor <cursoragent@cursor.com>
wuzilong пре 2 недеља
родитељ
комит
020fbe1b0e
4 измењених фајлова са 61 додато и 2 уклоњено
  1. 1 0
      web-admin/src/env.d.ts
  2. 3 1
      web-admin/src/main.ts
  3. 40 0
      web-admin/src/utils/check-app-update.ts
  4. 17 1
      web-admin/vite.config.ts

+ 1 - 0
web-admin/src/env.d.ts

@@ -63,3 +63,4 @@ declare module 'qrcode.vue' {
 
 // 全局变量声明
 declare const __APP_VERSION__: string // 版本号
+declare const __APP_BUILD_ID__: string // 构建指纹:短 git hash + 构建时间戳

+ 3 - 1
web-admin/src/main.ts

@@ -16,6 +16,7 @@ import '@styles/index.scss'                         // 样式
 import '@utils/sys/console.ts'                      // 控制台输出内容
 import { setupGlobDirectives } from './directives'
 import { setupErrorHandle } from './utils/sys/error-handle'
+import { startAppUpdateCheck } from './utils/check-app-update'
 
 function maskSecret(value?: string) {
   if (!value) return '(empty)'
@@ -62,4 +63,5 @@ setupGlobDirectives(app)
 setupErrorHandle(app)
 
 app.use(language)
-app.mount('#app')
+app.mount('#app')
+startAppUpdateCheck()

+ 40 - 0
web-admin/src/utils/check-app-update.ts

@@ -0,0 +1,40 @@
+// web-admin/src/utils/check-app-update.ts
+import { startAppUpdateChecker } from './app-update'
+
+/** 生产环境检查 dist/version.json,发现新 buildId 后强制刷新。 */
+export function startAppUpdateCheck(): () => void {
+  return startAppUpdateChecker({
+    isProd: import.meta.env.PROD,
+    localBuildId: __APP_BUILD_ID__,
+    localVersion: __APP_VERSION__,
+    baseUrl: import.meta.env.BASE_URL,
+    now: () => Date.now(),
+    fetchRemote: async (url) => {
+      const res = await fetch(url, { cache: 'no-store' })
+      if (!res.ok) throw new Error(String(res.status))
+      return res.json()
+    },
+    onReload: () => {
+      location.reload()
+    },
+    alertUpdate: async (message) => {
+      await ElMessageBox.alert(message, '发现新版本', {
+        confirmButtonText: '立即刷新',
+        showClose: false,
+        closeOnClickModal: false,
+        closeOnPressEscape: false,
+        showCancelButton: false,
+        dangerouslyUseHTMLString: true
+      })
+    },
+    setInterval: (fn, ms) => window.setInterval(fn, ms),
+    clearInterval: (id) => window.clearInterval(id),
+    subscribeVisible: (handler) => {
+      const listener = () => {
+        if (document.visibilityState === 'visible') handler()
+      }
+      document.addEventListener('visibilitychange', listener)
+      return () => document.removeEventListener('visibilitychange', listener)
+    }
+  })
+}

+ 17 - 1
web-admin/vite.config.ts

@@ -1,6 +1,8 @@
+import { execSync } from 'node:child_process'
 import { defineConfig, loadEnv } from 'vite'
 import vue from '@vitejs/plugin-vue'
 import path from 'path'
+import { createAppBuildMeta, readGitShort, writeVersionJson } from './scripts/app-build-meta'
 import { fileURLToPath } from 'url'
 import vueDevTools from 'vite-plugin-vue-devtools'
 import viteCompression from 'vite-plugin-compression'
@@ -17,13 +19,20 @@ export default ({ mode }: { mode: string }) => {
   const env = loadEnv(mode, root)
   const { VITE_VERSION, VITE_PORT, VITE_BASE_URL, VITE_API_URL, VITE_API_PROXY_URL } = env
 
+  const gitShort = readGitShort(() =>
+    execSync('git rev-parse --short HEAD', {
+      stdio: ['ignore', 'pipe', 'ignore']
+    }).toString()
+  )
+  const appBuildMeta = createAppBuildMeta(VITE_VERSION || '', Date.now(), gitShort)
+
   console.log(`🚀 API_URL = ${VITE_API_URL}`)
   console.log(`🚀 VERSION = ${VITE_VERSION}`)
 
   return defineConfig({
     define: {
       __APP_VERSION__: JSON.stringify(VITE_VERSION),
-      // html-to-docx 等依赖会读 Node 全局;浏览器映射到 globalThis
+      __APP_BUILD_ID__: JSON.stringify(appBuildMeta.buildId),
       global: 'globalThis'
     },
     base: VITE_BASE_URL,
@@ -88,6 +97,13 @@ export default ({ mode }: { mode: string }) => {
       }
     },
     plugins: [
+      {
+        name: 'emit-version-json',
+        apply: 'build',
+        closeBundle() {
+          writeVersionJson(path.resolve(root, 'dist'), appBuildMeta)
+        }
+      },
       vue(),
       tailwindcss(),
       // 自动按需导入 API