目前用的是vue3+js,还没上ts。
用的递归实现数据过滤,但是数据量大的话,递归会比较耗性能,之前看到一个通过 map去把扁平数据变成树结构的数据,我在想能否也通过map()去实现数据过滤功能,但是暂时毫无头绪,请求指导。 正文开始:
需求:
1.要做一个人员树组件,会存在多个一级,或者一个一级,只需要获取最后一级的员工数据(这里用了递归)。
2.需要实现全选功能(我这里为了省力,直接在存在多个一级时,插入一个“全选”的0级节点,当然也写了全选的方法,就是没有直接加0级节点效果好)。
3.为了避免重名问题,需要获取部门名字。
效果图:
操作演示:
数据过滤这里,直接用el-tree自带的
:filter-node-method属性+watch就可以实现,只是过滤时需要设置一步:如果存在children,就禁选,保证只有最后一级可选。
<template>
<ElTree
ref="treeRef"
empty-text="暂无数据"
:data="userList"
...
:filter-node-method="filterNode"
/>
</template>
<script setup>
const filterNode = (value, data, node) => {
if (!value) {
data.disabled = false
return true
} else {
//带过滤条件时,所有父级都禁止勾选
// array.map(k => (!k.disabled) && (_childArr.push(k)))
(data.children) && (data.disabled = true)
}
let _arr = []
getParentNode(node, _arr, value)
let result = false
_arr.forEach(item => {
result = result || item
})
return result
}
// tree数据过滤
watch(keyword, (newVal) => {
treeRef.value.filter(newVal)
})
</script>
清空选中的方法:
// 清空CheckList
const clearCheckList = () => {
if (!treeRef?.value) return
treeRef.value.setCheckedKeys([])
while (checkList.value.length > 0) {
checkList.value.pop()
}
}
全选 /全不选
const handleCheckAll = (array) => {
treeRef.value.setCheckedNodes(array)
array.map(item => {
handleCheckList(item)
})
}
在勾选时,通过递归实现获取最后一级的人员数据:
// 勾选或者取消勾选
const handleCheckList = (val) => {
const _node = treeRef?.value?.getNode(val.id)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { // 勾选时
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// 通过new Set() 实现数据去重
checkList.value = Array.from(new Set(_checkList))
// 遍历子级,递归,直到获取最后一级的人员
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { // 取消勾选
checkList.value = checkList.value.filter(item => item.checked)
}
}
全部代码:
<script setup name="MultiTreeSelector">
import { ref, watch, onMounted } from 'vue'
const treeProps = {
value: 'id',
label: 'label',
disabled: 'disabled',
children: 'children'
}
const treeRef = ref()
const isExpandAll = ref(true) // 是否全展开
const keyword = ref('') // 搜索关键子
const checkList = ref([]) // 选中的list
const userList = ref([])
const resData = ref([
{
id: '3',
label: '营销部',
children: [
{
id: '3-1',
label: '张三'
},
{
id: '3-2',
label: '张三'
}
]
},
{
id: '2',
label: '业务部',
children: [
{
id: '2-1-1',
label: '业务部子部1',
children: [
{
id: '2-1-0001',
label: '张四'
},
{
id: '2-1-0002',
label: '从五'
}
]
},
{
id: '2-2-1',
label: '业务部子部222',
children: [
{
id: '2-2-00006',
label: '业务四'
},
{
id: '2-2-00005',
label: '业务五'
}
]
}
]
},
{
id: '4',
label: '软件开发部',
children: [
{
id: '4-1',
label: '李四'
},
{
id: '5-2',
label: '王五'
}
]
}
])
// 勾选或者取消勾选
const handleCheckList = (val) => {
const _node = treeRef?.value?.getNode(val.id)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { // 勾选时
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// 通过new Set() 实现数据去重
checkList.value = Array.from(new Set(_checkList))
// 遍历子级,递归,直到获取最后一级的人员
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { // 取消勾选
checkList.value = checkList.value.filter(item => item.checked)
}
}
/* 人员树 父级过滤的递归
* @node 当前节点
* @result 存结果的数组
* @key
* */
const getParentNode = (node, result, key) => {
let isPass = node?.data?.label?.indexOf(key) !== -1
isPass ? result.push(isPass) : ''
if (!isPass && node.level !== 1 && node.parent) {
return getParentNode(node.parent, result, key)
}
}
/*人员树过滤
* @value 过滤的关键字
* @data 被过滤的tree
* @node
* */
const filterNode = (value, data, node) => {
if (!value) {
data.disabled = false
return true
} else {
//带过滤条件时,所有父级都禁止勾选
// array.map(k => (!k.disabled) && (_childArr.push(k)))
(data.children) && (data.disabled = true)
}
let _arr = []
getParentNode(node, _arr, value)
let result = false
_arr.forEach(item => {
result = result || item
})
return result
}
// 通过tag 取消勾选
const handleCloseTag = (tag) => {
if (!treeRef?.value) return
checkList.value = checkList.value.filter(item => {
treeRef.value.setChecked(tag, false)
if (!item?.data) return
return item.data.id !== tag.data.id
})
}
// 清空CheckList
const clearCheckList = () => {
if (!treeRef?.value) return
treeRef.value.setCheckedKeys([])
while (checkList.value.length > 0) {
checkList.value.pop()
}
}
// 全选 /全不选
const handleCheckAll = (array) => {
treeRef.value.setCheckedNodes(array)
array.map(item => {
handleCheckList(item)
})
}
// 折叠/展开
const expandAll = () => {
const nodesMap = treeRef.value.store.nodesMap
for (let key in nodesMap) {
nodesMap[key].expanded = isExpandAll.value
}
}
const saveAsCommon = () => {
console.log('保存为个人分组,功能待开发')
}
// 执行tree数据过滤
watch(keyword, (newVal) => {
treeRef.value.filter(newVal)
})
onMounted(() => {
// 全选方法2:插入 全选node
userList.value = [{
id: '全选',
label: '全选',
children: [...resData.value]
}]
})
</script>
<template>
<div class="multi-tree-selector flex oh">
<div class="flex-1 flex-column">
<div class="tree-toolbar flex">
<ElInput class="search-input flex-1" v-model="keyword" placeholder="搜索">
<template #prefix>
<ElIcon class="el-input__icon">
<Search/>
</ElIcon>
</template>
</ElInput>
<div class="tree-toolbar__right">
<ElCheckbox v-model="isExpandAll" @change="expandAll">展开全部</ElCheckbox>
<ElButton @click="handleCheckAll(userList)" size="small">全选</ElButton>
<ElButton @click="handleCheckAll([] )" size="small">全不选</ElButton>
</div>
</div>
<div class="flex-1 oh">
<ElScrollbar height="100%">
<ElTree
ref="treeRef"
node-key="id"
empty-text="暂无数据"
:data="userList"
:props="treeProps"
show-checkbox
highlight-current
:default-expand-all="isExpandAll"
@check="handleCheckList"
:filter-node-method="filterNode"
/>
</ElScrollbar>
</div>
</div>
<div class="multi-tree-selector__right">
<div class="multi-tree-selector__right__header">
<span class="mr-1">已选: {{ checkList.length }}</span>
<ElButton type="primary" link @click="saveAsCommon">保存为个人分组</ElButton>
<ElButton class="fr" link @click="clearCheckList">清空</ElButton>
</div>
<ElScrollbar class="multi-tree-selector__right__content" height="calc(100% - 49px)">
<ElTag v-for="user in checkList" :key="user.id" @close="handleCloseTag(user)" closable>{{
user.label
}}[{{
user.parent.data.label
}}]
</ElTag>
</ElScrollbar>
</div>
</div>
</template>
<style lang="scss">
.multi-tree-selector {
width: 680px;
height: 400px;
background: #fff;
z-index: 999;
.tree-toolbar {
border-bottom: 1px solid var(--border-light);
.tree-toolbar__right {
padding-right: var(--space-primary);
.el-checkbox {
padding-top: 10px;
margin-right: 8px;
vertical-align: -4px;
}
.el-button {
//margin-top: 10px;
}
}
}
.search-input {
height: 49px;
.el-input__wrapper {
box-shadow: none;
}
}
.multi-tree-selector__right {
width: 40%;
border-left: 1px solid var(--border-light);
.multi-tree-selector__right__header {
height: 49px;
padding: var(--space-primary);
border-bottom: 1px solid var(--border-light);
box-sizing: border-box;
}
.multi-tree-selector__right__content {
padding: 0 0 var(--space-primary) var(--space-primary);
.el-tag {
margin-top: var(--space-primary);
margin-right: var(--space-primary);
}
}
}
}
/*以下为 通用样式*/
.flex {
display: flex;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-1 {
flex: 1;
}
.oh {
overflow: hidden;
}
</style>