目录
极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图
一、环境要求
二、初识Vue Flow
2.1、安装Vue Flow
2.2、Vue Flow构成
2.3、一个小坑
2.4、入门案例
三、Vue Flow优秀的自定义功能
3.1、引入
3.2、节点与连线的自定义
①打样(做模板)
②模版取名
③替换模版
3.3、节点与连线的事件
①节点事件
②连线事件
3.4、句柄(handles)
四、一个优质的案例
五、总结
极致的灵活度满足工程美学:用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一定能满足您的预期。
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
更多优质内容,请关注:
JS语法与Vue开发:
Vue 性能革命:揭秘前端优化的终极技巧
属性描述符初探——Vue实现数据劫持的基础
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
最细最有条理解析:事件循环(消息循环)是什么?进程与线程的定义、关系与差异
路由通配符,小小的字符有大大的作用,你真的熟悉吗?
管理数据必备!侦听器watch用法详解
什么是深拷贝?深拷贝和浅拷贝有什么区别
对象数据的读取,看这一篇就够了!
通过array.every()实现数据验证、权限检查和一致性检查,array.some与array.every的区别
通过array.some()实现权限检查、表单验证、库存管理、内容审查和数据处理
通过array.map()实现数据转换、创建派生数组、异步数据流处理、搜索和过滤等需求
通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式等
通过array.filter()实现数组的数据筛选、数据清洗和链式调用
多维数组操作,不要再用遍历循环foreach了,来试试数组展平的小妙招!
别再用双层遍历循环来做新旧数组对比,寻找新增元素了!
shpfile转GeoJSON且控制转化精度;如何获取GeoJSON?GeoJson结构详解
Mapbox添加行政区矢量图层、分级设色图层、自定义鼠标悬浮框、添加天地图底图等
Element plus拓展:
通过el-tree自定义渲染网页版工作目录,实现鼠标悬浮显示完整名称等
el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能
el-table中如何添加渐变色带、多色色带