diff --git a/README.md b/README.md index 91388b3..6fee402 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ services: restart: on-failure ports: - '3000:3000' + - '3001:3001' environment: - USERNAME=admin - PASSWORD=orange @@ -111,6 +112,7 @@ services: restart: on-failure ports: - '3000:3000' + - '3001:3001' environment: - USERNAME=admin - PASSWORD=orange @@ -147,6 +149,7 @@ services: restart: on-failure ports: - '3000:3000' + - '3001:3001' environment: - USERNAME=admin - PASSWORD=orange diff --git a/src/app/api/admin/config/route.ts b/src/app/api/admin/config/route.ts index 7544700..846bab7 100644 --- a/src/app/api/admin/config/route.ts +++ b/src/app/api/admin/config/route.ts @@ -20,41 +20,74 @@ export async function GET(request: NextRequest) { } const authInfo = getAuthInfoFromCookie(request); - if (!authInfo || !authInfo.username) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - const username = authInfo.username; + const username = authInfo?.username; try { const config = await getConfig(); - const result: AdminConfigResult = { - Role: 'owner', - Config: config, - }; + + // 检查用户权限 + let userRole = 'guest'; // 未登录用户为 guest + let isAdmin = false; + if (username === process.env.USERNAME) { - result.Role = 'owner'; - } else { + userRole = 'owner'; + isAdmin = true; + } else if (username) { const user = config.UserConfig.Users.find((u) => u.username === username); if (user && user.role === 'admin' && !user.banned) { - result.Role = 'admin'; + userRole = 'admin'; + isAdmin = true; + } else if (user && !user.banned) { + userRole = 'user'; + } else if (user && user.banned) { + userRole = 'banned'; } else { - return NextResponse.json( - { error: '你是管理员吗你就访问?' }, - { status: 401 } - ); + // 认证了但用户不存在,可能是数据不同步 + userRole = 'unknown'; } } - return NextResponse.json(result, { - headers: { - 'Cache-Control': 'no-store', // 管理员配置不缓存 - }, - }); + // 根据用户权限返回不同的配置信息 + if (isAdmin) { + // 管理员返回完整配置 + const result: AdminConfigResult = { + Role: userRole as 'admin' | 'owner', + Config: config, + }; + + return NextResponse.json(result, { + headers: { + 'Cache-Control': 'no-store', // 管理员配置不缓存 + }, + }); + } else { + // 普通用户或未登录用户只返回公开配置 + const publicConfig = { + ThemeConfig: config.ThemeConfig, + SiteConfig: { + SiteName: config.SiteConfig.SiteName, + Announcement: config.SiteConfig.Announcement, + // 其他公开的站点配置可以在这里添加 + } + }; + + const result = { + Role: userRole, + Config: publicConfig, + }; + + console.log('返回公开配置给', userRole, ',包含主题配置:', !!publicConfig.ThemeConfig); + return NextResponse.json(result, { + headers: { + 'Cache-Control': 'public, max-age=60', // 公开配置可以缓存1分钟 + }, + }); + } } catch (error) { - console.error('获取管理员配置失败:', error); + console.error('获取配置失败:', error); return NextResponse.json( { - error: '获取管理员配置失败', + error: '获取配置失败', details: (error as Error).message, }, { status: 500 } diff --git a/src/app/api/admin/theme/route.ts b/src/app/api/admin/theme/route.ts index 5130bfa..28b3a6c 100644 --- a/src/app/api/admin/theme/route.ts +++ b/src/app/api/admin/theme/route.ts @@ -3,6 +3,7 @@ import { getAuthInfoFromCookie } from '@/lib/auth'; import { db } from '@/lib/db'; import { AdminConfig } from '@/lib/admin.types'; import { headers, cookies } from 'next/headers'; +import { getConfig, setCachedConfig, clearCachedConfig } from '@/lib/config'; export async function GET() { try { @@ -22,12 +23,8 @@ export async function GET() { return NextResponse.json({ error: '认证信息无效' }, { status: 401 }); } - const config = await db.getAdminConfig(); - const themeConfig = config?.ThemeConfig || { - defaultTheme: 'default' as const, - customCSS: '', - allowUserCustomization: true, - }; + const config = await getConfig(); + const themeConfig = config.ThemeConfig; return NextResponse.json({ success: true, @@ -75,40 +72,7 @@ export async function POST(request: Request) { } // 获取当前配置 - const currentConfig = await db.getAdminConfig(); - - // 如果没有配置,创建一个基础配置 - let baseConfig: AdminConfig; - if (!currentConfig) { - baseConfig = { - ConfigSubscribtion: { - URL: "", - AutoUpdate: false, - LastCheck: "", - }, - ConfigFile: "", - SiteConfig: { - SiteName: "OrangeTV", - Announcement: "", - SearchDownstreamMaxPage: 10, - SiteInterfaceCacheTime: 30, - DoubanProxyType: "direct", - DoubanProxy: "", - DoubanImageProxyType: "direct", - DoubanImageProxy: "", - DisableYellowFilter: false, - FluidSearch: true, - RequireDeviceCode: false, - }, - UserConfig: { - Users: [], - }, - SourceConfig: [], - CustomCategories: [], - }; - } else { - baseConfig = currentConfig; - } + const baseConfig = await getConfig(); // 更新主题配置 const updatedConfig: AdminConfig = { @@ -120,10 +84,23 @@ export async function POST(request: Request) { }, }; - console.log('保存主题配置:', updatedConfig.ThemeConfig); + console.log('=== 保存主题配置 ==='); + console.log('请求参数:', { defaultTheme, customCSS, allowUserCustomization }); + console.log('当前存储类型:', process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'); + console.log('待保存配置:', updatedConfig.ThemeConfig); + console.log('完整配置对象:', JSON.stringify(updatedConfig, null, 2)); + await db.saveAdminConfig(updatedConfig); console.log('主题配置保存成功'); + // 直接更新缓存,确保缓存与数据库同步 + await setCachedConfig(updatedConfig); + console.log('已更新配置缓存'); + + // 立即验证缓存中的配置 + const cachedConfig = await getConfig(); + console.log('保存后验证缓存中的配置:', cachedConfig.ThemeConfig); + return NextResponse.json({ success: true, message: '主题配置已更新', diff --git a/src/app/api/theme/route.ts b/src/app/api/theme/route.ts deleted file mode 100644 index ff47fe5..0000000 --- a/src/app/api/theme/route.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { NextResponse } from 'next/server'; -import { db } from '@/lib/db'; - -export async function GET() { - try { - console.log('API /theme: 开始获取主题配置...'); - const config = await db.getAdminConfig(); - - console.log('API /theme: 获取到的管理员配置:', { - hasConfig: !!config, - hasThemeConfig: !!(config?.ThemeConfig), - themeConfig: config?.ThemeConfig - }); - - const themeConfig = config?.ThemeConfig || { - defaultTheme: 'default' as const, - customCSS: '', - allowUserCustomization: true, - }; - - console.log('API /theme: 最终返回的主题配置:', themeConfig); - - return NextResponse.json({ - success: true, - data: themeConfig, - }); - } catch (error) { - console.error('API /theme: 获取主题配置失败,返回默认配置:', error); - - const defaultThemeConfig = { - defaultTheme: 'default' as const, - customCSS: '', - allowUserCustomization: true, - }; - - return NextResponse.json( - { - success: true, // 改为 success: true,因为我们提供了有效的默认配置 - data: defaultThemeConfig, - fallback: true, // 标记这是备用配置 - error: error instanceof Error ? error.message : '未知错误' - }, - { status: 200 } - ); - } -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 455031f..5ed01a7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -6,7 +6,6 @@ import { Inter } from 'next/font/google'; import './globals.css'; import { getConfig } from '@/lib/config'; -import { db } from '@/lib/db'; import { GlobalErrorIndicator } from '../components/GlobalErrorIndicator'; import { SiteProvider } from '../components/SiteProvider'; @@ -44,33 +43,6 @@ export default async function RootLayout({ }) { const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; - // 预先获取主题配置 - let themeConfig = { - defaultTheme: 'default', - customCSS: '', - allowUserCustomization: true - }; - - try { - if (storageType !== 'localstorage') { - const adminConfig = await db.getAdminConfig(); - if (adminConfig?.ThemeConfig) { - themeConfig = { - defaultTheme: adminConfig.ThemeConfig.defaultTheme || 'default', - customCSS: adminConfig.ThemeConfig.customCSS || '', - allowUserCustomization: adminConfig.ThemeConfig.allowUserCustomization !== false, - }; - console.log('服务端获取主题配置成功:', themeConfig); - } else { - console.log('服务端配置中没有主题配置,使用默认配置'); - } - } else { - console.log('LocalStorage模式,使用默认主题配置'); - } - } catch (error) { - console.error('服务端获取主题配置失败,使用默认配置:', error); - } - let siteName = process.env.NEXT_PUBLIC_SITE_NAME || 'OrangeTV'; let announcement = process.env.ANNOUNCEMENT || @@ -122,7 +94,6 @@ export default async function RootLayout({ CUSTOM_CATEGORIES: customCategories, FLUID_SEARCH: fluidSearch, REQUIRE_DEVICE_CODE: requireDeviceCode, - THEME_CONFIG: themeConfig, }; return ( @@ -141,84 +112,59 @@ export default async function RootLayout({ }} /> - {/* 主题初始化脚本 - 内联执行,确保在生产环境中立即应用主题 */} + {/* 立即从缓存应用主题,避免闪烁 */} {/* eslint-disable-next-line @next/next/no-sync-scripts */} +
{ useEffect(() => { - const validateAndSyncTheme = async () => { + const syncThemeWithAPI = async () => { try { - // 检查服务端预设的配置 - const runtimeConfig = (window as any).RUNTIME_CONFIG; - const serverThemeConfig = runtimeConfig?.THEME_CONFIG; - - console.log('检查服务端预设主题配置:', serverThemeConfig); - - if (serverThemeConfig && serverThemeConfig.defaultTheme !== 'default') { - // 服务端有非默认配置,优先使用服务端配置 - console.log('使用服务端主题配置,跳过API检查:', serverThemeConfig); - - // 确保服务端配置已正确应用到DOM - const html = document.documentElement; - const currentTheme = html.getAttribute('data-theme'); - - if (currentTheme !== serverThemeConfig.defaultTheme) { - console.log('DOM主题与服务端配置不一致,重新应用:', { - current: currentTheme, - expected: serverThemeConfig.defaultTheme - }); - applyTheme(serverThemeConfig.defaultTheme, serverThemeConfig.customCSS || ''); - } else { - console.log('服务端主题配置已正确应用,无需更新'); - } - return; - } - - // 只有当服务端配置为默认值时,才从API获取配置 - console.log('服务端为默认配置,从API获取最新配置...'); - const response = await fetch('/api/theme'); + console.log('从API同步主题配置...'); + const response = await fetch('/api/admin/config'); const result = await response.json(); - if (result.success && result.data) { - const { defaultTheme, customCSS } = result.data; - const isFallback = result.fallback; + if (result?.Config?.ThemeConfig) { + const themeConfig = result.Config.ThemeConfig; + const { defaultTheme, customCSS, allowUserCustomization } = themeConfig; - console.log('从API获取到主题配置:', { + console.log('API返回主题配置:', { defaultTheme, customCSS, - isFallback: !!isFallback, - error: result.error + allowUserCustomization }); - if (isFallback) { - console.log('API返回备用配置,保持服务端设置不变'); - return; + // 获取当前缓存的主题配置 + const cachedTheme = getCachedTheme(); + + // 比较API配置与缓存配置 + const configChanged = !cachedTheme || + cachedTheme.defaultTheme !== defaultTheme || + cachedTheme.customCSS !== customCSS; + + if (configChanged) { + console.log('检测到主题配置变更,更新应用:', { + from: cachedTheme, + to: { defaultTheme, customCSS } + }); + applyAndCacheTheme(defaultTheme, customCSS); + } else { + console.log('主题配置无变化,保持当前设置'); } - // 只有API返回非默认配置且非备用配置时才应用 - if (defaultTheme !== 'default' || customCSS) { - console.log('应用API获取的有效主题配置:', { defaultTheme, customCSS }); - applyTheme(defaultTheme, customCSS); - - // 更新运行时配置 - if (runtimeConfig) { - runtimeConfig.THEME_CONFIG = { defaultTheme, customCSS, allowUserCustomization: true }; - } - } else { - console.log('API返回默认配置,保持当前状态'); + // 将配置存储到运行时配置中,供ThemeManager使用 + const runtimeConfig = (window as any).RUNTIME_CONFIG; + if (runtimeConfig) { + runtimeConfig.THEME_CONFIG = themeConfig; } } else { - console.log('API获取失败,保持当前主题配置'); + console.log('无法获取主题配置,使用默认配置:', result); + // API失败时,如果有缓存就保持,没有缓存就用默认 + const cachedTheme = getCachedTheme(); + if (!cachedTheme) { + console.log('无缓存,应用默认主题'); + applyAndCacheTheme('default', ''); + } else { + console.log('保持缓存主题:', cachedTheme); + } } } catch (error) { - console.error('主题配置验证失败:', error); - - // 出错时优先使用服务端配置 - const runtimeConfig = (window as any).RUNTIME_CONFIG; - const serverThemeConfig = runtimeConfig?.THEME_CONFIG; - - if (serverThemeConfig) { - console.log('错误恢复:使用服务端配置:', serverThemeConfig); - applyTheme(serverThemeConfig.defaultTheme, serverThemeConfig.customCSS || ''); + console.error('API同步失败:', error); + // 错误时如果有缓存就保持,没有缓存就用默认 + const cachedTheme = getCachedTheme(); + if (!cachedTheme) { + console.log('无缓存且请求失败,应用默认主题'); + applyAndCacheTheme('default', ''); } else { - console.log('错误恢复:使用默认主题'); - applyTheme('default', ''); + console.log('请求失败,保持缓存主题:', cachedTheme); } } }; + // 获取缓存的主题配置 + const getCachedTheme = () => { + try { + const cached = localStorage.getItem('theme-cache'); + return cached ? JSON.parse(cached) : null; + } catch (error) { + console.warn('读取主题缓存失败:', error); + localStorage.removeItem('theme-cache'); + return null; + } + }; + + // 应用主题并缓存 + const applyAndCacheTheme = (themeId: string, css: string = '') => { + applyTheme(themeId, css); + + // 缓存主题配置 + const themeConfig = { defaultTheme: themeId, customCSS: css }; + try { + localStorage.setItem('theme-cache', JSON.stringify(themeConfig)); + console.log('主题配置已缓存:', themeConfig); + } catch (error) { + console.warn('缓存主题配置失败:', error); + } + }; + // 应用主题函数 const applyTheme = (themeId: string, css: string = '') => { const html = document.documentElement; @@ -108,9 +116,9 @@ const GlobalThemeLoader = () => { customStyleEl.textContent = css; }; - // 稍作延迟,确保DOM完全加载后再验证主题 + // 延迟一点时间,确保页面缓存主题已应用,然后同步API配置 const timer = setTimeout(() => { - validateAndSyncTheme(); + syncThemeWithAPI(); }, 100); return () => clearTimeout(timer); diff --git a/src/components/ThemeManager.tsx b/src/components/ThemeManager.tsx index be7e575..25483e9 100644 --- a/src/components/ThemeManager.tsx +++ b/src/components/ThemeManager.tsx @@ -277,32 +277,44 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => { const isAdmin = role === 'admin' || role === 'owner'; - // 加载全局主题配置 + // 更新主题缓存的辅助函数 + const updateThemeCache = (themeId: string, css: string) => { + try { + const themeConfig = { + defaultTheme: themeId, + customCSS: css + }; + localStorage.setItem('theme-cache', JSON.stringify(themeConfig)); + console.log('主题配置已缓存:', themeConfig); + } catch (error) { + console.warn('缓存主题配置失败:', error); + } + }; + + // 从API加载主题配置(唯一数据源) const loadGlobalThemeConfig = async () => { try { - // 优先使用运行时配置中的主题配置 - const runtimeConfig = (window as any).RUNTIME_CONFIG; - const serverThemeConfig = runtimeConfig?.THEME_CONFIG; - - if (serverThemeConfig) { - console.log('使用服务端预设的主题配置:', serverThemeConfig); - setGlobalThemeConfig(serverThemeConfig); - return serverThemeConfig; - } - - // 如果没有服务端配置,则从API获取 - console.log('服务端无主题配置,从API获取...'); - const response = await fetch('/api/theme'); + console.log('从API获取主题配置...'); + const response = await fetch('/api/admin/config'); const result = await response.json(); - if (result.success) { - if (result.fallback) { - console.log('API返回备用配置,可能存在数据库访问问题'); + + if (result?.Config?.ThemeConfig) { + const themeConfig = result.Config.ThemeConfig; + console.log('API返回的主题配置:', themeConfig); + setGlobalThemeConfig(themeConfig); + + // 更新运行时配置,保持同步 + const runtimeConfig = (window as any).RUNTIME_CONFIG; + if (runtimeConfig) { + runtimeConfig.THEME_CONFIG = themeConfig; } - setGlobalThemeConfig(result.data); - return result.data; + + return themeConfig; + } else { + console.log('无法获取主题配置,可能未登录或权限不足:', result); } } catch (error) { - console.error('加载全局主题配置失败:', error); + console.error('从API加载主题配置失败:', error); } return null; }; @@ -332,6 +344,10 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => { // 立即应用新的主题配置,确保当前页面也能看到更改 applyTheme(result.data.defaultTheme, result.data.customCSS); + + // 更新本地缓存 + updateThemeCache(result.data.defaultTheme, result.data.customCSS); + console.log('已立即应用新主题配置:', result.data.defaultTheme); showAlert({ @@ -517,6 +533,9 @@ const ThemeManager = ({ showAlert, role }: ThemeManagerProps) => { allowUserCustomization: globalThemeConfig?.allowUserCustomization ?? true, }; } + + // 更新本地缓存 + updateThemeCache(currentTheme, ''); } showAlert({ diff --git a/src/lib/config.ts b/src/lib/config.ts index 941678c..43e9119 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -379,6 +379,15 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig { adminConfig.SiteConfig.RequireDeviceCode = process.env.NEXT_PUBLIC_REQUIRE_DEVICE_CODE !== 'false'; } + // 确保 ThemeConfig 存在 + if (!adminConfig.ThemeConfig) { + adminConfig.ThemeConfig = { + defaultTheme: 'default', + customCSS: '', + allowUserCustomization: true, + }; + } + // 站长变更自检 const ownerUser = process.env.USERNAME; @@ -516,4 +525,8 @@ export async function getAvailableApiSites(user?: string): Promise