element-ui 组件使用之封装树形下拉选择组件

element-ui 组件使用之封装树形下拉选择组件

需求分析

  • 一、实现类似 el-select 下拉框与收缩功能
  • 二、展开结构为树形组件, 支持懒加载数据和完整树形结构数据
  • 三、支持数据双向绑定,数据静态搜索,限制选择等

实现 el-select

采用 el-input 模拟 select 组件, 并添加图标

<template>
  <div slot="reference" class="popover-input">
    <el-input placeholder="请选择" :value="inputValue" size="mini" readonly />
    <i class="el-icon-arrow-down input-icon" :class="{ 'input-focus': popoverShow }" />
  </div>
</template>

<script>
export default {
    data() {
        return {
            inputValue: '',
            popoverShow: false,
        }
    }
}
</script>

<style lang="scss" scoped>
.popover-input{
  position: relative;
  display: inline-block;
  .input-icon{
    position: absolute;
    top: 6px;
    right: 6px;
    color: #C0C4CC;
    transition: transform .3s;
  }
  .input-focus{
    transform: rotate(-180deg)
  }
}
</style>
复制代码

通过变量 popoverShow 控制图标方向

添加 popover 组件

<!--selectTree -->
<template>
  <el-popover
    v-model="popoverShow"
    placement="bottom"
    width="240"
    trigger="click"
  >
    <div slot="reference" class="popover-input">
      <el-input placeholder="请选择" :value="inputValue" size="mini" readonly />
      <i class="el-icon-arrow-down input-icon" :class="{ 'input-focus': popoverShow }" />
    </div>
  </el-popover>
</template>
复制代码

当 popover 展开图标向下, popover 收缩图标向上

添加 tree 组件

<template>
  <div class="tree">
    <div class="tree-search">
      <el-input v-model="filterText" clearable size="mini" placeholder="输入关键字进行过滤" />
    </div>
    <el-tree
      ref="tree"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      filterText: ''
    }
  },
  watch: {
    filterText(val) {
      this.$refs.tree.filter(val)
    }
  },
}
</script>

<style lang="scss" scoped>
.tree{
  height: 100%;
  background-color: #ffffff;
  .tree-search{
    height: 40px;
    line-height: 40px;
  }
  .tree-node{
    max-height: 300px;
    overflow-y: scroll;
    // 修复无法横向滚动问题
    ::v-deep>.el-tree-node{
      min-width:100%;
      display: inline-block;
    }
  }
}
</style>
复制代码

树形组件配置详解

参数 说明 类型
data 展示数据 array
load 加载子树数据的方法,仅当 lazy 属性为true 时生效 function(node, resolve)
props 属性名称配置选项(label, children, disabled, isLeaf) object
lazy 是否懒加载子节点,需与 load 方法结合使用 boolean
node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的, 固定为 id String
show-checkbox 节点是否可被选择 boolean
check-strictly 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法 boolean
expand-on-click-node 是否在点击节点的时候展开或者收缩节点 boolean
filter-node-method 对树节点进行筛选时执行的方法 Function(value, data, node)
highlight-current 是否高亮当前选中节点 boolean

树组件添加配置

<template>
  <div class="tree">
    <div class="tree-search">
      <el-input v-model="filterText" clearable size="mini" placeholder="输入关键字进行过滤" />
    </div>
    <el-tree
      ref="tree"
      :load="loadNode"
      :props="defaultProps"
      lazy
      node-key="id"
      show-checkbox
      check-strictly
      expand-on-click-node
      :filter-node-method="filterNode"
      highlight-current
    />
  </div>
  
</template>
<script>
export default {
  props: {
    loadNode: Function,
    defaultProps: {
      type: Object,
      default() {
        return {
          children: 'children',
          label: 'label',
          disabled: 'disabled'
        }
      }
    },
  },
  data() {
    return {
      filterText: ''
    }
  },
  watch: {
    filterText(val) {
      this.$refs.tree.filter(val)
    }
  },
  methods: {
    filterNode(value, data) {
      if (!value) return true
      return data[this.defaultProps.label].includes(value)
    },
  }
}
</script>
复制代码

