前言

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


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

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

技术栈

  • vue
  • javascript

动手实现

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

大纲预览

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

1. 明确需求

实现一个可折叠展开的table,看上去很迷茫的样子。但实际上就是:

  1. table上点击时对其子集进行隐藏显示
  2. 通过缩进的距离来表现层级关系

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

2. 树形结构数据准备

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


3. 数据扁平化

条件梳理

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

  1. 我们将一个树节点的所有父节点称为family,好比我们要知道的自己的大领导、二领导、直系领导...因为他们的命令咱们都得执行。在这个表单中就是当父节点或更高的祖辈节点发命令时,做一个懂事的子节点当然要听话办事。__family字段就是用来存放所有所有的父节点的数组。在扁平化数据是通过数组字段__family收集其所有父节点。这样一来,就像族谱一样,就能清晰知道该节点的所有父节点。至于如和折叠收缩呢?我们通过一个foldList数组来记录要折叠的节点,也称为死亡名单。如此一来,只要包含父节点标识节点就乖乖的隐藏吧。
  1. 每个成员都因该是唯一的,那么我们在遍历时都应该为每个成员添加一个唯一标识__identity。正是上一个步骤中说的节点标志
  1. 为了明确知道各个节点之间的层级关系,通过__level明确层级关系。那么我们规定__level的值越小等级越高。__level=0代表首层节点。
  1. 既然是无限层级,肯定是用递归来实现了。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
}

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

  • 原数据

  • 扁平化后的数据

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

4. 层级展示

如图所示:


如果展示层级呢?利用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点了,该回去吃饭午饭了。

实现一个可无限折叠的table的更多相关文章

  1. JSON无限折叠菜单

    JSON无限折叠菜单编写 2013-12-14 22:37 by 龙恩0707, 103 阅读, 1 评论, 收藏, 编辑 最近看了一篇关于JSON无限折叠菜单的文章 感觉写的不错,也研究了下代码,所 ...

  2. JSON无限折叠菜单编写

    最近看了一篇关于JSON无限折叠菜单的文章 感觉写的不错,也研究了下代码,所以用自己编码方式也做了个demo 其实这样的菜单项在我们网站上或者项目导航菜单项很常见的一种效果,特别是在一些电子商务网上上 ...

  3. 制作一个可以滑动操作的 Table View Cell

    本文转载至 https://github.com/nixzhu/dev-blog Apple 通过 iOS 7 的邮件(Mail)应用介绍了一种新的用户界面方案——向左滑动以显示一个有着多个操作的菜单 ...

  4. 一个可无限伸缩且无ABA问题的无锁队列

    关于无锁队列,详细的介绍请参考陈硕先生的<无锁队列的实现>一文.然进一步,如何实现一个不限node数目即能够无限伸缩的无锁队列,即是本文的要旨. 无锁队列有两种实现形式,分别是数组与链表. ...

  5. 自定义一个可以动态折叠的UITAbleViewCell

    看到code 4APP上有一个折叠的UITAbleViewCell,不过是swift的,所以自己尝试做一个简单的可折叠的UITAbleViewCell 主要实现一个可以折叠的UITAbleViewCe ...

  6. 手把手教你实现一个Vue无限级联树形表格(增删改)

    前言平时我们可能在做项目时,会遇到一个业务逻辑.实现一个无限级联树形表格,什么叫做无限级联树形表格呢?就是下图所展示的内容,有一个祖元素,然后下面可能有很多子孙元素,你可以实现添加.编辑.删除这样几个 ...

  7. 一个页面多Table多分页的问题

    一个页面有多个table,多个pagination,一个pagerForm.这种情况下怎么解决多个pagination不同pagenum的问题呢? 如果是这样的滴话,使用局部刷新就可以了,两个tabl ...

  8. React 实现一个漂亮的 Table

    概述 对于企业级后台产品来说,Table 应该是使用最频繁的组件了,它通常比 Form 和 Chart 的使用还频繁.对于这么一个常用的组件,我们决定要把它从 RSuite 中单独出来开发,并且要具有 ...

  9. CREATE TABLE - 定义一个新表

    SYNOPSIS CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name ( { column_name data_ty ...

随机推荐

  1. Python 中__new__()和__init__()的区别

    转自: https://blog.csdn.net/weixin_37579123/article/details/89515577 __new__方法:类级别的方法 特性: 1.是在类准备将自身实例 ...

  2. PTA(Basic Level)1033.旧键盘打字

    旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现.现在给出应该输入的一段文字.以及坏掉的那些键,打出的结果文字会是怎样? 输入格式: 输入在 2 行中分别给出坏掉的那些键.以及应该输入 ...

  3. WijmoJS V2019.0 Update2发布:再度增强 React 和 Vue 框架的组件功能

    前端开发工具包 WijmoJS 在2019年的第二个主要版本 V2019.0 Update2 已经发布,本次发布涵盖了React 和 Vue 框架下 WijmoJS 前端组件的功能增强,并加入更为易用 ...

  4. Spring IOC的底层实现原理

     PS:模块之间的相互依赖叫做耦合 传统方式的开发 UserService us=new UserService(); || v 面向接口编程 UserService us=new UserServi ...

  5. PB赋值粘贴

    复制:string ls_templs_temp = trim(sle_1.text)Clipboard(ls_temp) 粘贴:string ls_templs_temp = Clipboard() ...

  6. CentOS7-部署kubernetes

    1 环境准备   节点 主机名 IP OS Master     k8s-master        192.168.57.1       centos 7        Node1  k8s-nod ...

  7. 两个链表的第一个公共结点——牛客offer

    题目描述: 输入两个链表,找出它们的第一个公共结点. 题目分析: 只是数据域相同不是公共节点.公共结点代表该节点在两个链表中的数据域和指针域都是相同的,这意味着从该公共节点开始,后面的结点都是两个链表 ...

  8. 并不对劲的复健训练-bzoj5339:loj2578:p4593:[TJOI2018]教科书般的亵渎

    题目大意 题目链接 题解 先将\(a\)排序. \(k\)看上去等于怪的血量连续段的个数,但是要注意当存在\(a_i+1=a_{i+1}\)时,虽然它们之间的连续段为空,但是还要算上:而当\(a_m= ...

  9. 数值优化(Numerical Optimization)学习系列-无梯度优化(Derivative-Free Optimization)

    数值优化(Numerical Optimization)学习系列-无梯度优化(Derivative-Free Optimization) 2015年12月27日 18:51:19 下一步 阅读数 43 ...

  10. lua的数据类型

    Lua 是动态(弱)类型的语言,它有一下几种数据结构: nil(空) nil 类型表示一种没有任何有效值,它只有一个值 -- nil,例如打印一个没有赋值的变量,便会输出一个 nil 值: print ...