基于elementui的el-select、el-tree、el-popper和vue2自定义的ts-tree-select下拉树组件

通过使用el-popper和el-tree替换el-select原下拉框实现下拉树组件

预览

单选

多选

代码

<template>
  <div class="ts-tree-select">
    <el-popover placement="bottom" ref="el-popper"
                popper-class="ts-tree-popper"
                trigger="click">
      <el-tree ref="el-tree"
               :show-checkbox="multiple"
               :expand-on-click-node="multiple"
               :default-checked-keys="defaultCheckedKeys"
               :render-after-expand="false"
               :filter-node-method="filterNode"
               :node-key="valueKey"
               @node-click="treeNodeClick"
               @check-change="treeNodeCheck"
               :data="treeOptions"
               v-bind="treeAttribute"
               v-on="treeListener"></el-tree>
      <el-select slot="reference" :value="selectData" popper-class="ts-select-popper" @remove-tag="removeTag"
                 :value-key="valueKey"
                 @blur="blurFn"
                 filterable
                 :filter-method="filterMethod"
                 :multiple="multiple"
                 v-bind="selectAttribute" v-on="selectListener" ref="el-select">
        <el-option v-if="multiple" v-for="item in selectData" :label="item[selectProps.label]"
                   :value="item"></el-option>
        <el-option v-else :label="selectData[selectProps.label]" :value="selectData"></el-option>
      </el-select>
    </el-popover>
  </div>
</template>

<script>
export default {
  name: 'TsTreeSelect',
  props: {
    selectData: {
      type: [Object, Array],
      required: true,
    },
    valueKey: {
      type: String,
    },
    treeOptions: {
      required: true,
      type: Array,
    },
    selectProps: {
      type: Object,
      default() {
        return {
          label: 'label',
          value: 'value'
        }
      }
    },
    selectAttribute: {},
    selectListener: {},
    treeAttribute: {},
    treeListener: {},
    multiple: {
      type: Boolean,
      default() {
        return false;
      }
    },
    beforeRemoveTag: {
      type: Function,
      default(tag, resolve) {
        resolve()
      }
    }
  },
  model: {
    prop: 'selectData',
    event: 'updateSelectData'
  },
  data() {
    return {
      defaultCheckedKeys: [],
      minWidth: '',
      filterText: ''
    }
  },
  watch: {
    filterText(val) {
      this.$refs['el-tree'].filter(val);
    }
  },
  mounted() {
    if (this.multiple) {
      if (this.valueKey) {
        this.defaultCheckedKeys = this.selectData.map(item => {
          return item[this.valueKey]
        })
      } else {
        throw '多选必须传入valueKey作为唯一标识'
      }
    }
    this.minWidth = this.$refs['el-select'].$el.getBoundingClientRect().width;
    this.$refs['el-popper'].$refs['popper'].style.minWidth = this.minWidth + 'px';
  },
  methods: {
    filterMethod(val) {
      this.filterText = val
    },
    blurFn() {
      this.filterText = ''
    },
    filterNode(value, data) {
      if (!value) return true;
      const label = this.$refs['el-tree'].props.label;
      return data[label].indexOf(value) !== -1;
    },
    treeNodeClick(data, node, ref) {
      if (!this.multiple) {
        this.$emit('updateSelectData', data);
        this.$nextTick(() => {
          this.$refs['el-select'].$el.click();
          this.$refs['el-select'].blur();
        })
      }
    },
    treeNodeCheck(data, isSelect) {
      let newList = [];
      if (isSelect) {
        newList = this.selectData.concat(data);
      } else {
        newList = this.selectData.filter(item => {
          return item[this.valueKey] !== data[this.valueKey]
        })
      }
      this.$emit('updateSelectData', newList)
      this.$nextTick(() => {
        this.$refs['el-popper'].createPopper();
      })
    },
    removeTag(tag) {
      if (this.multiple) {
        this.beforeRemoveTag(tag, () => {
          this.$refs['el-tree'].setChecked(tag, false);
          let newList = this.selectData.filter(item => {
            return item[this.valueKey] !== tag[this.valueKey];
          })
          this.$emit('updateSelectData', newList)
        })
      }
    }
  }
}
</script>
<style>
.ts-select-popper {
  display: none !important;
}

.ts-tree-popper {
  max-height: 226px;
  overflow: auto;
  box-sizing: border-box;
}
</style>



多选使用示例

<template>
  <div id="app">
    <ts-tree-select v-model="selectData" value-key="value" :multiple="true" :tree-options="treeOptions"
                    :before-remove-tag="beforeRemoveTag"></ts-tree-select>
  </div>
</template>

<script>
import tsTreeSelect from './components/tsTreeSelect.vue'

export default {
  name: 'App',
  data() {
    return {
      selectData: [],
      treeOptions: [
        {
          value: '1',
          label: 'Level one 1',
          children: [
            {
              value: '1-1',
              label: 'Level two 1-1',
              children: [
                {
                  value: '1-1-1',
                  label: 'Level three 1-1-1',
                },
              ],
            },
          ],
        },
        {
          value: '2',
          label: 'Level one 2',
          children: [
            {
              value: '2-1',
              label: 'Level two 2-1',
              children: [
                {
                  value: '2-1-1',
                  label: 'Level three 2-1-1',
                },
              ],
            },
            {
              value: '2-2',
              label: 'Level two 2-2',
              children: [
                {
                  value: '2-2-1',
                  label: 'Level three 2-2-1',
                },
              ],
            },
          ],
        },
        {
          value: '3',
          label: 'Level one 3',
          children: [
            {
              value: '3-1',
              label: 'Level two 3-1',
              children: [
                {
                  value: '3-1-1',
                  label: 'Level three 3-1-1',
                },
              ],
            },
            {
              value: '3-2',
              label: 'Level two 3-2',
              children: [
                {
                  value: '3-2-1',
                  label: 'Level three 3-2-1',
                },
              ],
            },
          ],
        },
      ]
    }
  },
  components: {
    tsTreeSelect
  },
  methods:{
    // 移除tag提示
    beforeRemoveTag(tag,resolve){
      this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        center: true
      }).then(() => {
        resolve()
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    }
  }
}
</script>

单选

移除multiple

特殊属性

selectProps 用于自定义显示的label 例如:{label:‘objectName’},value暂不支持自定义。
selectAttribute 用于传递el-select原生属性如disabled size clearable等属性 例如:{disabled:true,loading:false}
selectListener 用于监听el-select原生事件 例如:{change:this.handleChange}
treeAttribute,treeListener类似
beforeRemoveTag:多选模式,移除tag标签前触发的方法

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