主题色优化
This commit is contained in:
parent
c6e27f1af4
commit
dd9838a16f
|
|
@ -123,6 +123,7 @@ aside {
|
|||
//main-container全局样式
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.components-container {
|
||||
|
|
|
|||
|
|
@ -89,49 +89,74 @@ $--color-info: #909399;
|
|||
|
||||
// 暗黑模式变量
|
||||
html.dark {
|
||||
/* 默认通用 */
|
||||
--el-bg-color: #141414;
|
||||
--el-bg-color-overlay: #1d1e1f;
|
||||
--el-text-color-primary: #ffffff;
|
||||
--el-text-color-regular: #d0d0d0;
|
||||
--el-border-color: #434343;
|
||||
--el-border-color-light: #434343;
|
||||
/* 默认通用 - 优化为更舒适的深色背景 */
|
||||
--el-bg-color: #0f1419;
|
||||
--el-bg-color-overlay: #1a1f24;
|
||||
--el-text-color-primary: #e4e7ed;
|
||||
--el-text-color-regular: #b3b9c4;
|
||||
--el-border-color: #2d3339;
|
||||
--el-border-color-light: #252a2f;
|
||||
|
||||
/* 主体内容区域背景 */
|
||||
--main-bg-color: #0f1419;
|
||||
--content-bg-color: #151a20;
|
||||
|
||||
/* 侧边栏 */
|
||||
--sidebar-bg: #141414;
|
||||
--sidebar-text: #ffffff;
|
||||
--menu-hover: #2d2d2d;
|
||||
--sidebar-bg: #0f1419;
|
||||
--sidebar-text: #e4e7ed;
|
||||
--menu-hover: #1f2529;
|
||||
--menu-active-text: #{$menuActiveText};
|
||||
|
||||
/* 顶部导航栏 */
|
||||
--navbar-bg: #141414;
|
||||
--navbar-text: #ffffff;
|
||||
--navbar-hover: #141414;
|
||||
--navbar-bg: #0f1419;
|
||||
--navbar-text: #e4e7ed;
|
||||
--navbar-hover: #1a1f24;
|
||||
--navbar-border: #252a2f;
|
||||
|
||||
/* 标签栏 */
|
||||
--tags-bg: #141414;
|
||||
--tags-item-bg: #1d1e1f;
|
||||
--tags-item-border: #303030;
|
||||
--tags-item-text: #d0d0d0;
|
||||
--tags-item-hover: #2d2d2d;
|
||||
--tags-close-hover: #64666a;
|
||||
--tags-bg: #0f1419;
|
||||
--tags-item-bg: #1a1f24;
|
||||
--tags-item-border: #252a2f;
|
||||
--tags-item-text: #b3b9c4;
|
||||
--tags-item-hover: #1f2529;
|
||||
--tags-close-hover: #4a5568;
|
||||
|
||||
/* splitpanes 组件暗黑模式变量 */
|
||||
--splitpanes-bg: #141414;
|
||||
--splitpanes-border: #303030;
|
||||
--splitpanes-splitter-bg: #1d1e1f;
|
||||
--splitpanes-splitter-hover-bg: #2d2d2d;
|
||||
--splitpanes-bg: #0f1419;
|
||||
--splitpanes-border: #252a2f;
|
||||
--splitpanes-splitter-bg: #1a1f24;
|
||||
--splitpanes-splitter-hover-bg: #1f2529;
|
||||
|
||||
/* blockquote 暗黑模式变量 */
|
||||
--blockquote-bg: #1d1e1f;
|
||||
--blockquote-border: #303030;
|
||||
--blockquote-text: #d0d0d0;
|
||||
--blockquote-bg: #1a1f24;
|
||||
--blockquote-border: #252a2f;
|
||||
--blockquote-text: #b3b9c4;
|
||||
|
||||
/* Cron 时间表达式 模式变量 */
|
||||
--cron-border: #303030;
|
||||
--cron-border: #252a2f;
|
||||
|
||||
/* splitpanes default-theme 暗黑模式变量 */
|
||||
--splitpanes-default-bg: #141414;
|
||||
--splitpanes-default-bg: #0f1419;
|
||||
|
||||
/* 搜索框暗黑模式变量 */
|
||||
--search-bg: #1a1f24;
|
||||
--search-border: #252a2f;
|
||||
--search-text: #e4e7ed;
|
||||
--search-placeholder: #6b7280;
|
||||
--search-hover-bg: #1f2529;
|
||||
--search-suffix-bg: #252a2f;
|
||||
|
||||
/* 下拉菜单暗黑模式变量 */
|
||||
--dropdown-bg: #1a1f24;
|
||||
--dropdown-border: #252a2f;
|
||||
--dropdown-item-hover: #1f2529;
|
||||
--dropdown-divider: #252a2f;
|
||||
|
||||
/* 主体内容区域 */
|
||||
--main-bg-color: #0f1419;
|
||||
--content-bg-color: #151a20;
|
||||
--card-bg-color: #1a1f24;
|
||||
--card-border-color: #252a2f;
|
||||
|
||||
/* 侧边栏菜单覆盖 */
|
||||
.sidebar-container {
|
||||
|
|
@ -224,5 +249,63 @@ html.dark {
|
|||
background: var(--cron-border);
|
||||
}
|
||||
|
||||
/* 主体内容区域样式覆盖 */
|
||||
.app-main {
|
||||
background-color: var(--main-bg-color) !important;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background-color: var(--content-bg-color);
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.components-container {
|
||||
background-color: var(--content-bg-color);
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
/* 卡片样式夜间模式 */
|
||||
.el-card {
|
||||
background-color: var(--card-bg-color) !important;
|
||||
border-color: var(--card-border-color) !important;
|
||||
color: var(--el-text-color-primary) !important;
|
||||
}
|
||||
|
||||
/* 面包屑导航夜间模式 */
|
||||
.el-breadcrumb {
|
||||
.el-breadcrumb__inner {
|
||||
color: var(--el-text-color-regular) !important;
|
||||
|
||||
&.is-link {
|
||||
color: var(--el-text-color-regular) !important;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 侧边栏菜单项夜间模式优化 */
|
||||
.sidebar-container {
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
&:hover {
|
||||
background-color: rgba(0, 168, 98, 0.12) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
background-color: rgba(0, 168, 98, 0.18) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 导航栏汉堡菜单夜间模式 */
|
||||
.hamburger-container {
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ function addIframe() {
|
|||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: var(--main-bg-color, #ffffff);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
|
|
@ -81,10 +83,32 @@ function addIframe() {
|
|||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #f1f1f1;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #c0c0c0;
|
||||
border-radius: 3px;
|
||||
transition: background-color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #a0a0a0;
|
||||
}
|
||||
}
|
||||
|
||||
// 夜间模式滚动条样式
|
||||
html.dark {
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: var(--el-bg-color, #0f1419);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #4a5568;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background-color: #5a6578;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@
|
|||
style="width: 130px"
|
||||
placeholder="搜索"
|
||||
class="search-input"
|
||||
@keydown.ctrl.k="handleSearch"
|
||||
readonly
|
||||
@click="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><search /></el-icon>
|
||||
|
|
@ -30,18 +31,28 @@
|
|||
</template>
|
||||
</el-input>
|
||||
|
||||
<Icon size="18">
|
||||
<Moon />
|
||||
</Icon>
|
||||
<Icon size="18">
|
||||
<ScanOutline />
|
||||
</Icon>
|
||||
<Icon size="18">
|
||||
<SunnyOutline />
|
||||
</Icon>
|
||||
<Icon size="18">
|
||||
<NotificationsOutline />
|
||||
</Icon>
|
||||
<el-tooltip
|
||||
:content="settingsStore.isDark ? '切换到日间模式' : '切换到夜间模式'"
|
||||
effect="dark"
|
||||
placement="bottom"
|
||||
>
|
||||
<div class="icon-wrapper theme-toggle" @click="handleThemeToggle">
|
||||
<Icon size="18">
|
||||
<SunnyOutline v-if="settingsStore.isDark" />
|
||||
<Moon v-else />
|
||||
</Icon>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<div class="icon-wrapper">
|
||||
<Icon size="18">
|
||||
<ScanOutline />
|
||||
</Icon>
|
||||
</div>
|
||||
<div class="icon-wrapper">
|
||||
<Icon size="18">
|
||||
<NotificationsOutline />
|
||||
</Icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <screenfull id="screenfull" class="right-menu-item hover-effect" />
|
||||
|
|
@ -62,10 +73,9 @@
|
|||
</template>
|
||||
|
||||
<el-dropdown
|
||||
placement="bottom-end"
|
||||
@command="handleCommand"
|
||||
class="avatar-container right-menu-item hover-effect"
|
||||
placement="bottom-end"
|
||||
trigger="click"
|
||||
>
|
||||
<div class="avatar-wrapper">
|
||||
<img :src="userStore.avatar" class="user-avatar" />
|
||||
|
|
@ -137,7 +147,9 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Moon,
|
||||
ScanOutline,
|
||||
|
|
@ -149,6 +161,7 @@ import {
|
|||
ChevronForward,
|
||||
ExitOutline,
|
||||
} from '@vicons/ionicons5'
|
||||
import { initThemeTransitionCSS } from '@/utils/themeTransition'
|
||||
import { Icon } from '@vicons/utils'
|
||||
import Breadcrumb from '@/components/Breadcrumb'
|
||||
import TopNav from '@/components/TopNav'
|
||||
|
|
@ -206,9 +219,34 @@ function toggleTheme() {
|
|||
settingsStore.toggleTheme()
|
||||
}
|
||||
|
||||
// 处理主题切换(带动画)
|
||||
function handleThemeToggle(event) {
|
||||
settingsStore.toggleThemeWithAnimation(event)
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
console.log('handleSearch')
|
||||
// 这里后续会打开搜索框
|
||||
}
|
||||
|
||||
// 监听全局 Ctrl+K 快捷键
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.ctrlKey && e.key === 'k') {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleSearch()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
// 初始化主题切换动画 CSS
|
||||
initThemeTransitionCSS()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
|
|
@ -218,6 +256,7 @@ const handleSearch = () => {
|
|||
position: relative;
|
||||
background: var(--navbar-bg);
|
||||
box-shadow: 0 2px 8px rgba(0, 168, 98, 0.1);
|
||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 46px;
|
||||
|
|
@ -260,6 +299,7 @@ const handleSearch = () => {
|
|||
justify-content: center;
|
||||
gap: 18px;
|
||||
border-right: 1px solid #e0e0e0;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
|
|
@ -311,18 +351,24 @@ const handleSearch = () => {
|
|||
align-items: center;
|
||||
border-radius: 6px;
|
||||
padding: 4px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.user-avatar {
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 8px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.user-nickname {
|
||||
padding: 0 4px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-weight: 500;
|
||||
color: var(--navbar-text, #303133);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
i {
|
||||
|
|
@ -349,6 +395,7 @@ const handleSearch = () => {
|
|||
font-size: 14px;
|
||||
font-family: 'PingFang SC', 'Microsoft YaHei', 'Arial', sans-serif;
|
||||
border-bottom: 1px dashed #f0f0f0;
|
||||
transition: border-color 0.3s ease;
|
||||
|
||||
.user-info-text {
|
||||
margin-left: 6px;
|
||||
|
|
@ -359,6 +406,7 @@ const handleSearch = () => {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,16 +415,20 @@ const handleSearch = () => {
|
|||
height: 36px;
|
||||
margin-right: 8px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.user-info-text div:last-child {
|
||||
color: #909599;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info-item {
|
||||
padding: 4px 0 14px 0;
|
||||
border-bottom: 1px dashed #f0f0f0;
|
||||
transition: border-color 0.3s ease;
|
||||
|
||||
.user-info-item-text {
|
||||
padding: 12px 20px;
|
||||
|
|
@ -384,11 +436,24 @@ const handleSearch = () => {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
transition: all 0.6s ease;
|
||||
}
|
||||
.user-info-item-text:hover {
|
||||
background-color: #f3f3f5;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 6px;
|
||||
margin: 0 4px;
|
||||
color: var(--el-text-color-regular, #606266);
|
||||
|
||||
:deep(svg) {
|
||||
color: inherit;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 168, 98, 0.1);
|
||||
color: var(--el-color-primary, #00a862);
|
||||
|
||||
:deep(svg:last-child) {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -399,20 +464,26 @@ const handleSearch = () => {
|
|||
justify-content: center;
|
||||
|
||||
.logout-item-text {
|
||||
padding: 6px 16px;
|
||||
padding: 8px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
background-color: #f4f5f5;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
color: #767c82;
|
||||
border-radius: 4px;
|
||||
letter-spacing: 1px;
|
||||
transition: all 0.6s ease;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 14px;
|
||||
|
||||
:deep(svg) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #eceded;
|
||||
background-color: rgba(245, 108, 108, 0.1);
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -427,6 +498,25 @@ const handleSearch = () => {
|
|||
--el-input-border-color: transparent;
|
||||
--el-input-hover-border-color: transparent;
|
||||
--el-input-focus-border-color: transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.el-input__wrapper {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
cursor: pointer;
|
||||
caret-color: transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.el-input__wrapper {
|
||||
background-color: rgba(0, 168, 98, 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-input-suffix-text {
|
||||
|
|
@ -435,16 +525,200 @@ const handleSearch = () => {
|
|||
line-height: 25px;
|
||||
padding: 0 4px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.xicon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xicon:hover {
|
||||
color: #00a862;
|
||||
background-color: rgba(0, 168, 98, 0.1);
|
||||
transform: scale(1.2);
|
||||
.icon-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: var(--navbar-text, #5a5e66);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
background-color: rgba(0, 168, 98, 0.1);
|
||||
|
||||
.xicon {
|
||||
color: #00a862;
|
||||
}
|
||||
}
|
||||
|
||||
&.theme-toggle {
|
||||
position: relative;
|
||||
|
||||
// 图标旋转动画
|
||||
:deep(svg) {
|
||||
transition: transform 0.3s ease;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
:deep(svg) {
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss'>
|
||||
// 全局样式,用于 Element Plus 的 popper(挂载在 body 下)
|
||||
.el-dropdown__popper .el-dropdown__list {
|
||||
padding: 8px 14px !important;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
// 夜间模式下的导航栏样式
|
||||
html.dark {
|
||||
.navbar {
|
||||
background: var(--navbar-bg);
|
||||
border-bottom: 1px solid var(--navbar-border, #252a2f);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
|
||||
.right-menu {
|
||||
.icon-container {
|
||||
border-right-color: var(--navbar-border, #252a2f);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
.avatar-wrapper {
|
||||
.user-nickname {
|
||||
color: var(--navbar-text);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--navbar-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索框夜间模式样式
|
||||
.icon-container :deep(.el-input) {
|
||||
--el-input-bg-color: var(--search-bg, #1a1f24);
|
||||
--el-input-text-color: var(--search-text, #e4e7ed);
|
||||
--el-input-placeholder-color: var(--search-placeholder, #6b7280);
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: var(--search-bg, #1a1f24);
|
||||
border: 1px solid var(--search-border, #252a2f);
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--search-hover-bg, #1f2529);
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
color: var(--search-text, #e4e7ed);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--search-placeholder, #6b7280);
|
||||
}
|
||||
}
|
||||
|
||||
.el-input__icon {
|
||||
color: var(--search-placeholder, #6b7280);
|
||||
}
|
||||
}
|
||||
|
||||
.search-input-suffix-text {
|
||||
background-color: var(--search-suffix-bg, #252a2f);
|
||||
color: var(--search-placeholder, #6b7280);
|
||||
border: 1px solid var(--search-border, #252a2f);
|
||||
}
|
||||
|
||||
// 图标夜间模式样式
|
||||
.icon-wrapper {
|
||||
color: var(--navbar-text, #e4e7ed);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 168, 98, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉菜单夜间模式样式
|
||||
.el-dropdown__popper {
|
||||
background-color: var(--dropdown-bg, #1a1f24) !important;
|
||||
border: 1px solid var(--dropdown-border, #252a2f) !important;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4) !important;
|
||||
|
||||
.el-dropdown-menu {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
color: var(--el-text-color-regular, #b3b9c4) !important;
|
||||
|
||||
&:not(.is-disabled):hover,
|
||||
&:not(.is-disabled):focus {
|
||||
background-color: var(--dropdown-item-hover, #1f2529) !important;
|
||||
color: var(--el-color-primary, #00a862) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 用户信息区域夜间模式样式
|
||||
.user-info {
|
||||
border-bottom-color: var(--dropdown-divider, #252a2f);
|
||||
color: var(--el-text-color-primary, #e4e7ed);
|
||||
|
||||
.user-info-text {
|
||||
div:first-child {
|
||||
span {
|
||||
color: var(--el-text-color-primary, #e4e7ed);
|
||||
}
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
color: var(--el-text-color-regular, #b3b9c4);
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
border-color: var(--dropdown-border, #252a2f);
|
||||
}
|
||||
}
|
||||
|
||||
.user-info-item {
|
||||
border-bottom-color: var(--dropdown-divider, #252a2f);
|
||||
|
||||
.user-info-item-text {
|
||||
color: var(--el-text-color-regular, #b3b9c4);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--dropdown-item-hover, #1f2529);
|
||||
color: var(--el-color-primary, #00a862);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logout-item {
|
||||
.logout-item-text {
|
||||
background-color: var(--dropdown-item-hover, #1f2529);
|
||||
color: var(--el-text-color-regular, #b3b9c4);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(245, 108, 108, 0.15);
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import defaultSettings from '@/settings'
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
import { useDynamicTitle } from '@/utils/dynamicTitle'
|
||||
import { toggleThemeWithTransition } from '@/utils/themeTransition'
|
||||
|
||||
const isDark = useDark()
|
||||
const toggleDark = useToggle(isDark)
|
||||
|
|
@ -40,10 +41,17 @@ const useSettingsStore = defineStore(
|
|||
this.title = title
|
||||
useDynamicTitle()
|
||||
},
|
||||
// 切换暗黑模式
|
||||
// 切换暗黑模式(无动画)
|
||||
toggleTheme() {
|
||||
this.isDark = !this.isDark
|
||||
toggleDark()
|
||||
},
|
||||
// 切换暗黑模式(带动画)
|
||||
toggleThemeWithAnimation(event = null) {
|
||||
toggleThemeWithTransition(() => {
|
||||
this.isDark = !this.isDark
|
||||
toggleDark()
|
||||
}, event)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
* 主题切换动画工具函数
|
||||
* 使用 View Transitions API 实现圆形扩展/收缩动画效果
|
||||
*/
|
||||
|
||||
/**
|
||||
* 计算从指定点到视口角落的最大距离
|
||||
* @param {number} x - 点击位置的 x 坐标
|
||||
* @param {number} y - 点击位置的 y 坐标
|
||||
* @returns {number} 最大距离(像素)
|
||||
*/
|
||||
function getDistance(x, y) {
|
||||
const maxX = Math.max(x, window.innerWidth - x)
|
||||
const maxY = Math.max(y, window.innerHeight - y)
|
||||
return Math.sqrt(maxX * maxX + maxY * maxY)
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行主题切换动画
|
||||
* @param {Function} callback - 主题切换的回调函数
|
||||
* @param {MouseEvent} event - 点击事件对象(可选)
|
||||
*/
|
||||
export function toggleThemeWithTransition(callback, event = null) {
|
||||
// 检查浏览器是否支持 View Transitions API
|
||||
if (!document.startViewTransition) {
|
||||
// 不支持时直接执行回调,无动画
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前主题状态(切换前的状态)
|
||||
const wasDark = document.documentElement.classList.contains('dark')
|
||||
|
||||
// 获取点击位置,如果没有事件则使用默认位置(右上角图标位置)
|
||||
let iconX, iconY
|
||||
if (event) {
|
||||
iconX = event.clientX
|
||||
iconY = event.clientY
|
||||
} else {
|
||||
// 默认位置:右上角图标区域(可以根据实际图标位置调整)
|
||||
iconX = window.innerWidth - 100
|
||||
iconY = 25 // 导航栏高度的一半
|
||||
}
|
||||
|
||||
// 左下角位置
|
||||
const bottomLeftX = 0
|
||||
const bottomLeftY = window.innerHeight
|
||||
|
||||
// 计算从不同位置到视口边缘的最大距离
|
||||
const iconRadius = getDistance(iconX, iconY)
|
||||
const bottomLeftRadius = getDistance(bottomLeftX, bottomLeftY)
|
||||
|
||||
// 创建 View Transition
|
||||
const transition = document.startViewTransition(() => {
|
||||
callback()
|
||||
})
|
||||
|
||||
// 设置动画样式
|
||||
transition.ready.then(() => {
|
||||
if (!wasDark) {
|
||||
// 当前是日间模式,切换到夜间模式
|
||||
// 动画效果:从左下角扩展到右上角(图标位置)
|
||||
|
||||
// 旧视图(日间模式)退出:淡出
|
||||
document.documentElement.animate(
|
||||
[
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0 }
|
||||
],
|
||||
{
|
||||
duration: 1000,
|
||||
easing: 'ease-in',
|
||||
pseudoElement: '::view-transition-old(root)'
|
||||
}
|
||||
)
|
||||
|
||||
// 新视图(夜间模式)进入:从左下角圆形扩展到图标位置
|
||||
document.documentElement.animate(
|
||||
[
|
||||
{
|
||||
clipPath: `circle(0px at ${bottomLeftX}px ${bottomLeftY}px)`,
|
||||
opacity: 0
|
||||
},
|
||||
{
|
||||
clipPath: `circle(${iconRadius}px at ${iconX}px ${iconY}px)`,
|
||||
opacity: 1
|
||||
}
|
||||
],
|
||||
{
|
||||
duration: 1000,
|
||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
pseudoElement: '::view-transition-new(root)'
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// 当前是夜间模式,切换到日间模式
|
||||
// 动画效果:从图标位置收缩到左下角
|
||||
|
||||
// 旧视图(夜间模式)退出:从图标位置圆形收缩到左下角
|
||||
document.documentElement.animate(
|
||||
[
|
||||
{
|
||||
clipPath: `circle(${iconRadius}px at ${iconX}px ${iconY}px)`,
|
||||
opacity: 1
|
||||
},
|
||||
{
|
||||
clipPath: `circle(0px at ${bottomLeftX}px ${bottomLeftY}px)`,
|
||||
opacity: 0
|
||||
}
|
||||
],
|
||||
{
|
||||
duration: 600,
|
||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
pseudoElement: '::view-transition-old(root)'
|
||||
}
|
||||
)
|
||||
|
||||
// 新视图(日间模式)进入:从图标位置扩展到全屏
|
||||
document.documentElement.animate(
|
||||
[
|
||||
{
|
||||
clipPath: `circle(0px at ${iconX}px ${iconY}px)`,
|
||||
opacity: 0
|
||||
},
|
||||
{
|
||||
clipPath: `circle(${iconRadius}px at ${iconX}px ${iconY}px)`,
|
||||
opacity: 1
|
||||
}
|
||||
],
|
||||
{
|
||||
duration: 600,
|
||||
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
pseudoElement: '::view-transition-new(root)'
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 View Transitions API 的 CSS
|
||||
* 需要在应用启动时调用
|
||||
*/
|
||||
export function initThemeTransitionCSS() {
|
||||
// 检查是否已经添加过样式
|
||||
if (document.getElementById('theme-transition-style')) {
|
||||
return
|
||||
}
|
||||
|
||||
const style = document.createElement('style')
|
||||
style.id = 'theme-transition-style'
|
||||
style.textContent = `
|
||||
/* View Transitions API 样式 */
|
||||
@view-transition {
|
||||
navigation: auto;
|
||||
}
|
||||
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation-duration: 0.6s;
|
||||
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* 确保动画覆盖整个视口 */
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
`
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue