element UI 使用 el-tree 多次进行不同条件查询时,渲染不同dom节点时,缓存数据。(@check-change的小众使用方法)

980 阅读6分钟

功能要求

点击按钮后,弹框显示可勾选的人员,一级节点为班级,二级节点为班级内的人员。同时提供 条件搜索 ,搜索后显示符合条件的可勾选人员。勾选操作都是在一个弹框页面进行的,等用户 多次搜索选定所有人员 后,点击确定按钮,点击提交将数据传给后端。

第一次的分析思路(失败的逻辑)

可能大家的第一反应都是用el-tree自带的 check-change 或者 node-click 事件拿到每次操作以后的勾选数组,但是这里有个问题就是,我们此时只能拿到符合当前条件的节点数据,假如在此之前用户勾选过条件以外的人员,我们就拿不到了。

此时我又想,那我们把每次条件搜索的数据暂存,搜索状态一变化,我们就将这次条件搜索时勾选的所有人员插入缓存不就好了? 又有新问题来了,那假如我上次勾选了一班的王一一同学,存入了缓存,这次我条件搜索所有的姓王的同学,发现我勾错了呀,于是我取消了对王一一同学的勾选,但是因为我每次都是最后插入新勾选的新员,却没有对取消勾选的人员进行删除操作。所以我们要增加一个操作,判断每次勾选状态改变时,比较此次回传数组的长度与上次缓存数组的长度,若低于上次的长度,贼每次取消勾选时将人员从缓存里移除。

其实到这里我觉得大体思路已经成型了,所以第一次实现这个功能的时候我就用了该思路。但是写完以后看我自己的代码总觉得特别乱,逻辑混乱,不清晰,而且很容易产生各种不好维护的bug。 所以在第二次实现类似功能的时候,我选择了另外一种思路,放弃了 check-change 的总体返回,打算从【勾选】这个操作本身入手,进行缓存。

第二次的逻辑思路(可以直接看这里)

如果从勾选这个操作本身入手,我们就不需要去考虑 check-change 时返回的总体勾选数组了,我们只需要去考虑我们本次操作是 勾选 还是 取消勾选 就可以了,勾选就将这个人员加入缓存,取消勾选就将这个人员从缓存中删除。

这样的思路就简单清晰又好维护。

然后由于我要传给后端的 待授权人员id数组 是要由 所对应的班级id作为key值 的对象,最后传一个总的数组对象。如:

    [
      {'43574521': [001,002,...]}, // key值为班级id
      {'56744522': [034,056,...]}, // value值为该班级待授权的人员id
      ...
    ]

所以在判断该操作是勾选还是取消勾选之后,我还要判断是否为班级

  1. 班级勾选,该班级是否在缓存中存在,若不存在,则要将整个班级插入缓存,若存在,则将原班级部分人员的缓存直接替换为此次的整个班级缓存;
  2. 班级取消勾选,我要删除整个班级的人员缓存;

若不是班级,而是人员的单独操作:

  1. 人员勾选:该人员所在班级是否存在缓存,若存在,将人员插入本班级缓存,若不存在,先在缓存里创建该班级对象,然后将人员插入该班级对象里;
  2. 人员取消勾选:循环检索到对应的班级对象,将该班级对象中的人员对象删除;

这时,我基本的操作逻辑就出来了,我个人感觉基本涵盖了所有可能性,且逻辑清晰有条理,简单易懂,容易维护。

虽然一开始我放弃了 check-change 的总体返回,看似是放弃了快捷简单的方法,但其实到最后发现,使用单独操作的状态响应来进行数据缓存反而更加方便简洁。

代码

1.html代码和一些属性解释

el-tree代码:

        <el-tree :props="studentsProps"
                 :data="studentsList"
                 node-key="id"
                 ref="studentsTree"
                 :default-expanded-keys="defaultKeys"
                 :default-expand-all="false"
                 show-checkbox
                 @check-change="studentsCheckChange"
                 empty-text="暂无用户可授权">
        </el-tree>

数据:

    data () {
      return {
      ruleForm: {
        hasCheckedClass: [], //已勾选班级集合
      },
      // 指派弹窗中选择学生
      studentSearch: {
        className: '',
        studentName: ''
      },
      // 学生数prop
      studentsProps: {
        children: 'students',
        label: 'userName',
      },
      studentsList: [], // 学生list
      lastCheckedClass: [], // 用于关闭弹窗后又打开进行勾选人员修改操作时的渲染
    },

2.单个勾选操作的代码

      // 选中节点变化时
      studentsCheckChange (data, node) {
        // data是选中节点对象的的data
        // node其实是Nodestatus,是Boolean对象
        // true表示为勾选操作,false表示取消勾选的操作
        if (data.isClass) {
        // 判断是否是班级
        // isClass是我在拿到学生数组时,对数据进行递归处理后自己设定的,递归遍历该层数据是否有classId,若有则设为true,若没有,则设为false
        // 虽然,如果后端能帮我处理好就更好啦
          if (node) {
            // 是否是勾选,是的话将所有学生UUID放进去
            let _uuidList = data.students.map((item) => {
              return item.id
            })
            this.ruleForm.hasCheckedClass.push({
              [data.classId]: _uuidList
            })
          } else {
            // 是取消的话将该班级IDs对象删除
            // 获取该班级在已选择中的位置并删除
            let _index
            this.ruleForm.hasCheckedClass.forEach((item, index) => {
            // 看不懂Object.keys(item)[0]这个操作的可以去看看上面要传给后端的数组的示例
            // 然后自己打印出来看一下,这个挺绕的,后端要的数据比较轴
              if (Object.keys(item)[0] == data.classId) {
                _index = index
              }
            })
            this.ruleForm.hasCheckedClass.splice(_index, 1)
          }
        } else {
          // 如果不是班级,先进行是勾选还是取消的判断
          if (node) {
            // 如果是非班级节点的勾选,判断已进行勾选过的班级里是否已存在班级ID
            let _isChecked
            let _index
            this.ruleForm.hasCheckedClass.forEach((item, index) => {
              if (Object.keys(item)[0] == data.classId) {
                _isChecked = true
                _index = index
              } else {
                _isChecked = false
              }
            })
            if (_isChecked) {
              // 如果存在,根据班级ID进行添加
              this.ruleForm.hasCheckedClass[_index][data.classId].push(data.id)
            } else {
              // 如果不存在,添加该班级对象
              this.ruleForm.hasCheckedClass.push({
                [data.classId]: [data.id]
              })
            }
          } else {
            // 如果是非班级节点的取消
            // 获取该节点在已选择中的班级位置并删除
            let _index
            let _classId = data.classId
            this.ruleForm.hasCheckedClass.forEach((item, index) => {
              if (Object.keys(item)[0] == data.classId) {
                _index = index
              }
            })
            if (_index || _index == 0) {
              let _userIDList = this.ruleForm.hasCheckedClass[_index][_classId]
              let _userIndex
              _userIDList.forEach((item, index) => {
                if (item === data.id) {
                  _userIndex = index
                }
              })
              _userIDList.splice(_userIndex, 1)
              this.ruleForm.hasCheckedClass[_index][_classId] = _userIDList
            }
          }
        }
      },

第二次的新思路基本上就是这样了,我个人感觉还是比较容易看懂的。这个逻辑比我第一次的方便简洁太多了,我现在倒回去看我第一次的代码,都有点看不太明白...

就是做一个存档和分享。