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_
        }
    }```
Logo

前往低代码交流专区

更多推荐