Skip to content
本页目录

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 级菜单
  • 1 级菜单
    • 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 实现层级结构
  • 通过 beforeafter 实现连接线

分步实现


使用 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>

修改 最外层第一个 liafter 伪类的 topheight

  • 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>

加入横向连线

通过liafter,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>

解决横向连线间的缝隙:通过调整右上连线的 widthleft

<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>

加入竖向连线

通过ulafter,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>