前言
工作中,刚好接手到一个需求,类似抖音出品的飞书软件中,组织架构选人的组件实现。看着似乎有些难度,但是仔细分析后,发现并不是很难,而且还跟我的另外一篇文章 Vue: 如何实现树状收展及勾选联动功能,同样的原理
飞书组织架构组件如图:
从图中我们可以看出,该数据是个树状形结构,每一层级有部门和人员组合,当勾选部门后,就无法点击下一级,搜索可直接匹配人员或部门;这恰恰跟我工作内容中的需求异曲同工
项目需求:
而我们的需求是这样的:
已选区域会显示出选择后的人员或部门。这里有几个逻辑需要描述:
当选择人仅可选择一人的时候:
- 1、部门的勾选框置灰不可点击,只能进入下级。
- 2、仅能勾选一个人进入已选区域
组件封装
主要利用到两个概念理论,一是地址引用;二是树状递归;如果有看过我这篇 Vue: 如何实现树状收展及勾选联动功能文章的JYM,那么这里就是另一个实践,应用到项目中去;
接下来,先从以下步骤去分析,为开发做好思路梳理
第一步:接口数据格式分析
根据后台人员给出的API,然后思考如何组装和增加属性,便于为数据交换做准备;
根层级给出的数据如图
虽然根节点没有父级parent属性,但可以通过数据加装,使其形成完整的树状;children中存储的是部门相关,staffs存储的是人员;
第二步:需求分析
需求中还需要我们实现勾选和半全选、全选状态,我们还需要为树状数据结构增加增加一个isChecked属性,用来标记选中状态;看需求图的右侧部分就是用来存储选中的内容,为了保持地址引用这个特性,我们可以将选中的人员或部门以一个对象的形式存储到选中数组中去;这样不论你重新替换左侧列表的展示数据,我们都可以保留原有对象的
可以翻看 Vue: 如何实现树状收展及勾选联动功能里面提到的,通过传入当前节点某个值(如id),查询所有父级节点 然后在vue中的<template>
里面循环遍历出来即可
第三步:代码实现
先定义几个存储变量
const membersDataObj = ref({ children: [] }); // 存储所有数据
const membersDataQueryList = ref([]); // 只存储所有人员 用于搜索
const searchStaffs = ref([]); // 通过搜索条件,模糊查询得出结果
const currentMembersAndDepartments = ref({ children: [], staffs: [] });
const levelDatas = ref([]); // 存储层级菜单面包屑
// 将已选中的对象全部加入
const isCheckedItems = ref([]);
转换数据,加装属性 isChecked, 层级level
const switchOrganiseData = (obj, level) => {
obj.level += 1;
if (obj.staffs && obj.staffs.length > 0) {
obj.staffs.forEach((staff) => {
staff.isChecked = false;
});
}
if (obj.children && obj.children.length > 0) {
obj.children.forEach((child) => {
child.isChecked = false;
switchOrganiseData(child, obj.level);
});
}
};
递归获取所有成员,并存储到membersDataQueryList
变量数组中,用于模糊查询的数据
// 递归获取所有成员
const getStaffs = (arr, staffArr) => {
arr.map((item) => {
if (item.staffs && item.staffs.length > 0) {
staffArr = staffArr.concat(item.staffs);
if (item.children && item.children.length > 0) {
staffArr = staffArr.concat(getStaffs(item.children, staffArr));
}
}
});
return staffArr;
};
接着就是,获取当前节点的所有父级别数据,再次用到,这篇文章 Vue: 如何实现树状收展及勾选联动功能文末的方法getParent
,再将所得数据存储到
const levelDatas = ref([]); // 存储层级菜单面包屑
const getParent = (data2, nodeID2) => {
let arrRes = [];
if (data2.length === 0) {
if (nodeID2) {
arrRes.unshift(data2);
}
return arrRes;
}
const rev = (data, nodeID) => {
for (let i = 0, { length } = data; i < length; i++) {
const node = data[i];
if (node.id == nodeID) {
arrRes.unshift(node);
// 查找到当前id,继续追随父级id
rev(data2, node.parent);
// 注意这里是传入的tree,不要写成data了,不然遍历的时候一直都是node.children,
//不是从最顶层开始遍历的
break;
} else {
// 如果当前节点没有对应id,则追溯该子类是否有匹配项
if (node.children && node.children.length > 0) {
rev(node.children, nodeID);
}
}
}
return arrRes;
};
arrRes = rev(data2, nodeID2);
return arrRes;
};
这里还有一个问题,就是部门下,还有成员,一个成员可能存在于多个部门下,所以需要去重,使用map函数即可
// 去重人员
const arrayNonRepeatfy = (staffArr) => {
const map = new Map();
staffArr.forEach((element) => {
if (!map.has(element.idStaff)) {
map.set(element.idStaff, element);
}
});
return Array.from(map.values());
# 更新推文
一、[Vue: 评论区实现发表情和@某人消息推送](https://juejin.cn/post/7200689071261106233)
二、[前端UI: 切图仔的新技能—PS动图制作](https://juejin.cn/post/7198135791234613303)
三、[Vue: 如何实现树状收展及勾选联动功能-超实用干货](https://juejin.cn/post/7195753369984958525)
四、[Vue3.2: 仿飞书App选址组件封装(TDesign)](https://juejin.cn/post/7249704630846554167)