wip: 临时提交

This commit is contained in:
Ray.Hao
2025-05-27 19:46:24 +08:00
21 changed files with 407 additions and 201 deletions

View File

@@ -14,7 +14,7 @@
<script setup lang="ts">
import { useAppStore, useSettingsStore } from "@/store";
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
import { ThemeMode } from "@/enums/settings/theme.enum";
import { ComponentSize } from "@/enums/settings/layout.enum";

View File

@@ -12,7 +12,7 @@
</template>
<script lang="ts" setup>
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
import logo from "@/assets/logo.png";
defineProps({
@@ -40,56 +40,36 @@ defineProps({
</style>
<style lang="scss">
// 全局样式:针对顶部布局和混合布局的特殊处理
// 顶部布局和混合布局的特殊处理
.layout-top,
.layout-mix {
.logo {
// 在顶部和混合布局中,移除背景色,使其透明
background-color: transparent !important;
.title {
// 确保标题颜色适配当前主题
color: var(--menu-text);
}
}
}
// 顶部布局的响应式宽度控制
.layout-top {
.layout__header-left .logo {
flex-shrink: 0; // 防止Logo被压缩
width: $sidebar-width; // 默认宽度:显示logo+文字
margin-right: 20px;
// 中屏设备优化800px-1100px适度缩小但保持显示文字
@media (min-width: 768px) and (max-width: 1100px) {
width: 180px; // 缩小到180px为菜单腾出空间
margin-right: 16px; // 减少右边距
}
// 小屏设备只显示logo使用收缩宽度
@media (max-width: 767px) {
width: $sidebar-width-collapsed; // 只显示logo54px
margin-right: 12px; // 减少右边距
}
// 宽屏时openSidebar 状态下显示完整Logo+文字
.openSidebar {
&.layout-top .layout__header-left .logo,
&.layout-mix .layout__header-logo .logo {
width: $sidebar-width; // 210px显示logo+文字
}
}
// 混合布局的响应式宽度控制
.layout-mix {
.layout__header-logo .logo {
flex-shrink: 0;
width: $sidebar-width; // 默认宽度:显示logo+文字
// 窄屏时hideSidebar 状态下只显示Logo图标
.hideSidebar {
&.layout-top .layout__header-left .logo,
&.layout-mix .layout__header-logo .logo {
width: $sidebar-width-collapsed; // 54px显示logo
}
// 中屏设备优化800px-1100px适度缩小但保持显示文字
@media (min-width: 768px) and (max-width: 1100px) {
width: 180px; // 缩小到180px为菜单腾出空间
}
// 小屏设备只显示logo使用收缩宽度
@media (max-width: 767px) {
width: $sidebar-width-collapsed; // 只显示logo54px
}
// 隐藏文字,只显示图标
.logo .title {
display: none;
}
}
</style>

View File

@@ -2,7 +2,7 @@
<template>
<el-menu
ref="menuRef"
:default-active="currentRoute.path"
:default-active="activeMenuIndex"
:collapse="!appStore.sidebar.opened"
:background-color="
theme === 'dark' || sidebarColorScheme === SidebarColor.CLASSIC_BLUE
@@ -37,7 +37,6 @@
</template>
<script lang="ts" setup>
import { ref, computed, watch, PropType } from "vue";
import { useRoute } from "vue-router";
import path from "path-browserify";
import type { MenuInstance } from "element-plus";
@@ -79,6 +78,83 @@ const theme = computed(() => settingsStore.theme);
// 获取浅色主题下的侧边栏配色方案
const sidebarColorScheme = computed(() => settingsStore.sidebarColorScheme);
// 计算当前激活的菜单项
const activeMenuIndex = computed(() => {
const currentPath = currentRoute.path;
// 如果路由设置了 activeMenu优先使用
if (currentRoute.meta?.activeMenu) {
return currentRoute.meta.activeMenu as string;
}
// 在水平模式下(顶部布局),需要找到匹配的顶级菜单
if (props.menuMode === "horizontal") {
// 首先尝试简单的路径前缀匹配
const pathSegments = currentPath.split("/").filter(Boolean);
if (pathSegments.length > 0) {
const topLevelPath = `/${pathSegments[0]}`;
// 检查是否有菜单项匹配这个顶级路径
const matchingMenu = props.data.find((menu) => {
const menuPath = resolveFullPath(menu.path);
return menuPath === topLevelPath;
});
if (matchingMenu) {
console.log("🎯 Top menu matched:", topLevelPath, "for route:", currentPath);
return topLevelPath;
}
}
// 如果简单匹配失败,使用详细匹配
const findMatchingTopMenu = (menus: RouteRecordRaw[], targetPath: string): string | null => {
for (const menu of menus) {
const menuPath = resolveFullPath(menu.path);
// 精确匹配
if (targetPath === menuPath) {
return menuPath;
}
// 路径前缀匹配(子路径匹配父菜单)
if (targetPath.startsWith(menuPath + "/")) {
return menuPath;
}
// 如果有子菜单,检查子菜单是否匹配
if (menu.children && menu.children.length > 0) {
const hasMatchingChild = menu.children.some((child) => {
// 对于子菜单,需要正确解析路径
let childPath;
if (child.path.startsWith("/")) {
// 如果子路径是绝对路径,直接使用
childPath = child.path;
} else {
// 如果是相对路径,基于父菜单路径解析
childPath = path.resolve(menuPath, child.path);
}
return targetPath === childPath || targetPath.startsWith(childPath + "/");
});
if (hasMatchingChild) {
return menuPath;
}
}
}
return null;
};
const matchedMenu = findMatchingTopMenu(props.data, currentPath);
if (matchedMenu) {
console.log("🎯 Detailed menu matched:", matchedMenu, "for route:", currentPath);
return matchedMenu;
}
}
// 默认返回当前路径
return currentPath;
});
/**
* 获取完整路径
*
@@ -93,6 +169,11 @@ function resolveFullPath(routePath: string) {
return props.basePath;
}
// 如果 basePath 为空(顶部布局),直接返回 routePath
if (!props.basePath || props.basePath === "") {
return routePath;
}
// 解析路径,生成完整的绝对路径
return path.resolve(props.basePath, routePath);
}
@@ -127,4 +208,54 @@ watch(
}
}
);
/**
* 监听激活菜单变化,为包含激活子菜单的父菜单添加样式类
*/
watch(
() => activeMenuIndex.value,
() => {
nextTick(() => {
updateParentMenuStyles();
});
},
{ immediate: true }
);
/**
* 更新父菜单样式 - 为包含激活子菜单的父菜单添加 has-active-child 类
*/
function updateParentMenuStyles() {
if (!menuRef.value?.$el) return;
const menuEl = menuRef.value.$el as HTMLElement;
// 移除所有现有的 has-active-child 类
const allSubMenus = menuEl.querySelectorAll(".el-sub-menu");
allSubMenus.forEach((subMenu) => {
subMenu.classList.remove("has-active-child");
});
// 查找当前激活的菜单项
const activeMenuItem = menuEl.querySelector(".el-menu-item.is-active");
if (activeMenuItem) {
// 向上查找父级 el-sub-menu 元素
let parent = activeMenuItem.parentElement;
while (parent && parent !== menuEl) {
if (parent.classList.contains("el-sub-menu")) {
parent.classList.add("has-active-child");
}
parent = parent.parentElement;
}
}
}
/**
* 组件挂载后立即更新父菜单样式
*/
onMounted(() => {
nextTick(() => {
updateParentMenuStyles();
});
});
</script>

View File

@@ -21,13 +21,17 @@
@select="handleMenuSelect"
>
<el-menu-item v-for="menuItem in processedTopMenus" :key="menuItem.path" :index="menuItem.path">
<MenuItemTitle v-if="menuItem.meta" :icon="menuItem.meta.icon" :title="menuItem.meta.title" />
<MenuItemContent
v-if="menuItem.meta"
:icon="menuItem.meta.icon"
:title="menuItem.meta.title"
/>
</el-menu-item>
</el-menu>
</template>
<script lang="ts" setup>
import MenuItemTitle from "./components/MenuItemTitle.vue";
import MenuItemContent from "./components/MenuItemContent.vue";
defineOptions({
name: "MixTopMenu",
@@ -81,14 +85,25 @@ const processedTopMenus = computed(() => {
});
});
// 获取当前路由路径的顶部菜单路径
const activeTopMenuPath =
useRoute().path.split("/").filter(Boolean).length > 1
? useRoute().path.match(/^\/[^/]+/)?.[0] || "/"
: "/";
const route = useRoute();
// 设置当前激活的顶部菜单路径
appStore.activeTopMenu(activeTopMenuPath);
// 获取当前路由路径的顶部菜单路径
const getActiveTopMenuPath = () => {
const pathSegments = route.path.split("/").filter(Boolean);
return pathSegments.length > 0 ? `/${pathSegments[0]}` : "/";
};
// 监听路由变化,更新活跃的顶部菜单
watch(
() => route.path,
() => {
const newActiveTopMenuPath = getActiveTopMenuPath();
if (newActiveTopMenuPath !== appStore.activeTopMenuPath) {
appStore.activeTopMenu(newActiveTopMenuPath);
}
},
{ immediate: true }
);
/**
* 处理菜单点击事件,切换顶部菜单并加载对应的左侧菜单
@@ -105,7 +120,11 @@ const handleMenuSelect = (routePath: string) => {
*/
function activateFirstLevelMenu(routePath: string) {
permissionStore.updateSideMenu(routePath); // 更新左侧菜单
navigateToFirstLeftMenu(permissionStore.sideMenuRoutes); // 跳转到左侧第一个菜单
// 使用 nextTick 确保侧边菜单更新完成后再跳转
nextTick(() => {
navigateToFirstLeftMenu(permissionStore.sideMenuRoutes); // 跳转到左侧第一个菜单
});
}
/**
@@ -115,22 +134,41 @@ function activateFirstLevelMenu(routePath: string) {
const navigateToFirstLeftMenu = (menus: RouteRecordRaw[]) => {
if (menus.length === 0) return;
const [firstMenu] = menus;
// 查找第一个可访问的菜单项
const findFirstAccessibleRoute = (routes: RouteRecordRaw[]): RouteRecordRaw | null => {
for (const route of routes) {
// 跳过隐藏的菜单项
if (route.meta?.hidden) continue;
// 如果第一个菜单有子菜单,递归跳转到第一个子菜单
if (firstMenu.children && firstMenu.children.length > 0) {
navigateToFirstLeftMenu(firstMenu.children as RouteRecordRaw[]);
} else if (firstMenu.name) {
// 如果有子菜单,递归查找
if (route.children && route.children.length > 0) {
const childRoute = findFirstAccessibleRoute(route.children);
if (childRoute) return childRoute;
} else if (route.name && route.path) {
// 找到第一个有名称和路径的菜单项
return route;
}
}
return null;
};
const firstRoute = findFirstAccessibleRoute(menus);
if (firstRoute && firstRoute.name) {
console.log("🎯 Navigating to first menu:", firstRoute.name, firstRoute.path);
router.push({
name: firstMenu.name,
name: firstRoute.name,
query:
typeof firstMenu.meta?.params === "object"
? (firstMenu.meta.params as LocationQueryRaw)
typeof firstRoute.meta?.params === "object"
? (firstRoute.meta.params as LocationQueryRaw)
: undefined,
});
}
};
// 当前激活的顶部菜单路径
const activeTopMenuPath = computed(() => appStore.activeTopMenuPath);
onMounted(() => {
topMenus.value = permissionStore.routes.filter((item) => !item.meta || !item.meta.hidden);
});

View File

@@ -22,7 +22,8 @@
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
>
<MenuItemTitle
<MenuItemContent
v-if="onlyOneChild.meta"
:icon="onlyOneChild.meta.icon || item.meta?.icon"
:title="onlyOneChild.meta.title"
/>
@@ -33,7 +34,7 @@
<!--【非叶子节点】显示含多个子节点的父菜单,或始终显示的单子节点 -->
<el-sub-menu v-else :index="resolvePath(item.path)" teleported>
<template #title>
<MenuItemTitle v-if="item.meta" :icon="item.meta.icon" :title="item.meta.title" />
<MenuItemContent v-if="item.meta" :icon="item.meta.icon" :title="item.meta.title" />
</template>
<MenuItem
@@ -48,7 +49,7 @@
</template>
<script setup lang="ts">
import MenuItemTitle from "./MenuItemTitle.vue";
import MenuItemContent from "./MenuItemContent.vue";
defineOptions({
name: "MenuItem",
@@ -140,12 +141,6 @@ function resolvePath(routePath: string) {
.submenu-title-noDropdown {
position: relative;
.el-tooltip {
.sub-el-icon {
margin-left: 19px;
}
}
& > span {
display: inline-block;
visibility: hidden;
@@ -195,4 +190,41 @@ html.sidebar-color-blue {
background-color: $menu-hover;
}
}
// 父菜单激活状态样式 - 当子菜单激活时,父菜单显示激活状态
.el-sub-menu {
// 当父菜单包含激活子菜单时的样式
&.has-active-child .el-sub-menu__title {
color: var(--el-color-primary) !important;
background-color: var(--el-color-primary-light-9) !important;
.menu-icon {
color: var(--el-color-primary) !important;
}
}
// 深色主题下的父菜单激活状态
html.dark & {
&.has-active-child .el-sub-menu__title {
color: var(--el-color-primary-light-3) !important;
background-color: rgba(64, 128, 255, 0.15) !important;
.menu-icon {
color: var(--el-color-primary-light-3) !important;
}
}
}
// 深蓝色侧边栏配色下的父菜单激活状态
html.sidebar-color-blue & {
&.has-active-child .el-sub-menu__title {
color: var(--el-color-primary-light-3) !important;
background-color: rgba(64, 128, 255, 0.2) !important;
.menu-icon {
color: var(--el-color-primary-light-3) !important;
}
}
}
}
</style>

View File

@@ -1,16 +1,16 @@
<template>
<!-- 菜单图标 -->
<template v-if="icon">
<el-icon v-if="isElIcon" class="el-icon">
<el-icon v-if="isElIcon" class="menu-icon">
<component :is="iconComponent" />
</el-icon>
<div v-else :class="`i-svg:${icon}`" />
<div v-else :class="`i-svg:${icon}`" class="menu-icon" />
</template>
<template v-else>
<div class="i-svg:menu" />
<div class="i-svg:menu menu-icon" />
</template>
<!-- 菜单标题 -->
<span v-if="title" class="ml-1">{{ translateRouteTitle(title) }}</span>
<span v-if="title" class="menu-title ml-1">{{ translateRouteTitle(title) }}</span>
</template>
<script setup lang="ts">
@@ -26,28 +26,15 @@ const iconComponent = computed(() => props.icon?.replace("el-icon-", ""));
</script>
<style lang="scss" scoped>
.el-icon {
width: 14px !important;
margin-right: 0 !important;
.menu-icon {
display: inline-flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
margin-right: 5px;
font-size: 18px;
color: currentcolor;
}
[class^="i-svg:"] {
width: 14px;
height: 14px;
color: currentcolor !important;
}
.hideSidebar {
.el-sub-menu,
.el-menu-item {
.el-icon {
margin: 0 auto;
}
}
[class^="i-svg:"] {
margin: 0 auto;
}
}
</style>

View File

@@ -62,7 +62,7 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router";
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { useAppStore, useSettingsStore, useUserStore } from "@/store";
import { SidebarColor, ThemeMode } from "@/enums/settings/theme.enum";

View File

@@ -146,15 +146,13 @@
<script setup lang="ts">
import { DocumentCopy, RefreshLeft, Check } from "@element-plus/icons-vue";
import { markRaw } from "vue";
const { t } = useI18n();
import { LayoutMode } from "@/enums/settings/layout.enum";
import { ThemeMode } from "@/enums/settings/theme.enum";
import { SidebarColor } from "@/enums/settings/theme.enum";
import { LayoutMode, SidebarColor, ThemeMode } from "@/enums";
import { useSettingsStore, usePermissionStore, useAppStore } from "@/store";
import { themeColorPresets } from "@/settings";
// 按钮图标 - 使用markRaw避免响应式警告
// 按钮图标
const copyIcon = markRaw(DocumentCopy);
const resetIcon = markRaw(RefreshLeft);
@@ -175,18 +173,8 @@ const layoutOptions: LayoutOption[] = [
{ value: LayoutMode.MIX, label: t("settings.mixLayout"), className: "mix" },
];
// 颜色预设
const colorPresets = [
"#4080FF",
"#626AEF",
"#ff4500",
"#ff8c00",
"#00ced1",
"#1e90ff",
"#c71585",
"rgb(255, 120, 0)",
"hsva(120, 40, 94)",
];
// 使用统一的颜色预设配置
const colorPresets = themeColorPresets;
const route = useRoute();
const appStore = useAppStore();

View File

@@ -1,6 +1,5 @@
import { computed } from "vue";
import { useAppStore, useSettingsStore } from "@/store";
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
/**
* 布局相关的通用逻辑
@@ -49,14 +48,6 @@ export function useLayout() {
appStore.closeSideBar();
}
// 暂时注释掉这个逻辑,避免影响手动操作
// 监听路由变化,在移动端自动关闭侧边栏
// watchEffect(() => {
// if (appStore.device === "mobile" && appStore.sidebar.opened) {
// appStore.closeSideBar();
// }
// });
return {
currentLayout,
isSidebarOpen,

View File

@@ -15,7 +15,7 @@ import TopLayout from "./views/TopLayout.vue";
import MixLayout from "./views/MixLayout.vue";
import Settings from "./components/Settings/index.vue";
import { LayoutMode } from "@/enums/settings/layout.enum";
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
const { currentLayout } = useLayout();

View File

@@ -102,7 +102,7 @@ console.log("🔍 LeftLayout - isMobile:", isMobile.value);
}
}
/* 移动端样式 - 注意这里需要正确应用到父元素 */
/* 移动端样式 */
.mobile {
.layout__sidebar {
width: $sidebar-width !important;

View File

@@ -58,7 +58,6 @@
</template>
<script setup lang="ts">
import { computed } from "vue";
import { useRoute } from "vue-router";
import { useWindowSize } from "@vueuse/core";
import { useLayout } from "../composables/useLayout";
@@ -136,21 +135,7 @@ console.log("🎨 MixLayout rendered");
flex-shrink: 0;
align-items: center;
justify-content: center;
width: $sidebar-width; // 默认宽度显示logo+文字
height: 100%;
// 小屏设备只显示logo使用收缩宽度
@media (max-width: 767px) {
width: $sidebar-width-collapsed; // 只显示logo54px
}
:deep(.logo) {
height: 100%;
a {
height: 100%;
}
}
}
&-menu {
@@ -177,20 +162,6 @@ console.log("🎨 MixLayout rendered");
line-height: $navbar-height;
border-bottom: none;
@media (min-width: 768px) and (max-width: 1200px) {
padding: 0 12px;
font-size: 14px;
}
@media (max-width: 767px) {
padding: 0 8px;
font-size: 13px;
}
&:hover {
background-color: rgba(255, 255, 255, 0.08);
}
&.is-active {
background-color: rgba(255, 255, 255, 0.12);
border-bottom: 2px solid var(--el-color-primary);
@@ -205,14 +176,6 @@ console.log("🎨 MixLayout rendered");
align-items: center;
height: 100%;
padding: 0 16px;
@media (min-width: 768px) and (max-width: 1200px) {
padding: 0 12px;
}
@media (max-width: 767px) {
padding: 0 8px;
}
}
}
@@ -250,6 +213,7 @@ console.log("🎨 MixLayout rendered");
width: 100%;
height: 50px;
line-height: 50px;
background-color: var(--menu-background);
box-shadow: 0 0 6px -2px var(--el-color-primary);
}
}

View File

@@ -66,18 +66,11 @@ const isLogoCollapsed = computed(() => width.value < 768);
align-items: center;
min-width: 0; // 允许flex收缩
height: 100%;
overflow: hidden; // 防止溢出
// Logo 样式 - 使用SCSS变量管理宽度
// Logo样式由AppLogo组件的全局样式控制
:deep(.logo) {
flex-shrink: 0; // 防止Logo被压缩
width: $sidebar-width; // 默认宽度显示logo+文字
height: $navbar-height;
// 小屏设备只显示logo使用收缩宽度
@media (max-width: 768px) {
width: $sidebar-width-collapsed; // 只显示logo54px
}
}
}
@@ -89,7 +82,7 @@ const isLogoCollapsed = computed(() => width.value < 768);
padding-left: 12px;
}
// 限制菜单高度
// 菜单样式
:deep(.el-menu--horizontal) {
flex: 1;
min-width: 0; // 允许菜单收缩
@@ -102,17 +95,6 @@ const isLogoCollapsed = computed(() => width.value < 768);
.el-menu-item {
height: $navbar-height;
line-height: $navbar-height;
// 响应式菜单项
@media (min-width: 768px) and (max-width: 1200px) {
padding: 0 12px; // 中屏设备减少内边距
font-size: 14px; // 稍微缩小字体
}
@media (max-width: 767px) {
padding: 0 8px; // 小屏设备进一步减少内边距
font-size: 13px;
}
}
.el-sub-menu {
@@ -121,6 +103,11 @@ const isLogoCollapsed = computed(() => width.value < 768);
line-height: $navbar-height;
}
}
// 修复子菜单弹出位置
.el-menu--popup {
min-width: 160px;
}
}
}

View File

@@ -5,7 +5,7 @@ const { pkg } = __APP_INFO__;
// 检查用户的操作系统是否使用深色模式
const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
const defaultSettings: AppSettings = {
export const defaultSettings: AppSettings = {
// 系统Title
title: pkg.name,
// 系统版本
@@ -24,7 +24,7 @@ const defaultSettings: AppSettings = {
size: ComponentSize.DEFAULT,
// 语言
language: LanguageEnum.ZH_CN,
// 主题颜色
// 主题颜色 - 修改此值时需同步修改 src/styles/variables.scss
themeColor: "#4080FF",
// 是否显示水印
showWatermark: false,
@@ -34,4 +34,17 @@ const defaultSettings: AppSettings = {
sidebarColorScheme: SidebarColor.CLASSIC_BLUE,
};
export default defaultSettings;
// 主题色预设 - 经典配色方案
// 注意:修改默认主题色时,需要同步修改 src/styles/variables.scss 中的 primary.base 值
export const themeColorPresets = [
"#4080FF", // Arco Design 蓝 - 现代感强
"#1890FF", // Ant Design 蓝 - 经典商务
"#409EFF", // Element Plus 蓝 - 清新自然
"#FA8C16", // 活力橙 - 温暖友好
"#722ED1", // 优雅紫 - 高端大气
"#13C2C2", // 青色 - 科技感
"#52C41A", // 成功绿 - 活力清新
"#F5222D", // 警示红 - 醒目强烈
"#2F54EB", // 深蓝 - 稳重专业
"#EB2F96", // 品红 - 时尚个性
];

View File

@@ -1,4 +1,4 @@
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
// 导入 Element Plus 中英文语言包
import zhCn from "element-plus/es/locale/lang/zh-cn";

View File

@@ -1,4 +1,4 @@
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
import { SidebarColor, ThemeMode } from "@/enums/settings/theme.enum";
import type { LayoutMode } from "@/enums/settings/layout.enum";
import { applyTheme, generateThemeColors, toggleDarkMode, toggleSidebarColor } from "@/utils/theme";

View File

@@ -10,6 +10,67 @@
background-color: var(--el-color-primary);
}
// 混合布局左侧菜单的hover样式
.layout-mix .layout__sidebar--left .el-menu {
.el-menu-item {
&:hover {
// 极简白主题:使用浅灰色背景
background-color: var(--el-fill-color-light) !important;
}
}
.el-sub-menu__title {
&:hover {
// 极简白主题:使用浅灰色背景
background-color: var(--el-fill-color-light) !important;
}
}
}
// 深色主题或深蓝色侧边栏配色下的左侧菜单hover样式
html.dark .layout-mix .layout__sidebar--left .el-menu,
html.sidebar-color-blue .layout-mix .layout__sidebar--left .el-menu {
.el-menu-item {
&:hover {
// 深色背景使用CSS变量
background-color: var(--menu-hover) !important;
}
}
.el-sub-menu__title {
&:hover {
// 深色背景使用CSS变量
background-color: var(--menu-hover) !important;
}
}
}
// 窄屏时隐藏菜单文字,只显示图标
.hideSidebar {
// Top布局和Mix布局的水平菜单
&.layout-top .layout__header .el-menu--horizontal,
&.layout-mix .layout__header .el-menu--horizontal {
.el-menu-item,
.el-sub-menu__title {
.menu-title,
span:not([class*="i-svg"]):not(.el-icon) {
display: none !important;
}
}
}
// Mix布局的左侧菜单
&.layout-mix .layout__sidebar--left .el-menu {
.el-menu-item,
.el-sub-menu__title {
.menu-title,
span:not([class*="i-svg"]):not(.el-icon) {
display: none !important;
}
}
}
}
// 全局搜索区域样式
.search-container {
padding: 18px 16px 0;

View File

@@ -1,6 +1,7 @@
@forward "element-plus/theme-chalk/src/common/var.scss" with (
$colors: (
"primary": (
// 默认主题色 - 修改此值时需同步修改 src/settings.ts 中的 themeColor
"base": #4080ff,
),
"success": (

View File

@@ -77,6 +77,12 @@ export function applyTheme(colors: Record<string, string>) {
Object.entries(colors).forEach(([key, value]) => {
el.style.setProperty(`--el-color-${key}`, value);
});
// 确保主题色立即生效,强制重新渲染
requestAnimationFrame(() => {
// 触发样式重新计算
el.style.setProperty("--theme-update-trigger", Date.now().toString());
});
}
/**

View File

@@ -86,22 +86,26 @@
</div>
<!-- 第三方登录 -->
<el-divider>
<el-text size="small">{{ t("login.otherLoginMethods") }}</el-text>
</el-divider>
<div class="flex-center gap-x-5 w-full text-[var(--el-text-color-secondary)]">
<CommonWrapper>
<div text-20px class="i-svg:wechat" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:qq" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:github" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:gitee" />
</CommonWrapper>
<div class="third-party-login">
<div class="divider-container">
<div class="divider-line"></div>
<span class="divider-text">{{ t("login.otherLoginMethods") }}</span>
<div class="divider-line"></div>
</div>
<div class="flex-center gap-x-5 w-full text-[var(--el-text-color-secondary)]">
<CommonWrapper>
<div text-20px class="i-svg:wechat" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:qq" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:github" />
</CommonWrapper>
<CommonWrapper>
<div text-20px cursor-pointer class="i-svg:gitee" />
</CommonWrapper>
</div>
</div>
</div>
</template>
@@ -257,3 +261,26 @@ function toOtherForm(type: "register" | "resetPwd") {
emit("update:modelValue", type);
}
</script>
<style lang="scss" scoped>
.third-party-login {
.divider-container {
display: flex;
align-items: center;
margin: 20px 0;
.divider-line {
flex: 1;
height: 1px;
background: linear-gradient(to right, transparent, var(--el-border-color-light), transparent);
}
.divider-text {
padding: 0 16px;
font-size: 12px;
color: var(--el-text-color-regular);
white-space: nowrap;
}
}
}
</style>

View File

@@ -46,7 +46,7 @@
<script setup lang="ts">
import logo from "@/assets/logo.png";
import defaultSettings from "@/settings";
import { defaultSettings } from "@/settings";
import CommonWrapper from "@/components/CommonWrapper/index.vue";
import DarkModeSwitch from "@/components/DarkModeSwitch/index.vue";