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>View Code