Vue3+logic-flow实现基础流程图

1,941 阅读9分钟

logic-flow 官网参考

LogicFlow 是什么

LogicFlow 是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow支持前端自定义开发各种逻辑编排场景,如流程图、ER图、BPMN流程等。在工作审批流配置、机器人逻辑编排、无代码平台流程配置都有较好的应用。**个人公众号:鱼樱AI实验室**

安装

pnpm add @logicflow/core
# 插件包(不使用插件时不需要引入)
pnpm add @logicflow/extension

基础使用

基础节点

LogicFlow的基础节点是比较简单的,但是在业务中对节点外观要求可能有各种情况。LogicFlow提供了非常强大的自定义节点功能,可以支持开发者自定义各种节点。下面是基于继承的自定义节点介绍。

image.png

<template>
  <div id="logic-flow"></div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue'
import LogicFlow from '@logicflow/core'
import { Control } from '@logicflow/extension'

import '@logicflow/core/lib/style/index.css'
import '@logicflow/extension/es/index.css'

// 全局使用 每一个lf实例都具备 Control
LogicFlow.use(Control)

let lf: LogicFlow

// 图数据
const graphData = {
  // 节点数据 基础节点 & 自定义节点数据
  nodes: [
    // 节点数据属性:节点1
    {
      id: '1',
      type: 'rect',
      x: 50,
      y: 50,
      text: { x: 100, y: 100, value: '方形' }, // 节点文本
      properties: {
        width: 80,
        height: 80,
        style: {
          stroke: 'blue',
        },
        isPass: 'true', // 例如:在审批流场景,我们定义某个节点,这个节点通过了,节点为绿色,不通过节点为红色。
      },
    },
    // 节点2
    {
      id: '2',
      type: 'circle',
      x: 200,
      y: 300,
      text: { x: 300, y: 300, value: '圆形' },
      properties: {},
    },
    {
      id: '3',
      type: 'ellipse',
      x: 500,
      y: 60,
      text: '椭圆',
    },
    {
      id: '4',
      type: 'polygon',
      x: 100,
      y: 200,
      text: '多边形',
    },
    {
      id: '5',
      type: 'diamond',
      x: 300,
      y: 200,
      text: '菱形',
    },
    {
      id: '6',
      type: 'text',
      x: 500,
      y: 200,
      text: '纯文本节点',
    },
    {
      id: '7',
      type: 'html',
      x: 100,
      y: 320,
      text: 'html节点',
    },
  ],
  edges: [
    // 边数据属性 连线属性配置
    {
      id: 'edge_id',
      type: 'polyline',
      sourceNodeId: '1',
      targetNodeId: '2',
      text: { x: 139, y: 200, value: '连线' }, // 连线文本
      properties: {},
    },
  ],
}

onMounted(() => {
  lf = new LogicFlow({
    container: document.querySelector('#logic-flow') as HTMLElement,
    stopScrollGraph: true,
    stopMoveGraph: true,
  })

  lf.render(graphData)
  lf.translateCenter() // 将图形移动到画布中央

  const data = lf.getGraphData()

  console.log(data, '获取画布数据')
})
</script>

<style>
#logic-flow {
  width: 100%;
  height: 500px;
  border: 1px solid #dcdcdc;
}
</style>

自定义节点

LogicFlow是基于继承来实现自定义节点、边。开发者可以继承LogicFlow内置的节点,然后利用面向对象的机制 重写

model: 数据层,包含节点各种样式(边框、颜色)、形状(宽高、顶点位置)、业务属性等。

view: 视图层,控制节点的最终渲染效果,通过改变model就可以满足自定义节点,同时可以在view 上定制更加复杂的svg元素。

LogicFlow 内部存在 7 种基础节点, 自定义节点的时候可以基于需要选择任意一种来继承, 然后取一个符合自己业务意义的名字。以@logicflow/extension中提供的可缩放节点为例:LogicFlow 基础节点不支持节点缩放,于是 LogicFlow 在extension包中,基于基础节点,封装了对节点缩放的逻辑,然后发布出去。这样开发者可以直接基于extension中的可缩放节点进行自定义。

image.png

import LogicFlow, { RectNode, RectNodeModel } from '@logicflow/core'

export type CustomProperties = {
  // 形状属性
  width?: number
  height?: number
  radius?: number

  // 文字位置属性
  refX?: number
  refY?: number

  // 样式属性
  style?: LogicFlow.CommonTheme
  textStyle?: LogicFlow.TextNodeTheme
}

class CustomRectNode extends RectNode {}

class CustomRectModel extends RectNodeModel {
  // 设置矩形的形状属性:大小和圆角
  setAttributes() {
    this.width = 200
    this.height = 80
    this.radius = 50
  }

