Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
22414326
Commit
22414326
authored
Dec 19, 2025
by
shaw
Browse files
fix: 修复前端切换页面时logo跟标题闪烁的问题
parent
14b155c6
Changes
5
Hide whitespace changes
Inline
Side-by-side
frontend/src/App.vue
View file @
22414326
<
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) {
...
@@ -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 () => {
...
@@ -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
>
...
...
frontend/src/components/layout/AppHeader.vue
View file @
22414326
...
@@ -156,7 +156,6 @@ import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
...
@@ -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();
...
@@ -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) {
...
@@ -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
(()
=>
{
...
...
frontend/src/components/layout/AppSidebar.vue
View file @
22414326
...
@@ -131,11 +131,10 @@
...
@@ -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);
...
@@ -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
=
{
...
...
frontend/src/stores/app.ts
View file @
22414326
...
@@ -5,8 +5,9 @@
...
@@ -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', () => {
...
@@ -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', () => {
...
@@ -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', () => {
...
@@ -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', () => {
...
@@ -309,5 +380,9 @@ export const useAppStore = defineStore('app', () => {
// Version actions
// Version actions
fetchVersion
,
fetchVersion
,
clearVersionCache
,
clearVersionCache
,
// Public settings actions
fetchPublicSettings
,
clearPublicSettingsCache
,
};
};
});
});
frontend/src/views/admin/SettingsView.vue
View file @
22414326
...
@@ -499,6 +499,8 @@ async function saveSettings() {
...
@@ -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
'
)));
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment