如下图所示
采用的是elementui的元素来进行此功能的开发,下面为代码示例
<template>
<div class="body">
<div class="left">
<el-input v-model="filterText" placeholder="请输入要搜索的知识点" />
<el-button type="primary" @click="selectAll" :disabled="Fselect">{{ checkAllText }}</el-button>
<el-tree ref="treeRef" class="filter-tree" :data="filteredData" default-expand-all
:filter-node-method="(filterNode as any)" :render-after-expand="false" multiple show-checkbox
@check="handleNodeClick" node-key="id" />
</div>
<el-divider direction="vertical" />
<div class="right">
<p>已选{{ selectNum }}个</p>
<el-tag type="info" size="large" v-for="(el, i) in childrenValues" :key="i" style="margin: 5px 0 0 5px;"
@close="handleClose(el)" closable>
{{ el.label }}
</el-tag>
</div>
</div>
</template>
<script lang='ts' setup>
import { computed, ref, watch } from 'vue'
import { ElTree } from 'element-plus'
interface Tree {
id: number
label: string
children?: Tree[]
}
//是否全选
const isCheckAll = ref<boolean>(false);
const checkAllText = computed(() => isCheckAll.value ? '取消全选' : '全选');
const selectNum = ref<number>(0);
const filterText = ref<string>('')
const treeRef = ref<InstanceType<typeof ElTree>>()
const Fselect = ref<boolean>(false)
const data: Tree[] = [
{
id: 1,
label: '计算机组成原理',
children: [
{
id: 2,
label: '寄存器1',
},
],
},
{
id: 3,
label: '中央处理器',
children: [
{
id: 4,
label: '寄存器2',
},
],
},
{
id: 5,
label: '寄存器',
children: [
{
id: 6,
label: '寄存器3',
},
],
},
{
id: 7,
label: '寄存器',
children: [
{
id: 8,
label: '寄存器4',
},
],
}
]
watch(filterText, (val) => {
treeRef.value!.filter(val)
seleHig(childrenValues.value)
if (val.length == 0) {
Fselect.value = false
} else {
Fselect.value = true
}
})
//选中高亮
const seleHig = (arr: { id: number, label: string }[]) => {
const levelOneNodes = arr.map((node: Tree) => node.id);
const levelTwoNodes = arr.flatMap((node: Tree) =>
node.children ? node.children.map((child: Tree) => child.id) : []
);
defaultCheckedKeys.value = levelOneNodes.concat(levelTwoNodes);
(treeRef.value as any).setCheckedKeys(defaultCheckedKeys.value);
}
const filterNode = (value: string, data: Tree) => {
if (!value) return true
return data.label.includes(value)
}
const filteredData = computed(() => {
const result = data.filter((node) => filterFn(filterText.value, node))
return result
})
const filterFn = (filterText: string, node: Tree) => {
// 如果节点匹配过滤器文本,则将其返回
if (node.label.includes(filterText)) {
return true
}
// 否则,递归遍历该节点的所有子节点,查找匹配项
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
if (filterNode(filterText, node.children[i])) {
return true
}
}
}
// 如果没有找到匹配项,则返回false
return false
}
//选中的数据操作
/**
* @param { id: number, label: string }>获取ref内选中的children的值
*/
const childrenValues = ref<{ id: number, label: string }[]>([])
const handleNodeClick = () => {
const checkedNodes = (treeRef.value as any).getCheckedNodes()
childrenValues.value = checkedNodes.flatMap(({ children }: Tree) =>
children ? children.map(({ id, label }: Tree) => ({ id, label })) : []
);
}
//默认选中的节点
const defaultCheckedKeys = ref<number[]>([]);
const selectAll = (): void => {
class Person {
getChildren = async (): Promise<void> => {
try {
isCheckAll.value = !isCheckAll.value;
if (isCheckAll.value) {
seleHig(filteredData.value);
handleNodeClick();
} else {
defaultCheckedKeys.value = [];
(treeRef.value as any).setCheckedKeys([]);
childrenValues.value = []
}
} catch (error) {
// 在这里处理错误
console.error('Error occurred:', error);
}
}
}
new Person().getChildren();
}
//监听childrenValues与data--children的差距
watch(childrenValues, (newVal: Tree[]) => {
selectNum.value = newVal.length
const childrenVa = filteredData.value.flatMap(node => node.children ?? []).map(child => child);
if (newVal.length == childrenVa.length) {
// 全选了
isCheckAll.value = true;
} else {
// 部分选中或全没选
isCheckAll.value = false;
}
})
const handleClose = (tag: any) => {
const index = childrenValues.value.findIndex((item) => item.id === tag.id);
if (index !== -1) {
childrenValues.value.splice(index, 1);
}
seleHig(childrenValues.value)
if (childrenValues.value.length == 0) {
isCheckAll.value = false;
}
}
defineExpose({
childrenValues
})
</script>
<style lang="scss" scoped>
.body {
display: flex;
justify-content: space-around;
.el-divider--vertical {
height: auto;
}
.left {
width: 50%;
position: relative;
.el-tree {
background: #F2F2F9;
}
.el-input {
width: 70%;
margin-bottom: 50px;
}
.el-button {
position: absolute;
top: 40px;
right: 0;
background: #060E83;
color: #fff;
}
}
.right {
width: 50%;
box-sizing: border-box;
padding: 0 0 0 30px;
}
}
:deep(.el-tree-node__content) {
display: flex !important;
align-items: center !important;
height: 40px !important;
cursor: pointer !important;
}
:deep(.el-tree-node__content):hover {
background: none;
}
:deep(.el-tree-node:focus>.el-tree-node__content) {
background: none;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #060E83;
border-color: #060E83;
}
.el-tag--large {
padding: 20px 20px;
height: 32px;
font-size: 16px;
}
</style>
下面示例是对接口数据以及修复的一些bug操作,可以相互对照一下有哪些不同
<template>
<div class="body">
<div class="left">
<el-input v-model="filterText" placeholder="请输入要搜索的知识点" :offset="80" />
<el-button type="primary" @click="selectAll" :disabled="Fselect" :offset="100">{{ checkAllText
}}</el-button>
<el-scrollbar>
<el-tree ref="treeRef" class="filter-tree" :data="filteredData" :filter-node-method="(filterNode as any)"
:render-after-expand="false" multiple show-checkbox @check="handleNodeClick"
node-key="knowledgePointsNo" :props="{ label: 'knowledgePointsName' }" />
</el-scrollbar>
</div>
<el-divider direction="vertical" />
<div class="right">
<p>已选{{ selectNum }}个</p>
<el-scrollbar>
<el-tag type="info" size="large" v-for="(el, i) in childrenValues" :key="i" style="margin: 5px 0 0 5px;"
@close="handleClose(el)" closable>
{{ el.knowledgePointsName }}
</el-tag>
</el-scrollbar>
</div>
</div>
</template>
<script lang='ts' setup>
import { computed, ref, watch, onMounted } from 'vue'
import { ElTree } from 'element-plus'
import { KnowledgeList } from "@/api/teacher/question_blank";
interface Tree {
id: number
label: string
children?: Tree[]
}
//是否全选
const isCheckAll = ref<boolean>(false);
const checkAllText = computed(() => isCheckAll.value ? '取消全选' : '全选');
const selectNum = ref<number>(0);
const filterText = ref<string>('')
const treeRef = ref<InstanceType<typeof ElTree>>()
const Fselect = ref<boolean>(false)
const data: Tree[] = ref([])
watch(filterText, (val) => {
treeRef.value!.filter(val)
seleHig(childrenValues.value)
if (val.length == 0) {
Fselect.value = false
} else {
Fselect.value = true
}
})
//选中高亮
const seleHig = (arr: { knowledgePointsNo: string, knowledgePointsName: string }[]) => {
const levelOneNodes = arr.map((node: Tree) => node.knowledgePointsNo);
const levelTwoNodes = arr.flatMap((node: Tree) =>
node.children ? node.children.map((child: Tree) => child.knowledgePointsNo) : []
);
defaultCheckedKeys.value = levelOneNodes.concat(levelTwoNodes);
(treeRef.value as any).setCheckedKeys(defaultCheckedKeys.value);
}
const filterNode = (value: string, data: Tree) => {
if (!value) return true
return data.knowledgePointsName.includes(value)
}
const filteredData = computed(() => {
const result = data.value.filter((node) => filterFn(filterText.value, node))
return result
})
const filterFn = (filterText: string, node: Tree) => {
// 如果节点匹配过滤器文本,则将其返回
if (node.knowledgePointsName.includes(filterText)) {
return true
}
// 否则,递归遍历该节点的所有子节点,查找匹配项
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
if (filterNode(filterText, node.children[i])) {
return true
}
}
}
// 如果没有找到匹配项,则返回false
return false
}
//选中的数据操作
/**
* @param { id: number, label: string }>获取ref内选中的children的值
*/
const childrenValues = ref<{ knowledgePointsNo: string, knowledgePointsName: string }[]>([])
const handleNodeClick = () => {
const checkedNodes = (treeRef.value as any).getCheckedNodes({ leafOnly: false });
childrenValues.value = checkedNodes.map(({ knowledgePointsNo, knowledgePointsName, children }: Tree) => {
if (!children || children.length === 0) {
return { knowledgePointsNo, knowledgePointsName };
}
return { knowledgePointsNo, knowledgePointsName, children };
});
}
//默认选中的节点
const defaultCheckedKeys = ref<number[]>([]);
const selectAll = (): void => {
class Person {
getChildren = async (): Promise<void> => {
try {
isCheckAll.value = !isCheckAll.value;
if (isCheckAll.value) {
seleHig(filteredData.value);
handleNodeClick();
} else {
defaultCheckedKeys.value = [];
(treeRef.value as any).setCheckedKeys([]);
childrenValues.value = []
}
} catch (error) {
// 在这里处理错误
console.error('Error occurred:', error);
}
}
}
new Person().getChildren();
}
//监听childrenValues与data--children的差距
watch(childrenValues, (newVal: Tree[]) => {
selectNum.value = newVal.length
const childrenVa = filteredData.value.flatMap(node => {
if (node.children && node.children.length > 0) {
return node.children;
} else {
return [node]; // 将当前父级节点作为选中项
}
});
if (newVal.length == childrenVa.length) {
// 全选了
isCheckAll.value = true;
} else {
// 部分选中或全没选
isCheckAll.value = false;
}
})
const handleClose = (tag: any) => {
const index = childrenValues.value.findIndex((item) => item.knowledgePointsNo === tag.knowledgePointsNo);
if (index !== -1) {
childrenValues.value.splice(index, 1);
}
selectNum.value = childrenValues.value.length
seleHig(childrenValues.value);
isCheckAll.value = false;
}
onMounted(() => {
KnowledgeList({
"knowledgePointsName": "",
"pageNum": "",
"pageSize": ""
}).then(({ data: { data: { list } } }) => {
data.value = list
})
})
defineExpose({
childrenValues
})
</script>
<style lang="scss" scoped>
.body {
display: flex;
justify-content: space-around;
height: 50vh;
.el-divider--vertical {
height: auto;
}
.left {
.el-scrollbar {
width: 100%;
height: 43.5vh;
}
width: 50%;
position: relative;
.el-tree {
background: #F2F2F9;
}
.el-input {
width: 70%;
margin-bottom: 50px;
}
.el-button {
position: absolute;
top: 40px;
right: 0;
background: #060E83;
color: #fff;
}
}
.right {
width: 50%;
box-sizing: border-box;
padding: 0 0 0 30px;
}
}
:deep(.el-tree-node__content) {
display: flex !important;
align-items: center !important;
height: 40px !important;
cursor: pointer !important;
}
:deep(.el-tree-node__content):hover {
background: none;
}
:deep(.el-tree-node:focus>.el-tree-node__content) {
background: none;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #060E83;
border-color: #060E83;
}
:deep(.el-checkbox__input.is-indeterminate .el-checkbox__inner) {
background-color: #060E83;
border-color: #060E83;
}
.el-tag--large {
padding: 20px 20px;
height: 32px;
font-size: 16px;
}
</style>