fix: 🐛 混合布局页签和菜单无法联动问题修复
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<el-menu
|
||||
ref="menuRef"
|
||||
:default-active="activeMenuIndex"
|
||||
:default-active="activeMenuPath"
|
||||
:collapse="!appStore.sidebar.opened"
|
||||
:background-color="menuThemeProps.backgroundColor"
|
||||
:text-color="menuThemeProps.textColor"
|
||||
@@ -79,80 +79,16 @@ const menuThemeProps = computed(() => {
|
||||
});
|
||||
|
||||
// 计算当前激活的菜单项
|
||||
const activeMenuIndex = computed(() => {
|
||||
const currentPath = currentRoute.path;
|
||||
const activeMenuPath = computed((): string => {
|
||||
const { meta, path } = currentRoute;
|
||||
|
||||
// 如果路由设置了 activeMenu,优先使用
|
||||
if (currentRoute.meta?.activeMenu) {
|
||||
return currentRoute.meta.activeMenu as string;
|
||||
// 如果路由meta中设置了activeMenu,则使用它(用于处理一些特殊情况,如详情页)
|
||||
if (meta?.activeMenu && typeof meta.activeMenu === "string") {
|
||||
return meta.activeMenu;
|
||||
}
|
||||
|
||||
// 在水平模式下(顶部布局),需要找到匹配的顶级菜单
|
||||
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;
|
||||
// 否则使用当前路由路径
|
||||
return path;
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -213,7 +149,7 @@ watch(
|
||||
* 监听激活菜单变化,为包含激活子菜单的父菜单添加样式类
|
||||
*/
|
||||
watch(
|
||||
() => activeMenuIndex.value,
|
||||
() => activeMenuPath.value,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
updateParentMenuStyles();
|
||||
@@ -222,6 +158,20 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
/**
|
||||
* 监听路由变化,确保菜单能随TagsView切换而正确激活
|
||||
*/
|
||||
watch(
|
||||
() => currentRoute.path,
|
||||
(newPath) => {
|
||||
console.log("🔍 Route changed in BasicMenu:", newPath);
|
||||
|
||||
nextTick(() => {
|
||||
updateParentMenuStyles();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 更新父菜单样式 - 为包含激活子菜单的父菜单添加 has-active-child 类
|
||||
*/
|
||||
|
||||
@@ -90,9 +90,25 @@ const processedTopMenus = computed(() => {
|
||||
* @param routePath 点击的菜单路径
|
||||
*/
|
||||
const handleMenuSelect = (routePath: string) => {
|
||||
appStore.activeTopMenu(routePath); // 设置激活的顶部菜单
|
||||
permissionStore.updateSideMenu(routePath); // 更新左侧菜单
|
||||
navigateToFirstLeftMenu(permissionStore.sideMenuRoutes); // 跳转到左侧第一个菜单
|
||||
updateMenuState(routePath);
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新菜单状态 - 同时处理点击和路由变化情况
|
||||
* @param topMenuPath 顶级菜单路径
|
||||
* @param skipNavigation 是否跳过导航(路由变化时为true,点击菜单时为false)
|
||||
*/
|
||||
const updateMenuState = (topMenuPath: string, skipNavigation = false) => {
|
||||
// 不相同才更新,避免重复操作
|
||||
if (topMenuPath !== appStore.activeTopMenuPath) {
|
||||
appStore.activeTopMenu(topMenuPath); // 设置激活的顶部菜单
|
||||
permissionStore.updateSideMenu(topMenuPath); // 更新左侧菜单
|
||||
}
|
||||
|
||||
// 如果是点击菜单且状态已变更,才进行导航
|
||||
if (!skipNavigation) {
|
||||
navigateToFirstLeftMenu(permissionStore.sideMenuRoutes); // 跳转到左侧第一个菜单
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -131,6 +147,21 @@ onMounted(() => {
|
||||
appStore.activeTopMenu(currentTopMenuPath); // 设置激活的顶部菜单
|
||||
permissionStore.updateSideMenu(currentTopMenuPath); // 更新左侧菜单
|
||||
});
|
||||
|
||||
// 监听路由变化,同步更新顶部菜单和左侧菜单的激活状态
|
||||
watch(
|
||||
() => router.currentRoute.value.path,
|
||||
(newPath) => {
|
||||
if (newPath) {
|
||||
// 提取顶级路径
|
||||
const topMenuPath =
|
||||
newPath.split("/").filter(Boolean).length > 1 ? newPath.match(/^\/[^/]+/)?.[0] || "/" : "/";
|
||||
|
||||
// 使用公共方法更新菜单状态,但跳过导航(因为路由已经变化)
|
||||
updateMenuState(topMenuPath, true);
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="layout__sidebar--left" :class="{ 'layout__sidebar--collapsed': !isSidebarOpen }">
|
||||
<el-scrollbar>
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
:default-active="activeLeftMenuPath"
|
||||
:collapse="!isSidebarOpen"
|
||||
:collapse-transition="false"
|
||||
:unique-opened="false"
|
||||
@@ -71,6 +71,9 @@ import AppMain from "../components/AppMain/index.vue";
|
||||
import MenuItem from "../components/Menu/components/MenuItem.vue";
|
||||
import Hamburger from "@/components/Hamburger/index.vue";
|
||||
import variables from "@/styles/variables.module.scss";
|
||||
import { isExternal } from "@/utils/index";
|
||||
import { computed, watch } from "vue";
|
||||
import { useAppStore, usePermissionStore } from "@/store";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
@@ -87,7 +90,7 @@ const { width } = useWindowSize();
|
||||
const isLogoCollapsed = computed(() => width.value < 768);
|
||||
|
||||
// 当前激活的菜单
|
||||
const activeMenu = computed(() => {
|
||||
const activeLeftMenuPath = computed(() => {
|
||||
const { meta, path } = route;
|
||||
// 如果设置了activeMenu,则使用
|
||||
if (meta?.activeMenu && typeof meta.activeMenu === "string") {
|
||||
@@ -101,13 +104,49 @@ const activeMenu = computed(() => {
|
||||
* 所以需要拼接顶级菜单路径
|
||||
*/
|
||||
function resolvePath(routePath: string) {
|
||||
// 如果已经是绝对路径,直接返回
|
||||
if (isExternal(routePath)) {
|
||||
return routePath;
|
||||
}
|
||||
|
||||
if (routePath.startsWith("/")) {
|
||||
return activeTopMenuPath.value + routePath;
|
||||
}
|
||||
// 否则拼接
|
||||
return `${activeTopMenuPath.value}/${routePath}`;
|
||||
}
|
||||
|
||||
// 监听路由变化,确保左侧菜单能随TagsView切换而正确激活
|
||||
watch(
|
||||
() => route.path,
|
||||
(newPath) => {
|
||||
console.log("📍 Route changed in MixLayout:", newPath);
|
||||
|
||||
// 获取顶级路径
|
||||
const topMenuPath =
|
||||
newPath.split("/").filter(Boolean).length > 1 ? newPath.match(/^\/[^/]+/)?.[0] || "/" : "/";
|
||||
|
||||
// 如果当前路径属于当前激活的顶部菜单
|
||||
if (newPath.startsWith(activeTopMenuPath.value)) {
|
||||
console.log("📍 Route is under active top menu, ensuring menu item is activated");
|
||||
}
|
||||
// 如果路径改变了顶级菜单,确保顶部菜单和左侧菜单都更新
|
||||
else if (topMenuPath !== activeTopMenuPath.value) {
|
||||
console.log(
|
||||
"📍 Top menu changed, updating active menu from:",
|
||||
activeTopMenuPath.value,
|
||||
"to:",
|
||||
topMenuPath
|
||||
);
|
||||
|
||||
// 主动更新顶部菜单和左侧菜单
|
||||
const appStore = useAppStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
appStore.activeTopMenu(topMenuPath);
|
||||
permissionStore.updateSideMenu(topMenuPath);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user