最近遇到开发组织架构的需求,与以往开发的组织架构不同,不光要展示人名,还要显示职务(或者子公司名称)、对应的头像等,并且要考虑,如果用户未上传头像,需使用默认头像(男、女、中性),(⊙o⊙)…要尊重尊重,不能随便喊那啥...,还要考虑子公司或者不同部门之间的员工借调问题,现简化效果图如下:
最终实现效果图如下:
纵向(默认展开前3级):
横向(默认展开前3级,因为截图无法全屏的问题,部分第3级未展开):
废话不多说,说方法:采用vue2-org-tree组件来实现效果。
npm
1 # use npm
2 npm i vue2-org-tree
3 # use yarn
4 yarn add vue2-org-tree
安装 loader
npm install --save-dev less less-loader(不安装less-loader基本上都会报错)
Import Plugins(main.js引入) (CDN方式请自行测试)
import Vue2OrgTree
from
'vue2-org-tree'
Vue.use(Vue2OrgTree)
由于测试过程当中,发生的版本兼容问题(加载css样式丢失),故将CSS样式统一放到样式文件中引入,样式文件如下:
1 @colors:#1FAAEB; 2 .org-tree-container { 3 display: inline-block; 4 padding: 15px; 5 background-color: #fff; 6 } 7 8 .org-tree { 9 // display: inline-block; 10 display: table; 11 text-align: center; 12 13 &:before, &:after { 14 content: ''; 15 display: table; 16 } 17 18 &:after { 19 clear: both; 20 } 21 } 22 23 .org-tree-node, 24 .org-tree-node-children { 25 position: relative; 26 margin: 0; 27 padding: 0; 28 list-style-type: none; 29 30 &:before, &:after { 31 transition: all .35s; 32 } 33 } 34 .org-tree-node-label { 35 position: relative; 36 display: inline-block; 37 38 .org-tree-node-label-inner { 39 padding: 10px 15px; 40 text-align: center; 41 border-radius: 3px; 42 box-shadow: 0 1px 5px rgba(0, 0, 0, .15); 43 } 44 } 45 .org-tree-node-btn { 46 position: absolute; 47 top: 100%; 48 left: 50%; 49 width: 20px; 50 height: 20px; 51 z-index: 10; 52 margin-left: -11px; 53 margin-top: 9px; 54 background-color: #fff; 55 border: 1px solid @colors; 56 border-radius: 50%; 57 box-shadow: 0 0 2px rgba(0, 0, 0, .15); 58 cursor: pointer; 59 transition: all .35s ease; 60 61 &:hover { 62 background-color: #e7e8e9; 63 transform: scale(1.15); 64 } 65 66 &:before, &:after { 67 content: ''; 68 position: absolute; 69 } 70 71 &:before { 72 top: 50%; 73 left: 4px; 74 right: 4px; 75 height: 0; 76 border-top: 1px solid @colors; 77 } 78 79 &:after { 80 top: 4px; 81 left: 50%; 82 bottom: 4px; 83 width: 0; 84 border-left: 1px solid @colors; 85 } 86 87 &.expanded:after { 88 border: none; 89 } 90 } 91 .org-tree-node { 92 padding-top: 20px; 93 display: table-cell; 94 vertical-align: top; 95 96 &.is-leaf, &.collapsed { 97 padding-left: 10px; 98 padding-right: 10px; 99 } 100 101 &:before, &:after { 102 content: ''; 103 position: absolute; 104 top: 0; 105 left: 0; 106 width: 50%; 107 height: 19px; 108 } 109 110 &:after { 111 left: 50%; 112 border-left: 1px solid @colors; 113 } 114 115 &:not(:first-child):before, 116 &:not(:last-child):after { 117 border-top: 1px solid @colors; 118 } 119 120 } 121 .collapsable .org-tree-node.collapsed { 122 padding-bottom: 30px; 123 124 .org-tree-node-label:after { 125 content: ''; 126 position: absolute; 127 top: 100%; 128 left: 0; 129 width: 50%; 130 height: 20px; 131 border-right: 1px solid @colors; 132 } 133 } 134 .org-tree > .org-tree-node { 135 padding-top: 0; 136 137 &:after { 138 border-left: 0; 139 } 140 } 141 .org-tree-node-children { 142 padding-top: 20px; 143 display: table; 144 145 &:before { 146 content: ''; 147 position: absolute; 148 top: 0; 149 left: 0; 150 width: 50%; 151 height: 20px; 152 border-right: 1px solid @colors; 153 border-left: none; 154 } 155 156 &:after { 157 content: ''; 158 display: table; 159 clear: both; 160 } 161 } 162 163 .horizontal { 164 .org-tree-node { 165 // display: flex; 166 // flex-direction: row; 167 // justify-content: flex-start; 168 // align-items: center; 169 display: table-cell; 170 float: none; 171 padding-top: 0; 172 padding-left: 20px; 173 174 &.is-leaf, &.collapsed { 175 padding-top: 10px; 176 padding-bottom: 10px; 177 } 178 179 &:before, &:after { 180 width: 19px; 181 height: 50%; 182 } 183 184 &:after { 185 top: 50%; 186 left: 0; 187 border-left: 0; 188 } 189 190 &:only-child:before { 191 top: 1px; 192 border-bottom: 1px solid @colors; 193 } 194 195 &:not(:first-child):before, 196 &:not(:last-child):after { 197 border-top: 0; 198 border-left: 1px solid @colors; 199 } 200 201 &:not(:only-child):after { 202 border-top: 1px solid @colors; 203 } 204 205 .org-tree-node-inner { 206 display: table; 207 } 208 209 } 210 211 .org-tree-node-label { 212 display: table-cell; 213 vertical-align: middle; 214 } 215 216 &.collapsable .org-tree-node.collapsed { 217 padding-right: 30px; 218 219 .org-tree-node-label:after { 220 top: 0; 221 left: 100%; 222 width: 20px; 223 height: 50%; 224 border-right: 0; 225 border-bottom: 1px solid @colors; 226 } 227 } 228 229 .org-tree-node-btn { 230 top: 50%; 231 left: 100%; 232 margin-top: -11px; 233 margin-left: 9px; 234 } 235 236 & > .org-tree-node:only-child:before { 237 border-bottom: 0; 238 } 239 240 .org-tree-node-children { 241 // display: flex; 242 // flex-direction: column; 243 // justify-content: center; 244 // align-items: flex-start; 245 display: table-cell; 246 padding-top: 0; 247 padding-left: 20px; 248 249 &:before { 250 top: 50%; 251 left: 0; 252 width: 20px; 253 height: 0; 254 border-left: 0; 255 border-top: 1px solid @colors; 256 } 257 258 &:after { 259 display: none; 260 } 261 262 & > .org-tree-node { 263 display: block; 264 } 265 } 266 }
组件中HTML代码如下:
1 <template> 2 <div class="org-boxs"> 3 <vue2-org-tree 4 name="test" 5 :data="datas" 6 :horizontal="horizontal" 7 :label-class-name="labelClassName" 8 :render-content="renderContent" 9 collapsable 10 @on-expand="onExpand" 11 @on-node-click="onNodeClick" 12 /> 13 </div> 14 </template>
data中定义如下:
1 data () { 2 return { 3 horizontal: false, 4 collapsable: false, 5 expandAll: true, 6 labelClassName: "bg-none", 7 datas:{ 8 id:'1', 9 label:'老张伟', 10 position:'董事长', 11 img:'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4007973853,1345044449&fm=15&gp=0.jpg', 12 relations:'1', 13 children:[ 14 { 15 id:'1-1', 16 label:'大张嘎', 17 position:'总经理', 18 img:'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=629230260,2582796696&fm=26&gp=0.jpg', 19 relations:'1-1', 20 children:[ 21 { 22 id:'1-1-1', 23 label:'小张嘎', 24 position:'策划部', 25 img:'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2495406741,1368429183&fm=26&gp=0.jpg', 26 relations:'1-1', 27 }, 28 { 29 id:'1-1-2', 30 label:'中张嘎', 31 position:'规划部', 32 img:'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=4125471676,2511464594&fm=26&gp=0.jpg', 33 relations:'1-1', 34 }, 35 ] 36 }, 37 { 38 id:'1-2', 39 label:'大刘彪', 40 position:'秘书长', 41 img:'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=291861640,4252957999&fm=26&gp=0.jpg', 42 relations:'1-2', 43 children:[ 44 { 45 id:'1-2-1', 46 label:'中刘彪', 47 position:'人事部', 48 img:'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4122062364,793453037&fm=26&gp=0.jpg', 49 relations:'1-2', 50 }, 51 { 52 id:'1-2-2', 53 label:'小刘彪', 54 position:'行政部', 55 img:'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1018449383,841488959&fm=15&gp=0.jpg', 56 relations:'1-2', 57 }, 58 ] 59 }, 60 { 61 id:'1-3', 62 label:'大美女', 63 position:'财务总监', 64 img:'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=2745812195,432411379&fm=26&gp=0.jpg', 65 relations:'1-3', 66 children:[ 67 { 68 id:'1-3-1', 69 label:'中美女', 70 position:'财务部', 71 img:'', 72 relations:'1-3', 73 sex:2, 74 children:[ 75 { 76 id:'1-3-1-1', 77 label:'小美女', 78 position:'财务科', 79 img:'', 80 sex:0, 81 relations:'1-3', 82 }, 83 ] 84 }, 85 ] 86 }, 87 { 88 id:'1-4', 89 label:'大霸王', 90 position:'技术总监', 91 img:'', 92 sex:1, 93 relations:'1-4', 94 children:[ 95 { 96 id:'1-4-1', 97 label:'小霸王', 98 position:'技术部', 99 img:'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=481633131,466344723&fm=26&gp=0.jpg', 100 relations:'1-4', 101 }, 102 { 103 id:'1-4-2', 104 label:'中霸王', 105 position:'研发部', 106 img:'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1490853638,1893546593&fm=26&gp=0.jpg', 107 relations:'1-4', 108 }, 109 ] 110 }, 111 { 112 id:'1-5', 113 label:'大赵帅', 114 position:'运营总监', 115 img:'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2010835987,458488842&fm=15&gp=0.jpg', 116 relations:'1-5', 117 children:[ 118 { 119 id:'1-5-1', 120 label:'小赵帅', 121 position:'市场部', 122 img:'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3576627708,888952220&fm=26&gp=0.jpg', 123 relations:'1-5', 124 }, 125 { 126 id:'1-5-2', 127 label:'中赵帅', 128 position:'销售部', 129 img:'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3102026869,1790261072&fm=26&gp=0.jpg', 130 relations:'1-3', 131 }, 132 { 133 id:'1-5-3', 134 label:'老赵帅', 135 position:'执行部', 136 img:'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3444708870,1488183897&fm=26&gp=0.jpg', 137 relations:'1-5', 138 }, 139 ] 140 }, 141 ], 142 }, 143 manUrl:require('@/assets/man.jpeg'), 144 womanUrl:require('@/assets/woman.jpg'), 145 shemaleUrl:require('@/assets/shemale.jpg'), 146 } 147 },
manUrl、womanUrl、shemaleUrl为对应的男、女、中性默认头像,datas中的img为用户上传到服务器的URL,relations默认为各个公司的id(可根据实际情况自行设置),用来判断该人员劳动关系属于哪个公司/部门(尤其是子公司之间的员工借调问题,或者政府部门市县局间的人员借调问题)。
组件自带方法如下:
1 onExpand(e, data) { 2 if ("expand" in data) { 3 data.expand = !data.expand; 4 if (!data.expand && data.children) { 5 this.collapse(data.children); 6 } 7 } else { 8 this.$set(data, "expand", true); 9 } 10 }, 11 //点击选项执行的方法,可以用于跳转到其他链接,注意一定要写协议头 12 onNodeClick(e, data) { 13 //console.log(data.label); 14 if(data.url==null){ 15 return false 16 }else{ 17 window.open(data.url) 18 } 19 }, 20 collapse(list) { 21 var _this = this; 22 list.forEach(function(child) { 23 if (child.expand) { 24 child.expand = false; 25 } 26 child.children && _this.collapse(child.children); 27 }); 28 }, 29 expandChange() { 30 this.toggleExpand(this.data, this.expandAll); 31 }, 32 toggleExpand(data, val) { 33 var _this = this; 34 if (Array.isArray(data)) { 35 data.forEach(function(item) { 36 _this.$set(item, "expand", val); 37 if (item.children) { 38 _this.toggleExpand(item.children, val); 39 } 40 }); 41 } else { 42 this.$set(data, "expand", val); 43 if (data.children) { 44 _this.toggleExpand(data.children, val); 45 } 46 } 47 }
组件加载时的初始化方法如下(默认展开3级):
1 initOrg(){ 2 this.$set(this.datas,'expand',true); 3 if(this.datas.children){ 4 this.datas.children.forEach((item,index)=>{ 5 this.$set(item,'expand',true); 6 }) 7 } 8 },
重点来了,每个节点内容的渲染方法如下:
1 renderContent(h, data) { 2 return ( 3 <span style="100%;height:100%;display:block;padding:10px 15px;border-radius:3px;" class={data.relations === '1-1'?'bg-tomato':(data.relations === '1-2'?'bg-gold':(data.relations === '1-3'?'bg-gray':(data.relations === '1-4'?'bg-lightpink':(data.relations === '1-5'?'bg-blue':'bg-green'))))}> 4 <dd style="height:6vh;border-radius:50%;padding:0;margin-bottom:1vh;"><img style="6vh;height:6vh;border-radius:50%;" src={data.img?data.img:(data.sex === 1?this.manUrl:(data.sex === 2?this.womanUrl:this.shemaleUrl))}/></dd> 5 <dd style="font-size:1.6vh;">{data.label}</dd> 6 <dd style="font-size:1vh;">{data.position}</dd> 7 </span> 8 ) 9 },
CSS样式如下:
1 <style lang="less"> 2 @import "~@assets/less/org-tree.less"; 3 .org-boxs{ 4 width:100%; 5 height:100%; 6 text-align: center; 7 /*background: #030C24;*/ 8 background-image: -webkit-radial-gradient(ellipse farthest-corner at center 40%, #000d4d 0%, #000105 100%); 9 background-image: radial-gradient(ellipse farthest-corner at center 40%, #000d4d 0%, #000105 100%); 10 overflow-y: scroll; 11 } 12 .org-tree-container{ 13 background:none!important; 14 } 15 .org-tree-node-label { 16 white-space: nowrap; 17 } 18 .bg-none{ 19 background-color:#030C24; 20 color:#ffffed; 21 } 22 .bg-white { 23 background-color: #ECF5FF; 24 } 25 .org-tree-node-label .org-tree-node-label-inner { 26 width:6vw; 27 padding: 0px 0px; 28 text-align: center; 29 border-radius: 3px; 30 box-shadow: 0 1px 5px rgba(0, 0, 0, 0.15); 31 /* border: 1px solid @colors;*/ 32 overflow: hidden; 33 box-sizing: border-box; 34 } 35 .bg-tomato { 36 background-color: #9E4A1C; 37 } 38 .bg-gold { 39 background-color: #ECA150; 40 } 41 .bg-gray { 42 background-color: #DECEAA; 43 } 44 .bg-lightpink { 45 background-color: lightpink; 46 } 47 .bg-blue { 48 background-color: #057D9F; 49 } 50 .bg-green { 51 background-color: #50CB90; 52 } 53 </style>
最终效果如文章开始效果图所示,renderContent方法中渲染节点背景颜色class的判断,有更好的方式的,请联系我,多谢!
PS:组件封装至此已基本完毕,转载请注明出处