使用Echarts的tree图完成一个组织架构图

制作组织结构图我所知道的两个实现方法:echarts的treeantv/X6的组织架构图

两者实现组织结构图的优缺点:

优点 缺点
echarts 从外观上来看,更加贴合"图"的形态,配置灵活 配置起来比较麻烦,节点太多或导致节点重叠,需要开发人员自行通过计算调整画布大小进行适配
antv/X6 不会因为节点的多少导致布局混乱,配置简单

Echarts实现组织结构图

树图自带有节点的展开与折叠,重点需要修改的配置是节点的配置,要实现节点的自定义样式调整可以通过【rich | formatter】两个配置修改。

rich:调整节点内富文本样式

formatter:富文本内容【可以写成函数形式,通过可选参数获取当前单个节点的数据信息(item.data:可以取到所配置的数据信息),改配置项最终返回的数据形式为:字符串】

效果展示:


数据转换说明

字段名 说明
id 唯一标识每个部门或员工的ID。
name 部门或员工的名称。
avatar 员工的头像文件名称(部门通常没有头像,可以用空值或特定标识表示)。
position 员工的职位(对于部门,可以用空值或特定标识表示)。
parentId 父级ID,表示该部门或员工的上级部门(-1表示没有上级,通常用于公司层级)。
levelType 表示级别类型(0表示公司,1表示一级部门,2表示二级部门,-1表示员工)。

option 配置

javascript 复制代码
/**
 * 初始化树图的配置选项
 * @param {string} Direction - 树图的方向,默认为"LR"(水平)
 * @returns {object} 返回树图的配置对象
 */
const initOption = (Direction = "LR") => {
  console.log(Direction);
  const option = {
    series: [
      {
        type: "tree",
        id: 0,
        name: "tree1",
        data: treeData, // 数据
        top: "5%",
        left: "5%",
        edgeShape: "polyline",
        edgeForkPosition: "20%",
        orient: Direction || direction.current, // 树图方向
        initialTreeDepth: 3,
        lineStyle: {
          width: 2,
          height: 5
        },
        label: {
          backgroundColor: "#fff",
          position: "center",
          align: "center",
          verticalAlign: "middle",
          width: 100,
          fontSize: 5,
          borderRadius: 5,
          padding: 10,
          lineHeight: 20,
          shadowBlur: 10, // 阴影模糊半径
          shadowColor: "rgba(108, 108, 108, 0.23)", // 阴影颜色
          formatter: function(item) {
            const { name, position, id, levelType } = item.data;
            if (!name) return;
            if (levelType === "-1") {
              return [
                `{gap|}`, // 间隙
                `{image_${id}|}`, // 员工头像
                `{gap|}`, // 间隙
                `{presen_img|} {name|${name}}`,
                `{position|${position ? position : "未知"}}`
              ].join("\n");
            }
            return `{branch|${name}}`;
          },
          // 节点样式配置
          rich: {
            // 行距
            gap: {
              lineHeight: 9
            },
            // 部门节点样式
            branch: {
              // 部门样式
              fontSize: 12,
              fontWeight: "bold",
              display: "flex",
              justifyContent: "center",
              alignItems: "center"
            },
            // 员工姓名前的图标
            presen_img: {
              backgroundColor: {
                image: persenImg
              },
              height: 16
            },
            // 员工名称
            name: {
              fontSize: 12,
              fontWeight: "bold",
              color: "rgb(53, 53, 53)"
            },
            // 员工岗位
            position: {
              fontSize: 12,
              color: "rgb(53, 53, 53)"
            },
            // 通过遍历原数据生成每个员工的头像配置
            ...richData
          },
          animation: false
        }
      }
    ]
  };
  return option;
};

将该类型数据转换为options data 数据

ini 复制代码
/**
 * 将数据转换为树形结构
 * @param {Array<Object>} data 数据库中存储的数据结构
 * @param {boolean} isFold 是否折叠
 * @returns {Array<Object>} 返回 option.series[0].data 树形结构的数据。
 */
export const buildTree = (data, isFold) => {
  // 创建id到节点的映射
  const map = new Map();
  // 初始化所有节点并添加children属性
  data.forEach(item => {
    map.set(item.id, {
      ...item,

      children: []
    });
  });

  // 构建树结构
  const tree = [];
  data.forEach(item => {
    const node = map.get(item.id);
    if (item.parentId === "-1") {
      // 根节点
      tree.push(node);
    } else {
      // 子节点
      const parent = map.get(item.parentId);
      if (parent) {
        parent.children.push(node);
      }
    }
  });

  // 将树结构转换为testData格式
  const convertFormat = node => {
    return {
      id: node?.id,
      name: node?.name,
      avatar: node?.avatar || "", // 部门 无头像
      position: node?.position || "", // 部门 无职位
      levelType: node?.levelType,
      collapsed:
        typeof isFold === "boolean"
          ? isFold
          : node?.levelType >= "1"
          ? true
          : false, // 如果没有配置折叠 1 级以上部分进行折叠,否则按照配置的是否全部折叠执行
      children:
        node?.children.length > 0
          ? node?.children.map(child => convertFormat(child))
          : undefined,
      // 根据原始数据补充其他字段
      ...(node?.children.length === 0 && {
        // 员工节点
        id: node?.id,
        position: node?.position,
        avatar: node?.avatar || defaultAvatar
      })
    };
  };

  // 从根节点开始转换(假设根节点是id为0的节点)
  return tree.map(rootNode => convertFormat(rootNode));
};

