vue-flow绘制流程图

1. 实现效果

  • 顶端左侧有Tab栏,通过拖拽添加节点
  • 底端左侧有工具栏
  • 底端右侧有MiniMap,显示全局节点样式
    在这里插入图片描述

2. 背景

根据项目需求,需要实现类似Visio的效果,从Tab栏拖拽节点到画布中,绘制不同类型的节点,并连线。

技术调研(仅个人看法)
  1. Antv G6:适合图结构(包括节点布局、节点个数)没有太大变化,比如树图,可以绘制动态线条、多种节点,增加图的动态效果。
  2. Antv X6:适合位置确定、节点确定的流程图,比如DAG图、固定流程的流程图,增加节点状态、边的动画效果,显示图的灵动性。
  3. Echarts:适合折线图、散点图、柱状图等简单图形。
  4. vue-flow:(vue-flow官网):实现类似visio的效果,通过拖拽在画布中添加节点,可整体拖拽、放大和缩小,自动适应画布并居中等功能。
  • 个人使用感受优缺点对比:
    • Antv G6、X6、Echarts技术成熟,适合vue2/3,官方文档是中文的,容易阅读;vue-flow是2022年发布的,只适用vue3,官方文档只有英文版且比较简单,需要结合文档Guide和Example,常常指南中找不到,需要在样例中找类似效果,学习实现方式。
    • Antv G6、X6常常只画一个图就会觉得比较卡顿,而且每次重新绘制都需要清空画布;vue-flow绘图十分流畅,且每次只更新图谱数据,不需要清空画布。
    • Antv G6、X6,vue-flow都允许自定义节点和边,样式灵活多样,更适合开发。

使用心得

1. 快速开始

  1. 依赖安装:
npm install @vue-flow/core
npm install @vue-flow/background 	 //背景
npm install @vue-flow/minimap  		 //小地图
npm install @vue-flow/controls		 //自带的缩放、居中、加锁功能
npm install @vue-flow/node-toolbar   //工具栏
npm install @vue-flow/node-resizer   //缩放
  1. 样式导入:全局样式文件style.scss中导入
@import "@vue-flow/core/dist/style.css";
@import "@vue-flow/core/dist/theme-default.css";
@import '@vue-flow/controls/dist/style.css';
@import '@vue-flow/minimap/dist/style.css';

依赖安装后就可以在页面中导入使用了

2. 基本工具栏

template

  • 加载基本组件: Background 、Controls、MiniMap ,分别是背景、左下缩放工具栏、右下地图,这些位置都是默认的。
  • 要在画布上显示的内容,都要放在 <VueFlow>标签内
<template>
  <VueFlow>   
    <Background />
    <Controls />
    <MiniMap />
</VueFlow>

script

import { VueFlow, Position, Panel, useVueFlow, MarkerType } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import { MiniMap } from '@vue-flow/minimap'

3. 加载节点和边,绘图

(1) 不指定节点和边的样式,就是加载默认节点和边

template

  • nodes绑定节点;edges绑定边
  • nodes中节点属性必须有id唯一标识,position记录位置
  • edges中边的属性必须有id唯一标识,sourcetarget分别记录边的源节点和目的节点
<VueFlow
      :nodes="chatNodes"
      :edges="chatEdges"
      fit-view-on-init
      class="flowchat-container"
    >
</VueFlow>

style:定义vue-flow画布的宽高和位置

.flowchat-container {
  width: 100%;
  height: calc(100% - 50px);
  position: absolute;
  top: 50px;
  padding: 20px 0;
}

节点数据结构

//节点
const nodes = ref([
  {
    id: '1',
    position: { x: 50, y: 50 },
    data: { label: 'Node 1', },
  }])
