Drawflow基础使用

Drawflow基础使用,vue组件作为节点并传递数据

Posted by catmai on March 20, 2024

AntV X6有个问题暂时没解,可以见AntV X6的问题

看了其他人的一些文章,感觉antv x6 v1版本制作复杂节点会比较复杂(在vue上)v2版本会好很多,但是目前还存在一些问题,且我作为半拉前端暂时也还没有时间去转型到vue3+ts的组合。官网上的例子又都是react的,转换起来属实是费劲。被折磨了好久 /(ㄒoㄒ)/

找了一圈又发现了一个适合vue体制的流程图插件drawflow

Github传送门

Demo❤️

内容基本上都在github中,配合作者的beautiful.css可以让节点更好看一些。这个插件对vue组件的支持性好了很多。

本文主要解决了使用VUE组件作为node后,数据传递的问题。

使用

安装

npm i drawflow

引入

import Drawflow from 'drawflow'
import styleDrawflow from 'drawflow/dist/drawflow.min.css'

创建画布容器

<div id="drawflow"></div>

初始化画布:

var id = document.getElementById("drawflow");
const editor = new Drawflow(id);
editor.start();

vue2可以采用这种方式:

重要!!!重要!!!原因请见问题一

import Vue from 'vue'

// Pass render Vue
this.editor = new Drawflow(id, Vue, this);

vue3采用以下方式:

import { h, getCurrentInstance, render } from 'vue'
const Vue = { version: 3, h, render };


this.editor = new Drawflow(id, Vue);
// Pass render Vue 3 Instance
const internalInstance = getCurrentInstance()
editor.value = new Drawflow(id, Vue, internalInstance.appContext.app._context);

editor可选配置

Parameter Type Default Description
reroute Boolean false Active reroute
reroute_fix_curvature Boolean false Fix adding points
curvature Number 0.5 Curvature
reroute_curvature_start_end Number 0.5 Curvature reroute first point and las point
reroute_curvature Number 0.5 Curvature reroute
reroute_width Number 6 Width of reroute
line_path Number 5 Width of line
force_first_input Boolean false Force the first input to drop the connection on top of the node
editor_mode Text edit edit for edit, fixed for nodes fixed but their input fields available, view for view only
zoom Number 1 Default zoom
zoom_max Number 1.6 Default zoom max
zoom_min Number 0.5 Default zoom min
zoom_value Number 0.1 Default zoom value update
zoom_last_value Number 1 Default zoom last value
draggable_inputs Boolean true Drag nodes on click inputs
useuuid Boolean false Use UUID as node ID instead of integer index. Only affect newly created nodes, do not affect imported nodes

添加节点

editor.addNode(name, inputs, outputs, posx, posy, class, data, html);

Parameter Type Description
name text Name of module
inputs number Number of de inputs
outputs number Number of de outputs
pos_x number Position on start node left
pos_y number Position on start node top
class text Added classname to de node. Multiple classnames separated by space
data json Data passed to node
html text HTML drawn on node or name of register node.
typenode boolean & text Default false, true for Object HTML, vue for vue

主要区分在html,typenode这边,如果是html节点,可以按以下方式:

var google = `
    <div>
    <div class="title-box"><i class="fab fa-google-drive"></i> Google Search </div>
    </div>
`;
this.editor.addNode('google', 1, 1, pos_x, pos_y, 'google', {}, google);

第二和第三个参数是输入桩的数量和输出桩的数量,与antv x6不同的是,这里桩的配置比较少,不能自定义桩信息。他会默认均匀分布在节点左侧(input)和右侧(output)

如果是vue组件作为节点,可以按以下方式:

//先注册节点
this.editor.registerNode('startNode', startNode, {}, {});

//添加节点
this.editor.addNode('startNode', 0, 1, pos_x, pos_y, 'startNode', {}, 'startNode', 'vue');

数据传递

往节点传输数据很方便,并且自带有双向绑定的方式。

HTML节点

例如:

var template = `
<div>
  <div class="title-box"><i class="fas fa-code"></i> Template</div>
  <div class="box">
    Ger Vars
    <textarea df-template></textarea>
    Output template with vars
  </div>
</div>
`;
editor.addNode('template', 1, 1, pos_x, pos_y, 'template', { "template": 'Write your template'}, template );

