Tree组件实现父子节点分离(父子节点自定义逻辑关联)【antd】实现Tree复选,数型数据递归处理

项目中我们用的Tree组件一般都是父子节点关联的,但是产品提出了个需求,没办法使用组件自带的逻辑了,只能自定义逻辑进行管理选中节点。需求如下:

1. 父节点选中所有子节点也要选中
2. 父节点取消勾选子节点也要全部取消
3. 子节点勾选所有父节点都要勾选
4. 子节点取消父节点不取消

效果如下:

 首先我们看一下antd tree这个组件需要什么:

<a-tree
    v-if="authTreeList && authTreeList.length > 0"
    v-model="checkedAuthKeys"
    checkable
    :expanded-keys="expandedKeys"
    :auto-expand-parent="autoExpandParent"
    :tree-data="authTreeList"
    @check="onCheck"
    checkStrictly
/>
  • v-moel对应是选中的id集 
  • checkable添加复选框功能
  • expanded-keys展开指定的树节点
  • auto-expand-parent是否自动展开父节点
  • tree-data树型数据
  • check选中复选框触发
  • checkStrictly节点选择完全受控(父子节点选中状态不再关联) 

首先后端返回的权限数据是这样的:

checked是否选中,pId代表父节点,所以我们首先要把数据转换成树形数据以供tree组件使用

/**
 * 获取树状结构
 * 通过定义map,key为当前对象id,value为该对象
 * 遍历集合,得到对象顶级节点放到集合中返回
 * 不是顶级的就是当前对象得子节点,将对象放到该节点下
 */
export const toTree = (nodes) => {
  let result = [];
  //如果值是 Array,则为true; 否则为false。
  if (!Array.isArray(nodes)) {
    return result;
  }
  //深拷贝,否则会影响原数组
  let node = JSON.parse(JSON.stringify(nodes));
  //根据父节点进行拼接子节点,
  node.forEach((item) => delete item.children); //已经有的话就删掉
  //把每一项的引用放入map对象里
  let map = {};
  node.forEach((item) => (map[item.id] = item));
  let newNode = [];
  node.forEach((dt) => {
    let parents = map[dt.pId];
    if (parents) {
      //如果 map[dt.pId] 有值 则 parents 为 dt 的父级
      //判断 parents 里有无child 如果没有则创建 如果有则直接把 dt push到children里
      if (!parents.children) {
        parents.children = [];
      }
      parents.children.push(dt);
    } else {
      newNode.push(dt);
    }
  });
  return newNode;
};

转换后的格式如下: 

 我们再继续抽离选中的id集:

const resData = res.data //后端返回的数据
let checkedAuthKeys = new Set()
for (let i = 0; i < resData.length; i++) {
    item = resData[i]
    if (item['checked']) {
        checkedAuthKeys.add(item['id'])
    } else {
        if (checkedAuthKeys.has(item['id']))
            checkedAuthKeys.delete(item['id'])
        }
    }
}

处理后:

分别赋值两个data给v-model和auto-expand-parent就可以了,这样展现应该没问题了,下面我们来控制一下节点选择时的逻辑。

大概讲一下如何实现,首先在默认进入时保留一个选中的id集,然后在选择的时候比对本次与上一次的差异,向下查找该节点下所有子节点的id,然后向上查找所有父节点的id,判断本次操作是否勾选来决定子节点集是合并还是移除。

onCheck(checkedKeys, e) {
      const beforeCheckedAuthKeys = this.beforeCheckedAuthKeys //上一次选中的ids 如果没有就是初始进入选中的集合
      // console.log('上一次选中ids', beforeCheckedAuthKeys)
      // console.log('选中ids', checkedKeys.checked)
      const changeNodes = getArrDifference(checkedKeys.checked, beforeCheckedAuthKeys) //比对这次选中与上次选中的差异
      // console.log('改变的节点', changeNodes);
      let checkedList = [];
      for (let i = 0; i < changeNodes.length; i++) {
        let itemNode = searchTreeNodes(this.authTreeList, changeNodes[i]) //查找该节点数据 authTreeList为树型数据
        let childrenNodesIds = searchTreeNodesAllId(itemNode.children) //向下查找所有子节点的ids
        checkedList = [...new Set([...checkedList, ...childrenNodesIds])] //合并去重
      }
      // console.log('该节点下所有ids', checkedList)
      //获取选中的节点的所有父节点
      let parentNodes = changeNodes
      parentNodes.forEach(
        (item) =>
          (parentNodes = parentNodes.concat(getParentIdList(item, this.authTreeList)))
      )
      // console.log('选中的所有父节点', parentNodes)
      if (e.checked) { // 勾选
        this.checkedAuthKeys = [...new Set([...checkedKeys.checked, ...checkedList, ...parentNodes])]
      } else { // 取消勾选
        if(checkedList && checkedList.length) {
          //如果取消勾选需要移除该节点下所有子节点ids
          this.checkedAuthKeys = checkedKeys.checked.filter(item => checkedList.indexOf(item) == -1)
        } else {
          this.checkedAuthKeys = this.checkedAuthKeys.checked
        }
      }
      this.beforeCheckedAuthKeys = this.checkedAuthKeys //本次选择处理结束 赋值当前选择供下次使用
      console.log('选中', this.checkedAuthKeys)
},

