fix: 修复前端切换页面时logo跟标题闪烁的问题
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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')));
|
||||
|
||||
Reference in New Issue
Block a user