vue+element+el-tree实现树形下拉可支持单选/多选/选择父类亲测可用,后续上图片暂时先上代码借鉴于[](github.com/yujinpan/el…)
但是这个不支持点击复选框单选和选择有子类的功能,我这边略微改动了一下。
话不多说直接上代码
/**单选*/
<selectTree
size="small"
:props="props"
:data="treeList"
v-model="value"
node-key="id"
show-checkbox
check-strictly
default-expand-all
/>
/**多选*/
<selectTree
size="small"
:props="props"
:data="treeList"
v-model="value"
node-key="id"
show-checkbox
check-strictly
default-expand-all
multiple
/>
export default {
data() {
return {
treeList: [{
label: '一级 1',
children: [{
label: '二级 1-1',
children: [{
label: '三级 1-1-1'
}]
}]
}, {
label: '一级 2',
children: [{
label: '二级 2-1',
children: [{
label: '三级 2-1-1'
}]
}, {
label: '二级 2-2',
children: [{
label: '三级 2-2-1'
}]
}]
}, {
label: '一级 3',
children: [{
label: '二级 3-1',
children: [{
label: '三级 3-1-1'
}]
}, {
label: '二级 3-2',
children: [{
label: '三级 3-2-1'
}]
}]
}],
defaultProps: {
children: 'children',
label: 'label'
}
}
}
}
components代码部分
<template>
<div class="el-select-tree">
<el-popover
ref="elPopover"
v-model="visible"
transition="el-zoom-in-top"
popper-class="el-select-tree__popover"
trigger="click"
:disabled="disabled"
:placement="placement"
:width="popoverWidth"
@after-enter="handleScroll()"
>
<!-- scrollbar wrap -->
<el-scrollbar
wrap-class="el-select-dropdown__wrap"
view-class="el-select-dropdown__list"
ref="scrollbar"
>
<el-tree
ref="elTree"
class="el-select-tree__list"
:default-expanded-keys="defaultExpandedKeys"
:show-checkbox="showCheckbox"
:expand-on-click-node="multiple"
:style="{ 'min-width': minWidth + 'px' }"
@node-click="nodeClick"
@check-change="checkChange"
@transitionend.native="$refs.elPopover.updatePopper()"
:data="data"
:props="props"
:node-key="propsValue"
:default-expand-all="defaultExpandAll"
:check-strictly="checkStrictly"
:lazy="lazy"
:load="load"
:icon-class="iconClass"
:indent="indent"
:accordion="accordion"
:filter-node-method="filterNodeMethod"
:auto-expand-parent="autoExpandParent"
:render-content="renderContent"
:render-after-expand="renderAfterExpand"
>
<div
class="el-select-tree__item"
slot-scope="{ data }"
:class="treeItemClass(data)"
>
{{ data[propsLabel] }}
</div>
</el-tree>
</el-scrollbar>
<!-- trigger input -->
<el-input
v-model="selectedLabel"
ref="reference"
slot="reference"
readonly
:validate-event="false"
:size="size"
:class="{
'is-active': visible,
'is-selected': selectedLabel,
'is-clearable': clearable
}"
:disabled="disabled"
:placeholder="placeholder"
>
<i
v-if="clearable"
@click.stop="clear()"
slot="suffix"
class="el-input__icon el-input__icon-close el-icon-circle-close"
></i>
<i
slot="suffix"
class="el-input__icon el-input__icon-arrow-down el-icon-arrow-down"
></i>
</el-input>
</el-popover>
</div>
</template>
<script>
import Vue from 'vue';
import Emitter from 'element-ui/lib/mixins/emitter';
import {
addResizeListener,
removeResizeListener
} from 'element-ui/lib/utils/resize-event';
export default {
name: 'ElSelectTree',
mixins: [Emitter],
model: {
prop: 'value',
event: 'change'
},
props: {
// [el-tree] forwarding parameters https://element.eleme.io/#/zh-CN/component/tree#attributes
data: {
type: Array,
default() {
return [];
}
},
props: {
type: Object,
default() {
return {
value: 'value',
label: 'label',
children: 'children',
disabled: 'disabled',
isLeaf: 'isLeaf'
};
}
},
checkStrictly: Boolean,
showCheckbox: Boolean,
nodeKey: String,
defaultExpandAll: Boolean,
lazy: Boolean,
load: Function,
iconClass: String,
indent: Number,
accordion: Boolean,
filterNodeMethod: Function,
autoExpandParent: {
type: Boolean,
default: true
},
renderContent: Function,
renderAfterExpand: Boolean,
// [el-tree] forwarding parameters end
clearable: Boolean,
placeholder: {
type: String,
default: '请选择'
},
placement: {
type: String,
default: 'bottom-start'
},
size: {
type: String,
default: Vue.prototype.$ELEMENT ? Vue.prototype.$ELEMENT.size : ''
},
disabled: Boolean,
multiple: Boolean,
value: {
type: [Number, String, Array],
default: ''
},
popoverWidth: Number
},
computed: {
propsValue() {
return this.nodeKey || this.props.value || 'value';
},
propsLabel() {
return this.props.label || 'label';
},
propsIsLeaf() {
return this.props.isLeaf || 'isLeaf';
},
defaultExpandedKeys() {
return Array.isArray(this.value)
? this.value
: this.value || this.value === 0
? [this.value]
: [];
}
},
data() {
return {
visible: false,
selectedLabel: '',
minWidth: 0
};
},
methods: {
valueChange(value, node) {
this.$emit('change', value, node);
},
clear() {
this.visible = false;
if (this.multiple) {
this.valueChange([]);
this.$nextTick(() => {
this.$refs.elTree.setCheckedKeys([]);
});
} else {
this.valueChange('');
}
this.$emit('clear');
},
// 触发滚动条的重置
handleScroll() {
this.$refs.scrollbar && this.$refs.scrollbar.handleScroll();
},
nodeClick(data, node, component) {
const children = data[this.props.children];
const value = data[this.propsValue];
if (
this.showCheckbox ||
((children && children.length) ||
(this.lazy && !data[this.propsIsLeaf])) &&
!this.checkStrictly
) {
component.handleExpandIconClick();
} else if (!this.multiple && !data.disabled) {
if (value !== this.value) {
this.valueChange(value, data);
this.selectedLabel = data[this.propsLabel];
}
this.visible = false;
}
},
checkChange(data, checked) {
if (this.multiple) {
//多选处理
const elTree = this.$refs.elTree;
const leafOnly = !this.checkStrictly;
const keys = elTree.getCheckedKeys(leafOnly);
const nodes = elTree.getCheckedNodes(leafOnly);
this.valueChange(keys, nodes);
this.setMultipleSelectedLabel();
}
else {
//显示checkbox,但不多选处理
if (checked) {
const value = data[this.propsValue];
this.valueChange(value, data);
this.$refs.elTree.setCheckedNodes([data]);
this.visible = false;
}
}
},
setSelected() {
this.$nextTick(() => {
const elTree = this.$refs.elTree;
if (this.multiple) {
elTree.setCheckedKeys(this.value);
this.setMultipleSelectedLabel();
} else {
const selectedNode = elTree.getNode(this.value);
this.selectedLabel = selectedNode
? selectedNode.data[this.propsLabel]
: '';
}
});
},
setMultipleSelectedLabel() {
const elTree = this.$refs.elTree;
const selectedNodes = elTree.getCheckedNodes(!this.checkStrictly);
this.selectedLabel = selectedNodes
.map((item) => item[this.propsLabel])
.join(',');
},
treeItemClass(data) {
return {
'is-selected': this.multiple
? false
: data[this.propsValue] === this.value,
'is-disabled': data.disabled
};
},
handleResize() {
// set the `tree` default `min-width`
// border's width is 2px
this.minWidth = this.$el.clientWidth - 2;
}
},
watch: {
value() {
this.setSelected();
// trigger parent `el-form-item` validate event
this.dispatch('ElFormItem', 'el.form.change');
},
data() {
this.setSelected();
}
},
created() {
if (this.multiple && !Array.isArray(this.value)) {
throw new Error(
'[el-select-tree] props `value` must be Array if use multiple!'
);
}
},
mounted() {
this.setSelected();
addResizeListener(this.$el, this.handleResize);
},
beforeDestroy() {
if (this.$el && this.handleResize) {
removeResizeListener(this.$el, this.handleResize);
}
}
};
</script>
<style lang="scss" scoped>
@import './common-variables';
::v-deep :focus {
outline: 0;
}
.el-select-tree {
display: inline-block;
.el-input__icon {
cursor: pointer;
transition: transform 0.3s;
&-close {
display: none;
}
}
.el-input__inner {
cursor: pointer;
padding-right: 30px;
}
.el-input {
&:hover:not(.is-disabled) {
.el-input__inner {
border-color: $--input-border-color-hover;
}
&.is-selected.is-clearable {
.el-input__icon {
&-close {
display: inline-block;
}
&-arrow-down {
display: none;
}
}
}
}
&.is-active {
.el-input__icon-arrow-down {
transform: rotate(-180deg);
}
.el-input__inner {
border-color: $--button-primary-border-color;
}
}
}
&__popover {
padding: 0 !important;
// extends el-select-dropdown - start
border: $--select-dropdown-border !important;
border-radius: $--border-radius-base !important;
// extends el-select-dropdown - end
.popper__arrow {
left: 35px !important;
}
.el-tree-node__expand-icon.is-leaf {
cursor: pointer;
}
}
&__list {
overflow-y: auto;
// scroll style - start
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
}
// scroll style - end
}
&__item {
position: relative;
white-space: nowrap;
padding-right: $spacing-medium;
&.is-selected {
color: $--select-option-selected-font-color;
font-weight: bolder;
}
&.is-disabled {
color: $--font-color-disabled-base;
cursor: not-allowed;
}
}
}
</style>
common-variables.scss
// 导入 element-ui 的默认样式变量
@import 'node_modules/element-ui/packages/theme-chalk/src/common/var.scss';
// 基准颜色
$color-blue: rgba(157, 184, 233, 1);
$color-primary: $--color-primary;
$color-white: $--color-white;
$color-black: $--color-black;
$color-success: $--color-success;
$color-warning: $--color-warning;
$color-danger: $--color-danger;
$color-info: $--color-info;
// 文本颜色
$color-text-primary: $--color-text-primary;
$color-text-regular: $--color-text-regular;
$color-text-secondary: $--color-text-secondary;
$color-text-placeholder: $--color-text-placeholder;
// 边框颜色
$border-color-base: $--border-color-base;
$border-color-light: $--border-color-light;
$border-color-lighter: $--border-color-lighter;
$border-color-extra-light: $--border-color-extra-light;
// 背景颜色
$background-color-base: $--background-color-base;
// 图标颜色
$icon-color: $--icon-color;
$icon-color-base: $color-info;
// 边框样式
$border-width-base: $--border-width-base;
$border-style-base: $--border-style-base;
$border-color-hover: $--color-text-placeholder;
$border-base: $--border-width-base $--border-style-base $--border-color-base;
$border-radius-base: $--border-radius-base;
$border-radius-small: $--border-radius-small;
$border-radius-circle: $--border-radius-circle;
$border-radius-zero: $--border-radius-zero;
// 投影样式
$box-shadow-default: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
$box-shadow-base: $--box-shadow-base;
$box-shadow-dark: $--box-shadow-dark;
$box-shadow-light: $--box-shadow-light;
// 字体大小
$font-size-extra-small: $--font-size-extra-small; // 12px
$font-size-small: $--font-size-small; // 13px
$font-size-base: $--font-size-base; // 14px
$font-size-medium: $--font-size-medium; // 16px
$font-size-large: $--font-size-large; // 18px
$font-size-extra-large: $--font-size-extra-large; // 20px
// 字体颜色
$font-color: #444;
// 间距大小
$spacing-base: 10px;
$spacing-medium: 20px;
$spacing-large: 30px;
// 过渡效果
$transition-base: all 0.3s;