Web端常用界面 树形菜单
1. 说明
树形菜单在web后台管理系统, 权限管理中基本上很容易见到。如:csdn的个人后台管理系统。
只不过这个目录只有两层。三层的如下:
甚至可以套n层。
为什么要递归?
因为数据库表的设计,菜单会用一张表设计。菜单表的两个关键列,主键id和其父级id。使用父级id引用主键id 来作为父级菜单。即主键自关联。
2. 表设计
create table acl_permission
(
id char(19) default '' not null comment '编号' primary key,
pid char(19) default '' not null comment '所属上级',
name varchar(20) default '' not null comment '名称',
type tinyint(3) default 0 not null comment '类型(1:菜单,2:按钮)',
permission_value varchar(50) null comment '权限值',
path varchar(100) null comment '访问路径',
component varchar(100) null comment '组件路径',
icon varchar(50) null comment '图标',
status tinyint null comment '状态(0:禁止,1:正常)',
is_deleted tinyint(1) unsigned default 0 not null comment '逻辑删除 1(true)已删除, 0(false)未删除',
gmt_create datetime null comment '创建时间',
gmt_modified datetime null comment '更新时间'
)
comment '权限';
实体类
package top.bitqian.rye.acl.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import lombok.Data;
/**
* 权限
*
* @author echo lovely
* @date 2021-01-21 19:31:47
*/
@Data
@TableName("acl_permission")
public class PermissionEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 编号
*/
@TableId
private String id;
/**
* 所属上级
*/
private String pid;
/**
* 名称
*/
private String name;
/**
* 类型(1:菜单,2:按钮)
*/
private Integer type;
/**
* 权限值
*/
private String permissionValue;
/**
* 访问路径
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 图标
*/
private String icon;
/**
* 状态(0:禁止,1:正常)
*/
private Integer status;
/**
* 逻辑删除 1(true)已删除, 0(false)未删除
*/
@TableField(fill = FieldFill.INSERT)
private Integer isDeleted;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
/**
* 等级 根节点为0
*/
@TableField(exist = false)
private String level;
/**
* 子级结点
*/
@TableField(exist = false)
private List<PermissionEntity> children;
}
3. 普通递归
@Test
void contextLoad1() {
// 表中所有数据
List<PermissionEntity> list = permissionService.list();
List<PermissionEntity> nodeList = new ArrayList<>();
for (PermissionEntity item : list) {
if ("1".equals(item.getPid())) {
// 一级父节点
nodeList.add(item);
// 为每个父结点设置 children
item.setChildren(collectTreeData(item, list));
}
}
nodeList.forEach(System.out::println);
}
/**
*
* @param permission 每个父级结点
* @param list 所有数据
* @return 封装好的数据 子节点
*/
private List<PermissionEntity> collectTreeData(PermissionEntity permission, List<PermissionEntity> list) {
List<PermissionEntity> tmpList = new ArrayList<>();
// 遍历所有元素
for (PermissionEntity item : list) {
// 找到子节点
if (item.getPid().equals(permission.getId())) {
// 将儿子收集
tmpList.add(item);
// 继续递归
collectTreeData(item, list);
}
}
// 设置父级结点的儿子
permission.setChildren(tmpList);
return tmpList;
}
4. 流式递归语法糖
public List<PermissionEntity> getMenuTree() {
// 所有树形菜单
List<PermissionEntity> list = this.list();
// pid = 1 的根节点
List<PermissionEntity> nodeList = list.stream().
filter(r -> "1".equals(r.getPid())).
collect(Collectors.toList());
// 为 pid=1 设置子节点。递归。
return nodeList.stream().peek(r -> {
r.setLevel("0");
// 为每个根结点 设置 children
List<PermissionEntity> dataList = collectTreeData(r, list);
r.setChildren(dataList);
}).collect(Collectors.toList());
}
private List<PermissionEntity> collectTreeData(PermissionEntity permission, List<PermissionEntity> list) {
return list.stream().
filter(r -> r.getPid().equals(permission.getId())).peek(r -> {
// set value..
r.setChildren(collectTreeData(r, list));
}).collect(Collectors.toList());
}