近期项目用到大量的树结构,比如目录树、文章标记动态生成树结构,实现的过程是基于vue的框架,结合vue数据驱动应用递归函数实现数据结构的增删改查
一切思绪的来源,结合vue官方提供的树形图实例,可以轻松实现自定义开源树结构,github上优秀的开源插件我都看过,都是基于树形结构实现的
<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);
});
以上是递归函数的应用,很实用可以解决很多后端的问题
另外说一嘴,解决递归组件事件传递的方法有很多,比如事件车,本实例之初也用到了事件车,因为代码设计的要求换了另一种实现的方式,心细的朋友多看几遍就会发现
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。