基于ivew的Tree 树形控件实现一个带搜索的红点报警,点击报警节点颜色变淡的记忆功能

需求:客户进入页面后,左侧节点树如有告警节点,则在节点名字后添加一个红色的圈,客户点击查看后,红色转为淡红,支持搜索。我这边节点树只有三级,根据自身情况变通,多级的话可以用递归写。

效果如下:

 

代码:

相关参数:

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;
                                    }
                                }
                            }
                        }
                    }
                }
            },

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值