需求背景
对于流程审核,在常见的 OA、ERP、CRM 等系统中是很常见的功能。审核自然就会有审核人的选择,这其中就会涉及公司或部门的组织架构关系,以及人员列表。目前公司做的产品就包含这个功能,而且是在移动端上实现。有这种功能的产品也很多,比如:企业微信、钉钉,或者一些定制化的 oa 系统等。一眼觉得企业微信的通讯录效果不错,也可以满足当前需求,so?
先上一波效果图(给各位看官老爷的前菜):
需求前瞻
上图是 web 端人员选择的方式,左侧显示组织架构,右侧加载对应的人员列表。要在移动端上实现这种效果的我对 uview 文档翻来翻去 n 遍也没找到合适的组件。插件市场也搜了下,有同样效果的,但是数据源和后端接口返回的格式有点区别,需要去拼格式。所以放弃了。后面灵机一动发现企业微信的通讯录功能好像可行,那就手撸一个。
需求分析
从前面的 web 端效果图可以看出有两种数据结构:
- 组织结构树形数据
js
[{
id: 'xxxxxx',
name: 'xxx',
parentId: '0',
children: [
{
id: 'xxxxxx',
name: 'xxx',
parent: 'xxxxxx'
},
{
id: 'xxxxxx',
name: 'xxx',
parent: 'xxxxxx',
children: []
},
...
]
}]
- 人员列表一维数组数据
js
[
{ id: 'xxxxxx', avatar: 'xxx', name: 'xxx', orgId: 'xxxxxx', orgName: 'xxx' },
{ id: 'xxxxxx', avatar: 'xxx', name: 'xxx', orgId: 'xxxxxx', orgName: 'xxx' },
{ id: 'xxxxxx', avatar: 'xxx', name: 'xxx', orgId: 'xxxxxx', orgName: 'xxx' },
{ id: 'xxxxxx', avatar: 'xxx', name: 'xxx', orgId: 'xxxxxx', orgName: 'xxx' },
...
]
再看企业微信通讯录,人员和组织数据渲染在一个页面,如何将两种类型的数据筛选出来?人员列表中有个关键字段为:orgId,对应的就是组织数据的 id。
需求实现
首先定义一个渲染列表 renderList,用来存放部门数据和人员数据,比如A部门下有M-1,M-2人员,D-1部门,D-1部门下又有 M-3 人员,... 。这里使用 pinia 来管理全局数据。
初始化获取到组织属性数据和所有人员列表数据后,进行数据筛选,我定义了 findChildrenById 和 findMemberByOrgId 方法来查找数据
ts
// 根据 id 获取当前节点的 children
const findChildrenById = (root: OrgTreeNode, targetId: string) => {
const searchNode = (node: OrgTreeNode) => {
if (node.id === targetId) {
return node.children?.map((item: OrgTreeNode) => {
item.type = "department";
return item;
});
}
if (node.children) {
for (const child of node.children) {
const result = searchNode(child);
if (result !== null) {
return result;
}
}
}
return null;
};
const result = searchNode(root);
return result || [];
};
// 根据组织的 id 和成员列表数据的 orgId 查询匹配的成员
const findMemberByOrgId = (targetId: string, memberList: any[]) => {
const result: any[] = [];
if (!memberList.length) return result;
for (const member of memberList) {
if (member.orgId === targetId) {
result.push({
id: member.id,
name: member.name,
orgId: member.orgId,
orgName: member.orgName,
gender: member.gender,
avatar: member.avatar,
positionName: member.positionName,
type: "member"
});
}
}
return result;
};
最终 renderList 里面的数据就是两个数组数据,在两个方法中,分别给目标数据添加了 type 属性,用于区分是部门(可点击进入下一层级)还是人员
数据渲染模板代码如下:
html
<template>
<view class="select-department-member box-border p-x-20rpx p-t-15rpx p-b-20rpx">
<template v-if="renderList.length">
<view v-for="(item, index) in renderList" :key="index">
<template v-if="item.type === 'member'">
<view class="item-box flex items-center" @click="handleClick(item)">
<view class="icon-box flex justify-center items-center">
<image :src="item.avatar" mode="scaleToFill" class="w-60 h-60"
style="border-radius: 10rpx;" />
</view>
<view class="member-name-box">
<view class="font-size-26rpx color-#2f3133">{{ item.name }}</view>
<view class="font-size-22rpx color-#81878c">{{ item.positionName || '--' }}</view>
</view>
</view>
</template>
<template v-if="item.type === 'department'">
<view class="item-box flex items-center" @click="handleClick(item)">
<view class="icon-box flex justify-center items-center">
<view class="bg-box flex justify-center items-center">
<image src="@/static/icons/folder.svg" mode="scaleToFill" class="w-60% h-60%" />
</view>
</view>
<view class="dept-name-box font-size-26rpx color-#2f3133">{{ item.name }}</view>
</view>
</template>
</view>
<view class="font-size-26rpx color-#81878c m-t-40rpx m-b-20rpx text-center">共<text class="m-x-5rpx">{{
totalMember }}</text>人</view>
</template>
<template v-else>
<view class="p-y-150rpx">
<empty-box paddingTop="10rpx" pWidth="200" pHeight="120"></empty-box>
</view>
</template>
</view>
</template>
里面的子部门在做层级跳转时,始终是在当前页面,所以定义了一个 pathStack 数组来存放当前部门的id与名称。进入下一级就新增,回退时就删除,并根据部门 id 去重新查找数据来更新 renderList 列表。
最后再来一波实操效果,暂时就先这样了,后面看情况再优化改进(os:能不改就不改,有要求再改,哈哈!)