优化样式,添加明暗主题
This commit is contained in:
parent
d1ae365964
commit
6cd942fb29
414
front/THEME_GUIDE.md
Normal file
414
front/THEME_GUIDE.md
Normal file
@ -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
|
||||||
|
<div class="scrollbar-hide">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**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
|
||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
<p>{{ content }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
border-color: var(--border-color-hover);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 2: 按钮组件
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<button :class="buttonClass">
|
||||||
|
{{ text }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
type?: 'primary' | 'secondary' | 'danger';
|
||||||
|
text: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const buttonClass = computed(() => ({
|
||||||
|
'btn-primary': true,
|
||||||
|
[`btn-${props.type || 'primary'}`]: true,
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: var(--text-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: var(--text-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--error-color);
|
||||||
|
color: var(--text-inverse);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 3: 状态提示
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div :class="['alert', `alert-${type}`]">
|
||||||
|
<i :class="iconClass"></i>
|
||||||
|
<span>{{ message }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
type: 'success' | 'warning' | 'error' | 'info';
|
||||||
|
message: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const iconClass = computed(() => {
|
||||||
|
const icons = {
|
||||||
|
success: 'fa-check-circle',
|
||||||
|
warning: 'fa-exclamation-triangle',
|
||||||
|
error: 'fa-times-circle',
|
||||||
|
info: 'fa-info-circle',
|
||||||
|
};
|
||||||
|
return `fas ${icons[props.type]}`;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.alert {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
color: var(--success-color);
|
||||||
|
background: var(--success-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
color: var(--warning-color);
|
||||||
|
background: var(--warning-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
color: var(--error-color);
|
||||||
|
background: var(--error-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: var(--info-color);
|
||||||
|
background: var(--info-bg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 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` - 组件示例
|
||||||
|
|
||||||
@ -1,4 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onBeforeMount } from 'vue';
|
||||||
|
import themeManager from '@/utils/theme';
|
||||||
|
|
||||||
|
// 在组件挂载前初始化全局主题系统
|
||||||
|
// 这样确保在首次渲染时就能应用正确的主题,避免闪烁
|
||||||
|
onBeforeMount(() => {
|
||||||
|
// 初始化主题管理器,自动检测系统偏好或读取保存的主题
|
||||||
|
themeManager.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导出主题管理器供全局使用(可选,用于调试)
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
(window as any).themeManager = themeManager;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
// 主题色变量
|
// 主题色变量(SCSS变量 - 默认为亮色主题)
|
||||||
$primary-color: #3498db;
|
$primary-color: #3498db;
|
||||||
$secondary-color: #2ecc71;
|
$secondary-color: #2ecc71;
|
||||||
$accent-color: #ffcc00;
|
$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);
|
$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-color: #2ecc71;
|
||||||
|
--secondary-hover: #27ae60;
|
||||||
|
--secondary-active: #229954;
|
||||||
--accent-color: #ffcc00;
|
--accent-color: #ffcc00;
|
||||||
--background-color: #f8f9fa;
|
--accent-hover: #e6b800;
|
||||||
--text-color: #333;
|
|
||||||
|
// 背景颜色
|
||||||
|
--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;
|
--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-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;
|
--font-family-base: 'Segoe UI', 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: $font-family-base;
|
font-family: var(--font-family-base);
|
||||||
color: $text-color;
|
color: var(--text-color);
|
||||||
background: $background-color;
|
background: var(--background-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $primary-color;
|
color: var(--primary-color);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: $primary-color
|
color: var(--primary-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.5em 1.2em;
|
padding: 0.5em 1.2em;
|
||||||
border-radius: $border-radius;
|
border-radius: var(--border-radius);
|
||||||
background: $primary-color;
|
background: var(--primary-color);
|
||||||
color: #fff;
|
color: var(--text-inverse);
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s;
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $primary-color
|
background: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: var(--primary-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.secondary {
|
&.secondary {
|
||||||
background: $secondary-color;
|
background: var(--secondary-color);
|
||||||
|
|
||||||
&:hover {
|
&: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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 10px 0;
|
// margin: 10px 0;
|
||||||
border-bottom: 1px solid #f2f3f5;
|
border-bottom: 1px solid #f2f3f5;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 20px;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
@ -22,3 +22,8 @@
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin: 18px 0 0 0;
|
margin: 18px 0 0 0;
|
||||||
}
|
}
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|||||||
235
front/src/assets/css/theme-usage.md
Normal file
235
front/src/assets/css/theme-usage.md
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
# 主题系统使用指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
项目已集成完整的亮色/暗色主题系统,支持一键切换并自动保存用户偏好。
|
||||||
|
|
||||||
|
## 主题标识
|
||||||
|
|
||||||
|
- **亮色主题**: `data-theme="light"` 或不设置该属性
|
||||||
|
- **暗色主题**: `data-theme="dark"`
|
||||||
|
- **主题模式标识**: 通过 CSS 变量 `--theme-mode` 可获取当前主题
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 在 Vue 组件中使用
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button @click="toggleTheme">
|
||||||
|
切换到 {{ isDark ? '亮色' : '暗色' }} 主题
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 使用主题变量 -->
|
||||||
|
<div class="card">内容</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTheme } from '@/utils/theme';
|
||||||
|
|
||||||
|
const { currentTheme, toggle, isDark } = useTheme();
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
toggle();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
border-color: var(--border-color-hover);
|
||||||
|
background: var(--background-hover);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
<template>
|
||||||
|
<button class="theme-toggle" @click="handleToggle">
|
||||||
|
<i :class="isDark ? 'fa-sun' : 'fa-moon'"></i>
|
||||||
|
<span>{{ isDark ? '切换到亮色' : '切换到暗色' }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTheme } from '@/utils/theme';
|
||||||
|
|
||||||
|
const { isDark, toggle } = useTheme();
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
toggle();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.theme-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle:hover {
|
||||||
|
background: var(--background-hover);
|
||||||
|
border-color: var(--border-color-hover);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
57
front/src/components/ThemeToggle.vue
Normal file
57
front/src/components/ThemeToggle.vue
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<button class="theme-toggle" @click="toggleTheme" :title="isDark ? '切换到亮色模式' : '切换到暗色模式'">
|
||||||
|
<i class="fas" :class="isDark ? 'fa-sun' : 'fa-moon'"></i>
|
||||||
|
<span class="theme-text">{{ isDark ? '亮色' : '暗色' }}</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useTheme } from '@/utils/theme';
|
||||||
|
|
||||||
|
const { toggle, isDark } = useTheme();
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
toggle();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.theme-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition-base);
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--background-hover);
|
||||||
|
border-color: var(--border-color-hover);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 16px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
.theme-toggle:hover & {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-text {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暗色主题下的特殊样式
|
||||||
|
:root[data-theme="dark"] .theme-toggle {
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
103
front/src/utils/theme.ts
Normal file
103
front/src/utils/theme.ts
Normal file
@ -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;
|
||||||
|
|
||||||
@ -337,33 +337,16 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
// 全局变量定义
|
// 使用主题变量,不需要本地定义了
|
||||||
$primary-color: #409EFF;
|
|
||||||
$success-color: #67C23A;
|
|
||||||
$warning-color: #E6A23C;
|
|
||||||
$danger-color: #F56C6C;
|
|
||||||
$gray-100: #f5f7fa;
|
|
||||||
$gray-200: #e4e7ed;
|
|
||||||
$gray-300: #dcdfe6;
|
|
||||||
$gray-400: #c0c4cc;
|
|
||||||
$gray-500: #909399;
|
|
||||||
$gray-600: #606266;
|
|
||||||
$gray-700: #303133;
|
|
||||||
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
||||||
$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
||||||
$shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.1);
|
|
||||||
$radius-sm: 4px;
|
|
||||||
$radius-md: 8px;
|
|
||||||
$radius-lg: 12px;
|
|
||||||
$transition-base: all 0.3s ease;
|
|
||||||
|
|
||||||
// 顶部横幅样式
|
// 顶部横幅样式
|
||||||
.hero.new-style {
|
.hero.new-style {
|
||||||
background: linear-gradient(135deg, #3b82f6, #0284c7);
|
background: linear-gradient(135deg, var(--primary-color), var(--info-color));
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
transition: var(--transition-base);
|
||||||
|
|
||||||
// 背景装饰
|
// 背景装饰
|
||||||
&::after {
|
&::after {
|
||||||
@ -406,18 +389,19 @@ $transition-base: all 0.3s ease;
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: 0 auto 30px;
|
margin: 0 auto 30px;
|
||||||
background: white;
|
background: var(--card-bg);
|
||||||
border-radius: $radius-md;
|
border-radius: var(--border-radius-lg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--shadow-xl);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-select {
|
.search-select {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-radius: $radius-md 0 0 $radius-md;
|
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
||||||
|
|
||||||
::v-deep .el-input__inner {
|
::v-deep .el-input__inner {
|
||||||
border-radius: $radius-md 0 0 $radius-md;
|
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,15 +417,16 @@ $transition-base: all 0.3s ease;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-button {
|
.search-button {
|
||||||
background-color: #f97316;
|
background-color: var(--accent-color);
|
||||||
border-color: #f97316;
|
border-color: var(--accent-color);
|
||||||
border-radius: 0 $radius-md $radius-md 0;
|
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
|
||||||
padding: 0 32px;
|
padding: 0 32px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #ea580c;
|
background-color: var(--accent-hover);
|
||||||
border-color: #ea580c;
|
border-color: var(--accent-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,24 +464,25 @@ $transition-base: all 0.3s ease;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
background: #fff;
|
background: var(--card-bg);
|
||||||
border-radius: $radius-lg;
|
border-radius: var(--border-radius-lg);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-shadow: $shadow-md;
|
box-shadow: var(--card-shadow);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition: $transition-base;
|
transition: var(--transition-base);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
box-shadow: $shadow-lg;
|
box-shadow: var(--shadow-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-icon {
|
.stat-icon {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border-radius: $radius-md;
|
border-radius: var(--border-radius-lg);
|
||||||
background-color: rgba(64, 158, 255, 0.1);
|
background: var(--info-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -504,7 +490,7 @@ $transition-base: all 0.3s ease;
|
|||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #409EFF;
|
color: var(--info-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,14 +499,14 @@ $transition-base: all 0.3s ease;
|
|||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $gray-600;
|
color: var(--text-secondary);
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $gray-700;
|
color: var(--text-color);
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
@ -536,11 +522,11 @@ $transition-base: all 0.3s ease;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.positive {
|
&.positive {
|
||||||
color: $success-color;
|
color: var(--success-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.negative {
|
&.negative {
|
||||||
color: $danger-color;
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -560,12 +546,17 @@ $transition-base: all 0.3s ease;
|
|||||||
h2 {
|
h2 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $gray-700;
|
color: var(--text-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-btn {
|
.create-btn {
|
||||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
|
box-shadow: var(--shadow-md);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -577,29 +568,29 @@ $transition-base: all 0.3s ease;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.repo-card {
|
.repo-card {
|
||||||
background: #fff;
|
background: var(--card-bg);
|
||||||
border-radius: $radius-lg;
|
border-radius: var(--border-radius-lg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: $shadow-md;
|
box-shadow: var(--card-shadow);
|
||||||
transition: $transition-base;
|
transition: var(--transition-base);
|
||||||
border: 1px solid $gray-200;
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
box-shadow: $shadow-lg;
|
box-shadow: var(--shadow-lg);
|
||||||
border-color: transparent;
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-header {
|
.repo-header {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-bottom: 1px solid $gray-100;
|
border-bottom: 1px solid var(--border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.repo-icon {
|
.repo-icon {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius);
|
||||||
background-color: rgba(64, 158, 255, 0.1);
|
background: var(--info-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -608,7 +599,7 @@ $transition-base: all 0.3s ease;
|
|||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: #409EFF;
|
color: var(--info-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -618,7 +609,7 @@ $transition-base: all 0.3s ease;
|
|||||||
.repo-name {
|
.repo-name {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $gray-700;
|
color: var(--text-color);
|
||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -631,14 +622,14 @@ $transition-base: all 0.3s ease;
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
.private-tag {
|
.private-tag {
|
||||||
background-color: #f0f9ff;
|
background: var(--info-bg);
|
||||||
color: #1677ff;
|
color: var(--info-color);
|
||||||
border-color: #91d5ff;
|
border-color: var(--info-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-owner {
|
.repo-owner {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $gray-600;
|
color: var(--text-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@ -646,7 +637,7 @@ $transition-base: all 0.3s ease;
|
|||||||
.owner-avatar {
|
.owner-avatar {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -658,11 +649,12 @@ $transition-base: all 0.3s ease;
|
|||||||
|
|
||||||
.repo-description {
|
.repo-description {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $gray-600;
|
color: var(--text-secondary);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin: 0 0 16px 0;
|
margin: 0 0 16px 0;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
@ -673,7 +665,7 @@ $transition-base: all 0.3s ease;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $gray-500;
|
color: var(--text-tertiary);
|
||||||
|
|
||||||
.stat-item {
|
.stat-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -689,7 +681,7 @@ $transition-base: all 0.3s ease;
|
|||||||
|
|
||||||
.repo-actions {
|
.repo-actions {
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
border-top: 1px solid $gray-100;
|
border-top: 1px solid var(--border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
@ -702,28 +694,28 @@ $transition-base: all 0.3s ease;
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
border-radius: $radius-sm;
|
border-radius: var(--border-radius);
|
||||||
transition: all 0.2s ease;
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $gray-100;
|
background-color: var(--background-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-btn {
|
.view-btn {
|
||||||
color: $primary-color;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-btn {
|
.edit-btn {
|
||||||
color: $success-color;
|
color: var(--success-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-btn {
|
.delete-btn {
|
||||||
color: $danger-color;
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -732,15 +724,16 @@ $transition-base: all 0.3s ease;
|
|||||||
.empty-state {
|
.empty-state {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 60px 20px;
|
padding: 60px 20px;
|
||||||
background-color: #fff;
|
background: var(--card-bg);
|
||||||
border-radius: $radius-lg;
|
border-radius: var(--border-radius-lg);
|
||||||
border: 1px dashed $gray-300;
|
border: 1px dashed var(--border-color);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
|
||||||
.empty-icon {
|
.empty-icon {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
background-color: $gray-100;
|
background: var(--background-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -748,19 +741,19 @@ $transition-base: all 0.3s ease;
|
|||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
color: $gray-400;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: $gray-700;
|
color: var(--text-color);
|
||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $gray-500;
|
color: var(--text-secondary);
|
||||||
margin: 0 0 20px 0;
|
margin: 0 0 20px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -791,8 +784,8 @@ $transition-base: all 0.3s ease;
|
|||||||
.search-select,
|
.search-select,
|
||||||
.search-input {
|
.search-input {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-right: 1px solid $gray-300;
|
border-right: 1px solid var(--border-color);
|
||||||
border-bottom: 1px solid $gray-300;
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
|
||||||
::v-deep .el-input__inner {
|
::v-deep .el-input__inner {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@ -800,11 +793,11 @@ $transition-base: all 0.3s ease;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-select {
|
.search-select {
|
||||||
border-radius: $radius-md $radius-md 0 0;
|
border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button {
|
.search-button {
|
||||||
border-radius: 0 0 $radius-md $radius-md;
|
border-radius: 0 0 var(--border-radius-lg) var(--border-radius-lg);
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,178 +0,0 @@
|
|||||||
<template>
|
|
||||||
<header class="common-header">
|
|
||||||
<div class="logo" @click="goHome" style="cursor: pointer;">
|
|
||||||
|
|
||||||
<slot name="logo">
|
|
||||||
<img src="@/assets/imgs/logo.webp" alt="Logo" />
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
<nav class="nav">
|
|
||||||
<slot name="nav"></slot>
|
|
||||||
</nav>
|
|
||||||
<div class="actions">
|
|
||||||
<slot name="actions">
|
|
||||||
<el-dropdown @command="handleCommand" placement="bottom-end">
|
|
||||||
<div class="user-info cursor-pointer">
|
|
||||||
<img
|
|
||||||
:src="getAvatarUrl()"
|
|
||||||
alt="User Avatar"
|
|
||||||
style="
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 8px;
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<span>{{ getUserName() }}</span>
|
|
||||||
</div>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item command="profile">
|
|
||||||
<!-- <el-icon><user /></el-icon> -->
|
|
||||||
<i class="fas fa-user"></i>
|
|
||||||
<span>个人中心</span>
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item command="logout" divided>
|
|
||||||
<!-- <el-icon><arrow-right /></el-icon> -->
|
|
||||||
<i class="fas fa-sign-out-alt"></i>
|
|
||||||
<span>退出登录</span>
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</slot>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 安全获取头像URL
|
|
||||||
defineOptions({ name: "CommonHeader" });
|
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { User, ArrowRight } from "@element-plus/icons-vue";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { authAPI } from "@/services/api";
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// 创建一个函数来安全地访问 localStorage
|
|
||||||
const getAvatarUrl = () => {
|
|
||||||
try {
|
|
||||||
// 检查 window 和 localStorage 是否存在
|
|
||||||
if (typeof window !== "undefined" && window.localStorage) {
|
|
||||||
try {
|
|
||||||
const userItem = window.localStorage.getItem("user");
|
|
||||||
const userInfo = userItem ? JSON.parse(userItem) : {};
|
|
||||||
return userInfo.avatar || "/src/assets/imgs/default_avatar.png";
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("解析用户信息失败:", error);
|
|
||||||
return "/src/assets/imgs/default_avatar.png";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 如果 localStorage 不可用,返回默认头像
|
|
||||||
return "/src/assets/imgs/default_avatar.png";
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("获取头像失败:", error);
|
|
||||||
return "/src/assets/imgs/default_avatar.png";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建一个函数来安全地访问 localStorage 中的用户名
|
|
||||||
const getUserName = () => {
|
|
||||||
try {
|
|
||||||
// 检查 window 和 localStorage 是否存在
|
|
||||||
if (typeof window !== "undefined" && window.localStorage) {
|
|
||||||
return JSON.parse(window.localStorage.getItem("user") || "{}").nickname;
|
|
||||||
}
|
|
||||||
// 如果 localStorage 不可用,返回默认用户名
|
|
||||||
return "用户";
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("获取用户名失败:", error);
|
|
||||||
return "用户";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 点击logo返回首页
|
|
||||||
const goHome = () => {
|
|
||||||
router.push('/');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理下拉菜单命令
|
|
||||||
const handleCommand = (command: string) => {
|
|
||||||
switch (command) {
|
|
||||||
case "profile":
|
|
||||||
handleProfile();
|
|
||||||
break;
|
|
||||||
case "logout":
|
|
||||||
handleLogout();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 个人中心处理函数
|
|
||||||
const handleProfile = () => {
|
|
||||||
// 这里可以根据实际项目情况跳转到个人中心页面
|
|
||||||
ElMessage.success("跳转到个人中心");
|
|
||||||
// 示例:router.push('/profile');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 登出处理函数
|
|
||||||
const handleLogout = async () => {
|
|
||||||
try {
|
|
||||||
// 调用登出API
|
|
||||||
await authAPI.logout();
|
|
||||||
|
|
||||||
// 清除本地登录状态
|
|
||||||
if (typeof window !== "undefined" && window.localStorage) {
|
|
||||||
window.localStorage.removeItem("user");
|
|
||||||
window.localStorage.removeItem("token");
|
|
||||||
window.localStorage.removeItem("isAuthenticated");
|
|
||||||
window.localStorage.removeItem("token");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳转到登录页面
|
|
||||||
router.push("/login");
|
|
||||||
|
|
||||||
ElMessage.success("退出登录成功");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("退出登录失败:", error);
|
|
||||||
ElMessage.error("退出登录失败");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.common-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 64px;
|
|
||||||
padding: 0 24px;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
.logo img {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
.nav {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
|
|
||||||
.user-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cursor-pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -11,13 +11,13 @@
|
|||||||
<!-- 头部 -->
|
<!-- 头部 -->
|
||||||
<el-header class="header">
|
<el-header class="header">
|
||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
|
<div class="header-left">
|
||||||
<el-button
|
<el-button
|
||||||
:icon="isCollapse ? Expand : Fold"
|
:icon="isCollapse ? Expand : Fold"
|
||||||
@click="toggleCollapse"
|
@click="toggleCollapse"
|
||||||
circle
|
circle
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
<div class="header-left">
|
|
||||||
<button
|
<button
|
||||||
class="menu-toggle"
|
class="menu-toggle"
|
||||||
@click="toggleSidebar"
|
@click="toggleSidebar"
|
||||||
@ -39,19 +39,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<button
|
||||||
|
class="action-btn theme-toggle-btn"
|
||||||
|
@click="toggleTheme"
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
>
|
||||||
|
<i class="fas" :class="isDark ? 'fa-sun' : 'fa-moon'"></i>
|
||||||
|
</button>
|
||||||
<button class="action-btn" aria-label="Notifications">
|
<button class="action-btn" aria-label="Notifications">
|
||||||
<svg-icon name="bell" />
|
<i class="fas fa-bell"></i>
|
||||||
<span class="notification-badge">3</span>
|
<span class="notification-badge">3</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="action-btn" aria-label="Settings">
|
|
||||||
<svg-icon name="settings" />
|
|
||||||
</button>
|
|
||||||
<div class="mobile-user">
|
|
||||||
<img
|
|
||||||
src="https://picsum.photos/id/1005/32/32"
|
|
||||||
alt="User avatar"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -70,21 +68,38 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { Expand, Fold } from "@element-plus/icons-vue";
|
import { Expand, Fold } from "@element-plus/icons-vue";
|
||||||
import Sidebar from "./sidebar.vue";
|
import Sidebar from "./sidebar.vue";
|
||||||
|
import { useTheme } from "@/utils/theme";
|
||||||
|
|
||||||
const isCollapse = ref(false);
|
const isCollapse = ref(false);
|
||||||
|
|
||||||
const toggleCollapse = () => {
|
const toggleCollapse = () => {
|
||||||
isCollapse.value = !isCollapse.value;
|
isCollapse.value = !isCollapse.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleSidebar = () => {
|
||||||
|
// 侧边栏切换逻辑(可以根据需要实现)
|
||||||
|
console.log("Toggle sidebar");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 主题切换
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
theme.toggle();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDark = computed(() => theme.isDark());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.layout-container {
|
.layout-container {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--background-color);
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-height {
|
.full-height {
|
||||||
@ -92,28 +107,31 @@ const toggleCollapse = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-sidebar {
|
.main-sidebar {
|
||||||
background-color: #f5f5f5;
|
background: var(--sidebar-bg);
|
||||||
border-right: 1px solid #e0e0e0;
|
border-right: 1px solid var(--border-color);
|
||||||
transition: width 0.3s ease;
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background: var(--el-bg-color);
|
background: var(--header-bg);
|
||||||
border-bottom: 1px solid var(--el-border-color-light);
|
border-bottom: 1px solid var(--border-color);
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
height: 81px;
|
height: 81px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
background: var(--el-bg-color-page);
|
background: var(--background-color);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: calc(100vh - 60px - 60px); /* 减去 header 和 footer 的高度 */
|
height: calc(100vh - 60px - 60px); /* 减去 header 和 footer 的高度 */
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -126,8 +144,8 @@ const toggleCollapse = () => {
|
|||||||
|
|
||||||
.sub-sidebar-wrapper {
|
.sub-sidebar-wrapper {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
background-color: #f9f9f9;
|
background: var(--background-secondary);
|
||||||
border-right: 1px solid #e0e0e0;
|
border-right: 1px solid var(--border-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,16 +153,16 @@ const toggleCollapse = () => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
/* padding: 20px; */
|
/* padding: 20px; */
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: #ffffff;
|
background: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--el-text-color-disabled);
|
color: var(--text-secondary);
|
||||||
background: var(--el-bg-color);
|
background: var(--header-bg);
|
||||||
border-top: 1px solid var(--el-border-color-light);
|
border-top: 1px solid var(--border-color);
|
||||||
padding: 15px 0;
|
padding: 15px 0;
|
||||||
height: 81px;
|
height: 81px;
|
||||||
}
|
}
|
||||||
@ -158,11 +176,11 @@ const toggleCollapse = () => {
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
// color: $dark-gray;
|
color: var(--text-color);
|
||||||
// @include transition(color);
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
// color: $primary-color;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +188,7 @@ const toggleCollapse = () => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,13 +201,23 @@ const toggleCollapse = () => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
// background-color: $light-gray;
|
background: var(--background-secondary);
|
||||||
border-radius: 6px;
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
width: 240px;
|
width: 240px;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--border-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
.search-icon {
|
.search-icon {
|
||||||
// color: $medium-gray;
|
color: var(--text-secondary);
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,9 +227,10 @@ const toggleCollapse = () => {
|
|||||||
outline: none;
|
outline: none;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
// color: $medium-gray;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,28 +243,42 @@ const toggleCollapse = () => {
|
|||||||
.action-btn {
|
.action-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
// color: $dark-gray;
|
color: var(--text-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
// @include transition(color);
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
// color: $primary-color;
|
background: var(--background-hover);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.theme-toggle-btn {
|
||||||
|
i {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-badge {
|
.notification-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -5px;
|
top: -5px;
|
||||||
right: -5px;
|
right: -5px;
|
||||||
// background-color: $danger-color;
|
background: var(--error-color);
|
||||||
color: white;
|
color: var(--text-inverse);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,8 +288,14 @@ const toggleCollapse = () => {
|
|||||||
img {
|
img {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,9 +17,9 @@
|
|||||||
<el-menu
|
<el-menu
|
||||||
:default-active="activeMenu"
|
:default-active="activeMenu"
|
||||||
class="sidebar-menu"
|
class="sidebar-menu"
|
||||||
background-color="#f5f5f5"
|
background-color="var(--sidebar-bg)"
|
||||||
text-color="#333"
|
text-color="var(--text-color)"
|
||||||
active-text-color="#409eff"
|
active-text-color="var(--primary-color)"
|
||||||
:collapse="isCollapse"
|
:collapse="isCollapse"
|
||||||
:collapse-transition="false"
|
:collapse-transition="false"
|
||||||
:router="false"
|
:router="false"
|
||||||
@ -270,13 +270,16 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--sidebar-bg);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
border-bottom: 1px solid #e0e0e0;
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
|
||||||
.logo-img {
|
.logo-img {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@ -285,7 +288,7 @@ onMounted(() => {
|
|||||||
.logo-text {
|
.logo-text {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,20 +299,26 @@ onMounted(() => {
|
|||||||
|
|
||||||
.bottom-settings {
|
.bottom-settings {
|
||||||
// margin-bottom: 20px;
|
// margin-bottom: 20px;
|
||||||
border-bottom: 1px solid #e0e0e0;
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-dropdown-trigger {
|
.user-dropdown-trigger {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s;
|
transition: var(--transition-base);
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -334,26 +343,43 @@ onMounted(() => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 菜单项样式 - 统一管理
|
||||||
.sidebar-menu :deep(.el-menu-item) {
|
.sidebar-menu :deep(.el-menu-item) {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
justify-content: flex-start !important;
|
justify-content: flex-start !important;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--background-hover) !important;
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-menu :deep(.el-menu-item) i {
|
i {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-menu :deep(.el-menu-item.is-active) {
|
.sidebar-menu :deep(.el-menu-item.is-active) {
|
||||||
background-color: #e6f0ff !important;
|
background-color: var(--background-hover) !important;
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.user-nickname {
|
.user-nickname {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: var(--text-color);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px 40px;
|
padding: 20px 40px;
|
||||||
}
|
}
|
||||||
@ -361,11 +387,11 @@ onMounted(() => {
|
|||||||
.debug-info {
|
.debug-info {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background: #f0f0f0;
|
background: var(--warning-bg);
|
||||||
border: 1px dashed #ccc;
|
border: 1px dashed var(--warning-color);
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
<el-menu
|
<el-menu
|
||||||
:default-active="activeSubMenu"
|
:default-active="activeSubMenu"
|
||||||
class="sub-sidebar-menu"
|
class="sub-sidebar-menu"
|
||||||
background-color="#fafafa"
|
background-color="var(--sidebar-bg)"
|
||||||
text-color="#666"
|
text-color="var(--text-secondary)"
|
||||||
active-text-color="#409eff"
|
active-text-color="var(--primary-color)"
|
||||||
:router="false"
|
:router="false"
|
||||||
>
|
>
|
||||||
<el-menu-item
|
<el-menu-item
|
||||||
@ -84,46 +84,47 @@ const handleSubMenuClick = (item: SubMenuItem) => {
|
|||||||
.sub-sidebar-container {
|
.sub-sidebar-container {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #fafafa;
|
background: var(--sidebar-bg);
|
||||||
border-right: 1px solid #e8e8e8;
|
border-right: 1px solid var(--border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-section {
|
.back-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid var(--border-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s;
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #f0f0f0;
|
background-color: var(--background-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-text {
|
.back-text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-title {
|
.module-title {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,28 +136,46 @@ const handleSubMenuClick = (item: SubMenuItem) => {
|
|||||||
height: 44px;
|
height: 44px;
|
||||||
line-height: 44px;
|
line-height: 44px;
|
||||||
margin: 4px 8px;
|
margin: 4px 8px;
|
||||||
border-radius: 6px;
|
border-radius: var(--border-radius);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #f0f0f0;
|
background-color: var(--background-hover) !important;
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
background-color: #e6f0ff;
|
background-color: var(--background-hover) !important;
|
||||||
color: #409eff;
|
color: var(--primary-color) !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-description {
|
.module-description {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-top: 1px solid #e8e8e8;
|
border-top: 1px solid var(--border-color);
|
||||||
background-color: #f5f5f5;
|
background-color: var(--background-secondary);
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #999;
|
color: var(--text-tertiary);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -409,11 +409,12 @@ onActivated(async () => {
|
|||||||
.universal-sub-menu {
|
.universal-sub-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
/* height: 100%; */
|
/* height: 100%; */
|
||||||
background: #f5f5f5;
|
background: var(--background-secondary);
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-sidebar-wrapper {
|
.sub-sidebar-wrapper {
|
||||||
border-right: 1px solid #e4e7ed;
|
border-right: 1px solid var(--border-color);
|
||||||
overflow-y: none;
|
overflow-y: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,6 +423,7 @@ onActivated(async () => {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: calc(100vh - 162px);
|
height: calc(100vh - 162px);
|
||||||
|
background: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 加载状态样式 */
|
/* 加载状态样式 */
|
||||||
@ -432,17 +434,18 @@ onActivated(async () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
background: #ffffff;
|
background: var(--card-bg);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: var(--card-shadow);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border: 2px solid rgba(64, 158, 255, 0.2);
|
border: 2px solid var(--info-bg);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
border-top-color: #409eff;
|
border-top-color: var(--info-color);
|
||||||
animation: spin 1s ease-in-out infinite;
|
animation: spin 1s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +458,7 @@ onActivated(async () => {
|
|||||||
.loading-state p,
|
.loading-state p,
|
||||||
.error-state p {
|
.error-state p {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
color: #606266;
|
color: var(--text-secondary);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,11 +468,11 @@ onActivated(async () => {
|
|||||||
|
|
||||||
.error-icon i {
|
.error-icon i {
|
||||||
font-size: 48px;
|
font-size: 48px;
|
||||||
color: #f56c6c;
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-state h3 {
|
.error-state h3 {
|
||||||
color: #f56c6c;
|
color: var(--error-color);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,15 +493,15 @@ onActivated(async () => {
|
|||||||
.component-loading .loading-spinner {
|
.component-loading .loading-spinner {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border: 2px solid rgba(64, 158, 255, 0.2);
|
border: 2px solid var(--info-bg);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
border-top-color: #409eff;
|
border-top-color: var(--info-color);
|
||||||
animation: spin 1s ease-in-out infinite;
|
animation: spin 1s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-loading p {
|
.component-loading p {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
color: #606266;
|
color: var(--text-secondary);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,10 +515,11 @@ onActivated(async () => {
|
|||||||
.development-component {
|
.development-component {
|
||||||
padding: 60px 40px;
|
padding: 60px 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
background: var(--background-tertiary);
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-xl);
|
||||||
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: var(--shadow-lg);
|
||||||
border: 1px solid #e4e7ed;
|
border: 1px solid var(--border-color);
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.development-icon {
|
.development-icon {
|
||||||
@ -524,46 +528,47 @@ onActivated(async () => {
|
|||||||
|
|
||||||
.development-icon i {
|
.development-icon i {
|
||||||
font-size: 64px;
|
font-size: 64px;
|
||||||
color: #409eff;
|
color: var(--info-color);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.development-component h3 {
|
.development-component h3 {
|
||||||
color: #303133;
|
color: var(--text-color);
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.development-path {
|
.development-path {
|
||||||
color: #606266;
|
color: var(--text-secondary);
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: 'Courier New', monospace;
|
font-family: 'Courier New', monospace;
|
||||||
background: rgba(64, 158, 255, 0.1);
|
background: var(--info-bg);
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 6px;
|
border-radius: var(--border-radius);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
border: 1px solid var(--info-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.development-desc {
|
.development-desc {
|
||||||
color: #909399;
|
color: var(--text-tertiary);
|
||||||
margin: 16px 0 24px 0;
|
margin: 16px 0 24px 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.development-tips {
|
.development-tips {
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: var(--info-bg);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
border-left: 4px solid #409eff;
|
border-left: 4px solid var(--info-color);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.development-tips p {
|
.development-tips p {
|
||||||
color: #606266;
|
color: var(--text-secondary);
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@ -572,11 +577,11 @@ onActivated(async () => {
|
|||||||
.debug-info {
|
.debug-info {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #666;
|
color: var(--text-secondary);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background: #f0f0f0;
|
background: var(--warning-bg);
|
||||||
border: 1px dashed #ccc;
|
border: 1px dashed var(--warning-color);
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -393,6 +393,7 @@ $warning-color: #f59e0b;
|
|||||||
$danger-color: #ef4444;
|
$danger-color: #ef4444;
|
||||||
$purple-color: #8b5cf6;
|
$purple-color: #8b5cf6;
|
||||||
|
|
||||||
|
|
||||||
// 工具类
|
// 工具类
|
||||||
@mixin transition($property: all, $duration: 0.3s, $easing: ease) {
|
@mixin transition($property: all, $duration: 0.3s, $easing: ease) {
|
||||||
transition: $property $duration $easing;
|
transition: $property $duration $easing;
|
||||||
@ -404,14 +405,15 @@ $purple-color: #8b5cf6;
|
|||||||
"medium": 0 4px 6px rgba(0, 0, 0, 0.05),
|
"medium": 0 4px 6px rgba(0, 0, 0, 0.05),
|
||||||
"large": 0 10px 15px rgba(0, 0, 0, 0.07),
|
"large": 0 10px 15px rgba(0, 0, 0, 0.07),
|
||||||
);
|
);
|
||||||
box-shadow: map-get($shadows, $size);
|
// box-shadow: map-get($shadows, $size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主容器
|
// 主容器
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f9fafb;
|
background: var(--background-color);
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 侧边栏
|
// 侧边栏
|
||||||
@ -570,8 +572,8 @@ $purple-color: #8b5cf6;
|
|||||||
|
|
||||||
.main-header {
|
.main-header {
|
||||||
height: $header-height;
|
height: $header-height;
|
||||||
background-color: white;
|
background: var(--header-bg);
|
||||||
@include shadow;
|
box-shadow: var(--shadow-sm);
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -589,11 +591,11 @@ $purple-color: #8b5cf6;
|
|||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $dark-gray;
|
color: var(--text-color);
|
||||||
@include transition(color);
|
@include transition(color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $primary-color;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,13 +615,19 @@ $purple-color: #8b5cf6;
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: $light-gray;
|
background: var(--background-secondary);
|
||||||
border-radius: 6px;
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
width: 240px;
|
width: 240px;
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--border-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
.search-icon {
|
.search-icon {
|
||||||
color: $medium-gray;
|
color: var(--text-secondary);
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,9 +637,10 @@ $purple-color: #8b5cf6;
|
|||||||
outline: none;
|
outline: none;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: $medium-gray;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -644,22 +653,22 @@ $purple-color: #8b5cf6;
|
|||||||
.action-btn {
|
.action-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: $dark-gray;
|
color: var(--text-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
@include transition(color);
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $primary-color;
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-badge {
|
.notification-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -5px;
|
top: -5px;
|
||||||
right: -5px;
|
right: -5px;
|
||||||
background-color: $danger-color;
|
background: var(--error-color);
|
||||||
color: white;
|
color: var(--text-inverse);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
@ -693,23 +702,25 @@ $purple-color: #8b5cf6;
|
|||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
background-color: white;
|
background: var(--card-bg);
|
||||||
@include shadow;
|
box-shadow: var(--card-shadow);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-top: 4px solid $secondary-color;
|
border-top: 4px solid var(--primary-color);
|
||||||
@include transition(transform, 0.3s);
|
transition: var(--transition-base);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-info {
|
.stat-info {
|
||||||
.stat-label {
|
.stat-label {
|
||||||
color: $medium-gray;
|
color: var(--text-secondary);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
margin: 0 0 0.5rem 0;
|
margin: 0 0 0.5rem 0;
|
||||||
}
|
}
|
||||||
@ -718,6 +729,7 @@ $purple-color: #8b5cf6;
|
|||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0 0 0.25rem 0;
|
margin: 0 0 0.25rem 0;
|
||||||
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-change {
|
.stat-change {
|
||||||
@ -728,28 +740,28 @@ $purple-color: #8b5cf6;
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|
||||||
.change-period {
|
.change-period {
|
||||||
color: $medium-gray;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.positive {
|
.positive {
|
||||||
color: $success-color;
|
color: var(--success-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.negative {
|
.negative {
|
||||||
color: $danger-color;
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-icon {
|
.stat-icon {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border-radius: 12px;
|
border-radius: var(--border-radius-lg);
|
||||||
background-color: rgba($secondary-color, 0.1);
|
background: var(--info-bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: $secondary-color;
|
color: var(--info-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -761,22 +773,30 @@ $purple-color: #8b5cf6;
|
|||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
.chart-card {
|
.chart-card {
|
||||||
background-color: white;
|
background: var(--card-bg);
|
||||||
@include shadow;
|
box-shadow: var(--card-shadow);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
.chart-header {
|
.chart-header {
|
||||||
padding: 1.25rem 1.5rem;
|
padding: 1.25rem 1.5rem;
|
||||||
border-bottom: 1px solid $light-gray;
|
border-bottom: 1px solid var(--border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
background: var(--header-bg);
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-filters {
|
.chart-filters {
|
||||||
@ -785,22 +805,24 @@ $purple-color: #8b5cf6;
|
|||||||
|
|
||||||
.filter-btn {
|
.filter-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: 1px solid $light-gray;
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius);
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@include transition(all);
|
color: var(--text-color);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: $secondary-color;
|
border-color: var(--primary-color);
|
||||||
color: $secondary-color;
|
color: var(--primary-color);
|
||||||
|
background: var(--background-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: $secondary-color;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: var(--text-inverse);
|
||||||
border-color: $secondary-color;
|
border-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -809,6 +831,7 @@ $purple-color: #8b5cf6;
|
|||||||
.chart-container {
|
.chart-container {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: var(--card-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -820,34 +843,42 @@ $purple-color: #8b5cf6;
|
|||||||
|
|
||||||
.tasks-card,
|
.tasks-card,
|
||||||
.activities-card {
|
.activities-card {
|
||||||
background-color: white;
|
background: var(--card-bg);
|
||||||
@include shadow;
|
box-shadow: var(--card-shadow);
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-lg);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
padding: 1.25rem 1.5rem;
|
padding: 1.25rem 1.5rem;
|
||||||
border-bottom: 1px solid $light-gray;
|
border-bottom: 1px solid var(--border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
background: var(--header-bg);
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-all {
|
.view-all {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: $secondary-color;
|
color: var(--primary-color);
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@include transition(color);
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $primary-color;
|
color: var(--primary-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -861,15 +892,16 @@ $purple-color: #8b5cf6;
|
|||||||
.task-item,
|
.task-item,
|
||||||
.activity-item {
|
.activity-item {
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
border-bottom: 1px solid $light-gray;
|
border-bottom: 1px solid var(--border-color);
|
||||||
@include transition(background-color);
|
background: var(--card-bg);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $light-gray;
|
background: var(--background-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -894,10 +926,10 @@ $purple-color: #8b5cf6;
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius);
|
||||||
border: 2px solid $medium-gray;
|
border: 2px solid var(--border-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@include transition(all);
|
transition: var(--transition-fast);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
@ -907,15 +939,15 @@ $purple-color: #8b5cf6;
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
border: solid white;
|
border: solid var(--text-inverse);
|
||||||
border-width: 0 2px 2px 0;
|
border-width: 0 2px 2px 0;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input:checked + label {
|
input:checked + label {
|
||||||
background-color: $success-color;
|
background: var(--success-color);
|
||||||
border-color: $success-color;
|
border-color: var(--success-color);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
display: block;
|
display: block;
|
||||||
@ -929,40 +961,41 @@ $purple-color: #8b5cf6;
|
|||||||
.task-title {
|
.task-title {
|
||||||
margin: 0 0 0.25rem 0;
|
margin: 0 0 0.25rem 0;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
@include transition(text-decoration, color);
|
color: var(--text-color);
|
||||||
|
transition: var(--transition-base);
|
||||||
|
|
||||||
&.completed {
|
&.completed {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
color: $medium-gray;
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-date {
|
.task-date {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: $medium-gray;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-priority {
|
.task-priority {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 4px;
|
border-radius: var(--border-radius);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
&.high {
|
&.high {
|
||||||
background-color: rgba(239, 68, 68, 0.1);
|
background: var(--error-bg);
|
||||||
color: $danger-color;
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.medium {
|
&.medium {
|
||||||
background-color: rgba(245, 158, 11, 0.1);
|
background: var(--warning-bg);
|
||||||
color: $warning-color;
|
color: var(--warning-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.low {
|
&.low {
|
||||||
background-color: rgba(16, 185, 129, 0.1);
|
background: var(--success-bg);
|
||||||
color: $success-color;
|
color: var(--success-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -998,7 +1031,7 @@ $purple-color: #8b5cf6;
|
|||||||
.activity-time {
|
.activity-time {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: $medium-gray;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,13 @@
|
|||||||
<el-card class="box-card">
|
<el-card class="box-card">
|
||||||
<div class="header-bar">
|
<div class="header-bar">
|
||||||
<h2>角色管理</h2>
|
<h2>角色管理</h2>
|
||||||
|
<div class="header-actions">
|
||||||
<el-button type="primary" @click="showRoleDialog = true">
|
<el-button type="primary" @click="showRoleDialog = true">
|
||||||
<el-icon><Plus /></el-icon>
|
<el-icon><Plus /></el-icon>
|
||||||
添加角色
|
添加角色
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
<!-- 加载状态 -->
|
||||||
<div v-if="loading" class="loading-state">
|
<div v-if="loading" class="loading-state">
|
||||||
@ -226,15 +228,6 @@ onMounted(() => {
|
|||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.03);
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.03);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-bottom: 6px;
|
|
||||||
border-bottom: 1px solid #f0f1f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-bar h2 {
|
.header-bar h2 {
|
||||||
font-size: 1.18rem;
|
font-size: 1.18rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@ -526,15 +526,17 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.tenant-management-module {
|
.tenant-management-module {
|
||||||
padding: 32px 30px 24px 20px;
|
padding: 20px;
|
||||||
min-height: 600px;
|
min-height: 600px;
|
||||||
background: #fff;
|
background: var(--card-bg);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
transition: var(--transition-base);
|
||||||
}
|
}
|
||||||
.header-bar {
|
.header-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-actions {
|
.header-actions {
|
||||||
@ -551,9 +553,9 @@ onMounted(() => {
|
|||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border: 4px solid #e5e5e5;
|
border: 4px solid var(--border-color);
|
||||||
border-left-color: #409eff;
|
border-left-color: var(--primary-color);
|
||||||
border-radius: 50%;
|
border-radius: var(--border-radius-full);
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@ -578,11 +580,11 @@ onMounted(() => {
|
|||||||
|
|
||||||
.tenant-detail .el-descriptions-item__label {
|
.tenant-detail .el-descriptions-item__label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #606266;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tenant-detail .el-descriptions-item__content {
|
.tenant-detail .el-descriptions-item__content {
|
||||||
color: #303133;
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.audit-content {
|
.audit-content {
|
||||||
@ -599,8 +601,8 @@ onMounted(() => {
|
|||||||
margin: 0 0 16px 0;
|
margin: 0 0 16px 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: var(--text-color);
|
||||||
border-bottom: 2px solid #409eff;
|
border-bottom: 2px solid var(--primary-color);
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,27 +9,51 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table :data="users" style="width: 100%">
|
<el-table :data="users" style="width: 100%">
|
||||||
<el-table-column prop="username" label="用户名" width="150" align="center" />
|
<el-table-column
|
||||||
<el-table-column prop="email" label="邮箱" align="center" min-width="200" />
|
prop="username"
|
||||||
|
label="用户名"
|
||||||
|
width="150"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="email"
|
||||||
|
label="邮箱"
|
||||||
|
align="center"
|
||||||
|
min-width="200"
|
||||||
|
/>
|
||||||
<el-table-column prop="role" label="角色" width="120" align="center">
|
<el-table-column prop="role" label="角色" width="120" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag :type="scope.row.role === 'admin' ? 'danger' : 'primary'">
|
<el-tag :type="scope.row.role === 'admin' ? 'danger' : 'primary'">
|
||||||
{{ scope.row.role === 'admin' ? '管理员' : '普通用户' }}
|
{{ scope.row.role === "admin" ? "管理员" : "普通用户" }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="status" label="状态" width="100">
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">
|
<el-tag
|
||||||
{{ scope.row.status === 'active' ? '启用' : '禁用' }}
|
:type="scope.row.status === 'active' ? 'success' : 'danger'"
|
||||||
|
>
|
||||||
|
{{ scope.row.status === "active" ? "启用" : "禁用" }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="lastLogin" label="最后登录" width="180" align="center" />
|
<el-table-column
|
||||||
|
prop="lastLogin"
|
||||||
|
label="最后登录"
|
||||||
|
width="180"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
<el-table-column label="操作" width="240" align="center" fixed="right">
|
<el-table-column label="操作" width="240" align="center" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
<el-button size="small" @click="handleEdit(scope.row)"
|
||||||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
>编辑</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row)"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -38,7 +62,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from "vue";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: number;
|
id: number;
|
||||||
@ -52,51 +76,52 @@ interface User {
|
|||||||
const users = ref<User[]>([
|
const users = ref<User[]>([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
username: 'admin',
|
username: "admin",
|
||||||
email: 'admin@example.com',
|
email: "admin@example.com",
|
||||||
role: 'admin',
|
role: "admin",
|
||||||
status: 'active',
|
status: "active",
|
||||||
lastLogin: '2025-01-20 10:30:00'
|
lastLogin: "2025-01-20 10:30:00",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
username: 'user1',
|
username: "user1",
|
||||||
email: 'user1@example.com',
|
email: "user1@example.com",
|
||||||
role: 'user',
|
role: "user",
|
||||||
status: 'active',
|
status: "active",
|
||||||
lastLogin: '2025-01-19 15:45:00'
|
lastLogin: "2025-01-19 15:45:00",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
username: 'user2',
|
username: "user2",
|
||||||
email: 'user2@example.com',
|
email: "user2@example.com",
|
||||||
role: 'user',
|
role: "user",
|
||||||
status: 'inactive',
|
status: "inactive",
|
||||||
lastLogin: '2025-01-18 09:20:00'
|
lastLogin: "2025-01-18 09:20:00",
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleAddUser = () => {
|
const handleAddUser = () => {
|
||||||
console.log('添加用户');
|
console.log("添加用户");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (user: User) => {
|
const handleEdit = (user: User) => {
|
||||||
console.log('编辑用户:', user);
|
console.log("编辑用户:", user);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (user: User) => {
|
const handleDelete = (user: User) => {
|
||||||
console.log('删除用户:', user);
|
console.log("删除用户:", user);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss" scoped>
|
||||||
.users-container {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user