手撸一个“组织架构图”

468 阅读3分钟

吹水群友说遇到个面试题,1个小时内撸一个组织架构图。

开始没啥思路,毕竟这东西以前都是找个库套一下就好了。不过几个小时候后群水友撸了个vue版本的。于是乎觉得也可以试试。

自己撸完最终效果:ge2iss.csb.app/

GIF 2022-3-20 14-01-33.gif

发现最难的还是样式的😥 其中有些觉得可以用选择器就能完成的工作,还是用js代替了。那些层级选择器尝试了一下发现没办法搞定。

横竖排版是用样式实现的,主要就是切换连接线条的一些位置。 所有并不需要脚本处理。

主要流程

生成节点信息

生成一个完整的节点作为html模板,包含节点:

  • 节点连接线
  • 节点内容
  • 节点展开/收起操作
  • 上下连接线
<div id="node-demo" style="display: none;">
    <div class="node">
        <div class="top-horizontal-middle-line"></div>
    <div class="node-text">
        <span class="text"></span>
        <div class="top-line"></div>
        <div class="operation-box">
            <div class="bottom-line"></div>
            <div class="operation">+</div>
        </div>
    </div>
    <div class="node-Child"></div>
    </div>
</div>

读取节点并且创建节点

主要是根据传进来的节点数据源 来确定是否生成连接线 。

(里面的这些判断开始像用样式选择器实现,但是没有搞定。)

function caretHtml(operation) {
  let nodeDiv = document.getElementById('node-demo').cloneNode(true).querySelector('.node')
  nodeDiv.querySelector('.text').innerText = operation.name
  // 最末端节点 目前目前css:has 还无法使用所以这个判断还在
  if (operation.hasChild) { 
      nodeDiv.querySelector('.operation-box').className = 'hide'
  }
  return nodeDiv
}

创建所有节点插入到页面中

主要就是用一个递归每次往 <div class='childNode'></div> 中插入子节点。最后把整个大节点放入html中。

递归中根据数据源生成一些节点位置,提供给caretHtml()方法来生成需要生成哪种类型的连接线。

 function caretNode(data, parentNode, childNode) {
    if (!parentNode) {
      parentNode = caretHtml({ name: data.name, hasChild: data.children ? false : true,})
      childNode = parentNode
      html = parentNode
    }
    else { parentNode.append(childNode) }
    if (data.children) {
      data.children.forEach((item, index, arr) => {
        let hasChild = false
        let position = '';
        if (!item.children) {hasChild = true}
        caretNode(item, childNode?.querySelector('.node-Child'), caretHtml({name: item.name,hasChild: hasChild,child: childNode,}))
      });
    }
    else {
      document.getElementById('node').append(html)
      return
    }
  }

脚本部分基本上就完成了,其他的就是一些节点点击展开/收起 的小操作。

样式部分

这部分可能是最麻烦的。完成了以下几个选择器

  1. 选择根节点,让根节点的 上连接线不可见。 这个需要从容器往下一层层选。才能选到仅匹配到第一个。 这个难度6分。
#node>.node>.node-text>.top-line {
      display: none;
    }
  1. 选择最左,和最有子节点,让横向的连接线偏移50%避免出头。这个之前用js动态添加className 来实现的。现在用样式选择器直接实现,难度得有8分。

image.png

找到.node-Child, 他下面得第一个(最后一个)元素必须=.node的标签紧接着的.top-horizontal-middle-line,

最后一个元素不显示的原因是,如果只有一个子节点,那么他是不应该显示横向连接线的。只有一个子节点,第一个也是最后一个可以匹配到这个不显示。

/* 最左元素连接线 */
#node .node-Child :first-of-type.node>.top-horizontal-middle-line {
  background-color: #f00;
  left: 50%;
}

/* 处理最右边元素 不显示 */
#node .node-Child :last-of-type.node>.top-horizontal-middle-line {
  display: none;
}

最后一个连接线直接不显示。不做偏移。之后用倒数第二个线条做延长处理。

  1. 因为最后一个节点的横连接线隐藏了,把倒数第二个元素延长50%
#node .node-Child :nth-last-of-type(2).node>.top-horizontal-middle-line {
  background-color: #00f;
  width: 150%;
  left: 0;
}
  1. 如果只有2个节点, 第一个节点 偏移了50%,倒数第二个节点=第一个节点把偏移又设置未0了。所以再2个元素的情况下。 连接线长度只需要100%,偏移量还是50%。 这个不太好理解,但是实际做的时候就很容易明白。
#node .node-Child :nth-of-type(1).node>.top-horizontal-middle-line {
  background-color: rgb(255, 0, 149);
  width: 100%;
  left: 50%;
}
  1. 横竖切换。 切换主要就是把样式重新复制一套,让把把线条竖着显示,主要就是把宽度和高度互相转换,然后左右上下定位置互相转换。然后把外层改成flex布局就好了。这个就没啥难度。