element-ui 2.0 下拉树组件简单记录
开发过程中有时需要使用下拉树的功能,element-ui 2.0 没有该组件。所有根据el-select 和el-tree组件基础上做一个简单的下拉树。
大致格式如下
<template>
<div class="select_tree_container">
<el-select
ref="selectRef"
v-bind="$attrs"
:value="modelValue"
:multiple="multiple"
:popper-class="'select_tree_container_popper_class' + ' ' + popperClass"
@remove-tag="onRemoveTag"
@clear="clearHandle">
<el-option v-for="(item, i) in selectOptions" :key="i" hidden :value="item.id" :label="item.label"> </el-option>
<el-tree
:class="elTreeClass"
ref="selectTreeRef"
:data="data"
:node-key="nodeKey"
v-bind="$attrs"
:show-checkbox="multiple"
@node-click="handleNodeClick"
@check-change="onCheckChange"
@check="onCheck">
</el-tree>
</el-select>
</div>
</template>
如上,使用el-option撑开弹窗高度,el-option的所有项为树的所有勾选结果,v-bind="$attrs"属性透传接收原有的el-select,el-tree的属性。
完整组件
<template>
<div class="select_tree_container">
<el-select
ref="selectRef"
v-bind="$attrs"
:value="modelValue"
:multiple="multiple"
:popper-class="'select_tree_container_popper_class' + ' ' + popperClass"
@remove-tag="onRemoveTag"
@clear="clearHandle">
<el-option v-for="(item, i) in selectOptions" :key="i" hidden :value="item.id" :label="item.label"> </el-option>
<el-tree
:class="elTreeClass"
ref="selectTreeRef"
:data="data"
:node-key="nodeKey"
v-bind="$attrs"
:show-checkbox="multiple"
@node-click="handleNodeClick"
@check-change="onCheckChange"
@check="onCheck">
</el-tree>
</el-select>
</div>
</template>
<script>
import { defineComponent, onMounted, getCurrentInstance, ref, watch, nextTick } from '@vue/composition-api';
// 该 defaultOption 是为了将下拉框的高度撑起来
const defaultOption = { id: '——', label: '请选择' };
export default defineComponent({
model: {
prop: 'modelValue',
event: 'update:modelValue',
},
props: {
modelValue: {
type: String | Number | Array,
},
// tree里面的类型的值对应的属性
nodeKey: {
type: String,
default: 'id',
},
multiple: {
type: Boolean,
default: false,
},
popperClass: {
type: String,
default: '',
},
data: {
type: Array,
default: () => {
return [];
},
},
elTreeClass: {
type: String,
default: '',
},
/**
* 是否点击父节点也选中
*/
isSelParent: {
type: Boolean,
default: true,
},
},
setup(props, { emit }) {
const insp = getCurrentInstance()?.proxy;
const selectOptions = ref([defaultOption]);
watch(
() => props.modelValue,
newVal => {
resetOptionAndTreeData(newVal);
}
);
watch(
() => props.data,
async newData => {
await nextTick();
// 需要关闭下拉框,不然会导致el-select input中的文字回显失败
insp?.$refs?.selectRef?.blur();
resetOptionAndTreeData(props.modelValue);
}
);
// 根据key值回显option数据和tree数据
const resetOptionAndTreeData = async key => {
await nextTick();
resetModelOptions(key);
setSelectOption(key);
};
const resetModelOptions = key => {
if (props.multiple) {
insp?.$refs?.selectTreeRef?.setCheckedKeys(key);
} else {
insp?.$refs?.selectTreeRef?.setCurrentKey(key);
}
};
// 设置当前 select 的option;
const setSelectOption = key => {
if (props.multiple) {
let nodes = insp?.$refs?.selectTreeRef?.getCheckedNodes();
if (nodes) {
selectOptions.value = [defaultOption].concat(
nodes.map(node => ({ id: node[props.nodeKey], label: node[insp?.$attrs?.props?.label ?? 'label'] }))
);
// 此处可以渲染的有父节点值,需同步下子节点
if (nodes.length && key.length && key.length !== nodes.length) {
let newModel = insp?.$refs?.selectTreeRef?.getCheckedKeys();
emit('update:modelValue', newModel);
}
}
} else {
let node = insp?.$refs?.selectTreeRef?.getNode(key)?.data;
if (node) {
selectOptions.value = [defaultOption].concat({ id: node[props.nodeKey], label: node[insp?.$attrs?.props?.label ?? 'label'] });
}
}
};
// // 切换选项
const handleNodeClick = (node, nodeObj) => {
if (props.multiple) {
} else {
if (props.isSelParent) {
emit('update:modelValue', node[props.nodeKey]);
insp?.$refs?.selectRef?.blur();
} else {
// 需要判断当前点击的是否是子节点
if (!nodeObj.childNodes || (Array.isArray(nodeObj.childNodes) && nodeObj.childNodes.length === 0)) {
emit('update:modelValue', node[props.nodeKey]);
insp?.$refs?.selectRef?.blur();
} else {
// 此处是重置高亮,因为点击父节点会将高亮状态移动到父节点
let key = props.modelValue || null;
insp?.$refs?.selectTreeRef?.setCurrentKey(key);
}
}
}
};
// // 清除选中
const clearHandle = () => {
if (props.multiple) {
emit('update:modelValue', []);
insp?.$refs?.selectTreeRef?.setCheckedKeys([]);
} else {
emit('update:modelValue', '');
insp?.$refs?.selectTreeRef?.setCurrentKey(null);
}
};
const onCheckChange = (val, val2, val3) => {};
const onCheck = (node, data) => {
emit('update:modelValue', data.checkedKeys);
};
const onRemoveTag = val => {
insp?.$refs?.selectTreeRef?.setChecked(val, false, true);
let newModel = insp?.$refs?.selectTreeRef?.getCheckedKeys();
emit('update:modelValue', newModel);
};
onMounted(() => {
resetOptionAndTreeData(props.modelValue);
});
return {
selectOptions,
handleNodeClick,
clearHandle,
onCheckChange,
onCheck,
onRemoveTag,
};
},
});
</script>
<style scoped lang="less">
.select_tree_container {
}
</style>
<style lang="less">
.select_tree_container_popper_class {
min-height: 200px;
.el-scrollbar {
// height: 100%;
}
}
</style>
使用组件
import SelectTree from './SelectTree.vue';
<SelectTree
v-model="testModel"
:data="treeList"
collapse-tags
highlight-current
node-key="testId"
:props="propsData"
:clearable="true"
popper-class="test_class"></SelectTree>