个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang -
Overview
树形菜单
在很多系统管理/菜单管理中经常会出现下面这样的树形菜单,它是通过前端的Tree
组件来渲染的。
后端返回的树形结构数据如下图所示。
在已有的Element
、ant-vue
等前端框架中这种组件都是有的,用起来也是非常简单。
Ant
Design Vue
问题
现在需求是这样的,如上图菜单搜索有两个条件,状态和菜单名称,在查询菜单时需要通过模糊匹配菜单名称和精确匹配菜单状态来查询。这个本身很简单,问题是在树形菜单搜索时需要带上父菜单和子菜单,而不是只展示匹配到的菜单。
如:搜索“用户”时应该有下面的结果,在查询到相关节点后同时带上父节点和子节点返回。
Ant
Design Vue在这里前端组件是有这样的实现的,如下。
前端是有这样的实现,而且很简单,那么有没有后端的实现方案呢?
如何实现
最直接的思路就是查询满足条件的菜单,然后去查这些菜单的父菜单和子菜单,讲起来很容易,但是这里要很注意重复节点和死循环。
重复节点问题:同级菜单具有相同的父节点,在查完第一个子节点的父节点,将父节点收集后,查第二就没有必要的,可以省去。
死循环:查子节点时不要再去查其父节点的子节点和父节点,有可能会死循环。
下面贴上了所有代码仅供参考。
查询菜单列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public List<MenuTreeRespVO> getMenuTreeList(MenuListVO reqVO) {
List<MenuPO> all = menuMapper.selectList();
List<MenuPO> menus = menuMapper.selectList(reqVO); Set<Long> menuIds = menus.stream().map(MenuPO::getId).collect(Collectors.toSet());
Set<MenuPO> menuSet = findMenusWithParentsOrChildrenByIds(all, menuIds, true, true);
return buildMenuTree(new ArrayList<>(menuSet), false); }
|
查询菜单的父/子菜单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
private Set<MenuPO> findMenusWithParentsOrChildrenByIds(List<MenuPO> all, Set<Long> menuIds, boolean withParent, boolean withChildren) { Map<Long, MenuPO> menuMap = new HashMap<>(); for (MenuPO menu : all) { menuMap.put(menu.getId(), menu); }
Set<MenuPO> result = new LinkedHashSet<>(); Set<Long> processedIds = new HashSet<>(); for (Long menuId : menuIds) { if (withParent) { collectMenuParents(result, menuMap, menuId, processedIds); } if (withChildren) { collectMenuChildren(result, menuMap, menuId); } }
return result; }
|
递归查找当前菜单的所有父菜单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
private void collectMenuParents(Set<MenuPO> resultSet, Map<Long, MenuPO> menuMap, Long menuId, Set<Long> processedIds) { if (processedIds.contains(menuId)) { return; }
processedIds.add(menuId); MenuPO menu = menuMap.get(menuId); if (menu != null) { resultSet.add(menu);
if (!Objects.equals(menu.getParentId(), ID_ROOT) && !processedIds.contains(menu.getParentId())) { collectMenuParents(resultSet, menuMap, menu.getParentId(), processedIds); } } }
|
递归查找当前菜单的所有子菜单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
private void collectMenuChildren(Set<MenuPO> resultSet, Map<Long, MenuPO> menuMap, Long menuId) { MenuPO menu = menuMap.get(menuId); if (menu != null) { resultSet.add(menu);
for (MenuPO child : menuMap.values()) { if (child.getParentId().equals(menu.getId())) { collectMenuChildren(resultSet, menuMap, child.getId()); } } } }
|
构建菜单树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public List<MenuTreeRespVO> buildMenuTree(List<MenuPO> menuList, boolean removeButton) {
if (removeButton) { menuList.removeIf(menu -> menu.getType().equals(MenuType.BUTTON.getType())); }
List<MenuTreeRespVO> convert = MenuConvert.INSTANCE.convert2TreeRespList(menuList);
Map<Long, MenuTreeRespVO> menuTreeMap = new HashMap<>(); for (MenuTreeRespVO menu : convert) { menuTreeMap.put(menu.getId(), menu); }
menuTreeMap.values().stream().filter(menu -> !ID_ROOT.equals(menu.getParentId())).forEach(childMenu -> { MenuTreeRespVO parentMenu = menuTreeMap.get(childMenu.getParentId()); if (parentMenu == null) { log.info("id:{} 找不到父菜单 parentId:{}", childMenu.getId(), childMenu.getParentId()); return; } if (parentMenu.getChildren() == null) { parentMenu.setChildren(new ArrayList<>()); } parentMenu.getChildren().add(childMenu); }
);
return menuTreeMap.values().stream().filter(menu -> ID_ROOT.equals(menu.getParentId())).collect(Collectors.toList()); }
|
总结
基于以上后端查询树形菜单的代码就可以实现“筛选树形菜单时关联其父节点和子节点”了,效果还是可以的。
写在最后
拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang -
Overview