vue 实现树节点编辑例子(增、删、改、节点移动)

 

 

目录

 

用到vue的一些技术细节

抛出事件

监听事件

强制更新渲染视图

js对象循环引用json序列问题

实例

完整代码

效果图


用到vue的一些技术细节

抛出事件

//selectChanged 是时间名称  ,后面可以带上几个参数
 this.$emit('selectChanged', id,node);

向父节点抛出事件

 // 往父节点抛出事件  最后再外层能接受到事件
this.$parent.$emit('selectChanged', this.selectedId,node);

 

监听事件

当前组件抛出的事件由当前的组件监听 


vueMainCon.$on('selectChanged',function (id,node) {
          this.selectedId = id;
          this.$forceUpdate();
          
          if(vueMainCon.clickNodes.indexOf(node) < 0 )
          		vueMainCon.clickNodes.push(node);
          
          selectNode(node);
          console.log("选中的节点标记:"+this.selectedId);
});

强制更新渲染视图

this.$forceUpdate();

 或者

vueApp.$forceUpdate();

js对象循环引用json序列问题

JONS.stringfy序列化一个树结构的时候会提示cyclic object value

用下面的解决办法

JSON.stringify(vueMainCon.cat,handlerSubmitData()),

// 解决循环引用对象造成json序列化问题
const handlerSubmitData = () => {
	  const seen = new WeakSet();
	  return (key, value) => {
	    if (typeof value === "object" && value !== null) {
	      if (seen.has(value)) {
	        return;
	      }
	      seen.add(value);
	    }
	    return value;
	  };
	};

实例

完整代码

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
<html>
<head>
<title>分类管理</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<jsp:include page="/WEB-INF/views/include/newhead.jsp" />
<style type="text/css">
.selected {
	background-color: #f1dac7;
}

.tree-node {
	font-size: 20px;
}

.tree-node .node {
	padding: 10px;
	color: #ca5530;
	cursor: pointer;
}

