如何对ElementUI、ElementPlus中的Tree树组件进行美化,如增加辅助线、替换展开收起图标、点击节点后文字高亮等效果?本文给你答案!

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 本文介绍了如何对ElementUI和ElementPlus的Tree树组件进行美化,包括增加辅助线、替换展开收起图标、点击节点后文字高亮等效果,并提供了详细的代码示例和实现效果。

ElementUI、ElementPlus树组件功能很不错,但是官方的树形组件没有显示线条,感觉稍微不够大气。于是网上查了一些资料,找了很多也感觉也不够完美,最后找到一个还不错的实现方案,并且再美化改进一下,分享给大家。

一、基于Vue2+ElementUI的例子

(1)示例代码

<template>
  <div class="tree-container">
    <div style="padding: 20px;">
      <el-checkbox v-model="isSelectAll" @change="handleCheckedAllTreeNodeChange">全选/全不选</el-checkbox>

      <el-button size="mini" style="margin-left: 20px;" @click="handleGetCheckedNodesAndKeys(true)">获取已勾选节点</el-button>
    </div>

    <el-tree
      ref="treeRef"
      show-checkbox
      icon-class="el-icon-arrow-right"
      :node-key="'id'"
      :data="treeList"
      :props="defaultProps"
      :default-expand-all="true"
      :expand-on-click-node="false"
      :default-checked-keys="[5]"
    >
      <span slot-scope="{ data }">
        <template v-if="data.children">
          <div v-if="data.children.length > 0">
            <!-- <i class="el-icon-folder" :style="'font-size: 14px; padding: 0 5px 0 5px'"/> -->
            <i :style="'font-size: 13px; padding: 0 5px 0 5px'"><svg viewBox="64 64 896 896" data-icon="apartment" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M908 640H804V488c0-4.4-3.6-8-8-8H548v-96h108c8.8 0 16-7.2 16-16V80c0-8.8-7.2-16-16-16H368c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h108v96H228c-4.4 0-8 3.6-8 8v152H116c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16H292v-88h440v88H620c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16zm-564 76v168H176V716h168zm84-408V140h168v168H428zm420 576H680V716h168v168z"></path></svg></i>
            <span>{
  
  { data.id + ' - ' + data.label }}</span>
          </div>

          <div v-else>
            <i class="leaf-node-line"></i>
            <!-- <i class="el-icon-folder" :style="'padding: 0 5px 0 5px'"/> -->
            <i :style="'font-size: 13px; padding: 0 5px 0 5px'"><svg viewBox="64 64 896 896" data-icon="apartment" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M908 640H804V488c0-4.4-3.6-8-8-8H548v-96h108c8.8 0 16-7.2 16-16V80c0-8.8-7.2-16-16-16H368c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h108v96H228c-4.4 0-8 3.6-8 8v152H116c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16H292v-88h440v88H620c-8.8 0-16 7.2-16 16v288c0 8.8 7.2 16 16 16h288c8.8 0 16-7.2 16-16V656c0-8.8-7.2-16-16-16zm-564 76v168H176V716h168zm84-408V140h168v168H428zm420 576H680V716h168v168z"></path></svg></i>
            <span>{
  
  { data.id + ' - ' + data.label }}</span>
          </div>
        </template>

        <template v-else>
          <div style="margin-left: 0px;">
            <i class="leaf-node-line"></i>
            <!-- <i class="el-icon-document" :style="'padding: 0 5px 0 5px'"></i> -->
            <i :style="'font-size: 13px; padding: 0 5px 0 5px'"><svg viewBox="64 64 896 896" data-icon="deployment-unit" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M888.3 693.2c-42.5-24.6-94.3-18-129.2 12.8l-53-30.7V523.6c0-15.7-8.4-30.3-22-38.1l-136-78.3v-67.1c44.2-15 76-56.8 76-106.1 0-61.9-50.1-112-112-112s-112 50.1-112 112c0 49.3 31.8 91.1 76 106.1v67.1l-136 78.3c-13.6 7.8-22 22.4-22 38.1v151.6l-53 30.7c-34.9-30.8-86.8-37.4-129.2-12.8-53.5 31-71.7 99.4-41 152.9 30.8 53.5 98.9 71.9 152.2 41 42.5-24.6 62.7-73 53.6-118.8l48.7-28.3 140.6 81c6.8 3.9 14.4 5.9 22 5.9s15.2-2 22-5.9L674.5 740l48.7 28.3c-9.1 45.7 11.2 94.2 53.6 118.8 53.3 30.9 121.5 12.6 152.2-41 30.8-53.6 12.6-122-40.7-152.9zm-673 138.4a47.6 47.6 0 0 1-65.2-17.6c-13.2-22.9-5.4-52.3 17.5-65.5a47.6 47.6 0 0 1 65.2 17.6c13.2 22.9 5.4 52.3-17.5 65.5zM522 463.8zM464 234a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm170 446.2l-122 70.3-122-70.3V539.8l122-70.3 122 70.3v140.4zm239.9 133.9c-13.2 22.9-42.4 30.8-65.2 17.6-22.8-13.2-30.7-42.6-17.5-65.5s42.4-30.8 65.2-17.6c22.9 13.2 30.7 42.5 17.5 65.5z"></path></svg></i>
            <span>{
  
  { data.id + ' - ' + data.label }}</span>
          </div>
        </template>
      </span>
    </el-tree>
  </div>
