LogicFlow的Vue3自定义节点


LogicFlow 的 Vue3 自定义节点

1.介绍

​ 当项目业务像贪吃蛇疯狂吃豆子一样越变越复杂,我摩拳擦掌把算盘打到了 LogicFlow 的自定义节点身上!想象中这官网得是个知识大礼包,我搓着手点进去,结果页面直接给我表演了个 “速通”—— 就一个案例,唰地一下结束了,仿佛是在玩网页版 “大家来找茬”,可找来找去就那孤零零的一个例子!

我跟打不死的小强似的,转头杀进搜索引擎。好家伙,打开一堆博客,不是把官网案例 Ctrl+V 得明明白白,博主们像在参加 “复制粘贴武林大会”,比谁手速快;就是惜字如金,说的内容还没我家猫打翻的猫粮颗粒多,气得我直拍大腿,靠,看源码!

2.LogicFlow 的 Api 介绍

官网 Api: https://site.logic-flow.cn/api/model/graph-model

img1
上表,请记住graphModelnodeModel,这两个 Api 集合。

Api 集合 说明
graphModel graphModel 是 LogicFlow 中整个画布对应的 model
nodeModel LogicFlow 中所有的节点都会有一个 nodeModel 与其对应
edgeModel LogicFlow 中所有的边都会有一个 edgeModel 与其对应
transformModel 控制画布的放大、缩小、平移
editConfigModel editConfigModel 是控制页面编辑状态

说明:

  1. 注意graphModel 上所有的属性都是只读,要想修改,请使用提供的对应方法进行修改。
  2. 由于数据驱动视图的机制,我们对节点和边的所有操作事实上就是对 model 的操作。大多数情况下,我们不建议直接对 nodeModel 和 edgeModel的属性进行赋值操作,而是调用 model , edgeModel 或者 graphModel: https://site.logic-flow.cn/api/model/graph-model 上提供的方法。

总结:以上自己熟悉熟悉即可,不要通过graphModel和nodeModel和edgeModel的属性进行赋值操作,均是只读。要改属性请调用方法。

3.让我们看看官方 Vue3 的自定义节点

官方的 Vue 自定义节点: https://site.logic-flow.cn/tutorial/advanced/vue

img2

由上图可知,提供了一个包@logicflow/vue-node-registry,要是有 Vue 自定义节点请先下载这个包

npm install @logicflow/core --save       # LogicFlow核心包
npm install @logicflow/extension --save  # 插件包(不使用插件时不需要引入)
npm install @logicflow/vue-node-registry --save  # Vue自定义节点包

import '@logicflow/core/es/index.css'         # LogicFlow核心包样式(2选1即可)
import '@logicflow/core/lib/style/index.css'   # LogicFlow核心包样式(2选1即可)

import '@logicflow/extension/es/index.css' # 插件包样式(2选1即可)
import '@logicflow/extension/lib/style/index.css' # 插件包样式(2选1即可)

1.如果以上的包下载了,未生效,请查看项目中的node_modules -- > @logicflow下面是否存在core,extension,vue-node-registry这三个包,如果不使用插件,可以不下载第二个包。

2.如果样式引入不生效,同样请查看node_modules -- > @logicflowcore,extension这两个包的样式位置即可

4.创建自定义节点

<script lang="ts" setup>
import { ref, inject } from 'vue'
import { lywMessage, lywConfirm } from '@/utils/feedBack' //自定义提示与弹框通知

const getNode: any = inject('getNode')
const getGraph: any = inject('getGraph')

const node = getNode()
const graph = getGraph()

// 事件触发
//  编辑
const updateSubmit = () => {
  graph.eventCenter?.emit('node:doEdit', {
    form: formUpdate.value,
    pojoList: fieldArrEdit.value,
  })
  graph.eventCenter.on('node:axiosStatus', (args: any) => {
    if (args) {
      doClose()
    } else {
      lywMessage('编辑失败', 'error')
    }
  })
}
// 删除
const del = () => {
  lywConfirm('您确定要移除当前项吗', null, async () => {
    graph.eventCenter?.emit('node:doDel', {
      data: node.properties.tableObject,
    })
  })
}
//...其余逻辑代码正常写,不要违反LogicFlow的Api介绍的注意点即可
</script>