用户头像配置

ini 复制代码
export const avatarRich = async data => {
  const rich = {};
  for (const item of data) {
    if (item.levelType !== "-1") continue;
    const img = await getImgData(item?.avatar);
    rich[`image_${item?.id}`] = {
      backgroundColor: {
        image: img ? img : item?.avatar
      },
      width: 45,
      height: 45
    };
  }
  return rich;
};

// 将头像转换为圆形
export const getImgData = imgSrc => {
  var fun = function(resolve) {
    const canvas = document.createElement("canvas");
    const contex = canvas.getContext("2d");
    const img = new Image();
    img.crossOrigin = "";
    console.log("contex", contex);
    if (!contex) return;
    img.onload = function() {
      let center = {
        x: img.width / 2,
        y: img.height / 2
      };
      var diameter = img.width;
      canvas.width = diameter;
      canvas.height = diameter;
      contex.clearRect(0, 0, diameter, diameter);
      contex.save();
      contex.beginPath();
      let radius = img.width / 2;
      contex.arc(radius, radius, radius, 0, 2 * Math.PI); //画出圆
      contex.clip(); //裁剪上面的圆形
      contex.drawImage(
        img,
        center.x - radius,
        center.y - radius,
        diameter,
        diameter,
        0,
        0,
        diameter,
        diameter
      ); // 在刚刚裁剪的园上画图
      contex.restore(); // 还原状态
      resolve(canvas.toDataURL("image/png", 1));
    };
    img.src = imgSrc || defaultAvatar;
    // console.log('img.src', img.src);
  };

  var promise = new Promise(fun);

  return promise;
};

当前已展开的层级(用于修改画布大小,避免节点层叠问题)

scss 复制代码
export function traverseTree(node, level, result, collapsedNodes) {
  // 确保结果数组有足够的层级
  if (!result[level]) {
    result[level] = 0;
  }

  // 计算当前层级的节点数
  result[level]++;

  // 如果节点没有展开,存储到 collapsedNodes 数组
  if (node.collapsed) {
    if (!collapsedNodes[level]) {
      collapsedNodes[level] = [];
    }
    collapsedNodes[level].push(node);
  }

  // 如果节点展开,递归处理子节点
  if (!node.collapsed && node.children) {
    for (let child of node.children) {
      traverseTree(child, level + 1, result, collapsedNodes);
    }
  }
}
export function calculateTreeStructure(treeData) {
  let result = []; // 存储每层的展开节点数
  let collapsedNodes = []; // 存储每层的未展开节点

  for (let node of treeData) {
    traverseTree(node, 0, result, collapsedNodes);
  }

  return { result, collapsedNodes };
}

修改制定节点的指

javascript 复制代码
/**
 * 遍历树形结构,查找匹配的 id 并修改该节点的值
 * @param { object } node 当前节点
 * @param { string } targetId 目标节点的 id
 * @param { any } newValue 要更新的值
 * @param { string } key 要更新的键
 * @returns {boolean} 返回 true 如果成功修改,否则返回 false
 */
export const modifyNodeById = (node, targetId, newValue, key) => {
  // 如果当前节点的 id 匹配目标 id,修改该节点的值
  if (node.id === targetId) {
    node[key] = newValue;
    return true; // 找到并修改,返回 true 表示修改成功
  }

  // 如果当前节点有子节点,递归查找
  if (node.children) {
    for (let child of node.children) {
      if (modifyNodeById(child, targetId, newValue, key)) {
        return true; // 子节点修改成功,直接返回
      }
    }
  }

  return false; // 未找到匹配的 id
}

组织架构图.js

treeFun.js

相关推荐
leafnote22 天前
【antd】Switch,0和1,怎么办?
前端·ant design
i建模1 个月前
Tauri+React+Ant Design跨平台开发环境搭建指南
前端框架·react·tauri·ant design·跨平台开发
冷环渊3 个月前
React快速上手到项目实战总篇
前端·javascript·react.js·前端框架·ant design·antd
飞翔的渴望3 个月前
antd3升级antd5总结
前端·react.js·ant design
袋鼠云数栈UED团队6 个月前
浅谈数栈产品里的 Descriptions 组件
前端·react.js·ant design
自白6 个月前
关于我是如何二次开发了 antd-vue 的a-range-picker组件,同时还添加了 vscod智能提示这件事
vue.js·visual studio code·ant design
袋鼠云数栈UED团队8 个月前
在 React 项目中 Editable Table 的实现
前端·react.js·ant design
孟宪磊mxl8 个月前
Element Plus& Ant Design(react) 表格的分页封装
vue.js·react.js·ant design·editplus
程序员也要学好英语8 个月前
搭建 react + antd 技术栈的测试框架
react.js·jest·ant design