吹水群友说遇到个面试题,1个小时内撸一个组织架构图。
开始没啥思路,毕竟这东西以前都是找个库套一下就好了。不过几个小时候后群水友撸了个vue版本的。于是乎觉得也可以试试。
自己撸完最终效果:ge2iss.csb.app/
发现最难的还是样式的😥 其中有些觉得可以用选择器就能完成的工作,还是用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
}
}
脚本部分基本上就完成了,其他的就是一些节点点击展开/收起 的小操作。
样式部分
这部分可能是最麻烦的。完成了以下几个选择器
- 选择根节点,让根节点的 上连接线不可见。 这个需要从容器往下一层层选。才能选到仅匹配到第一个。 这个难度6分。
#node>.node>.node-text>.top-line {
display: none;
}
- 选择最左,和最有子节点,让横向的连接线偏移50%避免出头。这个之前用js动态添加className 来实现的。现在用样式选择器直接实现,难度得有8分。
找到.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;
}
最后一个连接线直接不显示。不做偏移。之后用倒数第二个线条做延长处理。
- 因为最后一个节点的横连接线隐藏了,把倒数第二个元素延长50%
#node .node-Child :nth-last-of-type(2).node>.top-horizontal-middle-line {
background-color: #00f;
width: 150%;
left: 0;
}
- 如果只有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%;
}
- 横竖切换。 切换主要就是把样式重新复制一套,让把把线条竖着显示,主要就是把宽度和高度互相转换,然后左右上下定位置互相转换。然后把外层改成flex布局就好了。这个就没啥难度。