Vue Flow
Vue Flow 是通往交互式流程图和图形世界的桥梁,使您能够为流程图和图形表示带来动态性和交互性。无论是制作个人图表、生成动态编辑器,还是您想象出的任何其他事情,Vue Flow 都是您的创意伙伴。
Vue Flow 允许集成您自己的定制节点和边缘,从而可以毫不费力地定制和扩展基本功能。背景、小地图和控件等其他组件进一步丰富了界面,将您的作品转变为引人入胜的平台。
主要特点
-
无缝设置
Vue Flow 让你快速进入行动。凭借元素拖动、缩放和平移以及选择等内置功能,Vue Flow 开箱即用。 -
定制
Vue Flow 是你来塑造的。从自定义节点和边缘到连接线,您可以扩展 Vue Flow 的功能以满足您的创意需求。 -
高效且响应迅速
Vue Flow 会做出反应性地跟踪更改,确保只重新渲染必要的元素。 -
实用程序和可组合性
Vue Flow 专为复杂用途而设计,具有内置的图形助手和状态可组合函数
安装
vite 版本最低 3.0.9 node 版本 18.2.0
pnpm install @vue-flow/core@1.29.2
pnpm install @vue-flow/controls@1.1.0
pnpm install @vue-flow/background@1.2.0
注意事项为了确保 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';
Basic–官方基本使用
<script setup lang="ts">
import { ref } from 'vue';
import type { Node, Edge } from '@vue-flow/core';
import { VueFlow } from '@vue-flow/core';
// these components are only shown as examples of how to use a custom node or edge
// you can find many examples of how to create these custom components in the examples page of the docs
import SpecialNode from './components/SpecialNode.vue';
import SpecialEdge from './components/SpecialEdge.vue';
// these are our nodes
const nodes = ref<Node[]>([
// 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: 400, y: 200 },
data: {
label: 'Node 4',
hello: 'world',
},
},
]);
// these are our edges
const edges = ref<Edge[]>([
// 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">
<!-- bind your custom node type to a component by using slots, slot names are always `node-<type>` -->
<template #node-special="specialNodeProps">
<SpecialNode v-bind="specialNodeProps" />
</template>
<!-- bind your custom edge type to a component by using slots, slot names are always `edge-<type>` -->
<template #edge-special="specialEdgeProps">
<SpecialEdge v-bind="specialEdgeProps" />
</template>
</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';
@import '@vue-flow/controls/dist/style.css';
</style>
Custom–一些自定义用法
自己写一个页面效果
默认需要拖拽的内容,因为是动态数据,有数据的时候才让其进行操作 Main.vue
<CustomVueFlow :nodeData="vueFlowData.nodes" :edgeData="vueFlowData.edges" />
<div
:style="`cursor:${!!isEmptyBankAccountData ? 'not-allowed' : 'grab'}`"
class="basic-flow-node"
:draggable="!isEmptyBankAccountData"
@dragstart="handleOnDragStart($event, 'allot')"
>
<PlusOutlined
class="basic-flow-add"
:style="`cursor:${!!isEmptyBankAccountData ? 'not-allowed' : 'grab'}`"
/>
<Descriptions
title="银行账户信息"
:style="`cursor:${!!isEmptyBankAccountData ? 'not-allowed' : 'grab'}`"
>
<DescriptionsItem label="公司名称" :span="3">{{ bankAccountData.orgName }}</DescriptionsItem>
</Descriptions>
</div>
// 开始拖拽
function handleOnDragStart(event: DragEvent, nodeType: any) {
if (event.dataTransfer) {
event.dataTransfer.setData(
'application/vueflow',
JSON.stringify({
nodeType,
nodeData: bankAccountData.value,
}),
);
event.dataTransfer.effectAllowed = 'move';
}
}
内部使用 vue-flow 的组件 CustomVueFlow.vue
<script setup lang="ts">
import { markRaw, nextTick, ref, watch } from 'vue';
import { VueFlow, useVueFlow, Panel } from '@vue-flow/core';
import { Background } from '@vue-flow/background';
import { Controls } from '@vue-flow/controls';
import type { Dimensions, Elements } from '@vue-flow/core';
import CustomFlowNode from './CustomFlowNode.vue';
import CustomFlowEdge from './CustomFlowEdge.vue';
import CustomFlowLine from './CustomFlowLine.vue';
// CustomFlow动态父节点数据
const propsFlow = defineProps({
nodeData: {
type: Array,
required: true,
},
edgeData: {
type: Array,
required: true,
},
});
// 需要自定义固定内容时可以使用
const elements = ref<Elements>();
// 自定义节点的类型
const nodeTypes = {
allot: markRaw(AllotFlowNode),// 自定义节点
};
// vue-flow提供的hook函数
const { findNode, nodes, edges, addNodes, addEdges, project, vueFlowRef, onConnect } =
useVueFlow();
// 两个节点连接时进行校验
onConnect((params) => {
(params.type = 'custom'), (params.animated = false);
addEdges(params);
});
// 当拖拽至Background背景中时解析数据添加节点
function handleOnDrop(event: DragEvent) {
const nodeJsonObj = event.dataTransfer?.getData('application/vueflow');
const { nodeType, nodeData } = JSON.parse(nodeJsonObj);
const { left, top } = vueFlowRef.value!.getBoundingClientRect();
const position = project({ x: event.clientX - left, y: event.clientY - top });
const newNode = {
id: ,
type: nodeType,
position,
data: { ...nodeData, edgeShow: true }, // edgeShow默认展示
};
addNodes([newNode]);
}
// 拖拽结束
function handleOnDragOver(event: DragEvent) {
event.preventDefault();
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move';
}
}
</script>
<template>
<div class="relative h-full" id="main-canvas" @drop="handleOnDrop" @dragover="handleOnDragOver">
<VueFlow :nodes="propsFlow.nodeData" :edges="propsFlow.edgeData" :node-types="nodeTypes">
<Controls />
<Background />
<template #connection-line="{ sourceX, sourceY, targetX, targetY }">
<CustomFlowLine
:source-x="sourceX"
:source-y="sourceY"
:target-x="targetX"
:target-y="targetY"
/>
</template>
<template #edge-custom="props">
<CustomFlowEdge v-bind="props" />
</template>
</VueFlow>
</div>
</template>
<style>
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
@import '@vue-flow/controls/dist/style.css';
.vue-flow__handle {
width: 18px;
height: 18px;
background-color: rgb(37, 99, 235);
}
</style>
自定义 CustomFlowLine.vue
<script setup>
defineProps({
sourceX: {
type: Number,
required: true,
},
sourceY: {
type: Number,
required: true,
},
targetX: {
type: Number,
required: true,
},
targetY: {
type: Number,
required: true,
},
});
</script>
<template>
<g>
<path
class="vue-flow__connection animated"
fill="none"
stroke="#6F3381"
:stroke-width="2.5"
:d="`M${sourceX},${sourceY} C ${sourceX} ${targetY} ${sourceX} ${targetY} ${targetX},${targetY}`"
/>
<circle :cx="targetX" :cy="targetY" fill="#fff" :r="4" stroke="#6F3381" :stroke-width="1.5" />
</g>
</template>
自定义 CustomFlowEdge.vue
<script lang="ts" setup>
import type { EdgeProps, Position } from '@vue-flow/core'
import { EdgeLabelRenderer, getBezierPath, useVueFlow } from '@vue-flow/core'
import type { CSSProperties } from 'vue'
interface CustomEdgeProps<T = any> extends EdgeProps<T> {
id: string
sourceX: number
sourceY: number
targetX: number
targetY: number
sourcePosition: Position
targetPosition: Position
data: T
markerEnd: string
style?: CSSProperties
}
const props = defineProps<CustomEdgeProps>()
const { removeEdges } = useVueFlow()
const path = computed(() => getBezierPath(props))
</script>
<script lang="ts">
export default {
inheritAttrs: false,
}
</script>
<template>
<path :id="id" :style="style" class="vue-flow__edge-path" :d="path[0]" :marker-end="markerEnd" />
<EdgeLabelRenderer>
<div
:style="{
pointerEvents: 'all',
position: 'absolute',
transform: `translate(-50%, -50%) translate(${path[1]}px,${path[2]}px)`,
}"
class="nodrag nopan"
>
<button class="edgebutton" @click="removeEdges(id)">×</button>
</div>
</EdgeLabelRenderer>
</template>
<style>
.edgebutton {
border-radius: 999px;
cursor: pointer;
}
.edgebutton:hover {
box-shadow: 0 0 0 2px pink, 0 0 0 4px #f05f75;
}
</style>
自定义 CustomFlowNode.vue
<template>
<div class="allot-flow-node rounded-sm border border-gray-200 bg-white p-3 shadow-md">
<Handle type="target" :position="Position.Left" />
<div class="close-icon" @click="handleClickDeleteBtn">
<CloseOutlined />
</div>
<div class="flex flex-col">
<div class="text-xl font-semibold ml-2.5">公司{{ bankAccountData.xx }}</div>
<div class="flex">
<div class="w-20 text-right">XX:</div>
<div class="flex-1">{{ bankAccountData.xx }}</div>
</div>
</div>
<Handle type="source" :position="Position.Right" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, onUnmounted } from 'vue';
import { message, Select, Descriptions, DescriptionsItem } from 'ant-design-vue';
import { CloseOutlined } from '@ant-design/icons-vue';
import { Handle, Position, useVueFlow, useNode } from '@vue-flow/core';
import type { NodeProps } from '@vue-flow/core';
interface AllotNodeData {}
interface AllotNodeEvents {}
interface BankAccountData {
xx: string;
}
const props = defineProps({
data: {
type: Object as PropType<AllotNodeData>,
required: true,
},
events: {
type: Object as PropType<AllotNodeEvents>,
default: () => ({}),
},
});
const bankAccountData = ref<BankAccountData>({
xx: '',
});
const node = useNode();
const { removeNodes, nodes, edges, addNodes } = useVueFlow();
// 删除节点
function handleClickDeleteBtn() {
removeNodes(node.id);
}
onMounted(() => {
bankAccountData.value = props.data;
});
onUnmounted(() => {
bankAccountData.value = {};
});
</script>
<style lang="less" scoped>
.allot-flow-node {
position: relative;
width: 300px;
}
.close-icon {
position: absolute;
top: 6px;
right: 10px;
width: 32px;
height: 32px;
text-align: right;
cursor: pointer;
color: #ccc;
}
</style>
官方4个例子,其他更多去官方文档看看,地址在文章最底部
-
布局
-
拖拽
-
自定义节点
-
边
使用体会
最主要的 **findNode, nodes, edges, addNodes, addEdges,**这几个 hook 函数的使用在自定义 edge 中还可以加入表单使用,数据只需要加入节点中的 data 使用就可以了因为 vue-flow 内部的数据就处理的可以了,进行数据交互就没有使用 vuex 或者 pinia,通过 props 进行数据交互就可以了。
要使用 pinia 也可以,插件有使用现有的存储,通过 pinia 来存储我们的元素,更新它们的位置并切换类 😊Pinia Store 用例 😀 vue-flow 官方文档
扩展
还有一些插件,像echart、d3、logicflow、relation-graph/vue3、g、g6、x6、reactflow都去看过,因为使用的技术栈是vue,需要自定义线节点,感觉vue-flow开发体验比较好一些,虽然文档是英文,有例子使用起来还是很方便。