前言
项目需求所需,记录下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
/>
总结
以上是我封装的组件,如有问题,可以提出。