实现一个组织人员等的好用的树选择组件,给你想要的

前言

树组件也算是一个常用的数据展现形式了,一般具有层级关系的数据结构,我们想要直观的展现出来,基本都会考虑使用树组件来渲染。

像组织结构管理、库房货架物品管理等都是我们平时常见的。

概述

树结构有它独有的优势,各大组件库也都对它有自己的实现,功能也是相当丰富,能满足绝大部分场景。但是也有一些时候,我们想要这样那样,感觉它不是很尽如人意。

感觉不舒服那就要想办法,自己实现一个树,这样就可以完全自定义了,再结合上自己的一点小想法,做出来的组件用起来就舒服多了。

既然决定了要自己重新做,那么就考虑一下工作内容,实现一个树组件起码得保证它具有一些基本功能,比如:

① 支持层级展开收起功能

② 支持树节点的多选功能

③ 支持树节点的单选功能

④ 支持父节点一键选中或取消所有,以及半勾选状态

⑤ 支持节点禁用

⑥ 支持根据传入的数组参数回显勾选状态

⑦ 支持根据传入的数组默认展开某些节点

⑧ 单击节点触发事件,回传节点信息

⑨ 勾选节点触发事件,回传勾选信息对象

只有保证了这些基本功能,这才能叫做一个树组件,满足我们的基本需求,然后再做一些其他树组件具有的好的功能,以及它们没有的,自己设计的一些小功能点。比如:

① 鼠标上下滚动,自动将当前的树路径吸附在顶部,作为导航

② 点击导航中对应的条目能够跳转到相应位置

③ 导航超长放不下时,类似于文件夹会向左移动,优先展示最深层级

④ 提供关键字搜索功能,可以对树中文字进行检索,无论展开还是折叠状态

⑤ 显示检索之后匹配的各属于当前所处位置

⑥ 回车或者上下跳转按钮可以连续查找

⑦ 支持名称和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'
    }]
  }]
}]

在需要使用它的地方,通过这段代码就可以引入,默认展现的形式如下:

默认是多选树,具有两个小功能区域,竖着靠左上的三个分别为搜索、全部折叠、全部展开按钮,横着靠右上的三个分别为查询、上一个、下一个按钮,树本身也包含了很多功能,下面来逐个看一下。

组件使用

要想让组件具有功能,那么就要能够传入参数,以便达到更多的控制。

  1. organizeData:渲染树需要的数据,必须是一个数组,每一项都是一个对象,包含具有有唯一标识的属性和显示内容的属性,通过指定children数组属性来指定具有的子级。
  2. expandId:需要展开的节点的唯一标识组成的数组。也可以指定为字符串关键字,其中none表示没有展开的节点,也是默认值,all表示展开全部节点,auto表示自动展开包含选中节点的父节点。
  3. checkId:需要默认勾选的节点的唯一标识组成的数组。
  4. disabledId:需要禁用的节点的唯一标识组成的数组。
  5. nodeKey:指定节点的唯一标识,默认为id,也可以指定为数据的其他属性,但要保证唯一。
  6. nodeName:指定节点要展示的内容的属性,默认为name,也可以指定为其他属性。
  7. nodeCode:通过指定该属性,会影响检索的结果,也就是说可以通过nodeCode指定的内容进行检索,默认为code,也可以指定为手机号等字段。
  8. single:标识树是否为单选,默认不传为多选。
  9. 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]层级中,优先展示该层级,然后再展示它的上一层级,以此类推,直到展示不下时,超出部分会被隐藏。

鼠标悬浮相应层级路径上面,文字变色,可以进行点击:

点击之后,就会跳转到相应的层级处:

查找功能

这三个按钮是用来做查找的,点击第一个按钮,会出现一个输入框,输入内容按下回车即可:

我们查找内容包含"三级"的节点,输入框后面会显示出一共查找到多少个匹配的节点,以及当前所在的位置,继续按下回车,会自动导航到下一个匹配位置,所处位置会自动展开:

同样,导航路径也会同步跟着变化,也可以点击后两个按钮,表示导航到上一个和导航到下一个,这里不做演示。

属性配置

  1. 单选

传递属性single即可切换为单选:

ini 复制代码
<organize-select
	:organizeData="treeData"
	single
/>

单选状态将不显示复选框,并且只能选中一个节点:

  1. 纯树

传递属性pure即可切换为纯净状态:

ini 复制代码
<organize-select
	:organizeData="treeData"
	pure
/>

纯净状态将不显示复选框,不显示个数,不显示对勾,可以配合single使用:

  1. 默认展开

传递expandId为一个数组,包含要展开节点的唯一标识即可。

比如我们要展开[二级 3-1]节点,唯一标识为id,它的id为"3-1",

那么就可以这样写:

ini 复制代码
<organize-select
	:organizeData="treeData"
	:expandId="['3-1']"
/>

只需要传递要展开的节点唯一标识,对应的父层级会自动展开:

  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"
/>

效果如下:

默认选中的两个节点的父节点都被默认展开了。

  1. 默认禁用

传递disabledId数组,包含需要勾选的节点唯一标识即可:

ini 复制代码
<organize-select
	:organizeData="treeData"
	:disabledId="['1-1-1', '1-2-1']"
/>

效果如下:

禁用状态下的节点不可被勾选,即使设置了默认选中也不会有勾选状态。

  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'
    }]
  }]
}]
  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: '陆十二',
    }]
  }]
}]

现在就可以通过昵称来检索啦:

  1. style

可以指定--box-width属性来控制面板的宽度:

ini 复制代码
<organize-select
	:organizeData="treeData"
	style="--box-width: 460px"
/>

这样面板中的其他内容也会跟着自动调整:

事件监听

  1. click事件

只要点击了节点就会触发该事件:

typescript 复制代码
// template
<organize-select
	:organizeData="treeData"
	@click="clickNode"
/>
// script
clickNode(node) {
  console.log(node)
}

先看点击叶子节点[三级 2-1-1]的输出信息:

再看点击父级节点[一级 2]的输出信息:

  1. 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事件,这样就可以在用户还没有进行任何操作时获取勾选节点数据。

最后

无论组件最后被设计或者改造成什么样子,首要的目的都是方便用户使用,还可以有更复杂和更丰富的功能可以扩展,希望能够帮助到你。

相关推荐
独立开阀者_FwtCoder2 分钟前
stagewise:让AI与代码编辑器无缝连接
前端·javascript·github
清沫4 分钟前
Cursor Rules 开发实践指南
前端·ai编程·cursor
江城开朗的豌豆9 分钟前
JavaScript篇:对象派 vs 过程派:编程江湖的两种武功心法
前端·javascript·面试
不吃糖葫芦311 分钟前
App使用webview套壳引入h5(二)—— app内访问h5,顶部被手机顶部菜单遮挡问题,保留顶部安全距离
前端·webview
菥菥爱嘻嘻28 分钟前
JS手写代码篇---手写ajax
开发语言·javascript·ajax
江城开朗的豌豆31 分钟前
JavaScript篇:字母侦探:如何快速统计字符串里谁才是'主角'?
前端·javascript·面试
kite01217 小时前
浏览器工作原理06 [#]渲染流程(下):HTML、CSS和JavaScript是如何变成页面的
javascript·css·html
крон7 小时前
【Auto.js例程】华为备忘录导出到其他手机
开发语言·javascript·智能手机
coding随想9 小时前
JavaScript ES6 解构:优雅提取数据的艺术
前端·javascript·es6
年老体衰按不动键盘9 小时前
快速部署和启动Vue3项目
java·javascript·vue