Element UI Select选择器控件结合树形控件实现单选和多选,并且通过v-model的方式实现节点的双向绑定,封装成vue组件,文件名为electricity-meter-tree.vue,其代码如下:
<template>
<div>
<el-select
:value="selectedId"
:multiple="multiple"
placeholder="请选择"
ref="selectTree"
clearable
@change="handleChange"
@clear="handleClear">
<el-option v-for="form in hiddenForm" :key="form.id" :value="form.id" :label="form.label" hidden/>
<div style="padding: 10px 14px">
<el-input placeholder="输入关键字进行过滤" v-model="filterText" clearable>
<template #suffix>
<el-button type="text" icon="el-icon-search" style="max-width: 22px">
<i class="el-icon-search" style="display: none;"></i>
</el-button>
</template>
</el-input>
<el-tree
v-loading="loading"
:data="treeData"
ref="tree"
:props="defaultProps"
node-key="id"
accordion
default-expand-all
:filter-node-method="filterNode"
:show-checkbox="multiple"
:check-strictly="checkStrictly"
:highlight-current="highLightCurrent"
@check-change="handleCheckChange"
@node-click="handleNodeClick">
<!-- 来源于省的电表以蓝色底标识。-->
<template v-slot="{ node, data }">
<span v-if="data.sources === 'province'" style="background: #1890ff;color: #FFFFFF;padding: 2px;">
{{ node.label}}
</span>
<span v-else>{{ node.label}}</span>
</template>
</el-tree>
</div>
</el-select>
</div>
</template>
<script>
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: [Object, Array], // 根据实际情况选择类型
default: () => {} // 格式:{id: 'xx', label: 'xx'}或者[{id: 'xx', label: 'xx'}]
},
multiple: { // 是否多选
type: Boolean,
default: false
},
checkStrictly: { // 父子是否不互相关联
type: Boolean,
default: false
},
},
watch: {
value: {
handler (val) {
if (this.multiple) {
if (val && val.length > 0) {
this.hiddenForm = val;
this.selected = val;
}else {
this.hiddenForm = [{id: '', label: ''}];
this.selected = [];
}
} else {
if (val) {
this.selected = val;
this.hiddenForm = [val];
}else {
this.hiddenForm = [{id: '', label: ''}];
this.selected = null;
}
}
},
immediate: true,
deep: true
},
filterText(val) {
this.$refs.tree.filter(val);
}
},
computed: {
selectedId () {
if (this.multiple) {
return this.selected && this.selected.length > 0 ? this.selected.map(item => item.id) : [];
}
return this.selected? this.selected.id : '';
},
},
data () {
return {
filterText: '',
treeData: [],
defaultProps: {
children: 'children',
label: 'label',
id: 'id',
disabled: 'disabled'
},
selected: [],// 格式:{} 或者 []
hiddenForm: [
{
id: '',
label: ''
},
],
highLightCurrent: true,
loading: false,
}
},
methods: {
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
handleCheckChange (data, checked) {
if (this.multiple) { // 多选
const nodes = this.$refs.tree.getCheckedNodes();
this.hiddenForm = nodes.length > 0 ? nodes : [{id: '', label: ''}];
// 保证重现加载数据时,已选中的数据不被清除
if (checked) {
if (!this.selected || this.selected.length === 0) {
this.selected = nodes;
}else {
// 定义一个函数来去重
function removeDuplicates(array, uniqueKey) {
let seen = new Map(); // 使用 Map 来存储已见过的 id 和对应对象
// 遍历数组中的每个对象
for (let obj of array) {
// 如果 Map 中还没有这个 id,就添加进去
if (!seen.has(obj[uniqueKey])) {
seen.set(obj[uniqueKey], obj); // 使用 id 作为 key,对象作为 value
}
}
// 从 Map 中提取所有对象组成新的数组返回
return Array.from(seen.values());
}
this.selected = removeDuplicates([...this.selected, ...nodes], 'id');
}
}else {
this.selected = this.selected?.filter(item => item.id !== data.id);
}
this.$emit('change', this.selected);
} else {}
},
handleNodeClick (data, node) {
if (data.disabled) return;
if (this.multiple) {} else { // 单选
this.hiddenForm = [
{
id: data.id,
label: data.label
}
]
this.selected = data;
this.$emit('change', data);
// 使 input 失去焦点,并隐藏下拉框
// node.isLeaf && this.$refs.selectTree.blur();
}
},
handleChange (value) {
if (!value) return;
this.selected = this.selected?.filter(item => value.includes(item.id));
this.$emit('change', this.selected);
this.multiple && this.$refs.tree.setCheckedKeys(this.selectedId);
},
handleClear () {
this.selected = [];
this.multiple && this.$refs.tree.setCheckedKeys([]);
this.hiddenForm = [
{
id: '',
label: ''
}
];
this.highLightCurrent = false;
this.$emit('change', this.multiple ? [] : null);
},
getTreeDataAsync() {
// 这里模拟调用后端接口返回的数据
this.treeData = [
{
id: '1',
label: 'XX市智能电表',
children: [
{
id: '1-1',
label: '通用智能电表A',
children: [
{
id: '1-1-1',
label: '通用智能电表B',
sources: 'province',
},
{
id: '1-1-2',
label: '通用智能电表C',
},
]
},
{
id: '1-2',
label: '智能电表01',
children: [
{
id: '1-2-1',
label: '智能电表02',
sources: 'province',
}
]
},
]
}
];
}
},
created() {
this.getTreeDataAsync();
},
}
</script>
<style scoped>
</style>
该组件的用法如下:
<electricity-meter-tree v-model="electricityMeter" multiple checkStrictly @change="electricityMeterChange"/>
- 引入组件:electricity-meter-tree
- 如果multiple为true多选时,那么electricityMeter的结构为[{id: ‘xx’, label: ‘xx’}],如果 mutilple为false单选时,那么electricityMeter的结构为{id: ‘xx’, label: ‘xx’}
- checkStrictly是否父子不互相关联,前提是multiple为true
- change事件也可以获取选中的节点
效果如下: