递归循环获得tree树形结构数据

VUE + ElementUI tree组件树形数据处理(不限层级)

一、tree组件代码

	<el-tree
		class="tree_s"
		:data="treeData"
		highlight-current
		:expand-on-click-node="false"
		node-key="id"
		default-expand-all
		:props="defaultProps"
		@node-click="nodeClickFn"
	></el-tree>

二、数据分析
后台返回数据:id表示项目唯一性id、depth表示层级、parentId表示父级id、name表示节点名称。

[
    {
        "id":1,//唯一性id
        "parentId":0,//父级id
        "depth":1,//层级
        "name":"第一节点",//节点名称
    },
    {
       "id":2,
       "parentId":1,
       "depth":2,
       "name":"第二节点",
    },
    {
       "id":3,
       "parentId":2,
       "depth":3,
       "name":"第三节点",
    }
]

三、数据处理
转换数据结构,json转化为树形结构

methods: {
	//获取后台数据
    getDataList(){
      //AJAX获取数据
      this.data = res;//示例
      this.handleData(this.data);
    },
    //先找出第一层级,然后往里追加数据
    handleData(arr) {
      let dataArray = [];
      arr.forEach(function (data) {
        let parentId = data.parentId;
        if ( parentId == 0 ) {
          let objTemp = {
            id: data.id,
            label: data.name,
            depth: data.depth,
            parentId: parentId,
          }
          dataArray.push(objTemp);
        }
      })
      this.data2treeDG(arr, dataArray)
    },
    //递归追加数据
    data2treeDG(datas, dataArray) {
      for (let j = 0; j < dataArray.length; j++) {
        let dataArrayIndex = dataArray[j];
        let childrenArray = [];
        let Id = dataArrayIndex.id;
        for (let i = 0; i < datas.length; i++) {
          let data = datas[i];
          let parentId = data.parentId;
          if (parentId == Id) {//判断是否为儿子节点
            let objTemp = {
              id: data.id,
              label: data.name,
              depth: data.depth,
              parentId: parentId,
            }
            childrenArray.push(objTemp);
          }
        }
        dataArrayIndex.children = childrenArray;
        //有儿子节点则递归
        if (childrenArray.length > 0) {
          this.data2treeDG(datas, childrenArray)
        }
      }
      this.treeData = dataArray;
      return dataArray;
    },
}

四、格式化后数据结构
格式化后数据结构可满足tree组件需求

[
    {
        "id":1,
        "parentId":0,
        "depth":1,
        "label":"第一节点",
        "children": [
        	{
		       "id":2,
		       "parentId":1,
		       "depth":2,
		       "label":"第二节点",
		       "children": [
		       		{
				       "id":3,
				       "parentId":2,
				       "depth":3,
				       "label":"第三节点",
				    }
		       ]
		    },
        ]
    }
]

五、总结
此解决问题思路,也可用于动态导航栏数据处理。

六、优化
VUE3版本:——推荐

import * as R from 'remeda';

export enum TREE_DATA_TYPE {
  TREE = 'tree',
  LIST = 'list',
}

export interface TreeConfig {
  key?: string | undefined;
  label?: string | undefined;
  parentKey?: string | undefined;
  children?: string | undefined;
  dataType?: `${TREE_DATA_TYPE}` | undefined;
  list?: any[]; // 指定tree——treeList数据
}

