一、背景
因客户需要,有一个功能是需要注册用户时选择多个【单元 - 楼层 - 设备】的绑定,谷歌了一通,没有找到想要的,无奈之举只能手写一个…
二 、效果展示
三、功能点
1、初始化时,默认展开选中的菜单
2、点击每一层父级菜单,会自动折叠其子菜单
3、选中子级节点会默认选中父级节点
4、子级节点都没选中默认取消选中父级节点
5、选中父级节点默认选中其所有子级节点
四、代码实现
这里没写组件,如果需要可以改为组件。
可能会有一个疑问为哈是 treeMenu2 不是 treeMenu1
因为 treeMenu1 写的low,treeMenu2 升级了js代码。
1、treeMenu2.js
js里面的各种事件均为递归操作,可根据加载的数据动态进行操作。
// pages/biz/treeMenu/treeMenu.js
Page({
/**
* 页面的初始数据
*/
data: {
menuTreeImgLeft: "../../../icon/f_left.png",
menuTreeImgBottom: "../../../icon/f_bottom.png",
menuTree: [{
"checked": false,
"children": [{
"checked": false,
"children": [{
"id": "1-1-1",
"checked": true,
"field": "1",
"title": "设备1",
}, {
"id": "1-1-2",
"checked": false,
"field": "2",
"title": "设备2"
}],
"id": "1-1",
"field": "1-floor",
"title": "1楼",
"isHidden": true,
"bindAll": false,
}, {
"checked": false,
"children": [{
"checked": false,
"field": "3",
"title": "设备3"
}],
"id": "1-2",
"field": "2-floor",
"title": "2楼",
"isHidden": true,
}, {
"checked": false,
"children": [{
"id": "1-3-4",
"checked": true,
"field": "4",
"title": "设备4"
}],
"id": "1-3",
"field": "3-floor",
"title": "3楼",
"isHidden": true,
}],
"id": "1",
"isHidden": true,
"bindAll": false,
"field": "1-unit",
"title": "1单元"
}, {
"checked": false,
"children": [{
"checked": false,
"children": [{
"id": "2-1-1",
"checked": false,
"field": "5",
"title": "设备5"
}],
"id": "2-1",
"field": "1-floor",
"title": "1楼",
"isHidden": true,
}, {
"checked": false,
"children": [{
"id": "2-2-1",
"checked": false,
"field": "6",
"title": "设备6"
}],
"id": "2-2",
"field": "2-floor",
"title": "2楼",
"isHidden": true,
}],
"bindAll": false,
"isHidden": true,
"field": "2-unit",
"title": "2单元",
"id": "2",
}],
deepList: [],
deepListOne: []
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
this.checkForChecked()
},
/**
* 默认选中是否展开
*/
checkForChecked() {
var data = this.data.menuTree
// 获取所有被选中的节点
var checkedNodes = this.getDeep(data)
// 获取所有选中节点的父节点
checkedNodes.forEach(element => {
var tmp = this.getParentsById(data, element.id)
if (tmp != undefined && tmp.length > 0) {
// 最后一级选中,默认展开和选中父级菜单
tmp.forEach(element => {
element.isHidden = false
element.checked = true
})
}
})
this.setData({
menuTree: data
})
},
// 递归 - 根据id获取所有父节点
getParentsById(list, id) {
for (let i in list) {
if (list[i].id == id) {
return [list[i]]
}
if (list[i].children) {
let node = this.getParentsById(list[i].children, id);
if (node !== undefined) {
return node.concat(list[i])
}
}
}
},
// 递归 - 根据id获取当前节点对象
getNodeById(data, id, newNodes = []) {
data.forEach(element => {
// 匹配到节点
if (element.id === id) {
newNodes.push(element)
}
if (element.children) {
this.getNodeById(element.children, id, newNodes)
}
})
return newNodes;
},
// 递归 - 根据id获取所有子节点,(其实就是先获取当前id的节点对象,然后取当前对象,注意这里返回的是数组)
getChildrenById(data, id, newNodes = []) {
var list = data.children
if (list != undefined) {
list.forEach(element => {
newNodes.push(element)
if (element.children) {
this.getChildrenById(element, id, newNodes)
}
})
}
return newNodes;
},
// 递归 - 获取所有选中节点
getDeep(data, newCheckedNodes = []) {
data.forEach(element => {
if (element.checked) {
newCheckedNodes.push(element)
}
if (element.children) {
this.getDeep(element.children, newCheckedNodes)
}
})
return newCheckedNodes
},
// 递归 - 根据节点id获取兄弟所有节点
getBrotherNodesById(list, id) {
// 非顶级节点:获取节点父节点对象里的children
var parentNodes = this.getParentsById(list, id)
if (parentNodes && parentNodes.length >= 2) {
return parentNodes[1].children
}
// 顶级节点:第一级是自己,从原始数组中遍历第一层即可
return list
},
// 根据当前节点id,获取及所有的父级兄弟节点的所有父节点
getParentBrotherAllNodesById(list, id) {
var result = []
// 1、获取当前节点id父节点的父节点
var parentNodes = this.getParentsById(list, id)
// 小于3表示当前父节点是顶级节点
if (parentNodes.length < 3) {
return parentNodes[parentNodes.length - 1]
}
var testNode = parentNodes[2];
// 2、获取父节点的父节点所有兄弟节点
var children = testNode.children
children.forEach(element => {
var parentNodesById = this.getParentsById(list, element.id)
if (parentNodesById.length >= 2) {
// js 数组中添加多个元素 简单的方法 push(...[])
result.push(...(parentNodesById.slice(0, parentNodesById.length - 1)))
}
});
return result;
},
/**
* 点击事件 - 左侧绑定复选框事件
*/
checkboxChangeBindAll(e) {
var index = e.currentTarget.dataset.index;
var index2 = e.currentTarget.dataset.index2;
var list = this.data.menuTree
if (index2 == undefined) {
list[index].bindAll = !list[index].bindAll
}
if (index2 != undefined) {
list[index].children[index2].bindAll = !list[index].children[index2].bindAll
}
console.log(this.data.menuTree);
},
checkboxChange(e) {
// console.log(e);
console.log('checkbox发生change事件,携带value值为:', e.detail.value)
const values = e.detail.value
},
/**
* 点击事件 - 右侧复选框事件
*/
checkboxChangeAll(e) {
var id = e.currentTarget.dataset.id;
var data = this.data.menuTree
var node = this.getNodeById(data, id)
var childrenNodes = this.getChildrenById(node[0], id)
// 1、子节点点选中状态-跟随父节点移动
node[0].checked = !node[0].checked
// 节点下面的所有子节点跟随父节点的选中状态
childrenNodes.forEach(element => {
element.checked = node[0].checked
})
// 2、父节点选中状态,子节点都没选中,父节点默认不选中,子节点有一个选中,父节点也选中
// 获取同级兄弟节点
var bortherNodes = this.getBrotherNodesById(data, id)
// 3、同级都选中
var allChecked = false
bortherNodes.forEach(element => {
if (element.checked) {
allChecked = true
}
})
// 获取节点id所有父节点
var parentNodes = this.getParentsById(data, id)
if (parentNodes.length > 1) {
if (allChecked) {
// 下标index=0的节点是其本身,这里跳过
for (let index = 1; index < parentNodes.length; index++) {
const element = parentNodes[index];
element.checked = true
}
}else{
parentNodes[1].checked =false
}
}
// 4、同级都未选中
if (!allChecked) {
var allNoChecked = false
// 根据当前节点id,获取除去顶级节点的所有的父级兄弟节点的所有父节点
var parentBother = this.getParentBrotherAllNodesById(data, id)
console.log(parentBother);
if (parentBother.length > 1) {
parentBother.forEach(element => {
if (element.checked) {
allNoChecked = true
}
});
}
console.log(allNoChecked);
// console.log(parentBother);
if(!allNoChecked){
parentNodes.forEach(element => {
element.checked=false
});
}
}
this.setData({
menuTree: data
})
// console.log(this.data.menuTree);
},
/**
* 点击事件 - 点击层级显示和折叠事件
*/
openAndHide(e) {
var id = e.currentTarget.dataset.id;
var list = this.data.menuTree
console.log(id);
// 根据 id 获取选中节点的对象
var node = this.getNodeById(list, id)
// 根据 id 获取选中节点下的所有子节点
var res = this.getChildrenById(node[0], id)
// 包含当前id节点本身
res.push(node[0])
// 遍历选中节点(及自己)是否展开
res.forEach(element => {
element.isHidden = !element.isHidden
})
this.setData({
menuTree: list
})
}
})
2、treeMenu2.JSON
{
"usingComponents": {}
}
3、treeMenu2.WXMl
这里其实写的有问题,我主要写java的,前端用的不多,哪个高手指点一下,wxml 文件怎么样能递归呢,
我这里直接最笨的方法,三层for…,
<view class="divcss5-max">
<view class="page-section ">
<view style="padding-bottom:10px">设备选择列表:{{menuTree.length}}</view>
<view class="weui-cells weui-cells_after-title "></view>
<view>
<!-- 第一层 -->
<block class="weui-cell weui-check__label line-center" wx:for="{{menuTree}}" wx:for-index="index"
wx:for-item="item" wx:key="id">
<view class="paddingBottom_10 "></view>
<view class="paddingLeft_10">
<view class="treeMenuCenter">
<checkbox-group bindchange="checkboxChangeAll" data-index="{{index}}" data-id="{{item.id}}" >
<checkbox value="{{item.field}}" checked="{{item.checked}}" />
</checkbox-group>
<!-- 箭头图标 -->
<view class="treeMenuCenter" bindtap="openAndHide" data-id="{{item.id}}" data-index="{{index}}" >
<image wx:if="{{item.isHidden == true}}" class="icon-chioce" src="{{menuTreeImgLeft}}"></image>
<image wx:if="{{item.isHidden == false}}" class="icon-chioce" src="{{menuTreeImgBottom}}"></image>
<text style="color:black"
decode="{{true}}"> {{item.title}} </text>
</view>
<!-- <checkbox-group bindchange="checkboxChangeBindAll" data-index="{{index}}" >
<checkbox checked="{{item.bindAll}}" />绑定单元
</checkbox-group> -->
</view>
</view>
<!-- 第二层 -->
<view class="" hidden="{{item.isHidden}}" wx:for="{{item.children}}" wx:for-index="index2" wx:for-item="item2"
wx:key="id">
<view class="paddingLeft_20 ">
<view class="treeMenuCenter">
<!-- <view class="dashedStyleWidth_40"></view> -->
<checkbox-group bindchange="checkboxChangeAll" data-id="{{item2.id}}" data-index="{{index}}" data-index2="{{index2}}" >
<checkbox value="{{item2.field}}" checked="{{item2.checked}}" />
</checkbox-group>
<!-- 箭头图标 -->
<view class="treeMenuCenter" bindtap="openAndHide" data-id="{{item2.id}}"
data-index="{{index}}" data-index2="{{index2}}" >
<image wx:if="{{item2.isHidden == true}}" class="icon-chioce" src="{{menuTreeImgLeft}}"></image>
<image wx:if="{{item2.isHidden == false}}" class="icon-chioce" src="{{menuTreeImgBottom}}"></image>
<text style="color:#0094aa" decode="{{true}}"> {{item2.title}} </text>
</view>
<!-- <checkbox-group bindchange="checkboxChangeBindAll" data-index="{{index}}" data-index2="{{index2}}" >
<checkbox checked="{{item2.bindAll}}" />绑定楼层
</checkbox-group> -->
</view>
</view>
<!-- 第三层 -->
<block class="" wx:for="{{item2.children}}" wx:for-item="item3" wx:for-index="index3" wx:key="id">
<view class="paddingLeft_30 " hidden="{{item2.isHidden}}">
<!-- <view class="dashedStyleWidth_80"></view> -->
<checkbox-group bindchange="checkboxChangeAll" data-id="{{item3.id}}"
data-index="{{index}}" data-index2="{{index2}}" data-index3="{{index3}}" >
<checkbox value="{{item3.field}}" checked="{{item3.checked}}" />
{{item3.title}}
</checkbox-group>
</view>
</block>
</view>
<view class="paddingBottom_10 "></view>
<view class="weui-cells weui-cells_after-title "></view>
</block>
</view>
</view>
</view>
4、treeMenu2.WXSS
/* pages/biz/treeMenu/treeMenu.wxss */
@import '../../../lib/weui.wxss';
.empty{
padding: 5px 0px;
}
.paddingLeft_10{
padding-left: 10px;
padding-bottom: 5px;
}
.paddingLeft_20{
padding-left: 40px;
padding-bottom: 5px;
}
.paddingLeft_30{
padding-left: 80px;
padding-bottom: 5px;
}
.paddingBottom_10{
padding-top: 10px;
}
.line-center{
display: flex;
align-items: center;
justify-content: center;
flex-flow: column;
}
/**
*参考:https://cloud.tencent.com/developer/article/1690869
*
/
/* 选中后的 背景样式 (红色背景 无边框 可根据UI需求自己修改) */
checkbox .wx-checkbox-input.wx-checkbox-input-checked {
background: #0094aa;
}
/* 选中后的 对勾样式 (白色对勾 可根据UI需求自己修改) */
checkbox .wx-checkbox-input.wx-checkbox-input-checked::before{
color:#fff; /* 对勾颜色 白色 */
}
.treeMenuCenter{
display: flex;
align-items: center;
}
.dashedStyleWidth_80{
margin-left: 10px;
margin-right: 5px;
width: 80px;
height: 0px;
border: 0.5px dotted gray;
}
.dashedStyleWidth_40{
margin-left: 10px;
margin-right: 5px;
width: 40px;
height: 0px;
border: 0.5px dotted gray;
}
.icon-chioce {
height: 15px;
width: 15px;
padding-top: 10px;
padding-bottom: 10px;
background-color: fff;
}
五 、最后
哪个大侠指点一下,wxml如何递归就完美了