HTML + CSS + JSS实现页面树形菜单展示功能
- 功能介绍
- 后端代码
- 前端代码
- CSS样式
- JS
- 【JS旧代码,有问题】
- HTML
- 效果展示
功能介绍
- 后台管理端的角色管理功能;
- 树型菜单能够正确展示,包括父节点选中子节点全选,子节点全部取消父节点也取消,选中任一子项其父级自动选中,以及其他正常操作的展示;
- 由于是html+css+js编写,通过checked属性的确可以正确更改样式,但实际使用会发现页面无法实时渲染,从而导致父级全选子级不会全选等问题,可以自己更改代码验证。如果办法消除此问题,欢迎回复我;
- 通过name属性标记哪些节点是选中的,在添加角色时,遍历获取所有"name"="1"的input的value(角色id),提交到后端,从而实现功能。
后端代码
通过递归查出树形菜单结构
private List<WzAuthorityBO> getAuthorityTree(String parentId) {
List<WzAuthorityBO> list = new ArrayList<>();
List<WzAuthority> wzAuthorities = wzAuthorityMapper.selectWzAuthorityList(parentId);
if (!CollectionUtils.isEmpty(wzAuthorities)) {
for (WzAuthority item : wzAuthorities) {
WzAuthorityBO wzAuthorityBO = new WzAuthorityBO();
BeanUtils.copyProperties(item, wzAuthorityBO);
wzAuthorityBO.setChildren(this.getAuthorityTree(item.getId()));
list.add(wzAuthorityBO);
}
}
return list;
}
返回结构如下:
{
"code": "000000",
"msg": "操作成功",
"data": {
"menus": [
{
"id": "2",
"authorityName": "账号中心",
"type": 0,
"path": null,
"level": null,
"parentId": "0",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": [
{
"id": "32",
"authorityName": "账号列表",
"type": 0,
"path": null,
"level": null,
"parentId": "2",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": [
{
"id": "33",
"authorityName": "列表接口",
"type": 0,
"path": null,
"level": null,
"parentId": "32",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
}
]
},
{
"id": "91",
"authorityName": "角色列表",
"type": 0,
"path": null,
"level": null,
"parentId": "2",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
}
]
},
{
"id": "89",
"authorityName": "企业中心",
"type": 0,
"path": null,
"level": null,
"parentId": "0",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": [
{
"id": "86",
"authorityName": "变更管理",
"type": 0,
"path": null,
"level": null,
"parentId": "89",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": [
{
"id": "49",
"authorityName": "列表接口",
"type": 0,
"path": null,
"level": null,
"parentId": "86",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
}
]
},
{
"id": "87",
"authorityName": "机构列表",
"type": 0,
"path": null,
"level": null,
"parentId": "89",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": [
{
"id": "29",
"authorityName": "列表导出",
"type": 0,
"path": null,
"level": null,
"parentId": "87",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "58",
"authorityName": "详情接口",
"type": 0,
"path": null,
"level": null,
"parentId": "87",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "59",
"authorityName": "审核接口",
"type": 0,
"path": null,
"level": null,
"parentId": "87",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "60",
"authorityName": "查询接口",
"type": 0,
"path": null,
"level": null,
"parentId": "87",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "61",
"authorityName": "列表路由",
"type": 0,
"path": null,
"level": null,
"parentId": "87",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "65",
"authorityName": "审核路由",
"type": 0,
"path": null,
"level": null,
"parentId": "87",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "77",
"authorityName": "详情路由",
"type": 0,
"path": null,
"level": null,
"parentId": "87",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
}
]
},
{
"id": "88",
"authorityName": "企业列表",
"type": 0,
"path": null,
"level": null,
"parentId": "89",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": [
{
"id": "68",
"authorityName": "标签接口",
"type": 0,
"path": null,
"level": null,
"parentId": "88",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "69",
"authorityName": "列表接口",
"type": 0,
"path": null,
"level": null,
"parentId": "88",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "70",
"authorityName": "信息接口",
"type": 0,
"path": null,
"level": null,
"parentId": "88",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "71",
"authorityName": "详情路由",
"type": 0,
"path": null,
"level": null,
"parentId": "88",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "82",
"authorityName": "操作接口",
"type": 0,
"path": null,
"level": null,
"parentId": "88",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
},
{
"id": "90",
"authorityName": "列表导出",
"type": 0,
"path": null,
"level": null,
"parentId": "88",
"isRequireAuth": null,
"createTime": null,
"updateTime": null,
"children": []
}
]
}
]
}
],
"checkedKeys": []
}
}
前端代码
CSS样式
/**
* DOM树
*/
ul>li{
list-style: none;
}
/* 可展开*/
.switch-open {
margin-left:-12px;
border:6px solid transparent;
display:inline-block;
width:0px;
height:0px;
border-top-color: black;
margin-right: 5px;
}
/* 展开完毕*/
.switch-close {
margin-left:-12px;
border:6px solid transparent;
display:inline-block;
width:0px;
height:0px;
border-left-color: black;
margin-bottom: 2px;
margin-right: 5px;
}
/* 改变CheckBox样式*/
input[type='checkbox']{
width: 20px;
height: 20px;
-webkit-appearance:none;
-moz-appearance: none;
border: 1px solid #c9c9c9;
border-radius: 3px;
outline: none;
color:white;
text-align: center;
margin-right: 5px;
}
input[type='checkbox']:before {
content: '√ ';
color:transparent;
}
input[type=checkbox]:checked{
background-color: #409eff;
}
input[type=checkbox]:checked:before{
content: '√';
color:white;
font-weight: bold;
}
JS
function getRoleMenuTree(roleId) {
var param = {};
param.roleId = roleId;
$.ajax({
type: 'get',
url: ctx+'/user/admin/perm/roleMenuTreeselect',
cache: false, //禁用缓存
data: param,
dataType: 'json',
success: function(res) {
if (res.code === '000000') {
var menus = res.data.menus;
generate(menus, document.getElementById('container'));
}
},
error() {
console.log('error');
}
})
}
//这里生成DOM
function generate(menus, par) {
for (var i = 0; i < menus.length; i++) {
var wzAuthority = menus[i];
var ele = document.createElement('li');
if(wzAuthority.children !== null && wzAuthority.children.length == 0) {
if (wzAuthority.checked) {
ele.innerHTML=' <input type="checkbox" οnclick="checkSingleChange(this)" checked="true" name="1" value="' + wzAuthority.id + '"></input>'+wzAuthority.authorityName;
} else {
ele.innerHTML=' <input type="checkbox" οnclick="checkSingleChange(this)" value="' + wzAuthority.id + '"></input>'+wzAuthority.authorityName;
}
} else {
if (wzAuthority.checked) {
ele.innerHTML='<span><span class="switch-open" οnclick="toggle(this)"></span><input type="checkbox" checked="true" name="1" οnclick="checkChange(this)" value="' + wzAuthority.id + '"></input>' + wzAuthority.authorityName + '</span>';
} else {
ele.innerHTML='<span><span class="switch-open" οnclick="toggle(this)"></span><input type="checkbox" οnclick="checkChange(this)" value="' + wzAuthority.id + '"></input>' + wzAuthority.authorityName + '</span>';
}
var nextpar = document.createElement('ul');
ele.appendChild(nextpar);
generate(wzAuthority.children, nextpar);
}
par.appendChild(ele);
}
}
//处理展开和收起
function toggle(eve) {
var par=eve.parentNode.nextElementSibling;
if(par.style.display=='none') {
par.style.display='block';
eve.className='switch-open';
} else {
par.style.display='none';
eve.className='switch-close';
}
}
//处理全部勾选和全部不选
function checkChange(eve) {
var oul = eve.parentNode.nextElementSibling;
if($(eve).prop("checked")){
$(eve).parent().next().find("input[type='checkbox']").prop("checked",true);
// 下面是为了提交的时候,我能知道哪个权限是被选中的,
// 但是使用.setAttribute("checked", true)会导致页面勾选的显示异常,因此多加一个name属性标记,同时对页面显示无影响
eve.parentNode.parentNode.firstChild.querySelectorAll('input')[0].setAttribute("name", "1");
for(var i=0;i<oul.querySelectorAll('input').length;i++) {
if ("1" !== oul.querySelectorAll('input')[i].getAttribute("name")) {
oul.querySelectorAll('input')[i].setAttribute("name", "1");
}
}
}else{
$(eve).parent().next().find("input[type='checkbox']").filter(":checked").prop("checked",false).removeAttr("name");
eve.parentNode.parentNode.firstChild.querySelectorAll('input')[0].removeAttribute("name");
for(var i=0;i<oul.querySelectorAll('input').length;i++) {
oul.querySelectorAll('input')[i].removeAttribute("name");
}
}
}
// 处理单个勾选和单个不选
function checkSingleChange(eve) {
var oul = eve.parentNode.nextElementSibling;
if($(eve).prop("checked")){
$(eve).parent().parent().prev().find("input[type='checkbox']").prop("checked",true);
// 子项选中之后,子父级都要有name标记
// 父级加标记
eve.parentNode.parentNode.previousSibling.childNodes.item(1).setAttribute("name", "1");
// 自己加标记
eve.setAttribute("name", "1");
}else{
// 如果取消选中,子级不存在勾选中的,父级删除标记
if ($(eve).parent().siblings().find("input[type='checkbox']").filter(":checked").length === 0) {
$(eve).parent().parent().prev().find("input[type='checkbox']").filter(":checked").prop("checked",false);
eve.parentNode.parentNode.previousSibling.childNodes.item(1).removeAttribute("name");
}
//删除自己的标记
eve.removeAttribute("name");
}
}
【JS旧代码,有问题】
- 原来的思路是通过设置checked属性,控制选中与否的显示,最后发现树型菜单多点击几次就会出现异常展示,比如明明没有checked仍然显示、有checked却不显示。是由于原生js无法动态渲染页面(可能是我比较菜,有知道解决方案的大佬欢迎回复解决我的问题,谢谢。)
- 之后就想到,我用checked主要是为了找到我到底选中了哪些角色,后来就想到换个name属性标记,从而解决该问题。JS代码在上面。
//这里生成DOM
function generate(menus, par) {
for (var i = 0; i < menus.length; i++) {
var wzAuthority = menus[i];
var ele = document.createElement('li');
if(wzAuthority.children !== null && wzAuthority.children.length == 0) {
if (wzAuthority.checked) {
ele.innerHTML=' <input type="checkbox" οnclick="checkSingleChange(this)" checked="true" value="' + wzAuthority.id + '"></input>'+wzAuthority.authorityName;
} else {
ele.innerHTML=' <input type="checkbox" οnclick="checkSingleChange(this)" value="' + wzAuthority.id + '"></input>'+wzAuthority.authorityName;
}
// ele.innerHTML=' <input type="checkbox" οnclick="checkSingleChange(this)" value="' + wzAuthority.id + '"></input>'+wzAuthority.authorityName;
} else {
if (wzAuthority.checked) {
ele.innerHTML='<span><span class="switch-open" οnclick="toggle(this)"></span><input type="checkbox" checked="true" οnclick="checkChange(this)" value="' + wzAuthority.id + '"></input>' + wzAuthority.authorityName + '</span>';
} else {
ele.innerHTML='<span><span class="switch-open" οnclick="toggle(this)"></span><input type="checkbox" οnclick="checkChange(this)" value="' + wzAuthority.id + '"></input>' + wzAuthority.authorityName + '</span>';
}
// ele.innerHTML='<span><span class="switch-open" οnclick="toggle(this)"></span><input type="checkbox" οnclick="checkChange(this)" value="' + wzAuthority.id + '"></input>' + wzAuthority.authorityName + '</span>';
var nextpar = document.createElement('ul');
ele.appendChild(nextpar);
generate(wzAuthority.children, nextpar);
}
par.appendChild(ele);
}
}
//处理展开和收起
function toggle(eve) {
var par=eve.parentNode.nextElementSibling;
if(par.style.display=='none') {
par.style.display='block';
eve.className='switch-open';
} else {
par.style.display='none';
eve.className='switch-close';
}
}
//处理全部勾选和全部不选
function checkChange(eve) {
var oul = eve.parentNode.nextElementSibling;
var parent = oul.parentNode.firstChild.childNodes.item(1);
if(eve.checked) {
parent.setAttribute("checked", "true");
for(var i=0;i<oul.querySelectorAll('input').length;i++) {
if ("true" != oul.querySelectorAll('input')[i].getAttribute("checked")) {
oul.querySelectorAll('input')[i].setAttribute("checked", "true");
}
}
} else {
parent.setAttribute("checked","false");
console.log('当前元素---',oul)
for(var i=0;i<oul.querySelectorAll('input').length;i++) {
oul.querySelectorAll('input')[i].removeAttribute("checked");
}
}
}
//处理单个勾选和单个不选
function checkSingleChange(eve) {
console.log(eve)
if(eve.checked) {
eve.setAttribute("checked", "true");
//子元素选中,则父元素选中
console.log(eve.parentNode.parentNode.previousSibling.childNodes.item(1))
eve.parentNode.parentNode.previousSibling.childNodes.item(1).setAttribute("checked", "true");
} else {
eve.removeAttribute("checked");
//如果子元素全部清除,父元素也取消选中
for(var i=0;i<eve.parentNode.parentNode.querySelectorAll('input').length;i++) {
let inputElement = eve.parentNode.parentNode.querySelectorAll('input')[i];
if (inputElement.getAttribute("checked") == "true") {
return;
}
}
eve.parentNode.parentNode.previousSibling.childNodes.item(1).removeAttribute("checked");
}
}
HTML
<div class="warp">
<ul id="container">
</ul>
</div>
效果展示