//边
const edges = ref([
  {
    id: 'e1->2',
    source: '1',
    target: '2',
  }
])
(2)自定义节点(官网Examples/CustomeNode

index.vue

  • #node-custom#node-后的内容是根据自定义节点页面的名称来定义的,比如如果节点页面命名为ColorSelectorNode,那么这里定义就是#node-color-selector="props"
  • 所以自定义节点页面名称一定是xxxNode.vue,才能根据#node-xxx加载相应页面的效果。
  • 边的命名同理
<VueFlow :nodes="chatNodes" :edges="chatEdges"  fit-view-on-init>
	<template #node-custom="props">
        <CustomNode v-bind="props"></CustomNode >
    </template>
</VueFlow>

import CustomNode from './CustomNode.vue'

customNode.vue:按照普通vue界面开发即可,自定义节点样式和内容

<template>
  <div>Select a color</div>
  <Handle id="a" type="source" :position="Position.Right" />
</template>
(3)自定义边(官网Examples/customEdges
  • 命名方式同上

  • EdgeWithButton是可以×掉的边,具体实现参考样例中边的实现方式,在样例中给出了多种边的类型,可以根据需求选择不同的效果,多种效果如下
    在这里插入图片描述

  • 在定义边的数据时,需要加一个type: 'button'属性,才能识别出EdgeWithButton定义的边的效果

<VueFlow :nodes="chatNodes" :edges="chatEdges"  fit-view-on-init>
	<template #edge-button="props">
        <EdgeWithButton v-bind="props"  />
   </template>
</VueFlow>
import EdgeWithButton from './edgeWithButton.vue'

script中添加边

  • id命名方式:按照如下方式定义
  • source/target:源点/终点
  • sourceHandle/targetHandle:源点/终点句柄
  • markerEnd:是否显示箭头
  • type:边的类型,按照官网样例中的写法来写,就能被识别
//新建连接,添加边
onConnect((params) => {
  chatEdges.value.push({
    id: 'vueflow__edge-' + params.source + params.sourceHandle + '-' + params.target + params.targetHandle,
    source: params.source,
    sourceHandle: params.sourceHandle,
    target: params.target,
    targetHandle: params.targetHandle,
    markerEnd: MarkerType.ArrowClosed,
    type: 'button',
  })
})

4. 加载句柄handle,用于节点连线

(1) 加载固定句柄
  • id必须唯一
  • type标记source/target
  • position标记位置,默认四个方位Top、Bottom、Right、Left
import { Position, Handle} from '@vue-flow/core'
<Handle id="source_id_a" type="source" :position="Position.Right" />
<Handle  id="target_id_a" type="target" :position="Position.Left" />
(2)动态生成句柄、并计算位置
  • 背景:需要开发分支节点,随着动态添加分支条件,动态添加句柄并定义位置,使得每个分支条件有个与之对齐的handle,效果如下:
    在这里插入图片描述
  • 实现方式
    • template

      1. position指定handle在节点的哪一侧
      2. style动态计算每个handle距离顶部的距离
      3. 每个handledid要唯一,目前发现只有:id="'right_'+index"可以正常显示,比如写成:id="item.name"是显示不出来的,没有找到原因┭┮﹏┭┮
      <Handle
      v-for="(item, index) in conditions"
        :position="Position.Right"
        type="source"
        :id="'right_'+index"
        :style="getDynamicHandlePos(item,index)"
      >
      </Handle>
      
      
    • script实现

      1. 函数中 *16-8这些数值是根据节点高度尝试出来的,不是固定的。
      2. 如果是计算bottom,一定要加上top:auto属性,否则可能会不显示或者是出错。
      3. conditions.length - index计算的原因:第一个条件应该距离底部最远,最后一个距离最近,如果直接index计算,那么if对应的handle在最底部,else对应的handle在最顶部,刚好弄反了。
      import { Position, Handle } from '@vue-flow/core'
      const getDynamicHandlePos = (item, index: number) => {
        return {
          bottom: `${(conditions.length - index) * 16 - 8}px`,
          top: 'auto',
        }
      }
      

5. Panel:定义面板

自定义面板显示信息,比如节点详情、菜单栏面板等信息,需要使用<Panel>标签
template:写在<VueFlow>标签内

<template>
	<VueFlow>
		<Panel position="top-left">
          <div class="panel-title">
            基础配置
          </div>
          <div class="icon-text" :draggable="true" @dragstart="onDragStart($event,'HTTP')">
            <svg-icon icon-class="cloud" />
            <span>HTTP节点</span>
          </div>
          ......   //面板详情信息
      </Panel>
	</VueFlow>
</template>

style:定义position是生效的。设置animation也是有效的,普通div的css写法完全生效。

.top-left{
	position: absolute;
  right: 0;
  top: 3%;
  width: 100px;
 }

6. 从面板拖拽,添加节点

拖拽功能,实现参考:节点拖拽

  • 最外层div上绑定@drop
  • VueFlow上绑定@dragover、@dragleave,这些顺序不可以调换
  • 最关键的函数实现在@drop@dragover、@dragleave写法参考官网,基本无需任何改动
<template>
  <div class="dnd-flow" @drop="onDrop">
    <VueFlow :nodes="nodes" @dragover="onDragOver" @dragleave="onDragLeave">
    </VueFlow>
  </div>
</template>

const { onDragOver, onDrop, onDragLeave, isDragOver } = useDragAndDrop()

script:@drop="onDrop"函数

  • nodeId:可以使用uuid方式,唯一标识节点(百度可查uuid具体函数)
  • event:可以提供节点位置,
  • newNode:结合自己的项目构造节点具体信息,其中type是自己定义的类型,onDragStart 中会有定义,若没有自定义就是默认类型
const onDrop = (event)  => {
    const position = screenToFlowCoordinate({
      x: event.clientX,
      y: event.clientY,
    })

    const nodeId = getId()   
    const newNode = {
      id: nodeId,
      type: draggedType.value,
      position,
      data: { label: nodeId },
    }
    addNodes(newNode)
  }
  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值