添加自定义主题支持
This commit is contained in:
@@ -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'>
|
||||
建议值:60分钟(1小时)、1440分钟(1天)、10080分钟(7天)
|
||||
</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
|
||||
|
||||
111
src/app/api/admin/theme/route.ts
Normal file
111
src/app/api/admin/theme/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
74
src/app/api/theme/css/route.ts
Normal file
74
src/app/api/theme/css/route.ts
Normal 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',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
389
src/styles/themes.ts
Normal 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,
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user