「AntV」X6开发实践:踩过的坑与解决方案
长期更新版文档请移步语雀(「AntV」X6开发实践:踩过的坑与解决方案 (yuque.com))
️ | 如何自定义拖拽源?
相信你们在开发中更多的需求是需要自定义拖拽源,毕竟自定义的功能扩展性高一些,而且可以根据你的业务需求灵活设置。自定义拖拽的优点就是:万物皆可成为拖拽源,不管你使用的是html标签,还是第三方的ui框架,或者树形列表,……这些都可以设置成拖拽源,只有你想不到的,没有官方做不到的,来吧,开整。
官方提供的拖拽
自定义的拖拽
解决方案
步骤1:初始化Dnd
// 先定义个全局的dnd变量
let dnd = null;
// 在mounted中对dnd进行初始化(在graph之后初始化)
dnd = new Dnd({
  target: graph,
  scaled: false,
  dndContainer: proxy.$refs.dndContainer
});
步骤2:定义拖拽事件
/**
 * 自定义拖拽源事件
 * @param {*} e
 * @param {*} treeNode 根据需要传入要添加的参数
 * @param {*} data 根据需要传入要添加的参数
 * 这里使用的是elementPlus的tree组件
 */
const startDrag = (e, treeNode, data) => {
  console.log('eee', e);
  console.log('treeNode', treeNode);
  console.log('data', data);
  const node = graph.createNode({
    shape: 'cu-data-node',
    width: 150,
    height: 104,
    label: data?.label,
	// 传递给自定义节点的数据
    data: {
      label: data?.label,
      img: data?.img,
      desc: data?.desc
    },
    ports: {
      ...port,
      items: [
        {
          group: 'top'
        }
      ]
    }
  });
  dnd.start(node, e);
};
步骤3:自定义html节点
  // 注册自定义节点 图标+标题+描述
  Shape.HTML.register({
    shape: 'cu-data-node',
    width: 'auto',
    height: 104,
    effect: ['data'],
    html(cell) {
      // 获取节点传递过来的数据
      const { label, img, desc } = cell.getData();
      // 创建自定义的节点容器
      const container = document.createElement('div');
      container.setAttribute('class', 'cu-container');
      // 图片根据不同的类型进行切换,可以是后端返回的图标,也可以是自己本地的图标,如果是后端返回就通过节点的data传进来
      const container_img = document.createElement('img');
      container_img.src = currentTab.value === 0 ? '/src/assets/images/operator/datasouce.png' : img;
      container_img.setAttribute('class', 'cu-container-img');
      const container_title = document.createElement('div');
      container_title.innerText = label;
      container_title.setAttribute('class', 'cu-container-title');
      const container_desc = document.createElement('div');
      container_desc.setAttribute('class', 'cu-container-desc');
      container_desc.innerText = desc || '描述信息';
      container.appendChild(container_img);
      container.appendChild(container_title);
      container.appendChild(container_desc);
      return container;
    }
  });
