fix: 修复前端切换页面时logo跟标题闪烁的问题

This commit is contained in:
shaw
2025-12-19 22:33:36 +08:00
parent 14b155c66b
commit 22414326cc
5 changed files with 103 additions and 44 deletions

View File

@@ -1,12 +1,13 @@
<script setup lang="ts">
import { RouterView, useRouter, useRoute } from 'vue-router'
import { onMounted } from 'vue'
import { onMounted, watch } from 'vue'
import Toast from '@/components/common/Toast.vue'
import { getPublicSettings } from '@/api/auth'
import { useAppStore } from '@/stores'
import { getSetupStatus } from '@/api/setup'
const router = useRouter()
const route = useRoute()
const appStore = useAppStore()
/**
* Update favicon dynamically
@@ -24,6 +25,19 @@ function updateFavicon(logoUrl: string) {
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 })
onMounted(async () => {
// Check if setup is needed
try {
@@ -36,21 +50,8 @@ onMounted(async () => {
// If setup endpoint fails, assume normal mode and continue
}
try {
const settings = await getPublicSettings()
// Update favicon if logo is set
if (settings.site_logo) {
updateFavicon(settings.site_logo)
}
// Update page title if site name is set
if (settings.site_name) {
document.title = `${settings.site_name} - AI API Gateway`
}
} catch (error) {
console.error('Failed to load public settings for favicon:', error)
}
// Load public settings into appStore (will be cached for other components)
await appStore.fetchPublicSettings()
})
</script>

View File

@@ -156,7 +156,6 @@ import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useAppStore, useAuthStore } from '@/stores';
import { authAPI } from '@/api';
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue';
import SubscriptionProgressMini from '@/components/common/SubscriptionProgressMini.vue';
@@ -169,7 +168,7 @@ const authStore = useAuthStore();
const user = computed(() => authStore.user);
const dropdownOpen = ref(false);
const dropdownRef = ref<HTMLElement | null>(null);
const contactInfo = ref('');
const contactInfo = computed(() => appStore.contactInfo);
const userInitials = computed(() => {
if (!user.value) return '';
@@ -230,14 +229,8 @@ function handleClickOutside(event: MouseEvent) {
}
}
onMounted(async () => {
onMounted(() => {
document.addEventListener('click', handleClickOutside);
try {
const settings = await authAPI.getPublicSettings();
contactInfo.value = settings.contact_info || '';
} catch (error) {
console.error('Failed to load contact info:', error);
}
});
onBeforeUnmount(() => {

View File

@@ -131,11 +131,10 @@
</template>
<script setup lang="ts">
import { computed, h, ref, onMounted } from 'vue';
import { computed, h, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useAppStore, useAuthStore } from '@/stores';
import { getPublicSettings } from '@/api/auth';
import VersionBadge from '@/components/common/VersionBadge.vue';
const { t } = useI18n();
@@ -149,21 +148,10 @@ const mobileOpen = computed(() => appStore.mobileOpen);
const isAdmin = computed(() => authStore.isAdmin);
const isDark = ref(document.documentElement.classList.contains('dark'));
// Site settings
const siteName = ref('Sub2API');
const siteLogo = ref('');
const siteVersion = ref('');
onMounted(async () => {
try {
const settings = await getPublicSettings();
siteName.value = settings.site_name || 'Sub2API';
siteLogo.value = settings.site_logo || '';
siteVersion.value = settings.version || '';
} catch (error) {
console.error('Failed to load public settings:', error);
}
});
// Site settings from appStore (cached, no flicker)
const siteName = computed(() => appStore.siteName);
const siteLogo = computed(() => appStore.siteLogo);
const siteVersion = computed(() => appStore.siteVersion);
// SVG Icon Components
const DashboardIcon = {

View File

@@ -5,8 +5,9 @@
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import type { Toast, ToastType } from '@/types';
import type { Toast, ToastType, PublicSettings } from '@/types';
import { checkUpdates as checkUpdatesAPI, type VersionInfo, type ReleaseInfo } from '@/api/admin/system';
import { getPublicSettings as fetchPublicSettingsAPI } from '@/api/auth';
export const useAppStore = defineStore('app', () => {
// ==================== State ====================
@@ -16,6 +17,15 @@ export const useAppStore = defineStore('app', () => {
const loading = ref<boolean>(false);
const toasts = ref<Toast[]>([]);
// Public settings cache state
const publicSettingsLoaded = ref<boolean>(false);
const publicSettingsLoading = ref<boolean>(false);
const siteName = ref<string>('Sub2API');
const siteLogo = ref<string>('');
const siteVersion = ref<string>('');
const contactInfo = ref<string>('');
const apiBaseUrl = ref<string>('');
// Version cache state
const versionLoaded = ref<boolean>(false);
const versionLoading = ref<boolean>(false);
@@ -268,6 +278,59 @@ export const useAppStore = defineStore('app', () => {
hasUpdate.value = false;
}
// ==================== Public Settings Management ====================
/**
* Fetch public settings (uses cache unless force=true)
* @param force - Force refresh from API
*/
async function fetchPublicSettings(force = false): Promise<PublicSettings | null> {
// Return cached data if available and not forcing refresh
if (publicSettingsLoaded.value && !force) {
return {
registration_enabled: false,
email_verify_enabled: false,
turnstile_enabled: false,
turnstile_site_key: '',
site_name: siteName.value,
site_logo: siteLogo.value,
site_subtitle: '',
api_base_url: apiBaseUrl.value,
contact_info: contactInfo.value,
version: siteVersion.value,
};
}
// Prevent duplicate requests
if (publicSettingsLoading.value) {
return null;
}
publicSettingsLoading.value = true;
try {
const data = await fetchPublicSettingsAPI();
siteName.value = data.site_name || 'Sub2API';
siteLogo.value = data.site_logo || '';
siteVersion.value = data.version || '';
contactInfo.value = data.contact_info || '';
apiBaseUrl.value = data.api_base_url || '';
publicSettingsLoaded.value = true;
return data;
} catch (error) {
console.error('Failed to fetch public settings:', error);
return null;
} finally {
publicSettingsLoading.value = false;
}
}
/**
* Clear public settings cache
*/
function clearPublicSettingsCache(): void {
publicSettingsLoaded.value = false;
}
// ==================== Return Store API ====================
return {
@@ -277,6 +340,14 @@ export const useAppStore = defineStore('app', () => {
loading,
toasts,
// Public settings state
publicSettingsLoaded,
siteName,
siteLogo,
siteVersion,
contactInfo,
apiBaseUrl,
// Version state
versionLoaded,
versionLoading,
@@ -309,5 +380,9 @@ export const useAppStore = defineStore('app', () => {
// Version actions
fetchVersion,
clearVersionCache,
// Public settings actions
fetchPublicSettings,
clearPublicSettingsCache,
};
});

View File

@@ -499,6 +499,8 @@ async function saveSettings() {
saving.value = true;
try {
await adminAPI.settings.updateSettings(form);
// Refresh cached public settings so sidebar/header update immediately
await appStore.fetchPublicSettings(true);
appStore.showSuccess(t('admin.settings.settingsSaved'));
} catch (error: any) {
appStore.showError(t('admin.settings.failedToSave') + ': ' + (error.message || t('common.unknownError')));