记录一下Vue2 TreeSelect 树形选择器组件

61 阅读1分钟

记录一下在Vue2中Element-ui没有树形组件的问题,感觉找的一些解决方案都没有实际解决我的问题

包含功能,树形单选,树形多选,树形关联父级多选,树形不关联父级多选


<template>
  <Select ref="selectRef" :value="value" filterable :filter-method="setFilterKeyword" default-first-option clearable
    collapse-tags v-bind="selectProps" @remove-tag="(value) => setCheckedKeys(value, { isRemove: true })"
    @clear="handleClear" @visible-change="visibleChange">
    <template #header>
      <slot name="header" />
    </template>
    <template #content>
      <div class="oper-tree-select">
        <el-tree ref="treeRef" :data="options" :props="defaultProps" @node-click="selectChange" v-bind="treeProps"
          :filter-node-method="filterNode" :expand-on-click-node="treeProps.expandOnClickNode"
          :default-checked-keys="currentValue" :render-content="renderContent"
          :node-key="treeProps.nodeKey || 'value'"></el-tree>
      </div>
    </template>
    <template #footer>
      <slot name="footer" />
    </template>
  </Select>
</template>
<script>
export default {
  name: "TreeSelect",
  model: { prop: "value", event: "update", },
  props: {
    value: {
      type: [Array, String],
    },
    /**
     * Select组件属性
     */
    selectProps: {
      type: Object,
    },
    /**
     * Tree组件属性
     */
    treeProps: {
      type: Object,
    },
    /**
     * 字段映射
     */
    field: {
      type: Object,
    },
    /**
     * 排除父级在value的选中
     */
    excludeParent: {
      type: Boolean,
      default: false,
    },
    /**
     * 树形选项
     */
    options: {
      type: Array,
    },
  },
  emits: ['change', 'update', 'clear'],
  data() {
    return {
      cacheOptions: new Map(),
      keyword: '',
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.setSelectOptions(true)
    })
  },
  computed: {
    currentValue() {
      return Array.isArray(this.value) ? this.value ?? [] : [this.value]
    },
    select() {
      return this.$refs.selectRef.$refs.selectRef
    },
    tree() {
      return this.$refs.treeRef
    },
    defaultProps() {
      return {
        label: (data) => data.label || data.value,
        value: 'value',
        children: 'children',
        ...this.field
      }
    }
  },
  watch: {
    value() {
      setTimeout(() => {
        this.setSelectOptions()
      })
    },
    options() {
      setTimeout(() => {
        this.setSelectOptions()
      })
    }
  },
  methods: {
    renderContent(h, { data, node }) {
      const isSelected = this.cacheOptions.has(this.getValue(data, node))
      return h('span', {
        class: ['el-tree-node__label', { 'is-selected': isSelected }]
      }, this.getLabel(data, node))
    },
    setFilterKeyword(keyword) {
      this.keyword = keyword
      this.$refs.treeRef.filter(keyword);
      this.select.filteredOptionsCount = 1
    },
    filterNode(value, data, node) {
      return this.getLabel(data, node).indexOf(value) !== -1;
    },
    visibleChange(status) {
      if (!status && this.keyword != '') {
        setTimeout(() => this.setFilterKeyword(''), 300)
      }
    },
    getNodeValByProp(field) {
      const prop = this.defaultProps[field]
      return typeof prop === 'function' ? prop : (data) => data[prop]
    },
    getChildren(data) {
      return data && this.getNodeValByProp('children')(data)
    },
    getLabel(data, node) {
      return data && this.getNodeValByProp('label')(data, node)
    },
    getValue(data, node) {
      return data && this.getNodeValByProp('value')(data, node)
    },
    updateValue(value) {
      this.$emit('update', value)
      this.$emit('change', value)
      if (this.select) this.select.filteredOptionsCount = 1
    },
    setCheckedKeys(data, node) {
      const toValue = (data) => this.getValue(data, node)
      const value = node.isRemove ? data : toValue(data)
      const { multiple = false } = this.selectProps ?? {}
      let values = typeof this.value === 'string' ? [this.value] : (this.value ?? [])
      const index = values.findIndex((item) => item === value)
      if (multiple) {
        (index > -1) ? values.splice(index, 1) : values.push(value)
      } else {
        values = index === -1 ? [value] : []
      }
      if (index === -1 && this.cacheOptions.has(value)) {
        this.tree.setChecked(data, false, true)
      } else {
        this.tree.setCheckedKeys(values)
      }
      let options = this.tree.getCheckedNodes()
      if (this.treeProps.autoExpandParent === false || this.excludeParent) {
        options = options.filter(item => !this.getChildren(item))
      }
      const result = multiple ? options.map(item => toValue(item)) : toValue(options[0])
      this.updateValue(result)
    },
    selectChange(data, node) {
      const { expandOnClickNode = true } = this.treeProps ?? {}
      if ((!expandOnClickNode) || (expandOnClickNode && node.isLeaf)) {
        this.setCheckedKeys(data, node)
      }
    },
    setSelectOptions() {
      const options = this.tree.getCheckedNodes()
      this.cacheOptions = new Map(options.map(item => [this.getValue(item), item]))
      let values = this.cacheOptions.values().toArray()
      if (this.treeProps.autoExpandParent === false || this.excludeParent) {
        values = values.filter(item => !this.getChildren(item))
      }
      this.select.options = this.cacheOptions.size > 0 ? values : [{}]
      this.select.cachedOptions = values.map(item => ({ ...item, value: this.getValue(item), currentLabel: this.getLabel(item) }))
      this.select.setSelected()
    },
    handleClear() {
      this.updateValue(undefined)
      this.$emit('clear')
      this.cacheOptions.clear()
      this.tree.setCheckedKeys([])
    }
  },
};
</script>
<style lang="scss" scoped></style>