在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) { // 只点击ctrl15 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) { // 只点击shift31 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.expanded12 ) { // 通过检查有没有子节点,并且查看是否展开,从而确定是否递归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 slot-scope="{ node, data }"> 11 <span :>{{ 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.name148 });149 if (150 item.hasOwnProperty("childNodes") &&151 item.childNodes.length &&152 item.expanded153 ) {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>