步骤4:元素绑定拖拽事件
<!-- $event必传,后面的参数根据你的业务需求动态添加 -->
<div  @mousedown="startDrag($event, node, data)">拖拽的节点</div>
️ | 本地图片导出后不显示?
先看看官方的导出文档:
图片导出
由于业务需要,需要把画布上的节点保存成图片供其它模块展示,如果你的后端返回的数据格式是前端想要的,那么大不必搞图片的形式,直接把官网的快速上手代码拿过来循环一下就好了……,这里就拿
toPng的方法来讲解
问题梳理
- 调用toPng拿到画布的base64数据
- 把base64的数据传给后端
- 后端把base64的数据转存后生成可访问的图片地址再返回给前端
- 前端开始展示
然而事情却没有这么简单,第一步就遇到了一堆的坑,由于官网上导出的都是它内置的节点,所以导出都没啥问题,但是我使用的是html节点,导出的时候,我节点的图片就死活导不出来,而且导出的样式也是乱的(样式错乱问题)
解决方案
一句话:图片必须得是base64格式的导出才会有图片,不然无法导出节点的图片
- 自定义html节点中把图片转换成base格式的
需要调用 imageToDataUri把图片地址转成base64的数据,这个方法我也是摸索了好久才找到的,官方文档完全没有提及这个方法,如果需要查看其它方法,请打印
DataUri这个对象
DataUri.imageToDataUri('/images/operator/datasouce.png',
  function (nu, url) {
    // 第一个参数无效,用的只是第二个参数,但是第一个参数不写不行
    container_img.src = url; // 给图片标签赋值
  }
);
- 调用toPng生成base64数据
这个步骤中要处理的问题:导出后样式不正确,导出的时候页面闪动
graph.toPNG(
  dataUri => {
    console.log('dataUri >>>>', dataUri); // 这个就是base的图片地址
  },
  {
    width: 526,
    height: 268,
    backgroundColor: 'rgba(25, 87, 121, 0.18)',
    quality: 1, // 图片质量 取值范围:0-1,默认0.92
    // copyStyles: false,
    // 自定义样式表,为了解决导出后节点样式丢失的问题,暂时官方还没有修复这个bug
    stylesheet: `
      .cu-container {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      }
      .cu-container-title {
      color: #d3e6f3;
      }
      .cu-container-img {
      width: 53px;
      height: 53px;
      margin-bottom: 4px;
      }
      .cu-container-desc {
      color: rgba(211, 230, 243, 0.7);
      margin-top: 3px;
      }
      .cu-container-title,
      .cu-container-desc {
      font-size: 14px;
      font-weight: 400;
      line-height: 20px;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 1;
      }
    `
  }
);
:::warning
Tip:图片导出样式和原节点的样式不一致问题:如果你遇到了这个问题,目前最好的方法是不断调整stylesheet中的样式,直到导出的样式和原节点的样式几乎一致即可。当然,如果你的甲方不重视页面交互,咱们完全可以使用copyStyles:true这个属性就行了,这样就不用设置 stylesheet了
:::
️ | 神奇的图片边框
:::tips
昨天才把html节点中的图片转成base格式的,今天就发现一个用户体验的问题;那么是啥呢?就是我从左侧的树形菜单中拖拽节点的时候(鼠标按下也是同样问题),发现节点的图片区域那里会出现一个边框,持续时间不是很长,就几毫秒的时间,但是当你连续拖拽几个不同节点的时候就会发现这个边框竟然又消失不见了,如果此时重新进入页面,再开始拖动节点,图片的边框又出现了。
:::
问题梳理
- 是否是官方节点自带的边框?如果是,配置项是什么?
- 图片的默认边框是否被清除?还是外围元素的边框导致?
- 图片的加载时机?
- 是否是转base64的问题,毕竟在这之前没有这个情况发生……
 :::tips
 带着这些问题,第一时间去翻阅了官方文档,发现没有和这个问题相关的配置项,即使有设置了也不管用;然后就把问题抛到了交流群里面,发现压根没人回答……,那就只能开始第二个方案了:把图片的默认边框都去除掉,比如border,box-shadow,outline这些属性都去除掉了,发现还是没用;好吧,开始第三套方案:使用new Image处理图片的加载时机问题,嗯!不出意外的话,这个方法还是不行
 :::
