目录
极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图
极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图
Vue Flow的入门门槛要高不少,毕竟找不到中文文档,反正我是不太喜欢看纯英文的文档的。不过没关系,看完这篇还用不好Vue Flow请来找我麻烦。
一、环境要求
Node.js v20 或更高版本
Vue 3.3 或更高版本
前者,没有装nvm建议装一个nvm,noder没有nvm是行不通的。后者意味着Vue Flow不支持Vue2,据说有些辅助组件可以让部分功能在Vue中使用。
Tips:
如果把我的源码抄过去都跑不动,要么是依赖没有装好,要么是依赖的版本不对,Vue Flow的版本更迭是会伴随着API的更迭的,所以可以安装特定版本的Vue Flow来解决问题。我的Vue Flow版本是:v1.39.0
package.json:
"@vue-flow/background": "^1.3.0",
"@vue-flow/controls": "^1.1.2",
"@vue-flow/core": "^1.38.5",
"@vue-flow/minimap": "^1.5.0",
二、初识Vue Flow
2.1、安装Vue Flow
根据自己的包管理器安装
不习惯用add就用install,也没问题
npm add @vue-flow/core
pnpm add @vue-flow/core
yarn add @vue-flow/core
2.2、Vue Flow构成
在 Vue Flow 中,图由节点和边组成,每个节点或边都需要一个唯一的ID,节点还需要 XY 位置,而边需要 source 和 target 节点 ID,具体参照下方示例中的数据。
2.3、一个小坑
我最开始在官网上找案例的时候,发现除了教程案例以外的所有多组件案例都跑不动。不要只顾着怀疑自己,因为官网上的案例就是跑不动的。
原因在于:
为了确保 Vue Flow 正确显示,请确保包含必要的样式。
/* these are necessary styles for vue flow */
@import '@vue-flow/core/dist/style.css';
/* this contains the default theme, these are optional styles */
@import '@vue-flow/core/dist/theme-default.css';
是的,官网复杂案例全都没有添加这两行代码,估计是写文档的程序员被push的太狠了,抓紧写了几个案例就把代码放上来,没有意识到自己是全局引入(我猜的)了这两行代码,并且没有做任何提示。
但对于我们来说,只是为了画几个流程图的话,全局引入只会让代码变得冗余。局部引入就够了,把这两行放到style中官网的复杂案例就都可以执行了。
2.4、入门案例
请注意,这个代码确定能跑,如果跑不动请先检查依赖。
结构非常简单,定义了一个节点数据数组,一个连线数据数组,然后绑定给VueFlow组件即可。
-
<script setup>
-
import { ref }
from
"vue";
-
import {
VueFlow }
from
"@vue-flow/core";
-
-
const nodes =
ref([
-
// an input node, specified by using `type: 'input'`
-
{
-
id:
"1",
-
type:
"input",
-
position: {
x:
250,
y:
5 },
-
// all nodes can have a data object containing any data you want to pass to the node
-
// a label can property can be used for default nodes
-
data: {
label:
"Node 1" },
-
},
-
-
// default node, you can omit `type: 'default'` as it's the fallback type
-
{
-
id:
"2",
-
position: {
x:
100,
y:
100 },
-
data: {
label:
"Node 2" },
-
},
-
-
// An output node, specified by using `type: 'output'`
-
{
-
id:
"3",
-
type:
"output",
-
position: {
x:
400,
y:
200 },
-
data: {
label:
"Node 3" },
-
},
-
-
// this is a custom node
-
// we set it by using a custom type name we choose, in this example `special`
-
// the name can be freely chosen, there are no restrictions as long as it's a string
-
{
-
id:
"4",
-
type:
"special",
// <-- this is the custom node type name
-
position: {
x:
800,
y:
200 },
-
data: {
-
label:
"Node 4",
-
hello:
"world",
-
},
-
},
-
]);
-
-
const edges =
ref([
-
// default bezier edge
-
// consists of an edge id, source node id and target node id
-
{
-
id:
"e1->2",
-
source:
"1",
-
target:
"2",
-
},
-
-
// set `animated: true` to create an animated edge path
-
{
-
id:
"e2->3",
-
source:
"2",
-
target:
"3",
-
animated:
true,
-
},
-
-
// a custom edge, specified by using a custom type name
-
// we choose `type: 'special'` for this example
-
{
-
id:
"e3->4",
-
type:
"special",
-
source:
"3",
-
target:
"4",
-
-
// all edges can have a data object containing any data you want to pass to the edge
-
data: {
-
hello:
"world",
-
},
-
},
-
]);
-
</script>
-
-
<template>
-
<VueFlow :nodes="nodes" :edges="edges" style="height: 100vh; width: 100vw">
-
</VueFlow>
-
</template>
-
-
<style>
-
/* import the necessary styles for Vue Flow to work */
-
@import
"@vue-flow/core/dist/style.css";
-
-
/* import the default theme, this is optional but generally recommended */
-
@import
"@vue-flow/core/dist/theme-default.css";
-
</style>
三、Vue Flow优秀的自定义功能
3.1、引入
首先,预定义的配置项就不介绍了,上面的案例基本就是全部的预定义配置了,这一点和mermaid完全无法相比,但是你要用预定义的配置为什么不直接用mermaid呢?那个更简单清爽。
Vue Flow的特点就是,什么都可以要,什么都要自己写。
相比于mermaid来说,Vue Flow接受的数据更复杂冗长,并且预定义的内容极少,连默认的布局都没有(节点通过position控制位置,很容易重叠)。但是Vue Flow提供的自定义API非常丰富并且强大。
3.2、节点与连线的自定义
自定义三步走:打样取名做替换。
①打样(做模板)
做一个节点或者连线的模版,比如我需要一个长得像太阳的节点,那么我就写一个组件,然后把这个组件做成一个太阳的样子。
②模版取名
给自己做的模版取一个名字,比如就叫sun,然后将想要使用这个模版的节点的type属性全部改成"sun"。
③替换模版
在<VueFlow>标签之间添加如下代码,主要关注插槽名称“#node-sun”意味着所有type为sun的节点都使用这个插槽的模版。比如我设计了一种连线取名为darge,那么插槽名就是“#edge-darge”。
-
// 在script中引入
-
import sunNode
from
"./sunNode.vue";
-
-
<template>
-
<div class="layout-flow">
-
-
<VueFlow
-
:nodes=
"nodes"
-
:edges=
"edges"
-
@
nodes-initialized=
"layoutGraph('TB')"
-
>
-
<template #node-sun="props">
-
<div class="sunNode">
-
<span>{{ props.data.label }}
</span>
-
</div>
-
</template>
-
</VueFlow>
-
</div>
-
-
</template>
3.3、节点与连线的事件
该有的事件应有尽有,不得不佩服Vue Flow的完善与强大。
①节点事件
这是我比较建议的用法,比较简洁
-
<script setup>
-
import { ref }
from
'vue'
-
import {
VueFlow }
from
'@vue-flow/core'
-
-
const nodes =
ref([
-
{
-
id:
'1',
-
data: {
label:
'Node 1' },
-
position: {
x:
50,
y:
50 },
-
},
-
])
-
-
function
logEvent(
name, data) {
-
console.
log(name, data)
-
}
-
</script>
-
-
<template>
-
<!-- bind listeners to the event handlers -->
-
<VueFlow
-
:nodes=
"nodes"
-
@
node-drag-start=
"logEvent('drag start', $event)"
-
@
node-drag=
"logEvent('drag', $event)"
-
@
node-drag-stop=
"logEvent('drag stop', $event)"
-
@
node-click=
"logEvent('click', $event)"
-
@
node-double-click=
"logEvent('dblclick', $event)"
-
@
node-contextmenu=
"logEvent('contextmenu', $event)"
-
@
node-mouse-enter=
"logEvent('mouseenter', $event)"
-
@
node-mouse-leave=
"logEvent('mouseleave', $event)"
-
@
node-mouse-move=
"logEvent('mousemove', $event)"
-
/>
-
</template>
也可以使用useVueFlow这个API,将这些事件转化为钩子,我觉得优势不明显,写起来也冗余。
-
<script setup>
-
import { ref }
from
'vue'
-
import {
VueFlow, useVueFlow }
from
'@vue-flow/core'
-
-
// useVueFlow provides access to the event handlers
-
const {
-
onNodeDragStart,
-
onNodeDrag,
-
onNodeDragStop,
-
onNodeClick,
-
onNodeDoubleClick,
-
onNodeContextMenu,
-
onNodeMouseEnter,
-
onNodeMouseLeave,
-
onNodeMouseMove
-
} =
useVueFlow()
-
-
const nodes =
ref([
-
{
-
id:
'1',
-
data: {
label:
'Node 1' },
-
position: {
x:
50,
y:
50 },
-
},
-
])
-
-
// bind listeners to the event handlers
-
onNodeDragStart(
(event) => {
-
console.
log(
'Node drag started', event)
-
})
-
-
onNodeDrag(
(event) => {
-
console.
log(
'Node dragged', event)
-
})
-
-
onNodeDragStop(
(event) => {
-
console.
log(
'Node drag stopped', event)
-
})
-
-
// ... and so on
-
</script>
-
-
<template>
-
<VueFlow :nodes="nodes" />
-
</template>
②连线事件
连线事件也可以使用useVueFlow这个API,用法和上方一样,我就不赘述了,这里展示一下有哪些事件API。
-
<script setup>
-
import { ref }
from
'vue'
-
import {
VueFlow }
from
'@vue-flow/core'
-
-
const nodes =
ref([
-
{
-
id:
'1',
-
position: {
x:
50,
y:
50 },
-
data: {
label:
'Node 1', },
-
},
-
{
-
id:
'2',
-
position: {
x:
50,
y:
250 },
-
data: {
label:
'Node 2', },
-
},
-
])
-
-
const edges =
ref([
-
{
-
id:
'e1->2',
-
source:
'1',
-
target:
'2',
-
},
-
])
-
-
function
logEvent(
eventName, data) {
-
console.
log(eventName, data)
-
}
-
</script>
-
-
<template>
-
<VueFlow
-
:nodes=
"nodes"
-
:edges=
"edges"
-
@
edge-click=
"logEvent('edge clicked', $event)"
-
@
edge-double-click=
"logEvent('edge double clicked', $event)"
-
@
edge-context-menu=
"logEvent('edge context menu', $event)"
-
@
edge-mouse-enter=
"logEvent('edge mouse enter', $event)"
-
@
edge-mouse-leave=
"logEvent('edge mouse leave', $event)"
-
@
edge-mouse-move=
"logEvent('edge mouse move', $event)"
-
@
edge-update-start=
"logEvent('edge update start', $event)"
-
@
edge-update=
"logEvent('edge update', $event)"
-
@
edge-update-end=
"logEvent('edge update end', $event)"
-
/>
-
</template>
3.4、句柄(handles)
句柄是通常放置在节点边界上的小圆圈。它们用于通过将连接线从一个手柄拖动到另一个手柄来将节点连接在一起,从而在节点之间形成连接(边缘)。句柄是 VueFlow 的重要组成部分,因为它们是用户在节点之间创建边的主要交互点。如果没有句柄,基本上不可能在节点之间创建边,因为句柄用于计算边的源点和目标点。
简单来说,就是两个节点连接需要通过句柄,如果没有句柄就无法连接。句柄就是连线两端的端点。常用在自定义节点的组件中,用来指定连线的起始点。
句柄handdle需要设置两个属性,一个是类型(source/target),一个是位置(Top、Right、Bottom、Left),示例如下:
-
<script setup>
-
import {
Handle }
from
'@vue-flow/core'
-
-
defineProps([
'id',
'sourcePosition',
'targetPosition',
'data'])
-
</script>
-
-
<template>
-
<Handle type="source" :position="sourcePosition" />
-
-
<span>{{ data.label }}
</span>
-
-
<Handle type="target" :position="targetPosition" />
-
</template>
四、一个优质的案例
案例具体代码请访问:Layouting | Vue Flow
将所有组件代码复制到您的项目中即可,请注意相对路径关系,如果您没有全局引入2.3部分的内容,请在style中添加:
-
<style>
-
@
import
"@vue-flow/core/dist/style.css";
-
@
import
"@vue-flow/core/dist/theme-default.css";
-
-
-
// ...其他的css样式
-
</style>
五、总结
相比于mermaid,Vue Flow将注意力更多的转到灵活自定义的接口而不是大量的预定义模版,“什么都可以有,什么都要自己写。”是Vue Flow的典型特征。如果您想发挥自己天马行空的想象力,或者满足复杂的项目流程需求,Vue Flow一定能满足您的预期。
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~