</template>

<script>
export default {
    
    
  data() {
    
    
    return {
    
    
      // 树列表
      treeList: [
        {
    
    
          id: 1,
          label: "香烟 WiFi 啤酒",
          children: [
            {
    
    
              id: 3,
              label: '香烟',
              children: [
                {
    
    
                  id: 4,
                  label: '煊赫门',
                },
                {
    
    
                  id: 5,
                  label: 'ESSE双爆珠',
                  disabled: true,
                },
              ],
            },
            {
    
    
              id: 2,
              label: '后端开发技术',
              disabled: true,
              children: [
                {
    
    
                  id: 6,
                  label: 'Java编程技术',
                  children: [],
                },
                {
    
    
                  id: 7,
                  label: '数据库',
                  disabled: true,
                  children: [
                    {
    
    
                      id: 8,
                      label: '关系型数据库',
                      children: [
                        {
    
    
                          id: 9,
                          label: 'MySQL',
                        },
                        {
    
    
                          id: 10,
                          label: 'Oracle',
                          disabled: true,
                        },
                      ],
                    },
                    {
    
    
                      id: 11,
                      label: '非关系型数据库',
                      children: [
                        {
    
    
                          id: 12,
                          label: 'Redis',
                        },
                        {
    
    
                          id: 13,
                          label: 'Elasticsearch',
                          disabled: true,
                        },
                      ],
                    }
                  ],
                },
              ],
            },
          ],
        },
      ],

      // 对象关系映射
      defaultProps: {
    
    
        label: 'label',
        children: 'children',
      },

      // 是否全选所有节点标志
      isSelectAll: false,
    };
  },
  methods: {
    
    
    /**
     * 是否全选所有节点
     */
    handleCheckedAllTreeNodeChange() {
    
    
      if (this.isSelectAll) {
    
    
        // 深度遍历将子节点全选中
        for (let i = 0; i < this.treeList.length; i++) {
    
    
          this.$refs.treeRef.setChecked(this.treeList[i], true, true);
        }
      } else {
    
    
        // 全部不选中
        this.$refs.treeRef.setCheckedNodes([]);
      }

      const leafOnly = true;
      this.handleGetCheckedNodesAndKeys(leafOnly);
    },

    /**
     * 获取当前已被选中的节点集合
     */
    handleGetCheckedNodesAndKeys(leafOnly) {
    
    
      console.log('getCheckedNodes =>', this.$refs.treeRef.getCheckedNodes(leafOnly));
      console.log('getCheckedKeys =>', this.$refs.treeRef.getCheckedKeys(leafOnly));
    }
  },
}
</script>

<style lang="less" scoped>
  // 设置树形组件节点的定位和左内边距
  .tree-container /deep/ .el-tree-node {
    
    
    position: relative;
    padding-left: 13px;
  }

  // 设置树形组件节点的 before 伪类的样式
  .tree-container /deep/ .el-tree-node:before {
    
    
    width: 1px;
    height: 100%;
    content: '';
    position: absolute;
    top: -38px;
    bottom: 0;
    left: 0;
    right: auto;
    border-width: 1px;
    border-left: 1px solid #b8b9bb;
  }

  // 设置树形组件节点的 after 伪类的样式
  .tree-container /deep/ .el-tree-node:after {
    
    
    width: 13px;
    height: 13px;
    content: '';
    position: absolute;
    left: 0;
    right: auto;
    top: 12px;
    bottom: auto;
    border-width: 1px;
    border-top: 1px solid #b8b9bb;
  }

  // 设置树形组件首节点的左边框不显示
  .tree-container /deep/ .el-tree > .el-tree-node:before {
    
    
    border-left: none;
  }

  // 设置树形组件首节点的顶部边框不显示
  .tree-container /deep/ .el-tree > .el-tree-node:after {
    
    
    border-top: none;
  }

  // 设置树形组件末节点的 before 伪类的高度
  .tree-container /deep/ .el-tree .el-tree-node:last-child:before {
    
    
    height: 50px;
  }

  // 设置树形组件节点字体大小、以及取消左内边距
  .tree-container /deep/ .el-tree .el-tree-node__content {
    
    
    color: #000;
    font-size: 14px;
    padding-left: 0 !important;
  }

  // 设置树形组件孩子节点左内边距
  .tree-container /deep/ .el-tree .el-tree-node__children {
    
    
    padding-left: 11.5px;
  }

  // 设置树形组件复选框左右外边距
  .tree-container /deep/ .el-tree .el-tree-node__content > label.el-checkbox {
    
    
    margin: 0 5px 0 5px !important;
  }

  // 设置树形组件展开图标定位、图层、内边距
  .tree-container /deep/ .el-tree .el-tree-node__expand-icon {
    
    
    position: relative;
    z-index: 99;
  }

  // 设置树形组件叶子节点的默认图标不显示
  .tree-container /deep/ .el-tree .el-tree-node__expand-icon.is-leaf {
    
    
    display: none;
  }

  // 设置树形组件叶子节点的横线
  .tree-container /deep/ .el-tree .leaf-node-line {
    
    
    width: 23px;
    height: 13px;
    content: '';
    position: absolute;
    left: 13px;
    right: auto;
    top: 12px;
    bottom: auto;
    border-width: 1px;
    border-top: 1px solid #b8b9bb;
  }

  // 设置树形组件有叶子节点的左外边距
  .tree-container /deep/ .el-tree .el-tree-node__content:has(.is-leaf){
    
    
    // color: aqua;
    margin-left: 24px !important;
  }
