基于 vue3.x 的流程图绘制( 二 )

) => {commonRequest(`/workflow/getWfProcess/${processId}`).then((res: Record) => {// 调整属性,这里是为了当配置列表的节点或者属性有更新,从而更新已配置的节点的属性adjustProps(props, res.result.processJson)// 设置已配置好的数据,并渲染workflowRef.value?.setData(res.result.processJson, res.result.type || 'add')})}const init = () => {if (!processId) {ElMessageBox.alert('当前没有流程id')return}getConfig()}init()const transferOptions = (nodes: Record[], props: Record) => {nodes?.forEach((node: Record) => {props[node.name] = node.props})}const adjustProps = (props: Record, nodes: Record[]) => {nodes.forEach((node: Record) => {const oldProp: Record[] = node.propsconst res = transferKV(oldProp)node.props = JSON.parse(JSON.stringify(props[node.name]))node.props.forEach((prop: Record) => {prop.value = res[prop.name]})})}const transferKV = (props: Record[]) => {const res: Record = {}props.forEach((prop: Record) => {res[prop.name] = prop.value})return res}复制代码节点的拖拽与渲染及连接线的绘制关于节点的拖拽就不多说了,就是 drag 相关的用法,主要是渲染区域的节点和连接线的设计 。
这里的渲染区域的思路是:以 canvas 元素作为画布背景,节点是以 div 的方式渲染拖拽进去的节点,拖拽的位置将是以 canvas 的相对位置来移动,大概的结构如下:
<template><!-- 渲染区域的祖先元素 --><div><!-- canvas 画布,绝对于父级元素定位,inset: 0; --><canvas></canvas><!-- 节点列表渲染的父级元素,绝对于父级元素定位,inset: 0; --><div><!-- 节点1,绝对于父级元素定位 --><div></div><!-- 节点2,绝对于父级元素定位 --><div></div><!-- 节点3,绝对于父级元素定位 --><div></div><!-- 节点4,绝对于父级元素定位 --><div></div></div></div></template>复制代码而连接线的绘制是根据 next 字段的信息,查找到 targetComponentId 组件的位置,然后在canvas上做两点间的 线条绘制 。
链接的类型分为3种: 直线,折线,曲线

  • 直线
直线的绘制最为简单,取两个点连接就行 。
// 绘制直线const drawStraightLine = (ctx: CanvasRenderingContext2D,points: [number, number][],highlight?: boolean) => {ctx.beginPath()ctx.moveTo(points[0][0], points[0][1])ctx.lineTo(points[1][0], points[1][1])// 是否是当前选中的连接线,当前连接线高亮shadowLine(ctx, highlight)ctx.stroke()ctx.restore()ctx.closePath()}复制代码
基于 vue3.x 的流程图绘制

文章插图
 
  • 折线
折线的方式比较复杂,因为折线需要尽可能的不要把连接线和节点重合,所以它要判断每一种连接线的场景,还有两个节点的宽度和高度也需要考虑计算 。如下:
基于 vue3.x 的流程图绘制

文章插图
 
起始节点有四个方向,目标节点也有四个方向,还有目标节点相对于起始节点有四个象限,所以严格来说,总共有 4 * 4 * 4 = 64 种场景 。这些场景中的折线点也不一样,最多的有 4 次,最少的折 0 次,单求出这 64 种坐标点就用了 700 行代码 。
基于 vue3.x 的流程图绘制

文章插图
 
最后的绘制方法与直线一样:
// 绘制折线const drawBrokenLine = ({ ctx, points }: WF.DrawLineType, highlight?: boolean) => {ctx.beginPath()ctx.moveTo(points[0][0], points[0][1])for (let i = 1; i < points.length; i++) {ctx.lineTo(points[i][0], points[i][1])}shadowLine(ctx, highlight)ctx.stroke()ctx.restore()ctx.closePath()}复制代码
  • 曲线
曲线相对于折线来说,思路会简单很多,不需要考虑折线这么多场景 。
基于 vue3.x 的流程图绘制

文章插图
 
这里的折线是用三阶的贝塞尔曲线来绘制的,固定的取四个点,两个起止点,两个控制点,其中两个起止点是固定的,我们只需要求出两个控制点的坐标即可 。这里代码不多,可以直接贴出来:
/** * Description: 计算三阶贝塞尔曲线的坐标 */import WF from '../type'const coeff = 0.5export default function calcBezierPoints({ startDire, startx, starty, destDire, destx, desty }: WF.CalcBezierType,points: [number, number][]) {const p = Math.max(Math.abs(destx - startx), Math.abs(desty - starty)) * coeffswitch (startDire) {case 'down':points.push([startx, starty + p])breakcase 'up':points.push([startx, starty - p])breakcase 'left':points.push([startx - p, starty])breakcase 'right':points.push([startx + p, starty])break// no default}switch (destDire) {case 'down':points.push([destx, desty + p])breakcase 'up':points.push([destx, desty - p])breakcase 'left':points.push([destx - p, desty])breakcase 'right':points.push([destx + p, desty])break// no default}}复制代码


推荐阅读