小程序选人控件 - 仿企业微信实现多选及多层级无规则嵌套

本文介绍了一种实现多层级无规则嵌套的选人控件,类似于企业微信的选人功能。控件包括返回上一层按钮、显示部门和人员的列表以及已选人员的底部操作栏。通过动态更新列表、保存选中状态和双联动管理,实现了复杂的数据交互。文章详细分析了逻辑点并提供了代码实现。
摘要由CSDN通过智能技术生成

在很多系统中都有选择联系人的需求,市面上也没什么好的参照,产品经理看企业微信的选人挺好用的,就说参照这个做一个吧。。。

在这里插入图片描述

算了,还是试着做吧,企业微信的选人的确做的挺好,不得不佩服。

先看看效果图吧,多层级无规律的嵌套都能搞定

一、设计解读

在这里插入图片描述

整个界面分为三部分:

  • 最上面的返回上一层按钮
  • 中间的显示部门、人员的列表
  • 最下面显示和操作已选人员的 footer。

为什么加一个返回上一层按钮呢?

我也觉得比较丑,但小程序无法直接控制左上角返回键(自定义 Title 貌似可以,没试过),点左上角的返回箭头的话就退出选人控件到上个页面了。

我们的需求是点击一个文件夹,通过刷新当前列表进入下一级目录,感觉像是又进了一个页面,但其实并没有,只是列表的数据变化了。由此实现不定层级、无规律的部门和人员嵌套的支持。

比如先点击了首屏数据的第二个 item,它的 index1 ,就将 1 存入 indexList ;返回上一层时将最后一个元素删除。

当勾选了某个人或部门时,会在底部的框中显示所有已选人员或部门的名字,当文字超过屏幕宽度时可以向右无限滑动,底部 footer 始终保持一行。

最终选择的人以底部 footer 里显示的为准,点击确定时根据业务需要将已选人员数据发送给需要的界面。

二、功能逻辑分析

先看看数据格式

{
   
  id: TEACHER_ID,
  name: '教师',
  parentId: '',
  checked: false,
  isPeople: false,
  children: [
    {
   
      id: TEACHER_DEPARTMENT_ID,
      name: '部门',
      parentId: 'teacher',
      checked: false,
      isPeople: false,
      children: []
    },
    {
   
      id: TEACHER_SUBJECT_ID,
      name: '学科',
      parentId: 'teacher',
      checked: false,
      isPeople: false,
      children: []
    },
    {
   
      id: TEACHER_GRADECLASS_ID,
      name: '年级班级',
      parentId: 'teacher',
      checked: false,
      isPeople: false,
      children: []
    },
  ]
}

所有的数据组成一个数据树,子节点嵌套在父节点下。

id, name 不说了,parentId 指明它的父节点,children 包含它的所有子节点,checked 用来判断勾选状态,isPeople 判断是部门还是人员,因为两者的图标不一样。

注意:

本控件采用了数据分步加载的模式,除了最上层固定的几个分类,其他的每层数据都是点击具体的部门后才去请求服务器加载本部门下的数据的,然后再拼接到原始数据树上。这样可以提高加载速度,提升用户体验。

我也试了一次性把所有数据都拉下来,一是太慢,得三五秒,二是数据量太大的话(我这里应该是超过1000,阈值多少没测过),setData() 的时候就会报错:

在这里插入图片描述

超过最大长度了。。。所以只能分步加载数据。

当然如果你的数据量小,几十人或几百人,也可以选择一次性加载。

这个控件逻辑上还是比较复杂的,要考虑的细节太多……下面梳理一下主要的逻辑点

主要逻辑点

1. 需要一个数组存储所有被点击的部门在当前列表的索引 index ,这里用 indexList 表示

点击某个部门进入下一层目录时,将被点击部门的 index 索引 pushindexList 中。点击返回上一层按钮时,删除 indexList 中最后一个元素。

2. 要动态的更新当前列表 currentList

每进入新的一层,或返回上一层,都需要刷新 currentList 来实现页面的更新。知道下一层数据很容易,直接取被点击 itemchildren 赋值给 currentList 即可。

但如何还原上一层的数据呢?

第一点记录的 indexList 就发挥作用了,原始数据树为 originalList,循环遍历 indexList ,根据索引依次取出每层的 currentList 直到 indexList 的最后一个元素,就得到了返回上一层需要显示的数据。

3. 每一次勾选或取消选中都要更新原始的数据树 originalList

页面是根据每个 itemchecked 属性判断是否选中的,所以每次改变勾选状态都要设置被改变的 itemchecked 属性,然后更新 originalList。这样即使返回上一层了,再进到当前层级选中状态还会被保留,否则刷新 currentList 后已选状态将丢失。

