Cascader 后端返回数据转化为树形结构,以及获取节点路径

一、简介

有时候后端返回的树形结构数据是未处理的,就是一个数组,这个时候就需要前端自己来做处理。

二、后端返回的数据结构

[{
    id: '511681000042',
    name: '父节点01',
    parentId: ''
  },
  {
    id: '01',
    name: '子阶段01',
    parentId: '511681000042'
  },
  {
    id: '02',
    name: '子节点02',
    parentId: '511681000042'
  },
  {
    id: '03',
    name: '子节点03'
    parentId: '511681000042'
 }]

三、将上面的数据转化为树形结构

/*
思路:树形结构都是分层的,这里做法是每次递归将每一层的数据筛选出来,然后组装树形结构,减少递归次数
*/
/*
  @params parentIds 父节点id数组
  @params treeData 将要转化为树形结构的数据
  @params transformItemDataFunc 转化某个数据的函数(可不传)
*/
const handleTransformTreeData = function (parentIds = [], treeData = [], transformItemDataFunc) {
    // 判断数据类型
    let toString = Object.prototype.toString;
    if (toString.call(parentIds) !== '[object Array]') {
      return new Error('handleTransformTreeData: 父节点值必须为数组');
    }
    if (toString.call(treeData) !== '[object Array]') {
      return new Error('handleTransformTreeData: 节点数据必须为数组');
    }
    if (transformItemDataFunc != void 0 &&
        toString.call(transformItemDataFunc) !== '[object Function]') {
      return new Error('handleTransformTreeData: transformItemDataFunc必须为函数');
    }
    // 父节点数据Map
    let treeMap = {};
    // 子节点数据
    let children = [];
    // 下次参与筛选的数据
    let nextTreeData = [];

    // 1. 分离父节点和参与下一次递归的子节点数据
    treeData.forEach(item => {
      if (parentIds.includes(item.parentId)) {
        treeMap[item.id] = transformItemDataFunc === void 0
                           ? item
                           : transformItemDataFunc(item);
      } else {
        nextTreeData.push(item);
      }
    });

    // 2. 将分离后的父节点id当作下一次筛选的parentIds
    parentIds = Object.keys(treeMap);
    
    // 这里判断父节点和子阶段个数
    // 如果父节点或则子节点个数为零个,则不满足继续递归条件
    if (parentIds.length > 0 && nextTreeData.length > 0) {
      // 3. 从children里面筛选出tree的子节点数据
      children = handleTransformTreeData(parentIds, nextTreeData);
      // 4. 关联tree 和 children数据
      children.length > 0 &&
      children.forEach(item => {
        let { parentId } = item;
        if (treeMap[parentId].children === void 0) {
          treeMap[parentId].children = [];
        }
        treeMap[parentId].children.push(item);
      });
    }

    return Object.keys(treeMap).map(id => {
      return treeMap[id];
    });
 }

四、获取节点路径

/*
思路:和生成树形结构的思路一样,每次递归都在同一层找路径,直到找到为止
*/

/*
  @params nodes 要找到路径的节点id
  @params tree 树形结构数据
  @isFirstLayer 是否为第一层
*/
const handleGetTreePath = function (nodes = [], tree = [], isFirstLayer = true) {
    let toString = Object.prototype.toString;
    if (toString.call(nodes) !== '[object Array]') {
      throw new Error('handleGetTreePath: 节点数据必须为数组');
    }
    if (toString.call(tree) !== '[object Array]') {
      throw new Error('handleGetTreePath: 树形结构必须为数组');
    }
    // 如果是第一层,则初始化path
    tree = isFirstLayer
           ? tree.map(item => ({ ...item, path: [item.id] }))
           : tree;
           
    let currentTreeMap = {};
    let nextTree = []; // 下一层要循环的数据
    let path = []; // 找到的节点路径
    let notFindNodes = []; // 未找到的节点

    tree.forEach(item => {
      // 当前层数据
      currentTreeMap[item.id] = item;
      // 下一层数据
      nextTree = nextTree.concat(item.children === void 0 ? [] : item.children.map(it => {
        let pPath = JSON.parse(JSON.stringify(item.path));
        pPath.push(it.id);
        return {
          ...it,
          path: pPath
        }
      }));
    });

    // 区分找到的节点和未找到的节点数据
    nodes.forEach(id => {
      currentTreeMap[id] !== void 0
        ? path.push(currentTreeMap[id].path) : notFindNodes.push(id);
    });

    if (notFindNodes.length > 0 && nextTree.length > 0) {
      let findPath = handleGetTreePath(notFindNodes, nextTree, false);
      path = path.concat(findPath);
    }

    return path;
 }

五、测试结果

5.1 生成树形结构(以上面的数据为例)

image.png

5.2 节点路径查找 (以上面生成数据为例)

image.png
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容