  // 重写文本样式属性
  getTextStyle(): LogicFlow.TextNodeTheme {
    const { refX = 0, refY = 0 } = this.properties as CustomProperties
    const style = super.getTextStyle()

    // 通过 transform 重新设置 text 的位置:向下移动70px
    return {
      ...style,
      transform: `matrix(1 0 0 1 ${refX} ${refY + 60})`,
    }
  }

  // 设置矩形的样式属性:边框颜色
  getNodeStyle() {
    const style = super.getNodeStyle()
    style.stroke = 'blue'
    return style
  }
}

export default {
  // 类型
  type: 'custom-rect',
  view: CustomRectNode,
  model: CustomRectModel,
}
<template>
  <div id="logic-flow"></div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue'
import LogicFlow from '@logicflow/core'
import { Control } from '@logicflow/extension'

import '@logicflow/core/lib/style/index.css'
import '@logicflow/extension/es/index.css'
// 自定义节点 view & model
import CustomRect from './customRect'

// 全局使用 每一个lf实例都具备 Control
LogicFlow.use(Control)

let lf: LogicFlow

// 图数据
const graphData = {
  nodes: [
    // 节点数据属性:节点1
    {
      id: '1',
      type: 'rect',
      x: 50,
      y: 50,
      text: { x: 100, y: 100, value: '方形' }, // 节点文本
      properties: {
        width: 80,
        height: 80,
        style: {
          stroke: 'blue',
        },
        isPass: 'true', // 例如:在审批流场景,我们定义某个节点,这个节点通过了,节点为绿色,不通过节点为红色。
      },
    },
    // 节点2
    {
      id: '2',
      type: 'circle',
      x: 200,
      y: 300,
      text: { x: 300, y: 300, value: '圆形' },
      properties: {},
    },
    {
      id: '3',
      type: 'ellipse',
      x: 500,
      y: 60,
      text: '椭圆',
    },
    {
      id: '4',
      type: 'polygon',
      x: 100,
      y: 200,
      text: '多边形',
    },
    {
      id: '5',
      type: 'diamond',
      x: 300,
      y: 200,
      text: '菱形',
    },
    {
      id: '6',
      type: 'text',
      x: 500,
      y: 200,
      text: '纯文本节点',
    },
    {
      id: '7',
      type: 'html',
      x: 100,
      y: 320,
      text: 'html节点',
    },
    // 节点数据属性:节点1
    {
      id: '8',
      type: 'custom-rect', // 使用自定义节点类型
      x: 50,
      y: 50,
      text: { x: 100, y: 100, value: '自定义方形' }, // 节点文本
      properties: {
        width: 80,
        height: 80,
        style: { stroke: 'red' },
        isPass: 'true',
      },
    },
  ],
  edges: [
    // 边数据属性
    {
      id: 'edge_id',
      type: 'polyline',
      sourceNodeId: '1',
      targetNodeId: '2',
      text: { x: 139, y: 200, value: '连线' }, // 连线文本
      properties: {},
    },
  ],
}

onMounted(() => {
  lf = new LogicFlow({
    container: document.querySelector('#logic-flow') as HTMLElement,
    stopScrollGraph: true,
    stopMoveGraph: true,
  })
  // 注册自定义节点,注册后才能使用自定义节点
  lf.register(CustomRect)
  lf.render(graphData)
  lf.translateCenter() // 将图形移动到画布中央

  const data = lf.getGraphData()

  console.log(data, 'data')
})
</script>

<style>
#logic-flow {
  width: 100%;
  height: 500px;
  border: 1px solid #dcdcdc;
}
</style>

基础边

    1. 直线 - line (内置)
    1. 直角折线 - polyline (内置)
    1. 贝塞尔曲线 - bezier (内置)
<template>
  <div id="logic-flow"></div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue'
import LogicFlow from '@logicflow/core'
import { Control } from '@logicflow/extension'

import '@logicflow/core/lib/style/index.css'
import '@logicflow/extension/es/index.css'
// 自定义节点
import CustomRect from './customRect'

// 全局使用 每一个lf实例都具备 Control
LogicFlow.use(Control)

let lf: LogicFlow

