目录
一、树形菜单的介绍
1、什么是树形菜单?
二、实现思路流程
三、实现步骤
1、查看数据
1)表数据
2) 最终效果
2、编程
1)实体类编写
2)PermissionDao编写(难点)
第一
在线转json格式查看
第二
BuildTree解析
TreeVo
在线转json格式查看
3)PermissionAction编写
4)xml配置文件
5)jsp界面
一、树形菜单的介绍
1、什么是树形菜单?
LayUI是一款基于jQuery的前端UI框架,而树形权限菜单是一种常见的网页导航菜单设计。LayUI树形权限菜单结合了LayUI框架的特性和树状结构的展示方式,用于实现对用户权限的管理和控制。
树形权限菜单通常由多层级的树状菜单构成,每个节点表示一个功能或者页面,父节点表示上级菜单,子节点表示下级菜单。通过这种层级结构,可以清晰地展示网站或系统的功能模块之间的关系。
权限管理是指根据用户的角色或权限级别,对不同的用户展示不同的菜单选项。在LayUI树形权限菜单中,可以根据用户的权限动态生成菜单,并且对菜单进行增删改操作。通过这种方式,可以灵活地控制不同用户所能访问的功能模块,提高系统的安全性和可用性。
总之,LayUI树形权限菜单是一种基于LayUI框架和树状结构设计的前端UI组件,用于实现网页导航菜单和权限管理。
二、实现思路流程
实现LayUI树形权限菜单的思路流程可以分为以下几个步骤:
- 数据准备:首先,需要准备好菜单数据和用户权限数据。菜单数据包括菜单名称、菜单ID、父菜单ID等信息,用户权限数据包括用户ID和可访问的菜单ID列表等信息。这些数据可以从数据库或者后端接口获取。
- 数据处理:根据准备好的菜单数据和用户权限数据,进行数据处理和筛选。根据用户的权限列表,筛选出用户可以访问的菜单数据。
- 构建树形结构:使用LayUI提供的树形菜单组件,将筛选后的菜单数据构建成树形结构。树形结构可以通过递归的方式进行构建,每个节点包含菜单的名称、ID等信息,以及其子菜单节点。
- 动态渲染:将构建好的树形菜单结构动态渲染到页面上。使用LayUI提供的组件和模板引擎,将树形结构展示在页面上,并实现菜单的展开、折叠、跳转等功能。
- 权限控制:根据用户的权限列表,对菜单进行权限控制。对于用户无权限的菜单,可以进行隐藏或者禁用操作,以达到权限管理的目的。
- 事件处理:为菜单的点击事件绑定相应的处理函数。根据菜单的ID或其他标识符,处理菜单点击后的逻辑,例如跳转到对应的页面或执行相应的操作。
通过以上步骤,就可以实现LayUI树形权限菜单的设计与功能。需要注意的是,具体实现的细节和方式可能会根据项目需求和实际情况有所变化,上述流程提供了一个基本的思路和框架。
三、实现步骤
1、查看数据
1)表数据
我们查看数据库里面t_oa_permission表的数据,知道这是一个二级菜单
2) 最终效果
2、编程
根据前面的博客的步骤的配置,下面是我们树形菜单需要写的样子
1)实体类编写
package com.zking.entity;
/**
* 树形实体类
*
* @author tgq
*
*/
public class Permission {
private long id;
private String name;
private String description;
private String url;
private long pid;
private int ismenu;// 控制当前系统的权限是菜单还是按钮
private long displayno;// 排序
public Permission() {
// TODO Auto-generated constructor stub
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public long getPid() {
return pid;
}
public void setPid(long pid) {
this.pid = pid;
}
public int getIsmenu() {
return ismenu;
}
public void setIsmenu(int ismenu) {
this.ismenu = ismenu;
}
public long getDisplayno() {
return displayno;
}
public void setDisplayno(long displayno) {
this.displayno = displayno;
}
@Override
public String toString() {
return "Permission [id=" + id + ", name=" + name + ", description=" + description + ", url=" + url + ", pid="
+ pid + ", ismenu=" + ismenu + ", displayno=" + displayno + "]";
}
public Permission(long id, String name, String description, String url, long pid, int ismenu, long displayno) {
super();
this.id = id;
this.name = name;
this.description = description;
this.url = url;
this.pid = pid;
this.ismenu = ismenu;
this.displayno = displayno;
}
}
2)PermissionDao编写(难点)
我们需要继承BaseDao写两个dao方法,一个查询所有的,一个是讲平级数据转换为父级数据
用来作比较,难点和思路基本在这里
第一
package com.zking.dao;
import java.util.ArrayList;
import java.util.List;
import com.zking.entity.Permission;
import com.zking.util.BaseDao;
import com.zking.util.BuildTree;
import com.zking.util.PageBean;
import com.zking.util.TreeVo;
/**
* dao方法
*
* @author tgq
*
*/
public class PermissionDao extends BaseDao<Permission> {
/**
* 查询导所有的
*
* @param permission
* @param pageBean
* @return
* @throws Exception
*/
public List<Permission> listPermission(Permission permission, PageBean pageBean) throws Exception {
String sql = "select * from t_oa_Permission";
return super.executeQuery(sql, Permission.class, pageBean);
}
}
junit测试:
package com.zking.dao;
import java.util.List;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zking.entity.Permission;
import com.zking.util.TreeVo;
/**
* junit4测试
*
* @author tgq
*
*/
public class PermissionDaoTest {
private PermissionDao permissionDao = new PermissionDao();
@Test
public void testListPermission() throws Exception {
List<Permission> listPermission = permissionDao.listPermission(null, null);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(listPermission));
}
}
在线转json格式查看
我们可以看到并没有父级
第二
package com.zking.dao;
import java.util.ArrayList;
import java.util.List;
import com.zking.entity.Permission;
import com.zking.util.BaseDao;
import com.zking.util.BuildTree;
import com.zking.util.PageBean;
import com.zking.util.TreeVo;
/**
* dao方法
*
* @author tgq
*
*/
public class PermissionDao extends BaseDao<Permission> {
/**
* 查询导所有的
*
* @param permission
* @param pageBean
* @return
* @throws Exception
*/
public List<Permission> listPermission(Permission permission, PageBean pageBean) throws Exception {
String sql = "select * from t_oa_Permission";
return super.executeQuery(sql, Permission.class, pageBean);
}
// 将数据库的平级数据,转换为父子关系数据
public List<TreeVo<Permission>> menus(Permission permission, PageBean pageBean) throws Exception {
List<TreeVo<Permission>> listtp = new ArrayList<>();
List<Permission> list = this.listPermission(permission, pageBean);
for (Permission p : list) {// 把我们的平级对象转换为父级关系
TreeVo<Permission> tv = new TreeVo<>();
tv.setId(p.getId() + "");
tv.setText(p.getName());
tv.setParentId(p.getPid() + "");
listtp.add(tv);
}
// 这是我们还没有构建父子关系
// return listtp;
// 我们要做一个外层循环和一个内层循环
return BuildTree.buildList(listtp, "-1");
}
}
我们可以看到后面有这个代码
// 这是我们还没有构建父子关系 // return listtp; // 我们要做一个外层循环和一个内层循环 return BuildTree.buildList(listtp, "-1");
如果返回的是第一个还是没有父级的所以我们要做一个外层循环和一个内层循环
如果有三级菜单的话可以添加一个
public TreeVo<Permission> menu(Permission permission, PageBean pageBean) throws Exception {
List<TreeVo<Permission>> trees = new ArrayList<TreeVo<Permission>>();
List<Permission> list = this.listPermission(permission, pageBean);
for (Permission p : list) {
TreeVo<Permission> vo = new TreeVo<>();
vo.setId(p.getId() + "");
vo.setText(p.getName());// 节点名称
vo.setParentId(p.getPid() + "");
vo.setChildren(menus(null, null));// 将分好的二级节点和三级节点加到顶级节点里
trees.add(vo);
}
return BuildTree.build(trees);
}
BuildTree解析
BuildTree在我们的util包里面,里面有两个方法,一个build,一个buildList。
而我们的外层循环和内层循环在我们的buildList里面,方法里面我打了注释可以看这里就不解说了。
package com.zking.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BuildTree {
/**
* 默认-1为顶级节点
*
* @param nodes
* @param <T>
* @return
*/
public static <T> TreeVo<T> build(List<TreeVo<T>> nodes) {
if (nodes == null) {
return null;
}
List<TreeVo<T>> topNodes = new ArrayList<TreeVo<T>>();
for (TreeVo<T> children : nodes) {
String pid = children.getParentId();
if (pid == null || "-1".equals(pid)) {
topNodes.add(children);
continue;
}
for (TreeVo<T> parent : nodes) {
String id = parent.getId();
if (id != null && id.equals(pid)) {
parent.getChildren().add(children);
children.setHasParent(true);
parent.setChildren(true);
continue;
}
}
}
TreeVo<T> root = new TreeVo<T>();
if (topNodes.size() == 1) {
root = topNodes.get(0);
} else {
root.setId("000");
root.setParentId("-1");
root.setHasParent(false);
root.setChildren(true);
root.setChecked(true);
root.setChildren(topNodes);
root.setText("顶级节点");
Map<String, Object> state = new HashMap<>(16);
state.put("opened", true);
root.setState(state);
}
return root;
}
/**
* 指定idparam为顶级节点
*
* @param nodes
* @param idParam
* @param <T>
* @return
*/
public static <T> List<TreeVo<T>> buildList(List<TreeVo<T>> nodes, String idParam) {
if (nodes == null) {
return null;
}
List<TreeVo<T>> topNodes = new ArrayList<TreeVo<T>>();
// 外循环
for (TreeVo<T> children : nodes) {
// 外层循环的副id
String pid = children.getParentId();
if (pid == null || idParam.equals(pid)) {
topNodes.add(children);
continue;
}
// 内循环
for (TreeVo<T> parent : nodes) {
String id = parent.getId();
// 外层循环的id和内层循环的id做比较
if (id != null && id.equals(pid)) {
parent.getChildren().add(children);
children.setHasParent(true);
parent.setChildren(true);
continue;
}
}
}
return topNodes;
}
}
TreeVo
package com.zking.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class TreeVo<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<TreeVo<T>> children = new ArrayList<TreeVo<T>>();
/**
* 父ID
*/
private String parentId;
/**
* 是否有父节点
*/
private boolean hasParent = false;
/**
* 是否有子节点
*/
private boolean hasChildren = false;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Map<String, Object> getState() {
return state;
}
public void setState(Map<String, Object> state) {
this.state = state;
}
public boolean isChecked() {
return checked;
}
public void setChecked(boolean checked) {
this.checked = checked;
}
public Map<String, Object> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
public List<TreeVo<T>> getChildren() {
return children;
}
public void setChildren(List<TreeVo<T>> children) {
this.children = children;
}
public boolean isHasParent() {
return hasParent;
}
public void setHasParent(boolean isParent) {
this.hasParent = isParent;
}
public boolean isHasChildren() {
return hasChildren;
}
public void setChildren(boolean isChildren) {
this.hasChildren = isChildren;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public TreeVo(String id, String text, Map<String, Object> state, boolean checked, Map<String, Object> attributes,
List<TreeVo<T>> children, boolean isParent, boolean isChildren, String parentID) {
super();
this.id = id;
this.text = text;
this.state = state;
this.checked = checked;
this.attributes = attributes;
this.children = children;
this.hasParent = isParent;
this.hasChildren = isChildren;
this.parentId = parentID;
}
public TreeVo() {
super();
}
}
junit测试:
package com.zking.dao;
import java.util.List;
import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zking.entity.Permission;
import com.zking.util.TreeVo;
/**
* junit4测试
*
* @author tgq
*
*/
public class PermissionDaoTest {
private PermissionDao permissionDao = new PermissionDao();
@Test
public void testListPermission() throws Exception {
List<Permission> listPermission = permissionDao.listPermission(null, null);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(listPermission));
}
@Test
public void menus() throws Exception {
List<TreeVo<Permission>> menus = permissionDao.menus(null, null);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(menus));
}
}
在线转json格式查看
这个方法后我们可以看到以及对好了,有父级
3)PermissionAction编写
我们要继承和实现两个类,继承ActionSupport,实现ModelDriver<Permission>,
package com.zking.web;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.zking.dao.PermissionDao;
import com.zking.entity.Permission;
import com.zking.framework.ActionSupport;
import com.zking.framework.ModelDriver;
import com.zking.util.ResponseUtil;
import com.zking.util.TreeVo;
public class PermissionAction extends ActionSupport implements ModelDriver<Permission> {
private Permission permission = new Permission();
private PermissionDao permissionDao = new PermissionDao();
public void menus(HttpServletRequest req, HttpServletResponse resp) throws Exception {
List<TreeVo<Permission>> menus = permissionDao.menus(null, null);
ResponseUtil.writeJson(resp, menus);
}
@Override
public Permission getModel() {
return permission;
}
}
4)xml配置文件
记得在我们的mvc.xml里面进行一个配置
<?xml version="1.0" encoding="UTF-8"?>
<config>
<action path="/blog" type="com.zking.web.BlogAction">
<forward name="list" path="/blogList.jsp" redirect="false" />
<forward name="toList" path="/blog.action?methodName=list"
redirect="true" />
<forward name="toEdit" path="/blogEdit.jsp" redirect="false" />
</action>
<action path="/userAction" type="com.zking.web.UserAction">
</action>
<action path="/tree" type="com.zking.web.PermissionAction">
</action>
</config>
5)jsp界面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>后台首页</title>
<%@include file="/common/header.jsp"%>
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">layout demo</div>
<!-- 头部区域(可配合layui 已有的水平导航) -->
<ul class="layui-nav layui-layout-left">
<!-- 移动端显示 -->
<li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-header-event="menuLeft">
<i class="layui-icon layui-icon-spread-left"></i>
</li>
<!-- Top导航栏 -->
<li class="layui-nav-item layui-hide-xs"><a href="">nav 1</a></li>
<li class="layui-nav-item layui-hide-xs"><a href="">nav 2</a></li>
<li class="layui-nav-item layui-hide-xs"><a href="">nav 3</a></li>
<li class="layui-nav-item">
<a href="javascript:;">nav groups</a>
<dl class="layui-nav-child">
<dd><a href="">menu 11</a></dd>
<dd><a href="">menu 22</a></dd>
<dd><a href="">menu 33</a></dd>
</dl>
</li>
</ul>
<!-- 个人头像及账号操作 -->
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-md-inline-block">
<a href="javascript:;">
<img src="//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" class="layui-nav-img">
tester
</a>
<dl class="layui-nav-child">
<dd><a href="">Your Profile</a></dd>
<dd><a href="">Settings</a></dd>
<dd><a href="login.jsp">Sign out</a></dd>
</dl>
</li>
<li class="layui-nav-item" lay-header-event="menuRight" lay-unselect>
<a href="javascript:;">
<i class="layui-icon layui-icon-more-vertical"></i>
</a>
</li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 左侧导航区域(可配合layui已有的垂直导航) -->
<ul id="menu" class="layui-nav layui-nav-tree" lay-filter="menu">
<<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">menu group 1</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;">menu 1</a></dd>
<dd><a href="javascript:;">menu 2</a></dd>
<dd><a href="javascript:;">menu 3</a></dd>
<dd><a href="">the links</a></dd>
</dl>
</li>
<li class="layui-nav-item">
<a href="javascript:;">menu group 2</a>
<dl class="layui-nav-child">
<dd><a href="javascript:;">list 1</a></dd>
<dd><a href="javascript:;">list 2</a></dd>
<dd><a href="">超链接</a></dd>
</dl>
</li>
<li class="layui-nav-item"><a href="javascript:;">click menu item</a></li>
<li class="layui-nav-item"><a href="">the links</a></li>
</ul>
</div>
</div>
<div class="layui-body">
<!-- 内容主体区域 -->
<div style="padding: 15px;">内容主体区域。记得修改 layui.css 和 js 的路径</div>
</div>
<div class="layui-footer">
<!-- 底部固定区域 -->
底部固定区域
</div>
</div>
</body>
</html>
jsp界面效果
我们用scriptLayui编写
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>后台首页</title> <%@include file="/common/header.jsp"%> </head> <body> <div class="layui-layout layui-layout-admin"> <div class="layui-header"> <div class="layui-logo layui-hide-xs layui-bg-black">layout demo</div> <!-- 头部区域(可配合layui 已有的水平导航) --> <ul class="layui-nav layui-layout-left"> <!-- 移动端显示 --> <li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-header-event="menuLeft"> <i class="layui-icon layui-icon-spread-left"></i> </li> <!-- Top导航栏 --> <li class="layui-nav-item layui-hide-xs"><a href="">nav 1</a></li> <li class="layui-nav-item layui-hide-xs"><a href="">nav 2</a></li> <li class="layui-nav-item layui-hide-xs"><a href="">nav 3</a></li> <li class="layui-nav-item"> <a href="javascript:;">nav groups</a> <dl class="layui-nav-child"> <dd><a href="">menu 11</a></dd> <dd><a href="">menu 22</a></dd> <dd><a href="">menu 33</a></dd> </dl> </li> </ul> <!-- 个人头像及账号操作 --> <ul class="layui-nav layui-layout-right"> <li class="layui-nav-item layui-hide layui-show-md-inline-block"> <a href="javascript:;"> <img src="//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" class="layui-nav-img"> tester </a> <dl class="layui-nav-child"> <dd><a href="">Your Profile</a></dd> <dd><a href="">Settings</a></dd> <dd><a href="login.jsp">Sign out</a></dd> </dl> </li> <li class="layui-nav-item" lay-header-event="menuRight" lay-unselect> <a href="javascript:;"> <i class="layui-icon layui-icon-more-vertical"></i> </a> </li> </ul> </div> <div class="layui-side layui-bg-black"> <div class="layui-side-scroll"> <!-- 左侧导航区域(可配合layui已有的垂直导航) --> <ul id="menu" class="layui-nav layui-nav-tree" lay-filter="menu"> <!-- <li class="layui-nav-item layui-nav-itemed"> <a class="" href="javascript:;">menu group 1</a> <dl class="layui-nav-child"> <dd><a href="javascript:;">menu 1</a></dd> <dd><a href="javascript:;">menu 2</a></dd> <dd><a href="javascript:;">menu 3</a></dd> <dd><a href="">the links</a></dd> </dl> </li> --> <!-- <li class="layui-nav-item"> <a href="javascript:;">menu group 2</a> <dl class="layui-nav-child"> <dd><a href="javascript:;">list 1</a></dd> <dd><a href="javascript:;">list 2</a></dd> <dd><a href="">超链接</a></dd> </dl> </li> <li class="layui-nav-item"><a href="javascript:;">click menu item</a></li> <li class="layui-nav-item"><a href="">the links</a></li> --> </ul> </div> </div> <div class="layui-body"> <!-- 内容主体区域 --> <div style="padding: 15px;">内容主体区域。记得修改 layui.css 和 js 的路径</div> </div> <div class="layui-footer"> <!-- 底部固定区域 --> 底部固定区域 </div> </div> <script> //JS layui.use(['element', 'layer', 'util'], function(){ var element = layui.element ,layer = layui.layer ,util = layui.util ,$ = layui.$; $.ajax({ url : '${pageContext.request.contextPath }/tree.action?methodName=menus', method : 'post', dataType : 'json', success : function(data) { console.log(data); var htmlTree=""; $.each(data,function(index,node){ htmlTree+='<li class="layui-nav-item layui-nav-itemed">'; htmlTree+='<a href="javascript:;">'+node.text+'</a>'; if (node.hasChildren) { var children=node.children; htmlTree+='<dl class="layui-nav-child">'; $.each(children,function(i,n){ htmlTree+='<dd><a href="javascript:;">'+n.text+'</a></dd>'; }); htmlTree+='</dl>'; } htmlTree+='</li>'; }); $("#menu").html(htmlTree); /* 进行渲染 */ element.render('menu'); } }); }); </script> </body> </html>
在最后我们需要添加一个方法进行一个渲染,不然有的时候运行并不会出现还会是原来的一个布局和树形菜单
/* 进行渲染 */ element.render('menu');
我们可以看到如果我把渲染关闭,并没有收回的一个
如果我们渲染加上就会有一个收回
我的分享就在这里,也有相应的文件,这是一个比较通用的方法,并不是全通用。
希望对你们有些帮助!!!