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
This commit is contained in:
logan committed 2026-05-27 01:59:08 +08:00
1 parent 4e2df4d3d7
commit 746a000e8a
25 files changed
+7718 -178

No files matched your search

@@ -0,0 +1,741 @@
## 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;
}
```
以上实现方案确保五套主题能够稳定、高效、优雅地在平台中运行,为用户提供丰富的个性化选择。