公司项目有个功能要实现树形结构的展示,类似于组织架构的那种。

想了下几种方案,

  1.    表格内实现
  2.    div内通过绝对定位实现
  3.    canva实现

想了一下,感觉第一种简单一些, 用表格的话可能不需要计算繁琐的定位,top,left什么的,不过用表格的坏处就是可扩展性差了些

粗略一想,大概设计方案如下:

  •   后台组织好json格式数据返回前端,属性结构肯定有id和pid字段
  •   结构图前端js+css展示,内容用带边框的方块展示,关系用线条。

想玩就像立马码字实现,代码还没敲问题来了,树形结构后面的分叉是动态的,该怎么实现呢?

于是我在纸上尝试画出图形,从顶层开始画,第一层一个节点 T,第二层两个节点T1,T2。上下距离岔开点,我一想如果T1后面有3个节点,T2后面又有3个节点。在不知道第三次的情况下我之前T1,T2上下就留了一点位置,然后画第三层就会得到示例的图形: 

前端技术架构图 vue2 前端技术架构图怎么画_前端技术架构图 vue2

如果上图不算丑,那遇到第三层4个,第二个节点也有,则位置会被占用,那必然会出现更加不好看的效果:

前端技术架构图 vue2 前端技术架构图怎么画_css_02

第一张图如果我们事先知道是 1 2 6的结构,那我们肯定能画的好看一些,如下

前端技术架构图 vue2 前端技术架构图怎么画_css_03

这时候我意识到我这边除了之前考虑的那两点外,我可能还需要计算方块位置的算法,基于我们用table来实现位置的分布,方块位置需要如下展示:

前端技术架构图 vue2 前端技术架构图怎么画_结构图_04

 

那么如何得到图中的这个位置结构呢,首先知道这么画我们是知道后面节点的位置的,所以我决定从后往前推,T4,T5,T6是同一个父节点下的叶子节点,那T3的位置是与T5平齐的,于是得到下图:

前端技术架构图 vue2 前端技术架构图怎么画_css_05

如此可以得到T3位置,然后根据T2,T3得到T1的位置

当然这种子节点都在最后一层是理想结果,显示并不是所有情况都是上面那样,比如:

前端技术架构图 vue2 前端技术架构图怎么画_css_06

当然这只是简单的情况,还有各种更多层更加复杂的结构。

图中做了些改进,不同层之前间隔两列,一个用来父节点延申,一列用来分叉到子节点。另外便于画图,初步设计是两个子节点中间间隔一行,这样父节点位置刚好在一个表格的td中间。

现在来说核心的算法,不是太复杂,就是首先算好叶子节点的位置,比如图中的T3,T4,T5,T6,T8,T9。这些都可以通过遍历json数据来得到,通过递归,我们能得到前面所说几个节点的行和列的位置。

然后计算得到T2,T7位置,最后得出T1的位置。

得到方块内容的位置,我们就需要连线,连线的大体思路就是,td里放DIV,通过设置div的边框颜色和div的位置来绘制,小箭头么可以设置border得到三角形。

当然要实现并不是这么容易,需要各种折腾写样式,最后我是通过div然后写个before的伪类来实现线条,after伪类来实现箭头。

另外一般页面大小也就那么点大,为了减少点空间我去除了两个叶子节点中间的行,不过这也给父节点的定位造成小小的麻烦,比如T6,T7 这两个位置是在td中,而父节点则需要向上偏半个方块的高度。当然方块的高度是比td矮一些的,不然T6,T7就会碰一块了。

最后效果如下:

前端技术架构图 vue2 前端技术架构图怎么画_子节点_07

js代码如下:

前端技术架构图 vue2 前端技术架构图怎么画_js_08

前端技术架构图 vue2 前端技术架构图怎么画_前端技术架构图 vue2_09