以上代码就是 Vue 的自定义节点,他的Html结构与Css样式我就不展示了,我只展示关键代码

4.1.关键点一:数据从何而来?

img3

官方的 Vue 自定义节点: https://site.logic-flow.cn/tutorial/advanced/vue

在我们看见官方的演示中,并未进行provide传递数据。那 inject 数据从何而来?

const TeleportContainer = getTeleport()注意这行代码,让我们看看他的内部逻辑

img5

这 4 个暴露出的函数就是这个文件的全部,解释请看图即可,其中,让我们看看connect函数

img4

看见源码可以发现,人家在connect接入这个函数进行了数据传递,继续往下看

img6

img7

上方的这个图片红框解释一下,相当于一个模板吧,例如:

h(
 标签名,
 {
     标签的属性:属性值,
     (例如img标签属性,a标签的属性等)
     style:{
       width:0
     }
 },
 该标签的子组件(子组件为同一等级)
)

ok,解释玩完上方,有细心的伙伴就发现了,getTeleport这个函数并未调用connect函数,确实如此,为什么没调用,会触发?

4.2.getTeleport 并未调用函数,为何会触发?

我们先看一下 logicFlow 官方放出来自定义节点的流程图

img8

ok,这个流程图看完了,请问表达了什么?就只表达了自定义节点的底层是如何运行的,那怎样才能让自定义节点运行起来?当然是需要挂载到 LogicFlow 实例上才行啊!!!

分为以下几步:

  1. 创建 LogicFlow 实例
  2. 注册自定义节点
<script setup lang="ts">
onMounted(() => {
  if (containerRef.value) {
    // 1.创建LogicFlow实例
    lf.value = new LogicFlow({
      container: containerRef.value!,
      grid: true,
      nodeTextEdit: false,
      edgeTextEdit: false,
      zoom: {
        min: 0.2,
        max: 2,
        size: 0.1,
      },
      // ...多个属性,自己去官网找
    })
    // 2.注册节点
    register(
      {
        type: 'custom-vue-node',
        component: ProgressNode,
        model: CustomNodeModel,
      },
      lf.value
    )
  }
})
</script>

在注册节点时,调用了register函数,继续往下看

img9

我们自定义的组件其实就是图中的View,注意:我说的是自定义的组件,不是自定义节点。我们不传递自定义的 view,View 使用的是VueNodeView

img10

这里是代码位置,让我们看看内部代码。

img11

Vue2我们就不看了,直接看Vue3的代码,当在激活时,触发 connect 函数,函数在这里触发了。那当没有激活时,那下面那段代码是什么意思?

4.3.未激活时,会发生什么?

请问上面的未激活说明了什么?自己去看看teleport.ts代码,说明了并未调用getTeleport这个函数,那么就说明,自定义节点并不只是通过getTeleport()函数这一种写法,说明了还有另一种写法,这种写法不需要传递flowId,不需要写入getTeleport()的组件, 只不过这种写法官方并没有示例与说明。这种写法我就不演示了,我感觉官方的写法就挺好用的。我试了,是可行的。有兴趣的可以自己去试一下

4.4.数据的使用

前面 3 点,讲清楚了getNode,getGraph这两个数据是从何而来。现在说说他们的作用,先仔细看看前面的部分,去看第2点LogicFlow的Api介绍

  1. 尽量不要去改内部的属性,修改属性自己去官网找方法

  2. 通过``中的properties传递的数据去展示数据

  3. 通过getGraph中的eventCenter去触发事件

注意:千万不要自作聪明,去传参拿数据,去写自定义事件,我告诉你,一个都没用,渲染数据就用getNode,触发事件就用getGraph,getGraph 与 graphModel,只相差flowId一个参数,getNode就是整个自定义节点,即 View,他包含了getGraph

官网事件: https://site.logic-flow.cn/api/event-center

找到监听事件on,off,once,emit

// 父组件
// 编辑与移除事件结果
const sendStatus = (flag: boolean) => {
  lf.value?.graphModel.eventCenter.emit('node:axiosStatus', flag)  //触发
}