这样会渲染出一个带有textarea的节点,在addNode时,倒数第三个参数传递了一个对象,里面有template属性,可以看到在textarea上有一个df-template,这样会形成双向绑定(输入框内容和节点配置进行绑定)

导出的数据将会是如下:

{
  "drawflow": {
    "Home": {
      "data": {
        "1": {
          "id": 1,
          "name": "template",
          "data": {
            "template": "Write your template"
          },
          "class": "template",
          "html": "<div><div class='title-box'><i class='fas fa-code'></i> Template</div><div class='box'>Ger Vars<textarea df-template></textarea>Output template with var</div></div>",
          "typenode": false,
          "inputs": {
            "input_1": {
                "connections": []
            }
          },
          "outputs": {
            "output_1": {
              "connections": []
            }
          },
          "pos_x": 80,
          "pos_y": 80
        },
      }
    }
  }
}

在节点的data区,有我们传入的数据。当然,如果直接在节点中对textarea的内容进行编辑,也会使导出的json发生变化(我原称之为双向绑定)。

VUE节点

vue节点同html节点,可以通过df-xxx的配置来获取addNode时的参数。但是这里有个问题,有一些复杂节点,需要编辑一些东西(数据可能是json格式给后端的),这里就不适合让用户自己输入了。df-xxx的形式只能支持input、textarea或select双向绑定,并且是用户输入行为使值发生变化才能真正改变导出的json中data块发生变化。

说人话就是,如果想要使用js改变input的value是不生效的。editor.export()后的数据还是初始的数据。

如果是这种情况对业务复杂的节点是不友好的,这里我们需要借助vue的事件总线来解决这个问题。

//在vue组件中声明
mounted() {
  //节点基础通信
  this.$nextTick(() => {
    const id = this.$el.parentElement.parentElement.id;
    //获得节点ID
    this.nodeId = id;
    bus.$emit('getData', id);
  });
  //发送了数据过来,保存数据。
  bus.$on('sendData', (data) => {
    if (data.id === this.nodeId) {
      if (JSON.stringify(data.dataNode) != '{}') {
        console.log('填充data')
        this.dataNode = data.dataNode;
      }
      bus.$emit('refreshNode', this.nodeId);
    }
  });
},
//组件中保存数据
bus.$emit('saveData', this.nodeId, this.dataNode);
bus.$emit('refreshNode', this.nodeId);


//在画布中声明
mounted() {
    //获取到节点ID,把节点目前的data发送过去
    bus.$on('getData', (id) => {
        const dataNode = this.editor.getNodeFromId(id.slice(5)).data;
        bus.$emit('sendData', { id, dataNode })
    })
    //收到节点组件发来的保存数据请求
    bus.$on('saveData', (id, data) => {
        this.editor.drawflow.drawflow['Home'].data[id.slice(5)].data = data;
    })
    //重新渲染节点
    bus.$on('refreshNode', (id) => {
        this.editor.updateConnectionNodes(id);
    })
},

遇到的问题

问题一 TypeError: Cannot read properties of null (reading ‘version’)

错误内容如下:

TypeError: Cannot read properties of null (reading 'version')
  at i.addNode (drawflow.min.js:1:30657)
  at VueComponent.loadNode (cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/dng/index.vue?vue&type=script&lang=js:366:19)
  at VueComponent.initEditor (cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/dng/index.vue?vue&type=script&lang=js:358:12)
  at VueComponent.mounted (cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/dng/index.vue?vue&type=script&lang=js:348:10)
  at invokeWithErrorHandling (vue.runtime.esm.js:1853:57)
  at callHook (vue.runtime.esm.js:4213:7)
  at Object.insert (vue.runtime.esm.js:3136:7)
  at invokeInsertHook (vue.runtime.esm.js:6336:28)
  at VueComponent.patch [as __patch__] (vue.runtime.esm.js:6555:5)
  at Vue._update (vue.runtime.esm.js:3942:19)

解决这个问题的方案,请使用vue2的方式初始化画布!!!

如果缺省了new Drawflow后面的两个参数,会出现问题。

import Vue from 'vue'



// Pass render Vue
this.editor = new Drawflow(id, Vue, this);