一种常见的多层级树形表格组件

一种基于 Vue 和 Ant Design Vue / Element-ui 实现的,树形表格组件。这一种组件用于选择多层级树状数据中的多个节点。同时在各行各业后管需求中比较普遍常见。【后文有图示】

对于许多实际业务,因为往往比普通树形图组件稍微多了一套要实现的数据关系,所以单纯调用 Ant Design Vue 等等实现不了,只能二次开发实现。

本文将给出对于这一种组件,普遍通用的实现效果和大概的实现思路、经验

本文前面 2 个章节是介绍“实现效果”的;之后都是讲“实现的思路、经验”的。如果读者并不需要做同样的需求,看前面 2 个章节即可。

(本文尝试做一个“普遍通用”的总结,所以后文并不包含任何具体的实现代码。)

主要特点

  • 首先观察到它最主要的特点是:第二列的数据,也是分别从属于某个第一列数据的“子级”。 如图所示
    • 即,是树形表格。第一列数据之间存在多层树形父子级关系。
    • 同时,第二列的数据,也是分别从属于某个第一列数据的“子级”。

二次开发:上述的主要特点就意味着这个组件不能直接使用“第三方组件库自带的复选框显示逻辑”来实现。同时,交互逻辑都是需要自己二次开发编写的。

(另外,作为一种以“选择节点”作为功能的组件,这个组件需要把“已选择项”、“未选择项”、“将要选择项”,3者的信息同时表示出来。)

(图示只是仿造模拟图) custom-tree-table-mix-22091001

实现要求

我们再进一步,看看在一般的产品层面,对这一种组件还有什么实现的要求:

根据个人经验,在更广括看到同类事物层面,即规划“各种的树形组件”的实现方案,一般要考虑以下几个方面的功能点,基本上是“通用的”:

  • 一般而言,“通用的”的实现要求,有:
    • 是否需要懒加载
    • 查询功能
    • 勾选回显:(带勾选框的,在初始化时或者查询条件下,可能需要回显勾选上“已选择”的)
    • 所有节点的“展开”控制
    • (如果是支持“多选”,还要考虑以下这些方面:)
    • 勾选联动
    • “全选”功能

以上“通用的”要考虑的实现功能点,具体到这一种组件上,具体表现为:

  1. 初始化组件时,对于先前已选择项,回显它们的勾选状态
  2. 初始化组件 或 没有查询条件时,根据 已选择项 ,统计展开它们的祖先节点。 这样是为了方便系统使用者查看
  3. (初始化组件时,解析后端传来的数据回显,兼容“反映孤立节点”:若某子孙节点是选择状态,同时某一个祖先节点未被选择时,让该祖先节点显示为“半选”状态)
  4. 当某子孙节点被选择时,联动 让祖先节点被勾选。
    1. (比如示例图中的“用户修改”节点被选择时,需要联动让“部门用户管理test”和“用户管理test”被勾选上)
  5. 查询筛选:查询框输入了内容,按查询条件查询时,展示查询筛选后的树和节点。
  6. (还要兼顾实现树形图的常规联动交互:勾选某一节点时,联动其所有子孙节点勾选上)
  7. (常规“全选”功能需求:表格最顶部有个全选框,点击全选,更新所有节点状态为被选中)

(综上所述,除了“懒加载”效果,其他效果都在这一种组件中实现了。)

当这些功能需要叠加到同一个组件来实现的时候,组件复杂程度开始显著上升。

只是使用了 Ant Design Vue 的 a-table 的基础样式、节点展开控制样式(Element-ui同理,不涉及代码时都一样的)。而其他都是如上所说自行计算、控制,即所谓的“二次开发”的部分。

-----------内容分割线-----------

至此,这一种组件的“实现效果”介绍完毕。之后都是讲“实现的思路、经验”的。如果读者并不需要做同样的需求,后面的内容基本上可以不看。

提取出以上实现方案的难点

