Vue3 流程图组件库 :Vue Flow

32,460次阅读
没有评论

共计 19098 个字符,预计需要花费 48 分钟才能阅读完成。

Vue Flow 是一个轻量级的 Vue 3 组件库,它允许开发者以简洁直观的方式创建动态流程图。本篇文章记录一下 Vue Flow 的基本用法


安装


npm add @vue-flow/core

流程图的构成

Nodes、Edges、Handles

主题

默认样式

通过导入样式文件应用


/* 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';

对默认主题进行调整

1. 可以使用 css 类名去覆盖


.vue-flow__node-custom {
        background: purple;
        color: white;
        border: 1px solid purple;
        border-radius: 4px;
        box-shadow: 0 0 0 1px purple;
        padding: 8px;
    }
2. 可以在组件上使用 style 或者 class 属性进行替换


3. 通过在全局的 css 文件中对组件的样式变量进行覆盖


:root {
        --vf-node-bg: #fff;
        --vf-node-text: #222;
        --vf-connection-path: #b1b1b7;
        --vf-handle: #555;
    }

具体的 css 类名和变量名可以通过查阅官方文档确认  Theming | Vue Flow

Nodes

Nodes 是流程图中的一个基本组件,可以在图表中可视化任何类型的数据,独立存在并通过 edges 互连从而创建数据映射

1. 展示节点

节点的渲染是通过给 VueFlow 组件的 nodes 参数传入一个数组实现的




  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

2. 节点的增删

对于节点的增加和删除,我们可以通过直接改变 nodes 参数来实现,也可以使用  useVueFlow 提供的方法  addNodes 和  removeNodes 直接改变组件内部的状态实现

3. 节点的更新

节点的更新同样可以使用改变 nodes 参数来实现,也可以使用 useVueFlow 得到的实例 instance 上的 updateNodeData,传入对应组件的 id 和数据对象来更新;


instance.updateNode(nodeId, { selectable: false, draggable: false})

通过对实例的 findNode 方法拿到的节点实例直接修改组件 state 同样能够起到更新节点的效果


const node = instance.findNode(nodeId) 
node.data = {...node.data, hello: 'world',}

4. 节点的类型

节点的类型通过 nodes 数组中对应节点项的 type 属性确定

默认节点 (type:’default’)

Vue3 流程图组件库:Vue Flow

input 节点 (type:’input’)

Vue3 流程图组件库:Vue Flow

output 节点 (type:’output’)

Vue3 流程图组件库:Vue Flow

自定义节点 (type:’custom’, type:’special’,…)

除了默认的节点类型,用户也可以创建自定义的节点类型

模板插槽模式







  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.

在配置了自定义组件后,VueFlow 会将节点类型字段和插槽名字进行动态匹配,从而正确渲染。

Node-types 对象模式

直接将引入的组件对象通过 VueFlow 的 nodeTypes 参数传入,需要注意的是要去除组件对象的响应式




  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
节点事件

参考: Nodes | Vue Flow

Edges

Edges 就是节点之间的连线部分,每一条连线都是从一个 handle 到另一个 handle,其拥有独立的 id;

展示 Edges

Edges 的渲染是通过给 VueFlow 组件的 edges 参数传入一个数组实现的,需要配合 nodes 一起确定节点之间的连线关系;




  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

增删和更新 Edges

和节点的类似,可以通过直接改变 edges 传参实现,同时 useVueFlow 也提供了对 Edges 的操作方法 [addEdges],( vueflow.dev/typedocs/in…)  removeEdges

Edges 的更新

同样和节点类型类似,可以通过 useVueFlow 拿到实例,使用实例的 updateEdgeData 方法进行更新,也可以使用 findEdge 拿到的 edge 直接修改对应的 state 进行更新


instance.updateEdgeData(edgeId, { hello: 'mona'}) 
edge.data = {...edge.data, hello: 'world',}

Edges 类型

默认连线 (type:’default’)

Vue3 流程图组件库:Vue Flow

阶梯连线 (type:’step’)

Vue3 流程图组件库:Vue Flow

直线连接 (type:’straight’)

Vue3 流程图组件库:Vue Flow

自定义连接

用法和自定义节点类似,只是插槽名变为 edge- 开头,参数名由 nodeTypes 变为 edgeTypes

edge 事件

参考: Edges | Vue Flow

Handles

节点边缘上的小圆圈,使用拖拽的方式进行节点之间的连接

使用 Handle

Handle 是以组件的方式在节点中引入的




  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

Handle 位置

可以通过 Handle 组件的 position 参数来调整其位置


 

多个 Handle 使用时需要注意组件需要有唯一 id


 

多个 Handle 在同一侧时需要手动调整位置防止重叠


 

Handle 的隐藏

需要使用样式 opacity, 不能使用 v -if 和 v -show


是否限制连接可以使用 Handle 组件的 connectable 参数,传入一个布尔值


连接模式


配置了 ConnectionMode.Strict 后只允许在相同类型的 Handle 之间进行连接

动态位置

在需要动态处理 Handle 的位置时,需要调用  updateNodeInternals 方法传入需要更新的节点 id 数组去应用,防止边缘未对其的情况出现。


import {useVueFlow} from '@vue-flow/core'

const {updateNodeInternals} = useVueFlow()

const onSomeEvent = () => {updateNodeInternals(['1'])
}

Composables

Vue Flow 提供了一些用于获取流程图及其内部组件相关数据的 API,可以参考文档  Composables | Vue Flow

Controlled Flow

Vue Flow 同样提供了一些 API 用于对流程图的更新过程进行手动控制并且监听对应事件  受控流量 |Vue 流程 (vueflow.dev)

来看一下官方文档 Demo

 Layouting | Vue Flow 这个 demo 较全的使用到了 Vue Flow 中的一些基本用法:

Vue3 流程图组件库:Vue Flow

1. 主流程:App.vue:


import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";
import {nextTick, ref} from "vue";
import {Panel, VueFlow, useVueFlow} from "@vue-flow/core";
import {Background} from "@vue-flow/background";
import Icon from "./icon.vue";
import ProcessNode from "./processNode.vue";
import AnimationEdge from "./animationEdge.vue";
import {initialEdges, initialNodes} from "./initialElements";
import {useRunProcess} from "./useRunProcess";
import {useShuffle} from "./useShuffle";
import {useLayout} from "./useLayout";

// 节点的初始化数据
const nodes = ref(initialNodes);

// 节点的连接关系
const edges = ref(initialEdges);

// 打乱节点之间的连接关系
const shuffle = useShuffle();

// useLayout 处理节点布局对齐等
const {graph, layout, previousDirection} = useLayout();

const {fitView} = useVueFlow();

// 将节点和连线随机化
async function shuffleGraph() {await stop();

  reset(nodes.value);

  edges.value = shuffle(nodes.value);

  nextTick(() => {layoutGraph(previousDirection.value);
  });
}

// 进行重新排版
async function layoutGraph(direction) {await stop();

  reset(nodes.value);

  nodes.value = layout(nodes.value, edges.value, direction);

  nextTick(() => {fitView();
    run(nodes.value);
  });
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.


  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.


.layout-flow {
  background-color: #1a192b;
  height: 100%;
  width: 100%;
}

.process-panel,
.layout-panel {
  display: flex;
  gap: 10px;
}

.process-panel {
  background-color: #2d3748;
  padding: 10px;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
}

.process-panel button {
  border: none;
  cursor: pointer;
  background-color: #4a5568;
  border-radius: 8px;
  color: white;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}

.process-panel button {
  font-size: 16px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.checkbox-panel {
  display: flex;
  align-items: center;
  gap: 10px;
}

.process-panel button:hover,
.layout-panel button:hover {
  background-color: #2563eb;
  transition: background-color 0.2s;
}

.process-panel label {
  color: white;
  font-size: 12px;
}

.stop-btn svg {display: none;}

.stop-btn:hover svg {display: block;}

.stop-btn:hover .spinner {display: none;}

.spinner {
  border: 3px solid #f3f3f3;
  border-top: 3px solid #2563eb;
  border-radius: 50%;
  width: 10px;
  height: 10px;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% {transform: rotate(0deg);
  }
  100% {transform: rotate(360deg);
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.

2.useShuffle.js

该文件提供的方法主要是用来随机打乱节点以及连线的关系


// 打乱数组的顺序
function shuffleArray(array) {for (let i = array.length - 1; i> 0; i--) {const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

// 根据节点数组生成一个可能的节点之间的映射关系
function generatePossibleEdges(nodes) {const possibleEdges = [];

  for (const sourceNode of nodes) {for (const targetNode of nodes) {if (sourceNode.id !== targetNode.id) {const edgeId = `e${sourceNode.id}-${targetNode.id}`;
        possibleEdges.push({
          id: edgeId,
          source: sourceNode.id,
          target: targetNode.id,
          type: "animation",
          animated: true
        });
      }
    }
  }

  return possibleEdges;
}

// 返回新的节点连接关系;
export function useShuffle() {return nodes => {const possibleEdges = generatePossibleEdges(nodes);
    shuffleArray(possibleEdges);

    const usedNodes = new Set();
    const newEdges = [];

    for (const edge of possibleEdges) {
      if (!usedNodes.has(edge.target) &&
        (usedNodes.size === 0 || usedNodes.has(edge.source))
      ) {newEdges.push(edge);
        usedNodes.add(edge.source);
        usedNodes.add(edge.target);
      }
    }

    return newEdges;
  };
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.

3.useLayout.js

使用 dagre 对节点进行排版,返回排版后的图数据;


import dagre from "dagre";
import {ref} from "vue";
import {Position, useVueFlow} from "@vue-flow/core";

export function useLayout() {const { findNode} = useVueFlow();

  const graph = ref(new dagre.graphlib.Graph());

  const previousDirection = ref("LR");

  function layout(nodes, edges, direction) {const dagreGraph = new dagre.graphlib.Graph();

    graph.value = dagreGraph;

    // 设置默认的边标签
    dagreGraph.setDefaultEdgeLabel(() => ({}));

    const isHorizontal = direction === "LR";

    // 设置图布局
    dagreGraph.setGraph({rankdir: direction});

    previousDirection.value = direction;

    for (const node of nodes) {
      // 查找到节点的信息
      const graphNode = findNode(node.id);
      // 设置节点
      dagreGraph.setNode(node.id, {
        width: graphNode.dimensions.width || 150,
        height: graphNode.dimensions.height || 50
      });
    }

    // 设置边
    for (const edge of edges) {dagreGraph.setEdge(edge.source, edge.target);
    }

    // 排版
    dagre.layout(dagreGraph);

    // 排版结束后返回新的节点状态
    return nodes.map(node => {const nodeWithPosition = dagreGraph.node(node.id);

      return {
        ...node,
        targetPosition: isHorizontal ? Position.Left : Position.Top,
        sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
        position: {x: nodeWithPosition.x, y: nodeWithPosition.y}
      };
    });
  }

  return {graph, layout, previousDirection};
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.

4.useRunProcess.js

用于模拟流程运行过程中的各种状态


import {ref, toRef, toValue} from "vue";
import {useVueFlow} from "@vue-flow/core";

export function useRunProcess({graph: dagreGraph, cancelOnError = true}) {const { updateNodeData, getConnectedEdges} = useVueFlow();

  const graph = toRef(() => toValue(dagreGraph));

  // 是否正在运行
  const isRunning = ref(false);

  // 已执行的节点
  const executedNodes = new Set();

  // 当前正在执行的节点
  const runningTasks = new Map();

  // 即将执行的节点
  const upcomingTasks = new Set();

  async function runNode(node, isStart = false) {if (executedNodes.has(node.id)) {return;}

    // 加入到即将执行的节点
    upcomingTasks.add(node.id);

    // 过滤出指向当前节点的连线
    const incomers = getConnectedEdges(node.id).filter(connection => connection.target === node.id
    );

    // 等待进入动画全部执行完成
    await Promise.all(incomers.map(incomer => until(() => !incomer.data.isAnimating))
    );

    // 清空
    upcomingTasks.clear();

    if (!isRunning.value) {return;}

    // 节点加入到已经执行的节点
    executedNodes.add(node.id);

    // 更新节点的状态
    updateNodeData(node.id, {
      isRunning: true,
      isFinished: false,
      hasError: false,
      isCancelled: false
    });

    const delay = Math.floor(Math.random() * 2000) + 1000;

    return new Promise(resolve => {
      const timeout = setTimeout(async () => {
          // 获取当前节点的所有后续子节点
          const children = graph.value.successors(node.id);

          // 随机抛出错误
          const willThrowError = Math.random()  0) {await Promise.all(children.map(id => runNode({id})));
          }
          resolve();},
        isStart ? 0 : delay
      );
      // 将当前任务加入到运行任务
      runningTasks.set(node.id, timeout);
    });
  }

  // 从起始节点开始执行的情况
  async function run(nodes) {if (isRunning.value) {return;}

    reset(nodes);

    isRunning.value = true;

    // 过滤出起始节点
    const startingNodes = nodes.filter(node => graph.value.predecessors(node.id)?.length === 0
    );

    // 调用 runNode 从起始节点执行
    await Promise.all(startingNodes.map(node => runNode(node, true)));

    clear();}

  // 重置
  function reset(nodes) {clear();

    for (const node of nodes) {
      updateNodeData(node.id, {
        isRunning: false,
        isFinished: false,
        hasError: false,
        isSkipped: false,
        isCancelled: false
      });
    }
  }

  async function skipDescendants(nodeId) {const children = graph.value.successors(nodeId);

    for (const child of children) {updateNodeData(child, { isRunning: false, isSkipped: true});
      await skipDescendants(child);
    }
  }

  // 暂停运行
  async function stop() {
    isRunning.value = false;

    for (const nodeId of upcomingTasks) {clearTimeout(runningTasks.get(nodeId));
      runningTasks.delete(nodeId);
      updateNodeData(nodeId, {
        isRunning: false,
        isFinished: false,
        hasError: false,
        isSkipped: false,
        isCancelled: true
      });
      await skipDescendants(nodeId);
    }

    for (const [nodeId, task] of runningTasks) {clearTimeout(task);
      runningTasks.delete(nodeId);
      updateNodeData(nodeId, {
        isRunning: false,
        isFinished: false,
        hasError: false,
        isSkipped: false,
        isCancelled: true
      });
      await skipDescendants(nodeId);
    }

    executedNodes.clear();
    upcomingTasks.clear();}

  function clear() {
    isRunning.value = false;
    executedNodes.clear();
    runningTasks.clear();}

  return {run, stop, reset, isRunning};
}

// 等待直到 condition 为 true
async function until(condition) {return new Promise(resolve => {const interval = setInterval(() => {if (condition()) {clearInterval(interval);
        resolve();}
    }, 100);
  });
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.

5.processNode.js

流程图节点组件,根据节点状态显示不同的样式


import {computed, toRef} from 'vue'
import {Handle, useHandleConnections} from '@vue-flow/core'

const props = defineProps({
  data: {
    type: Object,
    required: true,
  },
  sourcePosition: {type: String,},
  targetPosition: {type: String,},
})

const sourceConnections = useHandleConnections({type: 'target',})

const targetConnections = useHandleConnections({type: 'source',})

// 判断是发送节点还是接收节点
const isSender = toRef(() => sourceConnections.value.length  targetConnections.value.length  {if (isSender.value) {return '#2563eb'}

  if (props.data.hasError) {return '#f87171'}

  if (props.data.isFinished) {return '#42B983'}

  if (props.data.isCancelled) {return '#fbbf24'}

  return '#4b5563'
})

const processLabel = computed(() => {if (props.data.hasError) {return '❌'}

  if (props.data.isSkipped) {return '🚧'}

  if (props.data.isCancelled) {return '🚫'}

  if (isSender.value) {return '📦'}

  if (props.data.isFinished) {return '😎'}

  return '🏠'
})




  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.

6.AnimationEdge.js

处理节点之间连线的动画效果






  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.

总结

文章主要介绍了如何使用 Vue Flow 库的基本概念和使用:

1. 安装

2. 基础组件:

  • Nodes:图中的基本单元,用于表示数据。
  • Edges:连接节点的连线。
  • Handles:节点上的小圆圈,用于连接节点。

3. 主题定制

可以通过以下方式调整默认样式:

  • 覆盖 CSS 类名 :通过 CSS 类名来自定义节点样式。
  • 组件属性 :在 Vue 组件上使用 styleclass 属性。
  • 全局 CSS 变量 :在全局 CSS 文件中覆盖样式变量。

4. 节点 (Nodes)

  • 节点展示 :通过传入 nodes 数组到 VueFlow 组件来展示节点。
  • 节点增删 :可以通过改变 nodes 参数或使用 useVueFlow 提供的 addNodesremoveNodes 方法。
  • 节点更新 :可以直接修改 nodes 参数或使用 updateNodeData 方法。
  • 节点类型 :包括默认节点、输入节点、输出节点和自定义节点。

5. 连线 (Edges)

  • 连线展示 :通过传入 edges 数组到 VueFlow 组件来展示连线。
  • 连线增删和更新 :类似于节点,可以通过改变 edges 参数或使用 useVueFlow 提供的方法。
  • 连线类型 :支持默认连线、阶梯连线、直线连线和自定义连线。

6. Handles

Handles 用于连接节点,可以自定义位置、多个 Handle 配置、动态更新和显示 / 隐藏等。

原文地址: Vue3 流程图组件库:Vue Flow

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-10-23发表,共计19098字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)