diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index ba27e7d..e6a860e 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -5,7 +5,9 @@ import { Sidebar } from './components/Sidebar' import { ContentArea } from './components/ContentArea' import { Wizard } from './components/Wizard' import { ThemeProvider } from './contexts/ThemeContext' +import { ThemeEditorProvider } from './contexts/ThemeEditorContext' import { GlobalSidebar } from './components/GlobalSidebar' +import { ThemeEditor } from './components/ThemeEditor' function MainContent(): React.JSX.Element { const navigate = useNavigate() @@ -143,12 +145,15 @@ function MainContent(): React.JSX.Element { function App(): React.JSX.Element { return ( - - - } /> - } /> - - + + + + } /> + } /> + + + + ) } diff --git a/src/renderer/src/components/Settings.tsx b/src/renderer/src/components/Settings.tsx index a1ddd90..075bcbe 100644 --- a/src/renderer/src/components/Settings.tsx +++ b/src/renderer/src/components/Settings.tsx @@ -13,6 +13,7 @@ import { MessagePlugin } from 'tdesign-react' import { useTheme } from '../contexts/ThemeContext' +import { useThemeEditor } from '../contexts/ThemeEditorContext' type permissionLevel = 'admin' | 'points' | 'view' type appSettings = { @@ -23,6 +24,7 @@ type appSettings = { export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission }) => { const { themes, currentTheme, setTheme } = useTheme() + const { startEditing } = useThemeEditor() const [activeTab, setActiveTab] = useState('appearance') const [settings, setSettings] = useState({ is_wizard_completed: false, @@ -261,15 +263,27 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
- +
+ + + +
diff --git a/src/renderer/src/components/ThemeEditor.tsx b/src/renderer/src/components/ThemeEditor.tsx new file mode 100644 index 0000000..d794a5a --- /dev/null +++ b/src/renderer/src/components/ThemeEditor.tsx @@ -0,0 +1,232 @@ +import React, { useEffect } from 'react' +import { + Drawer, + Form, + Input, + Select, + ColorPicker, + Button, + Space, + Divider, + Row, + Col +} from 'tdesign-react' +import { useThemeEditor } from '../contexts/ThemeEditorContext' +import { useTheme } from '../contexts/ThemeContext' +import { generateColorMap } from '../utils/color' +import { themeConfig } from '../../../preload/types' + +const variableGroups = { + background: { + title: '背景颜色', + items: [ + { key: '--ss-bg-color', label: '全局背景' }, + { key: '--ss-card-bg', label: '卡片背景' }, + { key: '--ss-header-bg', label: '顶部栏背景' }, + { key: '--ss-sidebar-bg', label: '侧边栏背景' } + ] + }, + text: { + title: '文字颜色', + items: [ + { key: '--ss-text-main', label: '主要文字' }, + { key: '--ss-text-secondary', label: '次要文字' }, + { key: '--ss-sidebar-active-text', label: '侧边栏选中文字' } + ] + }, + border: { + title: '边框与分割线', + items: [{ key: '--ss-border-color', label: '通用边框' }] + }, + interaction: { + title: '交互状态', + items: [ + { key: '--ss-item-hover', label: '列表悬浮' }, + { key: '--ss-sidebar-active-bg', label: '侧边栏选中背景' } + ] + } +} + +export const ThemeEditor: React.FC = () => { + const { + isEditing, + editingTheme, + updateEditingTheme, + updateConfig, + saveEditingTheme, + cancelEditing + } = useThemeEditor() + + const { currentTheme } = useTheme() + + // 实时预览逻辑 + useEffect(() => { + if (!isEditing || !editingTheme) return + + const applyPreview = (theme: themeConfig) => { + const { tdesign, custom } = theme.config + const root = document.documentElement + + // 1. 设置 TDesign 模式 + root.setAttribute('theme-mode', theme.mode) + + // 2. 设置 TDesign 品牌色 + if (tdesign.brandColor) { + const colorMap = generateColorMap(tdesign.brandColor, theme.mode) + Object.entries(colorMap).forEach(([key, value]) => { + root.style.setProperty(key, value) + }) + } + + // 3. 应用自定义变量 + Object.entries(custom).forEach(([key, value]) => { + root.style.setProperty(key, value) + }) + } + + applyPreview(editingTheme) + }, [editingTheme, isEditing]) + + // 关闭时恢复原有主题 + useEffect(() => { + if (!isEditing && currentTheme) { + const { tdesign, custom } = currentTheme.config + const root = document.documentElement + + root.setAttribute('theme-mode', currentTheme.mode) + + if (tdesign.brandColor) { + const colorMap = generateColorMap(tdesign.brandColor, currentTheme.mode) + Object.entries(colorMap).forEach(([key, value]) => { + root.style.setProperty(key, value) + }) + } + + Object.entries(custom).forEach(([key, value]) => { + root.style.setProperty(key, value) + }) + } + }, [isEditing, currentTheme]) + + if (!editingTheme) return null + + return ( + + + + + } + destroyOnClose + > + + + {/* 基本信息 */} +
+ 基本信息 + + + + updateEditingTheme({ name: v })} + placeholder="请输入主题名称" + /> + + + + + updateEditingTheme({ id: v })} + placeholder="请输入主题 ID" + /> + + + +
+ + {/* TDesign 品牌色 */} +
+ 品牌色 (Brand) + + updateConfig('tdesign', 'brandColor', v)} + enableAlpha={false} + format="HEX" + /> + +
+ + {/* 业务自定义变量 */} +
+ 界面配色 (Custom) + {Object.entries(variableGroups).map(([groupKey, group]) => ( +
+
+ {group.title} +
+ + {group.items.map((item) => ( + +
+
+ {item.label} +
+ updateConfig('custom', item.key, v)} + enableAlpha + format="HEX" + size="small" + /> +
+ + ))} +
+
+ ))} +
+
+ +
+ ) +}