需求:客户进入页面后,左侧节点树如有告警节点,则在节点名字后添加一个红色的圈,客户点击查看后,红色转为淡红,支持搜索。我这边节点树只有三级,根据自身情况变通,多级的话可以用递归写。
效果如下:
代码:
相关参数:
oldSelected: [],//上次选中的节点的定位
页面代码:
<section class="left-content">
<div style="margin-top: 12px">
<Select
placeholder="请输入节点名称"
prefix="ios-search"
style="width: 90%;margin-left: 5%"
@on-change="doAfterSearch"
filterable
remote
:remote-method="search">
<Option v-for="option in selectedList" :value="option.position.join(',')" :key="option.areaCode">
{{option.name}}
</Option>
</Select>
</div>
<div id="treeAreaId" class="tree-area">
<Tree :data="treeList" @on-select-change="selectChange">
</Tree>
</div>
</section>
上面是包含一个搜索和tree结构,搜索时将树形数据解析成一个大数组,调用search进行过滤,达到响应效果,搜索时如果节点过多,则会定位到该节点将自动拉动滚动条。
初始化节点:
mounted() {
getRedTree().then(res => {
for (let i = 0; i < res.length; i++) {
res[i]["expand"] = false;
res[i]["selected"] = false;
res[i]["position"] = [i];
res[i]["title"] = res[i].name;
if (res[i].alertLevel > 1) {
res[i]["render"] = this.render;
}
//这里不可直接push(res[i]),因为二级,三级会因为复制数据的引用的问题,不会响应了,所有需要深拷贝
this.treeListForSelect.push(this.deepCopy(res[i]));
if (res[i].children.length === 0) {
} else {
for (let j = 0; j < res[i].children.length; j++) {
res[i].children[j]["expand"] = false;
res[i].children[j]["selected"] = false;
res[i].children[j]["title"] = res[i].children[j]["name"];
res[i].children[j]["position"] = [i, j];
if (res[i].children[j].alertLevel > 1) {
res[i].children[j]["render"] = this.render;
}
this.treeListForSelect.push(this.deepCopy(res[i].children[j]));
if (res[i].children[j].children.length === 0) {
} else {
for (let k = 0; k < res[i].children[j].children.length; k++) {
res[i].children[j].children[k]["expand"] = false;
res[i].children[j].children[k]["selected"] = false;
res[i].children[j].children[k]["title"] = res[i].children[j].children[k]["name"];
if (res[i].children[j].children[k].alertLevel > 1) {
res[i].children[j].children[k]["render"] = this.render;
}
//方便后面定位
res[i].children[j].children[k]["position"] = [i, j, k];
this.treeListForSelect.push(this.deepCopy(res[i].children[j].children[k]));
}
}
}
}
}
this.treeList = res;
}).catch();
},
在获取到节点数据的时候,需要添加一些属性,方便后面操作。
expand:节点展开折叠
selected:节点书否选中
title:节点名字,这里跟后端数据的name转换一下
position:节点定位,每个节点都有自己的position数组,如[1,5,2],表示该节点在treeData[1][5][2]位置,每次遍历加一个,之后的操作会变得很方便
render:则是官网提供的自定义节点内容,官网描述:使用强大的 Render 函数可以自定义节点显示内容和交互,比如添加图标,按钮等。Render 函数的第二个参数,包含三个字段:
- root <Array>:树的根节点
- node <Object>:当前节点
- data <Object>:当前节点的数据
通过合理地使用 root、node 和 data 可以实现各种效果,其中,iView 给每个节点都设置了一个 nodeKey
字段,用来标识节点的 id。Render 函数分两种,一种是给当前树的每个节点都设置同样的渲染内容,此 render 通过 Tree 组件的属性 render
传递;另一种是单独给某个节点设置,在该节点的 render
字段内设置;同时设置时,会优先使用当前节点的 Render 函数。
这个函数自定义的写法初次接触可能会很不习惯,我也是实验,各种试错才明白了一些,还是要保持耐心吧。还有这个函数千万不要全部都设置,哪些需要特殊处理的节点就在这些节点上添加render(),如果都有render函数的话会出现一个大坑,每次点击节点都会重新渲染整个节点树,如果节点树数据多,每次打开折叠都很慢,我就因为这个测试测的时候打开一个节点几秒钟。官网的例子我测过,都是这样的。
这里贴一下render函数:
render: (h, {root, node, data}) => {
return h('span', {
style: {
display: 'inline-block',
cursor: "pointer"
},
on: {
click: () => {
this.resetHighlight(data);//红点显示逻辑
this.nodeClick(data);//右侧业务逻辑
}
}
}, [
h('span', [
h("span", {
//这里市selectedd的节点回带上ivew的默认样式,非自定义的节点也是这个样式
class: data.selected ? "ivu-tree-title-selected" : ""
}, [
h("span", data.name)//节点名字
]),
(h('span', { //红点,以及根据后端数据显示红色还是淡红
style: {
width: "8px",
height: "8px",
borderRadius: '4px',
background: data.alertLevel > 1 ? '#FF1818' : "#FF9696",
display: "inline-block",
marginLeft: "10px"
}
}))
]),
]
);
}
下面是一些用到的方法
tree组件的点击回调@on-select-change="selectChange"
selectChange(node) {
//当取消点击的时候,节点的selected会变为false,传回来的是空[],保持节点为选中
if (node.length === 0) {
switch (this.oldSelected.length) {
case 0: {
break;
}
case 1: {
this.treeList[this.oldSelected[0]].selected = true;
break;
}
case 2: {
this.treeList[this.oldSelected[0]].children[this.oldSelected[1]].selected = true;
break;
}
case 3: {
this.treeList[this.oldSelected[0]].children[this.oldSelected[1]].children[this.oldSelected[2]].selected = true;
}
}
return;
}
this.resetHighlight(node[0]);
this.nodeClick(node[0]);
},
doAfterSearch(val) {
let list = val.split(",");
this.showRight = false;
switch (list.length) {
case 1: {
this.treeList[list[0]].expand = true;
this.resetHighlight(this.treeList[list[0]]);
this.clearRightData();
break;
}
case 2: {
this.treeList[list[0]].expand = true;
this.treeList[list[0]].children[list[1]].expand = true;
this.resetHighlight(this.treeList[list[0]].children[list[1]]);
this.clearRightData();
break;
}
case 3: {
this.treeList[list[0]].expand = true;
this.treeList[list[0]].children[list[1]].expand = true;
this.resetHighlight(this.treeList[list[0]].children[list[1]].children[list[2]]);
//展示第一个节点
flowNodeMonitorDetail({areaCode: this.treeList[list[0]].children[list[1]].children[list[2]].areaCode}).then(res => {
this.showRight = true;
for (let i = 0; i < res.nodeDetails.length; i++) {
res.nodeDetails["select"] = false;
}
this.nodeDetailList = res.nodeDetails;
this.oldSelectIndex = 0;
this.showTableData(this.nodeDetailList[0], 0);
}).catch();
if (this.treeList[list[0]].children[list[1]].children[list[2]].alertLevel > 1) {
this.treeList[list[0]].children[list[1]].children[list[2]].alertLevel = 1;
this.cleanParentNode(this.treeList[list[0]].children[list[1]].children[list[2]].position);
}
}
}
setTimeout(() => {
//搜索定位,基本保持在中央显示,不定时的话第一次可能搜索dom可能没生成好
let doc = document.getElementsByClassName("ivu-tree-title-selected");
if (doc.length > 0) {
document.getElementById("treeAreaId").scrollTop = (doc[0].offsetTop - (document.body.clientHeight - 84) / 2);
}
}, 200);
},
search(query) {
if (query !== '') {
this.selectedList = [];
for (let i = 0; i < this.treeListForSelect.length; i++) {
if (this.treeListForSelect[i].name.indexOf(query) !== -1) {
this.selectedList.push(this.treeListForSelect[i]);
}
}
}
},
resetHighlight(data) {
switch (this.oldSelected.length) {
case 0: {
break;
}
case 1: {
this.treeList[this.oldSelected[0]].selected = false;
break;
}
case 2: {
this.treeList[this.oldSelected[0]].children[this.oldSelected[1]].selected = false;
break;
}
case 3: {
this.treeList[this.oldSelected[0]].children[this.oldSelected[1]].children[this.oldSelected[2]].selected = false;
}
}
this.oldSelected = data.position;
data.selected = true;
},
showTableData(data, index) {
this.nodeDetailList[this.oldSelectIndex].select = false;
this.oldSelectIndex = index;
this.nodeDetailList[index].select = true;
flowNodePeriodDetail({areaCode: data.areaCode}).then(res => {
this.timeData = res.period.reverse();
}).catch();
//设备信息
flowDeviceInfoList({nodeCode: data.areaCode}).then(res => {
this.deviceList = res;
}).catch();
},
nodeClick(data) {
if (data.areaType === 300) {
this.showRight = true;
//循环所有节点,判断父节点需不需要隐藏
//显示右侧节点数据
flowNodeMonitorDetail({areaCode: data.areaCode}).then(res => {
for (let i = 0; i < res.nodeDetails.length; i++) {
res.nodeDetails["select"] = false;
}
this.nodeDetailList = res.nodeDetails;
this.oldSelectIndex = 0;
this.showTableData(this.nodeDetailList[0], 0);
}).catch();
this.cleanParentNode(data.position);
if (data.alertLevel > 1) {
data.alertLevel = 1;
this.cleanParentNode(data.position);
}
data.alertLevel = 1;
} else {
this.showRight = false;
this.clearRightData();
data.expand = true;
}
},
cleanParentNode(position) {
//这里代码比较多,实际逻辑并不复杂,也可以用递归写。只要就是先循环第二级判断是否显示红点,如果取消红点则相同规则判断第一级
for (let i = 0; i < this.treeList[position[0]].children[position[1]].children.length; i++) {
if (this.treeList[position[0]].children[position[1]].children.length === 1) {
this.treeList[position[0]].children[position[1]].alertLevel = 1;
//继续判断根节点是否需要去红点
for (let j = 0; j < this.treeList[position[0]].children.length; j++) {
if (this.treeList[position[0]].children.length === 1) {
this.treeList[position[0]].alertLevel = 1;
break;
} else {
if (this.treeList[position[0]].children[j].alertLevel > 1) {
break;
}
if (j === this.treeList[position[0]].children.length - 1) {
this.treeList[position[0]].alertLevel = 1;
}
}
}
break;
} else {
if (this.treeList[position[0]].children[position[1]].children[i].alertLevel > 1) {
break;
}
if (i === this.treeList[position[0]].children[position[1]].children.length - 1) {
this.treeList[position[0]].children[position[1]].alertLevel = 1;
//继续判断根节点是否需要去红点
for (let j = 0; j < this.treeList[position[0]].children.length; j++) {
if (this.treeList[position[0]].children.length === 1) {
this.treeList[position[0]].alertLevel = 1;
break;
} else {
if (this.treeList[position[0]].children[j].alertLevel > 1) {
break;
}
if (j === this.treeList[position[0]].children.length - 1) {
this.treeList[position[0]].alertLevel = 1;
}
}
}
}
}
}
},