目录
1什么是树形菜单
LayUI(或称为layui)是一款基于web前端的轻量级模块化UI库,它提供了丰富的UI组件和易于使用的界面元素,可以帮助开发者快速构建美观、易用的网页界面。
LayUI动态树(Dynamic Tree)是layui库中提供的一个组件,用于创建动态的树形结构。它可以根据需求,动态加载和展示树节点,支持异步加载和无限级别的树结构。
LayUI动态树组件的主要特点和功能包括:
1. 动态加载:可以通过AJAX等方式动态加载树节点数据,可以根据需要异步获取子节点数据,实现懒加载。
2. 无限级别:支持无限级别的树结构,可以根据数据的层级关系,自动生成相应的树节点和层级显示。
3. 自定义节点:可以自定义每个节点的样式、图标、文本等内容,以及节点的展开和折叠操作。
4. 节点操作:支持对节点的增加、修改、删除等操作,可以通过事件和回调函数来实现节点的操作逻辑。
5. 事件处理:提供了一系列的事件回调函数,可以对树节点的展开、折叠、选中等事件进行响应和处理。
通过使用LayUI动态树组件,开发者可以方便地创建交互式的树形结构,用于展示和操作层级关系的数据。它适用于各种需要展示树形结构数据的场景,如组织架构图、文件目录结构等。
2树形菜单的作用
树形菜单(Tree Menu)是一种以树形结构展示的导航菜单。它的作用包括以下几个方面:
1. 层级组织和展示:树形菜单可以清晰地展示数据或功能的层级结构。通过树状的层级关系,用户可以迅速了解和浏览菜单项之间的关系,方便快速导航到特定的功能或内容。
2. 方便快速定位:树形菜单在大型系统或网站中,通过结构化的方式组织和展示大量的导航选项。用户可以根据自己的需求和目的,在树形菜单中快速定位到所需的功能,从而节省浏览和搜索的时间。
3. 显示和隐藏子菜单:树形菜单通常有展开和折叠的功能,即用户可以点击父级菜单项来显示或隐藏其下级子菜单。这使得菜单可以在有限的空间内展示较大的导航选项,同时保持界面的整洁性。
4. 多层级管理:树形菜单可以适应多层级的数据管理需求。它可以呈现多个层级的数据关系,比如组织架构、文件目录结构等。在管理和浏览复杂数据时,树形菜单能够提供一种直观且便捷的方式。
5. 用户交互体验:树形菜单可以提升用户的交互体验。通过鼠标点击或触摸操作,用户可以展开或折叠菜单项,快速导航到所需内容。这种直观且可视化的操作方式,提高了用户与系统之间的互动效率和便利性。
总之,树形菜单在网页、软件和应用程序中起到了非常重要的作用。它提供了一种组织和展示导航选项的有效方式,帮助用户快速定位和访问所需功能,提升用户的使用体验和效率。
3树形菜单优缺点
树形菜单具有以下优点:
1. 可视化层级关系:树形菜单以树状结构展示导航选项,使用户可以直观地了解各个选项之间的层级关系,方便快速导航和定位。
2. 灵活性:树形菜单可以适应不同层级和数量的导航选项,能够处理大量数据和复杂的层级关系。
3. 简洁性:通过折叠和展开的功能,树形菜单可以在有限的空间内显示大量的导航选项,保持界面整洁,减少页面的混乱感。
4. 易于扩展和维护:树形菜单的结构清晰,添加、删除和调整选项相对容易,维护和管理导航结构非常方便。
5. 用户体验:由于树形菜单提供了层级关系和可视化展示,用户可以更快速、方便地定位和访问所需的功能或信息,提升了用户的使用体验。
然而,树形菜单也存在一些缺点:
1. 空间占用:如果树形菜单的层级关系较多或导航选项较多,菜单可能需要占用较多的页面空间,特别是在小屏幕设备上可能会受到限制。
2. 复杂性:树形菜单在处理复杂的层级关系时可能变得复杂,包括维护关系、数据加载的处理等,可能需要更多的开发和设计工作。
3. 可视性问题:当层级关系过多时,一些子选项可能在屏幕上不可见,需要用户进行滚动等操作才能浏览到。
4. 导航路径的可视化不足:在多层级的树形菜单中,用户可能无法直观地看到当前所处的导航路径,需要通过其他方式来弥补。
综上所述,树形菜单在提供层级导航和可视化展示方面具有明显的优势,但在空间占用、复杂性和可视性等方面存在一些潜在的限制,需根据具体情况权衡利弊来选择使用。
4树形导航栏
实体类
package com.zking.entity;
/**
* 菜单权限实体类
*/
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;
}
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;
}
@Override
public String toString() {
return "Permission [id=" + id + ", name=" + name + ", description=" + description + ", url=" + url + ", pid="
+ pid + ", ismenu=" + ismenu + ", displayno=" + displayno + "]";
}
}
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();
}
}
BuildTree
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 || "0".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("0");
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) {
String pid = children.getParentId();
if (pid == null || idParam.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;
}
}
}
return topNodes;
}
}
dao方法
package com.zking.util;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 所有Dao层的父类 BookDao UserDao OrderDao ...
*
* @author Administrator
*
* @param <T>
*/
public class BaseDao<T> {
/**
* 适合多表联查的数据返回
* @param sql
* @param pageBean
* @return
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
*/
public List<Map<String, Object>> executeQuery(String sql, PageBean pageBean)
throws SQLException, InstantiationException, IllegalAccessException {
List<Map<String, Object>> list = new ArrayList<>();
Connection con = DBAccess.getConnection();
PreparedStatement pst = null;
ResultSet rs = null;
/*
* 是否需要分页? 无需分页(项目中的下拉框,查询条件教员下拉框,无须分页) 必须分页(项目中列表类需求、订单列表、商品列表、学生列表...)
*/
if (pageBean != null && pageBean.isPagination()) {
// 必须分页(列表需求)
String countSQL = getCountSQL(sql);
pst = con.prepareStatement(countSQL);
rs = pst.executeQuery();
if (rs.next()) {
pageBean.setTotal(String.valueOf(rs.getObject(1)));
}
// 挪动到下面,是因为最后才处理返回的结果集
// -- sql=SELECT * FROM t_mvc_book WHERE bname like '%圣墟%'
// -- pageSql=sql limit (page-1)*rows,rows 对应某一页的数据
// -- countSql=select count(1) from (sql) t 符合条件的总记录数
String pageSQL = getPageSQL(sql, pageBean);// 符合条件的某一页数据
pst = con.prepareStatement(pageSQL);
rs = pst.executeQuery();
} else {
// 不分页(select需求)
pst = con.prepareStatement(sql);// 符合条件的所有数据
rs = pst.executeQuery();
}
// 获取源数据
ResultSetMetaData md = rs.getMetaData();
int count = md.getColumnCount();
Map<String, Object> map = null;
while (rs.next()) {
map = new HashMap<>();
for (int i = 1; i <= count; i++) {
// map.put(md.getColumnName(i), rs.getObject(i));
map.put(md.getColumnLabel(i), rs.getObject(i));
}
list.add(map);
}
return list;
}
/**
*
* @param sql
* @param attrs
* map中的key
* @param paMap
* jsp向后台传递的参数集合
* @return
* @throws SQLException
* @throws NoSuchFieldException
* @throws SecurityException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public int executeUpdate(String sql, String[] attrs, Map<String, String[]> paMap) throws SQLException,
NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Connection con = DBAccess.getConnection();
PreparedStatement pst = con.prepareStatement(sql);
for (int i = 0; i < attrs.length; i++) {
pst.setObject(i + 1, JsonUtils.getParamVal(paMap, attrs[i]));
}
return pst.executeUpdate();
}
/**
* 批处理
* @param sqlLst
* @return
*/
public static int executeUpdateBatch(String[] sqlLst) {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = DBAccess.getConnection();
// 设置不自动提交
conn.setAutoCommit(false);
for (String sql : sqlLst) {
stmt = conn.prepareStatement(sql);
stmt.executeUpdate();
}
conn.commit();
} catch (Exception e) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
throw new RuntimeException(e1);
}
e.printStackTrace();
throw new RuntimeException(e);
} finally {
DBAccess.close(conn, stmt, null);
}
return sqlLst.length;
}
/**
* 通用的增删改方法
*
* @param book
* @throws Exception
*/
public int executeUpdate(String sql, T t, String[] attrs) throws Exception {
// String[] attrs = new String[] {"bid", "bname", "price"};
Connection con = DBAccess.getConnection();
PreparedStatement pst = con.prepareStatement(sql);
// pst.setObject(1, book.getBid());
// pst.setObject(2, book.getBname());
// pst.setObject(3, book.getPrice());
/*
* 思路: 1.从传进来的t中读取属性值 2.往预定义对象中设置了值
*
* t->book f->bid
*/
for (int i = 0; i < attrs.length; i++) {
Field f = t.getClass().getDeclaredField(attrs[i]);
f.setAccessible(true);
pst.setObject(i + 1, f.get(t));
}
return pst.executeUpdate();
}
/**
* 通用分页查询
*
* @param sql
* @param clz
* @return
* @throws Exception
*/
public List<T> executeQuery(String sql, Class<T> clz, PageBean pageBean) throws Exception {
List<T> list = new ArrayList<T>();
Connection con = DBAccess.getConnection();
;
PreparedStatement pst = null;
ResultSet rs = null;
/*
* 是否需要分页? 无需分页(项目中的下拉框,查询条件教员下拉框,无须分页) 必须分页(项目中列表类需求、订单列表、商品列表、学生列表...)
*/
if (pageBean != null && pageBean.isPagination()) {
// 必须分页(列表需求)
String countSQL = getCountSQL(sql);
pst = con.prepareStatement(countSQL);
rs = pst.executeQuery();
if (rs.next()) {
pageBean.setTotal(String.valueOf(rs.getObject(1)));
}
// 挪动到下面,是因为最后才处理返回的结果集
// -- sql=SELECT * FROM t_mvc_book WHERE bname like '%圣墟%'
// -- pageSql=sql limit (page-1)*rows,rows 对应某一页的数据
// -- countSql=select count(1) from (sql) t 符合条件的总记录数
String pageSQL = getPageSQL(sql, pageBean);// 符合条件的某一页数据
pst = con.prepareStatement(pageSQL);
rs = pst.executeQuery();
} else {
// 不分页(select需求)
pst = con.prepareStatement(sql);// 符合条件的所有数据
rs = pst.executeQuery();
}
while (rs.next()) {
T t = clz.newInstance();
Field[] fields = clz.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
f.set(t, rs.getObject(f.getName()));
}
list.add(t);
}
return list;
}
/**
* 将原生SQL转换成符合条件的总记录数countSQL
*
* @param sql
* @return
*/
private String getCountSQL(String sql) {
// -- countSql=select count(1) from (sql) t 符合条件的总记录数
return "select count(1) from (" + sql + ") t";
}
/**
* 将原生SQL转换成pageSQL
*
* @param sql
* @param pageBean
* @return
*/
private String getPageSQL(String sql, PageBean pageBean) {
// (this.page - 1) * this.rows
// pageSql=sql limit (page-1)*rows,rows
return sql + " limit " + pageBean.getStartIndex() + "," + pageBean.getRows();
}
}
PermissionAction
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;
public class PermissionDao extends BaseDao<Permission>{
public List<Permission> list(Permission permission,PageBean pagebean) throws Exception {
String sql="select * from t_easyui_permission where 1=1";
return super.executeQuery(sql, Permission.class, pagebean);
}
//将这些平级数据转换成父子关系的数据
public List<TreeVo<Permission>> menus(Permission permission,PageBean pagebean) throws Exception{
//创建一个父子关系的集合 TreeVo
List<TreeVo<Permission>> tvList = new ArrayList<TreeVo<Permission>>();
//获取平级数据
List<Permission> list = this.list(permission, pagebean);
for (Permission per : list) {
TreeVo<Permission> tv=new TreeVo<>();
//将per对象转成tv对象,因为tv对象才有children
tv.setId(per.getId()+"");
tv.setText(per.getName());
tv.setParentId(per.getPid()+"");
tvList.add(tv);
}
return BuildTree.buildList(tvList, "-1");//这个地方填写一级菜单的id
}
}
前台
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE>
<html>
<head>
<%@include file="common/header.jsp" %>
<!-- <script type="text/javascript" src="js/index.js"></script> -->
</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>
layui.use(['element', 'layer', 'util'], function(){
var element = layui.element
,layer = layui.layer
,util = layui.util
,$ = layui.$;
$.ajax({
url:"${pageContext.request.contextPath }/permission.action?methodName=menus",
dataType:'json',
success:function(data){
/* console.log(data); */
/* 定义标签要拼接的文本内容 */
let datacontent = '';
console.log(data);
$.each(data,function(i,n){
datacontent += ' <li class="layui-nav-item layui-nav-itemed">';
datacontent +='<a class="" href="javascript:;">'+n.text+'</a>';
/* 判断是否有子节点有就进行子菜单增加,没有就不加 */
if(n.hasChildren){
datacontent +='<dl class="layui-nav-child">';
/* 循环所有的子节点 n.children*/
$.each(n.children,function(index,node){
datacontent += '<dd><a href="javascript:;">'+node.text+'</a></dd>';
})
datacontent += ' </dl> ';
}
datacontent += ' </li> ';
})
/* 重置里面的文本内容 */
$("#menu").html(datacontent);
//渲染
element.render('menu');
}
});
});
</script>
</body>
</html>