mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 15:36:49 +08:00
refactor(versionChecker): 重构版本检查功能并更新通知样式
This commit is contained in:
@@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div class="version-update-toast">
|
||||||
|
<span class="message">{{ message }}</span>
|
||||||
|
<button class="refresh-button" @click="handleRefresh">
|
||||||
|
{{ refreshText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 接收 props
|
||||||
|
interface Props {
|
||||||
|
message: string
|
||||||
|
refreshText: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.version-update-toast {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-button {
|
||||||
|
padding: 6px 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-button:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-button:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -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<string | null>(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<void> => {
|
||||||
|
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<void> => {
|
||||||
|
// 如果已经检查过,则跳过
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,7 @@ export default {
|
|||||||
serviceUnavailable: 'Service Unavailable',
|
serviceUnavailable: 'Service Unavailable',
|
||||||
status: 'Status',
|
status: 'Status',
|
||||||
preset: 'Preset',
|
preset: 'Preset',
|
||||||
|
refresh: 'Refresh',
|
||||||
newVersionAvailable: 'New version detected, please refresh the page to get the latest features',
|
newVersionAvailable: 'New version detected, please refresh the page to get the latest features',
|
||||||
},
|
},
|
||||||
mediaType: {
|
mediaType: {
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export default {
|
|||||||
serviceUnavailable: '服务不可用',
|
serviceUnavailable: '服务不可用',
|
||||||
status: '状态',
|
status: '状态',
|
||||||
preset: '预设',
|
preset: '预设',
|
||||||
|
refresh: '刷新',
|
||||||
newVersionAvailable: '检测到新版本,请刷新页面以获取最新功能',
|
newVersionAvailable: '检测到新版本,请刷新页面以获取最新功能',
|
||||||
},
|
},
|
||||||
mediaType: {
|
mediaType: {
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export default {
|
|||||||
serviceUnavailable: '服務不可用',
|
serviceUnavailable: '服務不可用',
|
||||||
status: '狀態',
|
status: '狀態',
|
||||||
preset: '預設',
|
preset: '預設',
|
||||||
|
refresh: '刷新',
|
||||||
newVersionAvailable: '檢測到新版本,請刷新頁面以獲取最新功能',
|
newVersionAvailable: '檢測到新版本,請刷新頁面以獲取最新功能',
|
||||||
},
|
},
|
||||||
mediaType: {
|
mediaType: {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type { globalSettingsState } from '@/stores/types'
|
import type { globalSettingsState } from '@/stores/types'
|
||||||
import { fetchGlobalSettings } from '@/utils/globalSetting'
|
import { fetchGlobalSettings } from '@/utils/globalSetting'
|
||||||
|
import { useVersionChecker } from '@/composables/useVersionChecker'
|
||||||
|
|
||||||
export const useGlobalSettingsStore = defineStore('globalSettings', {
|
export const useGlobalSettingsStore = defineStore('globalSettings', {
|
||||||
state: (): globalSettingsState => ({
|
state: (): globalSettingsState => ({
|
||||||
@@ -18,6 +19,12 @@ export const useGlobalSettingsStore = defineStore('globalSettings', {
|
|||||||
const result = await fetchGlobalSettings()
|
const result = await fetchGlobalSettings()
|
||||||
this.data = result || {}
|
this.data = result || {}
|
||||||
this.initialized = true
|
this.initialized = true
|
||||||
|
|
||||||
|
// 检查版本更新
|
||||||
|
if (result.FRONTEND_VERSION) {
|
||||||
|
const { checkVersion } = useVersionChecker()
|
||||||
|
await checkVersion(result.FRONTEND_VERSION)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize global settings', error)
|
console.error('Failed to initialize global settings', error)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,58 +1,8 @@
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { useToast } from 'vue-toastification'
|
|
||||||
import i18n from '@/plugins/i18n'
|
|
||||||
|
|
||||||
// 创建一个专用的AbortController,用于globalSetting请求
|
// 创建一个专用的AbortController,用于globalSetting请求
|
||||||
const globalSettingController = new AbortController()
|
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<void> {
|
|
||||||
// 版本不同,且尚未显示通知
|
|
||||||
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() {
|
export async function fetchGlobalSettings() {
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('system/global', {
|
const result: { [key: string]: any } = await api.get('system/global', {
|
||||||
@@ -62,15 +12,7 @@ export async function fetchGlobalSettings() {
|
|||||||
// 手动设置signal,防止reqestOptimizer添加可中断的controller
|
// 手动设置signal,防止reqestOptimizer添加可中断的controller
|
||||||
signal: globalSettingController.signal,
|
signal: globalSettingController.signal,
|
||||||
})
|
})
|
||||||
|
return result.data || {}
|
||||||
const data = result.data || {}
|
|
||||||
|
|
||||||
// 检查版本更新
|
|
||||||
if (data.FRONTEND_VERSION) {
|
|
||||||
await checkVersionAndNotify(data.FRONTEND_VERSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch global settings', error)
|
console.error('Failed to fetch global settings', error)
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
Reference in New Issue
Block a user