文章目录
前言
页面中经常用到树菜单,大部分树菜单列表都是由后端处理,前端直接使用。或者前端生成固定层级的树菜单列表。我是因为在工作中被要求把一个二级菜单改造为支持三级菜单,且菜单列表仅是个list,不支持树,改造的过程中突发奇想万一后面再需要我继续改造四级,或者其他功能也需要这样显示,那我岂不是要重复很多工作任务,于是我决定封装自己无限树列表和树渲染。
一、普通列表改造为无限树列表
接口返回的菜单列表
var menulist = [
{"id":1633,"name":"一级菜单1","pId":1632,"open":"false","type":"themecat"},
{"id":1635,"name":"二级菜单1.1","pId":1633,"open":"false","type":"themecat"},
{"id":1636,"name":"二级菜单1.2","pId":1633,"open":"false","type":"themecat"},
{"id":1634,"name":"二级菜单1.3","pId":1633,"open":"false","type":"themecat"},
{"id":22393,"name":"三级菜单1.3.1","pId":1634,"open":"false","type":"themecat"},
{"id":22394,"name":"三级菜单1.3.2","pId":1634,"open":"false","type":"themecat"},
{"id":22395,"name":"三级菜单1.3.3","pId":1634,"open":"false","type":"themecat"},
{"id":1637,"name":"二级菜单1.4","pId":1633,"open":"false","type":"themecat"},
{"id":1638,"name":"一级菜单2","pId":1632,"open":"false","type":"themecat"},
{"id":1586,"name":"二级菜单2.1","pId":1638,"open":"false","type":"themecat"},
{"id":1585,"name":"二级菜单2.2","pId":1638,"open":"false","type":"themecat"},
{"id":1641,"name":"一级菜单3","pId":1632,"open":"false","type":"themecat"},
{"id":1571,"name":"二级菜单3.1","pId":1641,"open":"false","type":"themecat"},
{"id":1572,"name":"二级菜单3.2","pId":1641,"open":"false","type":"themecat"},
{"id":4281,"name":"二级菜单3.3","pId":1641,"open":"false","type":"themecat"}
]
要求pid 对应id作为对应id的children,代码如下
function initTreeOption(treearr) {
var obj = {}
, menutree = [];
// 这里遍历普通列表 把数组[]=>{}对象
treearr.forEach((item)=>{
// 这里可以加自己想要的属性,如class名之类的
// 增加id前缀,避免实际id为数字时对象对键进行自动排序
// 解构赋值避免生成menutree时对原来的数据有影响
obj['id-' + item.id] = {...item}
})
for (var key in obj) {
var menuObj = obj[key];
if (menuObj) {
// 取得当前节点的父节点
var parent = obj['id-' + menuObj.pId];
// 如果父节点存在则当前节点为叶节点
if (parent) {
// 在父节点的children下添加该叶节点
parent.children = parent.children || [];
parent.children.push(menuObj);
} else {
//否则为根节点
menutree.push(menuObj);
}
}
}
//返回树列表
return menutree;
}
var menutree = initTreeOption(menulist)
处理过后的数据,如下
[{
"id": 1633,
"name": "一级菜单1",
"pId": 1632,
"open": "false",
"type": "themecat",
"children": [{
"id": 1635,
"name": "二级菜单1.1",
"pId": 1633,
"open": "false",
"type": "themecat"
}, {
"id": 1636,
"name": "二级菜单1.2",
"pId": 1633,
"open": "false",
"type": "themecat"
}, {
"id": 1634,
"name": "二级菜单1.3",
"pId": 1633,
"open": "false",
"type": "themecat",
"children": [{
"id": 22393,
"name": "三级菜单1.3.1",
"pId": 1634,
"open": "false",
"type": "themecat"
}, {
"id": 22394,
"name": "三级菜单1.3.2",
"pId": 1634,
"open": "false",
"type": "themecat"
}, {
"id": 22395,
"name": "三级菜单1.3.3",
"pId": 1634,
"open": "false",
"type": "themecat"
}]
}, {
"id": 1637,
"name": "二级菜单1.4",
"pId": 1633,
"open": "false",
"type": "themecat"
}]
}, {
"id": 1638,
"name": "一级菜单2",
"pId": 1632,
"open": "false",
"type": "themecat",
"children": [{
"id": 1586,
"name": "二级菜单2.1",
"pId": 1638,
"open": "false",
"type": "themecat"
}, {
"id": 1585,
"name": "二级菜单2.2",
"pId": 1638,
"open": "false",
"type": "themecat"
}]
}, {
"id": 1641,
"name": "一级菜单3",
"pId": 1632,
"open": "false",
"type": "themecat",
"children": [{
"id": 1571,
"name": "二级菜单3.1",
"pId": 1641,
"open": "false",
"type": "themecat"
}, {
"id": 1572,
"name": "二级菜单3.2",
"pId": 1641,
"open": "false",
"type": "themecat"
}, {
"id": 4281,
"name": "二级菜单3.3",
"pId": 1641,
"open": "false",
"type": "themecat"
}]
}]
二、无限树菜单dom渲染
1.使用递归进行dom渲染代码如下:
function renderTree(menutree, $elem) {
// 内部初始化html文本方法
function initTreeHml(menutree) {
// 如果menutree不存在或长度为0 返回空
if(!menutree || menutree.length == 0){
return ''
}
var treeHtml = ['<ul class="tree-ul">'];
for (var i = 0; i < menutree.length; i++) {
var item = menutree[i];
// 渲染父节点html
if (item.children && item.children.length) {
//生成本级html
treeHtml.push('<li class="tree-li tree-parent"><a class="tree-li-title" href="javascript:void(0)" data-haschild="true" data-id=" ' + item.id + '" data-mc=" ' + item.name + '"><label class="tree-li-title-label">' + item.name + '</label><i class="tree-li-icon inactive"></i></a>');
// 递归调用自身 生成叶节点的html
treeHtml.push(initTreeHml(item.children) + '</li>');
} else {
// 渲染叶节点html
treeHtml.push('<li class="tree-li tree-leaf"><a class="tree-li-title" href="javascript:void(0)" id="' + item.id + '" mc="' + item.name + '"><label class="tree-li-title-label">' + item.name + '</label></a></li>');
}
}
treeHtml.push('</ul>')
//html拼接
return treeHtml.join('')
}
// dom渲染
$elem.html(initTreeHml(menutree));
}
2.全部代码
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
.tree-con {
width: 270px;
border: 1px solid #efefef;
}
.tree-ul {
line-height: 2em;
padding: 0;
}
.tree-li {
list-style: none;
}
.tree-parent {
padding: 0 10px;
}
.tree-parent .tree-ul {
display: none;
padding-left: 20px;
}
.tree-li-title {
display: block;
height: 30px;
padding: 0 10px;
text-decoration: none;
color: #5a5757;
}
.tree-parent>.tree-li-title {
margin: 1px 0;
background: #f7dede;
border-bottom: 1px solid #bdbdbd;
}
label.tree-li-title-label {
display: inline-block;
width: 100%;
vertical-align: middle;
}
.tree-li-icon {
float: right;
margin-top: -2px;
font-size: 24px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin-left: -30px;
}
.add:before {
content: '+';
}
.minus:before {
content: '-';
}
</style>
</head>
<body>
<div id="tree-menu" class="tree-con"></div>
<script type="text/javascript" src="https://xinjiang.chinatax.gov.cn/images/jquery-1.11.3.js"></script>
<script>
$(function(){
var menulist = [
{"id":1633,"name":"一级菜单1","pId":1632,"open":"false","type":"themecat"},
{"id":1635,"name":"二级菜单1.1","pId":1633,"open":"false","type":"themecat"},
{"id":1636,"name":"二级菜单1.2","pId":1633,"open":"false","type":"themecat"},
{"id":1634,"name":"二级菜单1.3","pId":1633,"open":"false","type":"themecat"},
{"id":22393,"name":"三级菜单1.3.1","pId":1634,"open":"false","type":"themecat"},
{"id":22394,"name":"三级菜单1.3.2","pId":1634,"open":"false","type":"themecat"},
{"id":22395,"name":"三级菜单1.3.3","pId":1634,"open":"false","type":"themecat"},
{"id":1637,"name":"二级菜单1.4","pId":1633,"open":"false","type":"themecat"},
{"id":1638,"name":"一级菜单2","pId":1632,"open":"false","type":"themecat"},
{"id":1586,"name":"二级菜单2.1","pId":1638,"open":"false","type":"themecat"},
{"id":1585,"name":"二级菜单2.2","pId":1638,"open":"false","type":"themecat"},
{"id":1641,"name":"一级菜单3","pId":1632,"open":"false","type":"themecat"},
{"id":1571,"name":"二级菜单3.1","pId":1641,"open":"false","type":"themecat"},
{"id":1572,"name":"二级菜单3.2","pId":1641,"open":"false","type":"themecat"},
{"id":4281,"name":"二级菜单3.3","pId":1641,"open":"false","type":"themecat"}
]
// 初始化树菜单列表
var menutree = initTreeOption(menulist);
// 渲染树菜单
renderTree(menutree,$('#tree-menu'));
// 监听点击事件
$('#tree-menu').on('click','.tree-li-title',event);
})
function initTreeOption(treearr) {
var obj = {}
, menutree = [];
// 这里遍历普通列表 把数组[]=>{}对象
treearr.forEach((item)=>{
// 这里可以加自己想要的属性,如class名之类的
// 增加id前缀,避免实际id为数字时对象对键进行自动排序
// 解构赋值避免生成menutree时对原来的数据有影响
obj['id-' + item.id] = {...item}
})
for (var key in obj) {
var menuObj = obj[key];
if (menuObj) {
// 取得当前节点的父节点
var parent = obj['id-' + menuObj.pId];
// 如果父节点存在则当前节点为叶节点
if (parent) {
// 在父节点的children下添加该叶节点
parent.children = parent.children || [];
parent.children.push(menuObj);
} else {
//否则为根节点
menutree.push(menuObj);
}
}
}
//返回树列表
return menutree;
}
function renderTree(menutree,$elem) {
// 内部初始化html文本方法
function initTreeHml(menutree) {
// 如果menutree不存在或长度为0 返回空
if(!menutree || menutree.length == 0){
return ''
}
var treeHtml = ['<ul class="tree-ul">'];
for (var i = 0; i < menutree.length; i++) {
var item = menutree[i];
// 渲染父节点html
if (item.children && item.children.length) {
//生成本级html
treeHtml.push('<li class="tree-li tree-parent"><a class="tree-li-title" href="javascript:void(0)" data-haschild="true" data-id=" ' + item.id + '" data-mc=" ' + item.name + '"><label class="tree-li-title-label">' + item.name + '</label><i class="tree-li-icon add"></i></a>');
// 递归调用自身 生成叶节点的html
treeHtml.push(initTreeHml(item.children) + '</li>');
} else {
// 渲染叶节点html
treeHtml.push('<li class="tree-li tree-leaf"><a class="tree-li-title" href="javascript:void(0)" id="' + item.id + '" mc="' + item.name + '"><label class="tree-li-title-label">' + item.name + '</label></a></li>');
}
}
treeHtml.push('</ul>')
//html拼接
return treeHtml.join('')
}
// dom渲染
$elem.html(initTreeHml(menutree));
}
function event(e){
var $this = $(this),
$next = $this.next(),
$parent = $this.parent();
$this.toggleClass('active');
if($parent.is('.tree-parent')){
if($this.is('.active')){
$this.children('.tree-li-icon').removeClass('add').addClass('minus');
$next.slideDown();
}else{
$next.children('ul').css('display','none');
$parent.find('.minus').removeClass('minus').addClass('add');
$next.slideUp();
}
}else{
//叶节点事件
}
}
</script>
</body>
</html>