【1】梳理如何兼顾父子节点间联动:

  • (警惕组件库给出的不适宜方案:Ant Design Vue给出的a-tree-table等常规的联动方案:“子节点被选择的,其祖先节点可以不被选择”——是在这里不适用的)
  • 如果试过就会知道,这种联动方案是和我们想要实现的效果相互冲突的,无论如何调整提交数据都是存在bug的
  • “父被勾选”能联动“子被勾选”,“子被勾选”也能联动“父被勾选”
  • 通常需要再补一条“使用规则说明”,如上方的用户提示所示:“若某节点被勾选,则自动勾选其所有直系祖先节点”

【2】考虑要不要做懒加载子节点:

  • 如果已经实现懒加载,联动关系很难做兼容处理。
  • 通常,在专供大量数据使用的另一版“特供版”中,也没有实现懒加载。
    • 解释“特供版”:面对大量数据的需求,往往为了性能流畅需要对“便利性”做一些牺牲:“特供版”组件通常就是在初始化时只展开第一层的节点,以此牺牲换取更高的性能效果。
      • 也就是通过拷贝组件代码,削减成为另外单独一份组件代码来实现“特供版”。

降低实现复杂度的解决办法

  • 维护都是“用于呈现于视图的”母子树:查询筛选数据的时候,整树切换数据来显示
    • 对比别的方案:试想,假设查询树形表格前后,都是双向绑定同一树形数据。每次切换显示一次,需要计算并且更新的数据状态是很多的。
      • 其中首先要做一次对比算法
      • 对比完之后,根据全树差异,实施数据的变更:还要做一个“变更算法”。
      • 可见,这个比常规的对比算法还要复杂很多层次。
    • “母子树”具体操作:
      • 任何对树的变更,都是从母树单向同步到子树
      • 没有查询条件的时候:在视图上,显示母树,隐藏子树
      • 有查询条件的时候:从母树同步数据给子树或者计算子树;在视图上,隐藏母树,显示子树
  • 在数据中设定一个虚拟的根节点,其id为0
    • 在计算和判断树状数据的时候,有非常大的辅助作用
  • 缓存由树状数据转化而来的一维数据,作为副本
    • (最好在任意一处代码都能获取到这些数据)
    • 数据副本多了也会带来副作用,增加了要考虑的问题:要不要更新这个数据,什么时候更新这个数据等等
  • 任1个节点的parentId和childrenIds,最好都在“此某1个节点”用字段记录下来

宏观的实现思路

  • 还是要传给 Ant Design Vue 的 a-table 的部分模块:(Element-ui同理)
    • 表示整体结构的数据:树状数据,树中每个节点包括视图的所有状态相关的字段
    • 展开收起树形中的节点的交互还是交给 a-table 来处理和维护:a-table 组件接收的 expandedRowKeys 字段
  • 需要自己实现和从 a-table 接管的部分模块:(即“二次开发”)
    • 编写 slot 取缔 a-table 的所有视图上的复选框的状态显示和事件处理
    • 自行维护并计算一份“全树中的目前被勾选的节点的id集合”,也就是代表所有复选框的状态
      • 这个集合需要一直保持更新:事件触发计算,然后更新到“树状数据”每个节点的对应字段,以引发视图更新

综上2点:只是使用了 a-table 的基础样式、节点展开控制样式。其他都是自行计算、控制,即所谓的“二次开发”的部分。

  • 其中比较困难的点有: (单个组件代码规模越大,越难进行抉择)
    • 状态更新的事务安排:确定要做什么,确定涉及到的树节点,确定要做的顺序
    • 折返成本大:这个组件集成了很多的功能要求。如果一开始没有梳理清楚,切换方案折返重做的成本很大
    • 需要花费大量开发自测的时间检查实现的完整程度
文章目录
  1. 主要特点
  2. 实现要求
  3. -----------内容分割线-----------
  4. 提取出以上实现方案的难点
  5. 降低实现复杂度的解决办法
  6. 宏观的实现思路