iview-select-tree树型选择器组件封装

iview-select-tree树型选择器组件封装

实现效果: 二级单选,二级多选(新建与编辑)
具体效果:
1、数据手风琴样式显示,默认第一组数据展开
2、每次只能选择同一组数据,选择不同组数据时原来被选择的数据清除
3、编辑时只用传一组数据,因为不能选择不同组数据
4、编辑时原先被选中的数据不能被删除,只能新增同组下的其他数据
数据格式:

// treeData: flag - 新建或编辑; title,value必填,name必须没有; 
// children里面的name,title必填,value前一个数字必须与父级一致。即父级value为m,则子级value为m-n
[
	{
	  checked: false,
	  expand: true,
	  flag: "add",
	  selected: false,
	  title: "单元测试1",
	  value: "0",
	  children: [
	    {
	      checked: false,
	      id: "00",
	      level: "1",
	      name: "pp1",
	      selected: false,
	      title: "pp1",
	      value: "0-0",
	    },
	    {
	      checked: false,
	      id: "01",
	      level: "2",
	      name: "pp2",
	      selected: false,
	      title: "pp2",
	      value: "0-1",
	    }
	  ],
	},
	{
	  checked: false,
	  expand: false,
	  flag: "add",
	  selected: false,
	  title: "单元测试2",
	  value: "1",
	  children: [
	    {
	      checked: false,
	      id: "10",
	      level: "3",
	      name: "pp3",
	      selected: false,
	      title: "pp3",
	      value: "1-0",
	    },
	    {
	      checked: false,
	      id: "11",
	      level: "4",
	      name: "pp4",
	      selected: false,
	      title: "pp4",
	      value: "1-1",
	    }
	  ],
	}
]
<template>
  <div class="ivu-select ivu-select-default">
    <div tabindex="0" class="ivu-select-selection ivu-form-item-content">
      <div @mouseover="mouseover" @mouseleave="mouseleave">
        <div @click="clickInputShow">
          <div v-show="multiple" class="ivu-tag ivu-tag-checked " v-for="(item,index) in multipleShowVal" :key="item.id">
            <span class="ivu-tag-text" :class="{'notClick': clickDisabled[item.id]}">{{item.title}}</span>
            <i class="ivu-icon ivu-icon-ios-close" :class="{'notClick': clickDisabled[item.id]}" @click.stop="!clickDisabled[item.id]?removeVal(index): ''"></i>
          </div>
          <span v-show="!multiple && queryVal!=''" class="ivu-select-selected-value">{{queryVal.title}}</span>
          <span v-if="multipleShowVal.length === 0 && multiple " class="ivu-select-placeholder">请选择</span>
          <span v-if="queryVal === '' && !multiple " class="ivu-select-placeholder">请选择</span>
        </div>
        <i :class="'ivu-icon ivu-icon-' +iconVal+ ' ivu-select-arrow'" @click="clickIcon"></i>
      </div>
      <div v-show="showTree" class="ivu-select-dropdown"
           style="max-height: 200px;overflow-y:scroll;z-index:9999;width:100%;" >
        <div style="width: 95%;margin-left: 10px;">
          <Tree 
            :data="queryData" 
            :multiple="multiple" 
            ref="tree" 
            @on-select-change="selectChange" 
            @on-check-change="selectChange" 
            @on-toggle-expand="toggleExpand" 
            show-checkbox
            check-directly>
          </Tree>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    name: 'selectTree',
    props: {
      // 下拉树的数据
      treeData: {
        type: Array
      },
      // 指定选中项目的 value 值,可以使用 v-model 双向绑定数据。单选时只接受 String 或 Number,多选时只接受 Array
      value: {},
      // 是否允许多选
      multiple: {
        type: Boolean,
        default: true
      },
      // 是否可以清空选项,只在单选时有效
      clearable: {
        type: Boolean,
        default: false
      },
      // 是否禁用或启用当前的下拉框
      disabled: {
        type: Boolean,
        default: true
      }
    },
    data () {
      return {
        queryVal: '',
        hideValue: '',
        valueType: 'string',
        multipleShowVal: [], // 选中的title值
        multipleHideVal: [], // 选中的value值
        showTree: false,
        iconVal: 'ios-arrow-down',
        cloneData: JSON.parse(JSON.stringify(this.treeData)),
        showData: [],
        queryData: [],
        clickDisabled: {},
      }
    },
    methods: {
      clickIcon () {
        if(this.disabled){
          if (this.iconVal === 'ios-close-circle') {
            this.clearVal()
          } else {
            this.showSelectTree()
          }
        }
      },
      pickTree (val) {
        if (this.valueType === 'string') {
          for (let i = 0; i < this.showData.length; i++) {
            if (this.showData[i].value === val) {
              this.showData[i].selected = false;
              this.showData[i].checked = false;
            } else if (this.showData[i].children !== undefined) {
              this.recursionPickTree(val, this.showData[i].children)
            }
          }
        } else {
          for (let j = 0; j < val.length; j++) {
            for (let i = 0; i < this.showData.length; i++) {
              if (this.showData[i].value === val[j]) {
                this.showData[i].selected = false;
                this.showData[i].checked = false;
              } else if (this.showData[i].children !== undefined) {
                this.recursionPickTree(val[j], this.showData[i].children)
              }
            }
          }
        }
      },
      recursionPickTree (val, data) {
        for (let i = 0; i < data.length; i++) {
          if (data[i].value === val) {
            data[i].selected = false;
            data[i].checked = false;
          } else if (data[i].children !== undefined && data[i].children.length > 0) {
            this.recursionPickTree(val, data[i].children)
          }
        }
      },
      clearVal () {
        if(this.disabled){
          if (this.clearable && !this.multiple && this.iconVal === 'ios-close-circle') {
            this.pickTree(this.hideValue)
            this.queryVal = ''
            this.hideValue = ''
            if (this.showTree) {
              this.iconVal = 'ios-arrow-up'
            } else {
              this.iconVal = 'ios-arrow-down'
            }
            this.$emit('input', '')
          }
        }
      },
      mouseover () {
        if (this.clearable && !this.multiple && this.iconVal !== 'ios-close-circle' && this.hideValue !== '') {
          this.iconVal = 'ios-close-circle'
        }
      },
      mouseleave () {
        if (this.clearable && !this.multiple) {
          if (this.showTree) {
            this.iconVal = 'ios-arrow-up'
          } else {
            this.iconVal = 'ios-arrow-down'
          }
        }
      },
      selectChange (obj,curentObj) {
        // 当前已选中的节点数组obj、当前项curentObj
        let hideVal;
        let showVal;
        if(this.multiple) {
          let obj1 = [], obj2 = [];
          obj.filter(item => {
            // children项有name值,obj里面有选中数据的父元素
            if(item.name && item.value.toString().charAt(0) === curentObj.value.toString().charAt(0)) {
              obj1.push(item); // 当前需要选中的数据
            } else if(item.value.toString().charAt(0) !== curentObj.value.toString().charAt(0)) {
              obj2.push(item); // 上次选中的数据
            }
          })
          for(let i in obj2) { // 上次选中的数据取消选中
            obj2[i].checked = false;
            obj2[i].selected = false;
          }
          if(obj2.length !== 0) { 
            for(let i in this.showData) {
              if(this.showData[i].value === obj2[0].value.toString().charAt(0)) {
                this.showData[i].indeterminate = false; // 上次选中的数据父节点取消半选
              }
            }
          }
          this.multipleShowVal = [];
          this.multipleHideVal = [];
          let len = obj1.length;
          if(len) {
            for(let i = 0; i < len; i++) {
              this.multipleShowVal.push(obj1[i]);
              this.multipleHideVal.push(obj1[i].value);
            }
          }
          this.$emit('input', this.multipleHideVal);
          hideVal = this.multipleHideVal;
          showVal = this.multipleShowVal; 
        }else {
          // 过滤掉所选择的父元素
          let obj1 = [], obj2 = [];
          if(obj.length !== 0) {
            if(!isNaN(Number(curentObj.value))) {
              // 点击的是父元素
              obj1.push(curentObj.children[0]);
              obj2 = obj.filter(item => {
                return item.value !== curentObj.children[0].value
              })
            } else {
              // 点击子元素
              obj1.push(curentObj)
              obj2 = obj.filter(item => {
                return item.value !== curentObj.value
              })
            }
          }
          for(let i in obj2) { // 上次选中的数据取消选中
            obj2[i].checked = false;
            obj2[i].selected = false;
            for(let j in this.showData) {
              if(this.showData[j].value === obj2[i].value.toString().charAt(0)) {
                this.showData[j].indeterminate = false; // 上次选中的数据父节点取消半选
              }
            }
          }
          if(obj1.length !== 0) {
            this.queryVal = obj1[0];
            this.hideValue = obj1[0].value;
            this.$emit('input', obj1[0].value);
          } else {
            this.queryVal = '';
            this.hideValue = '';
            this.$emit('input', '');
          }
          this.showTree = false;
          this.iconVal = 'ios-arrow-down';
          hideVal = this.hideValue;
          showVal = this.queryVal;
          this.$emit("on-select-change",  this.queryVal,this.hideValue);
          this.$emit("on-check-change",  this.queryVal,this.hideValue);
        }  
        this.$emit("on-select-change", hideVal, showVal);
        this.$emit("on-check-change", hideVal, showVal);
      },
      toggleExpand(val) {
        // 手风琴效果,每次只展开一组数据
        // 不选择任何数据时,展开与否与treeData有关;选择任意数据时,展开与否与showData有关
        for(let i in this.treeData) {
          if(this.treeData[i].value !== val.value) {
            this.treeData[i].expand = false;
          } 
        }
        for(let i in this.showData) {
          if(this.showData[i].value !== val.value) {
            this.showData[i].expand = false;
          } 
        }
      },
      // 点击图标的时候展示树形菜单
      clickInputShow () {
        if(this.disabled){
          this.showSelectTree()
        }
      },
      showSelectTree () {
        if (this.showTree) {
          this.showTree = false
        } else {
          this.showTree = true
        }
        if (this.iconVal !== 'ios-close-circle') {
          if (this.iconVal === 'ios-arrow-down') {
            this.iconVal = 'ios-arrow-up'
          } else if (this.iconVal === 'ios-arrow-up') {
            this.iconVal = 'ios-arrow-down'
          }
        }
      },
      // 多选模式的时候删除节点的数据
      removeVal (index) {
        if(this.disabled) {  
          if(this.showData[0].flag == 'add') {
            let showData = this.showData.filter(item => {
              return item.value === this.multipleHideVal[index].toString().charAt(0)
            })
            showData[0].selected = false;
            showData[0].checked = false;
            let len = showData[0].children.length;
            for (let i = 0; i < len; i++) {
              if(showData[0].children[i].value === this.multipleHideVal[index]) {
                showData[0].children[i].selected = false;
                showData[0].children[i].checked = false;
              }
            }
            this.multipleShowVal.splice(index, 1);
            this.multipleHideVal.splice(index, 1);
            if(this.multipleHideVal.length === 0) {
              showData[0].indeterminate = false;
            }
          } else if(this.showData[0].flag == 'update') {
            this.showData[0].selected = false;
            this.showData[0].checked = false;
            let len = this.showData[0].children.length;
            for (let i = 0; i < len; i++) {
              if(this.showData[0].children[i].value === this.multipleHideVal[index]) {
                this.showData[0].children[i].selected = false;
                this.showData[0].children[i].checked = false;
              }
            }
            this.multipleShowVal.splice(index, 1);
            this.multipleHideVal.splice(index, 1);
          } 
        }
      },
      isNotClick() {
        if(this.showData.length && this.showData[0].flag == 'update') {
          for(let i in this.showData[0].children) {
            this.clickDisabled[this.showData[0].children[i].id] = this.showData[0].children[i].disabled;
          }
        }
      },
      // 验证当前下拉
      checkValueType () {
        if (typeof this.value !== 'string') {
          this.valueType = 'array'
        }
      },
      initQueryMultiple () {
        if(this.value) {
          for (let i = 0; i < this.value.length; i++) {
            for (let j = 0; j < this.cloneData.length; j++) {
              if (this.cloneData[j].value === this.value[i]) {
                this.multipleShowVal.push(this.cloneData[j])
                this.multipleHideVal.push(this.cloneData[j].value)
                this.$emit('input', this.multipleHideVal)
                this.cloneData[j].selected = true;
                this.cloneData[j].checked = true;
                break
              } else if (this.cloneData[j].children !== undefined) {
                this.recursionQueryTreeData(this.cloneData[j].children, this.value[i])
              }
            }
          }
        }
      },
      initQueryVal () {
        this.$emit('input', '')
        for (let i = 0; i < this.cloneData.length; i++) {
          if (this.cloneData[i].value === this.value) {
            this.hideValue = this.cloneData[i].value
            this.queryVal = this.cloneData[i]
            this.$emit('input', this.cloneData[i].value)
            this.cloneData[i].selected = true;
            this.cloneData[i].checked = true;
            return
          } else if (this.cloneData[i].children !== undefined) {
            this.recursionQueryTreeData(this.cloneData[i].children, this.value)
          }
        }
      },
      recursionQueryTreeData (data, val) {
        for (let i = 0; i < data.length; i++) {
          if (data[i].value === val) {
            // 当前会多选
            if (this.multiple) {
              this.multipleShowVal.push(data[i])
              this.multipleHideVal.push(data[i].value)
              this.$emit('input', this.multipleHideVal)
            } else {
              this.hideValue = data[i].value
              this.queryVal = data[i]
              this.$emit('input', data[i].value)
            }
            data[i].selected = true;
            data[i].checked = true;
          } else if (data[i].children !== undefined) {
            this.recursionQueryTreeData(data[i].children, val)
          }
        }
      },
      removeShowVal(data,val,showVal,hideVal){
        for(let j = 0; j < data.length; j++){
          if(data[j].value === val){
            showVal.push(data[j])
            hideVal.push(data[j].value)
            break
          } else if(data[j].children!=undefined && data[j].children.length>0){
            this.removeShowVal(data[j].children,val,showVal,hideVal)
          }
        }
      }
    },
    watch:{
      treeData(val){
        this.cloneData = JSON.parse(JSON.stringify(val));
        this.showData = JSON.parse(JSON.stringify(this.cloneData));
        this.queryData = this.showData;
        let multipleHideValNew = [];
        let multipleShowValNew = [];
        if (this.multiple) {
          for(let i = 0;i<this.multipleHideVal.length;i++){
            this.removeShowVal(this.showData,this.multipleHideVal[i],multipleShowValNew,multipleHideValNew);
          }
          this.multipleHideVal = multipleHideValNew;
          this.multipleShowVal = multipleShowValNew;
        } else {
          this.removeShowVal(this.showData,this.hideValue,multipleShowValNew,multipleHideValNew);
          if(multipleHideValNew.length === 0){
            this.pickTree(this.hideValue);
            this.queryVal = '';
            this.hideValue = '';
            this.$emit('input', '');
          }
        }
      }
    },
    mounted () {
      // 因为父组件中要传递的 props 属性 是通过 发生ajax请求回来的, 请求的这个过程是需要时间的,
      // 但是子组件的渲染要快于ajax请求过程,所以此时 created 、 mounted 这样的只会执行一次的生命周期钩子,已经执行了,
      // 但是 props 还没有流进来(子组件),所以只能拿到默认值。
      // 解决方法: 
      // 1、父组件用v-if,当父组件有数据才渲染;
      // 2、子组件用watch,并且加上deep属性,可以直接在handler里面传入在子组件定义好的方法;
      this.checkValueType();
      // 要先初始化选中的数据,再将初始化好的数据赋值给到当前的tree,否则将导致选中的效果显示不出来
      if (this.multiple) {
        this.initQueryMultiple();
      } else {
        this.initQueryVal();
      }
      if(this.treeData.length && this.treeData[0].flag == 'add') {
        this.multipleShowVal = [];
        this.multipleHideVal = [];
        this.showData = JSON.parse(JSON.stringify(this.treeData));
        this.queryData = this.treeData;
        this.$emit('input', '');
      } else if(this.treeData.length && this.treeData[0].flag == 'update') {
        this.showData = JSON.parse(JSON.stringify(this.cloneData));
        this.queryData = this.showData;
      }
      this.isNotClick();
    }
  }
</script>
<style lang="less" scoped>
  .ivu-select-placeholder {
    display: block;
    height: 30px;
    line-height: 30px;
    color: #c5c8ce;
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding-left: 8px;
    padding-right: 22px;
  }
  .ivu-select-selected-value {
    display: block;
    height: 30px;
    line-height: 30px;
    font-size: 14px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding-left: 8px;
    padding-right: 24px;
  }
  .notClick {
    // pointer-events: none;
    color:blue;
    cursor:not-allowed;
  }
  /deep/ .ivu-select-item-selected.ivu-select-item-focus {
    background: #fff;
  }
  /deep/ .ivu-select-item-selected, .ivu-select-item-selected:hover {
    background: #fff;
  } 
  /deep/ .ivu-select-item-focus, .ivu-select-item:hover {
    background: #fff;
  }
  /deep/ .ivu-tree ul {
    text-align: left;
  }
</style>
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值