Vue组件(一) - 二次封装ElementUI实现tree树形组件

文章目录

  • 功能描述
  • 代码
    • base-tree.vue:
    • treeDemo.vue:

功能描述

基础功能同el-tree

Vue组件(一) - 二次封装ElementUI实现tree树形组件_第1张图片Vue组件(一) - 二次封装ElementUI实现tree树形组件_第2张图片Vue组件(一) - 二次封装ElementUI实现tree树形组件_第3张图片

代码

base-tree.vue:


<template>
  <div class="baseTree">
    <el-input v-if="isShowFilter" size="small" :placeholder="placeholder" v-model="filterText">
    el-input>
    <el-tree ref="baseTree"
    :data="treeList"
    :node-key="treeProps.id"
    :props="treeProps"
    :highlight-current="highlight"
    :accordion="accordion"
    :default-expand-all="expand"
    :default-expanded-keys="expandedKeys"
    :auto-expand-parent="expandParent"
    :expand-on-click-node="expandNode"
    :show-checkbox="multiple"
    :check-strictly="checkStrictly"
    :filter-node-method="filterNode"
    @node-click="handleNodeClick"
    @node-expand="handleNodeExpand"
    @node-collapse="handleNodeCollapse"
    @check="handleCheck"
    @check-change="handleCheckChange"
    @node-contextmenu="handleNodeContextMenu">
    el-tree>
  div>
template>