// 编辑与移除事件
const initEvent = (lf: any) => {
    // 监听编辑
  lf.value.on('graph:rendered', ({ graphModel }: { graphModel: any }) => {
    flowId.value = graphModel.flowId!
  })
  lf.value?.graphModel.eventCenter.on('node:doEdit', (agrs: any) => {
    const { form, pojoList } = agrs
    console.log({ form, pojoList }, '编辑')
    sendStatus(true)
  })
   // 监听删除
  lf.value?.graphModel.eventCenter.on('node:doDel', (agrs: any) => {
    const {
      data: { modeName, tableName },
    } = agrs
    console.log({ modeName, tableName }, '移除')
    sendStatus(true)
  })
}
// 子组件
// 编辑
const updateSubmit = () => {
  //触发
  graph.eventCenter?.emit('node:doEdit', {
    form: formUpdate.value,
    pojoList: fieldArrEdit.value,
  })
  //监听结果
  graph.eventCenter.on('node:axiosStatus', (args: any) => {
    if (args) {
      doClose()
    } else {
      lywMessage('编辑失败', 'error')
    }
  })
}

// 删除
const del = () => {
  lywConfirm('您确定要移除当前项吗', null, async () => {
    //触发
    graph.eventCenter?.emit('node:doDel', {
      data: node.properties.tableObject,
    })
  })
}

5.Model

之前的全部操作相当于全是做的ViewModel需要自己定义,当然你也可以不做,使用默认的,但是默认的往往无法达到你想要的效果,这边没什么好说的,你们自己去看看 Model 的 Api 即可

import { RectNodeModel, CircleNodeModel } from '@logicflow/core'
class CustomNodeModel extends RectNodeModel {
  //移入放大锚点
  getAnchorStyle(anchorInfo: any) {
    const style = super.getAnchorStyle(anchorInfo)
    style.stroke = 'rgb(24, 125, 255)'
    style.fill = '#4E88F3'
    style.r = 3
    if (!style.hover) return style
    style.hover.r = 8
    style.hover.fill = 'rgb(24, 125, 255)'
    style.hover.stroke = 'rgb(24, 125, 255)'
    return style
  }

  getOutlineStyle() {
    const style = super.getOutlineStyle()
    return {
      ...style,
      stroke: 'transparent', // 将边框颜色设为透明
      strokeDasharray: '0', // 取消虚线效果
      strokeWidth: 0, // 完全隐藏边框
    }
  }

  getNodeStyle() {
    const style = super.getNodeStyle()
    return {
      ...style, // fill: this.isSelected ? : '#fff', // 选中时浅蓝背景
      stroke: this.isSelected ? '#1890ff' : '#999', // 选中时蓝色,默认灰色
      strokeDasharray: this.isSelected ? '0' : '3,2', // 选中时实线,默认虚线
    }
  }
}
export default CustomNodeModel

6.Edge

import { BezierEdgeModel } from '@logicflow/core'
class CustomNodeLine extends BezierEdgeModel {
  getEdgeStyle() {
    const style = super.getEdgeStyle()
    style.stroke = '#4e88f3'
    // style.strokeDasharray = '3 3'   // 虚线
    style.strokeWidth = 1
    return style
  }
}
export default CustomNodeLine

边默认是黑色的,这边想好看点自己去看 Api,Api 实在太多了,说也说不完

7.注册

lf.value?.register({
  type: 'CustomNodeLine',
  view: BezierEdge,
  model: CustomNodeLine,
})

后续直接使用即可,你可以注册多个不同的自定义组件

最后,几个注意点:

  1. 当回退撤销按钮实现,即历史记录不完整,请更换插件包版本
  2. 当你的节点里面只有一张图片,拖拽时,就点击了一下,节点跟着你的鼠标到处跑,请在 img 中加入 draggable: false,不是包的问题
  3. 很多时候一定要基本遵循官方要求去操作数据, lf.value.graphModel.edgeType = 'CustomNodeLine',graphModel 的属性都是只读的,能找到他的方法去调用就去调用,一般都存在,官网找不到就去 TS 中去找,这种就是正确写法lf.value.graphModel.setDefaultEdgeType('CustomNodeLine')

自定义节点完结,如有错误,留言更正,希望对你们有帮助。


文章作者: 冷杨威
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 冷杨威 !
  目录
-->