1 $.fn.arch = function (rid, list) {
  2     var $tb = $(this);
  3     var maxRow = 0; //读取叶子节点位置开始行
  4     var maxLevel = 0; //最大层级  
  5     readTree(list);
  6     initTable();
  7     drawTree();
  8     drawPath();
  9 
 10     function setLeaf(list) {
 11         for (var i = 0; i < list.length; i++) {
 12             var isleaf = 1;
 13             for (var j = 0; j < list.length; j++) {
 14                 if (list[i].id == list[j].pid) {
 15                     isleaf = 0;
 16                     break;
 17                 }
 18             }
 19             list[i].isleaf = isleaf;
 20         }
 21     }
 22 
 23     function readLeaf(list, pid, level) {
 24         maxLevel = Math.max(level, maxLevel);
 25         for (var i = 0; i < list.length; i++) {
 26             if (list[i].pid == pid) {
 27                 list[i].level = level;
 28                 if (!list[i].isleaf) {
 29                     readLeaf(list, list[i].id, level + 1);
 30                 } else {
 31                     list[i].r = maxRow;
 32                     list[i].offset = 0;
 33                     maxRow++;
 34                 }
 35             }
 36         }
 37     }
 38 
 39     function calcPosition() {
 40         var tmpList = [];
 41         for (var i = maxLevel; i > 0; i--) {
 42             for (var j = 0; j < list.length; j++) {
 43                 if (list[j].level == i && tmpList.indexOf(list[j].pid) < 0) {
 44                     var total = 0;
 45                     var count = 0;
 46                     var pindex = -1;
 47                     var isOffset = 0;
 48                     for (var k = 0; k < list.length; k++) {
 49                         if (list[k].pid == list[j].pid) {
 50                             isOffset = list[k].offset;
 51                             total += list[k].r;
 52                             count++;
 53                         } else if (list[k].id == list[j].pid) {
 54                             pindex = k;
 55                         }
 56                     }
 57                     var last = total % count;
 58 
 59                     if (pindex >= 0) {
 60                         list[pindex].r = count == 0 ? 0 : ((total - last) / count + ((last > 0) ? 1 : 0));
 61                         list[pindex].offset = last > 0 ? 1 : 0;
 62                         if (count == 1) {
 63                             list[pindex].offset = isOffset;
 64                         } else {
 65                             list[pindex].offset = last > 0 ? 1 : 0;
 66                         }
 67                     }
 68                     tmpList.push(list[j].pid);
 69                 }
 70             }
 71         }
 72     }
 73 
 74     function readTree(list) {
 75         setLeaf(list);
 76         readLeaf(list, rid, 1);
 77         calcPosition();
 78     }
 79 
 80     function drawTree() {
 81         for (var i = 0; i < list.length; i++) {
 82             var item = list[i];
 83             $tb.find("tr:eq(" + (item.r) + ")")
 84                 .find("td:eq(" + (item.level - 1) * 3 + ")>div")
 85                 .addClass("item-box" + (item.offset == 1 ? " offset" : ""))
 86                 .attr("data-pid", item.pid)
 87                 .attr("data-id", item.id)
 88                 .attr("data-offset", item.offset)
 89                 .append("<div class='item'>" + item.name + "</div>");
 90         }
 91     }
 92 
 93 
 94     function drawPath() {
 95         for (var i = 0; i < maxLevel; i++) {
 96             var targetColumn = (i - 1) * 3;
 97             var pNodes = $tb.find("tr").find("td:eq(" + targetColumn + ")>div[data-id]");
 98             for (var j = 0; j < pNodes.length; j++) {
 99                 var $pNode = $(pNodes[j]);
100                 var $pTd = $pNode.closest("td");
101                 var isPOffset = $pNode.is("[data-offset='1']");
102 
103                 var subNodes = $tb.find("tr").find("[data-pid='" + $pNode.attr("data-id") + "']");
104                 var length = subNodes.length;
105                 if (length == 0) {
106                     continue;
107                 }
108 
109                 var ptopCls = isPOffset ? "line-top" : "line-center";
110                 $pTd.next().children("div").addClass(ptopCls);
111 
112                 if (length == 1) {
113                     $(subNodes[0]).closest("td").prev().children("div").addClass(ptopCls + " arrow");
114                 } else if (length == 2) {
115                     var startIndex = $(subNodes[0]).closest("tr").index();
116                     var endIndex = $(subNodes[1]).closest("tr").index();
117 
118                     var firstOffset = $(subNodes[0]).is("[data-offset='1']");
119                     var lastOffset = $(subNodes[1]).is("[data-offset='1']");
120 
121                     if (endIndex - startIndex == 1) {
122                         if (firstOffset == lastOffset) {
123                             $(subNodes[0]).closest("td").prev().children("div").addClass("corner-top-bottom " + (firstOffset ? "" : "top-35"));
124                         } else if (firstOffset) {
125                             $(subNodes[0]).closest("td").prev().children("div").addClass("corner-top arrow");
126                             $(subNodes[1]).closest("td").prev().children("div").addClass("corner-bottom arrow top-35");
127                         }
128                     } else {
129                         $(subNodes[0]).closest("td").prev().children("div").addClass("corner-top arrow " + (firstOffset ? "" : "top-35"));
130                         !lastOffset && $(subNodes[1]).closest("td").prev().children("div").addClass("corner-bottom arrow top-35");
131 
132                         for (var m = startIndex + 1; m < endIndex; m++) {
133                             if (m == endIndex - 1 && lastOffset) {
134                                 $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")").children("div").addClass("corner-bottom arrow");
135                             } else {
136                                 $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")>div").addClass("line-left");
137                                 var currNodes = $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 3) + ")").children(".item-box");
138                                 if (currNodes.length > 0) {
139                                     var $currNode = $(currNodes[0]);
140                                     var currOffset = $currNode.is("[data-offset='1']");
141                                     var cls = currOffset ? "line-top arrow" : "line-center arrow";
142                                     $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")").children("div").addClass(cls);
143                                 }
144                             }
145                         }
146                     }
147                 } else {
148                     var $firstNode = $(subNodes[0]);
149                     var $lastNode = $(subNodes[length - 1]);
150 
151                     var firstOffset = $firstNode.is("[data-offset='1']");
152                     $firstNode.closest("td").prev().children("div").addClass("corner-top arrow " + (firstOffset ? "" : "top-35"));
153 
154                     var lastOffset = $lastNode.is("[data-offset='1']");
155                     !lastOffset && $(subNodes[length - 1]).closest("td").prev().children("div").addClass("corner-bottom arrow top-35");
156 
157                     var startIndex = $firstNode.closest("tr").index();
158                     var endIndex = $lastNode.closest("tr").index();
159                     for (var m = startIndex + 1; m < endIndex; m++) {
160                         if (m == endIndex - 1 && lastOffset) {
161                             $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")").children("div").addClass("corner-bottom arrow");
162                         } else {
163                             $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")>div").addClass("line-left");
164                             var currNodes = $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 3) + ")").children(".item-box");
165                             if (currNodes.length > 0) {
166                                 var $currNode = $(currNodes[0]);
167                                 var currOffset = $currNode.is("[data-offset='1']");
168                                 var cls = currOffset ? "line-top arrow" : "line-center arrow";
169                                 $tb.find("tr:eq(" + m + ")").find("td:eq(" + (targetColumn + 2) + ")").children("div").addClass(cls);
170                             }
171                         }
172                     }
173                 }
174             }
175         }
176     }
177 
178     function initTable() {
179         var rowCount = maxRow;
180         var columnCount = maxLevel * 3 - 2;
181         createTable(rowCount, columnCount);
182     }
183 
184     function createTable(r, c) {
185         for (var i = 0; i < r; i++) {
186             var $tr = $("<tr class='item-row'></tr>");
187             for (var j = 0; j < c; j++) {
188                 var last = j % 3;
189                 var cls = "";
190                 if (last == 0) {
191                     cls = "td-item";
192                 } else if (last == 1) {
193                     cls = "td-line";
194                 } else if (last == 2) {
195                     cls = "td-arrow";
196                 }
197                 $tr.append("<td class='" + cls + "'><div></div></td>");
198                 //$tr.append("<td " + (isitem ? "data-item='1'" : "width='50px'") + "><div class='" + (isitem ? "" : "line-item") + "'><div>" + (isitem ? "</div></div>" : "") + "</td>");
199             }
200             $tb.append($tr);
201         }
202     }
203 
204 }