定位问题
经过上面四个方案的尝试后,我大概知道了问题的源头在哪边了,那就是我自定义html节点中图片地址赋值的地方,由于
DataUri.imageToDataUri这个方法是个异步执行的,所以才会导致在渲染的时候会出现短暂的视觉差
解决方案
- 先给图片赋值个普通的地址(非base64的地址)
- 在DataUri.imageToDataUri('随便写个参数名',url)的回调中再把图片的src替换成base64的
const container_img = document.createElement('img');
container_img.setAttribute('class', 'cu-container-img');
container_img.setAttribute('alt', '节点ico');
container_img.style.cursor = 'pointer';
// 先用远程图片地址给图片的src赋值,然后再重新赋值成base64的格式;这么做的目的就是解决节点拖拽到画布上会出现短暂的边框闪动问题,如果你要复现这个边框,可以把下面这一行代码注掉(不是必现)
container_img.src = img;
// 把图片转成base64方便存储到后端
DataUri.imageToDataUri(img, function (nu, url) {
// 第一个参数无效,用的只是第二个参数,但是第一个参数不写由不行
container_img.src = url;
});
️ | 画布内容从接口获取数据后无法居中?
场景
前端需要把画布上的节点保存到后端,然后前端在获取详情接口的时候要把节点进行居中处理
问题梳理
在不调用后端接口的情况下使用centerContent()是没得问题的;但是在动态获取节点数据后就会存在异步加载的问题,也就是先将内容居中了,之后再设置节点到画布中去,此时centerContent的时机已经过去了,节点还是更具自身的位置进行排列
解决方案
由于作者用的vue技术栈,所以这里的解决方法主要以vue为主
方案1:使用nextTick等待dom全部渲染完成
nextTick(() => {
	graph.centerContent();
});
方案2:直接在接口中使用
getDataView({ size: -1, name: item.tableMetaName }).then(res => {
    if (res.code === 0) {
      // 缩放
      graph.zoom(-0.1);
      // 画布居中
      graph.centerContent();
    }
});
️ | 自定义右键菜单坐标不准确?
问题梳理
原本的写法是节点右键的时候通过node.position()的方法获取节点的坐标,然后再把节点的坐标绑到右键菜单的dom上,但是发现对画布进行平移的时候,右键菜单的位置还停留在第一次的位置,原因就是画布平移和节点没啥关系,节点的坐标并不会因为画布平移了就自动更改自身的坐标
解决方案
对鼠标的坐标进行转换,这也是1.9版本中新增的一个方法,关键是在官方文档中还找不到这个方法,只能死马当活马医了 坐标转换,果然问题解决了
graph.on('node:contextmenu', ({ e, node }) => {
  const pos = graph.clientToGraph(e.clientX, e.clientY);// 核心代码就是这一行
  createMenuDom({ x: pos.x, y: pos.y, node, type: 0 });
});
附:自定义右键菜单完整代码
这里需要对javascript的dom有点基础,不过这只是我创建dom的方法,如果你们想用其它的方法也是可以的哈
let divMenuContainer = null;
const createMenuDom = ({ x, y, node, edge, type }) => {
  if (divMenuContainer) {
    // 如果存在了菜单,就先移除再创建,不然你的页面上会多出来好多菜单的
    document.getElementById('container').removeChild(divMenuContainer);
  }
  divMenuContainer = document.createElement('div');
  divMenuContainer.setAttribute('class', 'div-menu-container');
  divMenuContainer.style.left = x + 30 + 'px';
  divMenuContainer.style.top = y + 'px';
  const divMenuItem = document.createElement('div');
  divMenuItem.setAttribute('class', 'div-menu-item');
  divMenuItem.innerText = type === 0 ? '删除节点' : '删除边';
  divMenuItem.addEventListener('click', () => {
    type === 0 ? graph.removeNode(node) : graph.removeEdge(edge);
    divMenuContainer.style.display = 'none';
  });
  divMenuContainer.appendChild(divMenuItem);
  document.getElementById('container').appendChild(divMenuContainer);
  document.body.addEventListener('click', () => {
    if (divMenuContainer) {
      divMenuContainer.style.display = 'none';
    }
  });
};
graph.on('node:contextmenu', ({ e, node }) => {
  // 坐标转换
  const pos = graph.clientToGraph(e.clientX, e.clientY);
  // 调用创建dom的方法,把坐标和节点信息传递进去
  createMenuDom({ x: pos.x, y: pos.y, node, type: 0 });
});
效果图
️ | 在历史记录中忽略某个属性的修改
常见问题
也是下面这个问题的解决方案
这是官方的demo
连线 undo - CodeSandbox
history插件配置
new Graph({
  history: {
    enabled: true,
    beforeAddCommand(event, args: any) {
      // 忽略历史变更
      if (args.options.ignoreHistory) {
        return false
      }
    },
  },
})
边的写法
graph.on('edge:connected', ({ edge }) => {
   // 传入自定义的 ignoreHistory 选项来忽略历史变更
   edge.attr('line/strokeDasharray', null, { ignoreHistory: true })
})
节点的写法
new History({
  enabled: true,
  beforeAddCommand(event, args) {
    if (args.options.ignoreHistory) {
      return false;
    }
  }
})
node.setData({ tableMeta: res.data.records, desc: res?.data?.records?.length || 0 }, { ignoreHistory: true });
️ | x和y坐标为字符串报错问题
解决方案
直接把x,y坐标转成纯数字的即可,不然拖动节点的时候会报错的
️ | 设置节点移动范围在画布内
new Graph({
  translating: {
  restrict: true
}
})
️ | 获取当前节点的所有父级节点
此方法会返回所有的输入和输出边,如果只要输入边的节点或者输出边的节点信息,请看这里链接
const getParentNodes = node => {
  const nodeId = node.id;
  const connectedNodes = [];
  // 如果需要其他方法,请看下方的具体配置,根据自己的需要修改这里的代码即可
  const edges = graph.getConnectedEdges(node, { deep: true });
  for (const edge of edges) {
    const sourceNode = edge.getSourceNode();
    const targetNode = edge.getTargetNode();
    if (sourceNode.id !== nodeId) {
      connectedNodes.push(sourceNode);
    }
    if (targetNode.id !== nodeId) {
      connectedNodes.push(targetNode);
    }
  }
  return connectedNodes;
};
// 具体的配置
const edges = graph.getConnectedEdges(node) // 返回输入和输出边
const edges = graph.getConnectedEdges(node, { incoming: true, outgoing: true }) // 返回输入和输出边
const edges = graph.getConnectedEdges(node, { incoming: true }) // 返回输入边
const edges = graph.getConnectedEdges(node, { incoming: true, outgoing: false }) // 返回输入边
const edges = graph.getConnectedEdges(node, { outgoing: true }) // 返回输出边
const edges = graph.getConnectedEdges(node, { incoming:false, outgoing: true }) // 返回输出边
const edges = graph.getConnectedEdges(node, { deep: true }) // 返回输入和输出边,包含链接到所有子孙节点/边的输入和输出边
const edges = graph.getConnectedEdges(node, { deep: true, incoming: true }) // 返回输入边,包含链接到所有子孙节点/边的输入边
const edges = graph.getConnectedEdges(node, { deep: true, enclosed: true }) // 返回输入和输出边,同时包含子孙节点/边之间相连的边
const edges = graph.getConnectedEdges(node, { indirect: true }) // 返回输入和输出边,包含间接连接的边
️ | 注册自定义节点报错?
报错信息
(in promise) Error: Node with name 'cu-port' already registered.
下面这个报错是群友发的,但是问题和我之前遇到的是一类问题,所以就直接告诉群友问题所在了
错误代码
Graph.registerNode(name,options)
正确代码
Graph.registerNode(name,options,true)
️ | 常用方法集锦
- 清空画布:graph.clearCells()
- 画布缩小:graph.zoom(-0.5)
- 获取画布缩放比例:graph.zoom()
- 获取画布上所有节点:graph.getNodes()
- 获取画布上所有边:graph.getEdges()
- 设置节点移动范围在画布内链接
translating: {
  restrict: true
},
graph.on('edge:connected', ({ isNew, edge, currentCell }) => {
  // 回调的参数:https://antv-x6.gitee.io/zh/docs/tutorial/intermediate/events/#%E8%BE%B9%E8%BF%9E%E6%8E%A5%E5%8F%96%E6%B6%88%E8%BF%9E%E6%8E%A5
  console.log('被连接的节点详细参数', currentCell);
  if (currentCell.data['type'] === 0) {
    proxy.$modal.msgError('数据源无法作为输出节点');
    // 移除连接的边
    graph.removeEdge(edge?.id);
  }
});
- 判断当前节点是否被连接链接
const node = graph.getCellById('node1')
const connectedEdges = graph.getConnectedEdges(node)
附:参考文档
Dnd插件
Stencil插件
图片导出
1.x常见问题
坐标转换
Transform
Model
「AntV」X6开发实践:踩过的坑与解决方案的更多相关文章
- "开发路上踩过的坑要一个个填起来————持续更新······(7月30日)"
		欢迎转载,请注明出处! https://gii16.github.io/learnmore/2016/07/29/problem.html 踩过的坑及解决方案记录在此篇博文中! 个人理解,如有偏颇,欢 ... 