// 图数据
const graphData = {
  nodes: [
    // 节点数据属性:节点1
    {
      id: '1',
      type: 'rect',
      x: 50,
      y: 50,
      text: { x: 100, y: 100, value: '方形' }, // 节点文本
      properties: {
        width: 80,
        height: 80,
        style: {
          stroke: 'blue',
        },
        isPass: 'true', // 例如:在审批流场景,我们定义某个节点,这个节点通过了,节点为绿色,不通过节点为红色。
      },
    },
    // 节点2
    {
      id: '2',
      type: 'circle',
      x: 200,
      y: 300,
      text: { x: 300, y: 300, value: '圆形' },
      properties: {},
    },
    {
      id: '3',
      type: 'ellipse',
      x: 500,
      y: 60,
      text: '椭圆',
    },
    {
      id: '4',
      type: 'polygon',
      x: 100,
      y: 200,
      text: '多边形',
    },
    {
      id: '5',
      type: 'diamond',
      x: 300,
      y: 200,
      text: '菱形',
    },
    {
      id: '6',
      type: 'text',
      x: 500,
      y: 200,
      text: '纯文本节点',
    },
    {
      id: '7',
      type: 'html',
      x: 100,
      y: 320,
      text: 'html节点',
    },
    // 节点数据属性:节点1
    {
      id: '8',
      type: 'custom-rect', // 使用自定义节点类型
      x: 50,
      y: 50,
      text: { x: 100, y: 100, value: '自定义方形' }, // 节点文本
      properties: {
        width: 80,
        height: 80,
        style: { stroke: 'red' },
        isPass: 'true',
      },
    },
  ],
  edges: [
    // 边数据属性
    {
      id: '1',
      type: 'polyline',
      sourceNodeId: '1',
      targetNodeId: '2',
      text: { x: 139, y: 200, value: 'polyline直角折线' }, // 连线文本
      properties: {},
    },
    {
      id: '2',
      sourceNodeId: '2',
      targetNodeId: '3',
      type: 'line',
      text: 'line直线',
    },
    {
      id: '3',
      sourceNodeId: '3',
      targetNodeId: '4',
      type: 'bezier',
      text: 'bezier贝塞尔曲线',
    },
  ],
}

onMounted(() => {
  lf = new LogicFlow({
    container: document.querySelector('#logic-flow') as HTMLElement,
    stopScrollGraph: true,
    stopMoveGraph: true,
  })
  // 注册自定义节点,注册后才能使用自定义节点
  lf.register(CustomRect)
  lf.render(graphData)
  lf.translateCenter() // 将图形移动到画布中央

  const data = lf.getGraphData()

  console.log(data, 'data')
})
</script>

<style>
#logic-flow {
  width: 100%;
  height: 500px;
  border: 1px solid #dcdcdc;
}
</style>

自定义边

选择自定义边继承的内置边,和节点一样,LogicFlow 的边也支持基于继承的自定义机制。同样也只需同时继承viewmodel。 但是和节点不一样的是,由于边的编辑复杂度问题,绝大多数情况下,自定义边时不推荐自定义view。 只需要在自定义edgeModel中样式类即可。

// 直线
import { LineEdge, LineEdgeModel } from '@logicflow/core'
// 折线
import { PolylineEdge, PolylineEdgeModel } from '@logicflow/core'
// 贝塞尔曲线
import { BezierEdge, BezierEdgeModel } from '@logicflow/core'
import { PolylineEdge, PolylineEdgeModel, LogicFlow } from '@logicflow/core';
import EdgeTextTheme = LogicFlow.EdgeTextTheme;

class SequenceModel extends PolylineEdgeModel {
  // 设置边样式
  getEdgeStyle() {
    const style = super.getEdgeStyle();
    const { properties } = this;
    if (properties.isstrokeDashed) {
      style.strokeDasharray = '4, 4';
    }
    style.stroke = 'orange';
    return style;
  }

  // 设置边文本样式
  getTextStyle() {
    const style: EdgeTextTheme = super.getTextStyle();
    style.color = '#3451F1';
    style.fontSize = 20;
    style.background = Object.assign({}, style.background, {
      fill: '#F2F131',
    });
    return style;
  }

  // 设置 hover 轮廓样式
  getOutlineStyle() {
    const style = super.getOutlineStyle();
    style.stroke = 'blue';
    return style;
  }
}

export default {
  type: 'custom-line',
  view: PolylineEdge,
  model: SequenceModel,
};
<template>
  <div id="logic-flow"></div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue'
import LogicFlow from '@logicflow/core'
import { Control } from '@logicflow/extension'

import '@logicflow/core/lib/style/index.css'
import '@logicflow/extension/es/index.css'
// 自定义节点
import CustomRect from './customRect'
// 自定义边
import CustomLine from './customLine'

// 全局使用 每一个lf实例都具备 Control
LogicFlow.use(Control)

let lf: LogicFlow

