修复路由和主题

This commit is contained in:
2025-11-02 11:47:51 +08:00
parent bfba8d7ad6
commit 01c47ccbd4
13 changed files with 561 additions and 254 deletions
+193 -40
View File
@@ -9,7 +9,7 @@
:background-color="asideBgColor"
:text-color="asideTextColor"
:active-text-color="activeColor"
active-background-color="activeBgColor"
:active-background-color="activeBgColor"
class="el-menu-vertical-demo"
@select="handleMenuSelect"
:default-active="route.path"
@@ -73,31 +73,61 @@ const loading = ref(true);
const store = useAllDataStore();
const isCollapse = computed(() => store.state.isCollapse);
const width = computed(() => store.state.isCollapse ? '64px' : '180px');
const asideBgColor = ref('#0074e9');
const asideTextColor = ref('#ffffff');
const activeColor = ref ('#fff');
const activeBgColor = ref ('#0074e9');
// 使用 Element Plus 的颜色变量,主题切换时会自动适配
// 这些值会被 el-menu 组件使用,el-menu 会自动适配主题
// 计算是否为暗色主题(响应式)
const isDark = ref(document.documentElement.classList.contains('dark'));
// 更新主题颜色
const updateThemeColors = () => {
try {
const root = document.documentElement;
const bgColor = getComputedStyle(root).getPropertyValue('--aside-bg-color').trim();
const textColor = getComputedStyle(root).getPropertyValue('--aside-text-color').trim();
// 只有当获取到有效值时才更新
if (bgColor) {
asideBgColor.value = bgColor;
}
if (textColor) {
asideTextColor.value = textColor;
}
} catch (e) {
console.warn('更新主题颜色失败:', e);
// 出错时保持默认值
}
// 监听主题变化
const updateTheme = () => {
isDark.value = document.documentElement.classList.contains('dark');
};
// 使用 MutationObserver 监听 html 元素的 class 变化(主题切换时更新)
let themeObserver = null;
// 自定义颜色配置
const BG_COLOR = '#062da3'; // 背景和 active 颜色
const HOVER_COLOR = '#4f84ff'; // hover 颜色
// 将 hex 颜色转换为 rgba
function hexToRgba(hex, alpha = 1) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
// 亮色主题使用自定义背景色,暗色主题使用默认背景
const asideBgColor = computed(() => {
if (isDark.value) {
return 'var(--el-bg-color)';
}
// 亮色主题使用 #062da3 背景
return BG_COLOR;
});
const asideTextColor = computed(() => {
if (isDark.value) {
return 'var(--el-text-color-primary)';
}
// 亮色主题使用白色文字
return '#ffffff';
});
const activeColor = ref('#ffffff'); // active 文字颜色为白色
// hover 和 active 背景色
const activeBgColor = computed(() => {
if (isDark.value) {
return 'rgba(255, 255, 255, 0.1)';
}
// 亮色主题 active 使用 #4f84ff
return HOVER_COLOR;
});
// 不再需要手动更新主题颜色,因为使用了 Element Plus 的 CSS 变量,会自动适配
// 将后端数据转换为前端需要的格式
const transformMenuData = (menus) => {
// 首先映射字段格式
@@ -213,25 +243,25 @@ const fetchMenus = async () => {
}
};
// 组件挂载时获取菜单
// 组件挂载时初始化主题监听和获取菜单
onMounted(() => {
// 先更新主题颜色,确保组件可见
updateThemeColors();
// 初始化主题监听
themeObserver = new MutationObserver(() => {
updateTheme();
});
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
// 初始化时检查一次主题
updateTheme();
// 延迟一点获取菜单,确保主题已初始化
setTimeout(() => {
fetchMenus();
}, 100);
// 监听主题变化事件
window.addEventListener('theme-change', updateThemeColors);
// 监听 CSS 变量变化(MutationObserver
const observer = new MutationObserver(updateThemeColors);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
});
// 计算属性:统一排序所有菜单项(不再区分有无子菜单)
@@ -304,12 +334,18 @@ const findMenuItemByPath = (menus, path) => {
<style scoped lang="less">
.common-aside {
height: 100%;
background-color: var(--aside-bg-color, #0074e9);
// 亮色主题使用 primary 浅色,暗色主题使用默认背景
background-color: var(--el-bg-color);
transition: width 0.3s, background-color 0.3s ease;
overflow: hidden;
position: relative;
display: block !important;
visibility: visible !important;
// 亮色主题下的特殊处理
html:not(.dark) & {
background-color: #062da3;
}
}
.icons {
@@ -318,8 +354,10 @@ const findMenuItemByPath = (menus, path) => {
margin-right: 8px;
font-size: 16px;
transition: var(--transition-fast);
&:hover {
color: var(--primary-color);
// 亮色主题下默认使用白色
html:not(.dark) & {
color: rgba(255, 255, 255, 0.9);
}
}
@@ -330,6 +368,121 @@ h3{
justify-content: center;
font-size: 16px;
font-weight: bold;
color: #fff;
// 亮色主题使用白色,暗色主题使用默认文字颜色
color: var(--el-text-color-primary);
html:not(.dark) & {
color: #ffffff;
}
}
// 菜单项样式:优化 hover 和 active 状态的视觉效果
:deep(.el-menu) {
.el-menu-item {
transition: all 0.3s ease;
position: relative;
// hover 状态:亮色主题使用 #4f84ff
&:hover:not(.is-active) {
background-color: #4f84ff !important;
color: #ffffff !important;
// 确保图标在 hover 状态下也有正确的颜色
.icons {
color: #ffffff !important;
}
}
// active 状态:使用 #4f84ff 背景色,白色文字
&.is-active {
background-color: #4f84ff !important;
color: #ffffff !important;
font-weight: 600;
// 添加左侧边框作为指示器(使用更亮的颜色)
border-left: 3px solid #ffffff;
margin-left: -3px; // 使用负边距补偿,保持文字位置不变
// 确保图标在 active 状态下也有正确的颜色
.icons {
color: #ffffff !important;
}
}
}
// 子菜单项也需要相同的效果
.el-sub-menu {
.el-menu-item {
&:hover:not(.is-active) {
background-color: #4f84ff !important;
color: #ffffff !important;
.icons {
color: #ffffff !important;
}
}
&.is-active {
background-color: #4f84ff !important;
color: #ffffff !important;
font-weight: 600;
// 添加左侧边框作为指示器
border-left: 3px solid #ffffff;
margin-left: -3px; // 使用负边距补偿,保持文字位置不变
.icons {
color: #ffffff !important;
}
}
}
}
// 暗色主题下的特殊处理
html.dark & {
.el-menu-item {
&:hover:not(.is-active) {
background-color: rgba(255, 255, 255, 0.08) !important;
color: var(--el-color-primary-light-3) !important;
.icons {
color: var(--el-color-primary-light-3) !important;
}
}
&.is-active {
background-color: rgba(255, 255, 255, 0.12) !important;
color: var(--el-color-primary-light-3) !important;
border-left-color: var(--el-color-primary-light-3);
.icons {
color: var(--el-color-primary-light-3) !important;
}
}
}
.el-sub-menu {
.el-menu-item {
&:hover:not(.is-active) {
background-color: rgba(255, 255, 255, 0.08) !important;
color: var(--el-color-primary-light-3) !important;
.icons {
color: var(--el-color-primary-light-3) !important;
}
}
&.is-active {
background-color: rgba(255, 255, 255, 0.12) !important;
color: var(--el-color-primary-light-3) !important;
border-left-color: var(--el-color-primary-light-3);
.icons {
color: var(--el-color-primary-light-3) !important;
}
}
}
}
}
}
</style>
+96 -13
View File
@@ -46,7 +46,7 @@
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { ref, computed, onMounted, onUnmounted } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useAllDataStore } from "@/stores";
import { useAuthStore } from "@/stores/auth";
@@ -138,6 +138,73 @@ const handleCommand = (command) => {
router.push('/login');
}
};
// Element Plus 主题切换
const THEME_STORAGE_KEY = 'element-plus-theme';
const isDark = ref(false);
// 初始化主题:从 localStorage 读取或检测系统偏好
const initTheme = () => {
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
if (savedTheme) {
isDark.value = savedTheme === 'dark';
} else {
// 检测系统偏好
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
isDark.value = prefersDark;
}
applyTheme();
};
// 应用主题:在 html 元素上添加/移除 dark 类
const applyTheme = () => {
const htmlElement = document.documentElement;
if (isDark.value) {
htmlElement.classList.add('dark');
localStorage.setItem(THEME_STORAGE_KEY, 'dark');
} else {
htmlElement.classList.remove('dark');
localStorage.setItem(THEME_STORAGE_KEY, 'light');
}
};
// 切换主题
const toggleTheme = () => {
isDark.value = !isDark.value;
applyTheme();
};
// 计算当前主题
const currentTheme = computed(() => isDark.value ? 'dark' : 'light');
// 计算主题图标
const themeIcon = computed(() => isDark.value ? Sunny : Moon);
// 组件挂载时初始化主题
let mediaQuery: MediaQueryList | null = null;
let handleChange: ((e: MediaQueryListEvent) => void) | null = null;
onMounted(() => {
initTheme();
// 监听系统主题变化
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
handleChange = (e: MediaQueryListEvent) => {
// 如果用户没有手动设置主题,则跟随系统
if (!localStorage.getItem(THEME_STORAGE_KEY)) {
isDark.value = e.matches;
applyTheme();
}
};
mediaQuery.addEventListener('change', handleChange);
});
// 组件卸载时清理
onUnmounted(() => {
if (mediaQuery && handleChange) {
mediaQuery.removeEventListener('change', handleChange);
}
});
</script>
<style scoped lang="less">
@@ -148,9 +215,17 @@ const handleCommand = (command) => {
width: 100%;
height: 100%;
padding: 0 40px;
background-color: var(--header-bg-color, #0074e9);
color: var(--header-text-color, #fff);
transition: background-color 0.3s ease, color 0.3s ease;
// 使用 Element Plus 的背景色变量,暗黑模式下自动适配
background-color: var(--el-bg-color);
color: var(--el-text-color-primary);
border-bottom: 1px solid var(--el-border-color-lighter);
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
// 亮色主题下使用 #062da3 背景
html:not(.dark) & {
background-color: #062da3;
border-bottom-color: rgba(79, 132, 255, 0.3);
}
}
.icons {
@@ -164,13 +239,15 @@ const handleCommand = (command) => {
.el-button {
margin-right: 20px;
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
color: var(--header-text-color, #fff);
// 使用 Element Plus 的填充色变量
background-color: var(--el-fill-color-light);
border-color: var(--el-border-color);
color: var(--el-text-color-primary);
&:hover {
background-color: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.3);
background-color: var(--el-fill-color);
border-color: var(--el-border-color-dark);
color: var(--el-color-primary);
}
}
}
@@ -186,8 +263,13 @@ const handleCommand = (command) => {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.3);
// 使用 Element Plus 的边框颜色变量
border: 2px solid var(--el-border-color);
transition: border-color 0.3s ease;
&:hover {
border-color: var(--el-color-primary);
}
}
.el-dropdown-link {
@@ -234,16 +316,17 @@ const handleCommand = (command) => {
}
:deep(.bread) {
// 面包屑使用白色
.el-breadcrumb__inner {
color: var(--header-text-color, #fff) !important;
color: #ffffff !important;
}
.el-breadcrumb__inner.is-link {
color: var(--header-text-color, #fff) !important;
color: #ffffff !important;
cursor: pointer !important;
&:hover {
opacity: 0.8;
color: rgba(255, 255, 255, 0.8) !important;
}
}
}