style: 支持深色主题

This commit is contained in:
ccnetcore
2026-01-13 22:55:43 +08:00
parent 6b6ddcf550
commit c2f074cb08
8 changed files with 1484 additions and 24 deletions

View File

@@ -3,7 +3,8 @@
"allow": [
"Bash(npx vue-tsc --noEmit)",
"Bash(timeout 60 npx vue-tsc:*)",
"Bash(npm run dev:*)"
"Bash(npm run dev:*)",
"Bash(xargs:*)"
],
"deny": [],
"ask": []

View File

@@ -70,3 +70,87 @@ dotnet run
- [ ] 文件上传
- [ ] 其他...
深色主题样式编写指南
1. 在 src/styles/dark-theme.scss 中添加样式
所有深色主题样式都使用 [data-theme="dark"] 选择器包裹:
/* ========== 组件名称深色样式 ========== */
[data-theme="dark"] {
.your-component-class {
background-color: #1f2937 !important;
color: #e5e7eb !important;
border-color: #374151 !important;
}
}
2. 常用深色主题颜色值
┌─────────────────────────┬───────────────────┐
│ 用途 │ 颜色值 │
├─────────────────────────┼───────────────────┤
│ 主背景 │ #111827
├─────────────────────────┼───────────────────┤
│ 次级背景(卡片、弹窗) │ #1f2937
├─────────────────────────┼───────────────────┤
│ 三级背景hover、表头#374151
├─────────────────────────┼───────────────────┤
│ 主文字 │ #f3f4f6 / #f9fafb
├─────────────────────────┼───────────────────┤
│ 次级文字 │ #e5e7eb
├─────────────────────────┼───────────────────┤
│ 三级文字 │ #9ca3af
├─────────────────────────┼───────────────────┤
│ 边框浅色 │ #374151
├─────────────────────────┼───────────────────┤
│ 边框深色 │ #4b5563
├─────────────────────────┼───────────────────┤
│ 主色调 │ #60a5fa
└─────────────────────────┴───────────────────┘
3. 覆盖 Tailwind 工具类
[data-theme="dark"] {
.bg-white {
background-color: #374151 !important;
}
.text-gray-700 {
color: #e5e7eb !important;
}
}
4. 覆盖 Element Plus 组件
[data-theme="dark"] {
.el-card {
background-color: #1f2937 !important;
border-color: #374151 !important;
}
}
5. 使用 CSS 变量(推荐)
在 src/styles/var.scss 的 [data-theme="dark"] 块中定义变量,然后在组件中使用:
// var.scss
[data-theme="dark"] {
--my-component-bg: #1f2937;
}
// dark-theme.scss
[data-theme="dark"] {
.my-component {
background-color: var(--my-component-bg) !important;
}
}
6. 组件内动态颜色JS 方式)
如果需要在 JS 中动态获取颜色:
<script setup>
import { useColorMode } from '@vueuse/core';
const mode = useColorMode();
const bgColor = computed(() => mode.value === 'dark' ? '#1f2937' : '#ffffff');
</script>

View File

@@ -1,13 +1,18 @@
<!-- 欢迎提示词 -->
<script setup lang="ts">
import { Typewriter } from 'vue-element-plus-x';
import { useColorMode } from '@vueuse/core';
import { useTimeGreeting } from '@/hooks/useTimeGreeting';
import { useUserStore } from '@/stores';
const greeting = useTimeGreeting();
const userStore = useUserStore();
const mode = useColorMode();
const username = computed(() => userStore.userInfo?.username ?? '意心Ai一心只为打造更良心的Ai平台');
// 根据主题动态设置雾化背景色
const fogBgColor = computed(() => mode.value === 'dark' ? '#111827' : '#fff');
</script>
<template>
@@ -22,7 +27,7 @@ const username = computed(() => userStore.userInfo?.username ?? '意心Ai
interval: 45,
}"
:is-fog="{
bgColor: '#fff',
bgColor: fogBgColor,
}"
/>
</div>