- Dcloud开发webApp踩过的坑
		Dcloud开发webApp踩过的坑 一.总结 一句话总结:HTML5+扩展了JavaScript对象plus,使得js可以调用各种浏览器无法实现或实现不佳的系统能力,设备能力如摄像头.陀螺仪.文件系 ... 
- 转:Flutter开发中踩过的坑
		记录一下入手Flutter后实际开发中踩过的一些坑,这些坑希望后来者踩的越少越好.本文章默认读者已经掌握Flutter初步开发基础. 坑1问题:在debug模式下,App启动第一个页面会很慢,甚至是黑 ... 
- vue项目开发中踩过的坑
		一.路由 这两天移动端的同事在研究vue,跟我说看着我的项目做的,子路由访问的时候是空白的,我第一反应是,不会模块没加载进来吧,还是....此处省略一千字... 废话不多说上代码 路由代码 { pat ... 
- 「Ionic」设置开发环境
		轉載請一定註明地址:http://www.cnblogs.com/surge/p/5983024.html 謝謝! 濤叔是在mac環境下進行的,涉及android環境的配置不保證成功. 少废话,跟着濤 ... 
- vuejs  开发中踩到的坑
		用 v-for 循环式 每个item的值相等的情况下,会影响v-model的双向绑定: Modal 组件开发,主要用slot 标签来实现 <template> <transitio ... 
- 那些年,我们在Django web开发中踩过的坑(一)——神奇的‘/’与ajax+iframe上传
		一.上传图片并在前端展示 为了避免前端整体刷新,我们采用ajax+iframe(兼容所有浏览器)上传,这样用户上传之后就可以立即看到图片: 上传前: 上传后: 前端部分html: <form s ... 
- iphone 开发h5 踩过的坑
		html,body{ -webkit-text-size-adjust: none; } // 当需要在中文版chrome浏览器中显示小于12px的字体时,而且此时页面放大效果会被阻止 html,b ... 
- JavaScript 引擎「V8」发布 8.0 版本,内存占用量大幅下降
		上周,JavaScript 引擎「V8」的开发团队在该项目官方网站上正式宣布推出最新的 8.0 版本.这次更新的重点主要集中在错误修复及性能改善上,正式的版本将在数周后随着谷歌 Chrome 80 稳 ... 
- 【微信小程序】开发实战 之 「配置项」与「逻辑层」
		微信小程序作为微信生态重要的一环,在实际生活.工作.商业中的应用越来越广泛.想学习微信小程序开发的朋友也越来越多,本文将在小程序框架的基础上就微信小程序项目开发所必需的基础知识及语法特点进行了详细总结 ... 
随机推荐
- VUE2.x原理之Object.defineProperty()
			Object.defineProperty()语法说明 Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性 Object.defineP ... 
- Pause Giant AI Experiments: An Open Letter(暂停大型人工智能实验: 一封公开信)
			Pause Giant AI Experiments: An Open Letter(暂停大型人工智能实验: 一封公开信) 前几天在 futureoflife 网站上有一封公开信,呼吁暂停大型人工智能 ... 
- webrtc QOS笔记三 RTT计算,SRS增加XR
			webrtc QOS笔记三 RTT计算,SRS增加XR RTT计算方式 WebRTC中目前有两种方式计算RTT: 基于媒体流发送端的计算(默认开启).通过Sender Report(SR)与Recei ... 
- 电商平台趋势妙手采集类API接口
			电商平台趋势,平台化.大家可以看到大的电商都开始有自己的平台,其实这个道理很清楚,就是因为这是充分利用自己的流量.自己的商品和服务大效益化的一个过程,因为有平台,可以利用全社会的资源弥补自己商品的丰富 ... 
- JSTL标签fmt:formatDate格式化日期出错
			现象&背景: 异常: "org.apache.jasper.JasperException: 在 [115] 行处理 [/WEB-INF/jsp/modules/receivedya ... 
- 【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理
			ExceptionHandler的作用 ExceptionHandler是Spring框架提供的一个注解,用于处理应用程序中的异常.当应用程序中发生异常时,ExceptionHandler将优先地拦截 ... 
- Dokcer应用部署(搭建Wordpress网站)
			实现多个容器之间的协同,搭建Wordpress网站,要用到3个容器,Wordpress.MariaDB和Nginx 拉取镜像 使用docker pull拉取3个镜像: $ sudo docker pu ... 
- [C++基础入门] 1、C++初识
			文章目录 1 C++初识 1.1 第一个C++程序 1.1.1 创建项目 1.1.2 创建文件 1.1.3 编写代码 1.1.4 运行程序 1.2 注释 1.3 变量 1.4 常量 1.5 关键字 1 ... 
- 简单理解重载运算符&位运算
			重载运算符 作用 重载运算符的作用大致可以理解为自定义一个运算法则,比如当我们在使用结构体的时候,我们有时候会用到优先队列,但是优先队列并不能对于结构体使用,所以这个时候我们就需要用到重载运算符来自定 ... 
- 【解决方法】windows连接域时报错:An Active Directory Domain Controller(AD DC) for the domain“chinaskills.com“....
			目录-快速跳转 问题描述 原因分析: 解决方案: 附言: 问题描述 操作环境与场景: 在 VM 内 windos 2019 在连接到域时,提示报错: An Active Directory Domai ... 
