CSS 实现树状组织架构图
普通的树结构
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul class="domtree">
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul.domtree,
ul.domtree ul {
margin: 0;
padding: 0 0 0 2em;
}
ul.domtree li {
list-style: none;
position: relative;
margin-top: 0 !important;
}
ul.domtree > li:first-child:before {
border-style: none none solid none;
}
ul.domtree li:before {
position: absolute;
content: '';
top: -0.01em;
left: -0.7em;
width: 0.5em;
height: 0.615em;
border-style: none none solid solid;
border-width: 0.05em;
border-color: #aaa;
}
ul.domtree li:not(:last-child):after {
position: absolute;
content: '';
top: 0.7em;
left: -0.7em;
bottom: 0;
border-style: none none none solid;
border-width: 0.05em;
border-color: #aaa;
}
</style>
- 1 级菜单
- 2 级菜单
- 2 级菜单
- 3 级菜单
- 3 级菜单
- 2 级菜单
- 1 级菜单
- 2 级菜单
- 2 级菜单
- 2 级菜单
- 1 级菜单
<script setup>
const list = [
{
text: '1 级菜单',
children: [
{ text: '2 级菜单' },
{
text: '2 级菜单',
children: [{ text: '3 级菜单' }, { text: '3 级菜单' }],
},
],
},
{
text: '1 级菜单',
children: [{ text: '2 级菜单' }, { text: '2 级菜单' }],
},
{ text: '1 级菜单' },
]
</script>
<template>
<ul class="css-tree">
<li v-for="(lv1, v1idx) in list" :key="v1idx">
{{ lv1.text }}
<ul>
<li v-for="(lv2, v2idx) in lv1.children" :key="v2idx" class="child">
{{ lv2.text }}
<ul>
<li v-for="(lv3, v3idx) in lv2.children" :key="v3idx" class="child">
{{ lv3.text }}
</li>
</ul>
</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
.css-tree {
ul {
margin-top: 0 !important;
}
li {
list-style-type: none;
position: relative;
margin-top: 0 !important;
/* 横线直连 */
&::before {
position: absolute;
content: '';
width: 12px;
height: 1px;
border-top: 2px solid #eb3b5a;
left: -15px;
top: 12px;
}
/* 竖直连线 */
&::after {
position: absolute;
content: '';
width: 1px;
/* 高度需要大于自身,否则会有空隙 */
height: calc(100% + 3px);
border-right: 2px solid #eb3b5a;
left: -15px;
top: 0;
}
}
.child {
margin-left: 12px;
}
}
/* 去除每层ul最后一个li 竖直连线 */
.css-tree li:last-child::after {
display: none;
}
.css-tree > li:first-child::after {
// height: calc(100% - 14px);
top: 18px;
}
// 最外层ul第一个li横线带弧度
.css-tree > li:first-child::before {
height: 10px;
border-top-left-radius: 5px;
border-left: 2px solid #eb3b5a
}
// 每层ul最后一个li横线带弧度
.css-tree li:last-child::before {
height: 10px;
border-top: none;
border-bottom: 2px solid #eb3b5a;
top: 3px;
border-bottom-left-radius: 5px;
border-left: 2px solid #eb3b5a;
}
</style>
实现原理
- 通过
ul
li
实现层级结构 - 通过
before
和after
实现连接线
分步实现
使用 ul
li
实现层级结构
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
li {
list-style-type: none;
}
</style>
使用 before
伪类画出横线
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul {
margin-top: 0 !important;
}
li {
list-style-type: none;
position: relative;
margin-top: 0 !important;
&::before {
position: absolute;
content: '';
width: 10px;
height: 1px;
border-top: 2px solid #eb3b5a;
left: -15px;
top: 12px;
}
}
ul ul li {
margin-left: 10px;
}
</style>
使用 after
伪类画出竖线
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul {
margin-top: 0 !important;
}
li {
list-style-type: none;
position: relative;
margin-top: 0 !important;
&::before {
position: absolute;
content: '';
width: 10px;
height: 1px;
border-top: 2px solid #eb3b5a;
left: -15px;
top: 12px;
}
&::after {
position: absolute;
content: '';
width: 1px;
height: 100%;
border-left: 2px solid #eb3b5a;
left: -15px;
top: 0;
}
}
ul ul li {
margin-left: 10px;
}
</style>
隐藏每个 ul
下的最后一个直属孩子 li
🏷️ 的 after
伪类效果
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul class="domtree">
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul {
margin-top: 0 !important;
}
li {
list-style-type: none;
position: relative;
margin-top: 0 !important;
&::before {
position: absolute;
content: '';
width: 10px;
height: 1px;
border-top: 2px solid #eb3b5a;
left: -15px;
top: 12px;
}
&::after {
position: absolute;
content: '';
width: 1px;
height: 100%;
border-left: 2px solid #eb3b5a;
left: -15px;
top: 0;
}
}
ul ul li {
margin-left: 10px;
}
ul > li:last-child::after {
display: none;
}
</style>
修改 after
伪类的 height
值
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul class="domtree">
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul {
margin-top: 0 !important;
}
li {
list-style-type: none;
position: relative;
margin-top: 0 !important;
&::before {
position: absolute;
content: '';
width: 10px;
height: 1px;
border-top: 2px solid #eb3b5a;
left: -15px;
top: 12px;
}
&::after {
position: absolute;
content: '';
width: 1px;
height: calc(100% + 12px);
border-left: 2px solid #eb3b5a;
left: -15px;
top: 0;
}
}
ul ul li {
margin-left: 10px;
}
ul > li:last-child::after {
display: none;
}
</style>
修改 最外层第一个 li
的 after
伪类的 top
和 height
值
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul class="domtree">
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul {
margin-top: 0 !important;
}
li {
list-style-type: none;
position: relative;
margin-top: 0 !important;
&::before {
position: absolute;
content: '';
width: 10px;
height: 1px;
border-top: 2px solid #eb3b5a;
left: -15px;
top: 12px;
}
&::after {
position: absolute;
content: '';
width: 1px;
height: calc(100% + 12px);
border-left: 2px solid #eb3b5a;
left: -15px;
top: 0;
}
}
ul ul li {
margin-left: 10px;
}
ul > li:last-child::after {
display: none;
}
ul.domtree > li:first-child::after {
height: 100%;
top: 13px;
}
</style>
✍️ 改进 | 连线处带弧度
最外层 ul
的第一个 li
和每层 ul
最后一个 li
的横线带弧度
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul class="domtree">
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul {
margin-top: 0 !important;
}
li {
list-style-type: none;
position: relative;
margin-top: 0 !important;
&::before {
position: absolute;
content: '';
width: 10px;
height: 1px;
border-top: 2px solid #eb3b5a;
left: -15px;
top: 12px;
}
}
ul ul li {
margin-left: 10px;
}
ul > li:last-child::after {
display: none;
}
ul.domtree > li:first-child::after {
height: 100%;
top: 13px;
}
// 最外层ul第一个li横线带弧度
ul.domtree > li:first-child::before {
height: 10px;
border-top-left-radius: 5px;
border-left: 2px solid #eb3b5a;
}
// 每层ul最后一个li横线带弧度
ul > li:last-child::before {
height: 10px;
border-top: none;
border-bottom: 2px solid #eb3b5a;
top: 3px;
border-bottom-left-radius: 5px;
border-left: 2px solid #eb3b5a;
}
</style>
创建竖直连线
- 1级菜单
- 2级菜单
- 2级菜单
- 3级菜单
- 3级菜单
- 1级菜单
- 2级菜单
- 2级菜单
<template>
<ul class="domtree">
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>
2级菜单
<ul>
<li>3级菜单</li>
<li>3级菜单</li>
</ul>
</li>
</ul>
</li>
<li>
1级菜单
<ul>
<li>2级菜单</li>
<li>2级菜单</li>
</ul>
</li>
</ul>
</template>
<style lang="scss" scoped>
ul {
margin-top: 0 !important;
}
li {
list-style-type: none;
position: relative;
margin-top: 0 !important;
&::before {
position: absolute;
content: '';
width: 10px;
height: 1px;
border-top: 2px solid #eb3b5a;
left: -15px;
top: 12px;
}
// 竖直连线
&::after {
position: absolute;
content: '';
width: 1px;
height: calc(100% + 3px);
border-right: 2px solid #eb3b5a;
left: -15px;
top: 0;
}
}
ul ul li {
margin-left: 10px;
}
ul > li:last-child::after {
display: none;
}
ul.domtree > li:first-child::after {
height: calc(100% - 14px);
top: 18px;
}
// 最外层ul第一个li横线带弧度
ul.domtree > li:first-child::before {
height: 10px;
border-top-left-radius: 5px;
border-left: 2px solid #eb3b5a;
}
// 每层ul最后一个li横线带弧度
ul > li:last-child::before {
height: 10px;
border-top: none;
border-bottom: 2px solid #eb3b5a;
top: 3px;
border-bottom-left-radius: 5px;
border-left: 2px solid #eb3b5a;
}
</style>
组织架构/家族树
<script setup>
const list = {
title: 'Paren',
children:[
{
title: 'Child 1',
children: [
{
title: 'Grandchild 1',
},
],
},
{
title: 'Child 2',
children: [
{
title: 'Grandchild 1',
},
{
title: 'Grandchild 2',
children: [
{
title: 'Grand Grand Child 1',
},
{
title: 'Grand Grand Child 2',
},
{
title: 'Grand Grand Child 3',
},
],
},
{
title: 'Grandchild 3',
},
],
},
],
}
</script>
<template>
<div class="tree">
<ul>
<li>
<a class="succeed">
{{ list.title }}
</a>
<ul>
<li v-for="(child, key1) in list.children" :key="key1">
<a class="succeed">
{{ child.title }}
</a>
<ul v-if="child.children?.length">
<li v-for="(grand, key2) in child.children" :key="key2">
<a href="#">{{ grand.title }}</a>
<ul v-if="grand.children?.length">
<li v-for="(great, key3) in grand.children" :key="key3">
<a href="#">{{ great.title }}</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
ul,
li {
margin: 0 !important;
padding: 0 !important;
}
// 滚动条
.tree {
overflow: auto;
}
// 去除li前面的点
.tree ul,
.tree li {
list-style-type: none;
}
// 基于flex实现树的层级结构
.tree ul {
display: flex;
}
.tree li {
margin: 3px !important;
/* 让内容将li撑开 */
flex-shrink: 0;
/* li下的子元素水平居中效果 */
display: flex;
flex-direction: column;
align-items: center;
position: relative;
/* 左上横线连线 */
&::before {
position: absolute;
content: '';
border-bottom: 2px solid #a55eea;
width: 50%;
left: 0;
height: 1px;
top: -15px;
}
/* 右上横线连线 */
&::after {
position: absolute;
content: '';
border-bottom: 2px solid #a55eea;
/* width 和 right 解决连线之间的缝隙 */
width: 60%;
right: -10%;
height: 1px;
top: -15px;
}
}
/* ul下第一个直属li无需左上连线 */
.tree ul>li:first-child {
&::before {
display: none;
}
}
/* ul下最后一个直属li无需右上连线 */
.tree ul>li:last-child {
&::after {
display: none;
}
}
.tree li a {
position: relative;
background: #6b41e6;
color: #fff;
padding: 0.5rem 2rem;
font-weight: 500;
border-radius: 8px;
transition: background-color 0.3s;
&:hover {
background: #7b5cf1;
}
&::after {
position: absolute;
content: '';
display: block;
width: 2px;
height: 15px;
/* lv2 竖线 */
border-left: 2px solid #a55eea;
top: -15px;
left: 50%;
}
}
/* 通过 ul 的before 解决当只有一个子节点时,竖向连接线长度不足问题 */
.tree ul {
position: relative;
&:first-child::before {
display: none !important;
}
&:first-child > li:first-child > a::after {
display: none;
}
&::before {
content: '';
position: absolute;
height: 18px;
width: 2px;
/* lv1 */
border-left: 2px solid #a55eea;
left: 50%;
top: -30px;
}
}
/* 每个层级之间设置一点间隔 */
.tree li ul {
margin-top: 30px !important;
}
</style>
html
结构
<template>
<div class="tree">
<ul>
<li>
<a href="#">Parent</a>
<ul>
<li>
<a href="#">Child</a>
<ul>
<li>
<a href="#">Grand Child</a>
</li>
</ul>
</li>
<li>
<a href="#">Child</a>
<ul>
<li><a href="#">Grand Child</a></li>
<li>
<a href="#">Grand Child</a>
<ul>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
</ul>
</li>
<li><a href="#">Grand Child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
基于 flex 布局实现层级结构,并居中对齐
<template>
<div class="tree">
<ul>
<li>
<a href="#">Parent</a>
<ul>
<li>
<a href="#">Child</a>
<ul>
<li>
<a href="#">Grand Child</a>
</li>
</ul>
</li>
<li>
<a href="#">Child</a>
<ul>
<li><a href="#">Grand Child</a></li>
<li>
<a href="#">Grand Child</a>
<ul>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
</ul>
</li>
<li><a href="#">Grand Child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
ul,
li,
a {
margin: 0 !important;
padding: 0 !important;
color: #111;
}
// 滚动条
.tree {
overflow: auto;
}
// 去除li前面的点
.tree ul,
.tree li {
list-style-type: none;
}
// 基于flex实现树的层级结构
.tree ul {
display: flex;
}
.tree li {
// li的边框和间隙只是让层级结构看的更清除
border: 1px solid #eb3b5a;
margin: 3px !important;
// 让内容将li撑开
flex-shrink: 0;
// 以下css实现li下的子元素水平居中的效果
display: flex;
flex-direction: column;
align-items: center;
}
</style>
去除 li
边框 , 给a
加上边框, 各个层级之间加入间隔
<template>
<div class="tree">
<ul>
<li>
<a href="#">Parent</a>
<ul>
<li>
<a href="#">Child</a>
<ul>
<li>
<a href="#">Grand Child</a>
</li>
</ul>
</li>
<li>
<a href="#">Child</a>
<ul>
<li><a href="#">Grand Child</a></li>
<li>
<a href="#">Grand Child</a>
<ul>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
</ul>
</li>
<li><a href="#">Grand Child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
ul,
li {
margin: 0 !important;
padding: 0 !important;
}
// 滚动条
.tree {
overflow: auto;
}
// 去除li前面的点
.tree ul,
.tree li {
list-style-type: none;
}
// 基于flex实现树的层级结构
.tree ul {
display: flex;
}
.tree li {
margin: 3px !important;
// 让内容将li撑开
flex-shrink: 0;
// 以下css实现li下的子元素水平居中的效果
display: flex;
flex-direction: column;
align-items: center;
}
.tree li a {
color: #111;
opacity: 0.7;
border: 1px solid #eb3b5a;
padding: 5px 10px;
border-radius: 5px;
}
// 每个层级之间设置一点间隔
.tree li ul {
margin-top: 30px !important;
}
</style>
加入横向连线
通过li
的after
,before
设置横向连接线
<template>
<div class="tree">
<ul>
<li>
<a href="#">Parent</a>
<ul>
<li>
<a href="#">Child</a>
<ul>
<li>
<a href="#">Grand Child</a>
</li>
</ul>
</li>
<li>
<a href="#">Child</a>
<ul>
<li><a href="#">Grand Child</a></li>
<li>
<a href="#">Grand Child</a>
<ul>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
</ul>
</li>
<li><a href="#">Grand Child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
ul,
li {
margin: 0 !important;
padding: 0 !important;
}
// 滚动条
.tree {
overflow: auto;
}
// 去除li前面的点
.tree ul,
.tree li {
list-style-type: none;
}
// 基于flex实现树的层级结构
.tree ul {
display: flex;
}
.tree li {
margin: 3px !important;
// 让内容将li撑开
flex-shrink: 0;
// 以下css实现li下的子元素水平居中的效果
display: flex;
flex-direction: column;
align-items: center;
position: relative;
// 左上连线
&::before {
position: absolute;
content: '';
border-bottom: 2px solid #a55eea;
width: 50%;
left: 0;
height: 1px;
top: -15px;
}
// 右上连线
&::after {
position: absolute;
content: '';
border-bottom: 2px solid #a55eea;
width: 50%;
right: 0;
height: 1px;
top: -15px;
}
}
.tree ul > li:first-child {
// ul下第一个直属li无需左上连线
&::before {
display: none;
}
}
.tree ul > li:last-child {
// ul下最后一个直属li无需右上连线
&::after {
display: none;
}
}
.tree li a {
color: #111;
border: 1px solid #eb3b5a;
padding: 5px 10px;
border-radius: 5px;
}
// 每个层级之间设置一点间隔
.tree li ul {
margin-top: 30px !important;
}
</style>
解决横向连线间的缝隙:通过调整右上连线的 width
和 left
<template>
<div class="tree">
<ul>
<li>
<a href="#">Parent</a>
<ul>
<li>
<a href="#">Child</a>
<ul>
<li>
<a href="#">Grand Child</a>
</li>
</ul>
</li>
<li>
<a href="#">Child</a>
<ul>
<li><a href="#">Grand Child</a></li>
<li>
<a href="#">Grand Child</a>
<ul>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
</ul>
</li>
<li><a href="#">Grand Child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
ul,
li {
margin: 0 !important;
padding: 0 !important;
}
// 滚动条
.tree {
overflow: auto;
}
// 去除li前面的点
.tree ul,
.tree li {
list-style-type: none;
}
// 基于flex实现树的层级结构
.tree ul {
display: flex;
}
.tree li {
margin: 3px !important;
// 让内容将li撑开
flex-shrink: 0;
// 以下css实现li下的子元素水平居中的效果
display: flex;
flex-direction: column;
align-items: center;
position: relative;
// 左上连线
&::before {
position: absolute;
content: '';
border-bottom: 2px solid #a55eea;
width: 50%;
left: 0;
height: 1px;
top: -15px;
}
// 右上连线
&::after {
position: absolute;
content: '';
border-bottom: 2px solid #a55eea;
// width和right解决连线之间的缝隙
width: 60%;
right: -10%;
height: 1px;
top: -15px;
}
}
.tree ul > li:first-child {
// ul下第一个直属li无需左上连线
&::before {
display: none;
}
}
.tree ul > li:last-child {
// ul下最后一个直属li无需右上连线
&::after {
display: none;
}
}
.tree li a {
color: #111;
border: 1px solid #eb3b5a;
padding: 5px 10px;
border-radius: 5px;
}
// 每个层级之间设置一点间隔
.tree li ul {
margin-top: 30px !important;
}
</style>
加入竖向连线
通过ul
的after
,before
辅助解决竖向连线问题
<template>
<div class="tree">
<ul>
<li>
<a href="#">Parent</a>
<ul>
<li>
<a href="#">Child</a>
<ul>
<li>
<a href="#">Grand Child</a>
</li>
</ul>
</li>
<li>
<a href="#">Child</a>
<ul>
<li><a href="#">Grand Child</a></li>
<li>
<a href="#">Grand Child</a>
<ul>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
</ul>
</li>
<li><a href="#">Grand Child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
ul,
li {
margin: 0 !important;
padding: 0 !important;
}
// 滚动条
.tree {
overflow: auto;
}
// 去除li前面的点
.tree ul,
.tree li {
list-style-type: none;
}
// 基于flex实现树的层级结构
.tree ul {
display: flex;
}
.tree li {
margin: 3px !important;
// 让内容将li撑开
flex-shrink: 0;
// 以下css实现li下的子元素水平居中的效果
display: flex;
flex-direction: column;
align-items: center;
position: relative;
// 左上连线
&::before {
position: absolute;
content: '';
border-bottom: 2px solid #20bf6b;
width: 50%;
left: 0;
height: 1px;
top: -15px;
}
// 右上连线
&::after {
position: absolute;
content: '';
border-bottom: 2px solid orangered;
// width和right解决连线之间的缝隙
width: 60%;
right: -10%;
height: 1px;
top: -15px;
}
}
.tree ul > li:first-child {
// ul下第一个直属li无需左上连线
&::before {
display: none;
}
}
.tree ul > li:last-child {
// ul下最后一个直属li无需右上连线
&::after {
display: none;
}
}
.tree li a {
color: #111;
border: 1px solid #eb3b5a;
padding: 5px 10px;
border-radius: 5px;
position: relative;
&::after {
position: absolute;
content: '';
width: 1px;
height: 13px;
border-left: 2px solid #3867d6;
top: -15px;
left: 50%;
}
}
// 通过ul的before解决当只有一个子节点时,竖向连接线长度不足问题
.tree ul {
position: relative;
&::before {
content: '';
position: absolute;
height: 19px;
width: 1px;
border-left: 2px solid #fed330;
left: 50%;
top: -30px;
}
}
// 每个层级之间设置一点间隔
.tree li ul {
margin-top: 30px !important;
}
</style>
最终效果
<script setup>
const list = {
title: 'Paren',
children:[
{
title: 'Child 1',
children: [
{
title: 'Grandchild 1',
},
],
},
{
title: 'Child 2',
children: [
{
title: 'Grandchild 1',
},
{
title: 'Grandchild 2',
children: [
{
title: 'Grand Grand Child 1',
},
{
title: 'Grand Grand Child 2',
},
{
title: 'Grand Grand Child 3',
},
],
},
{
title: 'Grandchild 3',
},
],
},
],
}
</script>
<template>
<div class="tree">
<ul>
<li>
<a class="succeed">
{{ list.title }}
</a>
<ul>
<li v-for="(child, key1) in list.children" :key="key1">
<a class="succeed">
{{ child.title }}
</a>
<ul v-if="child.children?.length">
<li v-for="(grand, key2) in child.children" :key="key2">
<a href="#">{{ grand.title }}</a>
<ul v-if="grand.children?.length">
<li v-for="(great, key3) in grand.children" :key="key3">
<a href="#">{{ great.title }}</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
ul,
li {
margin: 0 !important;
padding: 0 !important;
}
// 滚动条
.tree {
overflow: auto;
}
// 去除li前面的点
.tree ul,
.tree li {
list-style-type: none;
}
// 基于flex实现树的层级结构
.tree ul {
display: flex;
}
.tree li {
margin: 3px !important;
/* 让内容将li撑开 */
flex-shrink: 0;
/* li下的子元素水平居中效果 */
display: flex;
flex-direction: column;
align-items: center;
position: relative;
/* 左上横线连线 */
&::before {
position: absolute;
content: '';
border-bottom: 2px solid #a55eea;
width: 50%;
left: 0;
height: 1px;
top: -15px;
}
/* 右上横线连线 */
&::after {
position: absolute;
content: '';
border-bottom: 2px solid #a55eea;
/* width 和 right 解决连线之间的缝隙 */
width: 60%;
right: -10%;
height: 1px;
top: -15px;
}
}
/* ul下第一个直属li无需左上连线 */
.tree ul>li:first-child {
&::before {
display: none;
}
}
/* ul下最后一个直属li无需右上连线 */
.tree ul>li:last-child {
&::after {
display: none;
}
}
.tree li a {
position: relative;
background: #6b41e6;
color: #fff;
padding: 0.5rem 2rem;
font-weight: 500;
border-radius: 8px;
transition: background-color 0.3s;
&:hover {
background: #7b5cf1;
}
&::after {
position: absolute;
content: '';
display: block;
width: 2px;
height: 15px;
/* lv2 竖线 */
border-left: 2px solid #a55eea;
top: -15px;
left: 50%;
}
}
/* 通过 ul 的before 解决当只有一个子节点时,竖向连接线长度不足问题 */
.tree ul {
position: relative;
&:first-child::before {
display: none !important;
}
&:first-child > li:first-child > a::after {
display: none;
}
&::before {
content: '';
position: absolute;
height: 18px;
width: 2px;
/* lv1 */
border-left: 2px solid #a55eea;
left: 50%;
top: -30px;
}
}
/* 每个层级之间设置一点间隔 */
.tree li ul {
margin-top: 30px !important;
}
</style>
另一种方式
- Parent
- Child
- Grand Child
- Child
- Grand Child
- Grand Child the text is long
- Grand Child
- Grand Child
- Great Grand Child
- Great Grand Child
- Great Grand Child
- Great Grand Child
- Great Grand Child
- Great Grand Child
- Grand Child
- Grand Child
- Grand Child
<script setup lang="ts">
import { ref } from 'vue'
/**
* 层级节点的间隔
*/
const levelNodeMargin = ref<number>(30)
const borderRadius = ref<string>('5px')
/**
* 连线的粗细
*/
const lineSize = ref<string>('3px')
</script>
<template>
<div class="tree">
<ul>
<li>
<div>Parent</div>
<ul>
<li>
<div>Child</div>
<ul>
<li>
<div>Grand Child</div>
</li>
</ul>
</li>
<li>
<div>Child</div>
<ul>
<li>
<div>Grand Child</div>
<ul>
<li>
<div>Grand Child the text is long</div>
</li>
</ul>
</li>
<li>
<div>Grand Child</div>
</li>
<li>
<div>Grand Child</div>
<ul>
<li>
<div>Great Grand Child</div>
</li>
<li>
<div>Great Grand Child</div>
</li>
<li>
<div>Great Grand Child</div>
</li>
<li>
<div>Great Grand Child</div>
</li>
<li>
<div>Great Grand Child</div>
</li>
<li>
<div>Great Grand Child</div>
</li>
</ul>
</li>
<li>
<div>Grand Child</div>
</li>
</ul>
</li>
<li>
<div>Grand Child</div>
</li>
<li>
<div>Grand Child</div>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.tree {
overflow: auto;
ul,
li {
margin: 0;
padding: 0;
list-style-type: none;
}
ul {
// 基于flex实现树的层级结构
display: flex;
position: relative;
margin-top: calc(v-bind(levelNodeMargin) * 1px);
&:first-child {
margin-top: 0;
}
&::before {
content: '';
position: absolute;
border-left: 2px solid pink;
height: 18px;
left: 50%;
top: -30px;
}
}
li {
box-sizing: border-box;
padding: 3px;
// 让内容将li撑开
flex-shrink: 0;
// 以下css实现li下的子元素水平居中的效果
display: flex;
flex-direction: column;
align-items: center;
position: relative;
>div {
color: #111;
opacity: 0.7;
border: 1px solid #111;
padding: 10px 15px;
border-radius: 3px;
cursor: pointer;
}
// 左上连线
&::before {
content: '';
position: absolute;
width: calc(50% + 1px);
left: 1px;
top: -13px;
height: 15px;
opacity: 0.7;
border-top: 2px solid #a55eea;
border-right: 2px solid #a55eea;
border-top-right-radius: 3px;
}
// 右上连线
&::after {
content: '';
position: absolute;
width: calc(50% + 1px);
right: -1px;
top: -13px;
height: 15px;
border-top: 2px solid #a55eea;
border-left: 2px solid #a55eea;
}
}
ul>li:first-child::before,
ul>li:last-child::after {
display: none;
}
ul>li:first-child {
&::after {
border-top-left-radius: v-bind(borderRadius);
width: 55%;
right: -5%;
}
}
ul>li:last-child {
&::before {
border-top-right-radius: v-bind(borderRadius);
width: 55%;
left: -5%;
}
}
ul>li:first-child:last-child {
&::after {
display: block;
position: absolute;
left: 50%;
top: -30px;
height: 32px;
border-top-left-radius: 0;
border-top: none;
}
}
}
</style>
横向
<template>
<div class="tree">
<ul>
<li>
<a href="#">Parent</a>
<ul>
<li>
<a href="#">Child</a>
<ul>
<li>
<a href="#">Grand Child</a>
</li>
</ul>
</li>
<li>
<a href="#">Child</a>
<ul>
<li><a href="#">Grand Child</a></li>
<li>
<a href="#">Grand Child</a>
<ul>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
</ul>
</li>
<li><a href="#">Grand Child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.tree {
overflow: auto;
ul,
li {
margin: 0;
padding: 0;
list-style-type: none;
}
ul {
// 基于flex实现树的层级结构
display: flex;
position: relative;
&::before {
content: '';
position: absolute;
border-top: 3px solid #a55eea;
width: 15px;
top: 50%;
left: -30px;
}
li {
align-items: center;
display: flex;
flex-shrink: 0;
box-sizing: border-box;
padding: 5px 10px;
position: relative;
&::before {
content: '';
position: absolute;
height: 50%;
top: 0;
left: -15px;
border-left: 3px solid #a55eea;
border-bottom: 3px solid #a55eea;
width: 23px;
}
&::after {
content: '';
position: absolute;
height: 50%;
bottom: 0;
left: -15px;
border-left: 3px solid #a55eea;
}
a {
color: #111;
opacity: 0.7;
border: 1px solid #111;
border-radius: 3px;
padding: 5px 10px;
}
ul {
display: flex;
flex-direction: column;
margin-left: 30px;
}
}
> li:first-child {
&::before {
display: none;
}
&::after {
border-top: 3px solid #a55eea;
width: 23px;
border-top-left-radius: 5px;
}
}
> li:last-child {
&::after {
display: none;
}
&::before {
border-bottom: 3px solid #a55eea;
width: 23px;
border-bottom-left-radius: 5px;
}
}
> li:first-child:last-child {
&::after {
display: block;
position: absolute;
top: 50%;
left: -30px;
width: 38px;
height: 0;
border-top-left-radius: 0;
border-top: 3px solid #a55eea;
border-left: 0;
}
}
}
}
</style>
<template>
<div class="tree">
<ul>
<li>
<a href="#">Parent</a>
<ul>
<li>
<a href="#">Child</a>
<ul>
<li>
<a href="#">Grand Child</a>
</li>
</ul>
</li>
<li>
<a href="#">Child</a>
<ul>
<li><a href="#">Grand Child</a></li>
<li>
<a href="#">Grand Child</a>
<ul>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
<li>
<a href="#">Great Grand Child</a>
</li>
</ul>
</li>
<li><a href="#">Grand Child</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.tree {
overflow: auto;
ul,
li {
margin: 0;
padding: 0;
list-style-type: none;
}
display: flex;
ul {
// 基于flex实现树的层级结构
display: flex;
position: relative;
margin-left: auto;
&::before {
content: '';
position: absolute;
border-top: 3px solid #a55eea;
width: 15px;
top: 50%;
right: -30px;
}
li {
align-items: center;
display: flex;
flex-shrink: 0;
box-sizing: border-box;
padding: 5px 10px;
position: relative;
flex-flow: row-reverse;
&::before {
content: '';
position: absolute;
height: 50%;
top: 0;
right: -14px;
border-right: 3px solid #a55eea;
border-bottom: 3px solid #a55eea;
width: 23px;
}
&::after {
content: '';
position: absolute;
height: 50%;
bottom: 0;
right: -14px;
border-right: 3px solid #a55eea;
}
a {
color: #111;
opacity: 0.7;
border: 1px solid #111;
border-radius: 3px;
padding: 5px 10px;
}
ul {
display: flex;
flex-direction: column;
margin-right: 30px;
}
}
> li:first-child {
&::before {
display: none;
}
&::after {
border-top: 3px solid #a55eea;
width: 22px;
border-top-right-radius: 5px;
border-left: none;
border-right: 3px solid #a55eea;
right: -14px;
}
}
> li:last-child {
&::after {
display: none;
}
&::before {
border-bottom: 3px solid #a55eea;
width: 23px;
border-bottom-right-radius: 5px;
}
}
> li:first-child:last-child {
&::after {
display: block;
position: absolute;
top: 50%;
right: -30px;
width: 38px;
height: 0;
border-top-right-radius: 0;
border-top: 3px solid #a55eea;
border-right: 0;
}
}
}
}
</style>