近期項目用到大量的樹結構,好比目錄樹、文章標記動態生成樹結構,實現的過程是基於vue的框架,結合vue數據驅動應用遞歸函數實現數據結構的增刪改查
一切思緒的來源,結合vue官方提供的樹形圖實例,能夠輕鬆實現自定義開源樹結構,github上優秀的開源插件我都看過,都是基於樹形結構實現的javascript
vue樹形視圖html
<item ref="treeNode" @handle="setSelectedNode" v-for='(item, index) in ztreeData' v-show='ztreeData&&elements' class="menuLi" :model="item"> </item>
<script type="text/x-template" id="item-template"> <li> <div style="positon:relative" @click="handle(model)" > <p class="col-md-6" style="position:absolute;left:0;top:0;bottom:0;" :id="'node'+model.resultId"> <span class="paddingLeft" :style="{paddingLeft:(model.level==0? 12: model.level*32)+'px'}"> <i class="fa fa-chevron-right pull-left" @click.stop="toggle(model)" :class="[!isFolder?'iconHidden':'', model.isExpand?'iconRotate':'']"></i> <i class="glyphicon glyphicon-edit pull-right" style="top:4px;"></i> </span> </p> <p class="col-md-6 text-center oldContent pull-right" style="border-left:1px solid #d9d9d9;" :title="model.oldContent"> {{model.oldContent}} </p> </div> <ul v-show="model.isExpand&&isFolder"> <item class="item" v-for="(model, index) in model.children" :key="index" @handle="emitHandle" :model="model"> </item> </ul> </li> </script>
以上是定義模板以及在頁面中應用,首先遍歷treedata把單項傳入子組件也就是本例的模板,下面js文件
// 定義子組件 Vue.component('item', { template: '#item-template', props: { model: Object, }, data: function () { return { openr:false, } }, computed: { isFolder: function () { return this.model.children && this.model.children.length; } }, methods: { //獲取選中節點數據 toggle(model){ if (this.isFolder) { vm.$set(model, 'isExpand', !model.isExpand); } }, //獲取選中節點數據以及設置選中狀態 handle (item) { this.emitHandle(item) console.log(item) var nodeId; if (event.path[0].id) { nodeId = event.path[0].id; } else if(event.path[1].id){ nodeId = event.path[1].id; } else{ nodeId = event.path[2].id; } setMouseMenu(nodeId,'.treeMenu'); }, emitHandle (item) { this.$emit('handle', item) } } }) // 定義父組件 var vm = new Vue({ el: '#bookMarker', data: { templateName:"",//內容模板 associations:'',//症狀關係 elements:'', //模板下elements數組 allIsExpand: true, dialogTit:'', //彈框的title getRangeText:'',//標引選中的文本 resultName:'',//彈框input值 elementId:"", elementType:'', orderNum:'', postArray:[], off:false, radio:'', scrollTop:0,//codemirror滾動條高度 height:'',//codemirror內容高度 oldContent:'',//子組件自定義title屬性數據 associationsElements:'', ariaHidden:true,//語義彈框顯隱控制 ztreeData:[],//ztree的數據列表 }, created(){ this.getMarkerList(); this.getTemplateInfo(); }, mounted(){ this.initView(); this.setCodeMenu(); setActiveClass(); $('body').click(()=>{ this.oldContent=''; }) setTreeFoundation(this.$refs.treebox, this.$refs.treeFoundation); }, watch:{ 'ztreeData':{ handler: function(val, oldval) { this.$nextTick(() => { this.$refs.treeMenu.style.display='none' }) }, deep: true } }, methods:{ // 獲取選中節點 setSelectedNode(model){ this.off = true; var g=function(child){ child.forEach(function (item, index) { if (item.Selected==true) { vm.$set(item, 'Selected', false) } var subChild = item.children; if(subChild!=null && subChild.length>0){ g(subChild); } }); } this.ztreeData.forEach(function (item, index) { if (item.Selected==true) { vm.$set(item, 'Selected', false) } var child =item.children; g(child); }); vm.$set(model, 'Selected', true) this.onCodemirrorLight(model); }, getSelectedNode () { var roots = []; var model; this.ztreeData.forEach(function(item,index){ if(item.parentId ==0){ roots.push(item); } }); var g=function(child){ child.forEach(function (item, index) { if (item.Selected==true) { model=item; return; } var subChild = item.children; if(subChild!=null && subChild.length>0){ g(subChild); } }); } roots.forEach(function (item, index) { if (item.Selected==true) { model=item; return; } var child =item.children; g(child); }); return model; }, //獲取模態框title getDialogTitle: function(item){ this.dialogTit=''; this.dialogChilTit=''; if (this.getRangeText) { this.dialogTit = item.elementName||item.associationName; this.orderNum=item.orderNum; this.elementId=item.elementId; this.elementType=item.elementType; event.target.dataset.toggle='modal'; } else{ event.target.dataset.toggle=''; } }, //所有摺疊 allClose(){ this.updateAllIsExpand(false); this.allIsExpand = true; }, //所有展開 allOpen(){ this.updateAllIsExpand(true); this.allIsExpand = false; }, //刪除節點 removeNode(){ var item = this.getSelectedNode() var index=0; if (item.parentId==0) { for(var i in this.ztreeData){ if(this.ztreeData[i]['id']==item.id){ index=i; break; } } this.ztreeData.splice(index,1); } else{ var parentItem = this.getNodeItem(item.parentId).children; parentItem.splice(parentItem.indexOf(item), 1); } //重置選中狀態 $('div').removeClass('activeClass'); }, //codemirror鼠標右鍵菜單 setCodeMenu(){ var doc = document.getElementById('box_fr'); var forRight = $('.codeMenu') var _this = this; doc.oncontextmenu=function(event){//關鍵點 var event=event||window.event; if (_this.getRangeText) { forRight.get(0).style.display="block"; forRight.get(0).style.left=event.pageX+"px"; forRight.get(0).style.top=event.pageY+"px"; return false; } }; doc.onclick=function(e){ forRight.get(0).style.display= "none"; e.preventDefault(); }; }, //設置語義關聯 setAssociation(item){ this.resultName = item.elementName; this.Submit(); }, //所有展開收起公共方法 updateAllIsExpand(boolean){ var g=function(child,expand){ child.forEach(function (item, index) { var childisExpand; childisExpand = vm.$set(item, 'isExpand', expand); var subChild = item.children; if(subChild!=null && subChild.length>0){ g(subChild,childisExpand); } }); } if (this.off==true) { var item = this.getSelectedNode() if (item) { var expand; expand = vm.$set(item, 'isExpand', boolean); var child =item.children; g(child,expand); } } else{ this.ztreeData.forEach((ite)=>{ var expand; expand = vm.$set(ite, 'isExpand', boolean); var child =ite.children; g(child,expand); }) } }, //獲取結構模板信息 getTemplateInfo: function(){ this.$ajax({ method: 'post', url: '/marker/api/getTemplateInfo', data: { taskId:19 } }).then((res)=>{ if (res.data.code === 1000) { this.elements = res.data.data.elements; this.associations = res.data.data.associations; this.templateName = res.data.data.templateName; res.data.data.associations.forEach((item)=>{ this.associationsElements = item.elements }) console.log(res.data.data,'getTemplete'); } },(err)=>{ console.log(err); }) }, //獲取根節點id getRoot(){ this.off=false; }, //獲取樹結構列表 getMarkerList: function(){ this.$ajax({ method: 'post', url: '/marker/api/getMarkerList', data: { taskId:19 } }).then((res)=>{ if (res.data.code === 1000) { if (res.data.data==null) {return}; this.ztreeData = res.data.data; var roots = []; this.ztreeData.forEach(function(item,index){ if(item.parentId ==0){ roots.push(item); } }); var g=function(child,level,isExpand,Selected){ var childLevel=level+1; var childisExpand = isExpand; var childSelected = Selected; child.forEach(function (item, index) { item.level=childLevel; vm.$set(item, 'isExpand', childisExpand) vm.$set(item, 'Selected', childSelected) var subChild = item.children; if(subChild!=null && subChild.length>0){ g(subChild,childLevel,childisExpand,childSelected); } }); } roots.forEach(function (item, index) { item.level= 0 ; vm.$set(item, 'isExpand', false) vm.$set(item, 'Selected', false) var child =item.children; g(child, item.level, item.isExpand, item.Selected); }); console.log(res.data,'getMarkerList'); } },(err)=>{ console.log(err); }) }, //經過resultId獲取item getNodeItem:function(resultId){ var resultItem = {}; var g=function(child,resultId){ child.forEach(function (item) { if (item.resultId==resultId) { resultItem = item; return; } var subChild = item.children; if(subChild!=null && subChild.length>0){ g(subChild,resultId); } }); } this.ztreeData.forEach((item)=>{ var child = item.children; if (item.resultId==resultId) { resultItem = item; return; } g(child, resultId); }) return resultItem; }, //codemirror滾動到頂部 goDocUp(){ this.CodeMirrorEditor.scrollTo(0,0) }, //codemirror滾動到底部 goDocDown(){ var initH = 5000 this.CodeMirrorEditor.scrollTo(0,this.height+ initH+ this.scrollTop) }, //codemirror顯示高亮 onCodemirrorLight:function(item){ if (!item) {return}; var startPos = item.startPos, endPos = item.endPos, strat, end; //將返回_index轉爲Pos對象 strat = this.posFromIndex(startPos-1); end = this.posFromIndex(endPos-1); //文本高亮 this.CodeMirrorEditor.setSelection(strat,end); // this.CodeMirrorEditor.scrollTo(0,item.scrollTop) // this.CodeMirrorEditor.setValue(model.oldContent) }, //提交用戶操做 Submit:function(){ var parentId,level,Selected,isExpand; if (this.off==true) { var item = this.getSelectedNode(); if (item) { parentId = item.resultId; level = item.level+1; Selected = item.Selected; isExpand = item.isExpand } } else{ parentId = 0; level = 0; Selected = false; isExpand = false; } if (!this.resultName) { alert("請填寫標題");return event.target.dataset.dismiss=''}; var _data = { taskId:19, parentId:parentId, startPos:this.postArray[0], endPos:this.postArray[1], resultName:this.resultName, elementId:this.elementId, elementType:this.elementType, orderNum:this.orderNum, oldContent:this.getRangeText, level:level, children:[], Selected:Selected, isExpand:isExpand, }; this.$ajax({ method: 'post', url: '/marker/api/save', data: _data }).then((res)=>{ if (res.data.code === 1000) { $('#myModal').modal('hide') //結合接口返回設置_data數據 this.resultName = ''; this.allIsExpand = true; this.$refs.codeMenu.style.display='none'; _data.resultId = res.data.data; if(parentId == 0){ this.ztreeData.push(_data); // console.log(JSON.parse(JSON.stringify(this.ztreeData)),'追加之後ztreeData') } else{ item.children.push(_data); vm.$set(item, 'isExpand', true); } console.log(JSON.parse(JSON.stringify(this.ztreeData)),'追加之後ztreeData'); } },(err)=>{ console.log(err); }) }, //用戶取消操做 Cancel:function(){ this.resultName=''; this.getRangeText=''; }, //初始化頁面結構以及數據 initView: function(){ //獲取右側文章 this.$ajax({ method: 'post', url: '/marker/api/getFullText', data: { taskId:19 } }).then((res)=>{ if (res.data.code === 1000) { this.CodeMirrorEditor.setValue(res.data.data); } },(err)=>{ console.log(err); }) //初始化codemirror let myTextarea = document.getElementById('editor'); this.CodeMirrorEditor = CodeMirror.fromTextArea(myTextarea, { lineWrapping :true, styleActiveLine: true, foldGutter: true, styleSelectedText: true, mode: 'text/javascript', matchBrackets: true, cursorScrollMargin:120,//光標上下預留額外空間 showCursorWhenSelecting: true, theme: "default", }); // 光標或選中(內容)事件 this.cursorActivity(); // 記錄內容改變事件 this.CodeMirrorEditor.on("change",(instance,changeObj)=>{ console.log("change",instance,changeObj) this.height = instance.doc.height; }); //記錄滾動事件 this.CodeMirrorEditor.on("scroll",(cm)=>{ this.scrollTop = cm.doc.scrollTop }); }, //設置resultName setResultName(event){ this.resultName = getSelectText(event); }, //根據Pos轉爲數字下標 indexFromPos: function(Pos) { if(Pos.line < 0 || Pos.ch < 0) { return false; } var _index= Pos.ch+1; this.CodeMirrorEditor.eachLine(0, Pos.line, function(item){ _index+= item.text.length+1; }) return _index; }, //根據數字下標轉爲Pos posFromIndex: function(_index) { var line = 0, ch; this.CodeMirrorEditor.eachLine(0, this.CodeMirrorEditor.lineCount(), function(item) { var textNum = item.text.length + 1; if(textNum > _index) { ch = _index; return true; } else{ _index -= textNum; ++line; } }); return({ line: line, ch: ch }) }, // 光標或選中(內容)事件 cursorActivity(){ this.CodeMirrorEditor.on("cursorActivity",(cm)=>{ var startObj = {}, endObj = {}, objArray = [], newObjArray = [], activeArray = []; //拖動鼠標開始位置結束位置,支持正反選 endObj.line = cm.getCursor('head').line; endObj.ch = cm.getCursor('head').ch; startObj.line = cm.getCursor('anchor').line; startObj.ch = cm.getCursor('anchor').ch; objArray.push(startObj,endObj); // 保存開始結束位置數字下標 newObjArray.push(this.indexFromPos(startObj),this.indexFromPos(endObj)); newObjArray.sort((x,y)=>{ return x-y; }) this.postArray = newObjArray; //根據Pos獲取選中文本 sortPosArray(objArray); this.getRangeText = cm.getRange(objArray[0],objArray[1]); }); } } }); /*** ***dom.js*** ***/ //設置選中active狀態 function setActiveClass(){ $(document).on('click','.menuLi>div', function(){ $('.treeFoundation').removeClass('activeClass') $('div').removeClass('activeClass'); $(this).addClass('activeClass'); }) $(document).on('click','.item>div', function(){ $('div').removeClass('activeClass'); $('.treeFoundation').removeClass('activeClass') $(this).addClass('activeClass'); }) $('.treeFoundation').click(function(){ $(this).addClass('activeClass') $('.sidebar-menu div').removeClass('activeClass'); }) }; //鼠標右鍵菜單 function setMouseMenu(target, cursorClass){ if (!target||target==null||!cursorClass) {return} var doc = document.getElementById(target); var forRight = $(cursorClass) doc.oncontextmenu=function(event){//關鍵點 var event=event||window.event; forRight.get(0).style.display="block"; forRight.get(0).style.left=event.pageX+"px"; forRight.get(0).style.top=event.pageY+"px"; return false; }; doc.onclick=function(e){ forRight.get(0).style.display="none"; e.preventDefault(); }; }; //根據codemirror對Pos下標排序 function sortPosArray(PosArray){ PosArray.sort((c1, c2) => { if (c1.ch == c2.ch) { return c1.line - c2.line } else if (c1.line>c2.line) { return c1.line - c2.line } else if(c1.line<c2.line){ return c1.line - c2.line } else{ return c1.ch - c2.ch } }) }; //初始化textarea function getSelectText(event){ var nullvalue = -1, selectStart,//選中開始位置 selectEnd,//選中結束位置 position,//焦點位置 selectText,//選中內容 rootId = event.target.id, oTxt = document.getElementById(rootId); if(oTxt.setSelectionRange){//非IE瀏覽器 selectStart= oTxt.selectionStart; selectEnd = oTxt.selectionEnd; if(selectStart == selectEnd){ position = oTxt.selectionStart; selectStart = nullvalue; selectEnd = nullvalue; } else{ position = nullvalue; } selectText = oTxt.value.substring(selectStart,selectEnd); } else{//IE var range = document.selection.createRange(); selectText=range.text; range.moveStart("character",-oTxt.value.length); position = range.text.length; selectStart = position - (selectText.length); selectEnd = selectStart + (selectText.length); if(selectStart != selectEnd){ position = nullvalue; }else{ selectStart = nullvalue; selectEnd = nullvalue; } } return selectText }; function setTreeFoundation(parent,child){ parent.addEventListener('scroll', () => { if (parent.scrollTop>=90) { child.style.position='fixed'; child.style.width=47.5+'%'; child.style.zIndex=2; child.style.boxShadow='0px 1px 1px #d9d9d9'; } else{ child.style.position='static'; child.style.width='auto'; child.style.boxShadow='none'; } }, false) };
var roots = []; this.ztreeData.forEach(function(item,index){ if(item.parentId ==0){ roots.push(item); } }); var g=function(child,level,isExpand,Selected){ var childLevel=level+1; var childisExpand = isExpand; var childSelected = Selected; child.forEach(function (item, index) { item.level=childLevel; vm.$set(item, 'isExpand', childisExpand) vm.$set(item, 'Selected', childSelected) var subChild = item.children; if(subChild!=null && subChild.length>0){ g(subChild,childLevel,childisExpand,childSelected); } }); } roots.forEach(function (item, index) { item.level= 0 ; vm.$set(item, 'isExpand', false) vm.$set(item, 'Selected', false) var child =item.children; g(child, item.level, item.isExpand, item.Selected); });
以上是遞歸函數的應用,很實用能夠解決不少後端的問題
另外說一嘴,解決遞歸組件事件傳遞的方法有不少,好比事件車,本實例之初也用到了事件車,由於代碼設計的要求換了另外一種實現的方式,心細的朋友多看幾遍就會發現vue