文章目录
一、使用
1.效果
el-tree改造了下:
- 可选可取消
- 有默认值
- data1包含父子节点id传递 data2只传子节点id
- 默认展开
- 点击节点也可触发选择
- 节点内容自定义
- 结果1:获取包含父节点的id
- 结果2:提取子节点的 ID,不包括父节点
2.封装树组件TreeSelect.vue
<template>
<div>
<el-scrollbar>
<div class="ball mb radius overflow">
<div class="bb" style="background: rgba(124, 162, 121, 0.1); padding: 4px 16px">
<el-checkbox v-model="allFlag" @change="selectCheckBox($event)">{{ props.title }}</el-checkbox>
</div>
<!--
default-checked-keys:默认展开值(正常来说需要包含父级id的 但是我们后端不要后端id )
show-checkbox:多选框
node-key:每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
default-expand-all:是否默认展开所有节点
expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点
default-checked-keys:默认勾选的节点的 key 的数组
check-on-click-node:是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点
props:配置选项,具体看下表
-->
{{ childKeys }}
<div style="padding: 4px 20px">
<el-tree
:key="keyIndex"
ref="treeRef"
class="tree_ref"
style="max-width: 600px"
:data="props.data"
show-checkbox
node-key="id"
:default-expand-all="true"
:expand-on-click-node="false"
:default-checked-keys="childKeys"
:check-on-click-node="true"
:props="defaultProps"
@check-change="checkChange"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span style="color: red; margin-left: 10px">id:{{ data.id }}</span>
</span>
</template>
</el-tree>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const emit = defineEmits(['update:childKeysVal']);
const props = defineProps({
useWhoId: { type: String, default: () => 'son' }, // 使用id是否包含父级 farAndSon 包含父级和自己id son 只有子级id
childKeysVal: { type: Array, default: () => [] }, // 初始化选中子节点的 ID
data: { type: Array, default: () => [] }, // 树数据
title: { type: String, default: () => '' },
flag: { type: Boolean, default: () => false }, // 勾选
allLength: { type: Number, default: () => 0 }, // 全部长度
})
let treeRef = ref(null);
// let childKeys = ref([5, 10]); // 初始化选中子节点的 ID
let childKeys = ref(props.childKeysVal || {})
let allFlag = ref(props.flag) // 仅做回显勾选
const defaultProps = {
children: 'children',
label: 'treeName',
};
let keyIndex = ref(1) // 注意该key在需要全id和只需要子集id的时候用法不同
const checkChange = () => {
// 获取所有选中的节点对象
const checkedNodes = treeRef.value.getCheckedNodes();
if (props.useWhoId === 'farAndSon') {
// 结果1:获取包含父节点的id
childKeys.value = treeRef.value.getCheckedKeys()
} else {
// 结果2:提取子节点的 ID,不包括父节点
childKeys.value = checkedNodes
.filter(node => !node.children) // 只保留没有子节点的节点
.map(node => node.id); // 提取 ID
}
emit('update:childKeysVal', childKeys.value) // 同步修改父组件的值
// console.log('默认值', childKeys.value, checkedNodes); // 只包含子节点的 ID
keyIndex.value++
}
// 手动全选
const selectCheckBox = (value,) => {
// console.log(value, );
if (props.useWhoId === 'farAndSon') {
// 结果1:获取包含父节点的id
if (value) {
childKeys.value = getAllNodeIds(props.data)
emit('update:childKeysVal', childKeys.value) // 同步修改父组件的值
console.log('所有层级id', getAllNodeIds(props.data));
} else {
// 取消勾选所有节点
childKeys.value = []
emit('update:childKeysVal', childKeys.value) // 同步修改父组件的值
treeRef.value.setCheckedKeys([]);
}
keyIndex.value++
} else {
// 结果2:提取子节点的 ID,不包括父节点
if (value) {
// 勾选所有节点
const allKeys = props.data.flatMap(node => getAllNodeKeys(node));
treeRef.value.setCheckedKeys(allKeys);
} else {
childKeys.value = []
emit('update:childKeysVal', childKeys.value) // 同步修改父组件的值
// 取消勾选所有节点
treeRef.value.setCheckedKeys([]);
}
}
};
const getAllNodeKeys = (node) => {
let keys = [node.id];
if (node.children) {
node.children.forEach(child => {
keys = keys.concat(getAllNodeKeys(child));
});
}
// console.log(keys);
return keys;
}
// 递归函数获取所有节点的 ID
const getAllNodeIds = (nodes) => {
let ids = [];
nodes.forEach(node => {
ids.push(node.id); // 添加当前节点的 ID
if (node.children) {
ids = ids.concat(getAllNodeIds(node.children)); // 递归调用以获取子节点的 ID
}
});
return ids;
};
watch(() => childKeys.value, (newOptions) => {
// 根据监听的值,判断是否全选
allFlag.value = newOptions.length === props.allLength
}, { deep: true })
</script>
<style lang="scss" scoped>
:deep(.tree_ref) {
margin-left: 12px;
.el-tree-node__expand-icon {
display: none;
}
}
.ball {
border: 1px solid #e9e9e9;
}
.mb {
margin-bottom: 12px;
}
.overflow {
overflow: hidden;
}
.bb {
border-bottom: 1px solid #e9e9e9;
}
</style>
3.使用树组件
<template>
<div>
<el-scrollbar class="pall">
<!--
useWhoId:
key:刷新
data:树数据
flag:全选
allLength:判断全选的长度
title:树标题
childKeysVal:选中的值
-->
<TreeSelect
v-if="data1.length"
:useWhoId="'farAndSon'"
:key="keyIndex"
:data="data1"
:flag="flag1"
:allLength="allLength1"
v-model:childKeysVal="childKeys1"
:title="'树数据:传递父子勾选'"
></TreeSelect>
<TreeSelect
v-if="data2.length"
:useWhoId="'son'"
:key="keyIndex"
:data="data2"
:flag="flag2"
:allLength="allLength2"
v-model:childKeysVal="childKeys2"
:title="'树数据:只传递子勾选'"
></TreeSelect>
</el-scrollbar>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import TreeSelect from './treeSelect.vue'
let keyIndex = ref(1) //刷新树
let childKeys1 = ref([]) // 父子勾选
let childKeys2 = ref([]) // 只传递子勾选
let data1 = ref([]) //树数据
let data2 = ref([]) //树数据
let flag1 = ref(false) // 是否全选
let flag2 = ref(false) // 是否全选
let allLength1 = ref(0) // 树数据长度
let allLength2 = ref(0) // 树数据长度
// 获取树
const getTreeData = async () => {
childKeys1.value = [1, 11, 4, 9, 10] // 父子勾选
childKeys2.value = [11, 9, 10] // 只传递子勾选
keyIndex.value++
// 返回的原始树数据
let treeData = [
{
id: 1,
treeName: 'Level one 1',
children: [
{
id: 11,
treeName: 'Level two 1-11',
children: [], // 没有子集就不要返回children-否则会勾选不上数据
},
{
id: 4,
treeName: 'Level two 1-1',
children: [
{
id: 9,
treeName: 'Level three 1-1-1',
},
{
id: 10,
treeName: 'Level three 1-1-2',
},
],
},
],
},
{
id: 2,
treeName: 'Level one 2',
children: [
{
id: 5,
treeName: 'Level two 2-1',
},
{
id: 6,
treeName: 'Level two 2-2',
},
],
},
{
id: 3,
treeName: 'Level one 3',
children: [
{
id: 7,
treeName: 'Level two 3-1',
},
{
id: 8,
treeName: 'Level two 3-2',
},
],
},
];
// 删除children是[]的 属性children 否则会影响点击选中
const removeEmptyChildren = (data) => {
return data.reduce((acc, item) => {
// 处理子节点
if (item.children) {
item.children = removeEmptyChildren(item.children);
// 如果子节点为空数组,则删除该属性
if (Array.isArray(item.children) && item.children.length === 0) {
delete item.children;
}
}
// 将处理后的节点添加到结果数组
acc.push(item);
return acc;
}, []);
};
data1.value = removeEmptyChildren(treeData);
data2.value = removeEmptyChildren(treeData);
flag1.value = getAllNodeIds(data1.value).length === childKeys1.value.length
flag2.value = getAllChildNodeIds(data2.value).length === childKeys2.value.length
allLength1.value = getAllNodeIds(data1.value).length
allLength2.value = getAllChildNodeIds(data2.value).length
}
// 获取所有子节点的 ID
const getAllChildNodeIds = (nodes) => {
let ids = [];
nodes.forEach(node => {
if (Array.isArray(node.children)) {
ids = ids.concat(getAllChildNodeIds(node.children));
} else {
ids.push(node.id); // 添加当前节点的 ID
}
});
return ids;
};
// 递归函数获取所有节点的 ID - 父级和子级
const getAllNodeIds = (nodes) => {
let ids = [];
nodes.forEach(node => {
ids.push(node.id); // 添加当前节点的 ID
if (node.children) {
ids = ids.concat(getAllNodeIds(node.children)); // 递归调用以获取子节点的 ID
}
});
return ids;
};
onMounted(() => {
getTreeData()
})
</script>
<style lang="scss" scoped>
:deep(.tree_ref) {
margin-left: 12px;
.el-tree-node__expand-icon {
display: none;
}
}
.pall {
padding: 16px 16px;
}
</style>
3.旧版本不建议使用(未封装,直接包含数据和树的单独一个vue文件)
<template>
<div class="absolute">
<el-scrollbar class="pall">
<div class="ball mb radius overflow">
<div class="bb" style="background: rgba(124, 162, 121, 0.1); padding: 4px 16px;">
{{allFlag}}
<el-checkbox v-model="allFlag" @change="selectCheckBox($event,1)">拓扑图树结构</el-checkbox>
</div>
<!--
default-checked-keys:默认展开值(正常来说需要包含父级id的 但是我们后端不要后端id )
show-checkbox:多选框
node-key:每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
default-expand-all:是否默认展开所有节点
expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点
default-checked-keys:默认勾选的节点的 key 的数组
check-on-click-node:是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点
indent 相邻级节点间的水平缩进,单位为像素
props:配置选项,具体看下表
-->
{{ childKeys }}
<div style="padding: 4px 20px">
<el-tree :key="keyIndex" ref="treeRef" class="tree_ref" style="max-width: 600px" :data="data" show-checkbox
node-key="id" :default-expand-all="true" :expand-on-click-node='false' :default-checked-keys="childKeys"
:check-on-click-node="true" :props="defaultProps" @check-change="checkChange">
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span style="color:red;margin-left: 10px">id:{{data.id }}</span>
</span>
</template>
</el-tree>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
let treeRef = ref(null);
let childKeys = ref([5, 10]); // 初始化选中子节点的 ID
const defaultProps = {
children: 'children',
label: 'label',
};
let keyIndex = ref(1) // 注意该key在需要全id和只需要子集id的时候用法不同
const data = [
{
id: 1,
label: 'Level one 1',
children: [
{
id: 4,
label: 'Level two 1-1',
children: [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 10,
label: 'Level three 1-1-2',
},
],
},
],
},
{
id: 2,
label: 'Level one 2',
children: [
{
id: 5,
label: 'Level two 2-1',
},
{
id: 6,
label: 'Level two 2-2',
},
],
},
{
id: 3,
label: 'Level one 3',
children: [
{
id: 7,
label: 'Level two 3-1',
},
{
id: 8,
label: 'Level two 3-2',
},
],
},
];
const checkChange = () => {
// 获取所有选中的节点对象
const checkedNodes = treeRef.value.getCheckedNodes();
// // 结果1:获取包含父节点的id
// childKeys.value = treeRef.value.getCheckedKeys()
// 结果2:提取子节点的 ID,不包括父节点
childKeys.value = checkedNodes
.filter(node => !node.children) // 只保留没有子节点的节点
.map(node => node.id); // 提取 ID
console.log('默认值', childKeys.value, checkedNodes); // 只包含子节点的 ID
keyIndex.value++
}
let allFlag = ref(false)
// 手动全选
const selectCheckBox = (value, num) => {
// console.log(value, num);
// // 结果1:获取包含父节点的id
// if (value) {
// childKeys.value = getAllChildNodeIds(data)
// console.log('所有层级id', getAllChildNodeIds(data));
// } else {
// // 取消勾选所有节点
// childKeys.value = []
// treeRef.value.setCheckedKeys([]);
// }
// keyIndex.value++
// 结果2:提取子节点的 ID,不包括父节点
if (value) {
// 勾选所有节点
const allKeys = data.flatMap(node => getAllNodeKeys(node));
treeRef.value.setCheckedKeys(allKeys);
} else {
childKeys.value = []
// 取消勾选所有节点
treeRef.value.setCheckedKeys([]);
}
};
// 递归函数获取所有子节点的 ID
const getAllNodeKeys = (node) => {
let keys = [node.id];
if (node.children) {
node.children.forEach(child => {
keys = keys.concat(getAllNodeKeys(child));
});
}
console.log(keys);
return keys;
}
// 递归函数获取所有节点的 ID(注意结果1和2是否需要父节点的id)
const getAllChildNodeIds = (nodes) => {
let ids = [];
nodes.forEach(node => {
if (Array.isArray(node.children)) {
ids = ids.concat(getAllChildNodeIds(node.children));
// ids.push(node.id); // 添加当前节点的 ID 结果1:获取包含父节点的id
} else {
ids.push(node.id); // 添加当前节点的 ID 结果2:提取子节点的 ID,不包括父节点
}
});
return ids;
};
watch(() => childKeys.value, (newVal) => {
allFlag.value = newVal.length === getAllChildNodeIds(data).length
}, { immediate: true, deep: true })
</script>
<style lang="scss" scoped>
:deep(.tree_ref) {
margin-left: 12px;
.el-tree-node__expand-icon {
display: none;
}
}
</style>
二、el-tree超出省略号
1.效果
2.代码
<el-tree style="width: 100%;" :data="treeData" :props="defaultProps" :default-expand-all="true"
:expand-on-click-node="false" @node-click="activeFun" :indent="4" :highlight-current="true">
<template #default="{ node, }">
<span class="custom-tree-node">
<span><my-tooltips :content="node.label ?? ''" /></span>
</span>
</template>
</el-tree>
.custom-tree-node {
width: calc(100%);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}