vue递归组件实战之简单树形控件实例代码

2022-10-16,,,,

1、递归组件-简单树形控件预览及问题

 

在编写树形组件时遇到的问题:

  • 组件如何才能递归调用?
  • 递归组件点击事件如何传递?

2、树形控件基本结构及样式

<template>
 <ul class="vue-tree">
  <li class="tree-item">
   <div class="tree-content"><!--节点内容-->
    <div class="expand-arrow"></div><!--展开或收缩节点按钮-->
    <div class="tree-label">小学</div><!--节点文本内容-->
   </div>
   <ul class="sub-tree"><!--子节点-->
    <li class="tree-item expand">
     <div class="tree-content">
      <div class="expand-arrow"></div>
      <div class="tree-label">语文</div>
     </div>
    </li>
    <li class="tree-item">
     <div class="tree-content">
      <div class="expand-arrow"></div>
      <div class="tree-label">数学</div>
     </div>
    </li>
   </ul>
  </li>
 </ul>
</template>

<style lang="stylus">
.vue-tree{
 list-style: none;
 padding: 0;
 margin: 0;
 .tree-item{
  cursor: pointer;
  transition: background-color .2s;
  .tree-content{
   position: relative;
   padding-left: 28px;
   &:hover{
    background-color: #f0f7ff;
   }
  }
  .expand-arrow{
   position: absolute;
   top: 0;
   left: 0;
   width: 28px;
   height: 28px;
   cursor: pointer;
   &::after{
    position: absolute;
    top: 50%;
    left: 50%;
    display: block;
    content: ' ';
    border-width: 5px;
    border-style: solid;
    border-color: transparent;
    border-left-color: #ccc;
    margin: -5px 0 0 -2.5px;
    transition: all .2s;
   }
  }
  &.expand{
   &>.tree-content{
    background-color: #f0f7ff;
    &>.expand-arrow{
     &::after{
      transform: rotate(90deg);
      margin: -2.5px 0 0 -5px;
     }
    }
   }
  }
  .tree-label{
   height: 28px;
   line-height: 28px;
   font-size: 14px;
  }
  .sub-tree{
   display: none;
   list-style: none;
   padding: 0 0 0 28px;
   margin: 0;
  }
  &.expand>.sub-tree{
   display: block;
  }
  &.no-child{
   &>.tree-content{
    &>.expand-arrow{
     display: none;
    }
   }
  }
 }
}
</style>

3、组件目录及数据结构

目录结构

vue-tree

vuetree.vue
treeitem.vue

树形控件数据结构

let treedata = [
 {
  text: "一级", // 显示的文字
  expand: false, // 默认是否展开
  children: [ // 子节点
   {
    text: "一级-1",
    expand: false,
   },
   {
    text: "一级-2",
    expand: false,
    children: [
     {
      text: "一级-2-1",
      expand: false,
     },
     {
      text: "一级-2-2",
      expand: false,
     }
    ]
   }
  ]
 }
];

3.1、 treeitem.vue 代码

<template>
 <li class="tree-item" :class="{expand: isexpand, 'no-child': !treeitemdata.children || treeitemdata.children.length === 0}">
  <div class="tree-content" @click="_clickevent">
   <div class="expand-arrow" @click.stop="expandtree()"></div>
   <div class="tree-label">{{treeitemdata.text}}</div>
  </div>
  <ul class="sub-tree" v-if="treeitemdata.children && treeitemdata.children.length > 0">
   <!--treeitem组件中调用treeitem组件-->
   <treeitem
    v-for="item in treeitemdata.children"
    :tree-item-data="item"
    :key="uuid()"
    :tree-click-event="treeclickevent"></treeitem>
  </ul>
 </li>
</template>

<script>
 export default {
  name: "treeitem",
  props: {
   treeitemdata: {
    type: object,
    default(){
     return {};
    }
   },
   // 节点点击事件
   treeclickevent: {
    type: function,
    default() {
     return function () {};
    }
   }
  },
  data(){
   return {
    // 节点是否展开
    isexpand: this.treeitemdata.expand || false
   }
  },
  methods: {
   // 展开/收缩
   expandtree(flag){
    if(!this.treeitemdata.children || this.treeitemdata.children.length === 0){
     return;
    }
    if(typeof flag === 'undefined'){
     flag = !this.isexpand;
    }else{

     flag = !!flag;
    }
    this.isexpand = flag;
   },
   // 创建一个唯一id
   uuid(){
    let str = math.random().tostring(32);
    str = str.substr(2);
    return str;
   },
   // 节点点击事件
   _clickevent(){
    // 如果有传递事件函数,则调用事件函数并传递当前节点数据及组件
    if(this.treeclickevent && typeof this.treeclickevent === 'function'){
     this.treeclickevent(this.treeitemdata, this);
    }
   }
  }
 }
</script>

3.1.1、解决 组件如何才能递归调用? 问题

在组件模板内调用自身 必须明确定义组件的name属性 ,并且递归调用时组件名称就是name属性。如在 treeitem.vue 组件中组件的name名称为'treeitem',那么在template中调用时组件名称就必须是 <treeitem> 。

当然也可以全局注册组件,具体可以查看vue官方文档 递归组件

3.1.2、解决 递归组件点击事件如何传递? 问题

我这里的解决方案是使用 props 将事件函数传递进来,在点击节点的时候调用事件函数,并把相应的数据传递进去。

之前也尝试过使用 $emit 的形式并把数据传递过去,由于是递归组件,这样一直 $emit ,到最外层时传递的数据就变了,比如传递是第3层节点的数据,到最后执行时数据就变成第1层节点的数据了

4、 vuetree.vue 组件

<template>
 <ul class="vue-tree">
  <treeitem
    v-for="(item, index) in treedata"
    :key="index"
    :treeitemdata="item"
    :tree-click-event="treeclickevent"></treeitem>
 </ul>
</template>

<script>
 import treeitem from "./treeitem";
 export default {
  name: "vuetreemenu",
  components: {
   treeitem
  },
  props: {
   // 树形控件数据
   treedata: {
    type: array,
    default(){
     return [];
    }
   },
   // 节点点击事件
   treeclickevent: {
    type: function,
    default() {
     return function () {};
    }
   }
  }
 }
</script>

<style lang="stylus">
.vue-tree{
 list-style: none;
 padding: 0;
 margin: 0;
 .tree-item{
  cursor: pointer;
  transition: background-color .2s;
  .tree-content{
   position: relative;
   padding-left: 28px;
   &:hover{
    background-color: #f0f7ff;
   }
  }
  .expand-arrow{
   position: absolute;
   top: 0;
   left: 0;
   width: 28px;
   height: 28px;
   cursor: pointer;
   &::after{
    position: absolute;
    top: 50%;
    left: 50%;
    display: block;
    content: ' ';
    border-width: 5px;
    border-style: solid;
    border-color: transparent;
    border-left-color: #ccc;
    margin: -5px 0 0 -2.5px;
    transition: all .2s;
   }
  }
  &.expand{
   &>.tree-content{
    background-color: #f0f7ff;
    &>.expand-arrow{
     &::after{
      transform: rotate(90deg);
      margin: -2.5px 0 0 -5px;
     }
    }
   }
  }
  .tree-label{
   height: 28px;
   line-height: 28px;
   font-size: 14px;
  }
  .sub-tree{
   display: none;
   list-style: none;
   padding: 0 0 0 28px;
   margin: 0;
  }
  &.expand>.sub-tree{
   display: block;
  }
  &.no-child{
   &>.tree-content{
    /*padding-left: 0;*/
    &>.expand-arrow{
     display: none;
    }
   }
  }
 }
}
</style>

5、使用树形组件

<template>
 <div class="app" id="app">
  <vuetree :tree-data="treedata2" :tree-click-event="treeclickevent"></vuetree>
 </div>
</template>

<script>
import vuetree from "./components/vue-tree/vuetree";

export default {
 name: 'app',
 data(){
  return {
   treedata2: [
    {
     text: "一级", // 显示的文字
     expand: false, // 默认是否展开
     children: [
      {
       text: "二级-1",
       expand: false,
      },
      {
       text: "二级-2",
       expand: false,
       children: [
        {
         text: "三级-1",
         expand: false,
        },
        {
         text: "三级-2",
         expand: false,
         children: [
          {
           text: "四级-1",
           expand: false,
          }
         ]
        }
       ]
      }
     ]
    },
    {
     text: "一级-2",
     expand: false
    }
   ]
  }
 },
 methods: {
  treeclickevent(item, treeitem){
   console.log(item);
  }
 },
 components: {
  vuetree
 }
}
</script>

总结

以上所述是小编给大家介绍的vue递归组件实战之简单树形控件实例代码,希望对大家有所帮助

《vue递归组件实战之简单树形控件实例代码.doc》

下载本文的Word格式文档,以方便收藏与打印。