我们项目中有很多需要用到树形组件的,以前写jq的时候用ztree,现在写vue,感觉找不到像ztree一样好用的树形组件,又不想在vue总用jq,所以打算自己封装一个tree组件,花了两个时间才搞完一些基本功能,不过已经可以正常使用了,效果如下:

图标的问题大家可以自行用ztree的样式去修改

加上上周的折叠式菜单,gitHub网址:https://github.com/421119407/vue-kwt-cly

做这个树形菜单是采用了递归组件的思路,因为事件只能传递一层,所以需要每一层子组件触发上一层父组件的事件。

然后通过Vue.set(对象,属性,值)的方式动态给对象添加属性

模板:

<template>
  <ul class="ul-ktree">
    <li v-for="(item,index) in treeDatas"
        :key="item.id"
        :class="{leaf:isLeaf(item),'first-node': index === 0 && !isLeaf(item),'only-node': treeDatas.length === 1 && !isLeaf(item)}"
        v-show="item.hasOwnProperty('visible') ? item.visible : true">
      <div class="ktree-node">
        <span @click="expandNode(item)" v-if="!item.parent || item.children && item.children.length>0" :class="item.expanded ? 'tree-open':'tree-close'"></span>
        <span v-if="checkModel" :class="[item.checked ? (item.halfCheck ? 'box-halfchecked' : 'box-checked') : 'box-unchecked','inputCheck']">
            <input :disabled="item.chkDisabled"
                   :class="['check', item.chkDisabled ? 'chkDisabled' : '']"
                   type="checkbox"
                   @change="changeNodeCheckStatus(item, $event)"
                   v-model="item.checked"/>
        </span>
        <span :class="changeStyle()"></span>
        <Render :node="item" :template ='tpl'/>
      </div>
      <transition name="expand" mode="out-in">
        <k-tree v-if="!isLeaf(item)"
                :datas="item.children"
                @node-check='nodeCheck'
                @on-click="onClick"
                @node-single-check = 'nodeCheck'
                :parent="item"
                :depth="childDepth+1"
                :tpl ="tpl"
                v-show="item.expanded"
                :checkModel="checkModel"
                :halfCheck="halfCheck"
                :scoped="scoped"
                :iconStyle="iconStyle" >
        </k-tree>
      </transition>
    </li>
  </ul>
</template>

 父组件传递属性:

props: {
      iconStyle: {
        type: String,
        default: icon.iconStyle.METRO
      },
      depth: {
        type: Number,
        default: 1
      },
      opinions: {
        type: Object,
        default: () => null
      },
      datas: {
        type: Array,
        default: () => []
      },
      parent: {
        type: Object,
        default: () => null
      },
      halfCheck: {
        type: Boolean,
        default: true
      },
      multiple: {
        type: Boolean,
        default: true
      },
      checkModel: {
        type: Boolean,
        default: true
      },
      draggable: {
        type: Boolean,
        default: false
      },
      drapAfterExpand: {
        type: Boolean,
        default: false
      },
      showCheck: {
        type: Boolean,
        default: false
      },
      isAddOrEdit: {
        type: Boolean,
        default: false
      },
      scoped: {
        type: Boolean,
        default: false
      },
      tpl: Function
    }

 datas:  父组件向子组件传递数据,也就是我们的树形信息。包括节点id,节点父id。。。

parent: 当前节点的父节点

opinions:封装了节点对应信息,主要用来转换后台传来的数据转成树形菜单适用的数据,我们后台传来的数据一般没有父子关系,需要通过特殊处理,转换成

{

   id:1

  children:【{

      id:11

}】

}

halfCheck:半选模式

checkModel:是否显示复选框

scoped:是否父子关联

multiple:是否可复选

tpl:这返回的是一个函数,可自定义标题模板

 

在父组件中:

<template>
 <div>
   <k-tree :datas="treeDatas1" :opinions="opinions" @on-click="onClick"></k-tree>
 </div>
</template>

可通过自定义事件绑定on-click,来自定义点击回调事件

 

数据处理片段:

initDatas() {
        let dataArray = []
        let pidArray = []
         //找出根节点,放入数组中
        for (let key of this.datas) {
          pidArray.push(key[this.opinions.id])   //遍历数据,把id存储在数据中
        }
        for (let key of this.datas) {
          if (pidArray.indexOf(key[this.opinions.pid]) === -1) {  //判断pid是否存在数组中,不存在的那个节点,便是根节点
            dataArray.push({
              id: key[this.opinions.id],
              name: key[this.opinions.name],
              checked: key.checked,
              visible: key.visible
            })
          }
        }
        return this.initArrayDatas(dataArray)
      },
      initArrayDatas(dataArray) {
        for (let key of dataArray) {
          let childDatas = []
          for (let k of this.datas) {      //遍历,通过pid===id建立父子关系
            if (key.id === k[this.opinions.pid]) {  
              childDatas.push({
                id: k[this.opinions.id],
                name: k[this.opinions.name],
                pid: k[this.opinions.pid],
                checked: k.checked,
                visible: k.visible
              })
            }
          }
          key.children = childDatas   //  添加进children
          if (childDatas.length > 0) {
            this.initArrayDatas(childDatas)
          }
        }
        return dataArray
      },

 

筛选节点:

getNodes(opt, data, isOriginal) {  
        data = data || this.datas
        let res = []
        for (const node of data) {
          for (const [key, value] of Object.entries(opt)) {
            if (node[key === value]) {
              if (isOriginal) {
                res.push(node)
              } else {
                let n = Object.assign({}, node)
                delete n['children']
                delete n['parent']
                res.push(n)
              }
            }
          }
          if (node.children && node.children.length) {
            res = res.concat(this.getNodes(opt, node.children, isOriginal))
          }
        }
        return res
      },
      getSelectNodes(isOriginal) {
        return this.getNodes({ select: true }, this.data, isOriginal)
      },
      getCheckedNodes(isOriginal) {
        return this.getNodes({ checked: true }, this.data, isOriginal)
      },

opt参数封装筛选条件,如{checked:true,select:true},然后遍历数据找到符合条件的节点并添加到数组中返回

getSelectNodes和getCheckedNodes可用于点击回调中获取选中节点的值

 

到了这里,我们的属性组件基本就封装完成了,当然了,后期我会逐步完善其他功能。

Logo

前往低代码交流专区

更多推荐