前言
树组件也算是一个常用的数据展现形式了,一般具有层级关系的数据结构,我们想要直观的展现出来,基本都会考虑使用树组件来渲染。
像组织结构管理、库房货架物品管理等都是我们平时常见的。
概述
树结构有它独有的优势,各大组件库也都对它有自己的实现,功能也是相当丰富,能满足绝大部分场景。但是也有一些时候,我们想要这样那样,感觉它不是很尽如人意。
感觉不舒服那就要想办法,自己实现一个树,这样就可以完全自定义了,再结合上自己的一点小想法,做出来的组件用起来就舒服多了。
既然决定了要自己重新做,那么就考虑一下工作内容,实现一个树组件起码得保证它具有一些基本功能,比如:
① 支持层级展开收起功能
② 支持树节点的多选功能
③ 支持树节点的单选功能
④ 支持父节点一键选中或取消所有,以及半勾选状态
⑤ 支持节点禁用
⑥ 支持根据传入的数组参数回显勾选状态
⑦ 支持根据传入的数组默认展开某些节点
⑧ 单击节点触发事件,回传节点信息
⑨ 勾选节点触发事件,回传勾选信息对象
只有保证了这些基本功能,这才能叫做一个树组件,满足我们的基本需求,然后再做一些其他树组件具有的好的功能,以及它们没有的,自己设计的一些小功能点。比如:
① 鼠标上下滚动,自动将当前的树路径吸附在顶部,作为导航
② 点击导航中对应的条目能够跳转到相应位置
③ 导航超长放不下时,类似于文件夹会向左移动,优先展示最深层级
④ 提供关键字搜索功能,可以对树中文字进行检索,无论展开还是折叠状态
⑤ 显示检索之后匹配的各属于当前所处位置
⑥ 回车或者上下跳转按钮可以连续查找
⑦ 支持名称和code搜索,跳转新面板,查询之后平铺展示,显示所在层级等信息,能够直接勾选
⑧ 提供一键折叠和一键展开功能
⑨ 选中之后所有所在层级都会显示相应的选中个数
当然了,还有更多的功能可以扩展,想要就能做。
组件引入
说了这么多,一起来看一下如何使用:
我们新建一个"organize-select"组件,用来承载整个选择功能区域的面板,由于树的渲染是一个递归的过程,因此我们还要再剥离出一个组件,纯粹用来构造树和与它的交互。
ini
<organize-select
:organizeData="treeData"
/>
treeData表示构造树的数据,我们仿造一个:
css
[{ id: '1', name: '一级 1', children: [{ id: '1-1', name: '二级 1-1', children: [{ id: '1-1-1', name: '三级 1-1-1' }]
},{
id: '1-2',
name: '二级 1-2',
children: [{
id: '1-2-1',
name: '三级 1-2-1'
},{
id: '1-2-2',
name: '三级 1-2-2',
children: [{
id: '1-2-2-1',
name: '四级 1-2-2-1',
children: [{
id: '1-2-2-1-1',
name: '五级 1-2-2-1-1',
children: [{
id: '1-2-2-1-1-1',
name: '六级 1-2-2-1-1-1',
children: [{
id: '1-2-2-1-1-1-1',
name: '七级 1-2-2-1-1-1-1'
},{
id: '1-2-2-1-1-1-2',
name: '七级 1-2-2-1-1-1-2'
},{
id: '1-2-2-1-1-1-3',
name: '七级 1-2-2-1-1-1-3'
}]
},{
id: '1-2-2-1-1-2',
name: '六级 1-2-2-1-1-2'
},{
id: '1-2-2-1-1-3',
name: '六级 1-2-2-1-1-3'
}]
},{
id: '1-2-2-1-2',
name: '五级 1-2-2-1-2'
},{
id: '1-2-2-1-3',
name: '五级 1-2-2-1-3'
}]
},{
id: '1-2-2-2',
name: '四级 1-2-2-2'
},{
id: '1-2-2-3',
name: '四级 1-2-2-3'
}]
},{
id: '1-2-3',
name: '三级 1-2-3'
}]
}]
}, {
id: '2',
name: '一级 2',
children: [{
id: '2-1',
name: '二级 2-1',
children: [{
id: '2-1-1',
name: '三级 2-1-1'
}]
}, {
id: '2-2',
name: '二级 2-2',
children: [{
id: '2-2-1',
name: '三级 2-2-1'
}]
}]
}, {
id: '3',
name: '一级 3',
children: [{
id: '3-1',
name: '二级 3-1',
children: [{
id: '3-1-1',
name: '三级 3-1-1'
}]
}, {
id: '3-2',
name: '二级 3-2',
children: [{
id: '3-2-1',
name: '三级 3-2-1'
}]
}]
}, {
id: '4',
name: '一级 4',
children: [{
id: '4-1',
name: '二级 4-1',
children: [{
id: '3-1-1',
name: '三级 4-1-1'
}]
}, {
id: '4-2',
name: '二级 4-2',
children: [{
id: '4-2-1',
name: '三级 4-2-1'
}]
}]
}, {
id: '5',
name: '一级 5',
children: [{
id: '5-1',
name: '二级 5-1',
children: [{
id: '5-1-1',
name: '三级 5-1-1'
}]
}, {
id: '5-2',
name: '二级 5-2',
children: [{
id: '5-2-1',
name: '三级 5-2-1'
}]
}]
}, {
id: '6',
name: '一级 6',
children: [{
id: '6-1',
name: '二级 6-1',
children: [{
id: '6-1-1',
name: '三级 6-1-1'
}]
}, {
id: '6-2',
name: '二级 6-2',
children: [{
id: '6-2-1',
name: '三级 6-2-1'
}]
}]
}, {
id: '7',
name: '一级 7',
children: [{
id: '7-1',
name: '二级 7-1',
children: [{
id: '7-1-1',
name: '三级 7-1-1'
}]
}, {
id: '7-2',
name: '二级 7-2',
children: [{
id: '7-2-1',
name: '三级 7-2-1'
}]
}]
}, {
id: '8',
name: '一级 8',
children: [{
id: '8-1',
name: '二级 8-1',
children: [{
id: '8-1-1',
name: '三级 8-1-1'
}]
}, {
id: '8-2',
name: '二级 8-2',
children: [{
id: '8-2-1',
name: '三级 8-2-1'
}]
}]
}, {
id: '9',
name: '一级 9',
children: [{
id: '9-1',
name: '二级 9-1',
children: [{
id: '9-1-1',
name: '三级 9-1-1'
}]
}, {
id: '9-2',
name: '二级 9-2',
children: [{
id: '9-2-1',
name: '三级 9-2-1'
}]
}]
}, {
id: '10',
name: '一级 10',
children: [{
id: '10-1',
name: '二级 10-1',
children: [{
id: '10-1-1',
name: '三级 10-1-1'
}]
}, {
id: '10-2',
name: '二级 10-2',
children: [{
id: '10-2-1',
name: '三级 10-2-1'
}]
}]
}]
在需要使用它的地方,通过这段代码就可以引入,默认展现的形式如下:
默认是多选树,具有两个小功能区域,竖着靠左上的三个分别为搜索、全部折叠、全部展开按钮,横着靠右上的三个分别为查询、上一个、下一个按钮,树本身也包含了很多功能,下面来逐个看一下。
组件使用
要想让组件具有功能,那么就要能够传入参数,以便达到更多的控制。
- organizeData:渲染树需要的数据,必须是一个数组,每一项都是一个对象,包含具有有唯一标识的属性和显示内容的属性,通过指定children数组属性来指定具有的子级。
- expandId:需要展开的节点的唯一标识组成的数组。也可以指定为字符串关键字,其中none表示没有展开的节点,也是默认值,all表示展开全部节点,auto表示自动展开包含选中节点的父节点。
- checkId:需要默认勾选的节点的唯一标识组成的数组。
- disabledId:需要禁用的节点的唯一标识组成的数组。
- nodeKey:指定节点的唯一标识,默认为id,也可以指定为数据的其他属性,但要保证唯一。
- nodeName:指定节点要展示的内容的属性,默认为name,也可以指定为其他属性。
- nodeCode:通过指定该属性,会影响检索的结果,也就是说可以通过nodeCode指定的内容进行检索,默认为code,也可以指定为手机号等字段。
- single:标识树是否为单选,默认不传为多选。
- pure:表示不需要勾选状态,只是一个单纯的树展示。
组件也暴露了两个事件,分别为change和click。当点击节点时,触发click事件,函数的参数为当前的数据对象,当选中的节点发生改变时,触发change事件,函数的参数为一个对象,其中包含三个属性,keys表示选中的节点的唯一标识的数组,names表示选中的节点的内容的数组,nodes表示选中的节点的数据对象的数组。
基本操作
树分为父级节点与叶子节点,每一个父节点一定有子级节点,没有子节点的叫做叶子节点,根节点的父节点为null,单击父级节点可以展开或收起当前层级,单击叶子节点可以改变选中状态,父级节点具有复选框,可以一键选中或取消选中所有子级节点,父级节点会显示所有子级节点被勾选的个数。
勾选叶子节点
展开某个层级,选中其中一个叶子节点:
单击节点[三级 1-1-1],该节点变为选中状态,文字颜色改变,后面出现对勾。
父节点[二级 1-1]也变为选中状态,并显示该节点的子级节点一共被选中了一个。
根节点[一级 1]变为半选中状态,因为其有部分子级没有被选中,并且数字显示为1。
勾选父级节点
我们再继续勾选节点[二级 1-2],选中后面的复选框即可:
可以看到,节点[二级 1-2]下的所有子级节点被全部选中,并显示了一共被选中11个,同时根节点[一级 1]的数字变为了12。
搜索按钮
鼠标悬浮在图标上面会显示相应的提示信息,点击之后会跳转到新面板:
输入检索的内容,点击查询或者按下回车即可:
前半部分显示检索匹配到的内容,后半部分显示该内容所在的层级路径。
单击可以选中,呈现选中状态:
点击返回之后我们展开相应的层级,看下效果:
也是同步被勾选的,同样,如果在树上勾选了某些节点,搜索的结果中包含其中的节点时,那么也是会同步显示勾选状态的,两边都是状态同步的,这里不做演示。
全部折叠
点击之后,所有树节点将处于收起状态,可用于快速回到根层级:
全部展开
点击之后,所有树节点将处于展开状态,对于一些小树来说有利于快速浏览内容:
路径导航
当对树进行滚动查看时,为了方便操作,会将当前所处的路径以导航的形式吸附在滚动面板顶部:
比如,目前处于[一级 1]节点下的[二级 1-2]节点下的子节点区域,那么该路径将会在顶部显示,如果展开[三级 1-2-2],然后继续滚动,路径也会自动变化:
如果展开更多层级,处在深层级当中,导致顶部显示区域放不下时,将会自动优先展示深层级,类似于文件夹路径的效果:
当前处于[六级 1-2-2-1-1-1]层级中,优先展示该层级,然后再展示它的上一层级,以此类推,直到展示不下时,超出部分会被隐藏。
鼠标悬浮相应层级路径上面,文字变色,可以进行点击:
点击之后,就会跳转到相应的层级处:
查找功能
这三个按钮是用来做查找的,点击第一个按钮,会出现一个输入框,输入内容按下回车即可:
我们查找内容包含"三级"的节点,输入框后面会显示出一共查找到多少个匹配的节点,以及当前所在的位置,继续按下回车,会自动导航到下一个匹配位置,所处位置会自动展开:
同样,导航路径也会同步跟着变化,也可以点击后两个按钮,表示导航到上一个和导航到下一个,这里不做演示。
属性配置
- 单选
传递属性single即可切换为单选:
ini
<organize-select
:organizeData="treeData"
single
/>
单选状态将不显示复选框,并且只能选中一个节点:
- 纯树
传递属性pure即可切换为纯净状态:
ini
<organize-select
:organizeData="treeData"
pure
/>
纯净状态将不显示复选框,不显示个数,不显示对勾,可以配合single使用:
- 默认展开
传递expandId为一个数组,包含要展开节点的唯一标识即可。
比如我们要展开[二级 3-1]节点,唯一标识为id,它的id为"3-1",
那么就可以这样写:
ini
<organize-select
:organizeData="treeData"
:expandId="['3-1']"
/>
只需要传递要展开的节点唯一标识,对应的父层级会自动展开:
- 默认勾选
传递checkId数组,包含需要勾选的节点唯一标识即可:
ini
<organize-select
:organizeData="treeData"
:checkId="['1-1-1', '2-1-1']"
/>
效果如下:
此时可以结合expandId来默认展开被选中的节点的父节点:
ini
<organize-select
:organizeData="treeData"
:checkId="['1-1-1', '2-1-1']"
expandId="auto"
/>
效果如下:
默认选中的两个节点的父节点都被默认展开了。
- 默认禁用
传递disabledId数组,包含需要勾选的节点唯一标识即可:
ini
<organize-select
:organizeData="treeData"
:disabledId="['1-1-1', '1-2-1']"
/>
效果如下:
禁用状态下的节点不可被勾选,即使设置了默认选中也不会有勾选状态。
- nodeKey、nodeName
可以指定映射的属性,比如:
ini
<organize-select
:organizeData="treeData"
nodeKey="userId"
nodeName="userName"
/>
然后数据结构就可以变成这样:
css
[{ userId: '1', userName: '一级 1', children: [{ userId: '1-1', userName: '二级 1-1', children: [{ userId: '1-1-1', userName: '三级 1-1-1' }]
},{
userId: '1-2',
userName: '二级 1-2',
children: [{
userId: '1-2-1',
userName: '三级 1-2-1'
}]
}]
}]
- nodeCode
可以指定为任意字段名,会在检索时用到,比如希望能够通过昵称来检索,那么可以额外增加一个字段nickName,指定为这个属性即可:
ini
<organize-select
:organizeData="treeData"
nodeCode="nickName"
/>
然后在数据中加上这个字段:
css
[{ id: '1', name: '一级 1', nickName: '张三', children: [{ id: '1-1', name: '二级 1-1', nickName: '李四', children: [{ id: '1-1-1', name: '三级 1-1-1', nickName: '王五', }]
},{
id: '1-2',
name: '二级 1-2',
nickName: '赵六',
children: [{
id: '1-2-1',
name: '三级 1-2-1',
nickName: '刘七',
},{
id: '1-2-2',
name: '三级 1-2-2',
nickName: '魏八',
children: [{
id: '1-2-2-1',
name: '四级 1-2-2-1',
nickName: '沈九',
},{
id: '1-2-2-2',
name: '四级 1-2-2-2',
nickName: '黄十',
},{
id: '1-2-2-3',
name: '四级 1-2-2-3',
nickName: '房十一',
}]
},{
id: '1-2-3',
name: '三级 1-2-3',
nickName: '陆十二',
}]
}]
}]
现在就可以通过昵称来检索啦:
- style
可以指定--box-width属性来控制面板的宽度:
ini
<organize-select
:organizeData="treeData"
style="--box-width: 460px"
/>
这样面板中的其他内容也会跟着自动调整:
事件监听
- click事件
只要点击了节点就会触发该事件:
typescript
// template
<organize-select
:organizeData="treeData"
@click="clickNode"
/>
// script
clickNode(node) {
console.log(node)
}
先看点击叶子节点[三级 2-1-1]的输出信息:
再看点击父级节点[一级 2]的输出信息:
- change事件
只要改变树上节点的勾选状态,就会触发该事件:
typescript
// template
<organize-select
:organizeData="treeData"
@change="changeCheck"
/>
// script
changeCheck(obj) {
console.log(obj)
}
随便勾选几个节点,看下输出:
包含三对属性,可以用来继续做一些其他操作,比如回显勾选的节点内容:
组件方法
我们可以调用组件的getChecks方法来获取当前选中的节点数据,主要是用来在初始化默认勾选时使用,能够获取到勾选的数据:
ini
<organize-select
ref="organizeSelectRef"
:organizeData="treeData"
:expandId="['1-1-1', '2-1-1', '2-2-1']"
/>
<div style="padding: 10px;">
<div>共选择:{{ selectNums }}个</div>
<div>{{ selectNames }}</div>
</div>
假设我们默认选中了三个节点,调用组件的getChecks方法之后,会自动触发一次change事件,这样就可以在用户还没有进行任何操作时获取勾选节点数据。
最后
无论组件最后被设计或者改造成什么样子,首要的目的都是方便用户使用,还可以有更复杂和更丰富的功能可以扩展,希望能够帮助到你。