ant-design-vue中实现a-tree树形控件父子关联选中过滤的算法

在使用ant-design-vue的框架时,a-tree是比较常用的组件,比较适合处理树形结构的数据。

但是在与后台数据进行授权交互时,就不友好了。

在原生官方文档的例子中,若子项被勾选,则父级节点会被关联勾选,但这勾选并不一定是选中的意思。有可能是半选中,通过方框样式选中,也就是说父级节点的值不会出现在checkedKeys的数组中。

html 复制代码
<a-tree
  v-model:checkedKeys="checkedKeys"
  checkable
  :tree-data="treeData"
>
</a-tree>

这对后端数据授权处理是不友好的。因为一般授权子节点时,父级节点必须是也授权的,否则应用无法到达子节点的功能。也就是说,关联被授权的父子节点的ID都要传给后端。

提交的时候,到还好说。将所有选中的列表和半选中的列表合并起来提交就可以了。

js 复制代码
const handleCheck = (checkedKeys, e) => {
  const {halfCheckedKeys } = e;
  const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys];
};

但是数据回写的时候就难受了,数据库里的授权列表回写到a-tree组件时,由于父节点被回传过来,那么当父节点被选中时,所有子节点都会被选中,这与实际不符。

所以需要将服务传过来的数据allCheckedKeys处理,再还原成选中checkedKeys和半选中halfCheckedKeys的两部分。

这个业务逻辑比较复杂,需要比较授权列表和树形结构数据。

我在网上找了很久都没找到关于此业务的算法逻辑。但是找到了另外的两种解决办法:

一是更改数据库结构,添加授权列表的字段分为选中和半选中的状态,传给后端服务时ID分类传输。

二是父子节点不再关联,即添加a-tree的属性为checkStrictly="true",分别独立处理单个节点的逻辑。

第一种方法缺点就是数据表设计不规范,数据库存储了适应前端页面的数据。优点是避免了js编写筛出未全部选中的父级id的工作。

第二种方法缺点是勾选父级节点时,子节点不会被关联勾选,层级很多时,操作不方便。同时子节点勾选时,可能遗漏父节点的勾选。当然可以再写代码,补上往上和往下关联的逻辑。但是还是有缺点,父节点被勾选时,是通过"√"表示,若不展开下级列表,会不知道子节点是否被全选上。

最后思来想去,感觉两种方法都有缺点。我还是觉得自己将这个业务逻辑的算法写出来吧。。。

代码如下:

ts 复制代码
/**
 * 提供两个方法:
 * 一是转换自定义树形对象数据为a-tree识别的树形对象列表
 * 二是将数据库存储的已分配id列表重新转化为checkedList
 * 
 * @param {string} idKey - 数据项 ID 的键名,默认为 'id'
 * @param {string} nameKey - 数据项名称的键名,默认为 'name'
 * @param {string} childrenKey - 子节点列表的键名,默认为 'children'
 */
export const useTreeConverter = (
  idKey: string = 'id',
  nameKey: string = 'name',
  childrenKey: string = 'children'
) => {
  /**
   * 转换对象
   * @param data 树形结构数据
   * @returns 返回UI组件认可的包含key、title、children属性的树形结构数据
   */
  const convertTree = (data: any[]): any[] => {
    return data.map((item) => ({
      key: item[idKey],
      title: item[nameKey],
      children:
        item[childrenKey] && item[childrenKey].length > 0 ? convertTree(item[childrenKey]) : []
    }))
  }

  /**
   *
   * @param savedKeys 授权已分配的ID列表
   * @param treeData 框架规定的treeData
   * @returns
   */
  const loadCheckState = (savedKeys: number[] = [], treeData: any[]) => {
    //选中数组
    const checkedKeysTemp: number[] = []
    //半选中数组
    const halfCheckedKeysTemp: number[] = []

    const checkNodeStatus = (node) => {
      //若本节点为叶子节点且ID列表包含节点的key值,则加入到选中数组中
      if (node.children.length === 0 && savedKeys.includes(node.key)) {
        checkedKeysTemp.push(node.key)
      }
      //若本节点为非叶子节点
      if (node.children.length > 0) {
        const isAllLeaf = node.children.every((child) => child.children.length === 0)
        //子节点都为叶子节点
        if (isAllLeaf) {
          //若叶子节点被选中,则加入到选中数组中
          for (let item of node.children) {
            if (savedKeys.includes(item.key)) {
              checkedKeysTemp.push(item.key)
            }
          }
          //若子节点都被选中,则该节点为被选中
          const allChildrenChecked = node.children.every((child) => savedKeys.includes(child.key))
          if (allChildrenChecked) {
            checkedKeysTemp.push(node.key)
            console.log(checkedKeysTemp)
          } else {
            //若子节点部分被选中,则该节点为半选中
            const someChildrenChecked = node.children.some((child) => savedKeys.includes(child.key))
            if (someChildrenChecked) {
              halfCheckedKeysTemp.push(node.key)
            }
          }
        } else {
          //若子节点不是都为叶子节点
          for (let item of node.children) {
            //子节点进行迭代
            if (item.children.length > 0) {
              item.children.forEach(checkNodeStatus)
            } else {
              checkNodeStatus(item)
            }
          }
          //迭代完子节点,继续判断该节点是否被选中
          const allChildrenChecked = node.children.every((child) =>
            checkedKeysTemp.includes(child.key)
          )
          //若子节点都被选中且不是半选中,则该节点为被选中
          if (allChildrenChecked) {
            checkedKeysTemp.push(node.key)
          } else {
            //若子节点部分被选中,则该节点为半选中
            const someChildrenChecked = node.children.some((child) => savedKeys.includes(child.key))
            if (someChildrenChecked) {
              halfCheckedKeysTemp.push(node.key)
            }
          }
        }
      }
    }

    // treeData 是你的树形结构的数据
    treeData.forEach(checkNodeStatus)

    return checkedKeysTemp
  }

  return {
    convertTree,
    loadCheckState
  }
}

由于a-tree的组件还是经常使用的,我将此算法封装为组合式函数useTreeConverter.ts中了,后面其他代码,也能复用该算法逻辑。

它包含两个逻辑:

一是将自定义的树形结构转换为组件识别的形如{children:'children', title:'title', key:'key' }的树形结构TreeData

二是给定形如[1,3,5,7,11]的授权ID列表和转换后的树形结构TreeData通过递归迭代的方式还原出哪些是checkedKeys的值。

该组合式函数的代码写一次就够了,后面其他使用a-tree的组件都用得上。提供2个服务端返回的授权列表和树形结构的参数输入,再调用提供的convertTreeloadCheckState的2个函数就可以得出结果了。

相关推荐
van叶~13 分钟前
算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
c++·算法
简简单单做算法14 分钟前
基于Retinex算法的图像去雾matlab仿真
算法·matlab·图像去雾·retinex
别拿曾经看以后~20 分钟前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死23 分钟前
导航栏及下拉菜单的实现
前端·css·css3
云卓SKYDROID28 分钟前
除草机器人算法以及技术详解!
算法·机器人·科普·高科技·云卓科技·算法技术
科技探秘人35 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人35 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR41 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香42 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel