前言
本文章是本人在开发过程中,遇到使用树形数据,动态单选或多选的需求,element中没有这种组件,故自己封装一个,欢迎多多指教
开发环境:element-UI、vue2
组件效果
单选
多选
组件引用
<treeselect v-model="form.parentId"
:options="deptOptions"
:props="{value:'id',label:'name',children: 'children'}"
:placeholder="'选择上级部门'"
/>
组件代码
<template>
<div>
<el-select
ref="treeSelect"
popper-class="custom-select-popper"
style="width: 100%"
v-model="valueLabel"
:clearable="clearable"
:placeholder="placeholder"
:multiple="multiple"
@clear="handleClear"
@remove-tag="handleRemoveTag"
>
<el-input v-if="filter"
v-model="filterText"
:placeholder="filterPlaceholder" style="margin-top: -6px;"
/>
<el-option :value="value" :label="option.name" class="select-options">
<el-tree
id="tree-option"
ref="treeSelectTree"
:accordion="accordion"
:data="options"
:props="props"
:node-key="props.value"
:highlight-current="!multiple"
:show-checkbox="multiple"
:check-strictly="checkStrictly"
:default-expand-all="expandAll"
:expand-on-click-node="multiple"
:filter-node-method="filterNode"
@node-click="handleNodeClick"
@check="handleNodeCheckbox"
>
<span slot-scope="{ node, data }" class="tree_label">
{{ node.label }}
</span>
</el-tree>
</el-option>
</el-select>
</div>
</template>
<script>
export default {
name: 'TreeSelect',
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: [String, Number, Object, Array],
default: () => {
return ''
}
},
clearable: {
type: Boolean,
default: true
},
placeholder: {
type: String,
default: '请选择'
},
multipleLimit: {
type: Number,
default: 2
},
//--------- filter props -----
filter: {
type: Boolean,
default: true
},
filterPlaceholder: {
type: String,
default: '请输入关键字'
},
//----- tree props -----
accordion: {
type: Boolean,
default: false
},
options: {
type: Array,
default: () => {
return []
}
},
props: {
type: Object,
default: () => {
return {
value: 'id',
label: 'label',
children: 'children'
}
}
},
expandAll: {
type: Boolean,
default: false
},
checkStrictly: {
type: Boolean,
default: false
}
},
data() {
return {
tp: {
value: 'id',
label: 'label',
children: 'children',
prentId: 'parentId'
},
multiple: false,
valueLabel: [],
option: {
id: '',
name: ''
},
filterText: undefined,
valueId: [],
treeIds: []
}
},
watch: {
valueId() {
if (this.multiple) {
let valueStr = ''
if (this.value instanceof Array) {
valueStr = this.value.join()
} else {
valueStr = '' + this.value
}
if (valueStr !== this.valueId.join()) {
this.$emit('change', this.valueId)
}
} else {
let id = this.valueId.length > 0 ? this.valueId[0] : undefined
if (id !== this.value) {
this.$emit('change', id)
}
}
},
value: {
handler(newVal, oldVal) {
if (newVal !== oldVal) {
this.init()
}
}
},
filterText: {
handler(newVal, oldVal) {
if (newVal !== oldVal) {
this.$refs.treeSelectTree.filter(newVal)
}
}
}
},
mounted() {
for (let key in this.tp) {
if (this.props[key] !== undefined) {
this.tp[key] = this.props[key]
}
}
this.multiple = this.multipleLimit > 1
this.init()
this.$nextTick(() => {
if (this.multiple) {
document.getElementsByClassName('el-select__tags')[0].style.maxHeight = document.getElementsByClassName('el-select')[0].offsetHeight * 2 - 4 + 'px'
}
})
},
methods: {
init() {
if (this.value instanceof Array) {
this.valueId = this.value
} else if (this.value === undefined) {
this.valueId = []
} else {
this.valueId = [this.value]
}
if (this.multiple) {
for (let id of this.valueId) {
this.$refs.treeSelectTree.setChecked(id, true, false)
}
} else {
this.$refs.treeSelectTree.setCurrentKey(this.valueId.length > 0 ? this.valueId[0] : undefined)
}
this.initValueLabel()
this.initTreeIds()
this.initScroll()
},
// 初始化滚动条
initScroll() {
this.$nextTick(() => {
let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0]
scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;'
let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar')
scrollBar.forEach((ele) => (ele.style.width = 0))
})
},
initTreeIds() {
let treeIds = []
let _this = this
function traverse(nodes) {
for (let node of nodes) {
treeIds.push(node[_this.tp.value])
if (node[_this.tp.children]) {
traverse(node[_this.tp.children])
}
}
}
traverse(this.options)
this.treeIds = treeIds
},
initValueLabel() {
let labels = []
let _this = this
for (let id of this.valueId) {
let node = this.traverse(this.options, node => node[_this.tp.value] === id)
if (node) {
labels.push(node[_this.tp.label])
}
}
if (this.multiple) {
this.valueLabel = labels
this.option.name = labels.join()
} else {
this.valueLabel = labels.length > 0 ? labels[0] : undefined
this.option.name = this.valueLabel
}
},
traverse(tree, func) {
for (let node of tree) {
if (func(node)) {
return node
}
if (node[this.tp.children]) {
let result = this.traverse(node[this.tp.children], func)
if (result !== undefined) {
return result
}
}
}
return undefined
},
handleClear() {
this.valueLabel = []
this.valueId = []
if (this.multiple) {
for (let id of this.treeIds) {
this.$refs.treeSelectTree.setChecked(id, false, false)
}
} else {
this.$refs.treeSelectTree.setCurrentKey(null)
}
},
/* 树filter方法 */
filterNode(value, data) {
if (!value) return true
return data[this.props.label].indexOf(value) !== -1
},
/* 树节点点击事件 */
handleNodeClick(data, node) {
if (!this.multiple) {
this.filterText = ''
this.valueId = [data[this.tp.value]]
}
if(node.childNodes){
node.expanded = true
}
},
handleNodeCheckbox(data, node) {
if (this.multiple) {
if (this.multipleLimit >= node.checkedKeys.length) {
this.valueId = node.checkedKeys
} else {
this.$refs.treeSelectTree.setChecked(data, false, !this.checkStrictly)
this.$message.error('最多选择' + this.multipleLimit + '项')
}
}
},
handleRemoveTag(tag) {
let n = this.traverse(this.options, node => node[this.tp.label] === tag)
if (n) {
this.$refs.treeSelectTree.setChecked(n[this.tp.value], false, !this.checkStrictly)
}
this.valueId = this.$refs.treeSelectTree.getCheckedKeys()
}
}
}
</script>
<style scoped lang="scss">
::v-deep .el-select__tags {
overflow: auto;
}
.custom-select-popper{
}
.el-scrollbar {
.el-scrollbar__view {
.el-select-dropdown__item {
height: auto;
max-height: 300px;
padding: 0;
overflow: hidden;
overflow-y: auto;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
}
}
ul li {
.el-tree {
.el-tree-node__content {
height: auto;
padding: 0 20px;
}
.el-tree-node__label {
font-weight: normal;
}
.is-current > .el-tree-node__label{
color: #409eff;
font-weight: 700;
}
}
}
.tree_label {
line-height: 23px;
.label_index {
background-color: rgb(0, 175, 255);
width: 22px;
height: 22px;
display: inline-flex;
border-radius: 4px;
.label_index_font {
color: #ffffff;
width: 100%;
text-align: center;
}
}
}
</style>