小言_互联网的博客

Vue - Element UI二次封装实现TreeSelect 树形选择器

716人阅读  评论(0)

功能描述

单选:

  • 默认初始值;
  • select输入框清空功能;
  • value数据双向绑定;

多选:

  • 默认初始值;
  • select输入框清空功能;
  • select输入框中的×删除勾选;
  • 支持角色菜单控制功能:check-strictly=true
    • 当点击勾选复选框时候,若状态为 选中
      • 其所有父节点 (父节点、父节点的父节点以此类推)全部统一跟随当前节点变化为选中;
      • 其所有子节点不跟随当前节点变化
    • 当点击勾选复选框时候,若状态为 未选中
      • 其所有父节点 不跟随当前节点变化;
      • 其所有子节点 全部统一跟随当前节点变化为 未选中;

check-strictly=false
从check-strictly=false父子互相关联的基础入手,需要解决的问题就是:
将尚未全部勾选的子节点对应的父节点改为半勾选状态,查找文档良久无果;
只有getHalfCheckedKeys和getHalfCheckedNodes,并没有设置成半勾选。

代码

treeSelect.vue

<!--
 * @Description: 树形选择器
 * @Author: HMM
 * @Date: 2021-05-24 10:27:34
 * @FilePath: \components\tree-select.vue
-->
<template>
  <div class="treeSelect">
    <el-select
      :value="valueTitle"
      :multiple="multiple"
      :collapse-tags="collapse"
      :clearable="clearable"
      :disabled="disabled"
      :size="size"
      :style="selectStyle"
      @input="$emit('input',$event)"
      @clear="handleClear"
      @remove-tag="handleRemoveTag"
    >
      <el-option :value="valueId" :label="label">
        <el-tree
          id="tree-option"
          ref="selectTree"
          :accordion="accordion"
          :show-checkbox="multiple"
          :data="options"
          :props="treeProps"
          :node-key="treeProps.id"
          :check-strictly="checkStrictly"
          :default-expand-all="expand"
          :auto-expand-parent="expandParent"
          :expand-on-click-node="expandNode"
          :default-expanded-keys="defaultExpandedKeys"
          @node-click="handleNodeClick"
          @check-change="handleCheckChange"
        >
        </el-tree>
      </el-option>
    </el-select>
  </div>
</template>



<style lang="scss" scoped>
 .el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
     
  height: auto;
  max-height: 274px;
  padding: 0;
  overflow: hidden;
  overflow-y: auto;
}

.el-scrollbar .el-select-dropdown__item.selected {
     
  font-weight: normal;
}

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

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

// ul li ::v-deep .el-tree .el-tree-node__content {
     
//   height: auto;
//   padding: 0 20px;
// }

// .el-tree-node__label {
     
//   font-weight: normal;
// }


.el-tree ::v-deep .is-current .el-tree-node__label {
     
  color: #1B65B9;
  font-weight: 700;
}

.el-tree ::v-deep .is-current .el-tree-node__children .el-tree-node__label {
     
  color: #606266;
  font-weight: normal;
}
</style>

