网站/小程序/APP个性化定制开发,二开,改版等服务,加扣:8582-36016

    前段时间公司突然要写小程序,项目中有一个树形控件。我找了很久的插件和框架。没有发现小程序能用的。只能硬着头皮自己写。

    老规矩,先贴图

    • 为什么要特意强调此图标呢?

    • 因为该图标为中间状态。此处是我后期要优化的地方,在下面的代码中还未实现。我有了初步的思考。
      我的代码中设定(以网络部为例)

    • 0 该部门成员全部未选中

    • 1 该部门成员全部选中

    • -1 该部门成员至少选中一人切少于部门总人员数

    好了,下面我们来实现树形结构的全选和零选中状态

    • 首先是树形组件的代码
      components文件夹下创建tree-select文件夹,选择新建component。命名为index

    index.wxml文件

                
                
                  
                    


    树形组件主要用的就是递归。所以上面组件中我在组件中又调用组件自身。通过depth来判断层级关系,来实现数据的缩进。treeList为页面中传进来的人员数据,treeArrary为我清洗数据之后传过来的初始的人员选中状态的数组

    index.json*

    {
       
      "component": true,
      "usingComponenuts": {
       
        "tree-select": "/components/tree-select/index"
      }
    }


    此处要在usingComponenuts中引入组件自身

    index.js

      /**
       * 组件的属性列表
       */
      properties: {
        treeList: {
          type: Array,
          value: []
        },
        depth: {
          type: Number,
          value: 0
        },
        treeArray: {
          type: Array,
          value: []
        }
      },


      /**
       * 组件的初始数据
       */
      data: {
        realTreeMap: new Map(),
      },


    因为小程序页面中不能直接传递map对象给组件,所以我在组件中定义了map对象。他的主要作用是用来修正人员的真实选中状态。

     ready(){
        const {
          treeList,
          treeArray,
        } = this.properties;
        this.setData({
          treeList,
          treeArray,
        })
      },


    此处,获取页面中传递过来的数据

        // 获取当前真实状态键值Map结构
        initRealTreeMap() {
          // 获取真实状态键值列表
          const {
       treeArray} = this.data
          // 生成真实状态键值Map结构
          var mRealTreeMap = new Map()
          treeArray.forEach(item => {
            mRealTreeMap.set(item.key, item.value);
          })
          // 给组件内部变量赋值真实状态Map结构
          this.setData({
            realTreeMap: mRealTreeMap
          })
        },


    在这里插入图片描述

    这里有一个要注意的就是,你点击某一项数据时,只能获取到与他同级的所有数据。比如:点击网络部,你拿到的数据就是 杨xx,马xx,营销部,网络部这四项数据

        handleClick(e){
       
       
          // 获取当前真实状态键值Map结构
          this.initRealTreeMap()
    
          // 获取当前的树形结构列表,真实状态键值列表,真实状态键值Map结构
          const {
       
        treeList, treeArray, realTreeMap } = this.data
    
          // 获取点击项id及其前一个状态
          const t = this.selectAllComponents('#treeItem');
          const {
       
        id, isCheck }  = e.currentTarget.dataset
    
          // 根据点击项id获取点击项及子节点id集合
          const idSet = new Set()
          this.getData(treeList, id, idSet)
    
          // 修改TreeList点击项及其子节点选中状态,但是,平级别的其他项及子项还是原始数据
          // this.setStatus(treeList, idSet, !isCheck)
          this.setStatus(treeList, idSet, !isCheck)
    
    
          // 修正TreeList除点击项及其子节点之外的其他平级及子节点项状态为真实状态
          this.repairTreeList(id, treeList)
          treeArray.forEach(item => {
       
       
            item.value = realTreeMap.get(item.key)
          })
    
          // 赋值给组件内部三大核心变量,树形结构列表,真实状态键值列表,真实状态键值Map结构
          this.setData({
       
       
            treeList,
            treeArray, 
            realTreeMap
          })
          // 触发向上通信传递事件,向父组件传递变量,主要为TreeList和TreeArray
          this.triggerEvent('handleClick', {
       
        id, isCheck, treeList, treeArray});
        },


    因为treeList 所传递的数据为所选中的部门的平级数据,所以每次点击后之前操作的数据就会被重置为初始状态。所以用 repairTreeList来修正数据的真正状态

        // 【递归函数】由于TreeList递归传递渲染,导致平级非点击项数据为原始状态,此方法用于修正TreeList数据状态为真实状态,真实状态保存在treeArray及RealTreeMap中
        repairTreeList(id, treeList) {
       
       
          treeList.forEach(item => {
       
       
            item.isCheck = this.data.realTreeMap.get(item.deptId)
            if(this.hasValidChildren(item)) {
       
       
              this.repairTreeList(id, item.children)
            }
          })
        },


        // 父组件向子组件传值
        treeClick(e){
       
       
          // 接收子组件向上传递的变量
          const {
       
        id, isCheck, treeList, treeArray } = e.detail
          // 设置真实状态键值列表
          this.setData({
       
       
            treeArray
          })
          // 子组件内嵌套组件,循环向上触发通信传递事件,向父组件传递变量,主要为TreeArray
          this.triggerEvent('handleClick', {
       
        id, isCheck, treeList, treeArray });
        },


    setStatus方法 将getData()方法返回的ids集合的id设为选中状态

        // 【递归函数】切换选中状态
        setStatus(dataTree, idSet, isCheck) {
       
       
          for (let i = 0; i < dataTree.length; i += 1) {
       
       
            // 遍历节点列表,若切换集合包含该节点,则切换选中状态
            if(idSet.has(dataTree[i].deptId)) {
       
       
              // 修正选中项状态
              this.resetCheckStatus(dataTree[i], isCheck)
              // 若当前节点的子节点列表不为空,则遍历子节点
              if (this.hasValidChildren(dataTree[i])) {
       
       
                let children = dataTree[i].children
                children.forEach( item => {
       
       
                  // 若切换集合包含该节点,则切换子节点选中状态
                  if(idSet.has(item.deptId)) {
       
       
                    // 修正子节点选中状态
                    this.resetCheckStatus(item, isCheck)
                  }
                  // 若子节点的子节点列表不为空,则递归调用切换选中状态
                  if(this.hasValidChildren(item)) {
       
       
                    this.setStatus(children, idSet, isCheck)
                  }
                })
              }
            }
          }
        },


        // 修正选中项状态
        resetCheckStatus(item, isCheck) {
       
       
          // 修改TreeList里的数据,用于渲染
          item.isCheck = isCheck
          // 修改RealTreeMap里的数据,保存真实状态
          this.data.realTreeMap.set(item.deptId, isCheck)
          // 优化后放到修正RepairTreeList完成之后,防止重复遍历造成性能损耗
           修改treeArray里的数据,保存真实状态,用于组件间传值
          // treeArray.forEach(i => {
       
       
          //   i.value = this.data.realTreeMap.get(i.key)
          // })
        },


    getData方法实现的逻辑是当部门被选择时,他下面的部员都被选中,部门被取消选中时,部员也取消选中,并返回选中的人员的id集合

        // 【递归函数】获取点击项及其子节点项id集合,即切换集合
        getData(dataTree, id, idSet) {
       
       
          // 由于dataTree为id项的同级别列表,因此遍历同级别列表,匹配其中id项及其子节点
          for (let i = 0; i < dataTree.length; i += 1) {
       
       
            if (dataTree[i].deptId === id) {
       
       
                // id匹配,加入切换集合
                idSet.add(id)
                if (this.hasValidChildren(dataTree[i])) {
       
       
                  // 存在子节点列表
                  let children = dataTree[i].children
                  // 遍历其子节点列表,将其子节点加入切换集合
                  children.forEach(item=> {
       
       
                    idSet.add(item.deptId)
                    // 子节点存在子节点列表,则递归调用函数,获取其子节点的子节点列表及其包含项
                    if(this.hasValidChildren(item)) {
       
       
                      // 注意,此处传递item及其同级别节点列表,而不是传递其子节点列表
                      this.getData(children, item.deptId, idSet);
                    }
                  })
                }
            }
          }
          return idSet
        },


    此方法用来判断数据的children不为空

        // 判断对象是否存在有效的子节点列表
        hasValidChildren(item) {
       
       
          return item.children != undefined && item.children.length > 0
        },


    • 下面为引入组件的页面中的代码

    • wxml页面



    • js页面

     // 将树形列表结构转化为一级列表结构,传递进入组件中,便于遍历、比较和赋值
      getTreeKeyValueArray(data, mTreeArray) {
    
        data.forEach(item => {
          mTreeArray.unshift({
            'key': item.deptId,
            'value': item.isCheck
          })
          // 若存在子节点列表,则递归处理数据
          if(this.hasValidChildren(item)) {
            this.getTreeKeyValueArray(item.children, mTreeArray)
          }
        })
      },


      treeClick(e){
    
        // 接收子组件向上传递的变量
        const {
       
        id, isCheck, treeList, treeArray } = e.detail
        // 更新设置树形结构列表及真实状态键值列表
        this.setData({
    
          treeList, 
          treeArray
        })
        this.getCheckedIds(treeArray)
      },


    到此,这个组件就全部完成了。


    评论 0

    暂无评论
    0
    0
    0
    立即
    投稿
    发表
    评论
    返回
    顶部