export default () => {
  /**
   * Descriptions 树形数据处理公用方法——先找出第一层级,然后往children里追加数据
   * @param {Array} treeList 列表原始数据
   * @param {Array} dataKeys 指定 其他数据展示字段
   * @param {Number} originId 指定 根目录id,默认为0
   * @param {Object} treeKeys 指定 生成结构树主节点字段,默认{key: 'id', label: 'name', level: 'level', parentKey: 'parentId'}
   * @param {number} levelLimitNumber 指定 结构树展示层级 默认展示所有
   * @returns
   */
  const handleListDataToTree = (
    treeList: any[],
    dataKeys: string[],
    originId = 0,
    treeKeys = {
      key: 'id',
      label: 'name',
      level: 'level',
      parentKey: 'parentId',
    },
    levelLimitNumber?: number | undefined,
    filterStr?: string,
  ) => {
    const { key, label, level, parentKey } = treeKeys;
    if (levelLimitNumber) {
      // 只显示指定层级数据
      treeList = treeList.filter((item) => item[level] <= levelLimitNumber);
    }
    const tree = [];
    // 找出根目录 并生成根节点
    let originData = treeList.find((item: any) => item[key] == originId);
    // 找不到根目录 就自动生成根节点(提示:若需自定义根节点,可在外层源数据list数组内 手动追加根节点数据)
    if (!originData) {
      originData = handleNodeData(
        {
          [key]: originId,
          [label]: '全部',
          [level]: 0,
          [parentKey]: -1,
        },
        dataKeys,
        treeKeys,
      );
    }
    // 根节点数据
    const originNodeTemp = handleNodeData(originData, dataKeys, treeKeys);
    tree.push(originNodeTemp);

    // 收集子节点数据
    handleListToTreeChildNodes(treeList, tree, dataKeys, originId, treeKeys, levelLimitNumber);

    // 只显示包含(筛选)指定字符串数据
    if (filterStr) {
      const _filterList = getTreeDataByFilterStr(tree, filterStr, {
        ...treeKeys,
        list: treeList, // 已有list 无需再扁平处理
        dataType: TREE_DATA_TYPE.TREE,
      });
      return _filterList;
    }

    return tree;

    // 递归追加数据
    function handleListToTreeChildNodes(
      treeList: any[],
      dataArray: any[],
      dataKeys: string[],
      originId = 0,
      treeKeys = {
        key: 'id',
        label: 'name',
        level: 'level',
        parentKey: 'parentId',
      },
      levelLimitNumber?: number | undefined,
    ) {
      const { key, parentKey } = treeKeys;
      for (let j = 0; j < dataArray.length; j++) {
        const parentNodeData = dataArray[j];
        const childrenArray = [];
        for (let i = 0; i < treeList.length; i++) {
          const data = treeList[i];
          if (data[parentKey] == parentNodeData[key]) {
            const nodeTemp = handleNodeData(data, dataKeys, treeKeys);
            childrenArray.push(nodeTemp);
          }
        }
        // 有儿子节点则递归
        if (childrenArray.length > 0) {
          parentNodeData.children = childrenArray;
          handleListToTreeChildNodes(
            treeList,
            childrenArray,
            dataKeys,
            originId,
            treeKeys,
            levelLimitNumber,
          );
        }
      }
      return dataArray;
    }
    // 自动追加数据
    function handleNodeData(
      nodeData: any,
      dataKeys: string[],
      treeKeys = {
        key: 'id',
        label: 'name',
        level: 'level',
        parentKey: 'parentId',
      },
    ) {
      const { key, label, level, parentKey } = treeKeys;
      // 判断是否为儿子节点
      const nodeTemp: any = {
        [key]: nodeData[key] || 0,
        [label]: nodeData[label] || '全部',
        [level]: nodeData[level] || 0,
        [parentKey]: nodeData[parentKey] !== undefined ? nodeData[parentKey] : -1,
      };
      const objTemp: any = {
        ...nodeTemp,
        id: nodeData[key],
        key: nodeData[key],
        value: nodeData[key],
        level: nodeData[level],
        label: nodeData[label],
        title: nodeData[label],
        name: nodeData[label],
        parentId: nodeData[parentKey],
      };
      // 追加额外参数**********
      if (Array.isArray(dataKeys)) {
        dataKeys.forEach((key) => {
          objTemp[key] =
            typeof nodeData[key] == 'boolean' ? nodeData[key] : nodeData[key] ? nodeData[key] : '';
        });
      } else if (dataKeys && typeof dataKeys === 'string') {
        objTemp[dataKeys] =
          typeof nodeData[key] == 'boolean' ? nodeData[key] : nodeData[key] ? nodeData[key] : '';
      }
      return objTemp;
    }
  };

  /**
   * Descriptions 将树形数组扁平化
   * @param tree 列表原始(树形)数据
   * @param parentId 指定将要被扁平化的节点ID
   */
  const handleTreeDataToList = (tree: any[], parentId?: any, config?: TreeConfig): T[] => {
    const { children = 'children', key = 'id' } = config || {};
    return tree.reduce((t, _) => {
      const child = _[children];
      return [
        ...t,
        parentId ? { ..._, parentId } : _,
        ...(child && child.length ? handleTreeDataToList(child, _[key]) : []),
      ];
    }, []);
  };

  /**
   * Descriptions 树形数据字符串搜索(筛选)
   * @param tree 列表原始(树形)数据
   * @param str 将要过滤字符串
   */
  const getTreeDataByFilterStr = (tree: any[], str?: any, config?: TreeConfig): T[] => {
    const filterStr = str || '';
    const {
      children = 'children',
      key = 'id',
      label = 'name',
      parentKey = 'parentId',
    } = config || {};
    // 先扁平化处理
    const flatArray = config?.list || handleTreeDataToList(tree);
    // 只显示包含(筛选)指定字符串数据
    const filterIds = flatArray.filter((_) => _[label].indexOf(filterStr) > -1).map((_) => _[key]);
    const filterNodes = getTreeNodeParentNodes(tree, filterIds, {
      key,
      children,
      label,
      parentKey,
      dataType: TREE_DATA_TYPE.TREE,
    });
    return filterNodes || [];
  };

  /**
   * Descriptions 获取树形数组中 指定id相关联的(关系链)所有父级节点id集合
   * @param {Array} tree 列表原始(树形)数据
   * @param {string | number} nodeId 节点id
   */
  const getTreeNodeParentIds = (tree: any[], nodeId: any, config?: TreeConfig) => {
    const { key = 'id', parentKey = 'parentId' } = config || {};
    // 先将tree树形数据扁平化
    const flatTree = handleTreeDataToList(tree, undefined, config);
    if (Array.isArray(nodeId)) {
      let ids = [] as any[];
      nodeId.forEach((_id) => {
        ids.push(_id);
        ids = [...getTreeNodeParentIds(tree, _id, config), ...ids];
      });
      return Array.from(new Set(ids));
    }
    return getIds(flatTree);

    function getIds(flatArray: any[], _nodeId?: any) {
      const __nodeId = _nodeId || nodeId;
      let ids = [__nodeId];
      let child = flatArray.find((_) => _[key] === __nodeId);
      while (child && child[parentKey]) {
        ids = [child[parentKey], ...ids];
        child = flatArray.find((_) => _[key] === child[parentKey]);
      }
      return ids;
    }
  };

  /**
   * Descriptions 获取树形数组中 指定id相关联的(关系链)所有父级节点数据集合
   * @param {Array} tree 列表原始(树形)数据
   * @param {string | number} nodeId 指定节点ID
   */
  const getTreeNodeParentNodes = (tree: any[], nodeId: any, config?: TreeConfig) => {
    const {
      children = 'children',
      key = 'id',
      parentKey = 'parentId',
      dataType = '',
    } = config || {};
    // 先将tree树形数据扁平化
    const flatTree = handleTreeDataToList(tree, undefined, config);
    const parentNodeIds = getTreeNodeParentIds(tree, nodeId, config);
    // return getDatas(flatTree);
    return getDatas(parentNodeIds);

    // 返回父级节点数据(新)
    function getDatas(flatIdsArray: any[]) {
      const datas = flatIdsArray
        .map((_nodeId) => {
          return flatTree.find((node) => node[key] === _nodeId);
        })
        .filter((n) => n);
      return dataType === TREE_DATA_TYPE.TREE ? getTreeDatas(datas) : datas;
    }

    // 返回父级节点数据(旧)
    // function getDatas(flatArray: any[]) {
    //   let child = flatArray.find((_) => _[key] === nodeId);
    //   let datas = [child];
    //   while (child && child[parentKey]) {
    //     const parent = flatArray.find((_) => _[key] === child[parentKey]);
    //     datas = [parent, ...datas];
    //     child = flatArray.find((_) => _[key] === child[parentKey]);
    //   }
    //   return dataType === TREE_DATA_TYPE.TREE ? getTreeDatas(datas) : datas;
    // }

    // 返回父级节点树形数据
    function getTreeDatas(_flatArray: any[]) {
      const flatArray = R.clone(_flatArray);
      // * 先生成parent建立父子关系
      const objMap: any = {};
      flatArray.forEach((item) => {
        if (item[key]) objMap[item[key]] = item;
        delete item[children];
      });
      // * objMap -> {1001: {id: 1001, parentId: 0, name: 'AA'}, 1002: {...}}
      const parentList: any[] = [];
      flatArray.forEach((item) => {
        const parent = item[parentKey] ? objMap[item[parentKey]] : null;
        if (parent) {
          // * 当前项有父节点
          parent.children = parent.children || [];
          parent.children.unshift(item);
        } else {
          // * 当前项没有父节点 -> 顶层
          parentList.push(item);
        }
      });
      return parentList;
    }
  };

  /**
   * Descriptions:获取树形数组中 指定id对应的节点数据
   * @param {Array} tree 数据数组
   * @param {Number} nodeId 指定节点ID
   */
  const getTreeNodeData = (tree: any[], nodeId: any, config?: TreeConfig): any => {
    const { children = 'children', key = 'id' } = config || {};
    //判断list是否是数组
    if (!Array.isArray(tree)) {
      return null;
    }
    //遍历数组
    for (const i in tree) {
      const item = tree[i];
      if (item[key] && item[key] == nodeId) {
        return item;
      }
      //查不到继续遍历
      if (item[children]) {
        const value = getTreeNodeData(item[children], nodeId, config);
        //查询到直接返回
        if (value) {
          return value;
        }
      }
    }
  };

  /**
   * Descriptions:获取树形数组中 指定id对应的节点下标(层级)信息
   * @param {Array} tree 数据数组
   * @param {Number, String} nodeId 指定节点数据 如:123 {id: '123'}
   * @param {String} config.key 取值键名 默认id
   * @returns {Array} 例如:[0,0]表示数组中 第一元素的 第一个子元素的层级下标
   */
  const getTreeNodeIndex = (tree: any[], nodeId: any, config?: TreeConfig): any => {
    const { children = 'children', key = 'id' } = config || {};
    //判断tree是否是数组
    if (!Array.isArray(tree)) {
      return null;
    }
    //遍历数组
    for (const i in tree) {
      const item = tree[i];
      if (item[key] && item[key] == nodeId) {
        return [i];
      }
      //查不到继续遍历
      if (item[children]) {
        const el = getTreeNodeIndex(item[children], nodeId, config);
        //查询到直接返回
        if (el) {
          return [i, ...el].map((num) => {
            return num - 0;
          });
        }
      }
    }
  };

  return {
    handleTreeDataToList,
    handleListDataToTree,

    getTreeNodeParentIds,
    getTreeNodeParentNodes,
    getTreeNodeData,
    getTreeNodeIndex,
    getTreeDataByFilterStr,
  };
};

