diff --git a/src/components/toast/VersionUpdateToast.vue b/src/components/toast/VersionUpdateToast.vue new file mode 100644 index 00000000..4498dd40 --- /dev/null +++ b/src/components/toast/VersionUpdateToast.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/src/composables/useVersionChecker.ts b/src/composables/useVersionChecker.ts new file mode 100644 index 00000000..e3b3535c --- /dev/null +++ b/src/composables/useVersionChecker.ts @@ -0,0 +1,113 @@ +import { ref, computed, h } from 'vue' +import { useToast } from 'vue-toastification' +import i18n from '@/plugins/i18n' +import VersionUpdateToast from '@/components/toast/VersionUpdateToast.vue' + +// 声明全局变量类型 +declare const __APP_VERSION__: string + +// 全局状态 +const currentVersion = ref(__APP_VERSION__) +const serverVersion = ref(null) +const versionChecked = ref(false) +const needsUpdate = computed(() => { + return serverVersion.value !== null && serverVersion.value !== currentVersion.value +}) + +/** + * 版本检查 Composable + * + * 功能: + * - 检查前端版本与服务端版本是否一致 + * - 检测到版本更新时清除缓存和 Service Worker + * - 显示持久化更新通知 + */ +export function useVersionChecker() { + const toast = useToast() + + /** + * 清除所有缓存和 Service Worker + */ + const clearCachesAndServiceWorker = async (): Promise => { + try { + // 1. 清除所有缓存 + if ('caches' in window) { + const cacheNames = await caches.keys() + await Promise.all(cacheNames.map(name => caches.delete(name))) + console.log('[VersionChecker] 已清除所有缓存') + } + + // 2. 注销 Service Worker + if ('serviceWorker' in navigator) { + const registrations = await navigator.serviceWorker.getRegistrations() + await Promise.all(registrations.map(registration => registration.unregister())) + console.log('[VersionChecker] 已注销所有 Service Worker') + } + } catch (error) { + console.error('[VersionChecker] 清除缓存失败:', error) + } + } + + /** + * 显示版本更新通知(带刷新按钮) + */ + const showUpdateNotification = (): void => { + // 使用自定义 Vue 组件作为 toast 内容,传递翻译后的文本作为 props + const component = h(VersionUpdateToast, { + message: i18n.global.t('common.newVersionAvailable'), + refreshText: i18n.global.t('common.refresh'), + }) + + toast.info(component, { + timeout: false, // 不自动消失 + closeButton: false, + closeOnClick: false, + draggable: false, + }) + } + + /** + * 检查版本并在需要时显示更新通知 + * @param latestVersion 服务端返回的最新版本号 + */ + const checkVersion = async (latestVersion: string): Promise => { + // 如果已经检查过,则跳过 + if (versionChecked.value) { + return + } + + // 更新服务端版本 + serverVersion.value = latestVersion + + // 版本不同,且尚未显示通知 + if (needsUpdate.value) { + versionChecked.value = true + console.log(`[VersionChecker] 检测到版本更新: ${currentVersion.value} -> ${latestVersion}`) + + // 清除缓存和 Service Worker + await clearCachesAndServiceWorker() + + // 显示持久化通知 + showUpdateNotification() + } + } + + /** + * 重置版本检查状态(用于测试或特殊场景) + */ + const resetVersionCheck = (): void => { + versionChecked.value = false + serverVersion.value = null + } + + return { + // 状态 + currentVersion: computed(() => currentVersion.value), + serverVersion: computed(() => serverVersion.value), + needsUpdate, + versionChecked: computed(() => versionChecked.value), + // 方法 + checkVersion, + resetVersionCheck, + } +} diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 73660dbb..916e2d8e 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -66,6 +66,7 @@ export default { serviceUnavailable: 'Service Unavailable', status: 'Status', preset: 'Preset', + refresh: 'Refresh', newVersionAvailable: 'New version detected, please refresh the page to get the latest features', }, mediaType: { diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 114010f1..5d4de0a3 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -66,6 +66,7 @@ export default { serviceUnavailable: '服务不可用', status: '状态', preset: '预设', + refresh: '刷新', newVersionAvailable: '检测到新版本,请刷新页面以获取最新功能', }, mediaType: { diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index da49cc34..60206381 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -66,6 +66,7 @@ export default { serviceUnavailable: '服務不可用', status: '狀態', preset: '預設', + refresh: '刷新', newVersionAvailable: '檢測到新版本,請刷新頁面以獲取最新功能', }, mediaType: { diff --git a/src/stores/global.ts b/src/stores/global.ts index 13eac221..50cce208 100644 --- a/src/stores/global.ts +++ b/src/stores/global.ts @@ -1,6 +1,7 @@ import { defineStore } from 'pinia' import type { globalSettingsState } from '@/stores/types' import { fetchGlobalSettings } from '@/utils/globalSetting' +import { useVersionChecker } from '@/composables/useVersionChecker' export const useGlobalSettingsStore = defineStore('globalSettings', { state: (): globalSettingsState => ({ @@ -18,6 +19,12 @@ export const useGlobalSettingsStore = defineStore('globalSettings', { const result = await fetchGlobalSettings() this.data = result || {} this.initialized = true + + // 检查版本更新 + if (result.FRONTEND_VERSION) { + const { checkVersion } = useVersionChecker() + await checkVersion(result.FRONTEND_VERSION) + } } catch (error) { console.error('Failed to initialize global settings', error) } finally { diff --git a/src/utils/globalSetting.ts b/src/utils/globalSetting.ts index bcde0e96..f1586b02 100644 --- a/src/utils/globalSetting.ts +++ b/src/utils/globalSetting.ts @@ -1,58 +1,8 @@ import api from '@/api' -import { useToast } from 'vue-toastification' -import i18n from '@/plugins/i18n' // 创建一个专用的AbortController,用于globalSetting请求 const globalSettingController = new AbortController() -// 声明全局变量类型 -declare const __APP_VERSION__: string - -// 当前前端版本号(由 Vite 在编译时注入) -const CURRENT_FRONTEND_VERSION = __APP_VERSION__ -console.log(`[VersionChecker] 当前前端版本: ${CURRENT_FRONTEND_VERSION}`) - -// 标记是否已经显示过版本更新通知 -let versionNotificationShown = false - -/** - * 检查版本并在需要时显示更新通知 - */ -async function checkVersionAndNotify(serverVersion: string): Promise { - // 版本不同,且尚未显示通知 - if (serverVersion !== CURRENT_FRONTEND_VERSION && !versionNotificationShown) { - versionNotificationShown = true - console.log(`[VersionChecker] 检测到版本更新: ${CURRENT_FRONTEND_VERSION} -> ${serverVersion}`) - - try { - // 1. 清除所有缓存 - if ('caches' in window) { - const cacheNames = await caches.keys() - await Promise.all(cacheNames.map(name => caches.delete(name))) - console.log('[VersionChecker] 已清除所有缓存') - } - - // 2. 注销Service Worker - if ('serviceWorker' in navigator) { - const registrations = await navigator.serviceWorker.getRegistrations() - await Promise.all(registrations.map(registration => registration.unregister())) - console.log('[VersionChecker] 已注销所有 Service Worker') - } - } catch (error) { - console.error('[VersionChecker] 清除缓存失败:', error) - } - - // 3. 显示持久化通知,提示用户刷新 - const toast = useToast() - toast.info(i18n.global.t('common.newVersionAvailable'), { - timeout: false, // 不自动消失 - closeButton: false, - closeOnClick: false, - draggable: false, - }) - } -} - export async function fetchGlobalSettings() { try { const result: { [key: string]: any } = await api.get('system/global', { @@ -62,15 +12,7 @@ export async function fetchGlobalSettings() { // 手动设置signal,防止reqestOptimizer添加可中断的controller signal: globalSettingController.signal, }) - - const data = result.data || {} - - // 检查版本更新 - if (data.FRONTEND_VERSION) { - await checkVersionAndNotify(data.FRONTEND_VERSION) - } - - return data + return result.data || {} } catch (error) { console.error('Failed to fetch global settings', error) throw error