fix: 修复系统设置动态注入到静态文件

- 简化前端 useSiteSettings,直接使用后端注入的 window.__SITE_CONFIG__
- 修复后端 serveIndexHTML 中的设置 key 名,与前端的 key 名一致
- site_name 替换为正确的数据库字段名
- logo 和 favicon 使用 site_logo 和 site_favicon
- 添加 getLogo 函数根据主题自动选择 logo

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 13:59:03 +08:00
parent 53ab7fd778
commit 1cb8f11acd
5 changed files with 137 additions and 174 deletions
+8 -10
View File
@@ -319,20 +319,18 @@ func serveIndexHTML(c *gin.Context, indexHTML string) {
settings = make(map[string]interface{}) settings = make(map[string]interface{})
} }
siteName := getSettingString(settings, "siteName", "微授权") siteName := getSettingString(settings, "site_name", "微授权")
siteDescription := getSettingString(settings, "siteDescription", "专业的应用验证平台") siteDescription := getSettingString(settings, "site_description", "专业的应用验证平台")
siteKeywords := getSettingString(settings, "siteKeywords", "验证平台,软件授权,卡密验证") siteLogo := getSettingString(settings, "site_logo", "")
logoLight := getSettingString(settings, "logoLight", "") siteFavicon := getSettingString(settings, "site_favicon", "")
logoDark := getSettingString(settings, "logoDark", "")
favicon := getSettingString(settings, "favicon", "")
html := indexHTML html := indexHTML
html = strings.ReplaceAll(html, "{{.SiteName}}", siteName) html = strings.ReplaceAll(html, "{{.SiteName}}", siteName)
html = strings.ReplaceAll(html, "{{.SiteDescription}}", siteDescription) html = strings.ReplaceAll(html, "{{.SiteDescription}}", siteDescription)
html = strings.ReplaceAll(html, "{{.SiteKeywords}}", siteKeywords) html = strings.ReplaceAll(html, "{{.SiteKeywords}}", "验证平台,软件授权,卡密验证")
html = strings.ReplaceAll(html, "{{.LogoLight}}", logoLight) html = strings.ReplaceAll(html, "{{.LogoLight}}", siteLogo)
html = strings.ReplaceAll(html, "{{.LogoDark}}", logoDark) html = strings.ReplaceAll(html, "{{.LogoDark}}", siteLogo)
html = strings.ReplaceAll(html, "{{.Favicon}}", favicon) html = strings.ReplaceAll(html, "{{.Favicon}}", siteFavicon)
c.Data(200, "text/html; charset=utf-8", []byte(html)) c.Data(200, "text/html; charset=utf-8", []byte(html))
} }
+5 -17
View File
@@ -2,19 +2,18 @@
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" media="(prefers-color-scheme: dark)" href="/logo.svg" /> <link rel="icon" type="image/svg+xml" href="/logo.svg" />
<link rel="icon" type="image/svg+xml" media="(prefers-color-scheme: light)" href="/logo-black.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>加载中...</title> <title>{{.SiteName}}</title>
<meta name="description" content="{{.SiteDescription}}" />
<!-- Google Fonts - Inter --> <!-- Google Fonts - Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- 后端注入的系统配置(生产环境由后端渲染) --> <!-- 后端注入的系统配置 -->
<script> <script>
// 后端渲染时会注入这些变量,开发环境使用默认值
window.__SITE_CONFIG__ = { window.__SITE_CONFIG__ = {
siteName: '{{.SiteName}}', siteName: '{{.SiteName}}',
siteDescription: '{{.SiteDescription}}', siteDescription: '{{.SiteDescription}}',
@@ -22,21 +21,10 @@
logoDark: '{{.LogoDark}}', logoDark: '{{.LogoDark}}',
favicon: '{{.Favicon}}', favicon: '{{.Favicon}}',
}; };
// 检测是否是开发环境(模板变量未被替换)
if (window.__SITE_CONFIG__.siteName.indexOf('{{') === 0) {
window.__SITE_CONFIG__ = {
siteName: '',
siteDescription: '',
logoLight: '',
logoDark: '',
favicon: '',
};
}
</script> </script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>
+22 -22
View File
@@ -1,7 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Boxes, Code, CreditCard, DollarSign, FileLock, Gauge, GitBranch, HardDrive, Hash, Key, KeyRound, Mail, Megaphone, MessageSquare, Monitor, Network, Plug, ScrollText, Settings, Shield, Smartphone, Users, Variable } from 'lucide-vue-next' import { Boxes, Code, CreditCard, DollarSign, FileLock, Gauge, GitBranch, HardDrive, Hash, Key, KeyRound, Mail, Megaphore, MessageSquare, Monitor, Network, Plug, ScrollText, Settings, Shield, Smartphone, Users, Variable } from 'lucide-vue-next'
import { computed, onMounted, onUnmounted, reactive, watch } from 'vue' import { computed, onMounted, reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { usePreferredDark } from '@vueuse/core'
import NavTeam from '@/components/app-sidebar/nav-team.vue' import NavTeam from '@/components/app-sidebar/nav-team.vue'
import TeamSwitcher from '@/components/app-sidebar/team-switcher.vue' import TeamSwitcher from '@/components/app-sidebar/team-switcher.vue'
@@ -9,7 +10,8 @@ import NavFooter from '@/components/admin-sidebar/nav-footer.vue'
import { useSiteSettings } from '@/composables/use-site-settings' import { useSiteSettings } from '@/composables/use-site-settings'
const { t } = useI18n() const { t } = useI18n()
const { siteSettings } = useSiteSettings() const { siteSettings, getLogo } = useSiteSettings()
const isDark = usePreferredDark()
const user = reactive({ const user = reactive({
name: t('nav.admin'), name: t('nav.admin'),
@@ -20,31 +22,32 @@ const user = reactive({
const teams = reactive([ const teams = reactive([
{ {
name: t('nav.adminDashboard'), name: '',
logo: Code as any, logo: '' as any,
}, },
]) ])
// 计算当前 logo
const currentLogo = computed(() => {
if (siteSettings.logoLight || siteSettings.logoDark || siteSettings.siteLogo) {
return getLogo(isDark.value)
}
return Code // 默认图标
})
// 监听系统设置变化 // 监听系统设置变化
watch(() => siteSettings.siteName, (newName) => { watch(() => siteSettings.siteName, (newName) => {
if (newName) { if (newName) {
teams[0].name = newName teams[0].name = newName
} else {
teams[0].name = t('nav.adminDashboard')
} }
}) }, { immediate: true })
watch(() => siteSettings.siteLogo, (newLogo) => { // 监听 logo 变化
if (newLogo) { watch(currentLogo, (newLogo) => {
teams[0].logo = newLogo teams[0].logo = newLogo
} }, { immediate: true })
})
// 初始化时设置
if (siteSettings.siteName) {
teams[0].name = siteSettings.siteName
}
if (siteSettings.siteLogo) {
teams[0].logo = siteSettings.siteLogo
}
onMounted(() => { onMounted(() => {
const storedUser = localStorage.getItem('user') const storedUser = localStorage.getItem('user')
@@ -62,9 +65,6 @@ onMounted(() => {
} }
}) })
onUnmounted(() => {
})
const navMain = computed(() => [ const navMain = computed(() => [
{ {
title: t('nav.mainFeatures'), title: t('nav.mainFeatures'),
+101 -125
View File
@@ -1,6 +1,14 @@
import { reactive } from 'vue' import { reactive, watch } from 'vue'
import { getFileUrl } from '@/utils/config' import { getFileUrl } from '@/utils/config'
interface SiteConfig {
siteName: string
siteDescription: string
logoLight: string
logoDark: string
favicon: string
}
interface SiteSettings { interface SiteSettings {
siteName: string siteName: string
siteDescription: string siteDescription: string
@@ -8,7 +16,6 @@ interface SiteSettings {
siteFavicon: string siteFavicon: string
logoLight: string logoLight: string
logoDark: string logoDark: string
loaded: boolean
} }
const siteSettings = reactive<SiteSettings>({ const siteSettings = reactive<SiteSettings>({
@@ -18,9 +25,62 @@ const siteSettings = reactive<SiteSettings>({
siteFavicon: '', siteFavicon: '',
logoLight: '', logoLight: '',
logoDark: '', logoDark: '',
loaded: false,
}) })
// 从 window.__SITE_CONFIG__ 获取后端注入的配置
function initFromServerConfig(): boolean {
const config = (window as any).__SITE_CONFIG__ as SiteConfig | undefined
if (config) {
if (config.siteName) {
siteSettings.siteName = config.siteName
document.title = config.siteName
}
if (config.siteDescription) {
siteSettings.siteDescription = config.siteDescription
}
if (config.logoLight) {
siteSettings.logoLight = config.logoLight
siteSettings.siteLogo = config.logoLight
}
if (config.logoDark) {
siteSettings.logoDark = config.logoDark
}
if (config.favicon) {
siteSettings.siteFavicon = config.favicon
updateFavicon(config.favicon)
}
return true
}
return false
}
// 从 localStorage 获取缓存的设置(作为备用)
function initFromLocalStorage(): boolean {
const storedSettings = localStorage.getItem('systemSettings')
if (storedSettings) {
try {
const parsed = JSON.parse(storedSettings)
if (parsed.site_name && !siteSettings.siteName) {
siteSettings.siteName = parsed.site_name
}
if (parsed.site_description && !siteSettings.siteDescription) {
siteSettings.siteDescription = parsed.site_description
}
if (parsed.site_logo && !siteSettings.siteLogo) {
siteSettings.siteLogo = parsed.site_logo
}
if (parsed.site_favicon && !siteSettings.siteFavicon) {
siteSettings.siteFavicon = parsed.site_favicon
}
return true
}
catch (e) {
console.error('Failed to parse systemSettings from localStorage', e)
}
}
return false
}
function updateFavicon(favicon: string) { function updateFavicon(favicon: string) {
if (!favicon) if (!favicon)
return return
@@ -38,7 +98,6 @@ function updateFavicon(favicon: string) {
link.href = getFileUrl(favicon) link.href = getFileUrl(favicon)
head.appendChild(link) head.appendChild(link)
// 同时添加 icon 类型的 link
const linkIcon = document.createElement('link') const linkIcon = document.createElement('link')
linkIcon.type = 'image/x-icon' linkIcon.type = 'image/x-icon'
linkIcon.rel = 'icon' linkIcon.rel = 'icon'
@@ -46,129 +105,33 @@ function updateFavicon(favicon: string) {
head.appendChild(linkIcon) head.appendChild(linkIcon)
} }
function updateTitle(title?: string) { // 初始化系统设置
if (title) { function initSiteSettings() {
document.title = title
} else if (siteSettings.siteName) {
document.title = siteSettings.siteName
}
}
function applySettings(settings: Partial<SiteSettings>) {
if (settings.siteName) {
siteSettings.siteName = settings.siteName
updateTitle(settings.siteName)
}
if (settings.siteDescription) {
siteSettings.siteDescription = settings.siteDescription
}
if (settings.siteLogo) {
siteSettings.siteLogo = settings.siteLogo
}
if (settings.siteFavicon) {
siteSettings.siteFavicon = settings.siteFavicon
updateFavicon(settings.siteFavicon)
}
if (settings.logoLight) {
siteSettings.logoLight = settings.logoLight
}
if (settings.logoDark) {
siteSettings.logoDark = settings.logoDark
}
siteSettings.loaded = true
}
// 从 window.__SITE_CONFIG__ 获取初始配置(由后端注入)
function initFromServerConfig() {
const config = (window as any).__SITE_CONFIG__
if (config) {
applySettings({
siteName: config.siteName || '',
siteDescription: config.siteDescription || '',
siteLogo: config.logoLight || config.logoDark || '',
logoLight: config.logoLight || '',
logoDark: config.logoDark || '',
favicon: config.favicon || '',
})
return true
}
return false
}
// 从 localStorage 获取缓存的设置
function initFromLocalStorage() {
const storedSettings = localStorage.getItem('systemSettings')
if (storedSettings) {
try {
const parsed = JSON.parse(storedSettings)
applySettings({
siteName: parsed.site_name || '',
siteDescription: parsed.site_description || '',
siteLogo: parsed.site_logo || '',
siteFavicon: parsed.site_favicon || '',
})
return true
}
catch (e) {
console.error('Failed to parse systemSettings from localStorage', e)
}
}
return false
}
// 从 API 获取最新设置
async function fetchSettings() {
try {
const response = await fetch('/api/v1/dev/system-settings', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
})
const data = await response.json()
if (data.code === 200 && data.data) {
const settings = {
site_name: data.data.site_name || '',
site_description: data.data.site_description || '',
site_logo: data.data.site_logo || '',
site_favicon: data.data.site_favicon || '',
}
localStorage.setItem('systemSettings', JSON.stringify(settings))
applySettings({
siteName: settings.site_name,
siteDescription: settings.site_description,
siteLogo: settings.site_logo,
siteFavicon: settings.site_favicon,
})
// 触发事件通知其他组件
window.dispatchEvent(new CustomEvent('system-settings-changed', { detail: settings }))
}
}
catch (error) {
console.error('Failed to fetch system settings:', error)
}
}
// 初始化系统设置(在应用启动时调用)
async function initSiteSettings() {
// 优先从后端注入的配置获取 // 优先从后端注入的配置获取
const hasServerConfig = initFromServerConfig() initFromServerConfig()
// 然后从 localStorage 获取缓存 // 如果后端没有注入配置,从 localStorage 获取
const hasLocalConfig = initFromLocalStorage() if (!siteSettings.siteName) {
initFromLocalStorage()
// 如果都没有,尝试从 API 获取
if (!hasServerConfig && !hasLocalConfig) {
await fetchSettings()
} }
// 监听设置变化 // 监听设置变化(来自系统设置页面的更新)
window.addEventListener('system-settings-changed', ((event: CustomEvent) => { window.addEventListener('system-settings-changed', ((event: CustomEvent) => {
applySettings({ const settings = event.detail
siteName: event.detail.site_name, if (settings.site_name) {
siteDescription: event.detail.site_description, siteSettings.siteName = settings.site_name
siteLogo: event.detail.site_logo, }
siteFavicon: event.detail.site_favicon, if (settings.site_description) {
}) siteSettings.siteDescription = settings.site_description
}
if (settings.site_logo) {
siteSettings.siteLogo = settings.site_logo
siteSettings.logoLight = settings.site_logo
}
if (settings.site_favicon) {
siteSettings.siteFavicon = settings.site_favicon
updateFavicon(settings.site_favicon)
}
}) as EventListener) }) as EventListener)
} }
@@ -183,12 +146,25 @@ function getPageTitle(pageTitle?: string): string {
return pageTitle || '' return pageTitle || ''
} }
// 获取 logo(根据主题自动选择)
function getLogo(isDark: boolean): string {
if (isDark && siteSettings.logoDark) {
return getFileUrl(siteSettings.logoDark)
}
if (siteSettings.logoLight) {
return getFileUrl(siteSettings.logoLight)
}
if (siteSettings.siteLogo) {
return getFileUrl(siteSettings.siteLogo)
}
return ''
}
export function useSiteSettings() { export function useSiteSettings() {
return { return {
siteSettings, siteSettings,
initSiteSettings, initSiteSettings,
updateTitle,
getPageTitle, getPageTitle,
fetchSettings, getLogo,
} }
} }
+1
View File
@@ -78,6 +78,7 @@ declare global {
const useRoute: typeof import('vue-router').useRoute const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter const useRouter: typeof import('vue-router').useRouter
const useSidebar: typeof import('../composables/use-sidebar').useSidebar const useSidebar: typeof import('../composables/use-sidebar').useSidebar
const useSiteSettings: typeof import('../composables/use-site-settings').useSiteSettings
const useSlots: typeof import('vue').useSlots const useSlots: typeof import('vue').useSlots
const useSystemTheme: typeof import('../composables/use-system-theme').useSystemTheme const useSystemTheme: typeof import('../composables/use-system-theme').useSystemTheme
const useTemplateRef: typeof import('vue').useTemplateRef const useTemplateRef: typeof import('vue').useTemplateRef