From 6cd942fb2931f48be340d93661fd265cf4f32f62 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com>
Date: Tue, 28 Oct 2025 10:59:23 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F=EF=BC=8C?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=98=8E=E6=9A=97=E4=B8=BB=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
front/THEME_GUIDE.md | 414 +++++++++++++++
front/src/App.vue | 14 +
front/src/assets/css/root.scss | 501 +++++++++++++++++-
front/src/assets/css/style.scss | 13 +-
front/src/assets/css/theme-usage.md | 235 ++++++++
front/src/components/ThemeToggle.vue | 57 ++
front/src/utils/theme.ts | 103 ++++
front/src/views/apps/knowledge/index.vue | 157 +++---
front/src/views/components/head.vue | 178 -------
front/src/views/components/layout.vue | 136 +++--
front/src/views/components/sidebar.vue | 66 ++-
front/src/views/components/sub-sidebar.vue | 57 +-
.../views/components/universal-sub-menu.vue | 71 +--
front/src/views/dashboard/index.vue | 167 +++---
front/src/views/system/roles/index.vue | 19 +-
front/src/views/system/tenant/index.vue | 22 +-
front/src/views/system/users/index.vue | 95 ++--
17 files changed, 1782 insertions(+), 523 deletions(-)
create mode 100644 front/THEME_GUIDE.md
create mode 100644 front/src/assets/css/theme-usage.md
create mode 100644 front/src/components/ThemeToggle.vue
create mode 100644 front/src/utils/theme.ts
delete mode 100644 front/src/views/components/head.vue
diff --git a/front/THEME_GUIDE.md b/front/THEME_GUIDE.md
new file mode 100644
index 0000000..1b8c999
--- /dev/null
+++ b/front/THEME_GUIDE.md
@@ -0,0 +1,414 @@
+# 主题系统使用指南
+
+## 概述
+
+项目已成功集成完整的亮色/暗色主题系统,支持一键切换并自动保存用户偏好。
+
+## ✨ 主要特性
+
+- ✅ 完整的亮色和暗色主题定义
+- ✅ 自动检测系统主题偏好
+- ✅ 用户偏好持久化(localStorage)
+- ✅ 监听系统主题变化
+- ✅ 平滑的过渡动画
+- ✅ 50+ CSS 变量覆盖所有UI场景
+- ✅ TypeScript 类型支持
+- ✅ 响应式数据绑定
+
+## 🚀 快速开始
+
+### 1. 全局初始化(已完成)
+
+主题系统已在 `App.vue` 中全局初始化,整个应用自动支持主题切换功能。
+
+### 2. 主题切换按钮
+
+在头部工具栏可以看到主题切换按钮(🌙/☀️图标),点击即可切换主题。
+
+### 3. 手动切换主题
+
+```typescript
+import { useTheme } from '@/utils/theme';
+
+// 在组件中使用
+const { toggle, setTheme, currentTheme, isDark } = useTheme();
+
+// 切换主题
+toggle();
+
+// 设置为特定主题
+setTheme('dark'); // 或 'light'
+
+// 获取当前主题状态
+console.log(isDark()); // true 或 false
+console.log(currentTheme); // 'light' 或 'dark'
+```
+
+### 4. 在样式文件中使用主题变量
+
+```scss
+.my-component {
+ background: var(--background-color);
+ color: var(--text-color);
+ border: 1px solid var(--border-color);
+
+ &:hover {
+ background: var(--background-hover);
+ border-color: var(--border-color-hover);
+ }
+}
+```
+
+## 📋 可用的 CSS 变量
+
+### 主题标识
+- `--theme-mode`: 当前主题模式 ('light' 或 'dark')
+
+### 主要颜色
+- `--primary-color`, `--primary-hover`, `--primary-active`
+- `--secondary-color`, `--secondary-hover`, `--secondary-active`
+- `--accent-color`, `--accent-hover`
+
+### 背景颜色
+- `--background-color`: 主要背景
+- `--background-secondary`, `--background-tertiary`
+- `--background-hover`
+
+### 文本颜色
+- `--text-color`: 主要文本
+- `--text-secondary`, `--text-tertiary`
+- `--text-inverse`: 反转文本色
+
+### 边框颜色
+- `--border-color`, `--border-color-hover`, `--border-color-active`
+
+### 状态颜色
+- `--success-color`, `--success-bg`
+- `--warning-color`, `--warning-bg`
+- `--error-color`, `--error-bg`
+- `--info-color`, `--info-bg`
+
+### 组件颜色
+- `--card-bg`, `--card-shadow`
+- `--sidebar-bg`, `--sidebar-hover`
+- `--header-bg`
+
+### 阴影
+- `--shadow-sm`, `--shadow-md`, `--shadow-lg`, `--shadow-xl`
+
+### 圆角
+- `--border-radius`, `--border-radius-sm`
+- `--border-radius-lg`, `--border-radius-xl`
+- `--border-radius-full`
+
+### 过渡
+- `--transition-base`: all 0.3s
+- `--transition-fast`: all 0.15s
+- `--transition-slow`: all 0.5s
+
+### 字体
+- `--font-family-base`
+
+## 🎨 自定义滚动条
+
+项目已经内置了自定义滚动条样式,支持主题切换:
+
+### 特性
+- ✅ 自动适配亮色/暗色主题
+- ✅ 支持 WebKit 和 Firefox 浏览器
+- ✅ 平滑的过渡动画
+- ✅ 悬停效果增强
+- ✅ 支持隐藏滚动条(保持滚动功能)
+
+### 滚动条大小
+- 宽度/高度:8px
+- 适合现代 UI 风格
+- 不影响内容布局
+
+### 使用方式
+
+**1. 默认滚动条(自动应用)**
+所有滚动容器都会自动使用主题滚动条样式。
+
+**2. 隐藏滚动条**
+如需隐藏滚动条但保持滚动功能:
+
+```vue
+
+
+
+```
+
+**3. 自定义滚动条颜色**
+如需特定区域使用不同的滚动条颜色,可在组件样式中覆盖:
+
+```scss
+.my-custom-scroll {
+ &::-webkit-scrollbar-thumb {
+ background: var(--primary-color);
+ }
+
+ scrollbar-color: var(--primary-color) var(--background-secondary);
+}
+```
+
+### 浏览器兼容性
+- ✅ Chrome/Edge: 完美支持
+- ✅ Firefox: 完美支持
+- ✅ Safari: 完美支持
+- ✅ Opera: 完美支持
+- ⚠️ IE: 使用原生滚动条
+
+## 📁 文件结构
+
+```
+front/
+├── src/
+│ ├── App.vue # 全局初始化主题系统 ⭐
+│ ├── main.ts # 入口文件,引入样式
+│ ├── assets/
+│ │ └── css/
+│ │ ├── root.scss # 主题变量定义
+│ │ └── theme-usage.md # 详细使用文档
+│ ├── utils/
+│ │ └── theme.ts # 主题切换工具
+│ ├── components/
+│ │ └── ThemeToggle.vue # 主题切换组件
+│ └── views/
+│ └── components/
+│ └── layout.vue # 已集成主题切换按钮
+├── THEME_GUIDE.md # 本文件
+```
+
+## 💡 使用示例
+
+### 示例 1: 基础组件
+
+```vue
+
+
+
{{ title }}
+
{{ content }}
+
+
+
+
+
+
+```
+
+### 示例 2: 按钮组件
+
+```vue
+
+
+
+
+
+
+
+```
+
+### 示例 3: 状态提示
+
+```vue
+
+
+
+ {{ message }}
+
+
+
+
+
+
+```
+
+## 🔧 API 参考
+
+### ThemeManager 类
+
+```typescript
+class ThemeManager {
+ // 初始化主题
+ init(): void;
+
+ // 设置主题
+ setTheme(theme: 'light' | 'dark', save?: boolean): void;
+
+ // 切换主题
+ toggle(): ThemeMode;
+
+ // 获取当前主题
+ getCurrentTheme(): ThemeMode;
+
+ // 检查是否为暗色
+ isDark(): boolean;
+
+ // 检查是否为亮色
+ isLight(): boolean;
+}
+```
+
+### useTheme Hook
+
+```typescript
+export const useTheme = () => {
+ return {
+ currentTheme: ThemeMode; // 当前主题
+ toggle(): void; // 切换主题
+ setTheme(theme: ThemeMode): void; // 设置主题
+ isDark(): boolean; // 是否暗色
+ isLight(): boolean; // 是否亮色
+ };
+};
+```
+
+## 🎨 主题配色方案
+
+### 亮色主题
+- 背景: 白色 → 浅灰
+- 文本: 深灰 → 黑色
+- 主色: 蓝色系 (#3498db)
+- 次色: 绿色系 (#2ecc71)
+- 强调: 黄色系 (#ffcc00)
+
+### 暗色主题
+- 背景: 深灰 → 黑色
+- 文本: 浅灰 → 白色
+- 主色: 浅蓝系 (#5dade2)
+- 次色: 浅绿系 (#58d68d)
+- 强调: 浅黄系 (#f7dc6f)
+
+## 📝 注意事项
+
+1. **始终使用 CSS 变量**: 不要在样式中硬编码颜色值
+2. **响应式绑定**: 在 Vue 组件中使用 `computed` 来响应式获取主题
+3. **测试两个主题**: 确保在亮色和暗色主题下都有良好的可读性
+4. **避免高对比度**: 暗色主题使用柔和的色彩,减少眼部疲劳
+5. **合理使用阴影**: 暗色主题中使用更深的阴影增强层次感
+
+## 🐛 故障排除
+
+### 主题切换不生效
+- 确保在组件中正确导入并使用 `useTheme()`
+- 检查是否正确添加了 `root.scss` 到主样式文件
+
+### 样式未应用
+- 确保使用 CSS 变量而不是硬编码值
+- 检查元素是否正确继承了主题变量
+
+### TypeScript 错误
+- 确保已正确导入类型定义
+- 重启 VS Code 的 TypeScript 服务器
+
+## 📞 支持
+
+如有问题或建议,请查看:
+- `front/src/assets/css/theme-usage.md` - 详细使用文档
+- `front/src/utils/theme.ts` - 源代码和注释
+- `front/src/components/ThemeToggle.vue` - 组件示例
+
diff --git a/front/src/App.vue b/front/src/App.vue
index 42c51de..7c4c3ac 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -1,4 +1,18 @@
diff --git a/front/src/assets/css/root.scss b/front/src/assets/css/root.scss
index 4d92857..252b2fc 100644
--- a/front/src/assets/css/root.scss
+++ b/front/src/assets/css/root.scss
@@ -1,4 +1,4 @@
-// 主题色变量
+// 主题色变量(SCSS变量 - 默认为亮色主题)
$primary-color: #3498db;
$secondary-color: #2ecc71;
$accent-color: #ffcc00;
@@ -21,56 +21,519 @@ $box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
// 过渡
$transition-base: all 0.3s cubic-bezier(.25,.8,.25,1);
-// 主题色快捷类
-:root {
- --primary-color: #3498db;
+// ==========================================
+// 主题系统 - 亮色和暗色主题
+// 使用 data-theme="light" 或 data-theme="dark" 来切换主题
+// ==========================================
+
+// 默认亮色主题
+:root[data-theme="light"],
+:root:not([data-theme]) {
+ // 主题标识
+ --theme-mode: light;
+
+ // 主要颜色
+ --primary-color: #1890ff;
+ --primary-hover: #40a9ff;
+ --primary-active: #096dd9;
--secondary-color: #2ecc71;
+ --secondary-hover: #27ae60;
+ --secondary-active: #229954;
--accent-color: #ffcc00;
- --background-color: #f8f9fa;
- --text-color: #333;
+ --accent-hover: #e6b800;
+
+ // 背景颜色
+ --background-color: #ffffff;
+ --background-secondary: #f8f9fa;
+ --background-tertiary: #f0f2f5;
+ --background-hover: #e9ecef;
+
+ // 文本颜色
+ --text-color: #212529;
+ --text-secondary: #6c757d;
+ --text-tertiary: #adb5bd;
+ --text-inverse: #ffffff;
+
+ // 边框颜色
+ --border-color: #dee2e6;
+ --border-color-hover: #ced4da;
+ --border-color-active: #adb5bd;
+
+ // 状态颜色
+ --success-color: #28a745;
+ --success-bg: #d4edda;
+ --warning-color: #ffc107;
+ --warning-bg: #fff3cd;
+ --error-color: #dc3545;
+ --error-bg: #f8d7da;
+ --info-color: #17a2b8;
+ --info-bg: #d1ecf1;
+
+ // 组件颜色
+ --card-bg: #ffffff;
+ --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+ --sidebar-bg: #f8f9fa;
+ --sidebar-hover: #e9ecef;
+ --header-bg: #ffffff;
+
+ // Hero 渐变(亮色主题 - 现代蓝色渐变)
+ --hero-gradient-start: #667eea;
+ --hero-gradient-end: #764ba2;
+
+ // 阴影
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+ --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08);
+ --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.1);
+ --shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.12);
+
+ // 圆角
--border-radius: 4px;
- --box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+ --border-radius-sm: 2px;
+ --border-radius-lg: 8px;
+ --border-radius-xl: 12px;
+ --border-radius-full: 9999px;
+
+ // 过渡
--transition-base: all 0.3s cubic-bezier(.25,.8,.25,1);
+ --transition-fast: all 0.15s ease-in-out;
+ --transition-slow: all 0.5s ease-in-out;
+
+ // 字体
+ --font-family-base: 'Segoe UI', 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
+}
+
+// 暗色主题
+:root[data-theme="dark"] {
+ // 主题标识
+ --theme-mode: dark;
+
+ // 主要颜色
+ --primary-color: #409EFF;
+ --primary-hover: #66b1ff;
+ --primary-active: #337ecc;
+ --secondary-color: #58d68d;
+ --secondary-hover: #2ecc71;
+ --secondary-active: #27ae60;
+ --accent-color: #f7dc6f;
+ --accent-hover: #f4d03f;
+
+ // 背景颜色
+ --background-color: #1a1a1a;
+ --background-secondary: #2d2d2d;
+ --background-tertiary: #3d3d3d;
+ --background-hover: #4a4a4a;
+
+ // 文本颜色
+ --text-color: #e9ecef;
+ --text-secondary: #adb5bd;
+ --text-tertiary: #6c757d;
+ --text-inverse: #212529;
+
+ // 边框颜色
+ --border-color: #404040;
+ --border-color-hover: #4d4d4d;
+ --border-color-active: #5a5a5a;
+
+ // 状态颜色
+ --success-color: #58d68d;
+ --success-bg: #1e3c24;
+ --warning-color: #f7dc6f;
+ --warning-bg: #4d3e1a;
+ --error-color: #ec7063;
+ --error-bg: #3c2626;
+ --info-color: #5dade2;
+ --info-bg: #1e2a33;
+
+ // 组件颜色
+ --card-bg: #2d2d2d;
+ --card-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+ --sidebar-bg: #1f1f1f;
+ --sidebar-hover: #3d3d3d;
+ --header-bg: #2d2d2d;
+
+ // Hero 渐变(暗色主题 - 深紫色渐变)
+ --hero-gradient-start: #4a5568;
+ --hero-gradient-end: #2d3748;
+
+ // 阴影
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
+ --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4);
+ --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.5);
+ --shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.6);
+
+ // 圆角
+ --border-radius: 4px;
+ --border-radius-sm: 2px;
+ --border-radius-lg: 8px;
+ --border-radius-xl: 12px;
+ --border-radius-full: 9999px;
+
+ // 过渡
+ --transition-base: all 0.3s cubic-bezier(.25,.8,.25,1);
+ --transition-fast: all 0.15s ease-in-out;
+ --transition-slow: all 0.5s ease-in-out;
+
+ // 字体
--font-family-base: 'Segoe UI', 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
}
body {
- font-family: $font-family-base;
- color: $text-color;
- background: $background-color;
+ font-family: var(--font-family-base);
+ color: var(--text-color);
+ background: var(--background-color);
margin: 0;
padding: 0;
+ transition: var(--transition-base);
}
a {
- color: $primary-color;
+ color: var(--primary-color);
text-decoration: none;
&:hover {
text-decoration: underline;
- color: $primary-color
+ color: var(--primary-hover);
}
}
.btn {
display: inline-block;
padding: 0.5em 1.2em;
- border-radius: $border-radius;
- background: $primary-color;
- color: #fff;
+ border-radius: var(--border-radius);
+ background: var(--primary-color);
+ color: var(--text-inverse);
border: none;
cursor: pointer;
- transition: background 0.2s;
+ transition: var(--transition-fast);
&:hover {
- background: $primary-color
+ background: var(--primary-hover);
+ }
+
+ &:active {
+ background: var(--primary-active);
}
&.secondary {
- background: $secondary-color;
+ background: var(--secondary-color);
&:hover {
- background: $secondary-color
+ background: var(--secondary-hover);
+ }
+
+ &:active {
+ background: var(--secondary-active);
+ }
+ }
+
+ &.accent {
+ background: var(--accent-color);
+ color: var(--text-color);
+
+ &:hover {
+ background: var(--accent-hover);
}
}
}
+
+// ==========================================
+// 自定义滚动条样式 - 支持主题切换
+// ==========================================
+
+// WebKit 浏览器 (Chrome, Edge, Safari)
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--background-secondary);
+ border-radius: var(--border-radius);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--border-color-hover);
+ border-radius: var(--border-radius);
+ transition: var(--transition-fast);
+
+ &:hover {
+ background: var(--text-tertiary);
+ }
+
+ &:active {
+ background: var(--primary-color);
+ }
+}
+
+::-webkit-scrollbar-corner {
+ background: var(--background-secondary);
+}
+
+// Firefox
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--border-color-hover) var(--background-secondary);
+}
+
+// 可选:隐藏滚动条但保持滚动功能
+.scrollbar-hide {
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+
+ &::-webkit-scrollbar {
+ display: none; /* Chrome, Safari, Opera */
+ }
+}
+
+// 平滑滚动
+html {
+ scroll-behavior: smooth;
+}
+
+// ==========================================
+// Element Plus 主题覆盖 - 支持主题切换
+// ==========================================
+
+// 亮色主题下的 Element Plus 组件
+:root[data-theme="light"],
+:root:not([data-theme]) {
+ --el-bg-color: #ffffff;
+ --el-bg-color-page: #f2f3f5;
+ --el-text-color-primary: #303133;
+ --el-text-color-regular: #606266;
+ --el-text-color-secondary: #909399;
+ --el-text-color-placeholder: #c0c4cc;
+ --el-text-color-disabled: #c0c4cc;
+ --el-border-color: #dcdfe6;
+ --el-border-color-light: #e4e7ed;
+ --el-border-color-lighter: #ebeef5;
+ --el-border-color-extra-light: #f2f6fc;
+ --el-border-color-dark: #d4d7de;
+ --el-border-color-darker: #cdd0d6;
+
+ // 组件背景
+ --el-fill-color: #f0f2f5;
+ --el-fill-color-light: #f5f7fa;
+ --el-fill-color-lighter: #fafafa;
+ --el-fill-color-extra-light: #fafcff;
+ --el-fill-color-dark: #ebedf0;
+ --el-fill-color-darker: #e6e8eb;
+ --el-fill-color-blank: #ffffff;
+
+ // 组件颜色
+ --el-color-primary: #1890ff;
+ --el-color-success: #2ecc71;
+ --el-color-warning: #ffcc00;
+ --el-color-danger: #dc3545;
+ --el-color-error: #dc3545;
+ --el-color-info: #909399;
+
+ // 阴影
+ --el-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+ --el-box-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
+ --el-box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
+ --el-box-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.12);
+
+ // 覆盖 Element Plus 表格样式(亮色主题)
+ --el-table-bg-color: #ffffff;
+ --el-table-header-bg-color: #fafafa;
+ --el-table-header-text-color: #909399;
+ --el-table-row-hover-bg-color: #f5f7fa;
+ --el-table-border-color: #ebeef5;
+ --el-table-border: 1px solid #ebeef5;
+
+ // 对话框
+ --el-overlay-color-light: rgba(0, 0, 0, 0.5);
+ --el-overlay-color-lighter: rgba(0, 0, 0, 0.3);
+ --el-mask-color: rgba(0, 0, 0, 0.5);
+ --el-mask-color-extra-light: rgba(0, 0, 0, 0.2);
+}
+
+// 暗色主题下的 Element Plus 组件
+:root[data-theme="dark"] {
+ --el-bg-color: #2d2d2d;
+ --el-bg-color-page: #1a1a1a;
+ --el-text-color-primary: #e9ecef;
+ --el-text-color-regular: #adb5bd;
+ --el-text-color-secondary: #6c757d;
+ --el-text-color-placeholder: #495057;
+ --el-text-color-disabled: #495057;
+ --el-border-color: #404040;
+ --el-border-color-light: #404040;
+ --el-border-color-lighter: #3d3d3d;
+ --el-border-color-extra-light: #3d3d3d;
+ --el-border-color-dark: #4a4a4a;
+ --el-border-color-darker: #4a4a4a;
+
+ // 组件背景
+ --el-fill-color: #3d3d3d;
+ --el-fill-color-light: #3d3d3d;
+ --el-fill-color-lighter: #3d3d3d;
+ --el-fill-color-extra-light: #3d3d3d;
+ --el-fill-color-dark: #4a4a4a;
+ --el-fill-color-darker: #4a4a4a;
+ --el-fill-color-blank: #2d2d2d;
+
+ // 组件颜色
+ --el-color-primary: #409EFF;
+ --el-color-success: #58d68d;
+ --el-color-warning: #f7dc6f;
+ --el-color-danger: #ec7063;
+ --el-color-error: #ec7063;
+ --el-color-info: #adb5bd;
+
+ // 阴影(暗色主题使用更深的阴影)
+ --el-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
+ --el-box-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.5);
+ --el-box-shadow-base: 0 2px 4px rgba(0, 0, 0, 0.4);
+ --el-box-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.6);
+
+ // 覆盖 Element Plus 表格样式
+ --el-table-bg-color: var(--card-bg);
+ --el-table-header-bg-color: var(--header-bg);
+ --el-table-header-text-color: var(--text-color);
+ --el-table-row-hover-bg-color: var(--background-hover);
+ --el-table-border-color: var(--border-color);
+ --el-table-border: 1px solid var(--border-color);
+
+ // 对话框
+ --el-overlay-color-light: rgba(0, 0, 0, 0.7);
+ --el-overlay-color-lighter: rgba(0, 0, 0, 0.5);
+ --el-mask-color: rgba(0, 0, 0, 0.8);
+ --el-mask-color-extra-light: rgba(0, 0, 0, 0.3);
+}
+
+// ==========================================
+// Element Plus 组件强制样式覆盖
+// ==========================================
+
+// 亮色和暗色主题统一样式
+.el-table {
+ background-color: var(--card-bg);
+ color: var(--text-color);
+ transition: var(--transition-base);
+
+ th.el-table__cell {
+ background-color: var(--header-bg);
+ color: var(--text-color);
+ border-bottom-color: var(--border-color);
+ }
+
+ td.el-table__cell {
+ background-color: var(--card-bg);
+ color: var(--text-color);
+ border-bottom-color: var(--border-color);
+ }
+
+ .el-table__row:hover {
+ background-color: var(--background-hover) !important;
+ }
+
+ .el-table__row.current-row {
+ background-color: var(--background-hover) !important;
+ }
+
+ .el-table__border {
+ border-color: var(--border-color);
+ }
+}
+
+.el-dialog {
+ background-color: var(--card-bg);
+ color: var(--text-color);
+ transition: var(--transition-base);
+
+ .el-dialog__header {
+ border-bottom: 1px solid var(--border-color);
+
+ .el-dialog__title {
+ color: var(--text-color);
+ }
+ }
+
+ .el-dialog__body {
+ color: var(--text-color);
+ }
+
+ .el-dialog__footer {
+ border-top: 1px solid var(--border-color);
+ }
+}
+
+.el-descriptions {
+ .el-descriptions__label {
+ color: var(--text-secondary);
+ }
+
+ .el-descriptions__content {
+ color: var(--text-color);
+ }
+
+ .el-descriptions__border {
+ border-color: var(--border-color);
+ }
+}
+
+// 表单
+.el-form {
+ .el-form-item__label {
+ color: var(--text-color);
+ }
+
+ .el-input__inner,
+ .el-textarea__inner {
+ background-color: var(--background-secondary);
+ color: var(--text-color);
+ border-color: var(--border-color);
+
+ &::placeholder {
+ color: var(--text-tertiary);
+ }
+ }
+}
+
+// 按钮
+.el-button {
+ background-color: var(--background-secondary);
+ color: var(--text-color);
+ border-color: var(--border-color);
+ transition: var(--transition-fast);
+
+ &:hover {
+ border-color: var(--primary-color);
+ color: var(--primary-color);
+ }
+
+ &.el-button--primary {
+ background-color: var(--primary-color);
+ color: var(--text-inverse);
+ border-color: var(--primary-color);
+
+ &:hover {
+ background-color: var(--primary-hover);
+ }
+ }
+}
+
+// 标签页
+.el-tabs {
+ .el-tabs__header {
+ background-color: var(--card-bg);
+ }
+
+ .el-tabs__item {
+ color: var(--text-secondary);
+
+ &.is-active {
+ color: var(--primary-color);
+ }
+
+ &:hover {
+ color: var(--primary-color);
+ }
+ }
+
+ .el-tabs__nav-wrap::after {
+ background-color: var(--border-color);
+ }
+}
diff --git a/front/src/assets/css/style.scss b/front/src/assets/css/style.scss
index 394a509..7d4c387 100644
--- a/front/src/assets/css/style.scss
+++ b/front/src/assets/css/style.scss
@@ -2,11 +2,11 @@
display: flex;
align-items: center;
justify-content: space-between;
- margin: 10px 0;
+ // margin: 10px 0;
border-bottom: 1px solid #f2f3f5;
- padding-bottom: 16px;
+ padding-bottom: 20px;
- h2{
+ h2 {
font-size: 1.4rem;
font-weight: 700;
}
@@ -21,4 +21,9 @@
display: flex;
justify-content: flex-end;
margin: 18px 0 0 0;
-}
\ No newline at end of file
+}
+.header-actions {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
diff --git a/front/src/assets/css/theme-usage.md b/front/src/assets/css/theme-usage.md
new file mode 100644
index 0000000..ec16531
--- /dev/null
+++ b/front/src/assets/css/theme-usage.md
@@ -0,0 +1,235 @@
+# 主题系统使用指南
+
+## 概述
+
+项目已集成完整的亮色/暗色主题系统,支持一键切换并自动保存用户偏好。
+
+## 主题标识
+
+- **亮色主题**: `data-theme="light"` 或不设置该属性
+- **暗色主题**: `data-theme="dark"`
+- **主题模式标识**: 通过 CSS 变量 `--theme-mode` 可获取当前主题
+
+## 使用方法
+
+### 1. 在 Vue 组件中使用
+
+```vue
+
+
+
+
+
+
内容
+
+
+
+
+
+
+```
+
+### 2. 在 TypeScript 中使用
+
+```typescript
+import { themeManager, useTheme } from '@/utils/theme';
+
+// 方式1: 使用主题管理器
+themeManager.setTheme('dark');
+themeManager.toggle();
+console.log(themeManager.getCurrentTheme()); // 'dark'
+
+// 方式2: 使用 useTheme hook
+const { currentTheme, toggle, setTheme } = useTheme();
+```
+
+### 3. 在 SCSS/CSS 中使用主题变量
+
+```scss
+.my-component {
+ // 背景颜色
+ background: var(--background-color);
+ background-secondary: var(--background-secondary);
+
+ // 文本颜色
+ color: var(--text-color);
+
+ // 主要颜色
+ border: 1px solid var(--primary-color);
+
+ // 状态颜色
+ &.success {
+ color: var(--success-color);
+ background: var(--success-bg);
+ }
+
+ &.error {
+ color: var(--error-color);
+ background: var(--error-bg);
+ }
+
+ // 阴影
+ box-shadow: var(--shadow-md);
+
+ // 圆角
+ border-radius: var(--border-radius-lg);
+
+ // 过渡
+ transition: var(--transition-base);
+}
+```
+
+## 可用 CSS 变量
+
+### 主题标识
+- `--theme-mode`: 'light' 或 'dark'
+
+### 主要颜色
+- `--primary-color`: 主色调
+- `--primary-hover`: 主色调悬停
+- `--primary-active`: 主色调激活
+- `--secondary-color`: 次要色调
+- `--secondary-hover`: 次要色调悬停
+- `--secondary-active`: 次要色调激活
+- `--accent-color`: 强调色
+- `--accent-hover`: 强调色悬停
+
+### 背景颜色
+- `--background-color`: 主要背景
+- `--background-secondary`: 次要背景
+- `--background-tertiary`: 第三背景
+- `--background-hover`: 悬停背景
+
+### 文本颜色
+- `--text-color`: 主要文本
+- `--text-secondary`: 次要文本
+- `--text-tertiary`: 第三文本
+- `--text-inverse`: 反转文本
+
+### 边框颜色
+- `--border-color`: 边框颜色
+- `--border-color-hover`: 悬停边框
+- `--border-color-active`: 激活边框
+
+### 状态颜色
+- `--success-color`: 成功色
+- `--success-bg`: 成功背景
+- `--warning-color`: 警告色
+- `--warning-bg`: 警告背景
+- `--error-color`: 错误色
+- `--error-bg`: 错误背景
+- `--info-color`: 信息色
+- `--info-bg`: 信息背景
+
+### 组件颜色
+- `--card-bg`: 卡片背景
+- `--card-shadow`: 卡片阴影
+- `--sidebar-bg`: 侧边栏背景
+- `--sidebar-hover`: 侧边栏悬停
+- `--header-bg`: 头部背景
+
+### 阴影
+- `--shadow-sm`: 小阴影
+- `--shadow-md`: 中等阴影
+- `--shadow-lg`: 大阴影
+- `--shadow-xl`: 超大阴影
+
+### 圆角
+- `--border-radius`: 基础圆角
+- `--border-radius-sm`: 小圆角
+- `--border-radius-lg`: 大圆角
+- `--border-radius-xl`: 超大圆角
+- `--border-radius-full`: 完全圆角
+
+### 过渡
+- `--transition-base`: 基础过渡
+- `--transition-fast`: 快速过渡
+- `--transition-slow`: 缓慢过渡
+
+### 字体
+- `--font-family-base`: 基础字体族
+
+## 特性
+
+1. **自动保存**: 主题偏好自动保存到 `localStorage`
+2. **系统偏好**: 首次访问自动检测系统主题偏好
+3. **系统监听**: 监听系统主题变化并自动更新
+4. **平滑过渡**: 主题切换时使用 CSS 过渡效果
+5. **完整变量**: 提供所有常见的 UI 变量
+
+## 最佳实践
+
+1. 始终使用 CSS 变量而不是固定颜色值
+2. 在组件中使用 `useTheme()` 获取主题状态
+3. 为交互元素添加悬停和激活状态
+4. 使用阴影和边框来增强暗色主题的可读性
+5. 测试两种主题下的所有页面和组件
+
+## 示例组件
+
+创建一个主题切换器组件:
+
+```vue
+
+
+
+
+
+
+
+```
+
diff --git a/front/src/components/ThemeToggle.vue b/front/src/components/ThemeToggle.vue
new file mode 100644
index 0000000..4465127
--- /dev/null
+++ b/front/src/components/ThemeToggle.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
diff --git a/front/src/utils/theme.ts b/front/src/utils/theme.ts
new file mode 100644
index 0000000..ae3a511
--- /dev/null
+++ b/front/src/utils/theme.ts
@@ -0,0 +1,103 @@
+/**
+ * 主题切换工具
+ * 支持亮色和暗色主题切换,并自动保存用户偏好
+ */
+
+export type ThemeMode = 'light' | 'dark';
+
+class ThemeManager {
+ private readonly STORAGE_KEY = 'app-theme';
+ private currentTheme: ThemeMode = 'light';
+
+ constructor() {
+ this.init();
+ }
+
+ /**
+ * 初始化主题
+ * 从本地存储读取或使用系统偏好
+ */
+ init() {
+ // 先读取本地存储的主题
+ const savedTheme = localStorage.getItem(this.STORAGE_KEY) as ThemeMode | null;
+
+ if (savedTheme) {
+ this.setTheme(savedTheme);
+ } else {
+ // 检测系统偏好
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ this.setTheme(prefersDark ? 'dark' : 'light');
+ }
+
+ // 监听系统主题变化
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
+ if (!localStorage.getItem(this.STORAGE_KEY)) {
+ this.setTheme(e.matches ? 'dark' : 'light', false);
+ }
+ });
+ }
+
+ /**
+ * 设置主题
+ * @param theme 主题模式
+ * @param save 是否保存到本地存储,默认 true
+ */
+ setTheme(theme: ThemeMode, save: boolean = true) {
+ this.currentTheme = theme;
+ document.documentElement.setAttribute('data-theme', theme);
+
+ if (save) {
+ localStorage.setItem(this.STORAGE_KEY, theme);
+ }
+ }
+
+ /**
+ * 切换主题
+ */
+ toggle() {
+ const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
+ this.setTheme(newTheme);
+ return newTheme;
+ }
+
+ /**
+ * 获取当前主题
+ */
+ getCurrentTheme(): ThemeMode {
+ return this.currentTheme;
+ }
+
+ /**
+ * 检查是否为暗色主题
+ */
+ isDark(): boolean {
+ return this.currentTheme === 'dark';
+ }
+
+ /**
+ * 检查是否为亮色主题
+ */
+ isLight(): boolean {
+ return this.currentTheme === 'light';
+ }
+}
+
+// 导出单例实例
+export const themeManager = new ThemeManager();
+
+// 导出便捷方法
+export const useTheme = () => {
+ return {
+ get currentTheme() {
+ return themeManager.getCurrentTheme();
+ },
+ toggle: () => themeManager.toggle(),
+ setTheme: (theme: ThemeMode) => themeManager.setTheme(theme),
+ isDark: () => themeManager.isDark(),
+ isLight: () => themeManager.isLight(),
+ };
+};
+
+// 导出常量和类型
+export default themeManager;
+
diff --git a/front/src/views/apps/knowledge/index.vue b/front/src/views/apps/knowledge/index.vue
index 8f30802..e5ad4aa 100644
--- a/front/src/views/apps/knowledge/index.vue
+++ b/front/src/views/apps/knowledge/index.vue
@@ -337,33 +337,16 @@ export default {
diff --git a/front/src/views/components/layout.vue b/front/src/views/components/layout.vue
index 8cedb68..2aa11f5 100644
--- a/front/src/views/components/layout.vue
+++ b/front/src/views/components/layout.vue
@@ -11,13 +11,13 @@
diff --git a/front/src/views/components/sub-sidebar.vue b/front/src/views/components/sub-sidebar.vue
index 7c4f5f7..4670b4f 100644
--- a/front/src/views/components/sub-sidebar.vue
+++ b/front/src/views/components/sub-sidebar.vue
@@ -3,9 +3,9 @@