#占位
.chanwei {
	margin-left: 20px;
}
</style>
</head>
<body class="childrenBody site-block">
	<header class="larry-personal-tit">
		<span> <!-- baseCat 将user对应替换 --> <c:if
				test="${empty id}">新增</c:if> <c:if
				test="${not empty id}">修改</c:if>
		</span>
	</header>

	<div class="mainCon" id="app" style="display:none;">
		<form id="inputForm" autocomplete="off" method="post">
			<sys:message content="${message}" />


			<div class="layui-form-item">
				<div class="layui-inline">
					<label class="layui-form-label"><font color="red">*</font>分类名称 </label>
					<div class="layui-input-inline layui-input-inline-one">
						<input v-model="cat.name" placeholder="请输入分类名称 " maxlength="64"
							class="layui-input required" />
					</div>
				</div>
			</div>
			<div class="layui-form-item">
				<div class="layui-inline">
					<label class="layui-form-label"><font color="red">*</font>分类类型</label>
					<div class="layui-input-inline layui-input-inline-one">
						<input v-model="cat.catType" placeholder="请输入分类类型" maxlength="64"
							class="layui-input required" />
					</div>
				</div>
			</div>
			<div class="layui-form-item">
				<div class="layui-inline">
					<label class="layui-form-label">排序</label>
					<div class="layui-input-inline layui-input-inline-one">
						<input type="number" v-model="cat.doSort" placeholder="请输入排序 "
							maxlength="11" class="layui-input" />
					</div>
				</div>
			</div>
			<div class="layui-form-item">
				<div class="layui-inline">
					<label class="layui-form-label">备注信息 </label>
					<div class="layui-input-inline layui-input-inline-one">
						<textarea v-model="cat.remarks" placeholder="请输入备注信息 " rows="4"
							maxlength="255" class="layui-textarea"></textarea>
					</div>
				</div>
			</div>

			<div class="layui-form-item bottom_xf" style="z-index: 9999;">
				<div class="layui-input-block input500">
					<button type="button" class="layui-btn layui-btn-primary"
						onclick="gotoView('${ctx}/base/baseCat/');">返回</button>
					<button class="layui-btn" type="button" @click="saveData()">保存</button>
				</div>
			</div>
		</form>



		<div class="layui-row" style="background-color: #f5f5f5" >
			<div class="layui-col-md12">

				<div class="layui-card" style="margin: 10px;">
					<div class="layui-card-header">树结构</div>
					<div class="layui-card-body">
						<div class="row">
							<div class="layui-col-md5">
								<tree-root @selectChanged="selectChanged1" :node="cat.treeNode"
									:selectedNode="[]" :selectedId="0"></tree-root>
							</div>
							<div class="layui-col-md1">
							<div style="height: 100px;"> </div>
							</div>

							<div class="layui-col-md6">
								<!-- 定义一个容器 -->
								<div style="height: 70px;">
									<blockquote class="layui-elem-quote" id="btnBar">
										<div class="layui-btn-group">
											<!-- 增加 字节点-->
											<button title="增加子节点" :disabled="selectedNode.id == undefined" :class="{'layui-btn-disabled':selectedNode.id == undefined}" @click="addnode();" type="button" class="layui-btn layui-btn-sm">
												<i class="layui-icon">&#xe654;</i>
											</button>
											
											<!-- 上移 -->
											<button  title="向上移动节点" :disabled="selectedNode.id == undefined || selectedNode.deep == 0" :class="{'layui-btn-disabled':selectedNode.id == undefined || selectedNode.deep == 0}" @click="movenode(-1);" type="button" class="layui-btn layui-btn-sm">
												<i class="layui-icon">&#xe619;</i>
											</button>
											
											<!-- 下移 -->
											<button  title="向下移动节点" :disabled="selectedNode.id == undefined || selectedNode.deep == 0"  :class="{'layui-btn-disabled':selectedNode.id == undefined || selectedNode.deep == 0}" @click="movenode(1);" type="button" class="layui-btn layui-btn-sm">
												<i class="layui-icon">&#xe61a;</i>
											</button>
											
											<!-- 删除选中节点 -->
											<button title="删除选中节点" :disabled="selectedNode.id == undefined || selectedNode.deep == 0" :class="{'layui-btn-disabled':selectedNode.id == undefined || selectedNode.deep == 0}" type="button" @click="removeNode" class="layui-btn layui-btn-sm">
												<i class="layui-icon">&#xe640;</i>
											</button>
										</div>
									</blockquote>
								</div>
								<form action="" id="">

									<div 
										class="layui-form-item">
										<div class="layui-inline">
											<label class="layui-form-label">节点名称 </label>
											<div class="layui-input-inline ">
												<input placeholder="请输入名称 "  @blur="inputBlur" v-model="selectedNode.name"
													maxlength="64" class="layui-input" />
											</div>
										</div>
									</div>
								</form>
							</div>
						</div>

						<div class="layui-col-md12"></div>

					</div>
				</div>
			</div>

		</div>


	</div>


	<jsp:include page="/WEB-INF/views/include/pageJs.jsp" />

	<script type="text/javascript">
		var vueMainCon;
		$(document).ready(function() {
			
			
			
			// 定义一个名为 button-counter 的新组件
			
			var nodeConfig = {
					props: {
						  node:{
							  type:Object,
							  default:{}
						  },
						  selectedNode:{
							  type:Array,
						  		default:[]
						  },
						  selectedId:{
							  type:String,
							  default:"0"
						  }
						 },
						 data:function (){
							 return {node:this.node,selected:this.selectedNode,selectedId:this.selectedId};
						 },
						 
					  template: `
					  <template>
					  <div class="tree-node" style="padding-left:20px;" >
					  	<div @click.stop="select(node)" class="node" v-bind:class="{selected:node.selected}">
					  		<i @click.stop="expand(node)"  style="float: left; margin: 10px;" v-if="!node.isExpand && node.children!= undefined &&node.children.length != 0" class="layui-icon layui-icon-right"></i>
					  		<i @click.stop="expand(node)"  style="float: left; margin: 10px;" v-if="node.isExpand && node.children!= undefined &&node.children.length != 0" class="layui-icon layui-icon-down"></i>
					  		
					  		<!--选中-->
					  		<span v-if="node.children == undefined || node.children.length==0" style="float: left; margin: 8px;"></span>
					  		<div  @click.stop="select(node)" class="">
								<label @click.stop="select(node)" v-bind:for="node.id"></label>
							</div>
							<span @click.stop="select(node)" v-bind:class="{selected:node.selected}">{{node.name}}</span>
					  		
					  	</div>
					 	<tree-node @selectChanged="selectChanged1" v-if="node.isExpand" v-for="(item,index) in node.children" v-bind:node="item"></tree-node>
					  </div>
					  </template>
					  `,
					  methods:{
						  clickNode:function(node){
							  console.log(node.id);
						  },
						  expand:function(node){
							  if(node.isExpand == undefined){
								  node.isExpand = true;
								  
							  }else{
								  node.isExpand = !node.isExpand;
							  } 
							  
							  this.$forceUpdate();
						  },
						  select:function (node){
							  node.selected = false;
							  if(node.selected == undefined){
								  node.selected = true;
							  }else{
								 node.selected = !node.selected;
							  } 
							  
							  this.selectedId = node.id;
							  
							  if(this.node.deep == 0){
								  // 往父节点抛出事件  最后再外层能接受到事件
								  this.$parent.$emit('selectChanged', this.selectedId,node);
							  }else{
								  // 往上一层抛事件  直到根节点 再往上抛事件
								  this.$emit('selectChanged', this.selectedId,node);
							  }
							  
							  this.$forceUpdate();
							  
						  },
						  selectChanged1:function (id,node){
							  if(this.node.deep == 0){
								// 往父节点抛出事件  最后再外层能接受到事件
								  this.$parent.$emit('selectChanged', id,node);
							  }else{
								  // 往上一层抛事件  直到根节点 再往上抛事件
								  this.$emit('selectChanged', id,node);
							  }
							  
						  }
					  }
					};
			
			
			
			Vue.component('tree-root', nodeConfig);
			Vue.component('tree-node', nodeConfig);
			
			layui.use([ 'layer' ], function() {
				window.top.layer.closeAll('loading');
				initData();
			});

			$(window).scroll(function() {//开始监听滚动条
				//checkHeight("#btnBar", "#detailForm");
			});

		});

		function initData() {
			var index = window.top.layer.load(1, {
				shade : [ 0.1, '#fff' ]
			//0.1透明度的白色背景
			});
			$.get("${ctx}/base/baseCat/getJson?id=${id}", function(data) {
				Vue.component('v-select', VueSelect.VueSelect);
				
				$("#app").show();
				

				vueMainCon = new Vue({
					el : "#app",
					data : {
						cat:data,//{name:'',catType:'',doSort:2,remarks:'ss',treeNode:{id:'aa',isExpand:true,selected:false,name:'aa',deep:0,children:[{id:'aabb',isExpand:true,selected:false,name:'aabb',deep:1,children:[{id:'aabbcc',isExpand:true,selected:false,name:'aabbcc',deep:2,},{id:'aabbcca',isExpand:true,selected:false,name:'aabbcc',deep:2,}]},{id:'asdasd',isExpand:true,selected:true,name:'aabb',deep:1,children:[{id:'qwef',isExpand:true,selected:false,name:'aabbcc',deep:2,}]}]}},
						selectedNode:{},
						clickNodes:[]
					},
					methods:{
						selectChanged1:function(selectedNodes){
							this.selectedNode.pushAll(selectedNodes);
							this.$forceUpdate();
							console.log('选中:' + this.selectedNode);
						},
						addnode:function (){
							if(vueMainCon.selectedNode.children == undefined)
							{
								vueMainCon.selectedNode.children = [];
							}
							var newNode = {id:guid(),isExpand:true,selected:true,name:'请输入名称',deep:vueMainCon.selectedNode.deep+1,children:[]};
							newNode.parent = vueMainCon.selectedNode;
							vueMainCon.selectedNode.children.push(newNode);
							
							this.clickNodes.push(newNode);
							
							selectNode(newNode);
						},
						inputBlur:function(){
							var name = $.trim(this.selectedNode.name);
							this.selectedNode.name = name==""?"请输入名称":name;
						},
						removeNode:function (){
							if(vueMainCon.selectedNode.parent != undefined)
							{
								var index = vueMainCon.selectedNode.parent.children.indexOf(vueMainCon.selectedNode);
								if(index >=0){
									vueMainCon.selectedNode.parent.children.splice(index,1);
									vueMainCon.selectedNode = {};
								}
							}
						},
						movenode:function(step) {
							
							var i = this.selectedNode.parent.children.indexOf(this.selectedNode);
							var newPos = i + step;
							if(newPos < 0){
								newPos = 0;
							}
							
							if( newPos+1 > this.selectedNode.parent.children.length){
								newPos =this.selectedNode.parent.children.length-1;
							}
							var tempOption = this.selectedNode.parent.children[newPos];
							this.$set(this.selectedNode.parent.children,newPos,this.selectedNode.parent.children[i]);
							this.$set(this.selectedNode.parent.children,i,tempOption);
							this.$forceUpdate();
						}
						
					},
					watch:{
						selectedNode: {
				            deep: true,
				            handler: function(newVal, oldVal) {
				                console.log(newVal, oldVal)
				            }
						}
					}
				});
				
				// 监听事件
				vueMainCon.$on('selectChanged',function (id,node) {
			           this.selectedId = id;
			           this.$forceUpdate();
			           
			           if(vueMainCon.clickNodes.indexOf(node) < 0 )
			           		vueMainCon.clickNodes.push(node);
			           
			           selectNode(node);
			           console.log("选中的节点标记:"+this.selectedId);
				});
				
				function selectNode(node){
					var select = node.selected;
					
					
					for(var i=0;i<vueMainCon.clickNodes.length;i++){
						vueMainCon.clickNodes[i].selected = false;
					}
					
					node.selected = select;
					
					vueMainCon.selectedNode = node;
					
					vueMainCon.$forceUpdate();
				}
				
				
				$.get("${ctx}/base/baseCatNode/getJsonList?catId=${id}", function(data) {
					var root = data.shift();
					root.children = findChildrenAndSetParent(data,root);
					
					
					
					for(var i=0;i<data.length;i++){
						var node = data[i];
						node.children = findChildrenAndSetParent(data,node);
					}
					
					vueMainCon.cat.treeNode = root;
					window.top.layer.closeAll();
				});
			});
		}
		
		// 找到孩子节点 并设置它的父节点
		function findChildrenAndSetParent(arr,parent){
			var children = [];
			for(var i=0;i<arr.length;i++){
				if(parent.id == arr[i].parent.id){
					children.push(arr[i]);
					arr[i].parent = parent;
				}
			}
			return children;
		}
		
		// 生成uuid
		function guid() {
		    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
		        var r = Math.random() * 16 | 0,
		            v = c == 'x' ? r : (r & 0x3 | 0x8);
		        return v.toString(16);
		    });
		}
		
		function saveData(){
			var ret = $("#inputForm").valid();
			if (ret == false) {
				return false;
			}
			
			var index = window.top.layer.load(1, {
				shade : [ 0.1, '#fff' ]
			//0.1透明度的白色背景
			});
			
			$.ajax({
			      type: "post",
			      url: "${ctx}/base/baseCat/saveJson",
			        dataType : "json",
			        data: JSON.stringify(vueMainCon.cat,handlerSubmitData()),
			        contentType: "application/json",
			      success: function (data) {
			    	  window.top.layer.closeAll();
			    	  if (data.resultCode == '00100') {
							window.top.layer.msg('提交成功');
							window.location.href = "${ctx}/base/baseCat";
						} else {
							window.top.layer.msg(data.resultMsg);
						}
			    	  
			      },
			      fail:function (data){
			    	  window.top.layer.closeAll();
			    	  window.top.layer.msg("系统异常");
			      }
			    });
		}
		
		// 解决循环引用对象造成json序列化问题
		const handlerSubmitData = () => {
			  const seen = new WeakSet();
			  return (key, value) => {
			    if (typeof value === "object" && value !== null) {
			      if (seen.has(value)) {
			        return;
			      }
			      seen.add(value);
			    }
			    return value;
			  };
			};
	</script>
