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开发体验比较好一些,虽然文档是英文,有例子使用起来还是很方便。