<script>
export default {
       
  props: {
       
    // treeList: {
       
    //   type: Array,
    //   default () {
       
    //     return [];
    //   }
    // },
    // 默认树形结构配置项
    treeProps: {
       
      type: Object,
      default() {
       
        return {
       
          id: 'id', // Number类型:树组件ID(node-key)
          label: 'label', // String类型 : 树组件显示名称
          pid: 'parentId', // Number类型:父级ID
          children: 'children' // String类型:子节点
        };
      }
    },
    // 自动收起
    accordion:{
       
      type:Boolean,
      default:() => {
        return false }
    },
    // 是否展开所有节点,默认展开
    expand: {
       
      type: Boolean,
      default() {
       
        return true;
      }
    },
    // 展开子节点的时候是否自动展开父节点 默认值为 true
    expandParent:{
       
      type: Boolean,
      default() {
       
        return true;
      }
    },
    // 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。
    expandNode: {
       
      type: Boolean,
      default() {
       
        return true;
      }
    },
    // 默认展开的节点的 key 的数组
    expandedKeys:{
       
      type: Array,
      default() {
       
        return [];
      }
    },
    // 是否高亮当前选中节点,默认值是 true
    highlight: {
       
      type: Boolean,
      default() {
       
        return true;
      }
    },
    // 是否可多选,默认单选
    multiple: {
       
      type: Boolean,
      default() {
       
        return false;
      }
    },
    // 显示复选框情况下,是否严格遵循父子不互相关联
    checkStrictly: {
       
      type: Boolean,
      default() {
       
        return false;
      }
    },
    // 图标url
    iconUrl: {
       
      type: String,
      default() {
       
        return '';
      }
    },
    placeholder: {
       
      type: String,
      default: () => {
       
        return '检索关键字';
      }
    },
    // 是否需要关键字过滤
    isShowFilter: {
       
      type: Boolean,
      default: () => {
       
        return false;
      }
    }
  },
  data() {
       
    return {
       
      filterText: '',
      visible: false
    };
  },
  methods: {
       
    /**
     * @description: 对树节点进行筛选时执行的方法,返回 true 表示这个节点可以显示,返回 false 则表示这个节点会被隐藏
     * @param {*} value
     * @param {*} data
     * @return {*}
     */
    filterNode(value, data) {
       
      if(!value) return true;
      return data[this.treeProps.label].indexOf(value) !== -1;
    },
    /**
     * @description: 节点被展开时触发的事件
     * @param {*} data         该节点所对应的数据对象
     * @param {*} node         节点对应的Node对象
     * @param {*} vueComponent 节点组件本身
     * @return {*}
     */
    handleNodeExpand(data, node, vueComponent){
       
        this.$emit('handleNodeExpand', data);
    },
    /**
     * @description: 节点被关闭时触发的事件
     * @param {*} data         该节点所对应的数据对象
     * @param {*} node         节点对应的Node对象
     * @param {*} vueComponent 节点组件本身
     * @return {*}
     */
    handleNodeCollapse(data, node, vueComponent){
       
      this.$emit('handleNodeCollapse', data);
    },
    /**
     * @description: [事件] - 节点被点击时的回调
     * @param {*} data         该节点所对应的数据对象
     * @param {*} node         节点对应的Node对象
     * @param {*} vueComponent 节点组件本身
     * @return {*}
     */
    handleNodeClick(data, node, vueComponent) {
       
      this.$emit('handleNodeClick', data);
    },
    /**
     * @description: 事件 - 当某一节点被鼠标右键点击时会触发该事件
     * @param {*} event
     * @param {*} data         传递给 data 属性的数组中该节点所对应的对象
     * @param {*} node         节点对应的Node对象
     * @param {*} vueComponent 节点组件本身
     * @return {*}
     */
    handleNodeContextMenu(event, data, node, vueComponent){
       
      this.$emit('handleNodeContextMenu', event, data);
    },
       /**
     * @description: [事件] - 当复选框被点击的时候触发
     * @param {*} checkedNodes      该节点所对应的对象
     * @param {*} checkedKeys       树目前的选中状态对象
     * @param {*} halfCheckedNodes
     * @param {*} halfCheckedKeys
     * @return {*}
     */
    handleCheck(node, checkedData) {
       
      // console.log('handleCheck: ', node, checkedData)
      this.$emit('handleCheck', node, checkedData);
    },
    /**
     * @description: 事件 - 节点选中状态发生变化时的回调
     * @param {*}
     * @return {*}
     */
    handleCheckChange(data, checked) {
       
      let currentNode = this.$refs.baseTree.getNode(data);
      // console.log('handleCheckChange: ', data, checked)
      if(this.checkStrictly){
       
        // 用于:父子节点严格互不关联时,父节点勾选变化时通知子节点同步变化,实现单向关联
        if(checked) {
       
          // 选中 子节点只要被选中父节点就被选中
          this.parentNodeChange(currentNode);
        } else {
       
          // 未选中 处理子节点全部未选中
          this.childNodeChange(currentNode);
        }
      }
      this.$emit('handleCheckChange', data, checked);
    },
    // ------------------------------------------------------------------------
    /**
     * @description: 获取当前被选中节点的 key,使用此方法必须设置 node-key 属性,若没有节点被选中则返回 null
     * @param {*}
     * @return {*}
     */
    getCurrentKey: function () {
       
      return this.$refs.baseTree.getCurrentKey();
    },
    /**
     * @description: 获取当前被选中节点的 data,若没有节点被选中则返回 null
     * @param {*}
     * @return {*}
     */
    getCurrentNode: function () {
       
      return this.$refs.baseTree.getCurrentNode();
    },
    /**
     * @description:  通过 key 设置某个节点的当前选中状态,使用此方法必须设置 node-key 属性
     * @param {*} key 待被选节点的 key,若为 null 则取消当前高亮的节点
     * @return {*}
     */
    setCurrentKey:function(key){
       
      // $nextTick 是确保DOM渲染结束之后执行的
      this.$nextTick(() => {
       
        this.$refs.baseTree.setCurrentKey(key);
      })
    },
    setCurrentNode:function(node){
       
      // $nextTick 是确保DOM渲染结束之后执行的
      this.$nextTick(() => {
       
        this.$refs.baseTree.setCurrentNode(node);
      })
    },
    /**
     * @description: 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点的 key 所组成的数组
     * @param {*}
     * @return {*}
     */
    getCheckedKeys: function () {
       
      return this.$refs.baseTree.getCheckedKeys();
    },
    /**
     * @description: 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点所组成的数组
     * @param {*}
     * @return {*}
     */
    getCheckedNodes: function () {
       
      return this.$refs.baseTree.getCheckedNodes();
    },
    /**
     * @description: 通过 keys 设置目前勾选的节点,使用此方法必须设置 node-key 属性
     * @param {*} keys     勾选节点的 key 的数组
     * @param {*} leafOnly boolean 类型的参数,若为 true 则仅设置叶子节点的选中状态,默认值为 false
     * @return {*}
     */
    setCheckedKeys: function(keys){
       
      // if(!keys)keys = [];
      const leafOnly = false;
      this.$nextTick(() => {
       
        this.$refs.baseTree.setCheckedKeys(keys, leafOnly);
      })
    },
    /**
     * @description: 通过 key / data 设置某个节点的勾选状态,使用此方法必须设置 node-key 属性
     * @param {*} val       勾选节点的 key 或者 data
     * @param {*} checked   boolean 类型,节点是否选中
     * @param {*} deep      boolean 类型,是否设置子节点 ,默认为 false
     * @return {*}
     */
    setChecked: function(val, checked, deep){
       
      this.$refs.baseTree.setChecked(val, checked);
    },
    // --------------------------------------------------------------------------
    // 统一处理子节点为不选中
    childNodeChange (node) {
       
      for(let i = 0; i < node.childNodes.length; i++) {
       
        node.childNodes[i].checked = false;
        this.childNodeChange(node.childNodes[i]);
      }
    },
    // 统一处理父节点为选中
    parentNodeChange (node) {
       
      if(node.parent.key !== undefined) {
       
        node.parent.checked = true;
        this.parentNodeChange(node.parent);
      }
    }
  },
  computed: {
       
    /**
     * @description: 树形结构数据(非标准的转换为标准结构)
     * @param {*}
     * @return {*}
     */
    treeList() {
       
      return this.$attrs.treeList || [];
    }
  },
  watch: {
       
    filterText(val) {
       
      this.$refs.baseTree.filter(val);
    }
  }
};
script>

