本微博只适用于两层的菜单展示,写作的初衷是为了介绍递归做铺垫。
详细说明都写在了代码的注释上,不做单独说明了,希望对你有所帮助。
为了方便大家练习,提供建表SQL如下:
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(50) DEFAULT NULL COMMENT '菜单名称',
`url` varchar(200) DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(500) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`type` int(11) DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(50) DEFAULT NULL COMMENT '菜单图标',
`order_num` int(11) DEFAULT NULL COMMENT '排序',
`gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8 COMMENT='菜单管理';
该实体类是在工作中总结出来的,字段较全,适用场景也比较多。但实际上,最主要的字段只有前几个:menuId、parentId、name、url。
import java.io.Serializable;
import java.util.Date;
public class MenuDO implements Serializable {
private static final long serialVersionUID = 1L;
// 主键
private Long menuId;
// 父菜单ID,一级菜单为0
private Long parentId;
// 菜单名称
private String name;
// 菜单URL
private String url;
// 授权(多个用逗号分隔,如:user:list,user:create)
private String perms;
// 类型 0:目录 1:菜单 2:按钮
private Integer type;
// 菜单图标
private String icon;
// 排序
private Integer orderNum;
// 创建时间
private Date gmtCreate;
// 修改时间
private Date gmtModified;
......
// 省略get()、set()等方法
}
public class Tree<T> {
// 节点ID
private String id;
// 显示节点文本
private String text;
// 节点状态,open closed
private Map<String, Object> state;
// 节点是否被选中 true false
private boolean checked = false;
// 节点属性
private Map<String, Object> attributes;
// 节点的子节点
private List<Tree<T>> children = new ArrayList<Tree<T>>();
// 父ID
private String parentId;
// 是否有父节点
private boolean hasParent = false;
// 是否有子节点
private boolean hasChildren = false;
......
// 省略get()、set()等方法
}
返回的list结果,就是满足逻辑的tree型数据,可以直接返回给页面使用。
public List<Tree<MenuDO>> listMenuTree() {
String idParam = "0"; // 自定义顶级结点
List<Tree<MenuDO>> trees = new ArrayList<Tree<MenuDO>>(); // 存放结果
List<MenuDO> menuDOs = menuMapper.queryList(); // 取出数据
// 1. 初步处理:让原始数据具备我们自定的的tree的特征
for (MenuDO sysMenuDO : menuDOs) {
Tree<MenuDO> tree = new Tree<MenuDO>();
tree.setId(sysMenuDO.getMenuId().toString());
tree.setParentId(sysMenuDO.getParentId().toString());
tree.setText(sysMenuDO.getName());
Map<String, Object> attributes = new HashMap<>(16);
attributes.put("url", sysMenuDO.getUrl());
attributes.put("icon", sysMenuDO.getIcon());
tree.setAttributes(attributes);
trees.add(tree);
}
// 2. 递归处理:根据数据库实际情况调整顶级结点的parentId值,默认为0
List<Tree<MenuDO>> list = BuildTree.buildList(trees, idParam);
return list;
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 递归树形结构类
*/
public class BuildTree {
/**
* 自定义顶级结点的parentId,返回递归树形结构
*/
public static <T> List<Tree<T>> buildList(List<Tree<T>> nodes, String idParam) {
// 1.非空判断
if (nodes == null) {
return null;
}
// 2.定义返回数据类型
List<Tree<T>> topNodes = new ArrayList<Tree<T>>();
// 3.取出每一个元素,判断它有没有父类
for (Tree<T> children : nodes) {
String pid = children.getParentId();
// 3.1 pid(parentId)为空,或者等于父节点,则没有父类,直接返回
if (pid == null || idParam.equals(pid)) {
topNodes.add(children);
continue;
}
// 3.2 否则,遍历一遍集合,找它的父类,原则:子类的parentId = 父类的id
for (Tree<T> parent : nodes) {
String id = parent.getId();
if (id != null && id.equals(pid)) {
// 3.2.1 将子类添加到父类的children属性下
parent.getChildren().add(children);
// 3.2.2 设置子节点闭合状态:true - 关闭
children.setHasParent(true);
parent.setChildren(true);
}
}
}
// 4. 返回结果集
return topNodes;
}
}
基于BootDo成熟的架构,小编自己搭建了一个开发框架,使用递归展示菜单栏,效果如下:
更多精彩,请关注我的"今日头条号":Java云笔记
随时随地,让你拥有最新,最便捷的掌上云服务