el-select+el-tree实现树形下拉选择

发布于:2025-06-13 ⋅ 阅读:(23) ⋅ 点赞:(0)

前言

项目需求所需,记录下el-select+el-tree实现树形下拉选择

一、使用步骤

1.封装

<template>
  <div style="display: flex">
    <el-select
      v-model="insideValue"
      :multiple="multiple"
      :disabled="disabled"
      :placeholder="placeholder"
      :collapse-tags="collapseTags"
      @visible-change="handleVisibleChange"
      v-on="$listeners"
      style="width: 100%"
      ref="treeSelect"
    >
      <!-- 添加实际的 el-option 用于显示标签 -->
      <el-option
        v-for="item in selectedOptions"
        :key="item.value"
        :label="item.label"
        :value="item.value"
        style="display: none"
      />

      <!-- 树形选择器 -->
      <el-option style="height: auto; padding: 0" :value="insideValue">
        <el-tree
          v-show="showTree"
          :data="options"
          :show-checkbox="multiple"
          :props="defaultProps"
          v-on="$listeners"
          :highlight-current="true"
          node-key="orgCode"
          ref="tree"
          :check-strictly="checkStrictly"
          @node-click="handleNodeClick"
          @check="handleCheckChange"
          :default-expanded-keys="defaultExpandedKeys"
        ></el-tree>
      </el-option>
    </el-select>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Prop, Watch, Ref } from 'vue-property-decorator'

interface TreeNode {
  orgCode: string
  orgName: string
  children?: TreeNode[]
}

interface SelectOption {
  value: string
  label: string
}

@Component({
  name: 'comTreeSelect'
})
export default class ComTreeSelect extends Vue {
  @Prop({ type: [String, Array] }) value!: string | string[]
  @Prop({ type: Boolean, default: false }) disabled!: boolean
  @Prop({ type: Boolean, default: false }) multiple!: boolean
  @Prop({ type: String, default: '请选择' }) placeholder!: string
  @Prop({ type: Boolean, default: false }) collapseTags!: boolean
  @Prop({ type: Boolean, default: true }) selectShow!: boolean
  @Prop({ type: Boolean, default: true }) checkStrictly!: boolean

  @Ref('treeSelect') treeSelectRef!: any
  @Ref('tree') treeRef!: any

  componentInit = false
  insideValue: string | string[] = this.multiple ? [] : ''
  options: TreeNode[] = []
  selectedOptions: SelectOption[] = [] // 用于存储选中的选项
  loading = false
  showTree = false
  

  defaultProps = {
    children: 'children',
    label: 'orgName',
    value: 'orgCode'
  }

  searchMap = {
    pageIndex: 1,
    pageSize: 999,
    search: {}
  }

  get selectedLabelText(): string {
    return this.selectedOptions.map((option) => option.label).join(', ')
  }

  get defaultExpandedKeys(): any {
    const keys = []
    this.options.forEach((node) => {
      keys.push(node.orgCode)
    })
    return keys
  }

  @Watch('value', { immediate: true })
  onValueChange(newVal: string | string[]): void {
    this.insideValue = this.multiple ? newVal || [] : newVal || ''
    // 如果组件已经初始化,需要同步树的选择状态
    if (this.componentInit) {
      this.syncTreeSelection()
    }
  }

  @Watch('insideValue')
  onInsideValueChange(newVal: string | string[]): void {
    this.$emit('input', newVal)
  }

  async getRemoteList(): Promise<void> {
    try {
      this.loading = true
      const { code, result } = await this.$agencyManage.list(this.searchMap, { isLoading: false })
      if (code === 200) {
        this.options = result ? result.items || [] : []
        // 数据加载完成后同步选择状态
        this.syncTreeSelection()
      }
    } catch (error) {
      console.error('获取机构列表失败:', error)
    } finally {
      this.loading = false
    }
  }

  // 同步树的选择状态
  syncTreeSelection(): void {
    this.$nextTick(() => {
      if (this.multiple && Array.isArray(this.insideValue)) {
        this.treeRef?.setCheckedKeys(this.insideValue)
        this.updateSelectedOptions()
        this.$emit('change', this.insideValue)
      } else if (!this.multiple && typeof this.insideValue === 'string') {
        this.treeRef?.setCurrentKey(this.insideValue)
        this.updateSelectedOptions()
        this.$emit('change', this.insideValue)
      }
    })
  }

  // 更新选中的选项
  updateSelectedOptions(): void {
    if (this.multiple) {
      const checkedNodes = this.treeRef?.getCheckedNodes() || []
      this.selectedOptions = checkedNodes.map((node: TreeNode) => ({
        value: node.orgCode,
        label: node.orgName
      }))
    } else {
      const currentNode = this.treeRef?.getCurrentNode()
      console.log('currentNode', currentNode)
      this.selectedOptions = currentNode ? [{ value: currentNode.orgCode, label: currentNode.orgName }] : []
    }
  }

  handleVisibleChange(visible: boolean): void {
    this.showTree = visible
    if (visible) {
      this.$nextTick(() => {
        this.syncTreeSelection()
      })
    }
  }

  handleNodeClick(node: TreeNode): void {
    if (!this.multiple) {
      this.insideValue = node.orgCode
      this.selectedOptions = [
        {
          value: node.orgCode,
          label: node.orgName
        }
      ]
      this.$emit('input', this.insideValue)
      this.$emit('change', this.insideValue)
      this.showTree = false
      this.treeSelectRef.blur()
    }
  }

  handleCheckChange(): void {
    if (this.multiple) {
      const checkedNodes = this.treeRef?.getCheckedNodes() || []
      this.insideValue = checkedNodes.map((node: TreeNode) => node.orgCode)
      this.selectedOptions = checkedNodes.map((node: TreeNode) => ({
        value: node.orgCode,
        label: node.orgName
      }))
      this.$emit('input', this.insideValue)
      this.$emit('change', this.insideValue)
    }
  }

  mounted(): void {
    this.$nextTick(() => {
      if (!this.componentInit) {
        this.getRemoteList()
      }
    })
  }

  activated(): void {
    this.componentInit = true
    this.getRemoteList()
  }
  handleChange(): void {
    this.insideValue = this.multiple ? [] : ''
  }
}
</script>

<style scoped lang="scss">
.el-select {
  .el-input--mini .el-input__inner {
    height: 28px !important;
    line-height: 28px !important;
  }

  /* 修复树形选择器的样式 */
  .el-tree {
    padding: 5px;
    min-width: 100%;
    box-sizing: border-box;
  }
}
</style>

2.使用 

 <com-tree-select
   v-model="value"
   :disabled="false"
   :multiple="true"
   :collapseTags="true"
   :checkStrictly="true
  />

总结

以上是我封装的组件,如有问题,可以提出。