mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
fix(frontend): refresh custom page document title
This commit is contained in:
+27
-4
@@ -4,9 +4,9 @@ import { onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import Toast from '@/components/common/Toast.vue'
|
||||
import NavigationProgress from '@/components/common/NavigationProgress.vue'
|
||||
import AdminComplianceDialog from '@/components/admin/AdminComplianceDialog.vue'
|
||||
import { resolveDocumentTitle } from '@/router/title'
|
||||
import { resolveRouteDocumentTitle } from '@/router/title'
|
||||
import AnnouncementPopup from '@/components/common/AnnouncementPopup.vue'
|
||||
import { useAppStore, useAuthStore, useSubscriptionStore, useAnnouncementStore, useAdminComplianceStore } from '@/stores'
|
||||
import { useAppStore, useAuthStore, useSubscriptionStore, useAnnouncementStore, useAdminComplianceStore, useAdminSettingsStore } from '@/stores'
|
||||
import { getSetupStatus } from '@/api/setup'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -16,6 +16,15 @@ const authStore = useAuthStore()
|
||||
const subscriptionStore = useSubscriptionStore()
|
||||
const announcementStore = useAnnouncementStore()
|
||||
const adminComplianceStore = useAdminComplianceStore()
|
||||
const adminSettingsStore = useAdminSettingsStore()
|
||||
|
||||
function updateDocumentTitle() {
|
||||
const customMenuItems = [
|
||||
...(appStore.cachedPublicSettings?.custom_menu_items ?? []),
|
||||
...(authStore.isAdmin ? adminSettingsStore.customMenuItems : []),
|
||||
]
|
||||
document.title = resolveRouteDocumentTitle(route, appStore.siteName, customMenuItems)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update favicon dynamically
|
||||
@@ -44,6 +53,20 @@ watch(
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
[
|
||||
() => route.fullPath,
|
||||
() => route.meta.title,
|
||||
() => route.meta.titleKey,
|
||||
() => appStore.siteName,
|
||||
() => appStore.cachedPublicSettings?.custom_menu_items,
|
||||
() => authStore.isAdmin,
|
||||
() => adminSettingsStore.customMenuItems,
|
||||
],
|
||||
updateDocumentTitle,
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// Watch for authentication state and manage subscription data + announcements
|
||||
function onVisibilityChange() {
|
||||
if (document.visibilityState === 'visible' && authStore.isAuthenticated) {
|
||||
@@ -123,8 +146,8 @@ onMounted(async () => {
|
||||
// Load public settings into appStore (will be cached for other components)
|
||||
await appStore.fetchPublicSettings()
|
||||
|
||||
// Re-resolve document title now that siteName is available
|
||||
document.title = resolveDocumentTitle(route.meta.title, appStore.siteName, route.meta.titleKey as string)
|
||||
// Re-resolve document title now that site settings are available
|
||||
updateDocumentTitle()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -70,12 +70,20 @@ export async function setLocale(locale: string): Promise<void> {
|
||||
document.documentElement.setAttribute('lang', locale)
|
||||
|
||||
// 同步更新浏览器页签标题,使其跟随语言切换
|
||||
const { resolveDocumentTitle } = await import('@/router/title')
|
||||
const { resolveRouteDocumentTitle } = await import('@/router/title')
|
||||
const { default: router } = await import('@/router')
|
||||
const { useAppStore } = await import('@/stores/app')
|
||||
const { useAuthStore } = await import('@/stores/auth')
|
||||
const { useAdminSettingsStore } = await import('@/stores/adminSettings')
|
||||
const route = router.currentRoute.value
|
||||
const appStore = useAppStore()
|
||||
document.title = resolveDocumentTitle(route.meta.title, appStore.siteName, route.meta.titleKey as string)
|
||||
const authStore = useAuthStore()
|
||||
const adminSettingsStore = useAdminSettingsStore()
|
||||
const customMenuItems = [
|
||||
...(appStore.cachedPublicSettings?.custom_menu_items ?? []),
|
||||
...(authStore.isAdmin ? adminSettingsStore.customMenuItems : []),
|
||||
]
|
||||
document.title = resolveRouteDocumentTitle(route, appStore.siteName, customMenuItems)
|
||||
}
|
||||
|
||||
export function getLocale(): LocaleCode {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveDocumentTitle } from '@/router/title'
|
||||
import { resolveDocumentTitle, resolveRouteDocumentTitle } from '@/router/title'
|
||||
|
||||
describe('resolveDocumentTitle', () => {
|
||||
it('路由存在标题时,使用“路由标题 - 站点名”格式', () => {
|
||||
@@ -23,3 +23,27 @@ describe('resolveDocumentTitle', () => {
|
||||
expect(after).toBe('Admin Dashboard - Beta')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveRouteDocumentTitle', () => {
|
||||
it('自定义页面菜单加载后,使用菜单名称作为标题', () => {
|
||||
const route = {
|
||||
name: 'CustomPage',
|
||||
params: { id: 'scheduler' },
|
||||
meta: {
|
||||
title: 'Custom Page'
|
||||
}
|
||||
}
|
||||
|
||||
expect(resolveRouteDocumentTitle(route, 'EzouAPI')).toBe('Custom Page - EzouAPI')
|
||||
expect(resolveRouteDocumentTitle(route, 'EzouAPI', [
|
||||
{
|
||||
id: 'scheduler',
|
||||
label: '账号调度器',
|
||||
icon_svg: '',
|
||||
url: 'https://example.com',
|
||||
visibility: 'admin',
|
||||
sort_order: 0
|
||||
}
|
||||
])).toBe('账号调度器 - EzouAPI')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useNavigationLoadingState } from '@/composables/useNavigationLoading'
|
||||
import { useRoutePrefetch } from '@/composables/useRoutePrefetch'
|
||||
import { getSetupStatus } from '@/api/setup'
|
||||
import { resolveCompletedSetupRedirectPath } from './setupRedirect'
|
||||
import { resolveDocumentTitle } from './title'
|
||||
import { resolveRouteDocumentTitle } from './title'
|
||||
|
||||
/**
|
||||
* Route definitions with lazy loading
|
||||
@@ -732,22 +732,12 @@ router.beforeEach(async (to, _from, next) => {
|
||||
|
||||
// Set page title
|
||||
const appStore = useAppStore()
|
||||
// For custom pages, use menu item label as document title
|
||||
if (to.name === 'CustomPage') {
|
||||
const id = to.params.id as string
|
||||
const publicItems = appStore.cachedPublicSettings?.custom_menu_items ?? []
|
||||
const adminSettingsStore = useAdminSettingsStore()
|
||||
const menuItem = publicItems.find((item) => item.id === id)
|
||||
?? (authStore.isAdmin ? adminSettingsStore.customMenuItems.find((item) => item.id === id) : undefined)
|
||||
if (menuItem?.label) {
|
||||
const siteName = appStore.siteName || 'Sub2API'
|
||||
document.title = `${menuItem.label} - ${siteName}`
|
||||
} else {
|
||||
document.title = resolveDocumentTitle(to.meta.title, appStore.siteName, to.meta.titleKey as string)
|
||||
}
|
||||
} else {
|
||||
document.title = resolveDocumentTitle(to.meta.title, appStore.siteName, to.meta.titleKey as string)
|
||||
}
|
||||
const adminSettingsStore = useAdminSettingsStore()
|
||||
const customMenuItems = [
|
||||
...(appStore.cachedPublicSettings?.custom_menu_items ?? []),
|
||||
...(authStore.isAdmin ? adminSettingsStore.customMenuItems : []),
|
||||
]
|
||||
document.title = resolveRouteDocumentTitle(to, appStore.siteName, customMenuItems)
|
||||
|
||||
// Check if route requires authentication
|
||||
const requiresAuth = to.meta.requiresAuth !== false // Default to true
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { i18n } from '@/i18n'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import type { CustomMenuItem } from '@/types'
|
||||
|
||||
/**
|
||||
* 统一生成页面标题,避免多处写入 document.title 产生覆盖冲突。
|
||||
@@ -20,3 +22,17 @@ export function resolveDocumentTitle(routeTitle: unknown, siteName?: string, tit
|
||||
|
||||
return normalizedSiteName
|
||||
}
|
||||
|
||||
export function resolveRouteDocumentTitle(
|
||||
route: Pick<RouteLocationNormalizedLoaded, 'name' | 'params' | 'meta'>,
|
||||
siteName: string | undefined,
|
||||
customMenuItems: CustomMenuItem[] = [],
|
||||
): string {
|
||||
const id = typeof route.params.id === 'string' ? route.params.id : ''
|
||||
const menuItem = route.name === 'CustomPage' && id
|
||||
? customMenuItems.find((item) => item.id === id)
|
||||
: undefined
|
||||
const menuTitle = menuItem?.label.trim()
|
||||
|
||||
return resolveDocumentTitle(menuTitle || route.meta.title, siteName, menuTitle ? undefined : route.meta.titleKey as string)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user