实现一个可无限折叠的table

前言

如何在table上实现一个可折叠展开子节点的table?先看下最终实现效果图:javascript

其实这个项目在两个月之前就以上上传在 github了,但当时没有写详细的实现过程。本身前几天发表的一篇技术贴 当下拉列表数据过大时,该如何应对?获得你们的很多支持,犹如歌曲《红日》里的歌词 像红日之火 点燃真的我~ 因此继续为掘金社区作奉献本身微不足道的力量啦 ~~

在周末听着嗨歌写文仍是很nice的~ 嗨起来 ~ ~css

技术栈vue

  • vue
  • javascript

动手实现

为有兴趣的同窗先准备一下大纲目录预览,为了避免让同窗们看入迷,让大纲为你指明前行的道路java

大纲预览

  1. 明确需求
  2. 树形结构数据准备
  3. 数据扁平化(重点)
  4. 层级展现
  5. 折叠展开功能实现

1. 明确需求

实现一个可折叠展开的table,看上去很迷茫的样子。但实际上就是:
  1. table上点击时对其子集进行隐藏显示
  2. 经过缩进的距离来表现层级关系

在代码里很东西其实都是假装出来的,例如咱们要实现的这个可无限折叠的table。但在用户操做的时候看来就是那么回事咯 ~ ~git

2. 树形结构数据准备

这里已经准备好了树形结构的数据,存放于data.js的文件中,节点经过Children链接。如标题所说,可无限折叠,不管这里的树形数据有多少层级,都能给它办稳当了。先把牛吹在这,接下来看看如何实现。github

3. 数据扁平化

条件梳理

树形结构数据扁平化并不难,难点在于在扁平化的时候要作的处理。这里深刻梳理一下:数组

  1. 咱们将一个树节点的全部父节点称为family,比如咱们要知道的本身的大领导、二领导、直系领导...由于他们的命令我们都得执行。在这个表单中就是当父节点或更高的祖辈节点发命令时,作一个懂事的子节点固然要听话办事。__family字段就是用来存放全部全部的父节点的数组。在扁平化数据是经过数组字段__family收集其全部父节点。这样一来,就像族谱同样,就能清晰知道该节点的全部父节点。至于如和折叠收缩呢?咱们经过一个foldList数组来记录要折叠的节点,也称为死亡名单。如此一来,只要包含父节点标识节点就乖乖的隐藏吧。
  2. 每一个成员都因该是惟一的,那么咱们在遍历时都应该为每一个成员添加一个惟一标识__identity。正是上一个步骤中说的节点标志
  3. 为了明确知道各个节点之间的层级关系,经过__level明确层级关系。那么咱们规定__level的值越小等级越高。__level=0表明首层节点。
  4. 既然是无限层级,确定是用递归来实现了。formatConversion()方法实现递归。那什么时候跳出当前遍历的递归呢?当前节点没有子集时Children.length = 0的时候跳出本次递归,进行下一次递归。该方法可能须要花点时间理解,代码里注释已写的比较详细,有问题欢迎留言沟通~~

数据扁平化

/*********************************
      ** Fn: formatConversion
      ** Intro: 将树形接口数据扁平化
      ** @params: parent 为当前累计的数组  也是最后返回的数组
      ** @params: children 为当前节点仍需继续扁平子节点的数据
      ** @params: index 默认等于0, 用于在递归中进行累计叠加 用于层级标识
      ** @params: family 装有当前包含元素自身的全部父级 身份标识
      ** @params: elderIdentity 父级的  惟一身份标识
      ** Author: zyx
    *********************************/
    formatConversion (parent, children, index = 0, family = [], elderIdentity = 'x') {
      // children若是长度等于0,则表明已经到了最低层
      // let page = (this.startPage - 1) * 10
      if (children.length > 0) {
        children.map((x, i) => {
          // 设置 __level 标志位 用于展现区分层级
          Vue.set(x, '__level', index)
          // 设置 __family 为家族关系 为全部父级,包含自己在内
          Vue.set(x, '__family', [...family, elderIdentity + '_' + i])
          // 自己的惟一标识  能够理解为我的的身份证咯 必定惟一。
          Vue.set(x, '__identity', elderIdentity + '_' + i)
          parent.push(x)
          // 若是仍有子集,则进行递归
          if (x.Children.length > 0) this.formatConversion(parent, x.Children, index + 1, [...family, elderIdentity + '_' + i], elderIdentity + '_' + i)
        })
      } return parent
    }

咱们来对比一下扁平话的数据ide

  • 原数据

  • 扁平化后的数据

