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