Files
opsxsql/SQL平台评估与优化/sql_platform_optimization_sec04.md
T
logan 746a000e8a feat: 全面UI优化 — 5套主题系统 + 设计体系 + 菜单重组 + 页面优化
1. 主题系统扩展 (5套主题)
   - ocean-deep(深海蓝) / light-professional(明亮白) / midnight-purple(午夜紫)
   - pine-green(松林绿) / orange-vitality(活力橙)
   - useTheme hook 重写支持5主题 + 向后兼容
   - AppearanceSettings 5主题预览卡片

2. 设计体系 (index.css)
   - 按钮系统: dg-btn-primary/secondary/ghost/danger + 尺寸变体
   - 徽章系统: dg-badge-* (8色)
   - 卡片/表格: dg-card-section, dg-table-row, dg-table-header
   - 状态点: dg-status-dot-* (4色)
   - 动效关键帧: slideInLeft, scaleIn

3. 菜单重组 (menu.ts)
   - 5分组: 概览/数据源/工单/权限/系统
   - 6个图标更新: Code2/FileEdit/ShieldCheck/FolderLock/ScrollText/Timer
   - 新增 permission 权限码字段

4. Layout 侧边栏增强
   - 左侧渐变指示条 + 发光效果
   - hover 微动画 + 图标变色
   - 面包屑导航头部
   - 主题切换循环5主题

5. 核心页面优化
   - TicketsPage: 设计系统类 + 颜色对齐
   - QueriesPage: 去Card依赖 + dg-table/dg-btn统一
   - Breadcrumb组件 + useBreadcrumb hook
2026-05-27 01:59:08 +08:00

