1, the preface


Recently, I was working on a new module, in which there was a three-layer tree structure. The product manager put forward a very strange requirement, so I could only control the interaction of the tree by myself. After writing, I felt that I had a different understanding of the usage of this component, so I wrote it down.

2, the demand for


  • If the upper node is selected, all the lower nodes are also selected
  • If child nodes are selected one by one until all child nodes are selected, the parent node cannot be selected and can only be selected
  • The selected nodes cannot be expanded. If the selected nodes are expanded, they are automatically shrunk back

Encountering problems:

Fault 1: The data on the back end is unfriendly and there is no unique key value (duplicate key). As a result, the Tree component has no unique key

Problem 2: The backend data is not friendly, the first layer field is not the same as the third layer field (the first layer field is dept_id, the subset field is children, the second layer subset field is porjs, the third layer field is porj_id)

Problem 3: You cannot use check-strictly, which is the parent/child association of the Tree component. You can only manually control the checkbox status

Problem 4: Data submitted to the back end, if tier 1 and tier 2 nodes are selected, their substructure does not need to be passed; if not, their substructure needs to be passed

As shown in figure:But fortunately, this tree structure only has three levels, so there are ways to do it. (Difficult if unknown)

3. Solutions


Problem 1: There is no unique key value

This is easy to do. After the interface requests the data, it makes a deep copy, iterates through it, manually adds characters to the ids to make them unique, and finally commits with the previously added characters removed

