- 添加路由预加载功能,使用 requestIdleCallback 在浏览器空闲时预加载 - 配置 Vite manualChunks 分离 vendor 库(vue/ui/chart/i18n/misc) - 新增 NavigationProgress 导航进度条组件,支持防闪烁和无障碍 - 集成 Vitest 测试框架,添加 40 个单元测试和集成测试 - 支持 prefers-reduced-motion 和暗色模式 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
92 lines
2.4 KiB
Vue
92 lines
2.4 KiB
Vue
<script setup lang="ts">
|
|
import { RouterView, useRouter, useRoute } from 'vue-router'
|
|
import { onMounted, watch } from 'vue'
|
|
import Toast from '@/components/common/Toast.vue'
|
|
import NavigationProgress from '@/components/common/NavigationProgress.vue'
|
|
import { useAppStore, useAuthStore, useSubscriptionStore } from '@/stores'
|
|
import { getSetupStatus } from '@/api/setup'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const appStore = useAppStore()
|
|
const authStore = useAuthStore()
|
|
const subscriptionStore = useSubscriptionStore()
|
|
|
|
/**
|
|
* Update favicon dynamically
|
|
* @param logoUrl - URL of the logo to use as favicon
|
|
*/
|
|
function updateFavicon(logoUrl: string) {
|
|
// Find existing favicon link or create new one
|
|
let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]')
|
|
if (!link) {
|
|
link = document.createElement('link')
|
|
link.rel = 'icon'
|
|
document.head.appendChild(link)
|
|
}
|
|
link.type = logoUrl.endsWith('.svg') ? 'image/svg+xml' : 'image/x-icon'
|
|
link.href = logoUrl
|
|
}
|
|
|
|
// Watch for site settings changes and update favicon/title
|
|
watch(
|
|
() => appStore.siteLogo,
|
|
(newLogo) => {
|
|
if (newLogo) {
|
|
updateFavicon(newLogo)
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
watch(
|
|
() => appStore.siteName,
|
|
(newName) => {
|
|
if (newName) {
|
|
document.title = `${newName} - AI API Gateway`
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
// Watch for authentication state and manage subscription data
|
|
watch(
|
|
() => authStore.isAuthenticated,
|
|
(isAuthenticated) => {
|
|
if (isAuthenticated) {
|
|
// User logged in: preload subscriptions and start polling
|
|
subscriptionStore.fetchActiveSubscriptions().catch((error) => {
|
|
console.error('Failed to preload subscriptions:', error)
|
|
})
|
|
subscriptionStore.startPolling()
|
|
} else {
|
|
// User logged out: clear data and stop polling
|
|
subscriptionStore.clear()
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
onMounted(async () => {
|
|
// Check if setup is needed
|
|
try {
|
|
const status = await getSetupStatus()
|
|
if (status.needs_setup && route.path !== '/setup') {
|
|
router.replace('/setup')
|
|
return
|
|
}
|
|
} catch {
|
|
// If setup endpoint fails, assume normal mode and continue
|
|
}
|
|
|
|
// Load public settings into appStore (will be cached for other components)
|
|
await appStore.fetchPublicSettings()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<NavigationProgress />
|
|
<RouterView />
|
|
<Toast />
|
|
</template>
|