近期项目用到大量的树结构,好比目录树、文章标记动态生成树结构,实现的过程是基于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