<style lang="scss">
// 字体和大小
.custom-tree-node {
       
  font-family:"Microsoft YaHei";
  font-size: $size14;
  position: relative;
}


// 选中状态背景色
.baseTree .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content {
       
    background-color: #F5F7FA !important;
}

// 原生el-tree-node的div是块级元素,需要改为inline-block,才能显示滚动条
.baseTree .el-tree >.el-tree-node {
       
  display: inline-block;
  min-width: 100%;
}

style>



treeDemo.vue:



<template>
  <div class="treeDemo">
    <el-container>
      <el-main>
        <baseTree
          ref="tree"
          :treeProps="props"
          :treeList="treeList"
          :accordion="isAccordion"
          :expandNode="false"
          :isShowFilter="true"
          @handleNodeClick="handleNodeClick"
          @handleNodeContextMenu="handleNodeContextMenu"/>
      el-main>
      <el-main>
        <span>父子不互相关联:check-strictly=truespan>
        <br />
        <br />
        <baseTree
          :multiple="true"
          :collapse="false"
          :checkStrictly="true"
          :treeProps="props"
          :treeList="treeList"
          :accordion="isAccordion"
          :expandNode="false"
          :isShowFilter="true"
          @handleCheck="handleCheck"
          @handleCheckChange="handleCheckChange"/>
      el-main>
      <el-main>
        <span>父子互相关联:check-strictly=falsespan>
        <br />
        <br />
        <baseTree
          :multiple="true"
          :collapse="false"
          :treeProps="props"
          :treeList="treeList"
          :accordion="isAccordion"
          :expandNode="false"
          :isShowFilter="true"
        />
      el-main>
      
        <div v-show="menuVisible">
          <ul id="menu" class="menu" @mouseleave="foo">
            <li class="menu_item" >新增li>
            <li class="menu_item" >删除li>
            <li class="menu_item" >上移li>
            <li class="menu_item" >下移li>
          ul>
        div>
    el-container>
  div>
template>



