Files
one-pipe-system/src/App.vue
sexygoat f62a8d98a4
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m55s
fix: 自动更新提示bug
2026-05-29 12:11:03 +08:00

198 lines
5.8 KiB
Vue

<template>
<ChunkErrorBoundary>
<ElConfigProvider size="default" :locale="locales[language]" :z-index="3000">
<RouterView></RouterView>
</ElConfigProvider>
</ChunkErrorBoundary>
</template>
<script setup lang="ts">
import { useUserStore } from './store/modules/user'
import zh from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
import { systemUpgrade } from '@/utils'
import { AuthService } from '@/api/modules'
import { ApiStatus } from './utils/http/status'
import { setThemeTransitionClass } from '@/utils'
import { checkStorageCompatibility } from '@/utils'
import ChunkErrorBoundary from '@/components/core/others/ChunkErrorBoundary.vue'
import { ElMessageBox } from 'element-plus'
const userStore = useUserStore()
const { language } = storeToRefs(userStore)
const locales = {
zh: zh,
en: en
}
const VERSION_CHECK_INTERVAL = 5 * 60 * 1000
const VERSION_PROMPT_STORAGE_KEY = 'admin-xm-iot:confirmed-version-update'
let versionCheckTimer: number | undefined
let currentEntrySignature = ''
let upgradePromptVisible = false
let removeVisibilityChangeListener: (() => void) | undefined
onBeforeMount(() => {
setThemeTransitionClass(true)
})
onMounted(() => {
// 检查存储兼容性
checkStorageCompatibility()
// 提升暗黑主题下页面刷新视觉体验
setThemeTransitionClass(false)
// 系统升级
systemUpgrade()
// 检查前端版本更新
startVersionUpdateCheck()
// 获取用户信息
getUserInfo()
})
onBeforeUnmount(() => {
if (versionCheckTimer) {
window.clearInterval(versionCheckTimer)
}
removeVisibilityChangeListener?.()
})
// 获取用户信息
const getUserInfo = async () => {
if (userStore.isLogin && userStore.accessToken) {
try {
const res = await AuthService.getUserInfo()
if (res.code === ApiStatus.success && res.data) {
// API 返回的是 { user, permissions },我们需要保存 user 和 permissions
userStore.setUserInfo(res.data.user)
userStore.setPermissions(res.data.permissions || [])
}
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
}
const getEntrySignature = (html: string) => {
const matches = html.match(/(?:src|href)="[^"]*\/assets\/[^""]+\.(?:js|css)"/g)
return matches?.sort().join('|') || ''
}
const getLoadedEntrySignature = () => {
return Array.from(
document.querySelectorAll<HTMLScriptElement | HTMLLinkElement>('script[src],link[href]')
)
.map((element) => ('src' in element ? element.src : element.href))
.filter((url) => /\/assets\/.*\.(js|css)(\?|$)/.test(url))
.sort()
.join('|')
}
const getStringHash = (value: string) => {
let hash = 0
for (let i = 0; i < value.length; i++) {
hash = (hash << 5) - hash + value.charCodeAt(i)
hash |= 0
}
return Math.abs(hash).toString(36)
}
const getBrowserFingerprint = () => {
return getStringHash(
[
navigator.userAgent,
navigator.language,
navigator.platform,
Intl.DateTimeFormat().resolvedOptions().timeZone,
`${screen.width}x${screen.height}x${screen.colorDepth}`
].join('|')
)
}
const getConfirmedVersionKey = (signature: string) => {
return `${getBrowserFingerprint()}:${getStringHash(signature)}`
}
const getConfirmedVersionUpdate = () => {
try {
return localStorage.getItem(VERSION_PROMPT_STORAGE_KEY)
} catch {
return null
}
}
const setConfirmedVersionUpdate = (signature: string) => {
try {
localStorage.setItem(VERSION_PROMPT_STORAGE_KEY, getConfirmedVersionKey(signature))
} catch {
// localStorage may be unavailable in private mode.
}
}
const reloadWithCacheBuster = () => {
const url = new URL(window.location.href)
url.searchParams.set('_t', Date.now().toString())
window.location.replace(url.toString())
}
const checkVersionUpdate = async () => {
if (upgradePromptVisible || document.hidden) return
try {
const response = await fetch(import.meta.env.BASE_URL, {
cache: 'no-store'
})
const html = await response.text()
const latestSignature = getEntrySignature(html)
if (!latestSignature) return
if (!currentEntrySignature) {
currentEntrySignature = latestSignature
return
}
if (currentEntrySignature !== latestSignature) {
if (getConfirmedVersionUpdate() === getConfirmedVersionKey(latestSignature)) {
currentEntrySignature = latestSignature
return
}
upgradePromptVisible = true
await ElMessageBox.alert('系统已发布新版本,请刷新页面后继续使用。', '发现新版本', {
confirmButtonText: '立即更新',
type: 'warning',
closeOnClickModal: false,
closeOnPressEscape: false
})
setConfirmedVersionUpdate(latestSignature)
currentEntrySignature = latestSignature
reloadWithCacheBuster()
}
} catch (error) {
console.warn('检查版本更新失败:', error)
} finally {
upgradePromptVisible = false
}
}
const startVersionUpdateCheck = () => {
if (import.meta.env.DEV) return
currentEntrySignature = getLoadedEntrySignature()
checkVersionUpdate()
versionCheckTimer = window.setInterval(checkVersionUpdate, VERSION_CHECK_INTERVAL)
const handleVisibilityChange = () => {
if (!document.hidden) {
checkVersionUpdate()
}
}
document.addEventListener('visibilitychange', handleVisibilityChange)
removeVisibilityChangeListener = () => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
}
}
</script>