前言
在开发中,我们经常会遇到导航菜单、部门菜单、权限树、评论等功能。
这些功能都有共同的特点:
存在父子关系,并且可以无限递归。
我们以导航菜单为例。 我们将导航菜单设置为动态角色权限设计,即动态加载菜单数据。
数据库设计
适合数据库存储的设计如下:
create table `menus`
(
`id` int primary key auto_increment,
`name` varchar(20) comment '菜单名称',
`pid` int default 0 comment '父级 ID, 最顶级为 0',
`order` int comment '排序, 序号越大, 越靠前'
)
前端渲染
对于前端来说,我们一般需要这样的效果:
菜单配置页面:
对应的导航菜单:
常用的树形展示插件有:JsTree、Layui Tree、Bootstrap Tree View等。
这些插件通常需要这两种格式:
基本格式:
[
{
"id": 1,
"name": "权限管理",
"pid": 0,
"order": 1
},
{
"id": 2,
"name": "用户管理",
"pid": 1,
"order": 2
},
{
"id": 3,
"name": "角色管理",
"pid": 1,
"order": 3
},
{
"id": 4,
"name": "权限管理",
"pid": 1,
"order": 4
}
]
树格式:
[
{
"id": 1,
"name": "权限管理",
"pid": 0,
"order": 1,
"children": [
{
"id": 2,
"name": "用户管理",
"pid": 1,
"order": 2,
"children": []
},
{
"id": 3,
"name": "角色管理",
"pid": 1,
"order": 3,
"children": []
},
{
"id": 4,
"name": "权限管理",
"pid": 1,
"order": 4,
"children": []
}
]
}
]
有的插件支持这两种格式游戏图片,有的则只支持树形结构,但我们数据库查询的结果往往都是普通的结构。 这时我们需要将普通格式转换为树格式。
这种转换一般在服务器端进行(因为大多数前端插件都是在后台请求一个URL来接收JSON数据地图场景,并且在加载数据之后-渲染之前不提供事件角色权限设计,所以无法在服务器端完成转换)前端。)
数据转换
首先是Java实体类:
public class Menu {
private int id,
private String name,
private int pid
// getter setter 略
}
数据库查询后,通常是在List中:
List
然后我们需要将这个List转换成树形结构。 首先定义一个树形结构的VO类:
public class MenuTreeVO {
private int id,
private String name,
private int pid,
private List children,
// getter setter 略
}
转换工具类:
package im.zhaojun.util;
import im.zhaojun.model.vo.MenuTreeVO;
import java.util.ArrayList;
import java.util.List;
public class TreeUtil {
/**
* 所有待用"菜单"
*/
private static List all = null;
/**
* 转换为树形
* @param list 所有节点
* @return 转换后的树结构菜单
*/
public static List toTree(List list) {
// 最初, 所有的 "菜单" 都是待用的
all = new ArrayList<>(list);
// 拿到所有的顶级 "菜单"
List roots = new ArrayList<>();
for (MenuTreeVO menuTreeVO : list) {
if (menuTreeVO.getParentId() == 0) {
roots.add(menuTreeVO);
}
}
// 将所有顶级菜单从 "待用菜单列表" 中删除
all.removeAll(roots);
for (MenuTreeVO menuTreeVO : roots) {
menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));;
}
return roots;
}
/**
* 递归函数
* 递归目的: 拿到子节点
* 递归终止条件: 没有子节点
* @param parent 父节点
* @return 子节点
*/
private static List getCurrentNodeChildren(MenuTreeVO parent) {
// 判断当前节点有没有子节点, 没有则创建一个空长度的 List, 有就使用之前已有的所有子节点.
List childList = parent.getChildren() == null ? new ArrayList<>() : parent.getChildren();
// 从 "待用菜单列表" 中找到当前节点的所有子节点
for (MenuTreeVO child : all) {
if (parent.getMenuId().equals(child.getParentId())) {
childList.add(child);
}
}
// 将当前节点的所有子节点从 "待用菜单列表" 中删除
all.removeAll(childList);
// 所有的子节点再寻找它们自己的子节点
for (MenuTreeVO menuTreeVO : childList) {
menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));
}
return childList;
}
}
调用方法:
// 从数据库获取
List
附:模板引擎渲染
有时我们会使用模板引擎来渲染菜单,但由于菜单是树形结构,单纯使用模板引擎中的for并不能完成无限极菜单的渲染。
这里有一个很新颖的方法,我以thymeleaf引擎为例:
index.html 的导航部分:
public.html公共模板部分:
系统管理
基本逻辑是使用include来引用模板。 各种模板引擎都有这个功能,然后判断当前节点是否有子节点。 如果是,则模板文件引用自身来完成递归。
结论
上面的代码是开发Shiro权限管理后端时的一些想法和代码。 完整代码请参考:/zhaojun1998...