<script>
export default {
     
  name: 'tree-select',
  props:{
     
    // 是否可多选,默认单选
    multiple: {
     
      type: Boolean,
      default: false
    },
    // 可清空选项
    clearable:{
     
      type:Boolean,
      default:() => {
      return true }
    },
    // -------------------- el-tree --------------------
    // 配置项
    treeProps:{
     
      type: Object,
      default:() => {
     
        return {
     
          id:'id', // ID字段名
          label: 'title', // 显示名称
          children: 'children' // 子级字段名
        }
      }
    },
    // 选项列表数据(树形结构的对象数组)
    options:{
     
      type: Array,
      default: () => {
      return [] }
    },
    // 自动收起
    accordion:{
     
      type:Boolean,
      default:() => {
      return false }
    },
    // 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false
    checkStrictly:{
     
      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;
      }
    },
    // -------------------- el-select --------------------
    // 初始值 - 绑定value是为了外面也可以传值改变到里面的值双向绑定
    value: {
     
      type: [String, Number, Boolean, Array],
      default: () => {
      return null }
    },
    // 多选时是否将选中值按文字的形式展示
    collapse: {
     
      type: Boolean,
      default: false
    },
    // 选择框大小
    size:{
     
      type:String,
      default:() => {
      return 'small' }
    },
    // 选择框 宽度
    width: {
     
      type: String,
      default: '270px'
    },
    // 是否禁用
    disabled:{
     
      type:Boolean,
      default:() => {
      return false }
    }
  },
  data() {
     
    return {
     
      valueId: this.value, // 初始值
      valueTitle: '',
      label:'', // 分组
      defaultExpandedKeys:[]
    }
  },
  mounted(){
     
    this.initHandle();
  },
  methods: {
     
    /**
     * @description: 初始化组件
     * @param {*}
     * @return {*}
     */
    initHandle(){
     
      // 单选
      if(this.valueId && !this.multiple){
     
        this.valueTitle = this.$refs.selectTree.getNode(this.valueId).data[this.treeProps.label]; // 初始化显示
        this.defaultExpandedKeys = [this.valueId]; // 设置默认展开
        this.$nextTick(() => {
     
          this.$refs.selectTree.setCurrentKey(this.valueId); // 设置默认选中
        })
      }
      // 多选
      if(this.valueId && this.multiple){
     
        this.defaultExpandedKeys = this.valueId; // 设置默认展开
        this.$nextTick(() => {
     
          this.$refs.selectTree.setCheckedKeys(this.valueId); // 设置默认选中
        })
      }
    },
    /**
     * @description: 单选 - 节点被点击时的回调,返回被点击的节点数据
     * @param {*}
     * @return {*}
     */
    handleNodeClick(node){
     
      this.valueTitle = node[this.treeProps.label];
      this.valueId = node[this.treeProps.id];
      this.defaultExpandedKey = [];
      this.$emit('getValue', this.valueId, this.valueTitle);
    },
    /**
     * @description: 多选,节点勾选状态发生变化时的回调
     * @param {*}
     * @return {*}
     */
    handleCheckChange (data, checked) {
     
      let currentNode = this.$refs.selectTree.getNode(data);
      if(this.checkStrictly){
     
        // 用于:父子节点严格互不关联时,父节点勾选变化时通知子节点同步变化,实现单向关联
        if(checked) {
     
          // 选中 子节点只要被选中父节点就被选中
          this.parentNodeChange(currentNode);
        } else {
     
          // 未选中 处理子节点全部未选中
          this.childNodeChange(currentNode);
        }
      }
      // 用于:父子节点严格关联时
      // console.log(this.$refs.selectTree.getHalfCheckedKeys())
      this.valueId = this.$refs.selectTree.getCheckedKeys();
      var checkedNodes = this.$refs.selectTree.getCheckedNodes();
      this.valueTitle = checkedNodes.map((node) => {
     
        return node[this.treeProps.label];
      });
      this.defaultExpandedKeys = [];
      this.$emit('getValue', this.valueId, this.valueTitle);
    },
    /**
     * @description: 清除选中
     * @param {*}
     * @return {*}
     */
    handleClear(){
     
      this.valueTitle = '';
      this.valueId = '';
      this.defaultExpandedKeys = [];
      // 清除树
      if(this.multiple){
     
        this.$refs.selectTree.setCheckedKeys(null);
      } else {
     
        this.$refs.selectTree.setCurrentKey(null);
      }
      this.$emit('getValue', null);
    },
    /**
     * @description: 多选 删除任一标签选项的回调
     * @param {*}
     * @return {*}
     */
    handleRemoveTag(val){
     
      var checkedNodes = this.$refs.selectTree.getCheckedNodes();
      var node = checkedNodes.find(node => node[this.treeProps.label] === val);
      this.$refs.selectTree.setChecked(node[this.treeProps.id], false);
    },
    // 统一处理子节点为不选中
    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);
      }
    }
  },
  watch: {
     
    value(){
     
      this.valueId = this.value
      this.initHandle()
    },
    // 父子组件双向绑定value
    valueId(){
     
      this.$emit('input', this.valueId);
    }
  },
  computed:{
     
    selectStyle() {
     
      return {
     
        width: `${
       this.width}`
      };
    }
  }
};
</script>


treeSelectDemo.vue


<!--
 * @Description: 树形下拉框 - 使用示例
 * @Author: HMM
 * @Date: 2021-01-14 16:04:43
 * @FilePath: \treeSelectDemo.vue
-->

<template>
  <div class="messageDemo">
    <el-container>
      <el-main>
        <treeSelect
          :treeProps="props"
          :options="treeSelectList"
          v-model="valueId"
          :clearable="isClearable"
          :accordion="isAccordion"
          :expandNode="false"
          size="small"
          width="100%"
          @getValue="getValue($event)"
        />
      </el-main>
      <el-main>
        <span>父子不互相关联:check-strictly=true</span>
        <treeSelect
          :multiple="true"
          :collapse="false"
          :checkStrictly="true"
          :treeProps="props"
          :options="treeSelectList"
          v-model="valueIds"
          :clearable="isClearable"
          :accordion="isAccordion"
          :expandNode="false"
          size="small"
          width="360px"
          @getValue="getValue2($event)"
        />
      </el-main>
      <el-main>
        <span>父子互相关联:check-strictly=false</span>
        <treeSelect
          :multiple="true"
          :collapse="false"
          :treeProps="props"
          :options="treeSelectList"
          v-model="valueIds2"
          :clearable="isClearable"
          :accordion="isAccordion"
          :expandNode="false"
          size="small"
          width="360px"
          @getValue="getValue2($event)"
        />
      </el-main>
    </el-container>
  </div>
</template>


<!-- JS -->
<script>
import treeSelect from '../components/tree-select';
export default {
     
  name:'demo',
  components: {
     
    treeSelect
  },
  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}
      ],
      treeSelectList:[],
      isClearable:true, // 可清空(可选)
      isAccordion:false, // 可收起(可选)
      valueId:null, // 初始ID(可选)
      valueIds:[],
      valueIds2:[]
    }
  },
  created(){
     
    this.initData();
  },
  methods: {
     
    initData(){
     
      this.treeSelectList = this.listToTree(this.list, this.props);
      console.log(this.treeSelectList);
    },
    /**
     * @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;
    },
    // 树形选择器 - 取值
    getValue(value){
     
      // this.valueId = value
      console.log('getValue', value, this.valueId);
    },
    // 树形选择器 - 取值
    getValue2(value){
     
      // this.valueId = value
      console.log('getValue2', value, this.valueIds);
    }
  },
  watch:{
     
    valueId(){
     
      console.log('valueId', this.valueId);
    },
    valueIds(){
     
      console.log('valueIds', this.valueIds);
    }
  }

}
</script>


参考:

基于Element-UI的组件改造的树形选择器(树形下拉框)

Element-UI二次封装实现TreeSelect 树形下拉选择组件

element ui的el-tree多选树(复选框)父子节点关联不关联的问题

vue+element-ui之tree树形控件有关子节点和父节点之间的各种选中关系详解


转载:https://blog.csdn.net/Trista_1999/article/details/117219219
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场