前言背景说明
现在有很多的插件来实现树图的需求。但是对于特定的需求,插件多多少少都有点小问题没法满足。
于是我查看了一些插件,并熟悉了其里面的原理,就准备着手自己封装一个树图组件
原理说明
其实树图组件很好理解,就是通过递归自身调用自身来实现树图。再说明白一点,最好在头脑中有一个画面,更加便于理解和开发。
1、就是我们先定义一个组件,在这个组件里面把第一个节点画出来
2、然后再这第一个节点下面进行递归调用自身组件。这样就可以实现像糖葫芦一样一串节点
3、但这只是一串,并不是树,所以还需要在这个基础上,画节点的地方加上循环的画节点。这样就成了树了。
总的来说先易后难,先把主干理解清楚,然后慢慢的往这个树干上面加树枝,这样一棵大树就画好了
效果图展示
详细设计步骤
(我个人不太喜欢直接贴代码上来,所以我就尽量用文字和简要代码来写清楚整个的设计流程)
1、数据的格式
[{
label: '显示文字',
pictype: '图标类型',
children: [
{
label: '显示文字',
pictype: '图标类型',
children: [
{
label: '显示文字',
pictype: '图标类型',
},
{
label: '显示文字',
pictype: '图标类型',
},
]
},
{
label: '显示文字',
pictype: '图标类型',
children: [
{
label: '显示文字',
pictype: '图标类型',
}
]
},
]
}]
2、数据格式解析
这是获取的数据格式,一般由后端返回给前端,但是这样直接传给组件是不够的,因为需要考虑到节点展开的高度,我封装的时候,为了让组件内部更加通俗易懂,就把高度这个属性在传进组件前就添加进去。计算格式如下:
1、遍历数组,对于节点对象中的children属性的"所有叶子节点"数有多少来乘以节点的高度
2、假设节点高度为70,以上图的数据格式为例,一个根节点对象中的children有两个节点对象,第一个子节点对象有2个叶子节点,第二个根节点对象有1个叶子节点,所以根节点的高度就是703,第一个子节点的高度就是702,第二个子节点的高度就是701,所有叶子结点的高度也是701
3、函数补充
// 计算高度,并添加height属性, nodeInfo是根节点
traveTree(nodeInfo) {
let childrenInfo = nodeInfo.children;
if (!childrenInfo || childrenInfo.length == 0) {
nodeInfo.height = 77;
} else {
// 循环叶子节点,给每个叶子节点都赋值高度
childrenInfo.map((item) => {
this.traveTree(item);
});
// 每个节点的高度都由子节点的高度相加
nodeInfo.height = childrenInfo.reduce((preV, n) => {
return preV + n.height;
}, 0);
}
return nodeInfo;
}
3、组件内部代码
<div v-for="item in data"> // 这是节点的循环,下面就是一个节点的内部组成
// 这是前面的竖线,竖线的高度由通过节点的高度和以及data的长度和data的index
<span class="v-line" :style="computerHeight(item.height,data.length,index)"> </span>
// 这是前面的横线
<span class="v-line" ></span>
// 这是节点,包括图标、文字、和展开收缩图标
<div>
<img :src="#"/> // 图标
// 展开收缩按钮图标 在这里增加一个属性,用来控制当前节点的递归调用,这样就能达到展开和收缩的效果了
<span @click="toggleChildren(item)">
<i class="open"> </i>
<i class="close"> </i>
</span>
// 显示文字
<span>{{itemlabel}}</span>
</div>
// 这是后面的横线
<span class="b-line"></span>
// 这是递归调用 ,当children为空或者为图标为收缩的时候都不展示
<tree-node data="data.children" v-if="item.children != 0 && item.expend"></tree-node>
</div>
4、上面的函数补充(计算高度的函数)
computerHeight(item.height,data.length,index) {
// 当前节点是唯一的一个子节点或者是叶子节点,前面竖线为0
if (length == 1 || length == 0) {
return {
height: '0px',
display: 'none'
};
} else {
// 当前节点是多个子节点之一时,通过当前节点的index,进行判断前面竖线的高度
let height = 0;
let marginTop = 0;
let marginB = 0;
// 当是第一个子节点时,去除一半的高度,并往下调一半的边距
if (index == 0) {
height = pheight / 2;
marginTop = height;
return {
height: height + 'px',
'margin-top': marginTop + 'px'
};
}
// 当是最后一个子节点时,去除一半的高度,并往上调一半的边距
if (index == length - 1) {
height = pheight / 2;
marginB = height;
return {
height: height + 'px',
'margin-bottom': marginB + 'px'
};
} else {
height = pheight;
return {
height: height + 'px'
};
}
}
}
扩展
1、在需求中必然要加入的就是通过点击图像需要将节点信息层层回调出来。
2、在其中我并没有将根节点的和叶子结点的特殊地方标明,在这里补充一下,根节点是不需要节点前面的竖线和横线的,叶子结点是不需要后面的横线的,只需要在其中加一些判断就行了
3、展开和收缩图标那里在叶子节点是不需要展示的。展开收缩按钮的函数,主要就是进行图标的切换和新增一个节点控制属性将控制展开和收缩的字段值赋值给这个节点控制属性,用来进行递归组件处的显隐判断。这就可以达到当前节点的展开和收缩的效果了
内容有点多,代码没有贴全,因为有些我觉得不需要展示出来,码字不容易,敬请指正!