View File

@@ -3,16 +3,58 @@ import { useColorMode } from '@vueuse/core';
// 使用 VueUse 的 useColorMode
const mode = useColorMode({
attribute: 'class',
attribute: 'data-theme',
modes: {
light: 'light',
dark: 'dark',
},
storageKey: 'yi-theme',
initialValue: 'light',
});
// 切换主题
function toggleTheme() {
mode.value = mode.value === 'dark' ? 'light' : 'dark';
// 是否正在切换动画中
const isAnimating = ref(false);
// 切换主题(带动画)
async function toggleTheme(event: MouseEvent) {
// 如果正在动画中,不执行
if (isAnimating.value) return;
const newTheme = mode.value === 'dark' ? 'light' : 'dark';
// 检查浏览器是否支持 View Transitions API
if (!document.startViewTransition) {
mode.value = newTheme;
return;
}
isAnimating.value = true;
// 获取点击位置
const x = event.clientX;
const y = event.clientY;
// 计算最大半径(从点击位置到最远角落的距离)
const maxRadius = Math.hypot(
Math.max(x, window.innerWidth - x),
Math.max(y, window.innerHeight - y),
);
// 设置 CSS 变量用于动画
document.documentElement.style.setProperty('--theme-transition-x', `${x}px`);
document.documentElement.style.setProperty('--theme-transition-y', `${y}px`);
document.documentElement.style.setProperty('--theme-transition-radius', `${maxRadius}px`);
// 使用 View Transitions API
const transition = document.startViewTransition(() => {
mode.value = newTheme;
});
try {
await transition.finished;
} finally {
isAnimating.value = false;
}
}
// 主题图标
@@ -24,20 +66,29 @@ const themeIcon = computed(() => {
const themeTitle = computed(() => {
return mode.value === 'dark' ? '切换到浅色模式' : '切换到深色模式';
});
// 是否为深色模式
const isDark = computed(() => mode.value === 'dark');
</script>
<template>
<div class="theme-btn-container" data-tour="theme-btn">
<div
class="theme-btn"
:class="{ 'is-dark': isDark }"
:title="themeTitle"
@click="toggleTheme"
>
<!-- PC端显示文字 + 图标 -->
<el-icon class="theme-icon">
<component :is="`i-ep-${themeIcon}`" />
</el-icon>
<span class="pc-text">主题</span>
<!-- 图标容器 -->
<div class="icon-wrapper">
<el-icon class="theme-icon sun-icon" :class="{ active: isDark }">
<Sunny />
</el-icon>
<el-icon class="theme-icon moon-icon" :class="{ active: !isDark }">
<Moon />
</el-icon>
</div>
<span class="pc-text">{{ isDark ? '深色' : '浅色' }}</span>
</div>
</div>
</template>
@@ -53,29 +104,68 @@ const themeTitle = computed(() => {
gap: 6px;
cursor: pointer;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.2s;
border-radius: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
color: var(--el-text-color-regular);
background-color: transparent;
&:hover {
background-color: var(--el-fill-color-light);
color: var(--el-color-primary);
}
&.is-dark {
.icon-wrapper {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
}
}
.icon-wrapper {
position: relative;
width: 28px;
height: 28px;
border-radius: 50%;
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
}
.theme-icon {
font-size: 18px;
transition: transform 0.3s;
position: absolute;
font-size: 16px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
&:hover .theme-icon {
transform: rotate(20deg);
.sun-icon {
color: #f59e0b;
transform: translateY(30px) rotate(-90deg);
opacity: 0;
&.active {
transform: translateY(0) rotate(0deg);
opacity: 1;
}
}
.moon-icon {
color: #6366f1;
transform: translateY(0) rotate(0deg);
opacity: 1;
&.active {
transform: translateY(-30px) rotate(90deg);
opacity: 0;
}
}
// PC端显示文字
.pc-text {
display: inline;
font-size: 14px;
font-weight: 500;
transition: color 0.3s;
}
}
}

View File

@@ -155,9 +155,9 @@ function toggleMobileMenu() {
<BuyBtn :is-menu-item="true" />
</el-menu-item>
<!-- 主题切换暂不显示 -->
<el-menu-item v-if="false" class="custom-menu-item" index="no-route">
<ThemeBtn :is-menu-item="true" />
<!-- 主题切换 -->
<el-menu-item class="custom-menu-item" index="no-route">
<ThemeBtn />
</el-menu-item>
<!-- 用户头像 -->

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
@use './element-plus';
@use './elx';
@use './guide-tour';
@use './dark-theme';
body{
//overflow: hidden;
}

View File

@@ -219,24 +219,96 @@
/* ========== 暗色模式变量 ========== */
[data-theme="dark"] {
--sidebar-background-color: #1f2937;
--header-background-color: #111827;
/* ========== 颜色系统 ========== */
--color-primary: #60a5fa;
--color-primary-light: #93c5fd;
--color-primary-lighter: #bfdbfe;
--color-primary-dark: #3b82f6;
--color-primary-darker: #2563eb;
/* 辅助色 */
--color-success: #34d399;
--color-warning: #fbbf24;
--color-danger: #f87171;
--color-info: #9ca3af;
/* 中性色 */
--color-white: #ffffff;
--color-black: #000000;
--color-gray-50: #111827;
--color-gray-100: #1f2937;
--color-gray-200: #374151;
--color-gray-300: #4b5563;
--color-gray-400: #6b7280;
--color-gray-500: #9ca3af;
--color-gray-600: #d1d5db;
--color-gray-700: #e5e7eb;
--color-gray-800: #f3f4f6;
--color-gray-900: #f9fafb;
/* ========== 头部区域 ========== */
--header-background-color: #1f2937;
--header-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
--header-text-color: #f3f4f6;
--header-icon-color: #9ca3af;
--header-border: 1px solid #374151;
/* ========== 侧边栏区域 ========== */
--sidebar-background-color: #111827;
--sidebar-active-bg-color: #374151;
--sidebar-active-text-color: #f9fafb;
--sidebar-hover-bg-color: #374151;
--sidebar-text-color: #d1d5db;
--sidebar-icon-color: #9ca3af;
--sidebar-border-color: rgba(255, 255, 255, 0.08);
/* ========== 主要内容区域 ========== */
--content-background-color: #111827;
/* ========== 聊天区域 ========== */
--chat-container-bg: #111827;
--chat-sender-bg: #1f2937;
--chat-bubble-ai-bg: #374151;
--chat-sender-border: 1px solid #374151;
--chat-bubble-user-bg: #3b82f6;
--chat-bubble-user-text: #ffffff;
--chat-bubble-ai-bg: #1f2937;
--chat-bubble-ai-text: #e5e7eb;
--chat-typing-indicator-color: #6b7280;
/* ========== 登录弹框 ========== */
--login-dialog-logo-background: #1f2937;
--login-dialog-logo-text-color: #f3f4f6;
--login-dialog-form-bg: #1f2937;
--login-dialog-input-border: 1px solid #374151;
--login-dialog-input-focus-border: 1px solid #60a5fa;
/* 文字颜色 */
--text-color-primary: #f9fafb;
--text-color-secondary: #e5e7eb;
--text-color-tertiary: #9ca3af;
--text-color-placeholder: #6b7280;
--text-color-disabled: #4b5563;
--text-color-inverse: #111827;
/* 背景颜色 */
--bg-color-primary: #111827;
--bg-color-secondary: #1f2937;
--bg-color-tertiary: #374151;
--bg-color-overlay: rgba(0, 0, 0, 0.7);
--bg-color-mask: rgba(0, 0, 0, 0.6);
/* ========== 边框 ========== */
--border-color-light: #374151;
--border-color-default: #4b5563;
--border-color-dark: #6b7280;
/* ========== 阴影系统 ========== */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3);
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
--shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.3);
}
/* ========== 响应式断点 ========== */