zhitan-vue/src/components/TopNav/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
zhitan-vue/src/layout/components/Sidebar/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
zhitan-vue/src/permission.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
zhitan-vue/src/store/modules/permission.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
zhitan-vue/src/components/TopNav/index.vue
@@ -135,6 +135,28 @@ setTimeout(updateScrollButtons, 300); } /** * 查找最深层的子菜单(叶子节点) * 递归查找第一个没有children的子菜单 */ function findDeepestLeafMenu(route) { if (!route) return null; // 如果没有子菜单或子菜单为空,则返回当前路由 if (!route.children || route.children.length === 0) { return route; } // 找到第一个非隐藏的子菜单 const firstVisibleChild = route.children.find(child => !child.hidden); if (!firstVisibleChild) { return route; // 如果所有子菜单都是隐藏的,返回当前路由 } // 递归查找这个子菜单的最深层子菜单 return findDeepestLeafMenu(firstVisibleChild); } function handleSelect(key, keyPath) { currentIndex.value = key; const route = routers.value.find(item => item.path === key); @@ -156,12 +178,70 @@ if (route && route.children && route.children.length > 0) { // 有子路由,显示侧边栏 activeRoutes(key); const firstChild = route.children[0]; const path = firstChild.path.startsWith('/') ? firstChild.path : `${key}/${firstChild.path}`; if (firstChild.query) { router.push({ path, query: firstChild.query }); // 按照正确的路径构建层级,这里是特殊处理 let targetPath = key; // 从当前点击的菜单路径开始 let targetQuery = null; let currentNode = route; let pathSegments = []; // 当前路径是第一段 pathSegments.push(currentNode.path); // 逐层添加子路径 while (currentNode.children && currentNode.children.length > 0) { const firstChild = currentNode.children.find(child => !child.hidden); if (!firstChild) break; // 跳过ParentView类型的中间节点,直接使用其子节点的path if (firstChild.component === 'ParentView' || firstChild.component.name === 'ParentView') { currentNode = firstChild; pathSegments.push(firstChild.path); continue; } // 普通节点处理 currentNode = firstChild; // 如果路径不是以/开头,则添加到路径片段中 if (!firstChild.path.startsWith('/')) { pathSegments.push(firstChild.path); } else { // 如果是绝对路径,则替换之前所有路径 pathSegments = [firstChild.path]; } targetQuery = firstChild.query; // 如果到达叶子节点(没有子节点),则结束查找 if (!firstChild.children || firstChild.children.length === 0) { break; } } // 构建最终路径 if (pathSegments.length > 0) { // 如果第一段不是以/开头,添加/ if (!pathSegments[0].startsWith('/')) { pathSegments[0] = '/' + pathSegments[0]; } // 组合路径 - 把数组中所有路径拼接起来,如果某段包含完整路径(以/开头)则从该段重新开始 targetPath = pathSegments.reduce((fullPath, segment, index) => { if (segment.startsWith('/')) { return segment; } else if (index === 0) { return segment; } else { return `${fullPath}/${segment}`; } }); } // 导航到目标路由 if (targetQuery) { router.push({ path: targetPath, query: targetQuery }); } else { router.push({ path }); router.push({ path: targetPath }); } } else { // 没有子路由,隐藏侧边栏 zhitan-vue/src/layout/components/Sidebar/SidebarItem.vue
@@ -9,7 +9,7 @@ </app-link> </template> <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported> <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported @click="handleSubMenuClick"> <template v-if="item.meta" #title> <svg-icon :icon-class="item.meta && item.meta.icon" /> <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span> @@ -31,6 +31,9 @@ import { isExternal } from '@/utils/validate' import AppLink from './Link' import { getNormalPath } from '@/utils/ruoyi' import { useRouter } from 'vue-router' const router = useRouter(); const props = defineProps({ // route object @@ -50,6 +53,100 @@ const onlyOneChild = ref({}); /** * 查找最深层的子菜单(叶子节点) * 递归查找第一个没有children的子菜单 */ function findDeepestLeafMenu(route) { if (!route) return null; // 如果没有子菜单或子菜单为空,则返回当前路由 if (!route.children || route.children.length === 0) { return route; } // 找到第一个非隐藏的子菜单 const firstVisibleChild = route.children.find(child => !child.hidden); if (!firstVisibleChild) { return route; // 如果所有子菜单都是隐藏的,返回当前路由 } // 递归查找这个子菜单的最深层子菜单 return findDeepestLeafMenu(firstVisibleChild); } // 处理子菜单点击 function handleSubMenuClick(e) { // 阻止事件冒泡 e.stopPropagation(); // 如果点击的是子菜单标题,则自动导航到最深层的子菜单 if (e.target.closest('.el-sub-menu__title')) { // 按照正确的路径构建层级 let currentNode = props.item; let pathSegments = []; // 首先添加当前节点的路径 if (currentNode.path) { pathSegments.push(currentNode.path); } // 逐层添加子路径 while (currentNode.children && currentNode.children.length > 0) { const firstChild = currentNode.children.find(child => !child.hidden); if (!firstChild) break; // 跳过ParentView类型的中间节点 if (firstChild.component === 'ParentView' || firstChild.component.name === 'ParentView') { currentNode = firstChild; pathSegments.push(firstChild.path); continue; } // 普通节点处理 currentNode = firstChild; // 如果路径不是以/开头,则添加到路径片段中 if (!firstChild.path.startsWith('/')) { pathSegments.push(firstChild.path); } else { // 如果是绝对路径,则替换之前所有路径 pathSegments = [firstChild.path]; } // 如果到达叶子节点,则结束查找 if (!firstChild.children || firstChild.children.length === 0) { break; } } // 构建最终路径 if (pathSegments.length > 0) { // 如果第一段不是以/开头,添加/ if (!pathSegments[0].startsWith('/')) { pathSegments[0] = '/' + pathSegments[0]; } // 组合路径 const targetPath = pathSegments.reduce((fullPath, segment, index) => { if (segment.startsWith('/')) { return segment; } else if (index === 0) { return segment; } else { return `${fullPath}/${segment}`; } }); // 导航到目标路由,如果有查询参数则添加 if (currentNode.query) { router.push({ path: targetPath, query: currentNode.query }); } else { router.push({ path: targetPath }); } } } } function hasOneShowingChild(children = [], parent) { if (!children) { children = []; @@ -65,10 +162,15 @@ }) // When there is only one child router, the child router is displayed by default if (showingChildren.length === 1) { if (showingChildren.length === 1 && !showingChildren[0].children) { return true } // If the single child also has children, don't treat it as a single showing child if (showingChildren.length === 1 && showingChildren[0].children && showingChildren[0].children.length > 0) { return false } // Show parent if there are no child router to display if (showingChildren.length === 0) { onlyOneChild.value = { ...parent, path: '', noShowingChildren: true } zhitan-vue/src/layout/components/Sidebar/index.vue
@@ -162,12 +162,13 @@ } :deep(.scrollbar-wrapper) { height: calc(100% - 290px) !important; height: calc(100% - 220px) !important; overflow-x: hidden !important; } :deep(.scrollbar-view) { height: 100%; padding-bottom: 20px; } :deep(.el-scrollbar__bar.is-vertical) { @@ -237,6 +238,28 @@ height: 38px !important; line-height: 38px !important; } // Add styling for deeply nested submenus (level 3+) .el-sub-menu { .el-menu-item { padding-left: 65px !important; &.is-active { padding-left: 65px !important; } } // Level 4 .el-menu { .el-menu-item { padding-left: 85px !important; &.is-active { padding-left: 85px !important; } } } } } } } @@ -250,7 +273,7 @@ // 底部用户区域样式 .sidebar-footer { position: absolute; bottom: 72px; bottom: 0; left: 0; width: 100%; border-top: 1px solid rgba(255, 255, 255, 0.1); zhitan-vue/src/permission.js
@@ -14,6 +14,75 @@ const whiteList = ['/login', '/register', '/energy'] /** * 查找最深层的子菜单并构建完整路径 */ function findDeepestPath(route) { if (!route) return { path: null, query: null }; // 首先添加当前节点的路径 let currentNode = route; let pathSegments = []; if (currentNode.path) { pathSegments.push(currentNode.path); } // 逐层添加子路径 while (currentNode.children && currentNode.children.length > 0) { const firstChild = currentNode.children.find(child => !child.hidden); if (!firstChild) break; // 跳过ParentView类型的中间节点 if (firstChild.component === 'ParentView' || (typeof firstChild.component === 'object' && firstChild.component.name === 'ParentView')) { currentNode = firstChild; pathSegments.push(firstChild.path); continue; } // 普通节点处理 currentNode = firstChild; // 如果路径不是以/开头,则添加到路径片段中 if (!firstChild.path.startsWith('/')) { pathSegments.push(firstChild.path); } else { // 如果是绝对路径,则替换之前所有路径 pathSegments = [firstChild.path]; } // 如果到达叶子节点,则结束查找 if (!firstChild.children || firstChild.children.length === 0) { break; } } // 构建最终路径 let targetPath = ''; if (pathSegments.length > 0) { // 如果第一段不是以/开头,添加/ if (!pathSegments[0].startsWith('/')) { pathSegments[0] = '/' + pathSegments[0]; } // 组合路径 targetPath = pathSegments.reduce((fullPath, segment, index) => { if (segment.startsWith('/')) { return segment; } else if (index === 0) { return segment; } else { return `${fullPath}/${segment}`; } }); } return { path: targetPath, query: currentNode.query }; } router.beforeEach((to, from, next) => { NProgress.start() if (getToken()) { @@ -45,11 +114,23 @@ if (topMenus.length > 0) { // 跳转到第一个菜单 const firstMenu = topMenus[0] if (firstMenu.children && firstMenu.children.length > 0) { // 有子菜单,跳转到第一个子菜单 // 查找最深层的子菜单并构建路径 const { path, query } = findDeepestPath(firstMenu); if (path) { // 有最深层子菜单,跳转到该菜单 if (query) { next({ path, query, replace: true }); } else { next({ path, replace: true }); } return; } else if (firstMenu.children && firstMenu.children.length > 0) { // 使用原有逻辑 const firstChild = firstMenu.children[0] const path = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}` next({ path: path, replace: true }) const childPath = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}` next({ path: childPath, replace: true }) return } else { // 没有子菜单,直接跳转 @@ -75,11 +156,23 @@ if (topMenus.length > 0) { // 跳转到第一个菜单 const firstMenu = topMenus[0] if (firstMenu.children && firstMenu.children.length > 0) { // 有子菜单,跳转到第一个子菜单 // 查找最深层的子菜单并构建路径 const { path, query } = findDeepestPath(firstMenu); if (path) { // 有最深层子菜单,跳转到该菜单 if (query) { next({ path, query, replace: true }); } else { next({ path, replace: true }); } return; } else if (firstMenu.children && firstMenu.children.length > 0) { // 使用原有逻辑 const firstChild = firstMenu.children[0] const path = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}` next({ path: path, replace: true }) const childPath = firstMenu.path.endsWith('/') ? firstMenu.path + firstChild.path : `${firstMenu.path}/${firstChild.path}` next({ path: childPath, replace: true }) return } else { // 没有子菜单,直接跳转 @@ -88,6 +181,26 @@ } } } // 自动处理带有重定向的路由 if (to.matched.length > 0 && to.matched[0].path === to.path) { const currentRouteConfig = router.getRoutes().find(r => r.path === to.path); if (currentRouteConfig && currentRouteConfig.children && currentRouteConfig.children.length > 0) { // 有子路由,自动导航到最深层子菜单 const { path, query } = findDeepestPath(currentRouteConfig); if (path && path !== to.path) { if (query) { next({ path, query, replace: true }); } else { next({ path, replace: true }); } return; } } } next() } } zhitan-vue/src/store/modules/permission.js
@@ -56,8 +56,13 @@ }) // 遍历后台传来的路由字符串,转换为组件对象 function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false, parentRoute = null) { return asyncRouterMap.filter(route => { // 设置父路由引用 if (parentRoute) { route.parent = parentRoute; } if (type && route.children) { route.children = filterChildren(route.children) } @@ -82,7 +87,8 @@ } } if (route.children != null && route.children && route.children.length) { route.children = filterAsyncRouter(route.children, route, type) // 将当前路由作为父路由传递给子路由 route.children = filterAsyncRouter(route.children, route, type, route) } else { delete route['children'] delete route['redirect'] @@ -97,6 +103,9 @@ if (el.children && el.children.length) { if (el.component === 'ParentView' && !lastRouter) { el.children.forEach(c => { // 设置父路由引用 c.parent = el; c.path = el.path + '/' + c.path if (c.children && c.children.length) { children = children.concat(filterChildren(c.children, c)) @@ -108,6 +117,9 @@ } } if (lastRouter) { // 设置父路由引用 el.parent = lastRouter; el.path = lastRouter.path + '/' + el.path if (el.children && el.children.length) { children = children.concat(filterChildren(el.children, el))