View Code

 

css如下

前端技术架构图 vue2 前端技术架构图怎么画_js_08

前端技术架构图 vue2 前端技术架构图怎么画_前端技术架构图 vue2_09

1 .item-box {
  2     position: absolute;
  3     top: 3px;
  4     height: 100%;
  5 }
  6 
  7     .item-box.offset {
  8         top: -35px;
  9     }
 10 
 11     .item-box .item {
 12         background-color: #c1dcfc;
 13         border: 2px solid #4499D6;
 14         border-radius: 10px;
 15         width: auto;
 16         height: 100%;
 17     }
 18 
 19     .item-box .item {
 20         height: 70px;
 21     }
 22 
 23 .line-item {
 24 }
 25 
 26 .line-left {
 27     border-left: 1px solid #4499D6;
 28 }
 29 
 30 .line-center:before {
 31     content: ' ';
 32     display: inline-block;
 33     width: 100%;
 34     border-bottom: 1px solid #4499D6;
 35     position: absolute;
 36     top: 50%;
 37 }
 38 
 39 .line-center.arrow:after {
 40     top: 36px;
 41 }
 42 
 43 .line-top:before {
 44     content: ' ';
 45     display: inline-block;
 46     width: 100%;
 47     border-bottom: 1px solid #4499D6;
 48     position: absolute;
 49 }
 50 
 51 .line-top.arrow:after {
 52     top: -5px;
 53 }
 54 
 55 .corner-top:before {
 56     content: ' ';
 57     width: 100%;
 58     height: 100%;
 59     border-top: 1px solid #4499D6;
 60     border-left: 1px solid #4499D6;
 61     border-top-left-radius: 10px;
 62     display: inline-block;
 63     position: absolute;
 64 }
 65 
 66 .corner-top.top-35:before {
 67     top: 40px;
 68 }
 69 
 70 .corner-top.arrow:after {
 71     top: -5px;
 72 }
 73 
 74 .corner-top.arrow.top-35:after {
 75     top: 35px;
 76 }
 77 
 78 
 79 
 80 .corner-bottom:before {
 81     content: ' ';
 82     width: 100%;
 83     height: 100%;
 84     border-bottom: 1px solid #4499D6;
 85     border-left: 1px solid #4499D6;
 86     border-bottom-left-radius: 10px;
 87     display: inline-block;
 88     position: absolute;
 89 }
 90 
 91 .corner-bottom.top-35:before {
 92     top: -40px;
 93 }
 94 
 95 .corner-bottom.arrow:after {
 96     bottom: -5px;
 97 }
 98 
 99 .corner-bottom.arrow.top-35:after {
100     bottom: 35px;
101 }
102 
103 .corner-top-bottom {
104     border-top: 1px solid #4499D6;
105     border-left: 1px solid #4499D6;
106     border-bottom: 1px solid #4499D6;
107     border-top-left-radius: 10px;
108     border-bottom-left-radius: 10px;
109     position: relative;
110 }
111 
112     .corner-top-bottom.top-35 {
113         top: 40px;
114     }
115 
116     .corner-top-bottom::before {
117         content: " ";
118         width: 0px;
119         height: 0px;
120         border-left: 10px;
121         border-right: 0px;
122         border-top: 5px;
123         border-bottom: 5px;
124         border-style: solid;
125         border-color: transparent transparent transparent #4499D6;
126         position: absolute;
127         right: -1px;
128         bottom: -5px;
129     }
130 
131     .arrow::after, .corner-top-bottom::after {
132         content: " ";
133         width: 0px;
134         height: 0px;
135         border-left: 10px;
136         border-right: 0px;
137         border-top: 5px;
138         border-bottom: 5px;
139         border-style: solid;
140         border-color: transparent transparent transparent #4499D6;
141         position: absolute;
142         right: -1px;
143     }
144 
145     .corner-top-bottom::after {
146         top: -5px;
147     }
148 
149 
150 .archtable {
151     border-spacing: 0px;
152 }
153 
154 
155     .archtable td {
156         position: relative;
157     }
158 
159         .archtable td > div { 
160             width:100%;
161             height: 100%;
162         }
163 
164     .archtable tr.item-row td {
165         height: 80px;
166     }
167 
168     .archtable tr:first-child,.archtable tr:last-child {
169         height: 40px;
170     }
171 
172     .archtable td.td-item {
173         min-width: 140px;
174     }
175 
176     .archtable td.td-line {
177         min-width: 30px;
178     }
179 
180     .archtable td.td-arrow {
181         min-width: 50px;
182     }

View Code

js主要用到jquery库,css js 都写的比较毛糙,基础没打好呀,欢迎大神指正~

PS:写个博客好费时间,真是佩服那些大神写一系列的文章,给你们点赞,写了两小时我快吐了~