function buildMenu(menu, menuList, menuIds, visited) { visited = visited || new Set() let nextLayer = [] for (let i = menu.length - 1; i > -1; i--) { const currentMenu = menu[i] if (visited.has(currentMenu.menu_id)) continue visited.add(currentMenu.menu_id) const subMenu = menuList.filter(item => { if (item.parent_id === currentMenu.menu_id) { if (visited.has(item.menu_id)) return false menuIds.push(item.menu_id) return true } }) nextLayer = nextLayer.concat(subMenu) currentMenu.children = subMenu } if (nextLayer.length) { buildMenu(nextLayer, menuList, menuIds, visited) } } function getParentIds(menuItem, menuList) { const parentArr = [] const visited = new Set() let currentItem = menuItem while (currentItem && currentItem.parent_id) { if (visited.has(currentItem.parent_id)) { // B7-1: 数据成环(A→B、B→A)时立即返回已收集链,不再死循环 parentArr.push(currentItem.parent_id) break } visited.add(currentItem.parent_id) parentArr.push(currentItem.parent_id) currentItem = menuList.find(item => item.menu_id === currentItem.parent_id) } return parentArr } // B7-1: 判断把 menuId 的 parent 改为 newParentId 是否会成环 function wouldCreateCycle(menuList, menuId, newParentId) { if (!newParentId) return false if (newParentId === menuId) return true const visited = new Set() let current = newParentId while (current) { if (current === menuId) return true if (visited.has(current)) return true visited.add(current) const item = menuList.find(m => m.menu_id === current) current = item && item.parent_id } return false } function buildMenus(menuList, trim = true) { // B7-1: sort 比较器不再每次重算 getParentIds —— 记忆化一次算完(链式 O(n)) const byId = new Map() for (const item of menuList) byId.set(item.menu_id, item) const parentMap = new Map() function ancestors(id) { if (parentMap.has(id)) return parentMap.get(id) const chain = [] const visited = new Set() let cur = byId.get(id) while (cur && cur.parent_id) { if (visited.has(cur.parent_id)) { chain.push(cur.parent_id); break } visited.add(cur.parent_id) chain.push(cur.parent_id) if (parentMap.has(cur.parent_id)) { chain.push(...parentMap.get(cur.parent_id)); break } cur = byId.get(cur.parent_id) } parentMap.set(id, chain) return chain } for (const item of menuList) ancestors(item.menu_id) // 保证父子级顺序 menuList = menuList.sort(function(a, b) { const parentIdsA = parentMap.get(a.menu_id) || [] const parentIdsB = parentMap.get(b.menu_id) || [] if (parentIdsA.includes(b.menu_id)) { return 1 } return parentIdsA.length - parentIdsB.length || a.sort - b.sort }) // 删除无subMenu且非子节点的菜单项 if (trim) { for (let i = menuList.length - 1; i > -1; i--) { const currentMenu = menuList[i] const subMenu = menuList.filter(subMenuItem => subMenuItem.parent_id === currentMenu.menu_id) if (!currentMenu.isLeafNode && !subMenu.length) { menuList.splice(i, 1) } } } const menuIds = [] const menu = menuList.filter(item => { if (!item.parent_id) { menuIds.push(item.menu_id) return true } }) buildMenu(menu, menuList, menuIds, new Set()) // 包含所有无效菜单 if (!trim && menuIds.length !== menuList.length) { menu.push(...menuList.filter(item => !menuIds.includes(item.menu_id))) } return menu } export { buildMenu, buildMenus, getParentIds, wouldCreateCycle }