4. 列表中选择状态的改变与底部 footer 的双联动

我们期望的效果是,选中currentList 列表的某一项,底部 footer 会自动添加被选人的名字。取消选中,底部 footer 也会自动删除。

也可以通过 footer 来删除已选人,点击 footer 中人名,会将此人从已选列表中删除,currentList 列表中也会自动取消勾选状态。

嗯,这个功能比较耗性能,每一次都需要大量的计算。考虑到性能和速度因素,本次只做了从 footer 删除只更新 currentList 的勾选状态。

什么意思呢?假如有两层,A 和 B,B 是 A 的下一层数据,即 A 是 B 的父节点。在 A 中选中了一个部门 校长室,点击下一层到 B,在 B 中又选了两个人 张三李四,这时底部 footer 里显示的应该是三个: 校长室张三李四。此时点击 footer张三footer 会把 张三 删除,中间列表中 张三 会被置为未选中状态,这没问题。但点击 footer校长室 , 在 footer 中是把 校长室 删除了,但再返回到上一层时,中间列表中的 校长室 依然是勾选状态,因为此时没有更新原始数据树 originalList。如果觉得这是个 bug, 可以加个更新 originalList 的操作。这样就要遍历 originalList 的每个元素判断与本次删除的 id 是否相等,然后改变 checked 值,如果数据量很大,会非常慢。我做了妥协……

关键的逻辑就这四块了,当然还有很多小细节,直接看代码吧,注释写的也比较详细。

三、代码

目录结构:
在这里插入图片描述

footer 文件夹下是抽离出的 footer 组件,userSelect 是选人控件的主要逻辑。把这几个文件复制过去就可以用了。

userSelect.js 里网络请求的代码替换为你的请求代码,注意数据的字段名是否一致。

userSelect 的代码

userSelect.js


import API from '../../../utils/API.js'
import ArrayUtils from '../../../utils/ArrayUtils.js'
import EventBus from '../../../components/NotificationCenter/WxNotificationCenter.js'

let TEACHER_ID = 'teacher';
let TEACHER_DEPARTMENT_ID = 't_department';
let TEACHER_SUBJECT_ID = 't_subject';
let TEACHER_GRADECLASS_ID = 't_gradeclass';
let STUDENT_ID = 'student';
let PARENT_ID = 'parent'

let TEACHER = {
   
  id: TEACHER_ID,
  name: '教师',
  parentId: '',
  checked: false,
  isPeople: false,
  children: [
    {
   
      id: TEACHER_DEPARTMENT_ID,
      name: '部门',
      parentId: 'teacher',
      checked: false,
      isPeople: false,
      children: []
    },
    {
   
      id: TEACHER_SUBJECT_ID,
      name: '学科',
      parentId: 'teacher',
      checked: false,
      isPeople: false,
      children: []
    },
    {
   
      id: TEACHER_GRADECLASS_ID,
      name: '年级班级',
      parentId: 'teacher',
      checked: false,
      isPeople: false,
      children: []
    },
  ]
}
let STUDENT = {
   
  id: STUDENT_ID,
  name: '学生',
  parentId: '',
  checked: false,
  isPeople: false,
  children: []
}
let PARENT = {
   
  id: PARENT_ID,
  name: '家长',
  parentId: '',
  checked: false,
  isPeople: false,
  children: []
}
let ORIGINAL_DATA = [
  TEACHER, STUDENT, PARENT
]

Page({
   
  data: {
   
    currentList: [], //当前展示的列表
    selectList: [],  //已选择的元素列表
    originalList: [], //最原始的数据列表
    indexList: [],  //存储目录层级的数组,用于准确的返回上一层
    selectList: [],  //已选中的人员列表
  },

  onLoad: function (options) {
   
    wx.setNavigationBarTitle({
   
      title: '选人控件'
    })
    this.init();
  },

  init(){
   
    //用户的单位id
    this.unitId = getApp().globalData.userInfo.unitId;
    //用户类型
    this.userType = 0;
    //上次选中的列表,用于判断是不是取消选中了
    this.lastTimeSelect = []

    this.setData({
   
      currentList: ORIGINAL_DATA, //当前展示的列表
      originalList: ORIGINAL_DATA, //最原始的数据列表
    })
  },

  clickItem(res){
   
    console.log(res)
    let index = res.currentTarget.id;
    let item = this.data.currentList[index]

    console.log("item", item)

    if (!item.isPeople) {
   
      //点击教师,下一层数据是写死的,不用请求接口
      if (item.id === TEACHER_ID) {
   
        this.userType = 2;
        
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值