vue遞歸實現樹形組件
本文實例為大傢分享瞭vue遞歸實現樹形組件的具體代碼,供大傢參考,具體內容如下
1. 先來看一下效果:
2. 代碼部分 (myTree.vue)
圖片可以自己引一下自己的圖片,或者使用iconfont的css引入。
<template> <div class="tree"> <ul class="ul"> <li v-for="(item,index) of treeMenu" :key="index"> <div class="jiantou" @click="changeStatus(index)"> <img src="../../assets/right.png" v-if="!scopesDefault[index]===true && item.children"> <img src="../../assets/down.png" v-if="scopesDefault[index]===true && item.children "> </div> <input type="checkbox" @click="checkBox(item)" v-model="item.check"> <span @click="changeStatus(index)">{{item.label}}</span> <div class="subtree"> <tree-menu :treeMenu='item.children' v-if="scopesDefault[index]" @selectnode = "selectnode"></tree-menu> </div> </li> </ul> </div> </template> <script> export default{ name:'treeMenu', props:{ treeMenu:{ type:Array, default:[] }, }, data(){ return{ scopesDefault: [], scopes: [], node:[], flatTreeMenu:[], check:'', } }, methods:{ //展開 scope() { this.treeMenu.forEach((item, index) => { this.scopesDefault[index] = false if ('children' in item) { this.scopes[index] = true //console.log(item, index) } else { this.scopes[index] = false } }) }, changeStatus(index) { if (this.scopesDefault[index] == true) { this.$set(this.scopesDefault, index, false) } else { this.$set(this.scopesDefault, index, this.scopes[index]) } }, //nodelist 深度優先遞歸 checkBox(node,nodelist=[]){ //console.log("start:",node,nodelist) if(node!==null){ nodelist.push(node); if(node.children){ let children=node.children; for(let i=0;i<children.length;i++){ this.checkBox(children[i],nodelist)//遞歸調用 children[i].check = nodelist[0].check==false?true:false;//選中父節點,子節點全選,取消,子節點取消 } } } this.node=node; this.check=node.check }, selectnode(node){ this.$emit("selectnode",node); } }, watch:{ node:{ handler(val){ this.selectnode(val); }, immediate: true }, check:{ handler(val){ this.selectnode(this.node); }, immediate: true } }, mounted(){ this.scope(); } } </script>
<style lang = "scss" scoped> .tree{ .ul{ margin: 5px 0 5px 0; >li{ .jiantou{ display: inline-block; width: 15px; >img{ position: relative; top: 2.0px; left: 4px; } } .subtree{ margin-left: 20px; margin-top: 8px; margin-bottom: 8px; } } } } input[type=checkbox]{ visibility: hidden; cursor: pointer; position: relative; width: 15px; height: 15px; font-size: 14px; border: 1px solid #dcdfe6; background-color: #fff !important; &::after{ position: absolute; top: 0; background-color: #fff; border: 1px solid #ddd; color: #000; width: 15px; height: 15px; display: inline-block; visibility: visible; padding-left: 0px; text-align: center; content: ' '; border-radius: 3px; transition: all linear .1s; } &:checked::after{ content: "\2713"; font-size: 12px; background-color: #409eff; border: 1px solid #409eff; transition: all linear .1s; color: #fff; font-weight: bold; } } .check{ &:checked::after{ content: "--" !important; } } </style>
講解:
1、調用組件:
我這用來一個global.js
來控制組件的使用(這個js附在文章末尾瞭),在component
文件夾中建立一個myTree
文件夾,裡面放同名vue文件(myTree.vue
),這樣無論在哪裡調用這個組件,都可以直接使用<my-tree></my-tree>
的方式去調用。
2、組件的方法:
scope():
會生成一個數組,裡面有根節點是否有子節點,如本代碼裡設定的數據,會有scopes=[true,true,true]
這樣的結果。changeStatus():
每點擊標題或者箭頭,如果當前下標的節點有沒有子節點,再將結果動態賦值給scopesDefault[index]
,將這個值放於dom上控制開關,遞歸組件。checkBox():
在組件內部實現瞭點擊全選、點擊取消全選的功能,遞歸調用當前方法,將子元素的狀態隨父元素一起變化。selectnode():
將當前點擊的node的節點內容上傳到父組件。
3、監聽:
同時監聽:不同節點的切換、同一個節點的是否選中的切換,監聽得到的結果都傳到父組件中。
4、組件遞歸:調用時與父組件相同
3. 使用組件(useMyTree.vue)
<template> <div class = "loginModuel"> <my-tree :treeMenu='tree' @selectnode="selectnode"></my-tree> </div> </template> <script> export default{ data(){ return{ msg:"這是登錄頁面", tree:[ { id:1, label:"1級目錄1", check:false, children:[ { id:"1-1", pid:1, label:"1.1目錄", check:false }, { id:"1-2", pid:1, label:"1.2目錄", check:false }, { id:"1-3", pid:1, label:"1.3目錄", check:false }, ] }, { id:2, label:"1級目錄2", check:false, children:[ { id:"2-1", label:"2.1目錄", check:false, pid:2, children:[ { id:"2-1-1", pid:'2-1', label:"2.1.1目錄", check:false, children:[ { id:"2-1-1-1", pid:'2-1-1', label:"2.1.1.1目錄", check:false, children:[ { id:"2-1-1-1-1", pid:'2-1-1-1', label:"2.1.1.1.1目錄", check:false, }, { id:"2-1-1-1-2", pid:'2-1-1-1', label:"2.1.1.1.2目錄", check:false, }, ] }, ] }, { id:"2-1-2", pid:'2-1', label:"2.1.2目錄", check:false, }, { id:"2-1-3", pid:'2-1', label:"2.1.3目錄", check:false, }, ] }, { id:"2-2", pid:2, label:"2.2目錄", check:false } ] },//在此繼續添加目錄 { id:3, label:"1級目錄3", check:false, children:[ { id:"3-1", pid:3, label:"3.1目錄", check:false, children:[ { id:"3-1-1", pid:"3-1", label:"3.1.1目錄", check:false, children:[ { id:"3-1-1-1", pid:"3-1-1", label:"3.1.1.1目錄", check:false, children:[ { id:"3-1-1-1-1", pid:"3-1-1-1", label:"3.1.1.1.1目錄", check:false }, ] }, ] }, ] } ] }, ], plist:[],//此級以上所有父節點列表 flatTree:[],//tree的平行數據 node:'',//當前點擊的node, } }, methods:{ //將tree樹形數據轉換為平行數據 transformData(tree){ tree.forEach(item=>{ this.flatTree.push(item); item.children && item.children.length>0 ? this.transformData(item.children) : "" }) }, //子組件傳遞過來的點擊的node的值 selectnode(node){ this.node=node; this.flatTree=[]; this.transformData(this.tree); if(node.check==false){//這個節點已經被選中,正在點擊取消選中 this.plist=[];//每次點擊一個新的節點都將原來plist的內容清空 this.getParentnode(this.flatTree,node.pid) }else{//正在選中 this.childAllToParent(node,this.flatTree,1); } }, //子節點取消選中,拿到此子節點所有的父節點plist getParentnode(tree,pid){ //this.plist=[] if(pid!==null){ tree.forEach(item=>{ if(item.id==pid){ this.plist.push(item) this.getParentnode(this.flatTree,item.pid) } }) } if(!pid){ this.plist.forEach(item=>{ this.updateParentCheck(this.tree,item) }) } }, //將原數據tree對應id的項的check值改為false updateParentCheck(tree,plistItem){ //console.log("方法updateParentCheck接收的plistItem參數:",plistItem) tree.forEach(item=>{ if(item.id==plistItem.id){ item.check=false; } if(item.id!==plistItem.id && item.children){ this.updateParentCheck(item.children,plistItem) } }) }, //子節點全部選中後父節點選中 childAllToParent(node,flatTree,j){ let fatherNode=''; let brotherNode=[]; this.flatTree.forEach(item=>{ if(node.pid && node.pid==item.id){ fatherNode=item;//找到瞭父節點--用於改變check的值 } }) //判斷該結點所有的兄弟節點是否全部選中 flatTree.forEach(item=>{ if(item.pid && node.pid && item.pid==node.pid){ brotherNode.push(item)//找到所有的兄弟節點 } }) //i為被選中的兄弟節點的個數 let i=0; this.flatTree.forEach(item=>{ if(node.pid==item.pid && item.check==true){ i=i+1; } }) //修改父節點的選中值 if(i==brotherNode.length && fatherNode){ fatherNode.check=true } // console.log(`第j次遞歸 j=${j}`) // console.log(`選中的bro=${i},brother的個數:${brotherNode.length}`) // console.log("父節點:",fatherNode,"兄弟節點",brotherNode) if(fatherNode.pid!==undefined){ j=j+1; this.childAllToParent(fatherNode,this.flatTree,j) } } }, mounted(){ this.transformData(this.tree);//數據初始化:將tree樹形數據轉換為平行數據 //console.log(this.flatTree) } } </script>
<style lang = "scss" scoped> .loginModuel{ margin-left: 400px; margin-top: 100px; .tree{ .ul{ >li{ margin: 5px 0 5px 0; >img{ position: relative; top: 2.4px; left: 4px; } } .ul2{ >li{ position: relative; left: 20px; margin: 5px 0 5px 0; >img{ //transition: all ease-in-out 1s; position: relative; top: 2.4px; left: 4px; } } } } } } input[type=checkbox]{ cursor: pointer; position: relative; width: 15px; height: 15px; font-size: 14px; border: 1px solid #dcdfe6; background-color: #fff !important; &::after{ position: absolute; top: 0; background-color: #fff; border: 1px solid #ddd; color: #000; width: 15px; height: 15px; display: inline-block; visibility: visible; padding-left: 0px; text-align: center; content: ' '; border-radius: 3px; transition: all linear .1s; } &:checked::after{ content: "✓"; font-size: 12px; background-color: #409eff; border: 1px solid #409eff; transition: all linear .1s; color: #fff; font-weight: bold; } } </style>
子組件主要是實現全選和取消全選。由於遞歸組件的原因,子組件拿不到完整的數據,所以接下來的兩個功能:全選後某一個子節點取消選中則父節點取消選中、子節點全選後父節點自覺選中的功能就要在父組件中完成瞭。
講解:
1、設值:
樹形數據必須有pid屬性,用於向上遍歷。
2、方法:
transformData():
將層級數據轉為平行數據,避免後期不停的遞歸調用消耗時間,平級數據使用一般的循環即可完成。selectnode():
由子組件傳遞過來的方法,大致分為兩個方向:選中、取消選中。選中時實現功能一:子節點全選後父節點自覺選中;取消選中實現功能二:全選後某一個子節點取消選中則父節點取消選中。getParentnode():
用於實現功能二。子節點取消選中後,根據pid,將在它上面級別的所有父節點列表拿到,再由方法updateParentCheck()
將父節點的check
值全部改為false
。childAllToParent():
用於實現功能一。遞歸調用該方法,將操作節點的父節點拿到,根據兄弟節點有相同的pid,拿到兄弟節點的個數,如果兄弟節點中被選中的個數等於兄弟節點的個數,則修改父節點的check
值為true
,直到到瞭根節點結束遞歸。
- 這個組件實現起來不是很難,隻要是心細就能很好的完成。
- 如果後期需要使用某些數據的話,直接掛到data裡就可以。
- 如果有更好的方法或者存在某些疑問,歡迎小夥伴留言!
附: (global.js => 放於component文件夾下)
import Vue from 'vue'; function capitalizeFirstLetter(string){ return string.charAt(0).toUpperCase() + string.slice(1); } const requireComponent = require.context( '.',true,/\.vue$/ //找到components文件夾下以.vue命名的文件 ) requireComponent.keys().forEach(fileName => { const componetConfig = requireComponent(fileName); let a = fileName.lastIndexOf('/'); fileName = '.' + fileName.slice(a); const componetName = capitalizeFirstLetter( fileName.replace(/^\.\//,'').replace(/\.\w+$/,'') ) Vue.component(componetName,componetConfig.default || componetConfig) })
- 由此其實可以實現很多遞歸組件,如側邊欄。
- 下面我放一個自己寫的側邊欄的動圖,方法比這個樹形組件要簡單些,畢竟不用考慮復選框的值。感興趣的小夥伴們可以試著實踐一下
以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- elementui中使用el-tree控件懶加載和局部刷新
- vue中的el-tree @node-click傳自定義參數
- vue使用拖拽方式創建結構樹
- vue深度優先遍歷多層數組對象方式(相當於多棵樹、三級樹)
- Vue使用el-tree 懶加載進行增刪改查功能的實現