对比数据的先后,先明确(父节点: Name: "App"), (子节点: Name: "企业查询")。咱们先看看__level字段,分别对应0和1,没问题。 __family包含了自己节点。 __identity的个格式就是 前缀 x加上在 数据中的位置。例如节点 App在数据中的位置是第一个节点,那对应的 __identity便是 x_0企业查询位于 App节点下的第一个元素, 则在父节点的标识的基础上追加一位0,那对应的 __identity便是 x_0_0。其余依次类推。一切已准备稳当~~

4. 层级展现

如图所示:post


若是展现层级呢?利用css便可实现

  • 字体图标准备: 这里先补充一点,这里涉及两个字体图标,图中红色框标注的,资源存放于src目录下的iconfont目录下中。层级最低的字段时不须要展现该图标呢该如何判断呢? 经过判断当前节点是否还有子集params.Children.length === 0便可判断。若没有子集则不设置字体图标便可。点击时还须要对图标进行切换,这又如何实现呢?前台提到过的死亡名单foldList,若是该存在该名单中,表明该数据已经被折叠,那返回折叠图标。不然返回展开图标。如代码:
//    html
<i :class="toggleFoldingClass(scope.row)"></i>

//    js methods:

    /*********************************
      ** Fn: toggleFoldingClass
      ** Intro: 若是子集长度为0,则不返回字体图标。
      ** Intro: 若是子集长度为不为0,根据foldList是否存在当前节点的标识返回相应的折叠或展开图标
      ** Intro: 关于class说明:permission_placeholder返回一个占位符,具体查看class
      ** @params: params 当前行的数据对象
      ** Author: zyx
    *********************************/
    toggleFoldingClass (params) {
      return params.Children.length === 0 ? 'permission_placeholder' : (this.foldList.indexOf(params.__identity) === -1 ? 'iconfont icon-minus-square-o' : 'iconfont icon-plussquareo')
    },
  • 层级展现: __level字段就是这时候发挥用处。__level值越大,则将margin-left值增大。如代码:
<p :style="`margin-left: ${scope.row.__level * 20}px;`">...

基本的样子已经有了,那接下来实现点击功能。

5. 折叠展开功能实现

记录点击的节点标识

经过死亡名单foldList来记录点击。点击事件在点击折叠展开图标时触发toggleFoldingStatus(scope.row)事件,前面也说过,foldList存在的标识,对于全部的子节点都要隐藏起来。若是foldList没有数据,则所有展开,万事大吉。代码如图所示,就是那么简单。

//    html
<i  @click="toggleFoldingStatus(scope.row)" class="permission_toggleFold" :class="toggleFoldingClass(scope.row)"></i>
//    js methods
    /*********************************
      ** Fn: toggleFoldingStatus
      ** Intro: 切换展开 仍是折叠
      ** @params: params 当前点击行的数据
      ** Author: zyx
    *********************************/
    toggleFoldingStatus (params) {
      this.foldList.includes(params.__identity) ? this.foldList.splice(this.foldList.indexOf(params.__identity), 1) : this.foldList.push(params.__identity)
    },

折叠展开功能实现

万事俱备,只欠东风。最后一件要作的大事就是根据死亡名单foldList来进行显示和隐藏数据。
这里借助el-table中的row-style实现。同窗们也能够本身实现。来看下官方的说明


该方法就是为table中的每一行数据设置样式。

/*********************************
      ** Fn: toggleDisplayTr
      ** Intro: 该方法会对每一行数据都作判断 若是foldList 列表中的元素 也存在与当前行的 __family列表中  则该行不展现
      ** @params:
      ** Author: zyx
    *********************************/
    toggleDisplayTr ({row, index}) {
      for (let i = 0; i < this.foldList.length; i++) {
        let item = this.foldList[i]
        // 若是foldList中元素存在于 row.__family中,则该行隐藏。  若是该行的自身标识等于隐藏元素,则表明该元素就是折叠点
        if (row.__family.includes(item) && row.__identity !== item) return 'display:none;'
      }
      return ''
    },
  • 在来看看效果

锦上添花

若是数据太多的话,一个层级层级的去搜索确实也麻烦,因此那么咱们再来添加两个按钮所有折叠所有展开好了。
咨询分析一下,其实这个功能很简单。仍是关于前面提到过的死亡名单foldList

  • 所有展开: 若是foldList为空,则万事大吉,数据所有展开。
  • 所有折叠:咱们的设计是只要存在于死亡名单的全部包含该标识的节点都要隐藏。那么只要将全部的首层节点添加进去就能够了。this.foldList = this.tableListData.map(x => x.__identity)即取出首层节点的标识。

结语

更复杂的需求是结合分页,当从其余页回到以前页时,以前页的折叠状态要依旧保持。这里就不在说明了,由于应用场景不多。

  • 点赞给个鼓励哟~
  • 又是下午3点了,该回去吃午餐了。
  • 版权说明:本文首发于掘金,如需转载请注明出处。
  • 掘金专栏地址:https://juejin.im/post/5be797...