主题色优化
This commit is contained in:
parent
c6e27f1af4
commit
dd9838a16f
|
|
@ -123,6 +123,7 @@ aside {
|
||||||
//main-container全局样式
|
//main-container全局样式
|
||||||
.app-container {
|
.app-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.components-container {
|
.components-container {
|
||||||
|
|
|
||||||
|
|
@ -89,49 +89,74 @@ $--color-info: #909399;
|
||||||
|
|
||||||
// 暗黑模式变量
|
// 暗黑模式变量
|
||||||
html.dark {
|
html.dark {
|
||||||
/* 默认通用 */
|
/* 默认通用 - 优化为更舒适的深色背景 */
|
||||||
--el-bg-color: #141414;
|
--el-bg-color: #0f1419;
|
||||||
--el-bg-color-overlay: #1d1e1f;
|
--el-bg-color-overlay: #1a1f24;
|
||||||
--el-text-color-primary: #ffffff;
|
--el-text-color-primary: #e4e7ed;
|
||||||
--el-text-color-regular: #d0d0d0;
|
--el-text-color-regular: #b3b9c4;
|
||||||
--el-border-color: #434343;
|
--el-border-color: #2d3339;
|
||||||
--el-border-color-light: #434343;
|
--el-border-color-light: #252a2f;
|
||||||
|
|
||||||
|
/* 主体内容区域背景 */
|
||||||
|
--main-bg-color: #0f1419;
|
||||||
|
--content-bg-color: #151a20;
|
||||||
|
|
||||||
/* 侧边栏 */
|
/* 侧边栏 */
|
||||||
--sidebar-bg: #141414;
|
--sidebar-bg: #0f1419;
|
||||||
--sidebar-text: #ffffff;
|
--sidebar-text: #e4e7ed;
|
||||||
--menu-hover: #2d2d2d;
|
--menu-hover: #1f2529;
|
||||||
--menu-active-text: #{$menuActiveText};
|
--menu-active-text: #{$menuActiveText};
|
||||||
|
|
||||||
/* 顶部导航栏 */
|
/* 顶部导航栏 */
|
||||||
--navbar-bg: #141414;
|
--navbar-bg: #0f1419;
|
||||||
--navbar-text: #ffffff;
|
--navbar-text: #e4e7ed;
|
||||||
--navbar-hover: #141414;
|
--navbar-hover: #1a1f24;
|
||||||
|
--navbar-border: #252a2f;
|
||||||
|
|
||||||
/* 标签栏 */
|
/* 标签栏 */
|
||||||
--tags-bg: #141414;
|
--tags-bg: #0f1419;
|
||||||
--tags-item-bg: #1d1e1f;
|
--tags-item-bg: #1a1f24;
|
||||||
--tags-item-border: #303030;
|
--tags-item-border: #252a2f;
|
||||||
--tags-item-text: #d0d0d0;
|
--tags-item-text: #b3b9c4;
|
||||||
--tags-item-hover: #2d2d2d;
|
--tags-item-hover: #1f2529;
|
||||||
--tags-close-hover: #64666a;
|
--tags-close-hover: #4a5568;
|
||||||
|
|
||||||
/* splitpanes 组件暗黑模式变量 */
|
/* splitpanes 组件暗黑模式变量 */
|
||||||
--splitpanes-bg: #141414;
|
--splitpanes-bg: #0f1419;
|
||||||
--splitpanes-border: #303030;
|
--splitpanes-border: #252a2f;
|
||||||
--splitpanes-splitter-bg: #1d1e1f;
|
--splitpanes-splitter-bg: #1a1f24;
|
||||||
--splitpanes-splitter-hover-bg: #2d2d2d;
|
--splitpanes-splitter-hover-bg: #1f2529;
|
||||||
|
|
||||||
/* blockquote 暗黑模式变量 */
|
/* blockquote 暗黑模式变量 */
|
||||||
--blockquote-bg: #1d1e1f;
|
--blockquote-bg: #1a1f24;
|
||||||
--blockquote-border: #303030;
|
--blockquote-border: #252a2f;
|
||||||
--blockquote-text: #d0d0d0;
|
--blockquote-text: #b3b9c4;
|
||||||
|
|
||||||
/* Cron 时间表达式 模式变量 */
|
/* Cron 时间表达式 模式变量 */
|
||||||
--cron-border: #303030;
|
--cron-border: #252a2f;
|
||||||
|
|
||||||
/* splitpanes default-theme 暗黑模式变量 */
|
/* 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 {
|
.sidebar-container {
|
||||||
|
|
@ -224,5 +249,63 @@ html.dark {
|
||||||
background: var(--cron-border);
|
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%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-color: var(--main-bg-color, #ffffff);
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-header + .app-main {
|
.fixed-header + .app-main {
|
||||||
|
|
@ -81,10 +83,32 @@ function addIframe() {
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background-color: #f1f1f1;
|
background-color: #f1f1f1;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background-color: #c0c0c0;
|
background-color: #c0c0c0;
|
||||||
border-radius: 3px;
|
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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@
|
||||||
style="width: 130px"
|
style="width: 130px"
|
||||||
placeholder="搜索"
|
placeholder="搜索"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
@keydown.ctrl.k="handleSearch"
|
readonly
|
||||||
|
@click="handleSearch"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<el-icon class="el-input__icon"><search /></el-icon>
|
<el-icon class="el-input__icon"><search /></el-icon>
|
||||||
|
|
@ -30,18 +31,28 @@
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|
||||||
<Icon size="18">
|
<el-tooltip
|
||||||
<Moon />
|
:content="settingsStore.isDark ? '切换到日间模式' : '切换到夜间模式'"
|
||||||
</Icon>
|
effect="dark"
|
||||||
<Icon size="18">
|
placement="bottom"
|
||||||
<ScanOutline />
|
>
|
||||||
</Icon>
|
<div class="icon-wrapper theme-toggle" @click="handleThemeToggle">
|
||||||
<Icon size="18">
|
<Icon size="18">
|
||||||
<SunnyOutline />
|
<SunnyOutline v-if="settingsStore.isDark" />
|
||||||
</Icon>
|
<Moon v-else />
|
||||||
<Icon size="18">
|
</Icon>
|
||||||
<NotificationsOutline />
|
</div>
|
||||||
</Icon>
|
</el-tooltip>
|
||||||
|
<div class="icon-wrapper">
|
||||||
|
<Icon size="18">
|
||||||
|
<ScanOutline />
|
||||||
|
</Icon>
|
||||||
|
</div>
|
||||||
|
<div class="icon-wrapper">
|
||||||
|
<Icon size="18">
|
||||||
|
<NotificationsOutline />
|
||||||
|
</Icon>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <screenfull id="screenfull" class="right-menu-item hover-effect" />
|
<!-- <screenfull id="screenfull" class="right-menu-item hover-effect" />
|
||||||
|
|
@ -62,10 +73,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-dropdown
|
<el-dropdown
|
||||||
|
placement="bottom-end"
|
||||||
@command="handleCommand"
|
@command="handleCommand"
|
||||||
class="avatar-container right-menu-item hover-effect"
|
class="avatar-container right-menu-item hover-effect"
|
||||||
placement="bottom-end"
|
|
||||||
trigger="click"
|
|
||||||
>
|
>
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<img :src="userStore.avatar" class="user-avatar" />
|
<img :src="userStore.avatar" class="user-avatar" />
|
||||||
|
|
@ -137,7 +147,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
|
import { Search } from '@element-plus/icons-vue'
|
||||||
import {
|
import {
|
||||||
Moon,
|
Moon,
|
||||||
ScanOutline,
|
ScanOutline,
|
||||||
|
|
@ -149,6 +161,7 @@ import {
|
||||||
ChevronForward,
|
ChevronForward,
|
||||||
ExitOutline,
|
ExitOutline,
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
|
import { initThemeTransitionCSS } from '@/utils/themeTransition'
|
||||||
import { Icon } from '@vicons/utils'
|
import { Icon } from '@vicons/utils'
|
||||||
import Breadcrumb from '@/components/Breadcrumb'
|
import Breadcrumb from '@/components/Breadcrumb'
|
||||||
import TopNav from '@/components/TopNav'
|
import TopNav from '@/components/TopNav'
|
||||||
|
|
@ -206,9 +219,34 @@ function toggleTheme() {
|
||||||
settingsStore.toggleTheme()
|
settingsStore.toggleTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理主题切换(带动画)
|
||||||
|
function handleThemeToggle(event) {
|
||||||
|
settingsStore.toggleThemeWithAnimation(event)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
console.log('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>
|
</script>
|
||||||
|
|
||||||
<style lang='scss' scoped>
|
<style lang='scss' scoped>
|
||||||
|
|
@ -218,6 +256,7 @@ const handleSearch = () => {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: var(--navbar-bg);
|
background: var(--navbar-bg);
|
||||||
box-shadow: 0 2px 8px rgba(0, 168, 98, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 168, 98, 0.1);
|
||||||
|
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
|
||||||
.hamburger-container {
|
.hamburger-container {
|
||||||
line-height: 46px;
|
line-height: 46px;
|
||||||
|
|
@ -260,6 +299,7 @@ const handleSearch = () => {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
border-right: 1px solid #e0e0e0;
|
border-right: 1px solid #e0e0e0;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
|
@ -311,18 +351,24 @@ const handleSearch = () => {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-nickname {
|
.user-nickname {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
|
color: var(--navbar-text, #303133);
|
||||||
|
transition: color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
|
|
@ -349,6 +395,7 @@ const handleSearch = () => {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: 'PingFang SC', 'Microsoft YaHei', 'Arial', sans-serif;
|
font-family: 'PingFang SC', 'Microsoft YaHei', 'Arial', sans-serif;
|
||||||
border-bottom: 1px dashed #f0f0f0;
|
border-bottom: 1px dashed #f0f0f0;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
|
||||||
.user-info-text {
|
.user-info-text {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
|
|
@ -359,6 +406,7 @@ const handleSearch = () => {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,16 +415,20 @@ const handleSearch = () => {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info-text div:last-child {
|
.user-info-text div:last-child {
|
||||||
color: #909599;
|
color: #909599;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info-item {
|
.user-info-item {
|
||||||
padding: 4px 0 14px 0;
|
padding: 4px 0 14px 0;
|
||||||
border-bottom: 1px dashed #f0f0f0;
|
border-bottom: 1px dashed #f0f0f0;
|
||||||
|
transition: border-color 0.3s ease;
|
||||||
|
|
||||||
.user-info-item-text {
|
.user-info-item-text {
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
|
|
@ -384,11 +436,24 @@ const handleSearch = () => {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.6s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
border-radius: 6px;
|
||||||
.user-info-item-text:hover {
|
margin: 0 4px;
|
||||||
background-color: #f3f3f5;
|
color: var(--el-text-color-regular, #606266);
|
||||||
border-radius: 4px;
|
|
||||||
|
: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;
|
justify-content: center;
|
||||||
|
|
||||||
.logout-item-text {
|
.logout-item-text {
|
||||||
padding: 6px 16px;
|
padding: 8px 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #f4f5f5;
|
background-color: #f4f5f5;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
color: #767c82;
|
color: #767c82;
|
||||||
border-radius: 4px;
|
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
transition: all 0.6s ease;
|
transition: all 0.3s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
:deep(svg) {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&: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-border-color: transparent;
|
||||||
--el-input-hover-border-color: transparent;
|
--el-input-hover-border-color: transparent;
|
||||||
--el-input-focus-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 {
|
.search-input-suffix-text {
|
||||||
|
|
@ -435,16 +525,200 @@ const handleSearch = () => {
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xicon {
|
.xicon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.xicon:hover {
|
.icon-wrapper {
|
||||||
color: #00a862;
|
display: inline-flex;
|
||||||
background-color: rgba(0, 168, 98, 0.1);
|
align-items: center;
|
||||||
transform: scale(1.2);
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import defaultSettings from '@/settings'
|
import defaultSettings from '@/settings'
|
||||||
import { useDark, useToggle } from '@vueuse/core'
|
import { useDark, useToggle } from '@vueuse/core'
|
||||||
import { useDynamicTitle } from '@/utils/dynamicTitle'
|
import { useDynamicTitle } from '@/utils/dynamicTitle'
|
||||||
|
import { toggleThemeWithTransition } from '@/utils/themeTransition'
|
||||||
|
|
||||||
const isDark = useDark()
|
const isDark = useDark()
|
||||||
const toggleDark = useToggle(isDark)
|
const toggleDark = useToggle(isDark)
|
||||||
|
|
@ -40,10 +41,17 @@ const useSettingsStore = defineStore(
|
||||||
this.title = title
|
this.title = title
|
||||||
useDynamicTitle()
|
useDynamicTitle()
|
||||||
},
|
},
|
||||||
// 切换暗黑模式
|
// 切换暗黑模式(无动画)
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
this.isDark = !this.isDark
|
this.isDark = !this.isDark
|
||||||
toggleDark()
|
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