X6 是基于 HTML 和 SVG 的图编辑引擎,提供低成本的定制能力和开箱即用的内置扩展,方便我们快速搭建 DAG 图、ER 图、流程图、血缘图等应用,详细见官方文档。最近用到了该项技术,做下问题总结,记录一下解决方案。
自定义元素ID
元素在拖动放到画布后,会自动生成一个ID,代表该元素会有唯一性,但是这个ID是很长且没有特点的,因此想要自定义ID。官方示例地址
但元素生成后ID不再支持修改,ID改变这个时候画布会认为你只是新生成了一个元素。那么节点信息就需要你额外再复制一遍,这样比较麻烦且不支持异步获取了。
解决方法:监听元素从拖动时,异步生成ID,且利用Dnd 实例自定义生成元素,放到画布后,该元素就会以该ID为主
具体代码如下
增加监听,这里只做获取ID
ini
<div id="stencil" class="stencil-container" @mousedown="startDrag($event)"></div>
···
const startDrag = async (event) => {
const res = await getNodeId({});
if (res.success) {
// 更新节点的ID属性
newNodeId.value = res.data
}
}
···
初始化时会生成stencil实例(用于注册拖拽元素),官方示例有,这里不再具体介绍
创建Dnd实例,并监听创建新节点时,用新ID进行覆盖,然后绑定到stencil实例即可成功实现ID替换
ini
//实现自定义节点替换
const initStencilEvents = async () => {
if (!stencil.value || !stencil.value.container) return;
// 创建 Dnd 实例来处理拖拽
const dnd = new Dnd({
target: graph.value,
getDropNode: (draggingNode) => {
// 创建一个新的节点配置对象,包含自定义 ID
const nodeConfig = {
...draggingNode.toJSON(),
id: newNodeId.value
};
// 创建带有自定义 ID 的新节点
const newNode = graph.value.createNode(nodeConfig);
console.log('创建节点,ID:', newNodeId.value);
return newNode;
},
});
// 将 Dnd 实例绑定到 stencil
stencil.value.dnd = dnd;
};
增加元素小工具
对于一些元素可以增加图标之类的展示,可以利用addTools方法实现,细节方法见官方文档
php
myNode.addTools([{
name: 'button',
args: {
markup: [
{
tagName: 'circle',
selector: 'button',
attrs: {
r: 8, // 圆的半径
fill: 'white', // 填充色为白色
cursor: 'pointer', // 鼠标指针样式
// 以下为可选属性,用于进一步控制外观和位置:
rx: 0, // 圆角x半径(对圆形通常为0或不设置)
ry: 0, // 圆角y半径(对圆形通常为0或不设置)
zIndex: 20, // 层级(确保显示在最前)
transform: 'translate(0, 16)', // 变换(可进行平移、旋转等操作)
},
},
{
tagName: 'image',
selector: 'icon',
attrs: {
'xlink:href': statusType, // 或 Base64 Data URL, 或在线 URL
width: 18,
height: 18,
x: -10, // 居中
y: 8, // 居中
cursor: 'pointer',
zIndex: 1000,
}
}
],
zIndex: 1000,
x: '100%',
y: '0%',
// offset: { x: -5, y: -36 },
},
}]);
这里我们用到了自定义图标statusType我传值是Base64,这里需要注意一个点,位置信息放在args这一层级,且相对位置,不然会出现元素增加或者缩小时图标位置没有变化,另外注意此方法在元素生成后调用
增加元素悬浮提示
鼠标放置元素上时,出现弹窗提示,大概下方效果

实现比较简单,监听画布中鼠标划入元素即可
graph.value为画布注册实例
这里也需要注意一个点,画布坐标和浏览器视口的页面坐标不一样,要进行转化
ini
// 获取提示框DOM元素
const tooltipEl = document.getElementById('x6-tooltip');
// 监听鼠标进入元素
graph.value.on('cell:mouseenter', ({ cell, e }) => {
console.log(cell.shape);
const data = cell.getData()
let tooltipContent = ''
//有执行信息再显示
if (data.startTime) {
tooltipContent = `<div style="text-align:left"></div> `;//这里为提示信息
// 更新提示框内容
tooltipEl.innerHTML = tooltipContent;
// 获取当前单元格的包围盒(相对于画布)
const bbox = cell.getBBox();
const tipX = bbox.x + bbox.width / 2; // 元素水平中心
const tipY = bbox.y - 10; // 元素顶部向上偏移10px
//将计算出的画布坐标转换为相对于浏览器视口的页面坐标
const clientPoint = graph.value.localToPage(tipX, tipY); // 转换为页面坐标
// 设置提示框位置并显示
tooltipEl.style.left = clientPoint.x - 150 + 'px';
tooltipEl.style.top = clientPoint.y - 40 + 'px';
tooltipEl.style.display = 'block';
}
});
鼠标离开元素
dart
// 监听鼠标离开单元格
graph.value.on('cell:mouseleave', () => {
// 隐藏提示框
tooltipEl.style.display = 'none';
})
css样式
css
.x6-tooltip {
display: none;
/* 默认隐藏 */
position: absolute;
/* 绝对定位 */
z-index: 1000;
/* 确保提示框在最上层 */
background-color: #333;
color: #fff;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
/* 防止提示框干扰鼠标事件 */
/* 可添加更多样式如阴影、箭头等 */
}
增加右击菜单
原理和增加元素悬浮提示类似,监听右击事件,增加悬浮窗

定义菜单样式
xml
<div id="context-menu" class="context-menu">
<div class="menu-item" data-action="retry">重试</div>
<div class="menu-item" data-action="cancle">取消</div>
<!-- 更多菜单项 -->
</div>
JS监听点击事件,将元素添加上去,并绑定处理菜单点击动作
ini
// 监听单元格(节点或边)的右键事件
graph.value.on('cell:contextmenu', ({ e, cell, x, y }) => {
e.preventDefault(); // 阻止浏览器默认的右键菜单
// 计算菜单显示位置(基于鼠标事件位置)
const left = e.clientX;
const top = e.clientY;
// 更新菜单位置并显示
contextMenu.style.left = `${left}px`;
contextMenu.style.top = `${top}px`;
contextMenu.style.display = 'block';
// 为菜单项添加点击事件(可选:每次显示时重新绑定)
const menuItems = contextMenu.querySelectorAll('.menu-item');
menuItems.forEach(item => {
item.onclick = () => {
const action = item.getAttribute('data-action');
handleMenuAction(action, cell); // 处理菜单动作
hideMenu(); // 隐藏菜单
};
});
});
监听其他地方点击消除菜单
csharp
// 监听画布点击事件以隐藏菜单
graph.value.on('cell:click', hideMenu);
graph.value.on('blank:click', hideMenu); // 点击画布空白处也隐藏
处理菜单动作
javascript
//隐藏菜单
function hideMenu() {
contextMenu.style.display = 'none';
}
// 处理菜单动作
async function handleMenuAction(action, cell) {
nodeJudgmentType.value = 2
nodeJudgmentId.value = cell.id
switch (action) {
case 'retry':
// 编辑逻辑,例如弹出编辑框
break;
case 'cancle':
// 编辑逻辑,例如弹出编辑框
break;
// ... 其他操作
}
}