在vue3中浅尝antv X6 2.0 demo(三)

avatar
作者
猴君
阅读量:3

终于抽空把antv X6 2.0这个版本的demo抽出来了,原以为项目会一直使用vue3去做这个流程,结果最近项目经理说antv X6的菜单功能只有react才能用...然鹅...写到菜单模块的时候,发现都可以用的...

目前我项目里面react版本的多一些功能(如:新增节点时自动布局、右键菜单,如图)

这篇记一下这个小demo的一些功能和实现~~~

(附上demo仓库连接: https://github.com/Tipchak5/vue3_antv_X6_2.0.git

  • 导入模版 (画布中的节点是点击插入模版直接形成的,且左侧目录树与模板的关系是相对应的,目录树的label我用的是节点id来显示的)

 代码实现:

1、先引入模版数据tsakMasterplate(后期应该是调后端接口选择模版导入)

2、直接调这个leadInMasterplate方法,导入模板前会先清空画布

const tsakMasterplate = ref(require('../../assets/masterplate.json')); // 引入模版数据  /** 导入模版 */ const leadInMasterplate = async () => { 	// 清空所有单元格 	const cells = graph.getCells(); // 获取所有单元格 	graph.removeCells(cells); // 画布清空 	// 模版渲染 	await new Promise((resolve) => setTimeout(resolve, 100)); 	graph.fromJSON(tsakMasterplate.value).centerContent(); // 引入模版数据 这里tsakMasterplate是我自己写的一个死数据 	// 对应的目录树 	treeInfo.value = [ 		{ 			label: 'node2', 			children: [ 				{ 					label: 'task2', 					children: [ 						{ 							label: 'task3', 							children: [], 						}, 						{ 							label: 'task5', 							children: [], 						}, 					], 				}, 			], 		}, 	]; };

  • 导出json(其实导出json就相当于创建了一个模版,以json的格式将你要常用的节点模版传给后端,以便后期调用)

    代码实现:

// 导出json function printNodeList() { 	console.log(graph.toJSON(), '导出数组'); 	console.log(JSON.stringify(graph.toJSON({ diff: true })), 'JSON'); 	graph.clearCells(); }

  • 新增节点

  • <el-button type="primary" :icon="Plus" :title="task"      @mousedown="startDrag(task, $event)" >新增{{ task }} </el-button>  const task = ref('任务');  // 新增任务节点 const startDrag = (type) => {  	const tree = startDragToGraph(graph, type); // graph就是初始化的画布 因为我是将startDragToGraph方法抽出来放在公共js中直接调用的 所以传了graph 如果你在当前页面就不需要传了  	id.value = tree.id; 	const label = { label: tree.id, children: [] }; 	treeInfo.value[0].children.push(label); // 更新树目录 };  // 这里startDragToGraph方法如下 let count = 0; const startDragToGraph = (graph, type) => { 	let node = null;  	node = graph.addNode({ 		shape: 'custom-rect', 		attrs: { 			label: { 				text: type, 				// 自动换行 				textWrap: { 					width: '90%', 					height: '80%', 					ellipsis: true, 					breakWord: true, 				}, 			}, 		},  		x: -50, 		y: -50, 		id: `task${++count}`, // 设置节点id 方便后期根据id抓节点 进行一些操作 	});  	ElMessage.success(`添加任务节点${node.id}成功!`); 	return node; };

  •  节点连接时的一些操作

        1、首先,它节点中带有一个叫做port的连接桩(也就是节点边上的小圆点)

        2、什么时候显示port,并让节点之间相连接?案例告诉我们是鼠标移入节点时显示port,

                移出隐藏

 代码实现

// 连接桩 const ports = { 	groups: { 		top: { 			position: 'top', 			attrs: { 				circle: { 					r: 2, 					magnet: true, 					stroke: 'black', 					strokeWidth: 1, 					fill: '#fff', 					style: { 						visibility: 'hidden', 					}, 				}, 			}, 		}, 		right: { 			position: 'right', 			attrs: { 				circle: { 					r: 2, 					magnet: true, 					stroke: 'black', 					strokeWidth: 1, 					fill: '#fff', 					style: { 						visibility: 'hidden', 					}, 				}, 			}, 		}, 		bottom: { 			position: 'bottom', 			attrs: { 				circle: { 					r: 2, 					magnet: true, 					stroke: 'black', 					strokeWidth: 1, 					fill: '#fff', 					style: { 						visibility: 'hidden', 					}, 				}, 			}, 		}, 		left: { 			position: 'left', 			attrs: { 				circle: { 					r: 2, 					magnet: true, 					stroke: 'black', 					strokeWidth: 1, 					fill: '#fff', 					style: { 						visibility: 'hidden', 					}, 				}, 			}, 		}, 	}, 	items: [ 		{ 			group: 'top', 		}, 		{ 			group: 'right', 		}, 		{ 			group: 'bottom', 		}, 		{ 			group: 'left', 		}, 	], };

port的显示与隐藏

	graph.on('node:mouseenter', () => { 		const container = document.getElementById('graph-container'); 		const ports = container.querySelectorAll('.x6-port-body'); 		showPorts(ports, true); 	}); 	graph.on('node:mouseleave', () => { 		const container = document.getElementById('graph-container'); 		const ports = container.querySelectorAll('.x6-port-body'); 		showPorts(ports, false); 	});       // 以上代码需要放在onMounted周期中 或者画布初始化的方法中也可以      // 函数showPorts我是单独抽成一个公共js的文件里面使用的 看个人实际情况      function showPorts(ports, show) { 	    for (let i = 0, len = ports.length; i < len; i = i + 1) { 		    ports[i].style.visibility = show ? 'visible' : 'hidden'; 	    }     }

3、连接成功后,根据连接的目标节点和源节点来判断 树目录 的数据结构要如何显示 (么错,递归要来了...)

拿到目标节点和源节点后,去 树目录 里面找这个两个id,有的话就让 树目录里面的目标节点 成为源节点子,然后你就可以拥有一个和节点相对应的目录树了

其中有一点很重要,当连接成功后,目录树里面 目标节点 会成为 源节点 的子后,要记得删除目录树原来的那个目标节点(顺便说一下: react里面的书组件是不支持整个目录树有相同的2个节点出现的,也就是一个子节点不能有两个源节点)

代码实现 

	// 节点连接成功时 	graph.on('edge:connected', ({ isNew, edge }) => { 		if (isNew) { 			const sourceId = edge.getSourceCell().id; 			const targetId = edge.getTargetCell().id;          // findNodeById 是我写的一个公共方法 在树目录里找和节点相同的id  			const sourceNode = findNodeById( 				treeInfo.value[0].children, 				sourceId 			); // 源节点id  			const targetNode = findNodeById( 				treeInfo.value[0].children, 				targetId 			); // 目标节点id  			if (sourceNode && targetNode) { 				sourceNode.children.push(targetNode); 				// push之后删除原targetNode 				for (let i = 0; i < treeInfo.value[0].children.length; i++) { 					const node = treeInfo.value[0].children[i]; 					if (node.label === targetId) { 						treeInfo.value[0].children.splice(i, 1); 					} 				} 			} 		} 	});    // 树形目录中添加节点 export const findNodeById = (nodes, id) => { 	for (let i = 0; i < nodes.length; i++) { 		const node = nodes[i]; 		if (node.label === id) { 			return node; 		} 		if (node.children) { 			const result = findNodeById(node.children, id); 			if (result) { 				return result; 			} 		} 	} 	return null; };

  • 删除功能

删除节点可以通过绑定键盘按钮操作,也可以使用官方的节点工具(想要实现下图的删除,可以参考官方文档,so easy)

我主要是通过键盘按钮('delete', 'backspace'都可以删除)来操作的,需要安装X6对应的插件@antv/x6-plugin-keyboard来绑定删除事件,当然,树目录也要删除对应的节点数据(删除的时候,如果该节点有子节点会连带后续所有的子节点都删除

代码实现

/** 删除的一些操作 */ 	graph.bindKey(['delete', 'backspace'], () => { 		const cells = graph.getSelectedCells(); 		const cellsId = cells[0].id; // 获取删除节点的id  		if (cells.length) { 			const allChildrenNode = []; 			const nodes = cells.filter((cell) => cell.isNode()); 			nodes.forEach((node) => { 				//  获取后继单元格 				const successors = graph.getSuccessors(node, { depth: 3 }); 				allChildrenNode.push(...successors); 				console.log(successors, 'successors'); 			}); 			// 获取后继节点id 			let keyArr = []; 			allChildrenNode.forEach((i) => { 				keyArr.push(i.id); 			});  			// 删除当前节点 			graph.removeCells(cells); 			removeNodes(keyArr); 			// 如果删除的节点和其他节点有共同子节点时 删除书目录中对应的数据 			let newArr = [...keyArr, cellsId]; // 要删除的节点id 			// 删除对应树目录数据 			deleteNodeById(treeInfo.value[0].children, newArr); 			console.log(newArr, 'newArr'); 		} 	});   // 删除后继节点 function removeNodes(keys) { 	if (keys && keys.length > 0) { 		keys.forEach((key) => { 			graph.removeNode(key, { 				deep: true, 			}); 		}); 	} }   // 树形目录中删除对应节点 export const deleteNodeById = (treeArr, keyArr) => { 	let deleted = false; 	for (let i = treeArr.length - 1; i >= 0; i--) { 		const node = treeArr[i]; 		if (keyArr.includes(node.label)) { 			treeArr.splice(i, 1); 			deleted = true; 		} else if (node.children && node.children.length) { 			if (deleteNodeById(node.children, keyArr)) { 				deleted = true; 				if (node.children.length === 0) { 					node.children = []; 				} 			} 		} 	} 	return deleted; };

以上差不多就是这个demo的所有功能了,还有文本编辑啥的,我还没测过,暂时就不管了,先这样吧~

期待与大家一起共同交流,共同进步,如果有地方写的烂,还望海涵,欢迎大佬指导,最近卡在react版的自定义工具注册上,react是真的不是很熟悉...头疼...

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!