</style>

(2)效果如下~

二、基于Vue3+ElementPlus的例子

1.普通示例

(1)示例代码

<template>
  <div class="element-plus-tree">
    <el-tree
      :data="treeData"
      :props="defaultProps"
      :show-checkbox="false"
      :default-expand-all="true"
      :highlight-current="true"
      :expand-on-click-node="false"
      :indent="22"
    >
      <template #default="{ node, data }">
        <span v-if="!node.isLeaf" style="display: flex; align-items: center;">
          <el-icon v-if="node.expanded" style="margin: 0 6px 0 2px;" size="16"><FolderOpened /></el-icon>
          <el-icon v-else style="margin: 0 6px 0 2px;" size="16"><Folder /></el-icon>
          <small @click="handleNodeClick(node, data)">{
  
  { node.label }}</small>
        </span>

        <span v-else style="display: flex; align-items: center;">
          <el-icon style="margin: 0 6px 0 2px;" size="16"><Document /></el-icon>
          <small @click="handleNodeClick(node, data)">{
  
  { node.label }}</small>
        </span>
      </template>
    </el-tree>
  </div>
</template>

<script setup>
import {
    
     onMounted, ref, getCurrentInstance } from 'vue'

// 代理对象
const {
    
     proxy } = getCurrentInstance()

// 树型数据
const treeData = ref(
  [
    {
    
    
      label: '香烟 WiFi 啤酒',
      expanded: true,
      children: [
        {
    
    
          label: '香烟',
          children: [
            {
    
    
              label: '煊赫门'
            },
            {
    
    
              label: 'ESSE双爆珠'
            }
          ],
        },
        {
    
    
          label: '后端开发技术',
          children: [
            {
    
    
              label: 'Java编程技术'
            },
            {
    
    
              label: 'Python编程技术'
            }
          ],
        },
        {
    
    
          label: '数据库',
          children: [
            {
    
    
              label: '关系型数据库',
              children: [
                {
    
    
                  label: 'MySQL'
                },
                {
    
    
                  label: 'Oracle'
                }
              ],
            },
            {
    
    
              label: '非关系型数据库',
              children: [
                {
    
    
                  label: 'Redis'
                },
                {
    
    
                  label: 'Elasticsearch'
                }
              ],
            }
          ],
        },
        {
    
    
          label: 'AI人工智能'
        },
      ],
    },
    {
    
    
      label: '火腿 iPad 泡面'
    },
  ]
)

// 树节点属性映射关系
const defaultProps = {
    
    
  children: 'children',
  label: 'label',
}

/**
 * 树组件点击事件句柄方法
 */
const handleNodeClick = (node, data) => {
    
    
  console.log(
        '%c 树组件点击事件句柄方法 %c handleNodeClick',
        'padding: 1px; background-color: #35495e; color: #fff',
        'padding: 1px; background-color: #5e7ce0; color: #fff',
      )
      console.log('%c ∟ %c node %c =>', 'text-indent: 10px', 'padding: 1px; background-color: #41b883; color: #fff', 'color: #000', node)
      console.log('%c ∟ %c data %c =>', 'text-indent: 10px', 'padding: 1px; background-color: #41b883; color: #fff', 'color: #000', data)
}

onMounted(() => {
    
    
  // ...
})
</script>