<script>
import baseTree from '_c/basics/base-tree';
export default {
       
  name:'demo',
  components: {
       
    baseTree
  },
  data() {
       
    return {
       
      props:{
        // 配置项(必选)
        id: 'id',
        label: 'name',
        pid: 'parentId',
        children: 'children'
        // disabled:true
      },
      // 数组
      list: [
          {
       id:1, parentId:0, name:'一级菜单A', rank:1},
          {
       id:2, parentId:0, name:'一级菜单B', rank:1},
          {
       id:3, parentId:0, name:'一级菜单C', rank:1},
          {
       id:4, parentId:1, name:'二级菜单A-A', rank:2},
          {
       id:5, parentId:1, name:'二级菜单A-B', rank:2},
          {
       id:6, parentId:2, name:'二级菜单B-A', rank:2},
          {
       id:7, parentId:4, name:'三级菜单A-A-A', rank:3},
          {
       id:15, parentId:0, name:'一级菜单C', rank:1},
          {
       id:16, parentId:0, name:'一级菜单C', rank:1},
          {
       id:17, parentId:0, name:'一级菜单C', rank:1},
          {
       id:18, parentId:0, name:'一级菜单C', rank:1}
      ],
      treeList:[],
      isClearable:true, // 可清空(可选)
      isAccordion:false, // 可收起(可选)
      menuVisible: false
    }
  },
  created(){
       
    this.initData();
  },
  methods: {
       
    initData(){
       
      this.treeList = this.listToTree(this.list, this.props);
      console.log(this.treeList);
    },
    handleNodeClick(data){
       
      console.log('handleNodeClick', data)
    },
    handleCheck(node, checkedData){
       
      console.log('handleCheck: ', node, checkedData)
    },
    handleCheckChange(data, checked){
       
      console.log('handleCheckChange: ', data, checked)
    },
    // ---------------------------------------------------------------
    /**
     * @description: 树节点右键事件
     * @param {*} event
     * @param {*} data
     * @return {*}
     */
    handleNodeContextMenu(event, data){
       
      // 设置当前节点
      this.$refs.tree.setCurrentNode(data);
      this.currentNode = data;
      // 弹出
      this.menuVisible = true;
      let menu = document.querySelector('#menu');
      /* 菜单定位基于鼠标点击位置 */
      document.addEventListener('click', this.foo) // 给整个document添加监听鼠标事件,点击任何位置执行foo方法
      menu.style.left = event.clientX + 20 + 'px';
      menu.style.top = event.clientY - 10 + 'px';
    },
    /**
     * @description: 取消鼠标监听事件 菜单栏
     * @param {*}
     * @return {*}
     */
    foo() {
       
      this.menuVisible = false;
      // 要及时关掉监听,不关掉的是一个坑,不信你试试,虽然前台显示的时候没有啥毛病,加一个alert你就知道了
      document.removeEventListener('click', this.foo);
    },
    // ---------------------------------------------------------------
    /**
     * @description        数组转树形数据
     * @param {数据数组}    list
     * @param {树结构配置}  config
     */
    listToTree(list, config) {
       
      let conf = {
       };
      Object.assign(conf, config);
      const nodeMap = new Map();
      const result = [];
      const {
        id, children, pid } = conf;
      for(const node of list) {
       
        // node[children] = node[children] || [];
        nodeMap.set(node[id], node);
      }
      for(const node of list) {
       
        const parent = nodeMap.get(node[pid]);
        (parent ? (parent.children ? parent.children : parent.children = []) : result).push(node);
      }
      return result;
    }
  }
}
script>

<style lang="scss" scoped>
// 树节点右键 - 弹出菜单栏
.treeDemo .menu {
       
  height: auto;
  width: 120px;
  color: #606266;
  position: absolute;
  padding: 0px;
  box-sizing: border-box;
  /*border: 1px solid #999999;*/
  text-align: center;
  background-color: #fff;
  border-radius: 0.25rem;
  box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}

.treeDemo .menu_item {
       
  line-height: 20px;
  text-align: center;
}
.treeDemo li:hover {
       
  background-color: rgba(232, 237, 250, 0.6);
  color: darkslategrey;
  cursor: pointer;
}
.treeDemo li {
       
  position: relative;
  display: flex;
  font-size: 14px;
  align-items: center;
  height: 34px;
  line-height: 34px;
  outline: none;
  margin-top: 0px;
  padding-left: 20px;
  padding-right: 5px;
}

style>





根据树节点右键事件:弹出菜单项:
Vue组件(一) - 二次封装ElementUI实现tree树形组件_第4张图片

你可能感兴趣的:(前端,vue,elementui,tree)