添加自定义主题支持

This commit is contained in:
mtvpls
2025-12-12 18:06:34 +08:00
parent 4ff7c1caf3
commit 15d86e3252
7 changed files with 918 additions and 1 deletions

View File

@@ -32,6 +32,7 @@ import {
ExternalLink,
FileText,
FolderOpen,
Palette,
Settings,
Tv,
Users,
@@ -4235,6 +4236,322 @@ const ConfigFileComponent = ({
);
};
// 主题配置组件
const ThemeConfigComponent = ({
config,
refreshConfig,
}: {
config: AdminConfig | null;
refreshConfig: () => Promise<void>;
}) => {
const { alertModal, showAlert, hideAlert } = useAlertModal();
const { isLoading, withLoading } = useLoadingState();
const [themeSettings, setThemeSettings] = useState({
enableBuiltInTheme: false,
builtInTheme: 'default',
customCSS: '',
enableCache: true,
cacheMinutes: 1440, // 默认1天1440分钟
});
useEffect(() => {
if (config?.ThemeConfig) {
setThemeSettings({
enableBuiltInTheme: config.ThemeConfig.enableBuiltInTheme || false,
builtInTheme: config.ThemeConfig.builtInTheme || 'default',
customCSS: config.ThemeConfig.customCSS || '',
enableCache: config.ThemeConfig.enableCache !== false,
cacheMinutes: config.ThemeConfig.cacheMinutes || 1440,
});
}
}, [config]);
const handleSave = async () => {
await withLoading('saveThemeConfig', async () => {
try {
const response = await fetch('/api/admin/theme', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(themeSettings),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '保存失败');
}
showAlert({
type: 'success',
title: '保存成功',
message: '主题配置已更新',
timer: 2000,
});
await refreshConfig();
// 刷新页面以应用新主题
setTimeout(() => {
window.location.reload();
}, 1000);
} catch (error) {
showAlert({
type: 'error',
title: '保存失败',
message: (error as Error).message,
});
}
});
};
const builtInThemes = [
{
value: 'default',
label: '默认主题',
color: '#3b82f6',
},
{
value: 'dark_blue',
label: '深蓝夜空',
color: '#3b82f6',
},
{
value: 'purple_dream',
label: '紫色梦境',
color: '#a78bfa',
},
{
value: 'green_forest',
label: '翠绿森林',
color: '#10b981',
},
{
value: 'orange_sunset',
label: '橙色日落',
color: '#f97316',
},
{
value: 'pink_candy',
label: '粉色糖果',
color: '#ec4899',
},
{
value: 'cyan_ocean',
label: '青色海洋',
color: '#06b6d4',
},
];
return (
<div className='space-y-6'>
{/* 主题类型选择 */}
<div className='bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700'>
<h3 className='text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4'>
</h3>
<div className='space-y-4'>
<label className='flex items-center space-x-3 cursor-pointer'>
<input
type='radio'
checked={!themeSettings.enableBuiltInTheme}
onChange={() =>
setThemeSettings((prev) => ({
...prev,
enableBuiltInTheme: false,
}))
}
className='w-4 h-4 text-blue-600'
/>
<span className='text-gray-900 dark:text-gray-100'>
CSS使CSS编辑器
</span>
</label>
<label className='flex items-center space-x-3 cursor-pointer'>
<input
type='radio'
checked={themeSettings.enableBuiltInTheme}
onChange={() =>
setThemeSettings((prev) => ({
...prev,
enableBuiltInTheme: true,
}))
}
className='w-4 h-4 text-blue-600'
/>
<span className='text-gray-900 dark:text-gray-100'>
使
</span>
</label>
</div>
</div>
{/* 内置主题选择 */}
{themeSettings.enableBuiltInTheme && (
<div className='bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700'>
<h3 className='text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4'>
</h3>
<div className='flex flex-wrap gap-3'>
{builtInThemes.map((theme) => (
<div
key={theme.value}
onClick={() =>
setThemeSettings((prev) => ({
...prev,
builtInTheme: theme.value,
}))
}
className={`cursor-pointer rounded-lg border-2 p-3 transition-all hover:shadow-md ${
themeSettings.builtInTheme === theme.value
? 'border-blue-500 ring-2 ring-blue-200 dark:ring-blue-800 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}`}
>
<div className='flex items-center gap-3'>
{/* 圆形颜色预览 */}
<div
className='w-10 h-10 rounded-full flex-shrink-0 shadow-sm'
style={{ backgroundColor: theme.color }}
/>
{/* 主题名称 */}
<div className='flex items-center gap-2'>
<span className='text-sm font-medium text-gray-900 dark:text-gray-100 whitespace-nowrap'>
{theme.label}
</span>
{themeSettings.builtInTheme === theme.value && (
<div className='w-4 h-4 rounded-full bg-blue-500 flex items-center justify-center flex-shrink-0'>
<svg
className='w-2.5 h-2.5 text-white'
fill='none'
stroke='currentColor'
viewBox='0 0 24 24'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={3}
d='M5 13l4 4L19 7'
/>
</svg>
</div>
)}
</div>
</div>
</div>
))}
</div>
<p className='mt-4 text-sm text-gray-600 dark:text-gray-400'>
CSS将被禁用
</p>
</div>
)}
{/* 自定义CSS编辑器 */}
{!themeSettings.enableBuiltInTheme && (
<div className='bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700'>
<h3 className='text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4'>
CSS
</h3>
<textarea
value={themeSettings.customCSS}
onChange={(e) =>
setThemeSettings((prev) => ({
...prev,
customCSS: e.target.value,
}))
}
placeholder='在此输入自定义CSS代码...'
className='w-full h-96 px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent'
/>
<p className='mt-2 text-sm text-gray-600 dark:text-gray-400'>
使CSS变量
</p>
</div>
)}
{/* 缓存设置 */}
<div className='bg-white dark:bg-gray-800 rounded-lg p-6 border border-gray-200 dark:border-gray-700'>
<h3 className='text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4'>
</h3>
<div className='space-y-4'>
<label className='flex items-center space-x-3 cursor-pointer'>
<input
type='checkbox'
checked={themeSettings.enableCache}
onChange={(e) =>
setThemeSettings((prev) => ({
...prev,
enableCache: e.target.checked,
}))
}
className='w-4 h-4 text-blue-600 rounded'
/>
<span className='text-gray-900 dark:text-gray-100'>
</span>
</label>
{themeSettings.enableCache && (
<div>
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
</label>
<input
type='number'
min='1'
max='43200'
value={themeSettings.cacheMinutes}
onChange={(e) =>
setThemeSettings((prev) => ({
...prev,
cacheMinutes: parseInt(e.target.value) || 1440,
}))
}
className='w-full 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'
/>
<p className='mt-2 text-sm text-gray-600 dark:text-gray-400'>
60114401100807
</p>
</div>
)}
</div>
<p className='mt-4 text-sm text-gray-600 dark:text-gray-400'>
CSS文件指定时间
</p>
</div>
{/* 保存按钮 */}
<div className='flex justify-end'>
<button
onClick={handleSave}
disabled={isLoading('saveThemeConfig')}
className={
isLoading('saveThemeConfig')
? buttonStyles.disabled
: buttonStyles.success
}
>
{isLoading('saveThemeConfig') ? '保存中...' : '保存主题配置'}
</button>
</div>
{/* 弹窗 */}
<AlertModal
isOpen={alertModal.isOpen}
onClose={hideAlert}
type={alertModal.type}
title={alertModal.title}
message={alertModal.message}
timer={alertModal.timer}
showConfirm={alertModal.showConfirm}
/>
</div>
);
};
// 新增站点配置组件
const SiteConfigComponent = ({
config,
@@ -5869,6 +6186,7 @@ function AdminPageClient() {
configFile: false,
dataMigration: false,
customAdFilter: false,
themeConfig: false,
});
// 获取管理员配置
@@ -6016,6 +6334,21 @@ function AdminPageClient() {
<SiteConfigComponent config={config} refreshConfig={fetchConfig} />
</CollapsibleTab>
{/* 主题配置标签 */}
<CollapsibleTab
title='主题配置'
icon={
<Palette
size={20}
className='text-gray-600 dark:text-gray-400'
/>
}
isExpanded={expandedTabs.themeConfig}
onToggle={() => toggleTab('themeConfig')}
>
<ThemeConfigComponent config={config} refreshConfig={fetchConfig} />
</CollapsibleTab>
<div className='space-y-4'>
{/* 用户配置标签 */}
<CollapsibleTab

View File

@@ -0,0 +1,111 @@
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db';
export const runtime = 'nodejs';
export async function POST(request: NextRequest) {
const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
if (storageType === 'localstorage') {
return NextResponse.json(
{
error: '不支持本地存储进行管理员配置',
},
{ status: 400 }
);
}
try {
const body = await request.json();
const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const username = authInfo.username;
const {
enableBuiltInTheme,
builtInTheme,
customCSS,
enableCache,
cacheMinutes,
} = body as {
enableBuiltInTheme: boolean;
builtInTheme: string;
customCSS: string;
enableCache: boolean;
cacheMinutes: number;
};
// 参数校验
if (
typeof enableBuiltInTheme !== 'boolean' ||
typeof builtInTheme !== 'string' ||
typeof customCSS !== 'string' ||
typeof enableCache !== 'boolean' ||
typeof cacheMinutes !== 'number'
) {
return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
}
const adminConfig = await getConfig();
// 权限校验
if (username !== process.env.USERNAME) {
// 管理员
const user = adminConfig.UserConfig.Users.find(
(u) => u.username === username
);
if (!user || user.role !== 'admin' || user.banned) {
return NextResponse.json({ error: '权限不足' }, { status: 401 });
}
}
// 获取当前版本号如果CSS有变化则递增
const currentVersion = adminConfig.ThemeConfig?.cacheVersion || 0;
const currentCSS = enableBuiltInTheme
? adminConfig.ThemeConfig?.builtInTheme
: adminConfig.ThemeConfig?.customCSS;
const newCSS = enableBuiltInTheme ? builtInTheme : customCSS;
const cssChanged = currentCSS !== newCSS;
// 更新主题配置
adminConfig.ThemeConfig = {
enableBuiltInTheme,
builtInTheme,
customCSS,
enableCache,
cacheMinutes,
cacheVersion: cssChanged ? currentVersion + 1 : currentVersion,
};
// 写入数据库
await db.saveAdminConfig(adminConfig);
return NextResponse.json(
{
ok: true,
cacheVersion: adminConfig.ThemeConfig.cacheVersion,
},
{
headers: {
'Cache-Control': 'no-store',
},
}
);
} catch (error) {
console.error('更新主题配置失败:', error);
return NextResponse.json(
{
error: '更新主题配置失败',
details: (error as Error).message,
},
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/no-explicit-any,no-console */
import { NextRequest, NextResponse } from 'next/server';
import { getConfig } from '@/lib/config';
import { getThemeCSS } from '@/styles/themes';
export const runtime = 'nodejs';
export async function GET(request: NextRequest) {
try {
const adminConfig = await getConfig();
const themeConfig = adminConfig.ThemeConfig;
// 如果没有配置主题返回空CSS
if (!themeConfig) {
return new NextResponse('', {
headers: {
'Content-Type': 'text/css',
'Cache-Control': 'no-store',
},
});
}
let css = '';
// 如果启用了内置主题使用内置主题CSS
if (themeConfig.enableBuiltInTheme) {
css = getThemeCSS(themeConfig.builtInTheme as any);
} else {
// 否则使用自定义CSS
css = themeConfig.customCSS || '';
}
// 设置缓存控制
const cacheMinutes = themeConfig.cacheMinutes || 1440; // 默认1天1440分钟
const maxAge = cacheMinutes * 60; // 转换为秒
const staleWhileRevalidate = maxAge * 7; // 过期后7倍时间内可使用旧版本
const cacheControl = themeConfig.enableCache
? `public, max-age=${maxAge}, stale-while-revalidate=${staleWhileRevalidate}`
: 'no-store';
// 添加版本号到ETag
const etag = `"${themeConfig.cacheVersion}"`;
// 检查客户端缓存
const ifNoneMatch = request.headers.get('if-none-match');
if (ifNoneMatch === etag && themeConfig.enableCache) {
return new NextResponse(null, {
status: 304,
headers: {
'Cache-Control': cacheControl,
ETag: etag,
},
});
}
return new NextResponse(css, {
headers: {
'Content-Type': 'text/css; charset=utf-8',
'Cache-Control': cacheControl,
ETag: etag,
},
});
} catch (error) {
console.error('获取主题CSS失败:', error);
return new NextResponse('', {
headers: {
'Content-Type': 'text/css',
'Cache-Control': 'no-store',
},
});
}
}

View File

@@ -109,6 +109,8 @@ export default async function RootLayout({
content='width=device-width, initial-scale=1.0, viewport-fit=cover'
/>
<link rel='apple-touch-icon' href='/icons/icon-192x192.png' />
{/* 主题CSS */}
<link rel='stylesheet' href='/api/theme/css' />
{/* 将配置序列化后直接写入脚本,浏览器端可通过 window.RUNTIME_CONFIG 获取 */}
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
<script

View File

@@ -63,6 +63,14 @@ export interface AdminConfig {
channelNumber?: number;
disabled?: boolean;
}[];
ThemeConfig?: {
enableBuiltInTheme: boolean; // 是否启用内置主题
builtInTheme: string; // 内置主题名称
customCSS: string; // 自定义CSS
enableCache: boolean; // 是否启用浏览器缓存
cacheMinutes: number; // 缓存时间(分钟)
cacheVersion: number; // CSS版本号用于缓存控制
};
}
export interface AdminConfigResult {

View File

@@ -133,6 +133,6 @@ function shouldSkipAuth(pathname: string): boolean {
// 配置middleware匹配规则
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/proxy-m3u8|api/cms-proxy|api/tvbox/subscribe).*)',
'/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/proxy-m3u8|api/cms-proxy|api/tvbox/subscribe|api/theme/css).*)',
],
};

389
src/styles/themes.ts Normal file
View File

@@ -0,0 +1,389 @@
// 内置主题定义
export const builtInThemes = {
default: {
name: '默认主题',
description: '使用系统默认样式',
color: '#3b82f6',
css: '',
},
dark_blue: {
name: '深蓝夜空',
description: '深邃的蓝色科技风格',
color: '#3b82f6',
css: `
/* 浅色模式 */
:root:not(.dark) {
--theme-primary: #2563eb;
--theme-primary-hover: #1d4ed8;
--theme-bg: #dbeafe;
--theme-bg-secondary: #eff6ff;
--theme-text: #1e3a8a;
--theme-text-secondary: #3b82f6;
--theme-border: #93c5fd;
}
:root:not(.dark) body {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%) !important;
color: var(--theme-text) !important;
}
/* 深色模式 */
.dark {
--theme-primary: #3b82f6;
--theme-primary-hover: #2563eb;
--theme-bg: #0f172a;
--theme-bg-secondary: #1e293b;
--theme-text: #e2e8f0;
--theme-text-secondary: #94a3b8;
--theme-border: #334155;
}
.dark body {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%) !important;
color: var(--theme-text) !important;
}
/* 通用样式 */
[class*="bg-white"]:not(.dark *), [class*="bg-gray-50"]:not(.dark *) {
background: var(--theme-bg-secondary) !important;
color: var(--theme-text) !important;
}
.dark [class*="bg-gray-800"], .dark [class*="bg-gray-900"] {
background: var(--theme-bg-secondary) !important;
color: var(--theme-text) !important;
}
[class*="border-gray"] {
border-color: var(--theme-border) !important;
}
[class*="text-gray-600"], [class*="text-gray-700"], [class*="text-gray-800"] {
color: var(--theme-text-secondary) !important;
}
.dark [class*="text-gray-"] {
color: var(--theme-text-secondary) !important;
}
`,
},
purple_dream: {
name: '紫色梦境',
description: '神秘的紫色渐变风格',
color: '#a78bfa',
css: `
/* 浅色模式 */
:root:not(.dark) {
--theme-primary: #7c3aed;
--theme-primary-hover: #6d28d9;
--theme-bg: #f3e8ff;
--theme-bg-secondary: #faf5ff;
--theme-text: #581c87;
--theme-text-secondary: #7c3aed;
--theme-border: #d8b4fe;
}
:root:not(.dark) body {
background: linear-gradient(135deg, #faf5ff 0%, #f3e8ff 100%) !important;
color: var(--theme-text) !important;
}
/* 深色模式 */
.dark {
--theme-primary: #a78bfa;
--theme-primary-hover: #8b5cf6;
--theme-bg: #1a0b2e;
--theme-bg-secondary: #2d1b4e;
--theme-text: #f3e8ff;
--theme-text-secondary: #c4b5fd;
--theme-border: #4c1d95;
}
.dark body {
background: linear-gradient(135deg, #1a0b2e 0%, #2d1b4e 50%, #4c1d95 100%) !important;
color: var(--theme-text) !important;
}
/* 通用样式 */
[class*="bg-white"]:not(.dark *), [class*="bg-gray-50"]:not(.dark *) {
background: var(--theme-bg-secondary) !important;
color: var(--theme-text) !important;
}
.dark [class*="bg-gray-800"], .dark [class*="bg-gray-900"] {
background: rgba(45, 27, 78, 0.8) !important;
color: var(--theme-text) !important;
backdrop-filter: blur(10px);
}
[class*="border-gray"] {
border-color: var(--theme-border) !important;
}
[class*="text-gray-600"], [class*="text-gray-700"], [class*="text-gray-800"] {
color: var(--theme-text-secondary) !important;
}
.dark [class*="text-gray-"] {
color: var(--theme-text-secondary) !important;
}
`,
},
green_forest: {
name: '翠绿森林',
description: '清新的绿色自然风格',
color: '#10b981',
css: `
/* 浅色模式 */
:root:not(.dark) {
--theme-primary: #059669;
--theme-primary-hover: #047857;
--theme-bg: #d1fae5;
--theme-bg-secondary: #ecfdf5;
--theme-text: #064e3b;
--theme-text-secondary: #059669;
--theme-border: #6ee7b7;
}
:root:not(.dark) body {
background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%) !important;
color: var(--theme-text) !important;
}
/* 深色模式 */
.dark {
--theme-primary: #10b981;
--theme-primary-hover: #059669;
--theme-bg: #064e3b;
--theme-bg-secondary: #065f46;
--theme-text: #d1fae5;
--theme-text-secondary: #6ee7b7;
--theme-border: #047857;
}
.dark body {
background: linear-gradient(135deg, #064e3b 0%, #065f46 100%) !important;
color: var(--theme-text) !important;
}
/* 通用样式 */
[class*="bg-white"]:not(.dark *), [class*="bg-gray-50"]:not(.dark *) {
background: var(--theme-bg-secondary) !important;
color: var(--theme-text) !important;
}
.dark [class*="bg-gray-800"], .dark [class*="bg-gray-900"] {
background: rgba(6, 95, 70, 0.9) !important;
color: var(--theme-text) !important;
}
[class*="border-gray"] {
border-color: var(--theme-border) !important;
}
[class*="text-gray-600"], [class*="text-gray-700"], [class*="text-gray-800"] {
color: var(--theme-text-secondary) !important;
}
.dark [class*="text-gray-"] {
color: var(--theme-text-secondary) !important;
}
`,
},
orange_sunset: {
name: '橙色日落',
description: '温暖的橙色日落风格',
color: '#f97316',
css: `
/* 浅色模式 */
:root:not(.dark) {
--theme-primary: #ea580c;
--theme-primary-hover: #c2410c;
--theme-bg: #fed7aa;
--theme-bg-secondary: #ffedd5;
--theme-text: #7c2d12;
--theme-text-secondary: #ea580c;
--theme-border: #fdba74;
}
:root:not(.dark) body {
background: linear-gradient(135deg, #ffedd5 0%, #fed7aa 100%) !important;
color: var(--theme-text) !important;
}
/* 深色模式 */
.dark {
--theme-primary: #f97316;
--theme-primary-hover: #ea580c;
--theme-bg: #431407;
--theme-bg-secondary: #7c2d12;
--theme-text: #fed7aa;
--theme-text-secondary: #fdba74;
--theme-border: #9a3412;
}
.dark body {
background: linear-gradient(135deg, #431407 0%, #7c2d12 50%, #9a3412 100%) !important;
color: var(--theme-text) !important;
}
/* 通用样式 */
[class*="bg-white"]:not(.dark *), [class*="bg-gray-50"]:not(.dark *) {
background: var(--theme-bg-secondary) !important;
color: var(--theme-text) !important;
}
.dark [class*="bg-gray-800"], .dark [class*="bg-gray-900"] {
background: rgba(124, 45, 18, 0.85) !important;
color: var(--theme-text) !important;
}
[class*="border-gray"] {
border-color: var(--theme-border) !important;
}
[class*="text-gray-600"], [class*="text-gray-700"], [class*="text-gray-800"] {
color: var(--theme-text-secondary) !important;
}
.dark [class*="text-gray-"] {
color: var(--theme-text-secondary) !important;
}
`,
},
pink_candy: {
name: '粉色糖果',
description: '甜美的粉色糖果风格',
color: '#ec4899',
css: `
/* 浅色模式 */
:root:not(.dark) {
--theme-primary: #db2777;
--theme-primary-hover: #be185d;
--theme-bg: #fce7f3;
--theme-bg-secondary: #fdf2f8;
--theme-text: #831843;
--theme-text-secondary: #db2777;
--theme-border: #fbcfe8;
}
:root:not(.dark) body {
background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 100%) !important;
color: var(--theme-text) !important;
}
/* 深色模式 */
.dark {
--theme-primary: #ec4899;
--theme-primary-hover: #db2777;
--theme-bg: #500724;
--theme-bg-secondary: #831843;
--theme-text: #fce7f3;
--theme-text-secondary: #fbcfe8;
--theme-border: #9f1239;
}
.dark body {
background: linear-gradient(135deg, #500724 0%, #831843 50%, #9f1239 100%) !important;
color: var(--theme-text) !important;
}
/* 通用样式 */
[class*="bg-white"]:not(.dark *), [class*="bg-gray-50"]:not(.dark *) {
background: var(--theme-bg-secondary) !important;
color: var(--theme-text) !important;
}
.dark [class*="bg-gray-800"], .dark [class*="bg-gray-900"] {
background: rgba(131, 24, 67, 0.85) !important;
color: var(--theme-text) !important;
}
[class*="border-gray"] {
border-color: var(--theme-border) !important;
}
[class*="text-gray-600"], [class*="text-gray-700"], [class*="text-gray-800"] {
color: var(--theme-text-secondary) !important;
}
.dark [class*="text-gray-"] {
color: var(--theme-text-secondary) !important;
}
`,
},
cyan_ocean: {
name: '青色海洋',
description: '清爽的青色海洋风格',
color: '#06b6d4',
css: `
/* 浅色模式 */
:root:not(.dark) {
--theme-primary: #0891b2;
--theme-primary-hover: #0e7490;
--theme-bg: #cffafe;
--theme-bg-secondary: #ecfeff;
--theme-text: #164e63;
--theme-text-secondary: #0891b2;
--theme-border: #67e8f9;
}
:root:not(.dark) body {
background: linear-gradient(135deg, #ecfeff 0%, #cffafe 100%) !important;
color: var(--theme-text) !important;
}
/* 深色模式 */
.dark {
--theme-primary: #06b6d4;
--theme-primary-hover: #0891b2;
--theme-bg: #083344;
--theme-bg-secondary: #164e63;
--theme-text: #cffafe;
--theme-text-secondary: #67e8f9;
--theme-border: #155e75;
}
.dark body {
background: linear-gradient(135deg, #083344 0%, #164e63 100%) !important;
color: var(--theme-text) !important;
}
/* 通用样式 */
[class*="bg-white"]:not(.dark *), [class*="bg-gray-50"]:not(.dark *) {
background: var(--theme-bg-secondary) !important;
color: var(--theme-text) !important;
}
.dark [class*="bg-gray-800"], .dark [class*="bg-gray-900"] {
background: rgba(22, 78, 99, 0.9) !important;
color: var(--theme-text) !important;
}
[class*="border-gray"] {
border-color: var(--theme-border) !important;
}
[class*="text-gray-600"], [class*="text-gray-700"], [class*="text-gray-800"] {
color: var(--theme-text-secondary) !important;
}
.dark [class*="text-gray-"] {
color: var(--theme-text-secondary) !important;
}
`,
},
};
export type ThemeName = keyof typeof builtInThemes;
export function getThemeCSS(themeName: ThemeName): string {
return builtInThemes[themeName]?.css || '';
}
export function getThemeNames(): { value: ThemeName; label: string }[] {
return Object.entries(builtInThemes).map(([key, theme]) => ({
value: key as ThemeName,
label: theme.name,
}));
}