<style lang="less" scoped>
  .element-plus-tree {
    
    
    padding: 100px;

    :deep(.el-tree) {
    
    

      .el-tree-node {
    
    

        /* ^ 所有节点 */
        i.el-tree-node__expand-icon {
    
    
          padding: 6px;

          &::before {
    
    
            font-family: element-ui-icons;
            font-style: normal;
            content: "\e6d9";
            color: #000000;
            border: 1px solid #606266;
            border-radius: 2px;
          }

          svg {
    
    
            display: none; // 隐藏所有节点的 svg 图标
          }
        }
        /* / 所有节点 */

        /* ^ 已展开的父节点 */
        i.el-tree-node__expand-icon.expanded {
    
    
          transform: rotate(0deg); // 取消旋转
          -webkit-transform: rotate(0deg); // 取消旋转

          &::before {
    
    
            font-family: element-ui-icons;
            font-style: normal;
            content: "\e6d8";
            color: #000000;
            border: 1px solid #606266;
            border-radius: 2px;
          }
        }
        /* / 已展开的父节点 */

        /* ^ 叶子节点 */
        i.el-tree-node__expand-icon.is-leaf {
    
    

          &::before {
    
    
            display: none;
          }
        }
        /* / 叶子节点 */

        /* ^ 复选框 */
        .el-checkbox {
    
    
          margin: 0 7px 0 2px;

          .el-checkbox__inner {
    
    
            width: 14px;
            height: 14px;
            border-radius: 2px;
            border: 1px solid #bbb;
          }

          .el-checkbox__input.is-checked .el-checkbox__inner,
          .el-checkbox__input.is-indeterminate .el-checkbox__inner {
    
    
            border: 1px solid #5e7ce0;
          }
        }
        /* / 复选框 */

        .el-tree-node__content {
    
    
          small {
    
    
            font-size: 13px;
          }
        }
      }
    }
  }
</style>

(2)效果如下~

2.高级示例

(1)示例代码

<template>
  <div class="element-plus-tree">

    <el-tree
      :data="treeData"
      :props="defaultProps"
      :show-checkbox="true"
      :default-expand-all="true"
      :highlight-current="true"
      :expand-on-click-node="false"
      :indent="22"
    >
      <template #default="{ node, data }">
        <span v-if="!node.isLeaf" style="display: flex; align-items: center;">
          <el-icon v-if="node.expanded" style="margin: 0 6px 0 0px;" size="16"><FolderOpened /></el-icon>
          <el-icon v-else style="margin: 0 6px 0 0px;" size="16"><Folder /></el-icon>
          <small @click="handleNodeClick(node, data)">{
  
  { node.label }}</small>
        </span>

        <span v-else style="display: flex; align-items: center;">
          <el-icon style="margin: 0 6px 0 0px;" size="16"><Document /></el-icon>
          <small @click="handleNodeClick(node, data)">{
  
  { node.label }}</small>
        </span>
      </template>
    </el-tree>
  </div>
</template>

<script setup>
import {
    
     onMounted, ref, getCurrentInstance } from 'vue'

// 代理对象
const {
    
     proxy } = getCurrentInstance()

// 树型数据
const treeData = ref(
  [
    {
    
    
      label: '香烟 WiFi 啤酒',
      expanded: true,
      children: [
        {
    
    
          label: '香烟',
          children: [
            {
    
    
              label: '煊赫门'
            },
            {
    
    
              label: 'ESSE双爆珠'
            }
          ],
        },
        {
    
    
          label: '后端开发技术',
          children: [
            {
    
    
              label: 'Java编程技术'
            },
            {
    
    
              label: 'Python编程技术'
            }
          ],
        },
        {
    
    
          label: '数据库',
          children: [
            {
    
    
              label: '关系型数据库',
              children: [
                {
    
    
                  label: 'MySQL'
                },
                {
    
    
                  label: 'Oracle'
                }
              ],
            },
            {
    
    
              label: '非关系型数据库',
              children: [
                {
    
    
                  label: 'Redis'
                },
                {
    
    
                  label: 'Elasticsearch'
                }
              ],
            }
          ],
        },
        {
    
    
          label: 'AI人工智能'
        },
      ],
    },
    {
    
    
      label: '火腿 iPad 泡面'
    },
  ]
)

// 树节点属性映射关系
const defaultProps = {
    
    
  children: 'children',
  label: 'label',
}

/**
 * 树组件点击事件句柄方法
 */
const handleNodeClick = (node, data) => {
    
    
  console.log(
    '%c 树组件点击事件句柄方法 %c handleNodeClick',
    'padding: 1px; background-color: #35495e; color: #fff',
    'padding: 1px; background-color: #5e7ce0; color: #fff',
  )
  console.log('%c ∟ %c node %c =>', 'text-indent: 10px', 'padding: 1px; background-color: #41b883; color: #fff', 'color: #000', node)
  console.log('%c ∟ %c data %c =>', 'text-indent: 10px', 'padding: 1px; background-color: #41b883; color: #fff', 'color: #000', data)
}

onMounted(() => {
    
    
  // ...
})
</script>

