Element中Tree树结构组件中实现Ctrl和Shift多选
在Element中的树结构中, 实现多选功能,首先的是判断有没有按下键盘ctrl和shift按键。但是在Element中的tree组件的左键点击事件是没有提供$event鼠标属性判断的。所以就需要在函数中使用自身的$event来判断。请看树结构下面左键和右键点击的函数传参的截图。
所以,左键的点击函数,需要自行判断。如下代码示例
<el-tree class="filter-tree" :load="loadNode" lazy :props="defaultProps" :filter-node-method="filterNode" :render-content="renderContent" ref="treeRef" :expand-on-click-node="false" @node-contextmenu="rightClick" @node-click="leftClick" // 左键点击事件 :highlight-current="true" node-key="id" :check-on-click-node="true" :show-checkbox="false" check-strictly ></el-tree>
里面的左键函数,是这样的
1 methods: { 2 leftClick(data, node, dom) { 3 let event = window.event || arguments.callee.caller.arguments[0]; 4 var ctrlKeyDowned = event.ctrlKey; 5 var shiftKeyDowned = event.shiftKey; 6 // 走单击事件 7 8 var allTreeNode = this.$refs.treeRef.getNode(1); 9 this.clickTime = ""; 10 if (ctrlKeyDowned == false && shiftKeyDowned == false) { // 都没有点击 11 this.cancelSelectTree(); // 取消原来的选中 12 this.leftTreeSelectedArr.splice(0); 13 this.leftTreeSelectedArr.push(data); 14 } else if (ctrlKeyDowned == true && shiftKeyDowned == false) { // 只点击ctrl 15 this.$set(data, "Selected", true); 16 var isIN = this.leftTreeSelectedArr.every(item => { 17 return item.id != data.id; 18 }); 19 isIN && this.leftTreeSelectedArr.push(data); 20 if (!isIN) { 21 // 如果已经是选中的了,那么应该取消选择 22 data.Selected = false; 23 this.leftTreeSelectedArr.map((item, i) => { 24 if (item.id == data.id) { 25 this.leftTreeSelectedArr.splice(i, 1); 26 this.$refs.treeRef.setCurrentKey(); // 取消高亮,要不然区分不出来,是不是没有选中 27 } 28 }); 29 } 30 } else if (ctrlKeyDowned == false && shiftKeyDowned == true) { // 只点击shift 31 this.delayeringArr.splice(0); 32 this.delayering([allTreeNode]); // 把现在展开的数据都扁平化 33 this.$set(data, "Selected", true); 34 this.leftTreeSelectedArr.push(data); 35 this.shiftTree(); // shifit多选 36 } 37 } 38 }
通过,第三行中的内容,获取到鼠标的点击事件属性,然后从中获取到是都点击了键盘的Ctrl和Shift;
Ctrl多选就不用过多的介绍了,把点击树结构的内容, 通过去重判断,直接放在leftTreeSelectedArr中就可以了。这里就不做过多的介绍了。具体请看,14至30行代码。下面主要是讲解一下,shift多选。
Shfit多选,在平常的列表中是很好实现的。我们可以把所有的数据,放在一个一维的数组中,那么任意选择其中的两项的话,就能把数组分割成为三部分。其中的中间部分,也就是第二部分就是Shift多选的结果。请看下面的草图
但是对于树结构的话,就稍微的麻烦一点了,树结构的数据是这样的。
那么他的真实的数据格式应该是这样的。
1 treeData: [ 2 { 3 id: 1, 4 name: "1节点", 5 childrenId: [ 6 { 7 id: 2, 8 name: "2节点", 9 childrenId: [ 10 { 11 id: 5, 12 name: "5节点", 13 childrenId: [] 14 }, 15 { 16 id: 6, 17 name: "6节点", 18 childrenId: [] 19 } 20 ] 21 }, 22 { 23 id: 3, 24 name: "3节点", 25 childrenId: [ 26 { 27 id: 7, 28 name: "7节点", 29 childrenId: [] 30 } 31 ] 32 }, 33 { 34 id: 4, 35 name: "4节点", 36 childrenId: [ 37 { 38 id: 8, 39 name: "8节点", 40 childrenId: [] 41 }, 42 { 43 id: 9, 44 name: "9节点", 45 childrenId: [] 46 }, 47 { 48 id: 10, 49 name: "10节点", 50 childrenId: [ 51 { 52 id: 11, 53 name: "11节点", 54 childrenId: [] 55 }, 56 { 57 id: 12, 58 name: "12节点", 59 childrenId: [] 60 } 61 ] 62 } 63 ] 64 } 65 ] 66 } 67 ]
那么树结构在页面上渲染完成之后就是这样的:
那shift多选是怎么判断的呢,怎么知道这个层级是属于哪个呢,怎么知道这个层级下面的内容需不需选中呢,如果展开了,就是应该选中的,如果没有展开是不是就不需要选中呢。所以的这些问题,如果思考下来的话, 确实比较复杂,如果遍历的话,也是很难的。任意选中两个之后,都不知道应该是向上查找遍历,还是向下查找遍历。所以遍历的话,是不可用的,或者说是不太容易实现的。
回到问题的本质,在一维的数组,shif多选是很简单的。那么这个树形结构是不是也可以转换成一维的呢。按照这个思路,我们通过递归循环遍历,把这个数组转换成为一维的数组。请看下面的代码
1 delayering(allTreeNode, pid) { 2 allTreeNode.map(item => { 3 this.delayeringArr.push({ 4 id: item.data.id, 5 pid: pid ? pid : "null", 6 name: item.data.name 7 }); 8 if ( 9 item.hasOwnProperty("childNodes") && 10 item.childNodes.length && 11 item.expanded 12 ) { // 通过检查有没有子节点,并且查看是否展开,从而确定是否递归 13 this.delayering(item.childNodes, item.data.id); 14 } 15 }); 16 },
调用的时候,则需要把所有的节点的数据都传过去。
1 this.delayeringArr.splice(0); 2 this.delayering([allTreeNode]); // 把现在展开的数据都扁平化
调用delayering之后,就能把现在树结构中,已经展开的树结构,格式化成为一个一维的数组。请看下面的截图
当我们把树结构中的数据格式化成为一个一维的数组之后,我们就能判断了。那些是需要选中的。
1 shiftTree() { 2 console.log("this.leftTreeSelectedArr", this.leftTreeSelectedArr); 3 console.log("this.delayeringArr", this.delayeringArr); 4 // 把第一个和最后一个当成是shift选择的 5 var nodeLength = this.leftTreeSelectedArr.length; 6 var startNode = this.leftTreeSelectedArr[0]; 7 var startNodeId = startNode.id; 8 var endNode = this.leftTreeSelectedArr[nodeLength - 1]; 9 var endNodeId = endNode.id; 10 11 // var startIndex = this.delayeringArr.filter((item,i)=>{ 12 // return itemid == startNodeId; 13 // }) 14 // var endIndex = this.delayeringArr.filter((item,i)=>{ 15 // return itemid == endNodeId; 16 // }) 17 var startIndex, endIndex; 18 this.delayeringArr.map((item, i) => { 19 if (item.id == startNodeId) { 20 startIndex = i; 21 } 22 if (item.id == endNodeId) { 23 endIndex = i; 24 } 25 }); 26 if (startIndex > endIndex) { 27 var rongIdex = endIndex; 28 endIndex = startIndex; 29 startIndex = rongIdex; 30 } 31 console.log(startIndex, endIndex); 32 this.leftTreeSelectedArr.splice(0); 33 this.delayeringArr.map((item, i) => { 34 if (i >= startIndex && i <= endIndex) { 35 console.log("需要选中的name", item.name); 36 var node = this.$refs.treeRef.getNode(item.id); 37 this.$set(node.data, "Selected", true); 38 this.leftTreeSelectedArr.push(node.data); 39 } 40 }); 41 console.log("this.leftTreeSelectedArr: ", this.leftTreeSelectedArr); 42 }
这个函数的主要目的就是,通过循环,找到对应的数据在扁平化处理之后数组数据中的位置。然后同理,就能找到需要选中的数据,通过设置Selected为true,则可以知道需要选中的节点。
最后附上完成的代码, 包括其中的打印信息。(注意其中依赖Element的tree组件)
1 <template> 2 <div id="MyVue"> 3 <el-tree 4 ref="treeRef" 5 :data="treeData" 6 node-key="id" 7 :props="defaultProps" 8 @node-click="leftClick" 9 > 10 <span class="custom-tree-node" slot-scope="{ node, data }"> 11 <span :class="data.Selected?'sel':''">{{ node.label }}</span> 12 </span> 13 </el-tree> 14 <div>扁平化数据:{{delayeringArr}}</div> 15 </div> 16 </template> 17 <script> 18 export default { 19 name: "MyVue", 20 data() { 21 return { 22 defaultProps: { 23 children: "childrenId", 24 label: "name" 25 }, 26 treeData: [ 27 { 28 id: 1, 29 name: "1节点", 30 childrenId: [ 31 { 32 id: 2, 33 name: "2节点", 34 childrenId: [ 35 { 36 id: 5, 37 name: "5节点", 38 childrenId: [] 39 }, 40 { 41 id: 6, 42 name: "6节点", 43 childrenId: [] 44 } 45 ] 46 }, 47 { 48 id: 3, 49 name: "3节点", 50 childrenId: [ 51 { 52 id: 7, 53 name: "7节点", 54 childrenId: [] 55 } 56 ] 57 }, 58 { 59 id: 4, 60 name: "4节点", 61 childrenId: [ 62 { 63 id: 8, 64 name: "8节点", 65 childrenId: [] 66 }, 67 { 68 id: 9, 69 name: "9节点", 70 childrenId: [] 71 }, 72 { 73 id: 10, 74 name: "10节点", 75 childrenId: [ 76 { 77 id: 11, 78 name: "11节点", 79 childrenId: [] 80 }, 81 { 82 id: 12, 83 name: "12节点", 84 childrenId: [] 85 } 86 ] 87 } 88 ] 89 } 90 ] 91 } 92 ], 93 delayeringArr: [], // 扁平化之后的数据 94 leftTreeSelectedArr: [] // 选中的数据 95 }; 96 }, 97 props: {}, 98 mounted() {}, 99 components: {}, 100 computed: {}, 101 methods: { 102 leftClick(data, node, dom) { 103 let event = window.event || arguments.callee.caller.arguments[0]; 104 var ctrlKeyDowned = event.ctrlKey; 105 var shiftKeyDowned = event.shiftKey; 106 107 var allTreeNode = this.$refs.treeRef.getNode(1); 108 console.log("allTreeNode: ", allTreeNode); 109 if (ctrlKeyDowned == false && shiftKeyDowned == false) { 110 this.cancelSelectTree(); // 取消原来的选中 111 this.leftTreeSelectedArr.splice(0); 112 this.leftTreeSelectedArr.push(data); 113 } else if (ctrlKeyDowned == true && shiftKeyDowned == false) { 114 // this.leftTreeSelectedArr.splice(0); 115 // data.Selected = true; 116 this.$set(data, "Selected", true); 117 var isIN = this.leftTreeSelectedArr.every(item => { 118 return item.id != data.id; 119 }); 120 isIN && this.leftTreeSelectedArr.push(data); 121 console.log("isIN: ", isIN); 122 if (!isIN) { 123 // 如果已经是选中的了,那么应该取消选择 124 data.Selected = false; 125 this.leftTreeSelectedArr.map((item, i) => { 126 if (item.id == data.id) { 127 this.leftTreeSelectedArr.splice(i, 1); 128 this.$refs.treeRef.setCurrentKey(); // 取消高亮,要不然区分不出来,是不是没有选中 129 } 130 }); 131 } 132 console.log("this.leftTreeSelectedArr: ", this.leftTreeSelectedArr); 133 } else if (ctrlKeyDowned == false && shiftKeyDowned == true) { 134 this.delayeringArr.splice(0); 135 this.delayering([allTreeNode]); // 把现在展开的数据都扁平化 136 this.$set(data, "Selected", true); 137 this.leftTreeSelectedArr.push(data); 138 this.shiftTree(); // shifit多选 139 } 140 }, 141 // 把所有的数据,进行扁平化处理 142 delayering(allTreeNode, pid) { 143 allTreeNode.map(item => { 144 this.delayeringArr.push({ 145 id: item.data.id, 146 pid: pid ? pid : "null", 147 name: item.data.name 148 }); 149 if ( 150 item.hasOwnProperty("childNodes") && 151 item.childNodes.length && 152 item.expanded 153 ) { 154 // 通过检查有没有子节点,并且查看是否展开,从而确定是否递归 155 this.delayering(item.childNodes, item.data.id); 156 } 157 }); 158 }, 159 shiftTree() { 160 console.log("this.leftTreeSelectedArr", this.leftTreeSelectedArr); 161 console.log("this.delayeringArr", this.delayeringArr); 162 // 把第一个和最后一个当成是shift选择的 163 var nodeLength = this.leftTreeSelectedArr.length; 164 var startNode = this.leftTreeSelectedArr[0]; 165 var startNodeId = startNode.id; 166 var endNode = this.leftTreeSelectedArr[nodeLength - 1]; 167 var endNodeId = endNode.id; 168 169 // var startIndex = this.delayeringArr.filter((item,i)=>{ 170 // return itemid == startNodeId; 171 // }) 172 // var endIndex = this.delayeringArr.filter((item,i)=>{ 173 // return itemid == endNodeId; 174 // }) 175 var startIndex, endIndex; 176 this.delayeringArr.map((item, i) => { 177 if (item.id == startNodeId) { 178 startIndex = i; 179 } 180 if (item.id == endNodeId) { 181 endIndex = i; 182 } 183 }); 184 if (startIndex > endIndex) { 185 var rongIdex = endIndex; 186 endIndex = startIndex; 187 startIndex = rongIdex; 188 } 189 console.log(startIndex, endIndex); 190 this.leftTreeSelectedArr.splice(0); 191 this.delayeringArr.map((item, i) => { 192 if (i >= startIndex && i <= endIndex) { 193 console.log("需要选中的name", item.name); 194 var node = this.$refs.treeRef.getNode(item.id); 195 this.$set(node.data, "Selected", true); 196 this.leftTreeSelectedArr.push(node.data); 197 } 198 }); 199 console.log("this.leftTreeSelectedArr: ", this.leftTreeSelectedArr); 200 } 201 } 202 }; 203 </script> 204 <style lang="scss" scoped> 205 #MyVue { 206 width: 100%; 207 height: 100%; 208 user-select: none; 209 .sel{ 210 color: aqua; 211 } 212 } 213 </style>