工作中遇到的需求,记录一下,效果图如下:
贴上代码,直接复制就可以看到效果
// 组织架构
<template>
<div id='newTreeView'>
<div class="ntv-company">
<div class="ntvc-child">
<span>芒果财税一分公司</span>
<span>(699人)</span>
</div>
</div>
<div class="ntv-line1">
<div></div>
</div>
<div class="ntv-list" v-for='(el, inx) in departmentList' :key='el.id' :style='{"margin-top": inx === 0 ? "114px" : "0"}'> <!-- 循环显示每行 -->
<div :id='"ntvList" + inx'></div> <!-- 用来撑开每一行与左侧的距离 -->
<div class="ntv-item" v-for='(item, index) in el' :key='item.id' :style='{"flex": inx === 0 && el.length < 6 ? "1" : ""}'> <!-- 循环显示每个部门 -->
<div class='ntv-line2'> <!-- 部门上方的线条 -->
<div class="ntvl2-left" :style='{"border-top": index === 0 ? "0" : ""}'></div>
<div class="ntvl2-right" :style='{"border-top": index === el.length - 1 ? "0" : ""}'></div>
</div>
<div @click='showChildDepartment(item, inx, index)' v-if='item.areaName !== "新增部门"' class="ntv-content"> <!-- 部门详情 -->
<span class='ntv-span1'>{{item.areaName}}</span>
<span class='ntv-span2'>(699人)</span>
</div>
<div v-else class="ntv-addcontent"> <!-- 新增部门 -->
<span>{{item.areaName}}</span>
</div>
<div v-if='curDepartmentId[inx] === item.id && item.children' class="ntv-line3"> <!-- 部门下方的线条 -->
<div></div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'newTreeView',
methods: {
showChildDepartment (item, inx, index) { // 当前部门对象, 父部门下标, 当前部门对象下标
/* 封装数据结构 */
if (this.curDepartmentId[inx]) {
this.departmentList = this.departmentList.splice(0, inx + 1) // 如果点击过该行 则删除该行下方所有行
for (let i in this.curDepartmentId) {
if (i > inx) {
delete this.curDepartmentId[i] // 删除行点击过的部门的记录
}
}
this.curDepartmentId[inx] = item.id
} else { // 如果没有点击过该行 则添加该行 以该行下标为key 当前点击部门id为value 用来控制点击过的部门下方的竖线的显示
this.curDepartmentId[inx] = item.id
}
if (!item.children) return // 如果点击的该部门没有子部门 则结束方法
let flag = true
item.children.forEach(item => { // 如果子部门中没有新增部门则添加 有则跳过
if (item.areaName === '新增部门') {
flag = false
}
})
if (flag) {
item.children.push({id: new Date().valueOf(), areaName: '新增部门', children: []})
}
this.departmentList.push(item.children)
/* 设置每一行的左边距 */
let curId = 'ntvList' + (inx + 1) // 每一行list的id
if (inx === 0) { // 当点击第一行部门时 设置子部门的左边距
if (item.children.length / 2 > index + 1) { // 如果子部门长度 / 2 超出了当前点击的部门到最左侧的宽度 则子部门左边距为0
this.listMarginLeft = 0
} else { // 如果子部门的长度 / 2 不大于当前点击部门到最左侧的距离 则设置子部门在当前部门下居中显示
this.listMarginLeft = ((index + 0.5) * 128) - (item.children.length / 2 * 128)
}
}
let marginLeft = document.getElementById('ntvList' + inx).style.marginLeft.replace('px', '') // 获取当前点击部门所在行的左边距 用来计算下一行(子部门的左边距)
if (inx !== 0 && item.children) { // 设置点击第二行及后面的行时 子部门的左边距
// 子部门的左边距 = 上一列(父部门)左边距 + (当前点击部门的下标 + 0.5) * 128(单个部门的长度) - 子部门行一般的长度 (结合树状图理解)
this.listMarginLeft = Number(marginLeft) + ((index + 0.5) * 128) - (item.children.length / 2 * 128)
this.listMarginLeft = this.listMarginLeft >= 0 ? this.listMarginLeft : 0
}
setTimeout(() => {
document.getElementById(curId).style.marginLeft = this.listMarginLeft + 'px'
}, 50)
}
},
data () {
return {
listMarginLeft: 0, // 子部门左边距
curDepartmentId: {}, // 存放所有点击过的部门id 控制点击过的部门下方的 竖线的显示
departmentList: [], // 存放每一行数据的数组,结构为 [[{第一行部门}], [{第二行部门}], [{第三行部门}]...]
treeData: [] // 总数据 一般是从后台请求接口得到
}
},
created () {
/* 数据结构为对象数组,每个对象中也有子对象数组,以此类推 */
this.treeData = [
{id: '124wre', areaName: '部门一', children: [
{id: '3245e2', areaName: '部门1', children: [
{id: '124esfd', areaName: '双方都', children: [
{id: '35etgfdg', areaName: '我确认', children: [
{id: '124rftgr', areaName: '无法改'},
{id: '234etrd', areaName: '无法改'}
]},
{id: '24resrf', areaName: '我确认'},
{id: '23retgdf', areaName: '我确认'},
{id: 'g45ytfh', areaName: '我确认'},
]},
{id: '214re', areaName: '双方都'},
{id: '2retgr', areaName: '双方都'},
{id: 'rhfgjh66', areaName: '双方都'},
{id: 'dfhert4', areaName: '双方都'},
{id: 'rh6yj', areaName: '双方都'},
{id: 'rh5ujyg', areaName: '双方都'},
{id: '34ytrtyhfg', areaName: '双方都'},
{id: '437yrtjhyhg', areaName: '双方都'},
{id: '24etr', areaName: '双方都'},
{id: 'fgth56ujt', areaName: '双方都'}
]},
{id: '346t5e', areaName: '部门2'},
{id: 'ryhfgs', areaName: '部门3'}
]},
{id: '365', areaName: '部门二', children: [
{id: '24erttg', areaName: '部门1'},
{id: 'dfhtrjh', areaName: '部门2', children: [
{id: '24rewtg54', areaName: '二分干'},
{id: '24rwetr', areaName: '的服务', children: [
{id: '245etrd', areaName: '按我发'},
{id: '124wr', areaName: '按我发'},
{id: '346rtyt', areaName: '按我发'},
{id: '346yrt', areaName: '按我发', children: [
{id: '235retre', areaName: '受访人'},
{id: '235erytr', areaName: '受访人'},
{id: '346rtyhyt', areaName: '受访人', children: [
{id: '25rwetre', areaName: '时发热'},
{id: 'ytuyt', areaName: '时发热'},
{id: 'uyjyj', areaName: '时发热'},
]},
]},
{id: '4yrtyt', areaName: '按我发'},
{id: '457tyjiu', areaName: '按我发'}
]}
]},
{id: '24wetr', areaName: '部门3'},
{id: '548utyjty', areaName: '部门4'},
{id: '457ytujgh', areaName: '部门5'}
]},
{id: '326tr', areaName: '部门三', children: [
{id: '23546tyf', areaName: '部门1'},
{id: 'dgdgfdsf', areaName: '部门2'},
{id: 'sdfegfdg', areaName: '部门3'},
{id: 'etg5u6jj', areaName: '部门4'},
]},
{id: '325trdy', areaName: '部门四', children: [
{id: '35rytf', areaName: '部门1'},
]},
{id: '254et', areaName: '部门五', children: [
{id: '23433wtrdg', areaName: '部门1'},
{id: '2354eryhgh', areaName: '部门2'},
]},
{id: '324etrd', areaName: '部门六', children: [
{id: '3tergfd', areaName: '部门1'}
]},
{id: '214wre', areaName: '部门四', children: [
{id: '35rytf', areaName: '部门1'},
{id: '25rwetre', areaName: '部门2'},
{id: '325tert', areaName: '部门3'},
{id: '36tryrgf', areaName: '部门4', children: [
{id: '1243wer', areaName: '部门3', children: [
{id: '23re', areaName: '部门5'},
{id: '23ter', areaName: '部门5'},
{id: '346trtu', areaName: '部门5'},
{id: '34trth', areaName: '部门5'}
]},
{id: '235ertr', areaName: '部门3', children: [
{id: '45ythft', areaName: '部门5'},
{id: '346thg', areaName: '部门5'},
{id: '45utyj', areaName: '部门5'}
]},
{id: '25tr', areaName: '部门3', children: [
{id: '235e4tr', areaName: '部门5', children: [
{id: '346ertge', areaName: '部门6'},
{id: '23trdhg', areaName: '部门6', children: [
{id: 'htrh34yr', areaName: '部门7'},
{id: '436yrthtf', areaName: '部门7'},
{id: '43yrdgtfg', areaName: '部门7'},
{id: '57ty', areaName: '部门7'}
]},
{id: '54utyjtg', areaName: '部门6'},
{id: '43yrtfhg', areaName: '部门6'}
]},
{id: '4576tuy', areaName: '部门5'}
]},
{id: '235tr', areaName: '部门3', children: [
{id: '235yt', areaName: '部门5'},
{id: '67iyjg', areaName: '部门5', children: [
{id: '24etr', areaName: '部门7'},
{id: '436yrt', areaName: '部门7'},
]},
{id: '43ythg', areaName: '部门5'}
]},
{id: '3476uyg', areaName: '部门3'}
]}
]},
{id: '25124tgf4et', areaName: '部门五', children: [
{id: '23433wtrdg', areaName: '部门1'},
{id: '2354eryhgh', areaName: '部门2'},
]},
{id: '214etry', areaName: '部门六', children: [
{id: '3tergfd', areaName: '部门1'}
]},
{id: '213retgtf', areaName: '新增部门', children: []},
]
this.departmentList.push(this.treeData)
}
}
</script>
<style lang="less">
#newTreeView {
overflow: auto;
padding-bottom: 20px;
.ntv-line3 {
width: 100%;
height: 30px;
margin-top: 10px;
> div {
width: 50%;
height: 100%;
border-right: 2px solid #ACB7C3;
}
}
.ntv-line2 {
margin-bottom: 10px;
width: 100%;
height: 33px;
display: flex;
.ntvl2-left {
width: 50%;
height: 100%;
border-right: 2px solid #ACB7C3;
border-top: 2px solid #ACB7C3;
}
.ntvl2-right {
width: calc(100% - 50% - 2px);
height: 100%;
border-top: 2px solid #ACB7C3;
}
}
.ntv-line1 {
margin-top: 8px;
position: absolute;
top: 117px;
left: 399px;
> div {
height: 30px;
width: 2px;
background-color: #ACB7C3;
}
}
.ntvc-child {
width: 132px;
height: 66px;
background: rgba(22,119,255,1);
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.ntv-company {
width: 142px;
height: 76px;
background: rgba(22,119,255,0.4);
box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.1);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
color: #FFFFFF;
position: absolute;
left: 327px;
top: 42px;
}
.ntv-span1 {
color: #4A4A4A;
font-size: 14px;
font-weight: 600;
}
.ntv-span2 {
color: #ACB7C3;
font-size: 12px;
}
.ntv-addcontent {
width: 108px;
height: 52px;
background: rgba(255,255,255,1);
box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.1);
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: #9B9B9B;
font-size: 14px;
}
.ntv-content {
width: 108px;
height: 52px;
background: rgba(255,255,255,1);
box-shadow: 0px 2px 8px 0px rgba(0,0,0,0.1);
border-radius: 4px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
}
.ntv-item {
min-width: 128px;
display: flex;
flex-direction: column;
align-items: center;
}
.ntv-list {
display: flex;
margin-top: 114px;
position: relative;
}
}
</style>