742 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 4. 五套登录后主题设计方案
为满足不同用户群体在不同使用场景下的视觉偏好,本章为 SQL-Platform 设计五套完整的登录后主题方案。每套主题均包含完整的 CSS 变量规范、色彩心理学依据及典型应用场景说明。五套主题涵盖从专业沉稳到活力温暖的全方位视觉谱系,确保平台在各类企业环境中都能提供最佳的用户体验。
---
### 4.1 主题一「深蓝专业」— 默认深色增强版
**设计定位**:深蓝专业主题是在现有深色主题基础上的全面增强版本,定位为平台的默认主题。设计目标是在保持深色模式优势(降低眼部疲劳、突出内容)的同时,通过更纯正深邃的蓝色调提升整体品质感,并强化视觉层次与可读性。
**色彩设计理念**:该主题摒弃了原主题中偏灰的杂色倾向,将背景色收敛到更纯正的深蓝谱系。主背景色 `#070d19` 接近纯黑但带有微弱的蓝色相,营造出深邃而稳定的基底。次背景色 `#0f172a` 参考了 Slate 色板中最深沉的蓝色调,既保证了与主背景的层次区分,又维持了蓝色谱系的纯粹性。文字色彩采用 `#f8fafc`(极亮蓝白)作为主文字色,相比纯白色更具温润感,长时间阅读不易产生刺眼感。次文字色 `#94a3b8` 提供了充分的对比度,确保辅助信息的可读性。
**强调色体系**:蓝色系强调色选用 `#3b82f6`(Tailwind Blue 500),这是一个饱和度适中、辨识度高的蓝色,在深色背景上呈现明亮但不刺眼的视觉效果。状态色(成功、错误、警告)均经过深色背景适配,确保各色彩在深蓝底上都有足够的明度对比。
**适用场景**:作为默认主题,适用于绝大多数使用场景,特别适合技术团队日常操作、数据库管理、权限配置等需要长时间专注的工作界面。
```css
/* === 主题一:深蓝专业 ocean-deep === */
[data-theme="ocean-deep"] {
/* shadcn/ui 基础变量 */
--background: 222 47% 4%;
--foreground: 210 40% 98%;
--card: 222 47% 7%;
--card-foreground: 210 40% 98%;
--popover: 222 47% 7%;
--popover-foreground: 210 40% 98%;
--primary: 217 91% 60%;
--primary-foreground: 0 0% 100%;
--secondary: 217 33% 17%;
--secondary-foreground: 210 40% 98%;
--muted: 217 33% 17%;
--muted-foreground: 215 20% 65%;
--accent: 217 33% 17%;
--accent-foreground: 210 40% 98%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 217 33% 17%;
--input: 217 33% 17%;
--ring: 217 91% 60%;
--radius: 0.5rem;
/* DataGuard 自定义变量 */
--dg-bg-primary: #070d19;
--dg-bg-secondary: #0f172a;
--dg-bg-tertiary: #172033;
--dg-bg-hover: #233049;
--dg-border: #1e293b;
--dg-border-light: #172033;
--dg-text-primary: #f8fafc;
--dg-text-secondary: #94a3b8;
--dg-text-muted: #64748b;
--dg-blue: #3b82f6;
--dg-green: #22c55e;
--dg-red: #ef4444;
--dg-orange: #f97316;
--dg-purple: #8b5cf6;
--dg-cyan: #06b6d4;
--dg-yellow: #eab308;
--dg-gradient-start: #3b82f6;
--dg-gradient-end: #2563eb;
}
```
---
### 4.2 主题二「浅色商务」— 企业办公风
**设计定位**:浅色商务主题面向白天办公环境优化,采用温暖的灰白色调替代常见的冷白背景,营造舒适、专业且不刺眼的工作界面。该主题适合不习惯深色模式的用户,或在明亮办公室环境中使用。
**色彩设计理念**:背景色摒弃了纯白的冷峻感,主背景色 `#fafaf9` 选用带有微弱暖色相的" zinc white",在显示器上呈现柔和的纸质感。次背景色 `#f5f5f4` 和三级背景色 `#e7e5e4` 构成自然的层次递进,避免了传统浅色主题中背景层次过于平淡的问题。边框色 `#d6d3d1` 同样带有暖色调,使界面分割更加自然和谐。文字色彩方面,主文字色 `#1c1917` 是带有微弱棕色相的深黑色,相比纯黑在暖白背景上对比度更舒适,避免了"黑字白纸"的刺眼感。
**强调色体系**:强调色选用稳重的 `#2563eb`(Tailwind Blue 600),这是一个偏深的标准蓝色,传达专业、可信赖的企业形象。状态色均调整为适合浅色背景的版本,成功绿 `#16a34a`、错误红 `#dc2626`、警告橙 `#ca8a04` 在浅色背景上均保持良好的可读性。
**适用场景**:适合白天办公环境、管理人员日常审批查看、客户演示投屏等场景。浅色界面在投影设备上的显示效果通常优于深色界面,因此该主题也是会议室演示的首选。
```css
/* === 主题二:浅色商务 light-professional === */
[data-theme="light-professional"] {
/* shadcn/ui 基础变量 */
--background: 40 9% 98%;
--foreground: 24 10% 10%;
--card: 40 9% 96%;
--card-foreground: 24 10% 10%;
--popover: 40 9% 96%;
--popover-foreground: 24 10% 10%;
--primary: 221 83% 53%;
--primary-foreground: 0 0% 100%;
--secondary: 40 6% 90%;
--secondary-foreground: 24 10% 10%;
--muted: 40 6% 90%;
--muted-foreground: 25 5% 45%;
--accent: 40 6% 90%;
--accent-foreground: 24 10% 10%;
--destructive: 0 72% 51%;
--destructive-foreground: 0 0% 100%;
--border: 24 6% 83%;
--input: 24 6% 83%;
--ring: 221 83% 53%;
--radius: 0.5rem;
/* DataGuard 自定义变量 */
--dg-bg-primary: #fafaf9;
--dg-bg-secondary: #f5f5f4;
--dg-bg-tertiary: #e7e5e4;
--dg-bg-hover: #d6d3d1;
--dg-border: #d6d3d1;
--dg-border-light: #e7e5e4;
--dg-text-primary: #1c1917;
--dg-text-secondary: #78716c;
--dg-text-muted: #a8a29e;
--dg-blue: #2563eb;
--dg-green: #16a34a;
--dg-red: #dc2626;
--dg-orange: #ea580c;
--dg-purple: #7c3aed;
--dg-cyan: #0891b2;
--dg-yellow: #ca8a04;
--dg-gradient-start: #2563eb;
--dg-gradient-end: #4f46e5;
}
```
---
### 4.3 主题三「午夜紫韵」— 高端紫色调
**设计定位**:午夜紫韵主题以深紫黑色为基底,搭配明亮的紫色渐变强调色,营造高端、神秘且富有科技感的视觉氛围。该主题适合作为给客户演示时的展示主题,能够传达产品的技术领先性和品质感。
**色彩设计理念**:主背景色 `#0a0612` 是带有明显紫色相的极深色,接近"午夜黑"的概念,为整个界面奠定了神秘而高贵的基调。次背景色 `#130c21` 和三级背景色 `#1c1330` 沿紫色谱系逐步提亮,形成清晰的层次结构。悬停背景色 `#2d1f4a` 是一个中等深度的紫色,在交互时能够产生明显的视觉反馈,同时保持整体色调的一致性。文字色彩方面,主文字色 `#faf5ff` 带有微弱的紫色调,使文字在深色背景上更加柔和雅致。
**强调色体系**:该主题的核心亮点在于紫色渐变强调体系。渐变起点 `#9333ea`(Tailwind Purple 600)到渐变终点 `#c084fc`(Tailwind Purple 400)构成了一段明亮且富有活力的紫色渐变,用于品牌标识、主要按钮和重要数据高亮。蓝色调整为偏紫的靛蓝色 `#818cf8`,绿色调整为翠绿色 `#34d399`,红色调整为玫红色 `#fb7185`,所有状态色都经过紫色调环境的协调适配。
**适用场景**:产品发布会演示、客户汇报展示、高端企业部署、技术峰会展台等需要彰显产品品质的正式场合。
```css
/* === 主题三:午夜紫韵 midnight-purple === */
[data-theme="midnight-purple"] {
/* shadcn/ui 基础变量 */
--background: 264 56% 4%;
--foreground: 280 100% 98%;
--card: 264 48% 8%;
--card-foreground: 280 100% 98%;
--popover: 264 48% 8%;
--popover-foreground: 280 100% 98%;
--primary: 270 91% 65%;
--primary-foreground: 0 0% 100%;
--secondary: 264 40% 20%;
--secondary-foreground: 280 100% 98%;
--muted: 264 40% 20%;
--muted-foreground: 280 30% 60%;
--accent: 264 40% 20%;
--accent-foreground: 280 100% 98%;
--destructive: 348 100% 72%;
--destructive-foreground: 264 56% 4%;
--border: 264 40% 20%;
--input: 264 40% 20%;
--ring: 270 91% 65%;
--radius: 0.5rem;
/* DataGuard 自定义变量 */
--dg-bg-primary: #0a0612;
--dg-bg-secondary: #130c21;
--dg-bg-tertiary: #1c1330;
--dg-bg-hover: #2d1f4a;
--dg-border: #2d1f4a;
--dg-border-light: #1c1330;
--dg-text-primary: #faf5ff;
--dg-text-secondary: #b794c7;
--dg-text-muted: #7c5a8a;
--dg-blue: #818cf8;
--dg-green: #34d399;
--dg-red: #fb7185;
--dg-orange: #fb923c;
--dg-purple: #c084fc;
--dg-cyan: #22d3ee;
--dg-yellow: #facc15;
--dg-gradient-start: #9333ea;
--dg-gradient-end: #c084fc;
}
```
---
### 4.4 主题四「松绿自然」— 护眼绿调
**设计定位**:松绿自然主题面向长时间使用平台的开发团队设计,以墨绿色为基调,利用绿色光谱对人眼最为友好的特性,最大程度降低长时间注视屏幕带来的视觉疲劳。
**色彩设计理念**:该主题的核心设计原则是"护眼优先"。主背景色 `#0a1210` 是带有明显绿色相的极深色,类似深夜松林深处的颜色,极低明度确保了基础视觉负荷的最小化。次背景色 `#111f1a`(深墨绿)和三级背景色 `#192c24`(墨绿灰)沿绿色谱系递进,在保持低明度的同时构建清晰的视觉层次。悬停背景色 `#253d32` 选用了中等深度的松绿色,交互时呈现自然、柔和的状态变化。边框色 `#253d32` 与悬停色共用,确保交互反馈与界面分割的视觉统一。主文字色 `#f0fdf4` 是极淡的薄荷白,在墨绿背景上明亮而不刺眼,具有极佳的阅读舒适度。
**强调色体系**:绿色系强调色选用 `#4ade80`(Tailwind Green 400),这是一个明亮、清新的绿色,在墨绿暗底上如同松针上的晨露般醒目。信息色 `#2dd4bf` 偏向青绿色,与主色调完美协调。蓝色调整为天蓝色 `#38bdf8`,在绿调环境中起到色彩平衡的作用,避免界面过于单调。
**适用场景**:开发团队日常长时间使用、DBA 持续监控数据库状态、运维人员驻屏值班等需要长时间面对屏幕的工作场景。
```css
/* === 主题四:松绿自然 pine-green === */
[data-theme="pine-green"] {
/* shadcn/ui 基础变量 */
--background: 150 25% 5%;
--foreground: 140 60% 97%;
--card: 150 25% 9%;
--card-foreground: 140 60% 97%;
--popover: 150 25% 9%;
--popover-foreground: 140 60% 97%;
--primary: 142 71% 45%;
--primary-foreground: 150 25% 5%;
--secondary: 150 20% 18%;
--secondary-foreground: 140 60% 97%;
--muted: 150 20% 18%;
--muted-foreground: 150 20% 55%;
--accent: 150 20% 18%;
--accent-foreground: 140 60% 97%;
--destructive: 0 90% 72%;
--destructive-foreground: 150 25% 5%;
--border: 150 20% 18%;
--input: 150 20% 18%;
--ring: 142 71% 45%;
--radius: 0.5rem;
/* DataGuard 自定义变量 */
--dg-bg-primary: #0a1210;
--dg-bg-secondary: #111f1a;
--dg-bg-tertiary: #192c24;
--dg-bg-hover: #253d32;
--dg-border: #253d32;
--dg-border-light: #192c24;
--dg-text-primary: #f0fdf4;
--dg-text-secondary: #86b8a0;
--dg-text-muted: #5a7c6b;
--dg-blue: #38bdf8;
--dg-green: #4ade80;
--dg-red: #f87171;
--dg-orange: #fb923c;
--dg-purple: #c084fc;
--dg-cyan: #2dd4bf;
--dg-yellow: #facc15;
--dg-gradient-start: #059669;
--dg-gradient-end: #10b981;
}
```
---
### 4.5 主题五「橙光活力」— 活力暖色调
**设计定位**:橙光活力主题以深棕暗橙为基底,搭配明亮的橙色渐变,传递温暖、积极、富有活力的品牌情绪。该主题面向年轻化团队和创新型企业,打破传统数据安全平台冰冷刻板的印象。
**色彩设计理念**:主背景色 `#110d08` 是带有明显棕色相的极深色,类似深烘焙咖啡豆的颜色,温暖而沉稳。次背景色 `#1d1610` 和三级背景色 `#2b1f16` 沿棕色谱系递进,营造出如同暖色调咖啡馆般的舒适感。悬停背景色 `#3d2d20` 是一个中等深度的暖棕色,交互反馈如同木材被温暖光线照亮。文字色彩方面,主文字色 `#fef7ed` 是极淡的暖白色,如同烛光下的纸张,在暗棕底上既明亮又温暖。
**强调色体系**:核心渐变从 `#ea580c`(Tailwind Orange 600)到 `#f59e0b`(Tailwind Amber 500),这是一段充满能量感的橙黄渐变,用于重要按钮、品牌标识和数据高亮时极具视觉冲击力。蓝色调整为柔和的 `#60a5fa`,在暖色调环境中起到中和平衡的作用。紫色调整为偏暖的 `#a78bfa`,与整体暖调和谐共存。
**适用场景**:互联网创业公司、年轻化的技术团队、创意型企业、产品内部分享会等追求活力与创新氛围的使用场景。
```css
/* === 主题五:橙光活力 orange-vitality === */
[data-theme="orange-vitality"] {
/* shadcn/ui 基础变量 */
--background: 30 35% 5%;
--foreground: 30 100% 97%;
--card: 30 30% 9%;
--card-foreground: 30 100% 97%;
--popover: 30 30% 9%;
--popover-foreground: 30 100% 97%;
--primary: 25 95% 53%;
--primary-foreground: 0 0% 100%;
--secondary: 30 25% 18%;
--secondary-foreground: 30 100% 97%;
--muted: 30 25% 18%;
--muted-foreground: 30 25% 55%;
--accent: 30 25% 18%;
--accent-foreground: 30 100% 97%;
--destructive: 0 80% 70%;
--destructive-foreground: 30 35% 5%;
--border: 30 25% 18%;
--input: 30 25% 18%;
--ring: 25 95% 53%;
--radius: 0.5rem;
/* DataGuard 自定义变量 */
--dg-bg-primary: #110d08;
--dg-bg-secondary: #1d1610;
--dg-bg-tertiary: #2b1f16;
--dg-bg-hover: #3d2d20;
--dg-border: #3d2d20;
--dg-border-light: #2b1f16;
--dg-text-primary: #fef7ed;
--dg-text-secondary: #d4a574;
--dg-text-muted: #9c7a54;
--dg-blue: #60a5fa;
--dg-green: #4ade80;
--dg-red: #f87171;
--dg-orange: #fb923c;
--dg-purple: #a78bfa;
--dg-cyan: #2dd4bf;
--dg-yellow: #fcd34d;
--dg-gradient-start: #ea580c;
--dg-gradient-end: #f59e0b;
}
```
---
### 4.6 各主题 CSS 变量规范对比表
下表汇总了五套主题的全部 DataGuard 自定义 CSS 变量值,便于开发实现时快速查阅与对比。
| CSS 变量 | 深蓝专业 | 浅色商务 | 午夜紫韵 | 松绿自然 | 橙光活力 |
|:---|:---|:---|:---|:---|:---|
| `--dg-bg-primary` | `#070d19` | `#fafaf9` | `#0a0612` | `#0a1210` | `#110d08` |
| `--dg-bg-secondary` | `#0f172a` | `#f5f5f4` | `#130c21` | `#111f1a` | `#1d1610` |
| `--dg-bg-tertiary` | `#172033` | `#e7e5e4` | `#1c1330` | `#192c24` | `#2b1f16` |
| `--dg-bg-hover` | `#233049` | `#d6d3d1` | `#2d1f4a` | `#253d32` | `#3d2d20` |
| `--dg-border` | `#1e293b` | `#d6d3d1` | `#2d1f4a` | `#253d32` | `#3d2d20` |
| `--dg-border-light` | `#172033` | `#e7e5e4` | `#1c1330` | `#192c24` | `#2b1f16` |
| `--dg-text-primary` | `#f8fafc` | `#1c1917` | `#faf5ff` | `#f0fdf4` | `#fef7ed` |
| `--dg-text-secondary` | `#94a3b8` | `#78716c` | `#b794c7` | `#86b8a0` | `#d4a574` |
| `--dg-text-muted` | `#64748b` | `#a8a29e` | `#7c5a8a` | `#5a7c6b` | `#9c7a54` |
| `--dg-blue` | `#3b82f6` | `#2563eb` | `#818cf8` | `#38bdf8` | `#60a5fa` |
| `--dg-green` | `#22c55e` | `#16a34a` | `#34d399` | `#4ade80` | `#4ade80` |
| `--dg-red` | `#ef4444` | `#dc2626` | `#fb7185` | `#f87171` | `#f87171` |
| `--dg-orange` | `#f97316` | `#ea580c` | `#fb923c` | `#fb923c` | `#fb923c` |
| `--dg-purple` | `#8b5cf6` | `#7c3aed` | `#c084fc` | `#c084fc` | `#a78bfa` |
| `--dg-cyan` | `#06b6d4` | `#0891b2` | `#22d3ee` | `#2dd4bf` | `#2dd4bf` |
| `--dg-yellow` | `#eab308` | `#ca8a04` | `#facc15` | `#facc15` | `#fcd34d` |
| `--dg-gradient-start` | `#3b82f6` | `#2563eb` | `#9333ea` | `#059669` | `#ea580c` |
| `--dg-gradient-end` | `#2563eb` | `#4f46e5` | `#c084fc` | `#10b981` | `#f59e0b` |
**shadcn/ui 基础变量对比表**
| HSL 变量 | 深蓝专业 | 浅色商务 | 午夜紫韵 | 松绿自然 | 橙光活力 |
|:---|:---|:---|:---|:---|:---|
| `--background` | `222 47% 4%` | `40 9% 98%` | `264 56% 4%` | `150 25% 5%` | `30 35% 5%` |
| `--foreground` | `210 40% 98%` | `24 10% 10%` | `280 100% 98%` | `140 60% 97%` | `30 100% 97%` |
| `--card` | `222 47% 7%` | `40 9% 96%` | `264 48% 8%` | `150 25% 9%` | `30 30% 9%` |
| `--primary` | `217 91% 60%` | `221 83% 53%` | `270 91% 65%` | `142 71% 45%` | `25 95% 53%` |
| `--secondary` | `217 33% 17%` | `40 6% 90%` | `264 40% 20%` | `150 20% 18%` | `30 25% 18%` |
| `--muted` | `217 33% 17%` | `40 6% 90%` | `264 40% 20%` | `150 20% 18%` | `30 25% 18%` |
| `--muted-foreground` | `215 20% 65%` | `25 5% 45%` | `280 30% 60%` | `150 20% 55%` | `30 25% 55%` |
| `--destructive` | `0 84% 60%` | `0 72% 51%` | `348 100% 72%` | `0 90% 72%` | `0 80% 70%` |
| `--border` | `217 33% 17%` | `24 6% 83%` | `264 40% 20%` | `150 20% 18%` | `30 25% 18%` |
| `--ring` | `217 91% 60%` | `221 83% 53%` | `270 91% 65%` | `142 71% 45%` | `25 95% 53%` |
---
### 4.7 主题切换实现方案
#### 4.7.1 CSS 变量组织方式
五套主题的 CSS 变量采用 **"分散定义、统一命名空间"** 的组织策略。每套主题的所有变量定义在一个独立的 `data-theme` 属性选择器下,避免变量冲突和覆盖混乱。
**文件结构建议**:
```
frontend/src/styles/
├── themes/
│ ├── index.css /* 主题入口文件,导入所有主题 */
│ ├── ocean-deep.css /* 主题一:深蓝专业 */
│ ├── light-professional.css /* 主题二:浅色商务 */
│ ├── midnight-purple.css /* 主题三:午夜紫韵 */
│ ├── pine-green.css /* 主题四:松绿自然 */
│ └── orange-vitality.css /* 主题五:橙光活力 */
└── index.css /* 现有主样式文件 */
```
**入口文件 `themes/index.css` 内容**:
```css
/* ===== SQL-Platform 主题系统入口 ===== */
/* 按加载顺序:默认主题(深蓝专业)无需 data-theme 属性 */
@import "./ocean-deep.css";
@import "./light-professional.css";
@import "./midnight-purple.css";
@import "./pine-green.css";
@import "./orange-vitality.css";
```
> **实现说明**:`ocean-deep.css` 同时提供 `:root` 级别的默认变量(确保无 `data-theme` 属性时回退到默认主题)和 `[data-theme="ocean-deep"]` 选择器下的显式定义,保证其他主题切换回来时变量能正确覆盖。
#### 4.7.2 useTheme Hook 增强实现
现有的 `useTheme` hook 需要从双值切换(dark/light)扩展为多主题选择。以下是增强后的实现方案:
```typescript
// frontend/src/hooks/useTheme.ts
import { useState, useEffect, useCallback } from 'react';
/** 支持的主题标识列表 */
export const THEMES = [
{ id: 'ocean-deep', label: '深蓝专业', description: '默认深色增强版' },
{ id: 'light-professional', label: '浅色商务', description: '企业办公风' },
{ id: 'midnight-purple', label: '午夜紫韵', description: '高端紫色调' },
{ id: 'pine-green', label: '松绿自然', description: '护眼绿调' },
{ id: 'orange-vitality', label: '橙光活力', description: '活力暖色调' },
] as const;
export type ThemeId = typeof THEMES[number]['id'];
const STORAGE_KEY = 'sqlp-theme-id';
const DEFAULT_THEME: ThemeId = 'ocean-deep';
/**
* 从 localStorage 读取保存的主题
*/
function getSavedTheme(): ThemeId {
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved && THEMES.some(t => t.id === saved)) {
return saved as ThemeId;
}
} catch {
// localStorage 不可用时忽略
}
return DEFAULT_THEME;
}
/**
* 检测系统色彩偏好
*/
function getSystemPreference(): 'dark' | 'light' {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
/**
* 根据系统偏好映射默认主题
*/
function getDefaultBySystem(): ThemeId {
return getSystemPreference() === 'dark' ? 'ocean-deep' : 'light-professional';
}
export function useTheme() {
const [theme, setThemeState] = useState<ThemeId>(() => {
const saved = getSavedTheme();
return saved || getDefaultBySystem();
});
/**
* 切换主题的核心方法
*/
const setTheme = useCallback((newTheme: ThemeId) => {
setThemeState(newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
try {
localStorage.setItem(STORAGE_KEY, newTheme);
} catch {
// localStorage 不可用时静默处理
}
}, []);
/**
* 初始化主题:首次加载时应用
*/
useEffect(() => {
const saved = getSavedTheme();
const initial = saved || getDefaultBySystem();
document.documentElement.setAttribute('data-theme', initial);
setThemeState(initial);
}, []);
/**
* 监听系统主题变化,仅在用户未手动选择主题时自动切换
*/
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handler = (e: MediaQueryListEvent) => {
// 检查用户是否已手动设置过主题
const hasUserPreference = localStorage.getItem(STORAGE_KEY) !== null;
if (!hasUserPreference) {
const newTheme = e.matches ? 'ocean-deep' : 'light-professional';
setTheme(newTheme);
}
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, [setTheme]);
return { theme, setTheme, themes: THEMES };
}
```
#### 4.7.3 主题预览组件设计
AppearanceSettings 页面需新增主题选择区域,包含五张主题预览卡片。每张卡片展示主题的核心色彩样本和简要描述。
```typescript
// frontend/src/components/settings/ThemePreviewCard.tsx
import { Check } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { ThemeId } from '@/hooks/useTheme';
interface ThemePreviewCardProps {
themeId: ThemeId;
label: string;
description: string;
colors: {
bg: string;
secondary: string;
accent: string;
text: string;
};
isActive: boolean;
onClick: () => void;
}
export function ThemePreviewCard({
label,
description,
colors,
isActive,
onClick,
}: ThemePreviewCardProps) {
return (
<button
onClick={onClick}
className={cn(
"relative flex flex-col gap-2 rounded-lg border-2 p-3 transition-all duration-200",
"hover:shadow-md hover:-translate-y-0.5",
isActive
? "border-dg-blue ring-2 ring-dg-blue/20"
: "border-dg-border hover:border-dg-border-light"
)}
aria-pressed={isActive}
>
{/* 预览色块区域 */}
<div
className="h-16 w-full rounded-md flex items-end justify-end p-1.5 gap-1"
style={{ backgroundColor: colors.bg }}
>
<div
className="w-4 h-4 rounded-sm"
style={{ backgroundColor: colors.secondary }}
/>
<div
className="w-4 h-4 rounded-sm"
style={{ backgroundColor: colors.accent }}
/>
<div
className="w-4 h-4 rounded-sm"
style={{ backgroundColor: colors.text }}
/>
</div>
{/* 文字信息 */}
<div className="text-left">
<p className="text-sm font-medium text-dg-text-primary">{label}</p>
<p className="text-xs text-dg-text-muted">{description}</p>
</div>
{/* 选中标记 */}
{isActive && (
<div className="absolute -top-1.5 -right-1.5 w-5 h-5 bg-dg-blue rounded-full flex items-center justify-center">
<Check className="w-3 h-3 text-white" />
</div>
)}
</button>
);
}
```
**AppearanceSettings 页面中的主题选择区域布局**:
```tsx
// 在 AppearanceSettings 页面中使用
import { useTheme, THEMES } from '@/hooks/useTheme';
import { ThemePreviewCard } from '@/components/settings/ThemePreviewCard';
// 各主题预览色值配置
const THEME_PREVIEW_COLORS: Record<string, { bg: string; secondary: string; accent: string; text: string }> = {
'ocean-deep': { bg: '#070d19', secondary: '#0f172a', accent: '#3b82f6', text: '#f8fafc' },
'light-professional': { bg: '#fafaf9', secondary: '#f5f5f4', accent: '#2563eb', text: '#1c1917' },
'midnight-purple': { bg: '#0a0612', secondary: '#130c21', accent: '#c084fc', text: '#faf5ff' },
'pine-green': { bg: '#0a1210', secondary: '#111f1a', accent: '#4ade80', text: '#f0fdf4' },
'orange-vitality': { bg: '#110d08', secondary: '#1d1610', accent: '#fb923c', text: '#fef7ed' },
};
function ThemeSelector() {
const { theme: activeTheme, setTheme } = useTheme();
return (
<div className="space-y-3">
<h3 className="text-sm font-medium text-dg-text-primary">界面主题</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
{THEMES.map((t) => (
<ThemePreviewCard
key={t.id}
themeId={t.id}
label={t.label}
description={t.description}
colors={THEME_PREVIEW_COLORS[t.id]}
isActive={activeTheme === t.id}
onClick={() => setTheme(t.id)}
/>
))}
</div>
</div>
);
}
```
#### 4.7.4 localStorage 持久化方案
主题持久化遵循以下策略:
| 策略项 | 说明 |
|:---|:---|
| **存储键名** | `sqlp-theme-id`,与现有 `theme` 键分离,避免升级冲突 |
| **存储值** | 主题标识字符串,如 `ocean-deep`、`light-professional` |
| **回退逻辑** | 读取不到有效值时,检测 `prefers-color-scheme` 系统偏好,暗色默认 `ocean-deep`,亮色默认 `light-professional` |
| **版本兼容** | 首次升级时,若检测到旧版 `theme: "dark"` 键,自动迁移至 `ocean-deep`;`theme: "light"` 迁移至 `light-professional`,随后删除旧键 |
| **异常处理** | localStorage 不可用时(隐私模式、存储已满),降级为仅内存状态,不抛出错误 |
| **跨设备同步** | 通过 `storage` 事件监听同源其他标签页的主题变更,实现多标签页同步: |
```typescript
/**
* 跨标签页主题同步
*/
useEffect(() => {
const handler = (e: StorageEvent) => {
if (e.key === STORAGE_KEY && e.newValue) {
const newTheme = e.newValue as ThemeId;
if (THEMES.some(t => t.id === newTheme)) {
setThemeState(newTheme);
document.documentElement.setAttribute('data-theme', newTheme);
}
}
};
window.addEventListener('storage', handler);
return () => window.removeEventListener('storage', handler);
}, []);
```
#### 4.7.5 系统主题偏好检测
平台需响应操作系统的 `prefers-color-scheme` 媒体查询,提供智能化的默认主题推荐。
**检测逻辑**:
```typescript
// 检测系统暗色/浅色偏好
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const prefersLight = window.matchMedia('(prefers-color-scheme: light)').matches;
```
**偏好映射规则**:
| 系统偏好 | 默认推荐主题 | 推荐逻辑 |
|:---|:---|:---|
| `prefers-color-scheme: dark` | **深蓝专业** (`ocean-deep`) | 暗色系统偏好用户通常偏好深色界面 |
| `prefers-color-scheme: light` | **浅色商务** (`light-professional`) | 亮色系统偏好用户通常偏好浅色界面 |
| 未指定(no-preference) | **深蓝专业** (`ocean-deep`) | 默认采用平台默认主题 |
**动态响应机制**:
```typescript
/**
* 系统主题偏好动态响应
* 仅在用户未手动设置主题时自动跟随系统
*/
useEffect(() => {
const darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
const lightQuery = window.matchMedia('(prefers-color-scheme: light)');
const handleChange = () => {
const hasUserPreference = localStorage.getItem(STORAGE_KEY) !== null;
if (hasUserPreference) return; // 用户已手动选择,不自动切换
if (darkQuery.matches) {
setTheme('ocean-deep');
} else if (lightQuery.matches) {
setTheme('light-professional');
}
};
darkQuery.addEventListener('change', handleChange);
lightQuery.addEventListener('change', handleChange);
return () => {
darkQuery.removeEventListener('change', handleChange);
lightQuery.removeEventListener('change', handleChange);
};
}, [setTheme]);
```
**重要原则**:系统偏好仅影响**首次访问**或**清除 localStorage 后**的默认主题选择。一旦用户手动选择了主题,该选择将被持久化并优先于系统偏好,避免在用户主动选择后因系统时间变化(如 macOS 自动切换暗色模式)导致界面意外变更。
#### 4.7.6 切换过渡动效
主题切换时应提供平滑的视觉过渡,避免色彩突变带来的不适感。
```css
/* 全局主题过渡效果 —— 添加到 index.css */
html {
/* 对所有 CSS 变量变化应用 200ms 缓动过渡 */
transition: background-color 200ms ease, color 200ms ease;
}
html *, html *::before, html *::after {
/* 谨慎使用:仅对关键属性过渡,避免性能问题 */
transition: background-color 200ms ease,
border-color 200ms ease,
color 150ms ease;
}
/* 可选:切换瞬间的淡入效果 */
[data-theme] {
animation: themeFadeIn 200ms ease;
}
@keyframes themeFadeIn {
from { opacity: 0.92; }
to { opacity: 1; }
}
```
**性能优化建议**:全局 `*` 过渡规则可能影响大型列表渲染性能。生产环境中建议仅对需要过渡的元素添加过渡规则,或通过 JavaScript 在切换主题时临时添加过渡 class,切换完成后再移除:
```typescript
/**
* 带过渡动画的主题切换
*/
const setThemeWithTransition = useCallback((newTheme: ThemeId) => {
const root = document.documentElement;
root.classList.add('theme-transitioning');
setTheme(newTheme);
// 300ms 后移除过渡 class
setTimeout(() => {
root.classList.remove('theme-transitioning');
}, 300);
}, [setTheme]);
```
```css
/* 仅在切换时启用过渡 */
html.theme-transitioning,
html.theme-transitioning * {
transition: background-color 200ms ease,
border-color 200ms ease,
color 150ms ease !important;
}
```
以上实现方案确保五套主题能够稳定、高效、优雅地在平台中运行,为用户提供丰富的个性化选择。