From 5f76c539f4688587dbc6695031eb3598d9183a91 Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Wed, 15 Oct 2025 17:54:40 +0800 Subject: [PATCH] =?UTF-8?q?menu=E8=B7=AF=E7=94=B1=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- schoolNewsWeb/src/types/menu/index.ts | 3 + schoolNewsWeb/src/utils/route-generator.ts | 89 ++++++++++++++----- .../views/manage/system/DeptManageView.vue | 77 ++++++++++++---- .../views/manage/system/MenuManageView.vue | 77 ++++++++++++---- 4 files changed, 189 insertions(+), 57 deletions(-) diff --git a/schoolNewsWeb/src/types/menu/index.ts b/schoolNewsWeb/src/types/menu/index.ts index 6f4bf0d..2f5ad1a 100644 --- a/schoolNewsWeb/src/types/menu/index.ts +++ b/schoolNewsWeb/src/types/menu/index.ts @@ -23,6 +23,9 @@ export interface SysMenu extends BaseDTO { url?: string; /** 菜单组件 */ component?: string; + /** 菜单布局 */ + layout?: string; + /** 菜单布局 */ /** 菜单图标 */ icon?: string; /** 菜单顺序 */ diff --git a/schoolNewsWeb/src/utils/route-generator.ts b/schoolNewsWeb/src/utils/route-generator.ts index 5f01d4c..96094fa 100644 --- a/schoolNewsWeb/src/utils/route-generator.ts +++ b/schoolNewsWeb/src/utils/route-generator.ts @@ -83,16 +83,21 @@ function generateRouteFromMenu(menu: SysMenu, isTopLevel = true): RouteRecordRaw // 如果有子菜单,使用布局组件 if (menu.children && menu.children.length > 0) { - // 如果是顶层的NAVIGATION类型菜单,使用NavigationLayout - if (isTopLevel && menu.type === MenuType.NAVIGATION) { - route.component = getComponent(menu.component || 'NavigationLayout'); - } else if (menu.type === MenuType.SIDEBAR) { - // SIDEBAR类型的菜单使用BlankLayout,避免嵌套布局 - // BlankLayout 只是一个纯容器,不会添加额外的导航栏或面包屑 - route.component = getComponent(menu.component || 'BlankLayout'); + // 根据layout字段选择布局 + const layout = (menu as any).layout || menu.component; + + if (layout) { + // 如果指定了layout,使用指定的布局 + route.component = getComponent(layout); } else { - // 其他情况使用BasicLayout - route.component = getComponent(menu.component || 'BasicLayout'); + // 根据菜单类型选择默认布局 + if (isTopLevel && menu.type === MenuType.NAVIGATION) { + route.component = getComponent('NavigationLayout'); + } else if (menu.type === MenuType.SIDEBAR) { + route.component = getComponent('BlankLayout'); + } else { + route.component = getComponent('BasicLayout'); + } } } else { // 没有子菜单,使用具体的页面组件 @@ -277,6 +282,7 @@ export function buildMenuTree(menus: SysMenu[]): SysMenu[] { const menuMap = new Map(); const rootMenus: SysMenu[] = []; + const maxDepth = allMenus.length; // 最多遍历len层 // 创建菜单映射 allMenus.forEach(menu => { @@ -285,23 +291,47 @@ export function buildMenuTree(menus: SysMenu[]): SysMenu[] { } }); - // 构建树结构 - allMenus.forEach(menu => { - const menuNode = menuMap.get(menu.menuID!); - if (!menuNode) return; + // 循环构建树结构,最多遍历maxDepth次 + for (let depth = 0; depth < maxDepth; depth++) { + let hasChanges = false; - if (!menu.parentID || menu.parentID === '0') { - // 根菜单 - rootMenus.push(menuNode); - } else { - // 子菜单 - const parent = menuMap.get(menu.parentID); - if (parent) { - parent.children = parent.children || []; - parent.children.push(menuNode); + allMenus.forEach(menu => { + if (!menu.menuID) return; + + const menuNode = menuMap.get(menu.menuID); + if (!menuNode) return; + + // 如果节点已经在树中,跳过 + if (isNodeInTree(menuNode, rootMenus)) { + return; } + + if (!menu.parentID || menu.parentID === '0' || menu.parentID === '') { + // 根菜单 + if (!isNodeInTree(menuNode, rootMenus)) { + rootMenus.push(menuNode); + hasChanges = true; + } + } else { + // 子菜单 + const parent = menuMap.get(menu.parentID); + if (parent && isNodeInTree(parent, rootMenus)) { + if (!parent.children) { + parent.children = []; + } + if (!parent.children.includes(menuNode)) { + parent.children.push(menuNode); + hasChanges = true; + } + } + } + }); + + // 如果没有变化,说明树构建完成 + if (!hasChanges) { + break; } - }); + } // 按orderNum排序 const sortMenus = (menus: SysMenu[]): SysMenu[] => { @@ -316,6 +346,19 @@ export function buildMenuTree(menus: SysMenu[]): SysMenu[] { return sortMenus(rootMenus); } +// 检查节点是否已经在树中 +function isNodeInTree(node: SysMenu, tree: SysMenu[]): boolean { + for (const treeNode of tree) { + if (treeNode.menuID === node.menuID) { + return true; + } + if (treeNode.children && isNodeInTree(node, treeNode.children)) { + return true; + } + } + return false; +} + /** * 根据权限过滤菜单 * @param menus 菜单列表 diff --git a/schoolNewsWeb/src/views/manage/system/DeptManageView.vue b/schoolNewsWeb/src/views/manage/system/DeptManageView.vue index 5367b87..620da74 100644 --- a/schoolNewsWeb/src/views/manage/system/DeptManageView.vue +++ b/schoolNewsWeb/src/views/manage/system/DeptManageView.vue @@ -271,32 +271,62 @@ async function loadDeptList() { // 将扁平数据转换为树形结构 function buildTree(flatData: SysDept[]): SysDept[] { + if (!flatData || flatData.length === 0) { + return []; + } + const tree: SysDept[] = []; - const map: Record = {}; + const map = new Map(); + const maxDepth = flatData.length; // 最多遍历len层 - // 创建映射表 + // 初始化所有节点 flatData.forEach(item => { if (item.deptID) { - map[item.deptID] = { ...item, children: [] }; + map.set(item.deptID, { ...item, children: [] }); } }); - // 构建树形结构 - flatData.forEach(item => { - if (item.deptID) { - const node = map[item.deptID]; - if (item.parentID && map[item.parentID]) { - // 有父节点,添加到父节点的children中 - if (!map[item.parentID].children) { - map[item.parentID].children = []; - } - map[item.parentID].children!.push(node); - } else { - // 没有父节点或父节点不存在,作为根节点 - tree.push(node); + // 循环构建树结构,最多遍历maxDepth次 + for (let depth = 0; depth < maxDepth; depth++) { + let hasChanges = false; + + flatData.forEach(item => { + if (!item.deptID) return; + + const node = map.get(item.deptID); + if (!node) return; + + // 如果节点已经在树中,跳过 + if (isNodeInTree(node, tree)) { + return; } + + if (!item.parentID || item.parentID === '0' || item.parentID === '') { + // 根节点 + if (!isNodeInTree(node, tree)) { + tree.push(node); + hasChanges = true; + } + } else { + // 查找父节点 + const parent = map.get(item.parentID); + if (parent && isNodeInTree(parent, tree)) { + if (!parent.children) { + parent.children = []; + } + if (!parent.children.includes(node)) { + parent.children.push(node); + hasChanges = true; + } + } + } + }); + + // 如果没有变化,说明树构建完成 + if (!hasChanges) { + break; } - }); + } // 清理空的children数组 function cleanEmptyChildren(nodes: SysDept[]) { @@ -313,6 +343,19 @@ function buildTree(flatData: SysDept[]): SysDept[] { return tree; } +// 检查节点是否已经在树中 +function isNodeInTree(node: SysDept, tree: SysDept[]): boolean { + for (const treeNode of tree) { + if (treeNode.deptID === node.deptID) { + return true; + } + if (treeNode.children && isNodeInTree(node, treeNode.children)) { + return true; + } + } + return false; +} + // 新增部门 function handleAdd() { isEdit.value = false; diff --git a/schoolNewsWeb/src/views/manage/system/MenuManageView.vue b/schoolNewsWeb/src/views/manage/system/MenuManageView.vue index d6e698f..cb3385c 100644 --- a/schoolNewsWeb/src/views/manage/system/MenuManageView.vue +++ b/schoolNewsWeb/src/views/manage/system/MenuManageView.vue @@ -347,32 +347,62 @@ async function loadMenuList() { // 将扁平数据转换为树形结构 function buildTree(flatData: SysMenu[]): SysMenu[] { + if (!flatData || flatData.length === 0) { + return []; + } + const tree: SysMenu[] = []; - const map: Record = {}; + const map = new Map(); + const maxDepth = flatData.length; // 最多遍历len层 - // 创建映射表 + // 初始化所有节点 flatData.forEach(item => { if (item.menuID) { - map[item.menuID] = { ...item, children: [] }; + map.set(item.menuID, { ...item, children: [] }); } }); - // 构建树形结构 - flatData.forEach(item => { - if (item.menuID) { - const node = map[item.menuID]; - if (item.parentID && map[item.parentID]) { - // 有父节点,添加到父节点的children中 - if (!map[item.parentID].children) { - map[item.parentID].children = []; - } - map[item.parentID].children!.push(node); - } else { - // 没有父节点或父节点不存在,作为根节点 - tree.push(node); + // 循环构建树结构,最多遍历maxDepth次 + for (let depth = 0; depth < maxDepth; depth++) { + let hasChanges = false; + + flatData.forEach(item => { + if (!item.menuID) return; + + const node = map.get(item.menuID); + if (!node) return; + + // 如果节点已经在树中,跳过 + if (isNodeInTree(node, tree)) { + return; } + + if (!item.parentID || item.parentID === '0' || item.parentID === '') { + // 根节点 + if (!isNodeInTree(node, tree)) { + tree.push(node); + hasChanges = true; + } + } else { + // 查找父节点 + const parent = map.get(item.parentID); + if (parent && isNodeInTree(parent, tree)) { + if (!parent.children) { + parent.children = []; + } + if (!parent.children.includes(node)) { + parent.children.push(node); + hasChanges = true; + } + } + } + }); + + // 如果没有变化,说明树构建完成 + if (!hasChanges) { + break; } - }); + } // 清理空的children数组 function cleanEmptyChildren(nodes: SysMenu[]) { @@ -389,6 +419,19 @@ function buildTree(flatData: SysMenu[]): SysMenu[] { return tree; } +// 检查节点是否已经在树中 +function isNodeInTree(node: SysMenu, tree: SysMenu[]): boolean { + for (const treeNode of tree) { + if (treeNode.menuID === node.menuID) { + return true; + } + if (treeNode.children && isNodeInTree(node, treeNode.children)) { + return true; + } + } + return false; +} + // 新增菜单 function handleAdd() { isEdit.value = false;