</body>
</html>

效果图

 

### 回答1: 好的,下面我将简单介绍一下如何使用 LiteGraph 和 Vue 实现拓扑图 Demo。 LiteGraph 是一个开源的 JavaScript 图形编程库,可以用于创建交互式的节点图。Vue 是一个流行的 JavaScript 框架,可以用于构建用户界面。 首先,我们需要安装 LiteGraph 和 Vue。可以通过以下命令进行安装: ``` npm install litegraph vue ``` 接下来,我们创建一个 Vue 组件,用于展示拓扑图。在组件中,我们需要引入 LiteGraph 和样式表文件: ```html <template> <div class="topology-graph"> <canvas ref="canvas"></canvas> </div> </template> <script> import LiteGraph from 'litegraph.js'; import 'litegraph.js/css/litegraph.css'; import './topology-graph.css'; export default { name: 'TopologyGraph', mounted() { this.graph = new LiteGraph.LGraph(); this.graph_canvas = new LiteGraph.LGraphCanvas(this.$refs.canvas, this.graph); }, beforeDestroy() { this.graph_canvas?.dispose(); this.graph?.reset(); }, }; </script> ``` 在组件的 `mounted` 生命周期钩子函数中,我们创建了一个 LiteGraph 图形对象和一个 LiteGraph 的画布对象,并将画布对象绑定到组件的 `canvas` 元素上。 接下来,我们可以添加节点和连接线到图形对象中。例如,以下代码将添加两个节点和一个连接线: ```javascript this.graph.add(new LiteGraph.LGraphNode('input')); this.graph.add(new LiteGraph.LGraphNode('output')); const node1 = this.graph.getNodeById(1); const node2 = this.graph.getNodeById(2); this.graph.add(new LiteGraph.LLink(node1, 0, node2, 0)); ``` 这些节点和连接线可以通过拖动和连接来交互。最后,我们需要使用 CSS 样式表对画布进行布局和样式设置。 以上是一个简单的 LiteGraph 和 Vue 实现拓扑图 Demo 的例子,你可以根据自己的需求进行更加复杂的实现。 ### 回答2: Litegraph是一个轻量级的图形编程框架,而Vue是一个流行的前端框架。在Vue中,我们可以使用Litegraph来实现一个拓扑图的demo。 首先,我们需要在Vue项目中引入Litegraph库。可以通过npm或者直接在html中引入Litegraph的脚本文件。 接下来,在Vue组件中,我们可以使用Litegraph来创建一个图形化的拓扑图。在组件的生命周期方法中,可以创建Litegraph的Graph实例,并设置其容器。 ```javascript <template> <div> <div ref="graphContainer"></div> </div> </template> <script> import Litegraph from 'litegraph.js'; export default { mounted() { const graph = new Litegraph.LGraph(); const container = this.$refs.graphContainer; // 设置图形容器 graph.setCanvas(container); // 在图形中添加节点 const node1 = Litegraph.createNode("basic/const"); const node2 = Litegraph.createNode("basic/watch"); // 将节点添加到图形中 graph.add(node1); graph.add(node2); // 连接节点 node1.connect(0, node2, 0); // 运行图形 graph.start(); } } </script> ``` 在上面的代码中,我们首先创建了一个Litegraph的Graph实例,并设置了一个容器来展示拓扑图。然后,我们创建了两个节点,并将它们添加到图中。最后,通过连接节点的输入输出来建立节点之间的关系。当图形被创建完毕后,我们可以调用start方法来运行图形。 通过以上步骤,我们就可以使用Litegraph和Vue实现一个简单的拓扑图demo了。当然,具体的拓扑图样式和逻辑还需要根据实际需求进行定制和扩展。 ### 回答3: litegraph是一个用于构建流程图和拓扑图的JavaScript库,而Vue是一种流行的JavaScript框架。结合litegraph和Vue,我们可以实现一个拓扑图的Demo。 首先,我们需要在项目中引入litegraph和Vue的依赖包。可以通过CDN或npm安装这些依赖。然后,我们在Vue的页面中创建一个div元素作为容器,用于展示拓扑图。 接下来,我们可以在Vue的生命周期钩子函数created中,使用litegraph创建一个Graph对象,并将其渲染到之前创建的div中。代码如下: ``` <template> <div id="topology"></div> </template> <script> import LiteGraph from 'litegraph.js'; export default { created() { const container = document.getElementById('topology'); const graph = new LiteGraph.Graph(container); // 添加节点 const node = LiteGraph.createNode('basic/input'); node.pos = [100, 100]; graph.add(node); // 添加连线 const outputNode = graph.getNodeById(node.id); const inputNode = graph.getNodeById(node.id); const link = outputNode.connect(0, inputNode, 0); // 渲染画布 graph.start(); // 支持鼠标拖拽移动节点和连线等交互操作 container.addEventListener('mousemove', graph.processMouseMove.bind(graph)); container.addEventListener('mouseup', graph.processMouseUp.bind(graph)); container.addEventListener('mousedown', graph.processMouseDown.bind(graph)); }, }; </script> ``` 在上述代码中,我们首先获取到之前创建的div容器元素,然后创建一个Graph对象,并将其渲染到该容器中。 接下来,我们创建节点和连线,将节点添加到图中,并通过连接连线起来。这里创建了一个输入节点和一个输出节点,并通过连线将它们连接起来。 最后,我们启动Graph对象,使其开始渲染画布。同时,我们添加了一些鼠标事件监听器,以支持鼠标拖拽移动节点和连线等交互操作。 通过上述步骤,我们就可以实现一个简单的拓扑图Demo,使用litegraph和Vue来创建和展示拓扑图。当然,如需更复杂的功能,还可以进一步扩展和定制化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值