重新封装ElementUI的select组件,显示树型结构选项,实现子级多选。
选中逻辑:
- 当选中父节点时,自动选中其所有子节点
- 当取消选中父节点时,自动取消其所有子节点
- 子节点可以独立选择
- 最终的值(emit 出去的值)只包含子节点
子组件实现:
创建src\components\TreeSelect\index.vue
<!-- 树型下拉选择器组件 -->
<template>
<div class="tree-select">
<el-select
v-model="displaySelection"
:placeholder="placeholder"
:clearable="clearable"
:disabled="disabled"
:multiple="multiple"
:collapse-tags="collapseTags"
@change="handleChange"
style="width: 100%"
>
<el-option
v-for="item in treeData"
:key="item.classId"
:label="getOptionLabel(item)"
:value="item.classId"
>
<span :style="{ paddingLeft: item.level * 20 + 'px' }">
<span
v-if="item.children && item.children.length > 0"
class="parent-node"
>
{{ item.className }}
</span>
<span v-else>{{ item.className }}</span>
</span>
</el-option>
</el-select>
</div>
</template>
<script>
export default {
name: "TreeSelect",
props: {
// 选中的值
value: {
type: [String, Number, Array],
default: "",
},
// 树形数据
data: {
type: Array,
default: () => [],
},
// 占位符
placeholder: {
type: String,
default: "请选择",
},
// 是否可清空
clearable: {
type: Boolean,
default: true,
},
// 是否禁用
disabled: {
type: Boolean,
default: false,
},
// 是否多选
multiple: {
type: Boolean,
default: false,
},
// 多选时是否将选中值按文字的形式展示
collapseTags: {
type: Boolean,
default: false,
},
},
data() {
return {
displaySelection: this.value, // 显示的选中值,用于显示在el-select中
treeData: [], // 树形数据,包含层级信息
nodeMap: new Map(), // 存储节点之间的关系
};
},
watch: {
// 监听value变化,更新显示的选中值
value(val) {
this.displaySelection = val;
},
// 监听data变化,重新初始化树形数据
data: {
// 深度监听,确保每次变化都重新初始化
handler(val) {
// 清空节点关系映射
this.initTreeData(val);
},
// 立即执行,确保组件初始化时也进行数据初始化
immediate: true,
},
},
methods: {
// 初始化树形数据,添加层级信息
initTreeData(data, level = 0, parentPath = "", parentId = null) {
// 初始化结果数组
const result = [];
// 遍历数据
data.forEach((item) => {
// 创建节点对象,并复制item属性,同时添加level和parentId属性
const node = { ...item, level, parentId };
// 添加显示路径
node.path = parentPath
? `${parentPath}->${item.className}`
: item.className;
// 将节点添加到结果数组中
result.push(node);
// 存储节点关系
this.nodeMap.set(node.classId, {
parent: parentId,
children: item.children
? item.children.map((child) => child.classId)
: [], // 子节点ID数组
});
// 处理子节点
if (item.children && item.children.length > 0) {
// 递归调用initTreeData处理子节点,并将结果添加到结果数组中
const children = this.initTreeData(
item.children,
level + 1,
node.path,
node.classId
);
result.push(...children);
}
});
// 将结果赋值给treeData属性
this.treeData = result;
// 返回结果
return result;
},
// 获取所有子节点ID
getAllChildrenIds(nodeId) {
const childrenIds = [];
// 获取当前节点的子节点ID数组,并递归获取所有子孙的ID
const node = this.nodeMap.get(nodeId);
if (node && node.children) {
// 遍历当前节点的所有子节点
node.children.forEach((childId) => {
// 将子节点的ID加入数组
childrenIds.push(childId);
// 递归获取当前子节点的所有子孙ID,并将结果添加到childrenIds数组中
childrenIds.push(...this.getAllChildrenIds(childId));
});
}
return childrenIds;
},
// 获取选项显示标签
getOptionLabel(item) {
// 如果是多选,并且是多选的父节点,则显示路径
return item.path || item.className;
},
// 处理选择变化
handleChange(val) {
if (!this.multiple) {
// 单选直接触发input和change事件
this.$emit("input", val);
this.$emit("change", val);
return;
}
// 转换为Set,以便去重并方便后续操作
const newSelection = new Set(Array.isArray(val) ? val : [val]);
// 最终选中的值集合,用于存储最终的选中结果
const finalSelection = new Set();
// 处理所有选中的值
newSelection.forEach((id) => {
// 获取当前节点的信息,包括是否有子节点等信息
const node = this.nodeMap.get(id);
if (node) {
if (node.children && node.children.length > 0) {
// 如果是父节点,添加所有子节点
const childrenIds = this.getAllChildrenIds(id);
childrenIds.forEach((childId) => finalSelection.add(childId));
} else {
// 如果是子节点,直接添加
finalSelection.add(id);
}
}
});
// 转换为数组并触发事件
const result = Array.from(finalSelection);
this.$emit("input", result);
this.$emit("change", result);
},
},
};
</script>
<style lang="scss" scoped>
.tree-select {
.parent-node {
color: #909399;
font-weight: bold;
}
:deep(.el-select-dropdown__item) {
padding: 0 20px;
}
}
</style>
父组件使用
<template>
<el-row>
<el-button type="primary"
@click="handleShare">打开弹窗</el-button>
</el-row>
<el-dialog
:visible.sync="publishDialogVisible"
width="600px"
>
<el-form
:model="homeworkForm"
ref="homeworkForm"
label-width="80px"
>
<el-form-item label="发布班级" prop="classId">
<tree-select
v-model="homeworkForm.classId"
:data="classOptions"
placeholder="请选择班级"
:multiple="true"
@change="handleClassChange"
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="publishDialogVisible = false">取 消</el-button>
<el-button type="primary" @click="submitForm('homeworkForm')"
>确 定</el-button
>
</div>
</el-dialog>
</template>
import TreeSelect from "@/components/TreeSelect";
export default {
components: {
TreeSelect,
},
data() {
return {
//对话框是否显示
publishDialogVisible: false,
//提交表单
homeworkForm: {
classId: "", //发布班级,数组格式整数,例如[1,2]
},
//班级下拉列表数据
classOptions: [
{
id: 1,
label: "一年级",
children: [
{
id: 2,
label: "一班",
},
{
id: 3,
label: "二班",
},
],
},
],
}
},
methods: {
// 班级选择变化的处理方法
handleClassChange(val) {
console.log("选中的班级:", val);
},
handleShare(row) {
this.publishDialogVisible= true; //居中弹框打开
},
}
}