经过本人使用vue的treeselect组件,功能都不错,但是有俩个问题,导致我抛弃使用这组件:
1,样式问题,用了elementui的card标签后,区域限制了高度,而treeselect组件的z-index不管怎么调大,都无法浮动在card上,导致过长的list列表大部分因此被遮挡,随可滚动选择,但是很难受。
2.第二点是校验问题,也是最大原因。组件由于是install下来的,改了源码,别人拉代码后用还是要去改下源码,不符合实际。
于是,整合百度的一些代码,自己封装了SelectTree组件。
使用方法:
第一步:建组件文件,文件位置,components下建文件夹SelectTree,SelectTree下建文件index.vue,代码如下直接复制:
<template>
<el-select v-model="valueId" :value="valueId" :clearable="clearable" @clear="clearHandle" filterable :filter-method="filterMethod" class="selectTree" ref="elSelect">
<el-option :value="valueId" :label="valueTitle" >
<el-tree id="tree-option"
ref="selectTree"
:filter-node-method="filterNode"
:accordion="accordion"
:data="options"
:props="props"
:node-key="props.value"
:default-expanded-keys="defaultExpandedKey"
@node-click="handleNodeClick">
</el-tree>
</el-option>
</el-select>
</template>
<script>
export default {
name: "SelectTree",
props:{
/* 配置项 */
props:{
type: Object,
default:()=>{
return {
value:'id', // ID字段名
label: 'label', // 显示名称
children: 'children' // 子级字段名
}
}
},
/* 选项列表数据(树形结构的对象数组) */
options:{
type: Array,
default: ()=>{ return [] }
},
/* 初始值 */
value:{
type: String,
default: ()=>{ return null }
},
/* 可清空选项 */
clearable:{
type:Boolean,
default:()=>{ return true }
},
/* 自动收起 */
accordion:{
type:Boolean,
default:()=>{ return false }
},
},
data() {
return {
valueId:this.value, // 初始值
valueTitle:'',
defaultExpandedKey:[]
}
},
mounted(){
this.initHandle()
},
methods: {
// 选择器检索过滤方法
filterMethod(query) {
// 调用树形控件的过滤
this.$refs.selectTree.filter(query);
// 忽略选择器本身的过滤
return true;
},
// 树节点过滤方法
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
// 初始化值
initHandle(){
if(this.valueId){
this.valueTitle = this.$refs.selectTree.getNode(this.valueId).data[this.props.label] // 初始化显示
this.$refs.selectTree.setCurrentKey(this.valueId) // 设置默认选中
this.defaultExpandedKey = [this.valueId] // 设置默认展开
}
this.$nextTick(()=>{
let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0]
let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar')
scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;'
scrollBar.forEach(ele => ele.style.width = 0)
})
},
// 切换选项
handleNodeClick(node){
this.valueTitle = node[this.props.label]
this.valueId = node[this.props.value]
this.$emit('input',this.valueId)
this.$emit('change',this.valueId)
this.defaultExpandedKey = []
let scrollWrap = document.querySelectorAll('.el-select-dropdown.el-popper')
scrollWrap.forEach(ele => {
ele.style.display = 'none'
ele.style.position = ''
ele.style.top = ''
ele.style.left = ''
})
this.clearSelected()
this.$refs.elSelect.blur()
},
// 清除选中
clearHandle(){
this.valueTitle = ''
this.valueId = null
this.defaultExpandedKey = []
this.clearSelected()
this.$emit('input',null)
this.$emit('change',null)
},
/* 清空选中样式 */
clearSelected(){
let allNode = document.querySelectorAll('#tree-option .el-tree-node')
allNode.forEach((element)=>element.classList.remove('is-current'))
}
},
watch: {
valueId(newValue, oldValue) {
this.valueId = newValue
this.$emit('input',newValue)
this.$emit('change', newValue)
this.initHandle()
}
},
};
</script>
<style scoped>
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item{
height: auto;
max-height: 274px;
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;
}
.el-tree >>>.is-current .el-tree-node__label{
color: #409EFF;
font-weight: 700;
}
.el-tree >>>.is-current .el-tree-node__children .el-tree-node__label{
color:#606266;
font-weight: normal;
}
</style>
第二步:在需要用的页面,引入组件
import SelectTree from '@/components/SelectTree'
第三步:使用组件:
<template>
<el-form ref="dataForm" :rules="rules" :model="temp">
<el-form-item label="所属机构" prop="orgId">
<SelectTree
:options="optionData"
placeholder="请选择机构"
v-model="temp.orgId"
id="orgSelect"
@input="validateField('dataForm','orgId')"
/>
</el-form-item>
</el-form>
</template>
<script >
export default {
components: {SelectTree},
data() {
const validateOrgId = (rule, value, callback) => {
value = this.temp.orgId;
if (!value) {
document.querySelector(".selectTree").style.borderColor ='red'
callback(new Error('请选择所属机构'))
} else {
document.querySelector(".selectTree").style.borderColor ='white'
callback()
}
}
return {
temp:{
orgId:null
},
rules: {
orgId: [{ required: true, trigger: ['input','change'],validator:validateOrgId}],
}
}
},
methods: {
validateField(form,val){
this.$nextTick(()=>{
this.$refs[form].validateField(val);
})
}
}
}
</script >
注意点:
1.validateField俩个参数:第一个是form表单ref的值,第二个是prop校验的变量
2.validateField下必须用this.$nextTick,因为,数据延迟,父子数据双向绑定,等select事件后数据才是最新的
3.组件里,this.$emit特别注意,必须是input,change,页面也是trigger: ['input','change']。不知为啥,经过测试,只有这样用,数据双向绑定,页面的input事件获取的temp.orgId才是选择后的最新值,我这里没用,需要获取值的可以自行加参数看看。
4.validator:validateOrgId,巨坑。不管是自定义组件,还是install的treeselect。const validateOrgId = (rule, value, callback),value值永远是初始值,初次进入赋值是啥,后面不管怎么select选择,value不变,导致校验根本就是错误校验。所以,在函数内直接获取最新值,并替换value, value = this.temp.orgId;这里就是为啥在上面一点强调必须input,change不能少,不能换位置,数据双向绑定,this.temp.orgId才能获取最新值。