import { computed, ref, readonly } from 'vue' import { getAggregateOAuthLoginTypesOrDefault, getProviderDisplayName } from '~/utils/oauth' import { applyThemeConfig, useTheme } from '~/composables/useTheme' const THEME_LOGO_SEPARATOR = '||' // 兼容旧配置:没有分隔符时,地址只归入深色;浅色运行时回退到深色。 export const splitThemeLogoUrl = (value) => { const normalized = typeof value === 'string' ? value.trim() : '' if (!normalized) return { dark: '', light: '' } const separatorIndex = normalized.indexOf(THEME_LOGO_SEPARATOR) if (separatorIndex < 0) return { dark: normalized, light: '' } const dark = normalized.slice(0, separatorIndex).trim() const light = normalized.slice(separatorIndex + THEME_LOGO_SEPARATOR.length).trim() return { dark: dark || light, light: light || dark } } export const joinThemeLogoUrl = (dark, light) => { const normalizedDark = typeof dark === 'string' ? dark.trim() : '' const normalizedLight = typeof light === 'string' ? light.trim() : '' if (!normalizedDark && !normalizedLight) return '' if (!normalizedLight || normalizedLight === normalizedDark) return normalizedDark || normalizedLight return `${normalizedDark}||${normalizedLight}` } const defaultSubmissionGuidelines = `1. 投稿时无需加入书名号 2. 除DJ外,其他类型歌曲均接收(包括小语种) 3. 禁止投递含有违规内容的歌曲 4. 点播的歌曲将由管理员进行审核 5. 审核通过后将安排在播放时段播出 6. 提交即表明我已阅读投稿须知并已知该歌曲有概率无法播出 7. 本系统仅提供音乐搜索和播放管理功能,不存储任何音乐文件。所有音乐内容均来自第三方音乐平台,版权归原平台及版权方所有。用户点歌时请确保遵守相关音乐平台的服务条款,尊重音乐作品版权。我们鼓励用户支持正版音乐,在官方平台购买和收听喜爱的音乐作品。 8. 最终解释权归广播站所有` // 站点配置状态 const siteConfig = ref({ siteTitle: '', siteLogoUrl: '', schoolLogoHomeUrl: '', schoolLogoPrintUrl: '', siteDescription: '', submissionGuidelines: '', icpNumber: '', gonganNumber: '', captchaEnabled: false, captchaProvider: 'graphic', turnstileSiteKey: '', enableSubmissionLimit: false, enableCardCodeRequests: false, requireCardCodeForRequests: false, enableCardCodeLimitBypass: false, githubOAuthEnabled: false, casdoorOAuthEnabled: false, googleOAuthEnabled: false, aggregateOAuthEnabled: false, aggregateOAuthLoginType: 'qq', customOAuthEnabled: false, customOAuthDisplayName: '', allowRegister: false, submissionNoteRequiresApproval: false, registerEmailRequired: false, registerRequiresGradeClass: false, defaultTheme: 'System', enabledThemes: JSON.stringify(['System', 'ClassicDark', 'ClassicLight', 'ModernLight']) }) const isLoaded = ref(false) const isLoading = ref(false) const getImageDisplayUrl = (url) => { const normalizedUrl = typeof url === 'string' ? url.trim() : '' if (!normalizedUrl) return '' // HTTP 图片通过同源代理加载,避免在 HTTPS 页面中被浏览器按混合内容拦截 if (/^http:\/\//i.test(normalizedUrl)) { return `/api/proxy/image?url=${encodeURIComponent(normalizedUrl)}` } return normalizedUrl } export const useSiteConfig = () => { const { isDark } = useTheme() // 获取站点配置 const fetchSiteConfig = async () => { if (isLoading.value) return try { isLoading.value = true const response = await fetch('/api/site-config') if (!response.ok) { throw new Error('获取站点配置失败') } const data = await response.json() siteConfig.value = data try { applyThemeConfig( data.defaultTheme, typeof data.enabledThemes === 'string' ? JSON.parse(data.enabledThemes) : data.enabledThemes ) } catch { applyThemeConfig('System', null) } isLoaded.value = true } catch (error) { console.error('获取站点配置失败:', error) // 使用默认配置 siteConfig.value = { siteTitle: '校园广播站点歌系统', siteLogoUrl: '/favicon.ico', schoolLogoHomeUrl: '', schoolLogoPrintUrl: '', siteDescription: '校园广播站点歌系统 - 让你的声音被听见', submissionGuidelines: defaultSubmissionGuidelines, icpNumber: '', gonganNumber: '', enableReplayRequests: false, enableSubmissionRestriction: false, enableCollaborativeSubmission: true, enableSubmissionRemarks: false, allowOAuthRegistration: false, captchaEnabled: false, captchaProvider: 'graphic', turnstileSiteKey: '', enableSubmissionLimit: false, enableCardCodeRequests: false, requireCardCodeForRequests: false, enableCardCodeLimitBypass: false, githubOAuthEnabled: false, casdoorOAuthEnabled: false, googleOAuthEnabled: false, aggregateOAuthEnabled: false, aggregateOAuthLoginType: 'qq', customOAuthEnabled: false, customOAuthDisplayName: '', allowRegister: false, submissionNoteRequiresApproval: false, registerEmailRequired: false, registerRequiresGradeClass: false, defaultTheme: 'System', enabledThemes: JSON.stringify(['System', 'ClassicDark', 'ClassicLight', 'ModernLight']) } isLoaded.value = true } finally { isLoading.value = false } } // 计算属性 const siteTitle = computed(() => siteConfig.value.siteTitle || '校园广播站点歌系统') const logoUrl = computed(() => siteConfig.value.siteLogoUrl || '/favicon.ico') const schoolLogoHomeUrl = computed(() => { const logos = splitThemeLogoUrl(siteConfig.value.schoolLogoHomeUrl) return (isDark.value ? logos.dark : logos.light) || logos.dark || logos.light || '' }) const schoolLogoPrintUrl = computed(() => siteConfig.value.schoolLogoPrintUrl || '') const schoolLogoHomeDisplayUrl = computed(() => getImageDisplayUrl(schoolLogoHomeUrl.value)) const schoolLogoPrintDisplayUrl = computed(() => getImageDisplayUrl(schoolLogoPrintUrl.value)) const description = computed( () => siteConfig.value.siteDescription || '校园广播站点歌系统 - 让你的声音被听见' ) const guidelines = computed( () => siteConfig.value.submissionGuidelines || defaultSubmissionGuidelines ) const icp = computed(() => siteConfig.value.icpNumber || '') const gonganNumber = computed(() => siteConfig.value.gonganNumber || '') const showBeianIcon = computed(() => siteConfig.value.showBeianIcon || false) const enableReplayRequests = computed(() => siteConfig.value.enableReplayRequests || false) const enableSubmissionRestriction = computed( () => siteConfig.value.enableSubmissionRestriction === true ) const enableCollaborativeSubmission = computed( () => siteConfig.value.enableCollaborativeSubmission !== false ) const enableSubmissionRemarks = computed(() => siteConfig.value.enableSubmissionRemarks === true) const enableSubmissionLimit = computed(() => siteConfig.value.enableSubmissionLimit === true) const enableCardCodeRequests = computed(() => siteConfig.value.enableCardCodeRequests === true) const requireCardCodeForRequests = computed( () => enableCardCodeRequests.value && siteConfig.value.requireCardCodeForRequests === true ) const enableCardCodeLimitBypass = computed( () => siteConfig.value.enableCardCodeLimitBypass === true ) const allowOAuthRegistration = computed(() => siteConfig.value.allowOAuthRegistration === true) const allowRegister = computed(() => siteConfig.value.allowRegister === true) const registerEmailRequired = computed(() => siteConfig.value.registerEmailRequired === true) const registerRequiresGradeClass = computed( () => siteConfig.value.registerRequiresGradeClass === true ) const submissionNoteRequiresApproval = computed( () => siteConfig.value.submissionNoteRequiresApproval === true ) const captchaEnabled = computed(() => siteConfig.value.captchaEnabled === true) const captchaProvider = computed(() => siteConfig.value.captchaProvider || 'graphic') const turnstileSiteKey = computed(() => siteConfig.value.turnstileSiteKey || '') const smtpEnabled = computed(() => !!siteConfig.value.smtpEnabled) const oauth = computed(() => ({ github: !!siteConfig.value.githubOAuthEnabled, casdoor: !!siteConfig.value.casdoorOAuthEnabled, google: !!siteConfig.value.googleOAuthEnabled, aggregate: !!siteConfig.value.aggregateOAuthEnabled, oauth2: !!siteConfig.value.customOAuthEnabled })) const oauthProviders = computed(() => { const providers = [] if (siteConfig.value.githubOAuthEnabled) { providers.push({ key: 'github', name: 'GitHub' }) } if (siteConfig.value.casdoorOAuthEnabled) { providers.push({ key: 'casdoor', name: 'Casdoor' }) } if (siteConfig.value.googleOAuthEnabled) { providers.push({ key: 'google', name: 'Google' }) } if (siteConfig.value.aggregateOAuthEnabled) { const enabledLoginTypes = getAggregateOAuthLoginTypesOrDefault( siteConfig.value.aggregateOAuthLoginType ) enabledLoginTypes.forEach((loginType) => { providers.push({ key: `aggregate:${loginType}`, routeProvider: 'aggregate', loginType, name: getProviderDisplayName(`aggregate:${loginType}`) }) }) } if (siteConfig.value.customOAuthEnabled) { providers.push({ key: 'oauth2', name: siteConfig.value.customOAuthDisplayName || '第三方 OAuth' }) } return providers }) // 初始化配置(仅在客户端执行) const initSiteConfig = async () => { if (typeof window !== 'undefined' && !isLoaded.value) { await fetchSiteConfig() } } // 刷新配置 const refreshSiteConfig = async () => { isLoaded.value = false await fetchSiteConfig() } return { siteConfig: readonly(siteConfig), isLoaded: readonly(isLoaded), isLoading: readonly(isLoading), siteTitle, logoUrl, schoolLogoHomeUrl, schoolLogoPrintUrl, schoolLogoHomeDisplayUrl, schoolLogoPrintDisplayUrl, description, guidelines, icp, gonganNumber, showBeianIcon, enableReplayRequests, enableSubmissionRestriction, enableCollaborativeSubmission, enableSubmissionRemarks, enableSubmissionLimit, enableCardCodeRequests, requireCardCodeForRequests, enableCardCodeLimitBypass, allowOAuthRegistration, allowRegister, submissionNoteRequiresApproval, registerEmailRequired, registerRequiresGradeClass, captchaEnabled, captchaProvider, turnstileSiteKey, smtpEnabled, oauth, oauthProviders, fetchSiteConfig, initSiteConfig, refreshSiteConfig } }