# 主题系统设计
## 🎯 学习目标
通过本章学习,您将能够:
- 理解TUI主题系统的设计原理和架构
- 掌握CSS样式和主题变量的使用技巧
- 学会创建动态主题切换和自定义主题
- 在Chat-Room项目中实现完整的主题系统
## 🎨 主题系统架构
### 主题系统概览
```mermaid
graph TB
subgraph "主题系统架构"
A[主题管理器
Theme Manager] --> B[主题加载器
Theme Loader]
B --> C[CSS解析器
CSS Parser]
C --> D[样式计算器
Style Calculator]
D --> E[渲染引擎
Render Engine]
F[主题存储
Theme Storage] --> B
G[变量系统
Variable System] --> C
H[动画系统
Animation System] --> D
I[响应式系统
Responsive System] --> D
end
subgraph "主题类型"
J[内置主题
Built-in Themes]
K[用户主题
User Themes]
L[动态主题
Dynamic Themes]
M[自适应主题
Adaptive Themes]
end
F --> J
F --> K
F --> L
F --> M
style A fill:#e8f5e8
style E fill:#f8d7da
```
### 主题切换流程
```mermaid
sequenceDiagram
participant U as 用户
participant TM as 主题管理器
participant TL as 主题加载器
participant CP as CSS解析器
participant RE as 渲染引擎
participant UI as 界面组件
Note over U,UI: 主题切换流程
U->>TM: 请求切换主题
TM->>TL: 加载新主题
TL->>TL: 读取主题文件
TL->>CP: 解析CSS样式
CP->>CP: 计算样式规则
CP->>TM: 返回样式对象
TM->>RE: 应用新样式
RE->>UI: 重新渲染界面
UI->>U: 显示新主题效果
Note over U,UI: 主题预览流程
U->>TM: 请求预览主题
TM->>TL: 临时加载主题
TL->>CP: 解析预览样式
CP->>RE: 应用预览效果
RE->>UI: 显示预览界面
U->>TM: 确认或取消
alt 确认应用
TM->>TM: 保存主题设置
else 取消预览
TM->>RE: 恢复原主题
end
```
## 🎭 主题系统实现
### Chat-Room主题管理器
```python
# client/tui/theme_system.py - 主题系统
import json
import os
from typing import Dict, List, Optional, Any, Callable
from dataclasses import dataclass, asdict
from enum import Enum
import yaml
from pathlib import Path
class ThemeType(Enum):
"""主题类型"""
BUILT_IN = "built_in"
USER_CUSTOM = "user_custom"
DYNAMIC = "dynamic"
ADAPTIVE = "adaptive"
@dataclass
class ColorScheme:
"""颜色方案"""
# 主要颜色
primary: str
secondary: str
accent: str
# 背景颜色
background: str
surface: str
overlay: str
# 文本颜色
text_primary: str
text_secondary: str
text_disabled: str
# 状态颜色
success: str
warning: str
error: str
info: str
# 边框颜色
border: str
border_focus: str
border_hover: str
def to_css_variables(self) -> Dict[str, str]:
"""转换为CSS变量"""
return {
f"--{key.replace('_', '-')}": value
for key, value in asdict(self).items()
}
@dataclass
class Typography:
"""字体排版"""
font_family: str = "monospace"
font_size_xs: int = 10
font_size_sm: int = 12
font_size_md: int = 14
font_size_lg: int = 16
font_size_xl: int = 20
line_height: float = 1.4
letter_spacing: float = 0.0
@dataclass
class Spacing:
"""间距系统"""
xs: int = 1
sm: int = 2
md: int = 4
lg: int = 8
xl: int = 16
xxl: int = 32
@dataclass
class BorderRadius:
"""圆角系统"""
none: int = 0
sm: int = 2
md: int = 4
lg: int = 8
full: int = 999
@dataclass
class Animation:
"""动画配置"""
duration_fast: float = 0.1
duration_normal: float = 0.2
duration_slow: float = 0.3
easing: str = "ease-in-out"
@dataclass
class Theme:
"""主题定义"""
name: str
display_name: str
description: str
author: str
version: str
theme_type: ThemeType
# 设计系统
colors: ColorScheme
typography: Typography
spacing: Spacing
border_radius: BorderRadius
animation: Animation
# 自定义CSS
custom_css: str = ""
# 元数据
preview_image: Optional[str] = None
tags: List[str] = None
def __post_init__(self):
if self.tags is None:
self.tags = []
def to_css(self) -> str:
"""生成CSS样式"""
css_parts = []
# CSS变量定义
css_parts.append(":root {")
# 颜色变量
for key, value in self.colors.to_css_variables().items():
css_parts.append(f" {key}: {value};")
# 字体变量
css_parts.append(f" --font-family: {self.typography.font_family};")
css_parts.append(f" --font-size-xs: {self.typography.font_size_xs}px;")
css_parts.append(f" --font-size-sm: {self.typography.font_size_sm}px;")
css_parts.append(f" --font-size-md: {self.typography.font_size_md}px;")
css_parts.append(f" --font-size-lg: {self.typography.font_size_lg}px;")
css_parts.append(f" --font-size-xl: {self.typography.font_size_xl}px;")
css_parts.append(f" --line-height: {self.typography.line_height};")
# 间距变量
css_parts.append(f" --spacing-xs: {self.spacing.xs};")
css_parts.append(f" --spacing-sm: {self.spacing.sm};")
css_parts.append(f" --spacing-md: {self.spacing.md};")
css_parts.append(f" --spacing-lg: {self.spacing.lg};")
css_parts.append(f" --spacing-xl: {self.spacing.xl};")
css_parts.append(f" --spacing-xxl: {self.spacing.xxl};")
# 圆角变量
css_parts.append(f" --border-radius-sm: {self.border_radius.sm};")
css_parts.append(f" --border-radius-md: {self.border_radius.md};")
css_parts.append(f" --border-radius-lg: {self.border_radius.lg};")
# 动画变量
css_parts.append(f" --animation-duration-fast: {self.animation.duration_fast}s;")
css_parts.append(f" --animation-duration-normal: {self.animation.duration_normal}s;")
css_parts.append(f" --animation-duration-slow: {self.animation.duration_slow}s;")
css_parts.append(f" --animation-easing: {self.animation.easing};")
css_parts.append("}")
# 添加自定义CSS
if self.custom_css:
css_parts.append("")
css_parts.append(self.custom_css)
return "\n".join(css_parts)
class ThemeManager:
"""
主题管理器
功能:
1. 主题的加载、保存和管理
2. 动态主题切换
3. 主题预览和自定义
4. 主题的导入导出
"""
def __init__(self, themes_dir: str = "themes"):
self.themes_dir = Path(themes_dir)
self.themes_dir.mkdir(exist_ok=True)
# 主题存储
self.themes: Dict[str, Theme] = {}
self.current_theme: Optional[Theme] = None
# 主题变更回调
self.theme_change_callbacks: List[Callable[[Theme], None]] = []
# 初始化内置主题
self._init_built_in_themes()
# 加载用户主题
self._load_user_themes()
def _init_built_in_themes(self):
"""初始化内置主题"""
# 深色主题
dark_theme = Theme(
name="dark",
display_name="深色主题",
description="经典的深色主题,适合长时间使用",
author="Chat-Room Team",
version="1.0.0",
theme_type=ThemeType.BUILT_IN,
colors=ColorScheme(
primary="#00D4FF",
secondary="#FF6B6B",
accent="#4ECDC4",
background="#1A1A1A",
surface="#2D2D2D",
overlay="#3D3D3D",
text_primary="#FFFFFF",
text_secondary="#B0B0B0",
text_disabled="#666666",
success="#4CAF50",
warning="#FF9800",
error="#F44336",
info="#2196F3",
border="#404040",
border_focus="#00D4FF",
border_hover="#555555"
),
typography=Typography(),
spacing=Spacing(),
border_radius=BorderRadius(),
animation=Animation(),
tags=["dark", "classic", "professional"]
)
# 浅色主题
light_theme = Theme(
name="light",
display_name="浅色主题",
description="清新的浅色主题,适合白天使用",
author="Chat-Room Team",
version="1.0.0",
theme_type=ThemeType.BUILT_IN,
colors=ColorScheme(
primary="#0066CC",
secondary="#E91E63",
accent="#009688",
background="#FFFFFF",
surface="#F5F5F5",
overlay="#EEEEEE",
text_primary="#212121",
text_secondary="#757575",
text_disabled="#BDBDBD",
success="#4CAF50",
warning="#FF9800",
error="#F44336",
info="#2196F3",
border="#E0E0E0",
border_focus="#0066CC",
border_hover="#CCCCCC"
),
typography=Typography(),
spacing=Spacing(),
border_radius=BorderRadius(),
animation=Animation(),
tags=["light", "clean", "modern"]
)
# 高对比度主题
high_contrast_theme = Theme(
name="high_contrast",
display_name="高对比度主题",
description="高对比度主题,提升可访问性",
author="Chat-Room Team",
version="1.0.0",
theme_type=ThemeType.BUILT_IN,
colors=ColorScheme(
primary="#FFFF00",
secondary="#FF0000",
accent="#00FF00",
background="#000000",
surface="#333333",
overlay="#666666",
text_primary="#FFFFFF",
text_secondary="#CCCCCC",
text_disabled="#999999",
success="#00FF00",
warning="#FFFF00",
error="#FF0000",
info="#00FFFF",
border="#FFFFFF",
border_focus="#FFFF00",
border_hover="#CCCCCC"
),
typography=Typography(),
spacing=Spacing(),
border_radius=BorderRadius(),
animation=Animation(),
tags=["accessibility", "high-contrast", "a11y"]
)
# 注册内置主题
self.themes["dark"] = dark_theme
self.themes["light"] = light_theme
self.themes["high_contrast"] = high_contrast_theme
# 设置默认主题
self.current_theme = dark_theme
def _load_user_themes(self):
"""加载用户主题"""
for theme_file in self.themes_dir.glob("*.yaml"):
try:
with open(theme_file, 'r', encoding='utf-8') as f:
theme_data = yaml.safe_load(f)
theme = self._parse_theme_data(theme_data)
if theme:
self.themes[theme.name] = theme
except Exception as e:
print(f"加载主题文件失败 {theme_file}: {e}")
def _parse_theme_data(self, data: Dict[str, Any]) -> Optional[Theme]:
"""解析主题数据"""
try:
# 解析颜色方案
colors_data = data.get("colors", {})
colors = ColorScheme(**colors_data)
# 解析字体排版
typography_data = data.get("typography", {})
typography = Typography(**typography_data)
# 解析间距
spacing_data = data.get("spacing", {})
spacing = Spacing(**spacing_data)
# 解析圆角
border_radius_data = data.get("border_radius", {})
border_radius = BorderRadius(**border_radius_data)
# 解析动画
animation_data = data.get("animation", {})
animation = Animation(**animation_data)
# 创建主题对象
theme = Theme(
name=data["name"],
display_name=data["display_name"],
description=data.get("description", ""),
author=data.get("author", "Unknown"),
version=data.get("version", "1.0.0"),
theme_type=ThemeType(data.get("theme_type", "user_custom")),
colors=colors,
typography=typography,
spacing=spacing,
border_radius=border_radius,
animation=animation,
custom_css=data.get("custom_css", ""),
tags=data.get("tags", [])
)
return theme
except Exception as e:
print(f"解析主题数据失败: {e}")
return None
def get_theme(self, theme_name: str) -> Optional[Theme]:
"""获取主题"""
return self.themes.get(theme_name)
def get_all_themes(self) -> List[Theme]:
"""获取所有主题"""
return list(self.themes.values())
def get_themes_by_type(self, theme_type: ThemeType) -> List[Theme]:
"""按类型获取主题"""
return [theme for theme in self.themes.values() if theme.theme_type == theme_type]
def get_themes_by_tag(self, tag: str) -> List[Theme]:
"""按标签获取主题"""
return [theme for theme in self.themes.values() if tag in theme.tags]
def set_current_theme(self, theme_name: str) -> bool:
"""设置当前主题"""
theme = self.get_theme(theme_name)
if not theme:
return False
old_theme = self.current_theme
self.current_theme = theme
# 触发主题变更回调
for callback in self.theme_change_callbacks:
try:
callback(theme)
except Exception as e:
print(f"主题变更回调失败: {e}")
return True
def get_current_theme(self) -> Optional[Theme]:
"""获取当前主题"""
return self.current_theme
def add_theme_change_callback(self, callback: Callable[[Theme], None]):
"""添加主题变更回调"""
self.theme_change_callbacks.append(callback)
def remove_theme_change_callback(self, callback: Callable[[Theme], None]):
"""移除主题变更回调"""
if callback in self.theme_change_callbacks:
self.theme_change_callbacks.remove(callback)
def save_theme(self, theme: Theme) -> bool:
"""保存主题到文件"""
try:
theme_file = self.themes_dir / f"{theme.name}.yaml"
# 转换为字典
theme_data = {
"name": theme.name,
"display_name": theme.display_name,
"description": theme.description,
"author": theme.author,
"version": theme.version,
"theme_type": theme.theme_type.value,
"colors": asdict(theme.colors),
"typography": asdict(theme.typography),
"spacing": asdict(theme.spacing),
"border_radius": asdict(theme.border_radius),
"animation": asdict(theme.animation),
"custom_css": theme.custom_css,
"tags": theme.tags
}
# 保存到文件
with open(theme_file, 'w', encoding='utf-8') as f:
yaml.dump(theme_data, f, default_flow_style=False, allow_unicode=True)
# 添加到主题列表
self.themes[theme.name] = theme
return True
except Exception as e:
print(f"保存主题失败: {e}")
return False
def delete_theme(self, theme_name: str) -> bool:
"""删除主题"""
theme = self.get_theme(theme_name)
if not theme:
return False
# 不能删除内置主题
if theme.theme_type == ThemeType.BUILT_IN:
return False
try:
# 删除文件
theme_file = self.themes_dir / f"{theme_name}.yaml"
if theme_file.exists():
theme_file.unlink()
# 从主题列表中移除
del self.themes[theme_name]
# 如果是当前主题,切换到默认主题
if self.current_theme and self.current_theme.name == theme_name:
self.set_current_theme("dark")
return True
except Exception as e:
print(f"删除主题失败: {e}")
return False
def export_theme(self, theme_name: str, export_path: str) -> bool:
"""导出主题"""
theme = self.get_theme(theme_name)
if not theme:
return False
try:
export_file = Path(export_path)
export_file.parent.mkdir(parents=True, exist_ok=True)
# 创建导出数据
export_data = {
"name": theme.name,
"display_name": theme.display_name,
"description": theme.description,
"author": theme.author,
"version": theme.version,
"theme_type": theme.theme_type.value,
"colors": asdict(theme.colors),
"typography": asdict(theme.typography),
"spacing": asdict(theme.spacing),
"border_radius": asdict(theme.border_radius),
"animation": asdict(theme.animation),
"custom_css": theme.custom_css,
"tags": theme.tags,
"exported_at": str(datetime.now()),
"exported_by": "Chat-Room Theme Manager"
}
# 保存到文件
with open(export_file, 'w', encoding='utf-8') as f:
yaml.dump(export_data, f, default_flow_style=False, allow_unicode=True)
return True
except Exception as e:
print(f"导出主题失败: {e}")
return False
def import_theme(self, import_path: str) -> bool:
"""导入主题"""
try:
with open(import_path, 'r', encoding='utf-8') as f:
theme_data = yaml.safe_load(f)
theme = self._parse_theme_data(theme_data)
if not theme:
return False
# 检查主题名冲突
if theme.name in self.themes:
# 生成新名称
base_name = theme.name
counter = 1
while f"{base_name}_{counter}" in self.themes:
counter += 1
theme.name = f"{base_name}_{counter}"
# 保存主题
return self.save_theme(theme)
except Exception as e:
print(f"导入主题失败: {e}")
return False
# 使用示例
def demo_theme_system():
"""主题系统演示"""
theme_manager = ThemeManager()
print("=== Chat-Room主题系统演示 ===")
# 列出所有主题
themes = theme_manager.get_all_themes()
print(f"可用主题数量: {len(themes)}")
for theme in themes:
print(f"- {theme.display_name} ({theme.name}) - {theme.description}")
# 切换主题
print("\n切换到浅色主题...")
success = theme_manager.set_current_theme("light")
print(f"切换结果: {'成功' if success else '失败'}")
# 获取当前主题
current = theme_manager.get_current_theme()
if current:
print(f"当前主题: {current.display_name}")
print(f"主要颜色: {current.colors.primary}")
# 生成CSS
if current:
css = current.to_css()
print(f"\nCSS样式长度: {len(css)} 字符")
print("CSS预览:")
print(css[:200] + "..." if len(css) > 200 else css)
if __name__ == "__main__":
demo_theme_system()
```
## 🎯 实践练习
### 练习1:动态主题生成
```python
class DynamicThemeGenerator:
"""
动态主题生成练习
要求:
1. 基于用户偏好自动生成主题
2. 支持颜色的智能搭配算法
3. 实现主题的实时预览功能
4. 提供主题的评分和推荐
"""
def generate_theme_from_color(self, base_color: str) -> Theme:
"""基于基础颜色生成主题"""
# TODO: 实现颜色搭配算法
pass
def analyze_user_preferences(self, user_id: int) -> Dict[str, Any]:
"""分析用户偏好"""
# TODO: 实现用户偏好分析
pass
```
### 练习2:主题动画系统
```python
class ThemeAnimationSystem:
"""
主题动画系统练习
要求:
1. 实现主题切换的平滑动画
2. 支持颜色渐变和过渡效果
3. 添加主题预览的动画演示
4. 优化动画性能和用户体验
"""
def animate_theme_transition(self, from_theme: Theme, to_theme: Theme):
"""主题切换动画"""
# TODO: 实现主题切换动画
pass
def create_color_transition(self, from_color: str, to_color: str, duration: float):
"""颜色过渡动画"""
# TODO: 实现颜色过渡
pass
```
## ✅ 学习检查
完成本章学习后,请确认您能够:
- [ ] 理解主题系统的设计原理和架构
- [ ] 创建和管理自定义主题
- [ ] 实现动态主题切换功能
- [ ] 使用CSS变量和样式系统
- [ ] 设计主题的导入导出功能
- [ ] 完成实践练习
## 📚 下一步
主题系统设计掌握后,请继续学习:
- [第9章:管理员系统](../13-admin-system/permission-model.md)
---
**恭喜!您已经完成了用户界面设计的学习!** 🎭