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,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>
<!-- 用户头像 -->