以上通用的函数我都封装到js里了 :

/**
 * 比较两个数组差异
 */
export function getArrDifference(arr1 = [], arr2 = []) {
  return arr1.concat(arr2).filter((v, i, arr) => {
    return arr.indexOf(v) === arr.lastIndexOf(v);
  })
}

/**
 * 根据id查找节点 输出节点
 * @param nodes 树
 * @param searchKey id
 */
 export function searchTreeNodes(nodes, searchKey) {
  for (let _i = 0; _i < nodes.length; _i++) {
    if (nodes[_i].id == searchKey) {
      return nodes[_i]
    } else {
      if (nodes[_i].children && nodes[_i].children.length > 0) {
        let res = searchTreeNodes(nodes[_i].children, searchKey);
        if (res) {
          return res
        }
      }
    }
  }
  return null
}

/**
 * 获取该节点下子孙节点的id
 * @param data 节点
 * @param arr 返回数组
 */
 export function searchTreeNodesAllId(data = [], arr = []) {
  Object.keys(data).forEach((key) => {
    arr.push(data[key].id)
    if (data[key].children && data[key].children.length) searchTreeNodesAllId(data[key].children, arr)
  })
  return arr
}

/**
 * 通过当前节点id,获取树状结构所有的祖先节点id,包含当前节点id
 * @param {String|Number} code 当前节点id
 * @param {Array} tree 树状数组
 * @returns {Array} 所有祖先id,包含当前code
 */
 export const getParentIdList = (code, tree) => {
  let arr = []; //要返回的数组
  for (let i = 0; i < tree.length; i++) {
    let item = tree[i];
    arr = [];
    arr.push(item.id); //保存当前节点id
    if (code == item.id) {
      //判断当前id是否是默认id
      return arr; //是则退出循环、返回数据
    } else {
      //否则进入下面判断,判断当前节点是否有子节点数据
      if (item.children && item.children.length > 0) {
        //合并子节点返回的数据
        arr = arr.concat(getParentIdList(code, item.children));
        if (arr.includes(code)) {
          //如果当前数据中已包含默认节点,则退出循环、返回数据
          return arr;
        }
      }
    }
  }
}

这样就大功告成了,当然可以做到更细致,比如控制半选状态,v-model它是一个有checkedhalfChecked属性的对象,我们可以处理选中的ids赋值给checked对象、半选的ids赋值给halfChecked对象,这样就能做到复现一个Tree的复选框,并且我们还可以控制其中的关联。

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
可以通过在Tree组件中设置onSelect函数来实现点击节点文字展开收起。 具体实现步骤如下: 1. 在Tree组件中设置onSelect函数,该函数会在节点被选中时触发。 ```javascript <Tree onSelect={onSelect} // 其他属性 > // 树节点 </Tree> ``` 2. 在onSelect函数中判断被选中的节点是否有子节点,如果有则展开节点,否则不做任何操作。 ```javascript const onSelect = (selectedKeys, { node }) => { const { children } = node.props; if (children) { setExpandedKeys(expandedKeys => { const index = expandedKeys.indexOf(selectedKeys[0]); if (index > -1) { // 如果节点已展开,则收起节点 return [...expandedKeys.slice(0, index), ...expandedKeys.slice(index + 1)]; } else { // 如果节点未展开,则展开节点 return [...expandedKeys, selectedKeys[0]]; } }); } } ``` 3. 在Tree组件中设置expandedKeys属性,该属性为展开的节点的key的数组。 ```javascript <Tree onSelect={onSelect} expandedKeys={expandedKeys} // 其他属性 > // 树节点 </Tree> ``` 完整代码如下: ```javascript import React, { useState } from 'react'; import { Tree } from 'antd'; const Demo = () => { const [expandedKeys, setExpandedKeys] = useState([]); const onSelect = (selectedKeys, { node }) => { const { children } = node.props; if (children) { setExpandedKeys(expandedKeys => { const index = expandedKeys.indexOf(selectedKeys[0]); if (index > -1) { // 如果节点已展开,则收起节点 return [...expandedKeys.slice(0, index), ...expandedKeys.slice(index + 1)]; } else { // 如果节点未展开,则展开节点 return [...expandedKeys, selectedKeys[0]]; } }); } } return ( <Tree onSelect={onSelect} expandedKeys={expandedKeys} > // 树节点 </Tree> ); } export default Demo; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值