高级组件封装技巧--tree的封装

el-tree是一个经常用到的组件,但是它不支持v-model,使用起来很麻烦,这篇教程封装了el-tree,使得它使用起来很简单,并且支持搜索,支持叶子节点横向排列,这样就算数据多了,也会显的很紧凑,同时它支持提交halfCheck节点,这点在做菜单管理的时候很有用,如果数据中不保存halfCheck,你需要向上遍历父节点,但是保存了父节点在回显的时候会有问题,因为只要父节点选中子节点都会选中,这些在组件封装中都做了处理

组件的封装 

<template>
  <div>
    <el-input v-model="filterText" placeholder="请输入搜索条件" v-if="filter" clearable/>
    <el-tree v-bind="allProps" ref="treeRef" :filter-node-method="filterNode"
             @check="handleCheck"/>
  </div>
</template>

<script lang="ts">
// @ts-nocheck
export default {
  name: "ui-tree",
  props: { // 参考 https://element-plus.org/zh-CN/component/tree.html#%E5%B1%9E%E6%80%A7
    modelValue: {default: () => []}, //要提交的表单值
    nodeKey: {default: 'id'}, //每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
    defaultExpandAll: {default: true}, //是否默认展开所有节点
    showCheckbox: {default: true}, //是否显示选择框
    data: {default: null}, //树的数据
    filter: {default: true}, //是否显示过滤框
    leafInline: {default: true}, //叶子节点是否显示成一行
    props: { //参考:https://element-plus.org/zh-CN/component/tree.html#props
      type: Object, default: () => {
        return {}
      }
    }
  },
  data() {
    return {
      textValue: '', //view模式显示的内容
      userChecked: false, //是否用户引起的变化
      filterText: '',
      allProps: {
        ...this.$attrs,
        ...this.$props
      }
    }
  },
  created() {
    if (this.leafInline) {
      this.props.class = this.customNodeClass
    }
  },
  mounted() {
    this.setChecked()
  },
  watch: {
    'modelValue': {
      handler(val) {
        this.setChecked()
      },
    },
    // 过滤
    filterText(val) {
      this.$refs.treeRef.filter(val)
    },
  },
  computed: {
    labelField() {
      if (!this.props || !this.props.label) return 'label'
      return this.props.label
    },
    childrenField() {
      if (!this.props || !this.props.children) return 'children'
      return this.props.children
    },
  },
  methods: {
    // 设置选中的节点
    setChecked() {
      let val = this.modelValue
      //如果是用户点击则不设置
      if (this.userChecked) {
        this.userChecked = false
        return
      }
      if (!val || !this.$refs.treeRef) return
      let leafNode = []
      // 如果后台保存了half节点,需要过滤掉
      this.filterLeafNode(leafNode, this.data, val)
      this.$refs.treeRef.setCheckedKeys(leafNode)
    },
    //用户选择后回调
    handleCheck(data, check) {
      this.updateModelValue(check.halfCheckedKeys, check.checkedKeys)
    },
    updateModelValue(halfCheckedKeys, checkedKeys) {
      this.userChecked = true
      let checkIds = []
      checkIds.push(...halfCheckedKeys)
      checkIds.push(...checkedKeys)
      this.$emit('update:modelValue', checkIds)
    },
    //根据搜索框过滤节点
    filterNode(value: string, data) {
      // 该方法会遍历所有节点,显示返回为true的节点
      if (!value) return true
      return data[this.labelField].includes(value)
    },
    // 过滤父节点,只返回叶子节点
    filterLeafNode(leafNode, children, checkedArray) {
      if (!children) return []
      children.forEach(item => {
        if (!item[this.childrenField] || item[this.childrenField].length == 0) {
          if (checkedArray.indexOf(item[this.nodeKey]) > -1) {
            leafNode.push(item[this.nodeKey])
          }
        } else {
          this.filterLeafNode(leafNode, item[this.childrenField], checkedArray)
        }
      })
    },
    customNodeClass(data, node) {
      if (node.isLeaf) return ''
      let addClass = true
      for (const key in node.childNodes) {
        if (!node.childNodes[key].isLeaf) {
          addClass = false
        }
      }
      let levelClass = 'level-' + node.level
      return addClass ? `penultimate-node ${levelClass}` : ''
    },
  }
}
</script>

<style>
.penultimate-node .el-tree-node__children {
  line-height: 12px;
}

.el-tree-node.penultimate-node > .el-tree-node__children {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.el-tree-node.penultimate-node.level-1 > .el-tree-node__children {
  padding-left: 30px;
}
.el-tree-node.penultimate-node.level-2 > .el-tree-node__children {
  padding-left: 48px;
}
.el-tree-node.penultimate-node.level-3 > .el-tree-node__children {
  padding-left: 64px;
}
.el-tree-node.penultimate-node.level-4 > .el-tree-node__children {
  padding-left: 84px;
}

.penultimate-node .el-tree-node__children > .el-tree-node .el-tree-node__content {
  padding-left: 12px !important;
}

.penultimate-node .el-tree-node__children .el-tree-node__content .el-tree-node__expand-icon {
  display: none;
}
</style>

组件的使用

node-key就是绑定值,如果要form绑定id就传id,组件默认显示label,子节点保存在children里面,如果要变更可以通过:props="{label:'title',children:'children'}"来实现

<template>
  <div style="width: 400px">
    <ui-tree :data="data" node-key="label" v-model="form"></ui-tree>
  </div>
</template>

<script>
import UiTree from "@/components/ui-tree.vue";
export default {
  name: "tree",
  components: {UiTree},
  data() {
    return {
      form: ['菜单管理'],
      data: [{label: '系统管理',children:[{label:'用户管理', children:[{label:'菜单管理'},{label:'按钮管理'},{label:'权限管理'}]},{label:'角色管理'}]},
        {label:'文档管理',children:[{label:'目录管理'},{label:'图片管理'},{label:'文件管理'}]}]
    }
  },
}
</script>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值