之后就是把封装好的树形组件放入 selectTree 组件了

模拟数据

参照官网我们模拟一个获取数据的方法, 为了便于使用, 每条数据都加上 id

let index = 0 
loadNode(node, resolve) {
        if (node.level === 0) {
          return resolve([{ name: 'region', id: index++ }]);
        }
        if (node.level > 1) return resolve([]);

        setTimeout(() => {
          const data = [{
            name: 'leaf',
            leaf: true,
            id: index++
          }, {
            name: 'zone',
            id: index++
          }];

          resolve(data);
        }, 500);
 }
复制代码

父组件调用

<template>
  <select-tree v-model="value" :load-node="loadNode" :default-props="defaultProps" />
</template>

<script>
import selectTree from './components/selectTree'
let index = 0
export default {
  components: {
    selectTree
  },
  data() {
    return {
      value: [],
      defaultProps: {
        label: 'name',
        isLeaf: 'leaf'
      }
    }
  },
  methods: {
    loadNode(node, resolve) {
      if (node.level === 0) {
        return resolve([{ name: 'region', id: index++ }])
      }
      if (node.level > 1) return resolve([])

      setTimeout(() => {
        const data = [{
          name: 'leaf',
          leaf: true,
          id: index++
        }, {
          name: 'zone',
          id: index++
        }]

        resolve(data)
      }, 500)
    }
  }
}
</script>
复制代码

目前我们已经可以点击输入框展开树组件了, 但是数据绑定还没有,下面我们就开始实现的数据的双向绑定

树组件事件监听

树组件需要监听的事件有两个

  • node-click 树节点点击事件, 如果该节点可选, 那么点击后选中, 也可以配置 expand-on-click-node 让节点点击后展开树层
  • check 当复选框被点击的时候触发, 当调用内容方法时不会触发该事件

当这两个事件其中一个触发时, 我们选中值就应该发生改变, 故定义一个数据变动方法(暂时先做单选)

首先我们先要定义 v-model value 保存的值是什么,因为考虑都后续需要回写数据,故我们需要获取选中数据的所有层级数据, 那么 value 值应该为一个数组,并且该数组应该是其所有祖先元素并包含自己

例如:['0', '0-1', '0-1-1'] 表示当前选中的数据 ID 为 0-1-1 其父级元素 ID 为 0-1 , 其父级的父级为最高级, 并且 ID 为 0

由于 value 数据需要保存所有父级节点的信息, 但是其触发的两个事件的入参都只是当前节点信息, 故我们需要封装一个获取其祖先元素集合的方法

查看源码我们可以发现在 element-ui el-tree 组件中实例中保存了一个所有节点信息的对象 nodesMap,因此我们可以通过该对象遍历拿到选中节点所有父级元素集合

// 获取数据缓存
getNodesMap() {
  return this.$refs.tree.store.nodesMap
},
/**
 * 根据 id 获取所有父级
 */
getParentID(id) {
  const nodesMap = this.getNodesMap()
  const parentId = []
  let node = nodesMap[id]
  while (node.parent) {
    parentId.unshift(node.data.id)
    node = node.parent
  }
  return parentId
}
复制代码

多选我们暂时先不做, 先控制单选, 也就是选择后如果选中数据与较之前一致那么我们就反选值, 同时我们需要设置一下选中值的文本内容

element-ui 为我们提供了切换选择的方法

 setCheckedKeys(keys) {
   this.$refs.tree.setCheckedKeys(keys)
 }
复制代码
function selectChange(data) {
      const alTreeData = this.$refs.alTree
      const id = data.id
      let selectValue = alTreeData.getParentID(id)
      if (selectValue === this.value) {
        selectValue = []
        this.inputValue = ''
      } else {
        const { label } = alTreeData.getNode(id)
        this.inputValue = label
      }
      alTreeData.setCheckedKeys([id])
      this.$emit('value:change', selectValue)
}
复制代码

现在选择数据双向绑定已经实现, 那么接下来实现数据回写

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享