Vue3.2: 仿飞书App组织架构选人组件封装

13,521 阅读5分钟

前言

工作中,刚好接手到一个需求,类似抖音出品的飞书软件中,组织架构选人的组件实现。看着似乎有些难度,但是仔细分析后,发现并不是很难,而且还跟我的另外一篇文章 Vue: 如何实现树状收展及勾选联动功能,同样的原理

飞书组织架构组件如图:

QQ20230213-210135-HD.gif

从图中我们可以看出,该数据是个树状形结构,每一层级有部门和人员组合,当勾选部门后,就无法点击下一级,搜索可直接匹配人员或部门;这恰恰跟我工作内容中的需求异曲同工

项目需求:

而我们的需求是这样的:

image.png

已选区域会显示出选择后的人员或部门。这里有几个逻辑需要描述:

  1. 如果只选人,那么该人员就会被选中。

  2. 如果选择部门,那么该部门就会被选中,这里需要说明的是当选中了部门,此时已选区域会显示部门名称+人数。并且已选的数量统计这里只会统计项数,不会只统计人数。

  3. 另外,交互层面,如果选择了部门,该部门会被置灰不可进入下级。

  4. 选择部门这里需要明确的是,虽然已选区域显示的部门,但是实际上选择的还是该部门下的人。这里不考虑新进部门的人和之后退出部门的人。只会以当时选择时部门下的所有人。

  5. 当再次添加人的时候,根据业务不同,会有两种方式:

    1. 业务功能本身具有移除已选中的人。此时再次选人时,未选区域会回显出之前已经选中过的人,并且会置灰显示不可取消勾选;已选区域不会回显之前选中的人,而是每次进来只会显示当前已选的人或部门。
    2. 业务功能没有移除已选中的人。此时再次选人时,未选区域同样会回显之前已经选中过的人,但是不会置灰,可以取消勾选;已选区域会回显之前选中的人。
  6. 再次添加人时,如果之前小明已经被添加过了,后面在添加时选中了小明所在的部门,那么此时需要做人员的去重处理,这里的去重处理不需要弹出提示,由系统自动过滤。

当选择人仅可选择一人的时候:

  • 1、部门的勾选框置灰不可点击,只能进入下级。
  • 2、仅能勾选一个人进入已选区域

组件封装

主要利用到两个概念理论,一是地址引用;二是树状递归;如果有看过我这篇 Vue: 如何实现树状收展及勾选联动功能文章的JYM,那么这里就是另一个实践,应用到项目中去;

接下来,先从以下步骤去分析,为开发做好思路梳理

第一步:接口数据格式分析

根据后台人员给出的API,然后思考如何组装和增加属性,便于为数据交换做准备;

根层级给出的数据如图image.png

第二层级的如图image.png

虽然根节点没有父级parent属性,但可以通过数据加装,使其形成完整的树状;children中存储的是部门相关,staffs存储的是人员;

第二步:需求分析

需求中还需要我们实现勾选和半全选、全选状态,我们还需要为树状数据结构增加增加一个isChecked属性,用来标记选中状态;看需求图的右侧部分就是用来存储选中的内容,为了保持地址引用这个特性,我们可以将选中的人员或部门以一个对象的形式存储到选中数组中去;这样不论你重新替换左侧列表的展示数据,我们都可以保留原有对象的记录值 QQ20230214-085057-HD.gif

这里还有一个点就是这部分的实现

image.png

可以翻看 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());
};

应JYM要求,附带源码参考:仅供参考,不通用哦!

更新推文

一、Vue: 评论区实现发表情和@某人消息推送

二、前端UI: 切图仔的新技能—PS动图制作

三、Vue: 如何实现树状收展及勾选联动功能-超实用干货

四、Vue3.2: 仿飞书App选址组件封装(TDesign)