前言
今天小谭要给大家分享的是基于element ui 的下拉框和树型控件二次分装的树型下拉框,element plus新增了这一组件,但是对于还在使用vue2的我来说,就很不友好。组件可以实现单选和多选,以及其他常用功能,废话不多说,直接上效果图:
代码实现
效果图如上,接下来实现代码:
使用时,如果想实现回显效果node-key必须要传!!!
HTMl:
<!--
* @description: 通用树型下拉框
* @fileName: treeSelect.vue
* @author: t
* @date: 2023-03-07 17:15:03
* @Attributes: data 展示数据 array
props 配置选项,具体配置可以参照element ui库中el-tree的配置 object
show-checkbox 节点是否可被选择 boolean
check-strictly 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false boolean
icon-class 自定义树节点的图标 string
load 加载子树数据的方法,仅当 lazy 属性为true 时生效 function(node, resolve)
lazy 是否懒加载子节点,需与 load 方法结合使用 boolean
disabled 下拉框是否禁用
getCheckedKeys 若节点可被选择(即 show-checkbox 为 true),则返回目前被选中的节点的 key 所组成的数组
getCurrentNode 获取当前被选中节点的 data,若没有节点被选中则返回 null
collapse-tags 多选时是否将选中值按文字的形式展示
select-last-node 单选时是否只能选择最后一个节点
Scoped Slot 自定义树节点的内容,参数为 { node, data }
show-count 若节点中存在children 则在父节点展示所属children的数量,注意但设置插槽时 show-count将失效!
clearable 单选时是否可以清空选项
filterable 属性即可启用搜索功
!!!!!必传!!!!!! props-value 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 !!!!!!
!-->
<!--:clearable="$attrs['show-checkbox']==undefined?$attrs['clearable']:false"-->
<template>
<el-select
:value="valueFilter(value)"
:placeholder="$attrs['placeholder']"
:multiple="$attrs['show-checkbox']"
:disabled="$attrs['disabled']"
:filterable="$attrs['filterable']"
:clearable="$attrs['clearable']"
:collapse-tags="$attrs['collapse-tags']"
@change="selectChange"
@clear="selectClear"
ref="mySelect"
:filter-method="remoteMethod"
>
<template slot="empty">
<div class="selecTree">
<el-tree
:data="data"
:props="props"
@node-click="handleNodeClick"
:show-checkbox="$attrs['show-checkbox']"
:check-strictly="$attrs['check-strictly']"
:icon-class="$attrs['icon-class']"
:lazy="$attrs['lazy']"
:load="$attrs['load']"
:node-key="props.value"
:filter-node-method="filterNode"
@check-change="handleCheckChange"
:default-expanded-keys="defaultExpandedKeys"
ref="myTree"
>
<template slot-scope="{ node, data }">
<slot :node="node" :data="data">
<span class="slotSpan">
<span>
{{ data[props.label] }}
<b v-if="$attrs['show-count'] != undefined && data[props.children]">({{ data[props.children].length }})</b>
</span>
</span>
</slot>
</template>
</el-tree>
</div>
</template>
</el-select>
</template>
JS:
export default {
props: {
value: {
type: undefined,
default: null,
},
data: {
type: Array,
default: null,
},
props: {
type: Object,
default: null,
},
},
data() {
return {
defaultExpandedKeys: [],
};
},
created() {
this.propsInit();
},
mounted() {
setTimeout(this.initData, 10);
},
beforeUpdate() {
this.propsInit();
this.initData();
},
methods: {
initData() {
if (this.$attrs['show-checkbox'] === undefined) {
let newItem = this.recurrenceQuery(this.data, this.props.value, this.value);
if (newItem.length) {
if (this.props.value && newItem[0][this.props.value]) {
this.defaultExpandedKeys = [newItem[0][this.props.value]];
}
this.$nextTick(() => {
this.$refs.myTree.setCurrentNode(newItem[0]);
});
}
} else {
let newValue = JSON.parse(JSON.stringify(this.value));
if (!(newValue instanceof Array)) {
newValue = [newValue];
}
if (newValue?.length) {
let checkList = newValue.map(key => {
if (key) {
let newItem = this.recurrenceQuery(this.data, this.props.value, key);
return newItem[0] || '';
}
});
if (checkList?.length) {
let defaultExpandedKeys = checkList.map(item => item?.[this.props.value || '']);
if (defaultExpandedKeys.length) this.defaultExpandedKeys = defaultExpandedKeys;
this.$nextTick(() => {
this.$refs.myTree.setCheckedNodes(checkList);
});
}
}
}
this.$forceUpdate();
},
// 多选
handleCheckChange(data, e, ev) {
let checkList = this.$refs.myTree.getCheckedNodes();
let setList = null;
if (checkList.length) {
setList = checkList.map(item => item[this.props.value]);
}
this.$emit('input', setList);
// 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点
this.$emit('change', data, e, ev);
},
// 单选事件
handleNodeClick(data, e) {
if (!(this.$attrs['select-last-node'] === undefined)) {
if (data[this.props.children] && data[this.props.children]?.length) {
return false;
}
}
if (this.$attrs['show-checkbox'] === undefined) {
this.$emit('input', data[this.props.value]);
this.$refs.mySelect.blur();
}
this.$emit('change', data, e);
},
// 递归查找通用方法
recurrenceQuery(list, key, value) {
if (!list || !key || !value) return [];
let queryData = [];
list.map(item => {
if (item[this.props.children] && item[this.props.children].length) {
queryData.push(...this.recurrenceQuery(item[this.props.children], key, value));
}
if (item[key] == value) {
queryData.push(item);
}
return item;
});
return queryData;
},
selectChange(e) {
if (this.$attrs['show-checkbox'] !== undefined) {
let checkList = e.map(key => {
let newItem = this.recurrenceQuery(this.data, this.props.label, key);
return newItem[0] || '';
});
this.$refs.myTree.setCheckedNodes(checkList);
this.$emit('input', e);
}
},
selectClear(flag) {
if (this.$attrs['show-checkbox'] === undefined) {
if (!flag) this.$emit('input', '');
this.$refs.myTree.setCurrentKey(null);
} else {
if (!flag) this.$emit('input', []);
this.$refs.myTree.setCheckedKeys([]);
}
this.remoteMethod('');
},
getCheckedNodes() {
if (this.value !== null && this.value !== undefined && this.value !== '') {
return this.$refs.myTree.getCheckedNodes();
}
return [];
},
getCurrentNode() {
if (this.value !== null && this.value !== undefined && this.value !== '') {
return this.$refs.myTree.getCurrentNode();
}
return null;
},
valueFilter(val) {
if (this.$attrs['show-checkbox'] === undefined) {
let res = '';
[res] = this.recurrenceQuery(this.data, this.props.value, val);
return res?.[this.props.label] || '';
} else {
if (!val?.length) return [];
let res = val.map(item => {
let [newItem] = this.recurrenceQuery(this.data, this.props.value, item);
return newItem?.[this.props.label] || '';
});
if (!res?.length) return [];
res = res.filter(item => item);
return res;
}
},
propsInit() {
this.props.label = this.props.label || 'label';
this.props.value = this.props.value || 'value';
this.props.children = this.props.children || 'children';
if (this.$attrs['select-last-node'] !== undefined && !this.props.disabled) {
this.props.disabled = data => data?.[this.props.children]?.length;
this.$attrs['check-strictly'] = true;
}
},
remoteMethod(query) {
this.$refs.myTree.filter(query);
},
filterNode(value, data) {
if (!value) return true;
return data[this.props.label].indexOf(value) !== -1;
},
},
watch: {
value: {
deep: true,
handler(val) {
if (!val || !val?.length) {
this.selectClear(true);
}
},
},
},
};
CSS:
.selecTree {
max-height: 50vh;
overflow: auto;
padding: 5px;
}
.el-select {
width: 100%;
}
.slotSpan {
font-size: 14px;
b {
font-weight: normal;
font-size: 12px;
color: #999;
}
}
.selecTree ::v-deep .el-tree-node__content {
font-size: 14px;
}