// 图数据
const graphData = {
  nodes: [
    // 节点数据属性:节点1
    {
      id: '1',
      type: 'rect',
      x: 50,
      y: 50,
      text: { x: 100, y: 100, value: '方形' }, // 节点文本
      properties: {
        width: 80,
        height: 80,
        style: {
          stroke: 'blue',
        },
        isPass: 'true', // 例如:在审批流场景,我们定义某个节点,这个节点通过了,节点为绿色,不通过节点为红色。
      },
    },
    // 节点2
    {
      id: '2',
      type: 'circle',
      x: 200,
      y: 300,
      text: { x: 300, y: 300, value: '圆形' },
      properties: {},
    },
    {
      id: '3',
      type: 'ellipse',
      x: 500,
      y: 60,
      text: '椭圆',
    },
    {
      id: '4',
      type: 'polygon',
      x: 100,
      y: 200,
      text: '多边形',
    },
    {
      id: '5',
      type: 'diamond',
      x: 300,
      y: 200,
      text: '菱形',
    },
    {
      id: '6',
      type: 'text',
      x: 500,
      y: 200,
      text: '纯文本节点',
    },
    {
      id: '7',
      type: 'html',
      x: 100,
      y: 320,
      text: 'html节点',
    },
    // 节点数据属性:节点1
    {
      id: '8',
      type: 'custom-rect', // 使用自定义节点类型
      x: 50,
      y: 50,
      text: { x: 100, y: 100, value: '自定义方形' }, // 节点文本
      properties: {
        width: 80,
        height: 80,
        style: { stroke: 'red' },
        isPass: 'true',
      },
    },
  ],
  edges: [
    // 边数据属性
    {
      id: '1',
      type: 'polyline',
      sourceNodeId: '1',
      targetNodeId: '2',
      text: { x: 139, y: 200, value: '连线' }, // 连线文本
      properties: {},
    },
    {
      id: '2',
      sourceNodeId: '2',
      targetNodeId: '3',
      type: 'line',
      text: 'line',
    },
    {
      id: '3',
      sourceNodeId: '3',
      targetNodeId: '4',
      type: 'bezier',
      text: 'bezier',
    },
    {
      id: '4',
      sourceNodeId: '4',
      targetNodeId: '5',
      startPoint: {
        x: 100,
        y: 60,
      },
      endPoint: {
        x: 500,
        y: 50,
      },
      text: 'custom-line自定义边',
      type: 'custom-line',
      properties: {
        isstrokeDashed: true, // 是否虚线
      },
    },
  ],
}

onMounted(() => {
  lf = new LogicFlow({
    container: document.querySelector('#logic-flow') as HTMLElement,
    stopScrollGraph: true,
    stopMoveGraph: true,
  })
  // 注册自定义节点,注册后才能使用自定义节点
  lf.register(CustomRect)
  // 注册自定义边,注册后才能使用自定义边
  lf.register(CustomLine)
  // 设置当节点直接由用户手动连接的默认边类型
  lf.setDefaultEdgeType('sequence')

  lf.render(graphData)
  lf.translateCenter() // 将图形移动到画布中央

  const data = lf.getGraphData()

  console.log(data, 'data')
})
</script>

<style>
#logic-flow {
  width: 100%;
  height: 500px;
  border: 1px solid #dcdcdc;
}
</style>

logic-flow 其他配置属性

  • 背景相关
const lf = new LogicFlow({
  background: false | BackgroundConfig,
})


type BackgroundConfig = {
  backgroundImage?: string,
  backgroundColor?: string,
  backgroundRepeat?: string,
  backgroundPosition?: string,
  backgroundSize?: string,
  backgroundOpacity?: number,
  filter?: string, // 滤镜
  [key: any]: any,
};
  • 网格相关
const lf1 = new LogicFlow({
  grid: true,
})


// 等同于默认属性如下
const lf2 = new LogicFlow({
  grid: {
    size: 20,
    visible: true,
    type: 'dot',
    config: {
      color: '#ababab',
      thickness: 1,
    },
  },
})
  • 事件相关

lf实例上提供on方法支持监听事件

lf.on("node:dnd-add", (data) => {});

LogicFlow 支持用逗号分割事件名

lf.on('node:click,edge:click', (data) => {})

自定义事件

除了 lf 上支持的监听事件外,还可以使用eventCenter 对象来监听和触发事件。eventCenter是一个graphModel 上的一个属性。所以在自定义节点的时候,我们可以使用eventCenter触发自定义事件

拖拽面板 (DndPanel)

自定义拖拽面板内容

lf.setPatternItems(patternItems)

lf.extension.dndPanel.setPatternItems(patternItems)  1.1.0新增

  • 设置拖拽面板组件内容
type PatternItem = {
  type?: string;
  text?: string;
  label?: string;
  icon?: string;
  className?: string;
  properties?: object;
  callback?: () => void;
}


// lf.extension.dndPanel.setPatternItems = (patternItems:PatternItem[]) => void
lf.extension.dndPanel.setPatternItems(patternItems)

有了这些你搞起流程图来就方便很多了~~~