fix: 修复前端切换页面时logo跟标题闪烁的问题
This commit is contained in:
@@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterView, useRouter, useRoute } from 'vue-router'
|
import { RouterView, useRouter, useRoute } from 'vue-router'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted, watch } from 'vue'
|
||||||
import Toast from '@/components/common/Toast.vue'
|
import Toast from '@/components/common/Toast.vue'
|
||||||
import { getPublicSettings } from '@/api/auth'
|
import { useAppStore } from '@/stores'
|
||||||
import { getSetupStatus } from '@/api/setup'
|
import { getSetupStatus } from '@/api/setup'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update favicon dynamically
|
* Update favicon dynamically
|
||||||
@@ -24,6 +25,19 @@ function updateFavicon(logoUrl: string) {
|
|||||||
link.href = logoUrl
|
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 () => {
|
onMounted(async () => {
|
||||||
// Check if setup is needed
|
// Check if setup is needed
|
||||||
try {
|
try {
|
||||||
@@ -36,21 +50,8 @@ onMounted(async () => {
|
|||||||
// If setup endpoint fails, assume normal mode and continue
|
// If setup endpoint fails, assume normal mode and continue
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Load public settings into appStore (will be cached for other components)
|
||||||
const settings = await getPublicSettings()
|
await appStore.fetchPublicSettings()
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
|||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useAppStore, useAuthStore } from '@/stores';
|
import { useAppStore, useAuthStore } from '@/stores';
|
||||||
import { authAPI } from '@/api';
|
|
||||||
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue';
|
import LocaleSwitcher from '@/components/common/LocaleSwitcher.vue';
|
||||||
import SubscriptionProgressMini from '@/components/common/SubscriptionProgressMini.vue';
|
import SubscriptionProgressMini from '@/components/common/SubscriptionProgressMini.vue';
|
||||||
|
|
||||||
@@ -169,7 +168,7 @@ const authStore = useAuthStore();
|
|||||||
const user = computed(() => authStore.user);
|
const user = computed(() => authStore.user);
|
||||||
const dropdownOpen = ref(false);
|
const dropdownOpen = ref(false);
|
||||||
const dropdownRef = ref<HTMLElement | null>(null);
|
const dropdownRef = ref<HTMLElement | null>(null);
|
||||||
const contactInfo = ref('');
|
const contactInfo = computed(() => appStore.contactInfo);
|
||||||
|
|
||||||
const userInitials = computed(() => {
|
const userInitials = computed(() => {
|
||||||
if (!user.value) return '';
|
if (!user.value) return '';
|
||||||
@@ -230,14 +229,8 @@ function handleClickOutside(event: MouseEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
document.addEventListener('click', handleClickOutside);
|
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(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|||||||
@@ -131,11 +131,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, h, ref, onMounted } from 'vue';
|
import { computed, h, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useAppStore, useAuthStore } from '@/stores';
|
import { useAppStore, useAuthStore } from '@/stores';
|
||||||
import { getPublicSettings } from '@/api/auth';
|
|
||||||
import VersionBadge from '@/components/common/VersionBadge.vue';
|
import VersionBadge from '@/components/common/VersionBadge.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -149,21 +148,10 @@ const mobileOpen = computed(() => appStore.mobileOpen);
|
|||||||
const isAdmin = computed(() => authStore.isAdmin);
|
const isAdmin = computed(() => authStore.isAdmin);
|
||||||
const isDark = ref(document.documentElement.classList.contains('dark'));
|
const isDark = ref(document.documentElement.classList.contains('dark'));
|
||||||
|
|
||||||
// Site settings
|
// Site settings from appStore (cached, no flicker)
|
||||||
const siteName = ref('Sub2API');
|
const siteName = computed(() => appStore.siteName);
|
||||||
const siteLogo = ref('');
|
const siteLogo = computed(() => appStore.siteLogo);
|
||||||
const siteVersion = ref('');
|
const siteVersion = computed(() => appStore.siteVersion);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// SVG Icon Components
|
// SVG Icon Components
|
||||||
const DashboardIcon = {
|
const DashboardIcon = {
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref, computed } from 'vue';
|
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 { checkUpdates as checkUpdatesAPI, type VersionInfo, type ReleaseInfo } from '@/api/admin/system';
|
||||||
|
import { getPublicSettings as fetchPublicSettingsAPI } from '@/api/auth';
|
||||||
|
|
||||||
export const useAppStore = defineStore('app', () => {
|
export const useAppStore = defineStore('app', () => {
|
||||||
// ==================== State ====================
|
// ==================== State ====================
|
||||||
@@ -16,6 +17,15 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const toasts = ref<Toast[]>([]);
|
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
|
// Version cache state
|
||||||
const versionLoaded = ref<boolean>(false);
|
const versionLoaded = ref<boolean>(false);
|
||||||
const versionLoading = ref<boolean>(false);
|
const versionLoading = ref<boolean>(false);
|
||||||
@@ -268,6 +278,59 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
hasUpdate.value = false;
|
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 Store API ====================
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -277,6 +340,14 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
loading,
|
loading,
|
||||||
toasts,
|
toasts,
|
||||||
|
|
||||||
|
// Public settings state
|
||||||
|
publicSettingsLoaded,
|
||||||
|
siteName,
|
||||||
|
siteLogo,
|
||||||
|
siteVersion,
|
||||||
|
contactInfo,
|
||||||
|
apiBaseUrl,
|
||||||
|
|
||||||
// Version state
|
// Version state
|
||||||
versionLoaded,
|
versionLoaded,
|
||||||
versionLoading,
|
versionLoading,
|
||||||
@@ -309,5 +380,9 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
// Version actions
|
// Version actions
|
||||||
fetchVersion,
|
fetchVersion,
|
||||||
clearVersionCache,
|
clearVersionCache,
|
||||||
|
|
||||||
|
// Public settings actions
|
||||||
|
fetchPublicSettings,
|
||||||
|
clearPublicSettingsCache,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -499,6 +499,8 @@ async function saveSettings() {
|
|||||||
saving.value = true;
|
saving.value = true;
|
||||||
try {
|
try {
|
||||||
await adminAPI.settings.updateSettings(form);
|
await adminAPI.settings.updateSettings(form);
|
||||||
|
// Refresh cached public settings so sidebar/header update immediately
|
||||||
|
await appStore.fetchPublicSettings(true);
|
||||||
appStore.showSuccess(t('admin.settings.settingsSaved'));
|
appStore.showSuccess(t('admin.settings.settingsSaved'));
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
appStore.showError(t('admin.settings.failedToSave') + ': ' + (error.message || t('common.unknownError')));
|
appStore.showError(t('admin.settings.failedToSave') + ': ' + (error.message || t('common.unknownError')));
|
||||||
|
|||||||
Reference in New Issue
Block a user