上海古都建筑设计集团,上海办公室装修设计公司,上海装修公司高质量的内容分享社区,上海装修公司我们不是内容生产者,我们只是上海办公室装修设计公司内容的搬运工平台

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

guduadmin201月前

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

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

在vue3中浅尝antv X6 2.0 demo(三),第1张

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

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

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

    在vue3中浅尝antv X6 2.0 demo(三),第2张

     代码实现:

    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的格式将你要常用的节点模版传给后端,以便后期调用)

    • 在vue3中浅尝antv X6 2.0 demo(三),第3张

          代码实现:

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

      • 新增节点

      • 新增{{ task }}
        
        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,

                        移出隐藏

        在vue3中浅尝antv X6 2.0 demo(三),第4张

         代码实现

        // 连接桩
        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个节点出现的,也就是一个子节点不能有两个源节点)

        在vue3中浅尝antv X6 2.0 demo(三),第5张

        代码实现 

        	// 节点连接成功时
        	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)

          在vue3中浅尝antv X6 2.0 demo(三),第6张

          我主要是通过键盘按钮('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是真的不是很熟悉...头疼...

网友评论

搜索
最新文章
热门文章
热门标签
 
 周公梦见猫解梦大全查询  做梦梦见荡秋千  做梦是睡眠好还是不好