diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 183c4ec..ac407c3 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -7329,6 +7329,7 @@ const ThemeConfigComponent = ({ }); const [loginBackgroundImages, setLoginBackgroundImages] = useState(['']); const [registerBackgroundImages, setRegisterBackgroundImages] = useState(['']); + const [homeBackgroundImages, setHomeBackgroundImages] = useState(['']); useEffect(() => { if (config?.ThemeConfig) { @@ -7363,6 +7364,16 @@ const ThemeConfigComponent = ({ } else { setRegisterBackgroundImages(['']); } + + if (config.ThemeConfig.homeBackgroundImage) { + const urls = config.ThemeConfig.homeBackgroundImage + .split('\n') + .map((url) => url.trim()) + .filter((url) => url !== ''); + setHomeBackgroundImages(urls.length > 0 ? urls : ['']); + } else { + setHomeBackgroundImages(['']); + } } }, [config]); @@ -7403,6 +7414,22 @@ const ThemeConfigComponent = ({ } } + const validHomeUrls = homeBackgroundImages + .map((url) => url.trim()) + .filter((url) => url !== ''); + + for (const url of validHomeUrls) { + if (!url.startsWith('http://') && !url.startsWith('https://')) { + showAlert({ + type: 'error', + title: '格式错误', + message: `首页背景图URL格式错误:${url}\n每个URL必须以http://或https://开头`, + showConfirm: true, + }); + return; + } + } + const response = await fetch('/api/admin/theme', { method: 'POST', headers: { @@ -7412,6 +7439,7 @@ const ThemeConfigComponent = ({ ...themeSettings, loginBackgroundImage: validLoginUrls.join('\n'), registerBackgroundImage: validRegisterUrls.join('\n'), + homeBackgroundImage: validHomeUrls.join('\n'), }), }); @@ -7766,9 +7794,59 @@ const ThemeConfigComponent = ({ + + {/* 首页背景图 */} +
+ +
+ {homeBackgroundImages.map((url, index) => ( +
+ { + const newImages = [...homeBackgroundImages]; + newImages[index] = e.target.value; + setHomeBackgroundImages(newImages); + }} + placeholder='请输入首页背景图URL (http:// 或 https://)' + className='flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono text-sm' + /> + {homeBackgroundImages.length > 1 && ( + + )} +
+ ))} + +
+

- 配置登录和注册页面的背景图链接,留空则使用默认样式。支持配置多张图片,将随机展示其中一张 + 配置登录、注册和首页的背景图链接,留空则使用默认样式。支持配置多张图片,将随机展示其中一张

diff --git a/src/app/api/admin/theme/route.ts b/src/app/api/admin/theme/route.ts index 8a31c9f..ae767a9 100644 --- a/src/app/api/admin/theme/route.ts +++ b/src/app/api/admin/theme/route.ts @@ -36,6 +36,7 @@ export async function POST(request: NextRequest) { cacheMinutes, loginBackgroundImage, registerBackgroundImage, + homeBackgroundImage, progressThumbType, progressThumbPresetId, progressThumbCustomUrl, @@ -47,6 +48,7 @@ export async function POST(request: NextRequest) { cacheMinutes: number; loginBackgroundImage?: string; registerBackgroundImage?: string; + homeBackgroundImage?: string; progressThumbType?: 'default' | 'preset' | 'custom'; progressThumbPresetId?: string; progressThumbCustomUrl?: string; @@ -96,6 +98,22 @@ export async function POST(request: NextRequest) { } } + if (homeBackgroundImage && homeBackgroundImage.trim() !== '') { + const urls = homeBackgroundImage + .split('\n') + .map((url) => url.trim()) + .filter((url) => url !== ''); + + for (const url of urls) { + if (!url.startsWith('http://') && !url.startsWith('https://')) { + return NextResponse.json( + { error: `首页背景图URL格式错误:${url},每个URL必须以http://或https://开头` }, + { status: 400 } + ); + } + } + } + const adminConfig = await getConfig(); // 权限校验 - 使用v2用户系统 @@ -124,6 +142,7 @@ export async function POST(request: NextRequest) { cacheVersion: cssChanged ? currentVersion + 1 : currentVersion, loginBackgroundImage: loginBackgroundImage?.trim() || undefined, registerBackgroundImage: registerBackgroundImage?.trim() || undefined, + homeBackgroundImage: homeBackgroundImage?.trim() || undefined, progressThumbType: progressThumbType || 'default', progressThumbPresetId: progressThumbPresetId?.trim() || undefined, progressThumbCustomUrl: progressThumbCustomUrl?.trim() || undefined, diff --git a/src/app/api/server-config/route.ts b/src/app/api/server-config/route.ts index 0f1380a..eacf716 100644 --- a/src/app/api/server-config/route.ts +++ b/src/app/api/server-config/route.ts @@ -61,6 +61,7 @@ export async function GET(request: NextRequest) { DanmakuAutoLoadDefault: config.SiteConfig.DanmakuAutoLoadDefault !== false, loginBackgroundImage: config.ThemeConfig?.loginBackgroundImage || '', registerBackgroundImage: config.ThemeConfig?.registerBackgroundImage || '', + homeBackgroundImage: config.ThemeConfig?.homeBackgroundImage || '', progressThumbType: config.ThemeConfig?.progressThumbType || 'default', progressThumbPresetId: config.ThemeConfig?.progressThumbPresetId || '', progressThumbCustomUrl: config.ThemeConfig?.progressThumbCustomUrl || '', diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f82d369..d2a3de4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,13 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { Metadata, Viewport } from 'next'; -import { cookies } from 'next/headers'; import { Inter } from 'next/font/google'; +import { cookies } from 'next/headers'; import './globals.css'; -import { getConfig } from '@/lib/config'; import { parseAuthInfo } from '@/lib/auth'; +import { getConfig } from '@/lib/config'; import { getUserFeatureAccess } from '@/lib/permissions'; import { listEnabledSourceScripts } from '@/lib/source-script'; @@ -15,11 +15,11 @@ import { StartupCacheCleanup } from '../components/DanmakuCacheCleanup'; import { DownloadBubble } from '../components/DownloadBubble'; import { DownloadPanel } from '../components/DownloadPanel'; import { GlobalErrorIndicator } from '../components/GlobalErrorIndicator'; +import RouteScrollReset from '../components/RouteScrollReset'; import { SiteProvider } from '../components/SiteProvider'; import { ThemeProvider } from '../components/ThemeProvider'; import { TokenRefreshManager } from '../components/TokenRefreshManager'; import TopProgressBar from '../components/TopProgressBar'; -import RouteScrollReset from '../components/RouteScrollReset'; import ChatFloatingWindow from '../components/watch-room/ChatFloatingWindow'; import { WatchRoomProvider } from '../components/WatchRoomProvider'; import { DownloadProvider } from '../contexts/DownloadContext'; @@ -76,6 +76,7 @@ export default async function RootLayout({ let xiaoyaEnabled = false; let loginBackgroundImage = ''; let registerBackgroundImage = ''; + let homeBackgroundImage = ''; let progressThumbType = 'default'; let progressThumbPresetId = ''; let progressThumbCustomUrl = ''; @@ -139,6 +140,7 @@ export default async function RootLayout({ tmdbApiKey = config.SiteConfig.TMDBApiKey || ''; loginBackgroundImage = config.ThemeConfig?.loginBackgroundImage || ''; registerBackgroundImage = config.ThemeConfig?.registerBackgroundImage || ''; + homeBackgroundImage = config.ThemeConfig?.homeBackgroundImage || ''; progressThumbType = config.ThemeConfig?.progressThumbType || 'default'; progressThumbPresetId = config.ThemeConfig?.progressThumbPresetId || ''; progressThumbCustomUrl = config.ThemeConfig?.progressThumbCustomUrl || ''; @@ -226,6 +228,7 @@ export default async function RootLayout({ (xiaoyaEnabled && userFeatureAccess.xiaoya), LOGIN_BACKGROUND_IMAGE: loginBackgroundImage, REGISTER_BACKGROUND_IMAGE: registerBackgroundImage, + HOME_BACKGROUND_IMAGE: homeBackgroundImage, PROGRESS_THUMB_TYPE: progressThumbType, PROGRESS_THUMB_PRESET_ID: progressThumbPresetId, PROGRESS_THUMB_CUSTOM_URL: progressThumbCustomUrl, diff --git a/src/components/PageLayout.tsx b/src/components/PageLayout.tsx index 6c06380..3ae58df 100644 --- a/src/components/PageLayout.tsx +++ b/src/components/PageLayout.tsx @@ -1,3 +1,7 @@ +'use client'; + +import { useEffect, useState } from 'react'; + import { BackButton } from './BackButton'; import MobileBottomNav from './MobileBottomNav'; import MobileHeader from './MobileHeader'; @@ -14,16 +18,61 @@ interface PageLayoutProps { } const PageLayout = ({ children, activePath = '/', hideNavigation = false }: PageLayoutProps) => { + const [backgroundImage, setBackgroundImage] = useState(''); + const shouldShowSharedBackground = !hideNavigation && activePath !== '/play'; + + useEffect(() => { + if (typeof window === 'undefined' || !shouldShowSharedBackground) { + setBackgroundImage(''); + return; + } + + const homeBg = ( + window as Window & { + RUNTIME_CONFIG?: { + HOME_BACKGROUND_IMAGE?: string; + }; + } + ).RUNTIME_CONFIG?.HOME_BACKGROUND_IMAGE; + if (!homeBg) { + setBackgroundImage(''); + return; + } + + const urls = homeBg + .split('\n') + .map((url: string) => url.trim()) + .filter((url: string) => url !== ''); + + if (urls.length === 0) { + setBackgroundImage(''); + return; + } + + const randomIndex = Math.floor(Math.random() * urls.length); + setBackgroundImage(urls[randomIndex]); + }, [shouldShowSharedBackground]); + return ( -
+
+ {shouldShowSharedBackground && backgroundImage && ( + <> +
+
+ + )} + {/* 移动端头部 */} {!hideNavigation && ( )} {/* 主要布局容器 */} -
+
{/* 侧边栏 - 桌面端显示,移动端隐藏 */} {!hideNavigation && (
diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts index 3aaf8e4..2b6eb84 100644 --- a/src/lib/admin.types.ts +++ b/src/lib/admin.types.ts @@ -126,6 +126,7 @@ export interface AdminConfig { cacheVersion: number; // CSS版本号(用于缓存控制) loginBackgroundImage?: string; // 登录界面背景图 registerBackgroundImage?: string; // 注册界面背景图 + homeBackgroundImage?: string; // 首页背景图 // 进度条图标配置 progressThumbType?: 'default' | 'preset' | 'custom'; // 图标类型 progressThumbPresetId?: string; // 预制图标ID