VUE2版本

methods: {
        // 树形数据处理公用方法——先找出第一层级,然后往children里追加数据
        /**
         * @param {Array} arr  resData列表原始数据
         * @param {Array} keys 自定义字段数组
         * @param {Number} originId 根目录parentId,默认为0, 可自定义传入 如‘-1’
         * @returns
         */
        handleData(arr = [], keys = [], originId = 0) {
        	//如果后端没有返回根目录节点数据,则需要自定义根节点 如下:
            if (originId < 0) arr.push({ id: 0, value: 0, label: '全部', name: '全部', depth: 0, parentId: originId })
            const dataArray = []
            for (let i = 0; i < arr.length; i++) {
                const data = arr[i]
                const parentId = data.parentId
                if (parentId == originId) {
                    const objTemp = {
                            id: data.id,
                            value: data.id,
                            label: data.name,
                            name: data.name,
                            depth: data.depth,
                            parentId: parentId,
                            disabled: data.disabled
                        }
                        // 追加额外参数**********
                    if (Array.isArray(keys)) {
                        keys.forEach(key => {
                            objTemp[key] = data[key] ? data[key] : ''
                        })
                    } else if (keys && typeof keys === 'string') {
                        objTemp[keys] = data[key] ? data[key] : ''
                    }
                    dataArray.push(objTemp)
                    break
                }
            }
            return this.data2treeDG(arr, dataArray, keys, originId)
        },
        // 递归追加数据
        data2treeDG(datas, dataArray, keys, originId) {
            for (let j = 0; j < dataArray.length; j++) {
                const dataArrayIndex = dataArray[j]
                const childrenArray = []
                const Id = dataArrayIndex.id
                for (let i = 0; i < datas.length; i++) {
                    const data = datas[i]
                    const parentId = data.parentId
                    if (parentId == Id) { // 判断是否为儿子节点
                        const objTemp = {
                                id: data.id,
                                value: data.id,
                                name: data.name,
                                label: data.name,
                                depth: data.depth,
                                parentId: parentId,
                                disabled: data.disabled
                            }
                            // 追加额外参数**********
                        if (Array.isArray(keys)) {
                            keys.forEach(key => {
                                objTemp[key] = data[key]
                            })
                        } else if (keys && typeof keys === 'string') {
                            objTemp[keys] = data[keys]
                        }
                        childrenArray.push(objTemp)
                    }
                }
                // 有儿子节点则递归
                if (childrenArray.length > 0) {
                    dataArrayIndex.children = childrenArray
                    this.data2treeDG(datas, childrenArray, keys)
                }
            }
            return originId ? dataArray[0].children : dataArray
        },

        // 获取父子级字符串组合
        getCascaderStr(id) {
            if (!id) return []
            const resData_ = this.resData
            const typesStrArr = []
            const nowTypeObj = this.checkTypeObj(id)
            typesStrArr.unshift(nowTypeObj.name)
            checkId(nowTypeObj.parentId)
            return typesStrArr

            function checkId(parentId) {
                for (let i = 0; i < resData_.length; i++) {
                    const el = resData_[i]
                    if (parentId == el.id && parentId != 0) {
                        typesStrArr.unshift(el.name)
                        checkId(el.parentId)
                        break
                    }
                }
            }
        },

        // 获取父子级ID组合数组
        getCascaderArr(id) {
            if (!id) return []
            const resData_ = this.resData
            const typesArr = []
            const nowTypeObj = this.checkTypeObj(id)
            typesArr.unshift(nowTypeObj.id)
            checkId(nowTypeObj.parentId)
            return typesArr

            function checkId(parentId) {
                for (let i = 0; i < resData_.length; i++) {
                    const el = resData_[i]
                    if (parentId == el.id && parentId != 0) {
                        typesArr.unshift(el.id)
                        checkId(el.parentId)
                        break
                    }
                }
            }
        },
        //根据id 遍历数组 返回目标对象
        checkTypeObj(id) {
            const resData_ = this.resData
            let nowTypeObj_ = null
            for (let i = 0; i < resData_.length; i++) {
                const el = resData_[i]
                if (el.id == id) {
                    nowTypeObj_ = el
                    break
                }
            }
            return nowTypeObj_
        }
    }```
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
使用递归解决树形结构数据循环调用问题的关键是要踪已经访问过的节点,以免进入无限循环。下是一个示例,演示了如何使用递归解决树形结构数据循环调用问题: ```javascript // 树节点类 class TreeNode { constructor(value) { this.value = value; this.children = []; } addChild(childNode) { this.children.push(childNode); } } // 递归遍历树 function traverseTree(node, visitedNodes = new Set()) { // 如果节点已经被访问过,则直接返回 if (visitedNodes.has(node)) { return; } // 访问当前节点 console.log(node.value); visitedNodes.add(node); // 遍历子节点 for (const child of node.children) { traverseTree(child, visitedNodes); } } // 创建树形结构数据 const root = new TreeNode('A'); const nodeB = new TreeNode('B'); const nodeC = new TreeNode('C'); const nodeD = new TreeNode('D'); const nodeE = new TreeNode('E'); root.addChild(nodeB); root.addChild(nodeC); nodeB.addChild(nodeD); nodeD.addChild(nodeE); // 循环调用测试 nodeE.addChild(root); // 在 E 节点中添加对根节点的引用 // 使用递归遍历树 const visitedNodes = new Set(); traverseTree(root, visitedNodes); ``` 在这个示例中,我们定义了一个 `TreeNode` 类表示树的节点,每个节点都有一个值属性 `value` 和一个子节点数组 `children`。我们使用 `addChild` 方法添加子节点。 `traverseTree` 函数用于递归遍历树,并通过一个 `Set` 数据结构来记录已经访问过的节点,以防止无限循环。在访问节点时,我们打印节点的值,并将节点添加到已访问节点集合中。 在创建树形结构数据后,我们对节点 `E` 执行了一个循环调用测试,将根节点 `A` 作为子节点添加到了 `E` 节点。在使用递归遍历树时,我们传入一个空的已访问节点集合,并在遍历过程中传递该集合,确保只访问未被访问过的节点,避免了无限循环。 请注意,以上示例仅提供了一个基本的使用递归解决树形结构数据循环调用问题的思路。在实际应用中,可能需要根据具体情况进行适当的调整和优化。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值