// Add one, two, three to all ids according to the hierarchy
handlePushLabel(data) {
  try {
    data.forEach(item1= > {
      item1.dept_id += 'one'
      if (item1.children && item1.children.length > 0) {
        item1.children.forEach(item2= > {
          item2.dept_id += '. '
          item2.parent_id += 'one'
          if (item2.children.length > 0) {
            item2.children.forEach(item3= > {
              item3.dept_id += '叁'
              item3.parent_id += '. '})}})return data
  } catch (error) {
    console.warn(error)
  }
}
// Restore the data key to the original key
treeList.forEach(item1= > {
  item1.dept_id = item1.dept_id.replace('one'.' ')
  if (item1.children.length > 0) {
    item1.children.forEach(item2= > {
      item2.dept_id = item2.dept_id.replace('. '.' ')
      item2.parent_id = item2.parent_id.replace('one'.' ')
      if (item2.children.length > 0) {
        item2.children.forEach(item3= > {
          item3.dept_id = item3.dept_id.replace('叁'.' ')
          item3.parent_id = item3.parent_id.replace('. '.' ')})}})Copy the code

Problem 2: Layer 1 and Layer 2 fields are inconsistent with layer 3 fields

The best way to do this is to make the backend the same, but if you have a non-communicating backend like a blogger, the front end will have to convert the fields itself, using forEach traversal and then replacing the object keys with map.

// Rename the projs field and proj_id and proj_name of the tree data
handleChangeKey(data) {
  try {
    const tree = data
    tree.forEach(item= > {
      if (item.children) {
        const arr = item.children
        // Convert projs field to children
        item.children = arr.map(item1= > {
          if (item1.projs.length > 0) {
            const obj = item1.projs
            const parent_id = item1.dept_id
            // Convert proj_id to dept_id convert proj_name to dept_name
            // Add depth and parent node ID
            item1.projs = obj.map(item2= > {
              return {
                dept_id: item2.proj_id,
                dept_name: item2.proj_name,
                depth: 3,
                parent_id
              }
            })
          }
          return {
            dept_id: item1.dept_id,
            dept_name: item1.dept_name,
            depth: item1.depth,
            parent_id: item1.parent_id,
            children: item1.projs
          }
        })
      }
    })
    return this.handlePushLabel(tree)
  } catch (error) {
    console.warn(error)
  }
}
Copy the code

Problem 3: Check-strictly cannot be used

This is a bit complicated. You cannot use the parent and child association of the check box provided by Tree (see requirement 2 for the reason). You can only write the check logic of the first, second and third tier nodes by hand. In this case, the second – and third-level nodes need to have a parent_id field, which is the id of their parent, and a depth field, which represents their depths 1, 2, and 3.

<el-tree
  @check-change="handleTreeClick"
  :data="treeList"
  show-checkbox
  :default-expand-all="false"
  :check-strictly="true"
  @node-expand="handleTreeOpen"
  node-key="dept_id"
  ref="tree"
  highlight-current
  :props="defaultProps"
/>
Copy the code

Add the ref attribute to the Tree component, set check-strictly to true, use @check-change to listen to the check of nodes, use @node-expand to listen to the expansion and collapse of nodes, and set node-key to the ID of each node.

Get the first parameter data, which contains the node’s data, and then get the depth to determine the node’s level. Also, get the parent_id to find its parent node. $refs.tree.getNode(id); $refs.tree.getNode(id); If checked of Node is set to true, the Node becomes checked. Setting indeTerminate to true makes it a selected state, and setting expanded to true makes it an expanded state. Check can also be set to this.$refs.tree.setchecked (ID, true).

Problem 4: Data submitted to the back end

This is a hole, need to first change the previously changed key back, as well as the child key name back, and then according to check or just check to join the data. Here we use getCheckedNodes to get an array of currently selected nodes, and getHalfCheckedNodes to get an array of half-selected nodes.

4. Complete code


export default {
  // Rename the projs field and proj_id and proj_name of the tree data
  handleChangeKey(data) {
    try {
      const tree = data
      tree.forEach(item= > {
        if (item.children) {
          const arr = item.children
          // Convert projs field to children
          item.children = arr.map(item1= > {
            if (item1.projs.length > 0) {
              const obj = item1.projs
              const parent_id = item1.dept_id
              // Convert proj_id to dept_id convert proj_name to dept_name
              // Add depth and parent node ID
              item1.projs = obj.map(item2= > {
                return {
                  dept_id: item2.proj_id,
                  dept_name: item2.proj_name,
                  depth: 3,
                  parent_id
                }
              })
            }
            return {
              dept_id: item1.dept_id,
              dept_name: item1.dept_name,
              depth: item1.depth,
              parent_id: item1.parent_id,
              children: item1.projs
            }
          })
        }
      })
      return this.handlePushLabel(tree)
    } catch (error) {
      console.warn(error)
    }
  },
  // Add one, two, three to all ids according to the hierarchy
  handlePushLabel(data) {
    try {
      data.forEach(item1= > {
        item1.dept_id += 'one'
        if (item1.children && item1.children.length > 0) {
          item1.children.forEach(item2= > {
            item2.dept_id += '. '
            item2.parent_id += 'one'
            if (item2.children.length > 0) {
              item2.children.forEach(item3= > {
                item3.dept_id += '叁'
                item3.parent_id += '. '})}})return data
    } catch (error) {
      console.warn(error)
    }
  },
  /** * when the selected state of the tree changes *@param {Object} Data Indicates the data * of the node@param {Object} On whether the node itself is selected *@param {Object} Is there a selected node */ in the child node's subtree
  handleTreeClick(data, on, child) {
    try {
      this.form.tree = data
      if (data.depth === 1) {
        this.handleOneNode(on, data)
      } else if (data.depth === 2) {
        this.handleTwoNode(on, data)
      } else if (data.depth === 3) {
        this.handleThreeNode(on, data)
      }
    } catch (error) {
      console.warn(error)
    }
  },
  /** * Level 1 node processing *@param {Boolean} On Is selected *@param {Object} Data Data of the current node */
  handleOneNode(on, data) {
    try {
      const tree = this.$refs.tree
      // If the current node is not selected and is in half-selected state
      const node = tree.getNode(data.dept_id)
      if(node.indeterminate && ! node.checked)return
      // Cannot expand if the current node is selected
      if (node.checked && node.expanded) node.expanded = false
      // check all the children
      let arr = []
      if (data.children.length > 0) {
        data.children.forEach(item= > {
          // Filter out all the lower keys
          arr.push(item.dept_id)
          if (item.children.length > 0) {
            item.children.forEach(child= > {
              // Select all lower keys
              arr.push(child.dept_id)
            })
          }
        })
      }
      // Select or cancel
      if (on) {
        arr.forEach(dept= > {
          tree.setChecked(dept, true)})}else {
        arr.forEach(dept= > {
          tree.setChecked(dept, false)}}}catch (error) {
      console.warn(error)
    }
  },
  /** * Secondary node processing *@param {Boolean} On Is selected *@param {Object} Data Data of the current node */
  handleTwoNode(on, data) {
    try {
      const tree = this.$refs.tree
      const node = tree.getNode(data.dept_id)
      // If the current is half-selected
      if(node.indeterminate && ! node.checked)return
      // Cannot expand if the current node is selected
      if (node.checked && node.expanded) node.expanded = false
      // Superior node
      const parentNode = tree.getNode(data.parent_id)
      // check all the children
      let arr = []
      if (data.children.length > 0) {
        data.children.forEach(item= > {
          // Filter out all the lower keys
          arr.push(item.dept_id)
        })
      }
      // Select or cancel
      if (on) {
        arr.forEach(dept= > {
          tree.setChecked(dept, true)})// If the parent node is not selected, leave the parent node half-selected
        if(! parentNode.checked) { parentNode.indeterminate =true}}else {
        // Uncheck all sub-boxes
        arr.forEach(dept= > {
          tree.setChecked(dept, false)})// If the parent node is selected, leave the parent node half-selected
        if (parentNode.checked) {
          parentNode.indeterminate = true
          // If the upper level is half-selected, the loop checks whether the lower level is still checked to determine whether the upper level needs to remove half-selected
        } else if (parentNode.indeterminate) {
          const parentData = parentNode.data || []
          let bool = true
          const children = parentData.children
          const childArr = []
          // Filter out keys for all sibling nodes
          if (children && children.length > 0) {
            children.forEach(childItem= > {
              childArr.push(childItem.dept_id)
            })
          }
          // loop judgment
          if (childArr.length > 0) {
            for (let i of childArr) {
              let thisNode = tree.getNode(i)
              // If one of them is checked or half-selected
              if (thisNode.checked || thisNode.indeterminate) {
                bool = false}}}if (bool) {
            parentNode.indeterminate = false}}}}catch (error) {
      console.warn(error)
    }
  },
  /** * Tertiary node processing *@param {Boolean} On Is selected *@param {Object} Data Data of the current node */
  handleThreeNode(on, data) {
    try {
      // 1, if the upper node is not selected, change the upper node and upper node to half-selected
      // 2, if the upper node is selected, the upper node and the upper node are half-selected
      const tree = this.$refs.tree
      // Superior node
      console.log(data)
      const parentNode = tree.getNode(data.parent_id)
      const forefathersKey = parentNode.data.parent_id
      // Ancestor node
      console.log(parentNode)
      console.log(forefathersKey)
      const forefathersNode = tree.getNode(forefathersKey)
      console.log(forefathersNode)
      // If the current node is selected
      if (on) {
        // If the parent node is not selected, make it half-selected
        if(! parentNode.checked) { parentNode.indeterminate =true
        }
        // If the ancestor node is not selected, make it half-selected
        if(! forefathersNode.checked) { forefathersNode.indeterminate =true
        }
        // If the current node is deselected
      } else {
        const parentArr = []
        const forefathersArr = []
        const parentData = parentNode.data
        const forefathersData = forefathersNode.data
        let parentBool = true
        let forefathersBool = true
        // Select all sibling keys. If there are checked keys, the superior does not need to remove the check box
        if (parentData.children.length > 0) {
          parentData.children.forEach(parent= > {
            parentArr.push(parent.dept_id)
          })
          for (let i of parentArr) {
            let thisNode = tree.getNode(i)
            if (thisNode.checked) {
              parentBool = false}}}// If the value is tree, no tier 3 node is selected
        if (parentBool) {
          parentNode.checked = false
          parentNode.indeterminate = false
        } else {
          parentNode.indeterminate = true
        }
        // Select all sibling keys of the superior. If the sibling keys are checked, the superior does not need to be unchecked
        if (forefathersData.children.length > 0) {
          forefathersData.children.forEach(parent= > {
            forefathersArr.push(parent.dept_id)
          })
          for (let i of forefathersArr) {
            let thisNode = tree.getNode(i)
            if (thisNode.checked || thisNode.indeterminate) {
              forefathersBool = false}}}if (forefathersBool) {
          forefathersNode.indeterminate = false}}}catch (error) {
      console.warn(error)
    }
  },
  /** ** when the tree is expanded@param {Object} Data Indicates the data * of the node@param {Object} Node Node * of a node@param {Object} The ref node component */
  handleTreeOpen(data, node) {
    // Do not expand if the node is selected
    if (node.checked) {
      Tip.warn('The current tier has been selected and cannot be expanded! ')
      node.expanded = false}},// Splice the required tree data
  handleJoinTree() {
    try {
      const tree = this.$refs.tree
      const treeList = _.cloneDeep(this.treeList)
      // The selected node
      const onItem = tree.getCheckedNodes()
      // A half-selected node
      const halfItem = tree.getHalfCheckedNodes()
      const oneArr = []
      const twoArr = []
      const threeArr = []
      const oneArr_ = []
      const twoArr_ = []
      const threeArr_ = []
      // Node layer
      if (onItem.length > 0) {
        onItem.forEach(item= > {
          switch (item.depth) {
            case 1:
              oneArr.push(item.dept_id)
              break
            case 2:
              twoArr.push(item.dept_id)
              break
            case 3:
              threeArr.push(item.dept_id)
              break}})}if (halfItem.length > 0) {
        halfItem.forEach(item= > {
          switch (item.depth) {
            case 1:
              oneArr_.push(item.dept_id)
              break
            case 2:
              twoArr_.push(item.dept_id)
              break
            case 3:
              threeArr_.push(item.dept_id)
              break}})}const oneList = this.handlejoinOne(treeList, oneArr, oneArr_)
      const twoList = this.handlejoinTwo(treeList, twoArr, twoArr_)
      const threeList = this.handlejoinThree(treeList, threeArr, threeArr_)
      // Put the second layer into the first layer
      oneList.forEach(item= > {
        twoList.forEach(item2= > {
          if (item2.parent_id === item.dept_id) {
            if(! item.isOn) { item.children.push(item2) } } }) })// Put the third layer into the second layer
      oneList.forEach(child1= > {
        if (child1.children.length > 0) {
          child1.children.forEach(child2= > {
            threeList.forEach(child3= > {
              if (child3.parent_id === child2.dept_id) {
                if(! child2.isOn) { child2.children.push(child3) } } }) }) } })return oneList
    } catch (error) {
      console.warn(error)
    }
  },
  // return to the first layer
  handlejoinOne(treeList, oneArr, oneArr_) {
    try {
      // Find the first layer node
      const oneList = []
      treeList.forEach(item= > {
        for (let i of oneArr) {
          if (item.dept_id === i) {
            oneList.push({
              dept_id: item.dept_id,
              children: [].isOn: true.name: item.dept_name
            })
          }
        }
        for (let i of oneArr_) {
          if (item.dept_id === i) {
            oneList.push({
              dept_id: item.dept_id,
              children: [].isOn: false.name: item.dept_name
            })
          }
        }
      })
      return oneList
    } catch (error) {
      console.warn(error)
    }
  },
  // return to layer 2
  handlejoinTwo(treeList, twoArr, twoArr_) {
    try {
      const twoList = []
      treeList.forEach(item= > {
        if (item.children.length > 0) {
          item.children.forEach(item2= > {
            for (let i of twoArr) {
              if (item2.dept_id === i) {
                twoList.push({
                  dept_id: item2.dept_id,
                  children: [].isOn: true.parent_id: item2.parent_id,
                  name: item2.dept_name
                })
              }
            }
            for (let i of twoArr_) {
              if (item2.dept_id === i) {
                twoList.push({
                  dept_id: item2.dept_id,
                  children: [].isOn: false.parent_id: item2.parent_id,
                  name: item2.dept_name
                })
              }
            }
          })
        }
      })
      return twoList
    } catch (error) {
      console.warn(error)
    }
  },
  // return to layer 3
  handlejoinThree(treeList, threeArr, threeArr_) {
    try {
      const threeList = []
      treeList.forEach(item= > {
        if (item.children.length > 0) {
          item.children.forEach(item2= > {
            if (item2.children.length > 0) {
              item2.children.forEach(item3= > {
                for (let i of threeArr) {
                  if (item3.dept_id === i) {
                    threeList.push({
                      dept_id: item3.dept_id,
                      isOn: true.parent_id: item3.parent_id,
                      name: item3.dept_name
                    })
                  }
                }
                for (let i of threeArr_) {
                  if (item3.dept_id === i) {
                    threeList.push({
                      dept_id: item3.dept_id,
                      isOn: false.parent_id: item3.parent_id,
                      name: item3.dept_name
                    })
                  }
                }
              })
            }
          })
        }
      })
      return threeList
    } catch (error) {
      console.warn(error)
    }
  },
  // Restore the data key to the original key
  handleRestoreKey() {
    try {
      const treeList = this.handleJoinTree()
      // get rid of id
      treeList.forEach(item1= > {
        item1.dept_id = item1.dept_id.replace('one'.' ')
        if (item1.children.length > 0) {
          item1.children.forEach(item2= > {
            item2.dept_id = item2.dept_id.replace('. '.' ')
            item2.parent_id = item2.parent_id.replace('one'.' ')
            if (item2.children.length > 0) {
              item2.children.forEach(item3= > {
                item3.dept_id = item3.dept_id.replace('叁'.' ')
                item3.parent_id = item3.parent_id.replace('. '.' ')})}})// Convert dept_id to proj_id convert dept_name to proj_name and children to projs
      treeList.forEach(child1= > {
        if (child1.children.length > 0) {
          const childObj = child1.children.map(item= > {
            let returnObj = {}
            if (item.children.length > 0) {
              const obj = item.children
              obj.children = obj.map(child2= > {
                return {
                  proj_id: child2.dept_id,
                  proj_name: child2.name
                }
              })
              returnObj = {
                dept_id: item.dept_id,
                dept_name: item.name,
                projs: obj.children
              }
            } else {
              returnObj = {
                projs: [].dept_id: item.dept_id,
                isOn: true.name: item.name
              }
            }
            return returnObj
          })
          child1.children = childObj
        }
      })
      console.log(treeList)
      return treeList
    } catch (error) {
      console.warn(error)
    }
  },
  // Select details tree
  handleSetTree(list) {
    try {
      console.log(list)
      const one = []
      const two = []
      const three = []
      if (list.length > 0) {
        / / the first layer
        list.forEach(item= > {
          let child = item.children || ' '
          let obj = { id: item.dept_id + 'one'.isOn: true }
          if (child && child.length > 0) {
            obj.isOn = false
          }
          one.push(obj)
        })
        / / the second floor
        list.forEach(item1= > {
          let child1 = item1.children || ' '
          if (child1 && child1.length > 0) {
            child1.forEach(item2= > {
              let child2 = item2.projs || ' '
              let obj = { id: item2.dept_id + '. '.isOn: true }
              if (child2 && child2.length > 0) {
                obj.isOn = false
              }
              two.push(obj)
            })
          }
        })
        / / the second floor
        list.forEach(item1= > {
          let child1 = item1.children || ' '
          if (child1 && child1.length > 0) {
            child1.forEach(item2= > {
              let child2 = item2.projs || ' '
              if (child2 && child2.length > 0) {
                child2.forEach(item3= > {
                  let obj = { id: item3.proj_id + '叁'.isOn: true }
                  three.push(obj)
                })
              }
            })
          }
        })
        const tree = this.$refs.tree
        // Select the first layer
        if (one && one.length > 0) {
          one.forEach(item= > {
            let node = tree.getNode(item.id)
            if (item.isOn) {
              node.checked = true
              this.handleOneNode(true, node.data)
            } else {
              node.indeterminate = true}})}// Select layer 2
        if (two && two.length > 0) {
          two.forEach(item= > {
            let node = tree.getNode(item.id)
            if (item.isOn) {
              node.checked = true
              this.handleTwoNode(true, node.data)
            } else {
              node.indeterminate = true}})}// Select layer 3
        if (three && three.length > 0) {
          three.forEach(item= > {
            let node = tree.getNode(item.id)
            node.checked = true})}}}catch (error) {
      console.warn(error)
    }
  }
}
Copy the code

Get the transformed structure:

this.treeList = this.handleChangeKey(data)
Copy the code

Submit the transformed structure:

const treeList = this.handleRestoreKey()
Copy the code

5,


If you use the Tree component, and the product requirements are not good, you can take a look at the Tree method techniques commonly used;

  • $refs.tree.getNode(ID);

  • Return the half of the selected node array: enclosing $refs. Tree. GetHalfCheckedNodes ()

  • Returns the currently selected node composed of an array: enclosing $refs. Tree. GetCheckedNodes ()

  • $refs.tree.setchecked (id, true)


If you think it is helpful, I am @pengduo, welcome to like and follow the comments; END

PS: on this page press F12, input document. In the console querySelectorAll (‘ like – BTN ‘) [0]. Click (), a surprise

The articles

  • Use NVM to manage node.js version and change NPM Taobao image source
  • More detailed! Vue’s nine ways of communication
  • Wechat small program to achieve search keyword highlighting
  • Env files are used in vUE to store global environment variables and configure vUE startup and package commands
  • More detailed! Vuex hands-on tutorials

Personal home page

  • CSDN
  • GitHub
  • Jane’s book
  • Blog garden
  • The Denver nuggets