<style lang="less" scoped>
  .element-plus-tree {
    
    
    padding: 100px;

    :deep(.el-tree) {
    
    

      /* ---- ---- ---- ---- ^(节点对齐)---- ---- ---- ---- */
      .el-tree-node {
    
    

        /* ^ 所有节点 */
        i.el-tree-node__expand-icon {
    
    
          padding: 6px;

          &::before {
    
    
            font-family: element-ui-icons;
            font-style: normal;
            content: "\e6d9";
            color: #000000;
            border: 1px solid #606266;
            border-radius: 2px;
          }

          svg {
    
    
            display: none; // 隐藏所有节点的 svg 图标
          }
        }
        /* / 所有节点 */

        /* ^ 已展开的父节点 */
        i.el-tree-node__expand-icon.expanded {
    
    
          transform: rotate(0deg); // 取消旋转
          -webkit-transform: rotate(0deg); // 取消旋转

          &::before {
    
    
            font-family: element-ui-icons;
            font-style: normal;
            content: "\e6d8";
            color: #000000;
            border: 1px solid #606266;
            border-radius: 2px;
          }
        }
        /* / 已展开的父节点 */

        /* ^ 叶子节点 */
        i.el-tree-node__expand-icon.is-leaf {
    
    

          &::before {
    
    
            display: none;
          }
        }
        /* / 叶子节点 */

        /* ^ 复选框 */
        .el-checkbox {
    
    
          margin: 0 7px 0 2px;

          .el-checkbox__inner {
    
    
            width: 14px;
            height: 14px;
            border-radius: 2px;
            border: 1px solid #bbb;
          }

          .el-checkbox__input.is-checked .el-checkbox__inner,
          .el-checkbox__input.is-indeterminate .el-checkbox__inner {
    
    
            border: 1px solid #5e7ce0;
          }
        }
        /* / 复选框 */

        .el-tree-node__content {
    
    
          small {
    
    
            font-size: 13px;
          }
        }
      }
      /* ---- ---- ---- ---- /(节点对齐)---- ---- ---- ---- */

      /* ---- ---- ---- ---- ^(文字高亮)---- ---- ---- ---- */
      .el-tree-node.is-current {
    
    
        .el-tree-node__content {
    
    
          small {
    
    
            color: #5e7ce0;
          }
        }

        .el-tree-node__children {
    
    
          small {
    
    
            color: unset;
          }
        }
      }
      /* ---- ---- ---- ---- /(文字高亮)---- ---- ---- ---- */

      /* ---- ---- ---- ---- ^(新增辅助线)---- ---- ---- ---- */
      /* ^ 树节点 */
      .el-tree-node {
    
    
        position: relative;
        width: auto;
        // width: max-content; // 显示文字宽度
        padding-left: 13px;

        &::before {
    
    
          width: 1px;
          height: 100%;
          content: '';
          position: absolute;
          top: -38px;
          bottom: 0;
          left: 0;
          right: auto;
          border-width: 1px;
          border-left: 1px solid #b8b9bb;
        }

        &::after {
    
    
          width: 13px;
          height: 13px;
          content: '';
          position: absolute;
          z-index: 0;
          left: 0;
          right: auto;
          top: 12px;
          bottom: auto;
          border-width: 1px;
          border-top: 1px solid #b8b9bb;
        }

        .el-tree-node__content {
    
    
          position: relative;
          z-index: 1;
          color: #000;
          padding-left: 0 !important;

          /* ^ 复选框 */
          .el-checkbox {
    
    
            margin: 0 10px 0 5.5px;
          }
          /* / 复选框 */
        }

        .el-tree-node__children {
    
    
          padding-left: 12px;
        }

        &:last-child::before {
    
    
          height: 50px;
        }
      }
      /* / 树节点 */

      /* ^ 第一层节点 */
      > .el-tree-node {
    
    
        padding-left: 0;

        &::before {
    
    
          border-left: none;
        }

        &::after {
    
    
          border-top: none;
        }
      }
      /* / 第一层节点 */

      /* ^ 叶子节点 */
      i.el-tree-node__expand-icon.is-leaf {
    
    
        display: none;
      }
      /* / 叶子节点 */

      /* ^ 设置子节点左外边距 */
      .el-tree-node__content:has(.is-leaf) {
    
    
        // color: #00ffff;
        margin-left: 25px !important;
      }
      /* / 设置子节点左外边距 */
      /* ---- ---- ---- ---- /(新增辅助线)---- ---- ---- ---- */
    }
  }
</style>

(2)效果如下~

三、基于Vue3+DevUI的附赠例子

1.导入相关依赖

npm i vue-devui -D

2.官方文档

https://vue-devui.github.io/

3.普通示例

(1)示例代码

<template>
  <div class="vue-devui-tree">

    <div>
      <span>是否展开所有节点&nbsp;:&nbsp;</span>
       <el-switch
          v-model="isExpandAllNodes"
          size="small"
          @change="handleIsExpandAllNodesChange"
        />
    </div>

    <DevTree ref="treeRef" :data="data">
      <template #icon="{ nodeData, toggleNode }">
        <span
          v-if="!nodeData.isLeaf"
          class="devui-tree__node-folder"
          @click="
            (event) => {
              event.stopPropagation();
              toggleNode(nodeData);
            }
          "
        >
          <!-- 未展开图标 -->
          <svg v-if="!nodeData.expanded" style="margin-right: 8px" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
            <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
              <rect x="0.5" y="0.5" width="15" height="15" rx="2" stroke="#666666"></rect>
              <path fill="#666666" d="M8.75,4 L8.75,7.25 L12,7.25 L12,8.75 L8.749,8.75 L8.75,12 L7.25,12 L7.249,8.75 L4,8.75 L4,7.25 L7.25,7.25 L7.25,4 L8.75,4 Z"></path>
            </g>
          </svg>

          <!-- 已展开图标 -->
          <svg v-else style="margin-right: 8px" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
            <g stroke-width="1" fill="none" fill-rule="evenodd">
              <rect x="0.5" y="0.5" width="15" height="15" rx="2" stroke="#666666"></rect>
              <rect x="4" y="7" width="8" height="2" fill="#666666"></rect>
            </g>
          </svg>
        </span>

        <span
          v-else
          class="devui-tree__node-leaf"
        >
        </span>
      </template>

      <template #content="{ nodeData }">
        <svg style="margin-right: 7px" viewBox="0 0 16 16" width="16" height="16">
          <path
            :d="`${
              nodeData.isLeaf
                ? 'M13,6 L9,6 L9,5 L9,2 L3,2 L3,14 L13,14 L13,6 Z M12.5857864,5 L10,2.41421356 L10,5 L12.5857864,5 Z M2,1 L10,1 L14,5 L14,15 L2,15 L2,1 Z'
                : nodeData.expanded
                ? 'M16,6 L14,14 L2,14 L0,6 L16,6 Z M14.7192236,7 L1.28077641,7 L2.78077641,13 L13.2192236,13 L14.7192236,7 Z M6,1 L8,3 L15,3 L15,5 L14,5 L14,4 L7.58578644,4 L5.58578644,2 L2,2 L2,5 L1,5 L1,1 L6,1 Z'
                : 'M14,6 L14,5 L7.58578644,5 L5.58578644,3 L2,3 L2,6 L14,6 Z M14,7 L2,7 L2,13 L14,13 L14,7 Z M1,2 L6,2 L8,4 L15,4 L15,14 L1,14 L1,2 Z'
            }`"
            stroke-width="1"
            fill="#8a8e99"
          ></path>
        </svg>
        <small>{
  
  { nodeData.label }}</small>
      </template>
    </DevTree>

  </div>
</template>

<script setup>
import {
    
     onMounted, ref, getCurrentInstance, toRaw } from 'vue'
import {
    
     Tree as DevTree } from "vue-devui"
import 'vue-devui/style.css'

// 代理对象
const currentInstance = getCurrentInstance()

// 树型DOM引用
const treeRef = ref()

// 是否展开所有节点
const isExpandAllNodes = ref(false)

// 树型数据
const data = ref(
  [
    {
    
    
      label: '香烟 WiFi 啤酒',
      expanded: true,
      children: [
        {
    
    
          label: '香烟',
          children: [
            {
    
    
              label: '煊赫门'
            },
            {
    
    
              label: 'ESSE双爆珠'
            }
          ],
        },
        {
    
    
          label: '后端开发技术',
          children: [
            {
    
    
              label: 'Java编程技术'
            },
            {
    
    
              label: 'Python编程技术'
            }
          ],
        },
        {
    
    
          label: '数据库',
          children: [
            {
    
    
              label: '关系型数据库',
              children: [
                {
    
    
                  label: 'MySQL'
                },
                {
    
    
                  label: 'Oracle'
                }
              ],
            },
            {
    
    
              label: '非关系型数据库',
              children: [
                {
    
    
                  label: 'Redis'
                },
                {
    
    
                  label: 'Elasticsearch'
                }
              ],
            }
          ],
        },
        {
    
    
          label: 'AI人工智能'
        },
      ],
    },
    {
    
    
      label: '火腿 iPad 泡面'
    },
  ]
)

/**
 * 是否展开所有节点句柄方法
 */
const handleIsExpandAllNodesChange = (val) => {
    
    
  if (val) {
    
    
    handleExpandAllNodes()
  } else {
    
    
    handleCollapseAllNodes()
  }
}

/**
 * 将树节点全部展开句柄方法
 */
const handleExpandAllNodes = () => {
    
    
  treeRef.value.treeFactory.expandAllNodes()
}

/**
 * 将树节点全部收起句柄方法
 */
const handleCollapseAllNodes = () => {
    
    
  const treeFactory = treeRef.value.treeFactory
  const treeData = treeFactory.treeData._rawValue
  recursionCollapseNode(treeData)
}

/**
 * 递归收起节点句柄方法
 */
const recursionCollapseNode = (treeData) => {
    
    
  for (let node of treeData) {
    
    
    if (node.expanded) {
    
    
      treeRef.value.treeFactory.collapseNode(node)
    }

    if (node.children && node.children.length > 0) {
    
    
      recursionCollapseNode(node.children)
    }
  }
}

onMounted(
  async () => {
    
    
    // Todo
  }
)
</script>

<style lang="less" scoped>
  .vue-devui-tree {
    
    
    padding: 100px;

    :deep(.devui-tree) {
    
    

      .devui-tree__node-leaf {
    
    
        // display: none;
        position: relative;
        width: 16px;
        height: 16px;
        margin-right: 6px;
        // background-color: #ccc;
        // border: 1px solid #ccc;
        // border-radius: 2px;
      }

      .devui-tree__node-vline {
    
    
        margin-left: 4.5px;
        background-color: #cccccc !important;
      }

      .devui-tree__node-hline {
    
    
        margin-left: 4.5px;
        background-color: #cccccc !important;
      }

      .devui-tree__node {
    
    

        .devui-tree__node-content {
    
    

          &.active {
    
    
            // background-color: unset;

            small {
    
    
              color: #5e7ce0;
            }
          }

          &:hover {
    
    
            // background-color: unset;
          }

          small {
    
    
            font-size: 13px;
            transition: all ease 0.3s;

            &:hover {
    
    
              color: #5e7ce0;
            }
          }
        }
      }
    }
  }
</style>

(2)效果如下~

4.高级示例

(1)示例代码

<template>
  <div class="vue-devui-tree">

    <DevTree ref="treeRef" :data="data" check>
      <template #icon="{ nodeData, toggleNode }">
        <span
          v-if="!nodeData.isLeaf"
          class="devui-tree__node-folder"
          @click="
            (event) => {
              event.stopPropagation();
              toggleNode(nodeData);
            }
          "
        >
          <!-- 未展开图标 -->
          <svg v-if="!nodeData.expanded" style="margin-right: 7px" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
            <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
              <rect x="0.5" y="0.5" width="15" height="15" rx="2" stroke="#666666"></rect>
              <path fill="#666666" d="M8.75,4 L8.75,7.25 L12,7.25 L12,8.75 L8.749,8.75 L8.75,12 L7.25,12 L7.249,8.75 L4,8.75 L4,7.25 L7.25,7.25 L7.25,4 L8.75,4 Z"></path>
            </g>
          </svg>

          <!-- 已展开图标 -->
          <svg v-else style="margin-right: 7px" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
            <g stroke-width="1" fill="none" fill-rule="evenodd">
              <rect x="0.5" y="0.5" width="15" height="15" rx="2" stroke="#666666"></rect>
              <rect x="4" y="7" width="8" height="2" fill="#666666"></rect>
            </g>
          </svg>
        </span>

        <span
          v-else
          class="devui-tree__node-leaf"
        >
        </span>
      </template>

      <template #content="{ nodeData }">
        <svg style="margin: 0 7px" viewBox="0 0 16 16" width="16" height="16">
          <path
            :d="`${
              nodeData.isLeaf
                ? 'M13,6 L9,6 L9,5 L9,2 L3,2 L3,14 L13,14 L13,6 Z M12.5857864,5 L10,2.41421356 L10,5 L12.5857864,5 Z M2,1 L10,1 L14,5 L14,15 L2,15 L2,1 Z'
                : nodeData.expanded
                ? 'M16,6 L14,14 L2,14 L0,6 L16,6 Z M14.7192236,7 L1.28077641,7 L2.78077641,13 L13.2192236,13 L14.7192236,7 Z M6,1 L8,3 L15,3 L15,5 L14,5 L14,4 L7.58578644,4 L5.58578644,2 L2,2 L2,5 L1,5 L1,1 L6,1 Z'
                : 'M14,6 L14,5 L7.58578644,5 L5.58578644,3 L2,3 L2,6 L14,6 Z M14,7 L2,7 L2,13 L14,13 L14,7 Z M1,2 L6,2 L8,4 L15,4 L15,14 L1,14 L1,2 Z'
            }`"
            stroke-width="1"
            fill="#8a8e99"
          ></path>
        </svg>
        <small>{
  
  { nodeData.label }}</small>
      </template>
    </DevTree>

  </div>
</template>

<script setup lang="ts">
import {
    
     onMounted, ref, getCurrentInstance, toRaw, useCssModule, provide } from 'vue'
import {
    
     Tree as DevTree } from "vue-devui"
import 'vue-devui/style.css'

// 代理对象
const proxy = getCurrentInstance()

// 树型DOM引用
const treeRef = ref(null);

// 树型数据
const data = ref(
  [
    {
    
    
      label: '香烟 WiFi 啤酒',
      expanded: true,
      children: [
        {
    
    
          label: '香烟',
          children: [
            {
    
    
              label: '煊赫门'
            },
            {
    
    
              label: 'ESSE双爆珠'
            }
          ],
        },
        {
    
    
          label: '后端开发技术',
          children: [
            {
    
    
              label: 'Java编程技术'
            },
            {
    
    
              label: 'Python编程技术'
            }
          ],
        },
        {
    
    
          label: '数据库',
          children: [
            {
    
    
              label: '关系型数据库',
              children: [
                {
    
    
                  label: 'MySQL'
                },
                {
    
    
                  label: 'Oracle'
                }
              ],
            },
            {
    
    
              label: '非关系型数据库',
              children: [
                {
    
    
                  label: 'Redis'
                },
                {
    
    
                  label: 'Elasticsearch'
                }
              ],
            }
          ],
        },
        {
    
    
          label: 'AI人工智能'
        },
      ],
    },
    {
    
    
      label: '火腿 iPad 泡面'
    },
  ]
)

onMounted(() => {
    
    
  // 展开全部节点
  if (treeRef && treeRef.value) {
    
    
    const treeFactory: any = treeRef.value['treeFactory']
    treeFactory.expandAllNodes()
  }
})
</script>

<style lang="less" scoped>
  .vue-devui-tree {
    
    
    padding: 100px;

    :deep(.devui-tree) {
    
    

      .devui-tree__node-leaf {
    
    
        // display: none;
        position: relative;
        width: 16px;
        height: 16px;
        margin-right: 6px;
        // background-color: #ccc;
        // border: 1px solid #ccc;
        // border-radius: 2px;
      }

      .devui-tree__node-vline {
    
    
        margin-left: 4.5px;
        background-color: #cccccc !important;
      }

      .devui-tree__node-hline {
    
    
        margin-left: 4.5px;
        background-color: #cccccc !important;
      }

      .devui-tree__node {
    
    

        .devui-tree__node-content {
    
    

          &.active {
    
    
            // background-color: unset;

            small {
    
    
              color: #5e7ce0;
            }
          }

          // &:hover {
    
    
            // background-color: unset;
          // }

          small {
    
    
            font-size: 13px;
            transition: all ease 0.3s;

            &:hover {
    
    
              color: #5e7ce0;
            }
          }
        }
      }
    }
  }
</style>

(2)效果如下~

四、参考资料

vue树形控件tree的使用方法_vue.js_脚本之家

Vue DevUI

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
目录
相关文章
|
4月前
|
JavaScript
vue + d3.js(v6) 绘制【树状图/思维导图】可折叠/展开,可点击跳转,可带标签
vue + d3.js(v6) 绘制【树状图/思维导图】可折叠/展开,可点击跳转,可带标签
617 1
|
4月前
|
前端开发
ElementPlus卡片如何能够一行呈四,黑马UI前端布局视频资料,element样式具体的细节无法修改,F12找到那个位置,可能在其他组件写了错误,找到那个位置,围绕着位置解决问题最快了,卡片下边
ElementPlus卡片如何能够一行呈四,黑马UI前端布局视频资料,element样式具体的细节无法修改,F12找到那个位置,可能在其他组件写了错误,找到那个位置,围绕着位置解决问题最快了,卡片下边
文本,vitepress代码风格配置和序号配置,第几行到第几行出现高亮显示,最上方图标出现 ,网页排序出现博客,vitepress如何默认折叠效果
文本,vitepress代码风格配置和序号配置,第几行到第几行出现高亮显示,最上方图标出现 ,网页排序出现博客,vitepress如何默认折叠效果
vitepress如何配置右上角的小两侧标志,利用nav标签进行修改,右侧边栏如何设置成自动弹出水平框,让原先隐藏的框能够显示出来
vitepress如何配置右上角的小两侧标志,利用nav标签进行修改,右侧边栏如何设置成自动弹出水平框,让原先隐藏的框能够显示出来
|
6月前
|
小程序 前端开发 Android开发
微信小程序(van-tabs) 去除横向滚动条样式(附加源码解决方案+报错图)
微信小程序(van-tabs) 去除横向滚动条样式(附加源码解决方案+报错图)
440 1
|
数据安全/隐私保护 计算机视觉
qss样式表笔记大全(三):可设置样式的窗口部件列表(中)(持续更新示例) 一
qss样式表笔记大全(三):可设置样式的窗口部件列表(中)(持续更新示例)
qss样式表笔记大全(三):可设置样式的窗口部件列表(中)(持续更新示例) 一
|
小程序 算法 前端开发
小程序之移花宫-自定义底部标签图标---【浅入深出系列005】
小程序之移花宫-自定义底部标签图标---【浅入深出系列005】
|
前端开发 JavaScript
echarts tooltip设置正常却无法显示被遮挡设置层级堆叠顺序的问题解决方案
echarts tooltip设置正常却无法显示被遮挡设置层级堆叠顺序的问题解决方案
552 0
|
容器
使用导航菜单如何实现在多个页面下左边导航菜单右边内容的效果
使用导航菜单如何实现在多个页面下左边导航菜单右边内容的效果
198 0
ElementUI导航菜单嵌套多级折叠面板的小箭头图标bug
ElementUI导航菜单嵌套多级折叠面板的小箭头图标bug
ElementUI导航菜单嵌套多级折叠面板的小箭头图标bug