vue整合logicflow

LogicFlow是什么

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

特性

  • 可视化模型:通过 LogicFlow 提供的直观可视化界面,用户可以轻松创建、编辑和管理复杂的逻辑流程图。
  • 高可定制性:用户可以根据自己的需要定制节点、连接器和样式,创建符合特定用例的定制逻辑流程图。
  • 自执行引擎:执行引擎支持浏览器端执行流程图逻辑,为无代码执行提供新思路。

 创建logicflow的vue项目

创建vue项目

vue create mylogicflow

我选择vue2

 

idea打开项目 

执行安装依赖命令

npm install

执行启动命令

npm run serve

打开如上页面证明vue项目创建成功

安装logicflow依赖

npm install @logicflow/core --save

 插件包(不使用插件时不需要引入)推荐引入

npm install @logicflow/extension --save

创建draw.vue

创建pages文件夹,并在pages下面创建draw.vue

内容如下

<template>
  <div class="container" ref="container"></div>
</template>

<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
// import "@logicflow/core/dist/style/index.css"; // 2.0版本前的引入方式

export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  mounted() {
    this.lf = new LogicFlow({
      container: this.$refs.container,
      grid: true,
    });
    this.lf.render();
  },
};
</script>

<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}
</style>

安装router

安装router

npm install vue-router@3.1.3 --force

配置 router

src 文件夹下创建一个 router 文件夹,并在其中创建一个 index.js 文件:

在index.js中添加如下代码

// 引入Vue核心库
import Vue from 'vue'
// 引入vue-router
import VueRouter from 'vue-router'
// 应用vue-router插件
Vue.use(VueRouter)

// 引入组件(方式一)
import Home from '../pages/draw.vue'

// 配置路由
const routes = [
    {
        // 路径,/ 表示首页
        path: '/',
        // 使用组件(方式一)
        component: Home
    }
    // ,
    // {
    //     // 路径
    //     path: '/about',
    //     // 引入并使用组件(方式二)
    //     component: () => import('../views/About.vue')
    // }
]

// 创建路由器
const router = new VueRouter({
    routes
})

// 导出路由器
export default router

在 main.js 中引入并使用 Vue Router

import Vue from 'vue'
import App from './App.vue'
import router from './router';  // 引入 router 配置
Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

修改app.vue

<template>
  <router-view></router-view>
</template>

<script>
export default {
  name: 'App',
  components: {
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

安装element ui

npm install element-ui@2.15.7

 在mian.js中使用

import Vue from 'vue'
import App from './App.vue'
import router from './router';  // 引入 router 配置
Vue.config.productionTip = false
import Element from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(Element)
new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

启动项目

logicflow实操

添加控件(render之前)

系统默认控件

<template>
  <div class="container" ref="container"></div>
</template>

<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  mounted() {
    this.lf = new LogicFlow({
      container: this.$refs.container,
      plugins: [Control],
      grid: true,
    });
    this.lf.render();
  },
};
</script>

<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}
</style>

为什么会有上面默认控件,查看源码

node_modules/@logicflow/extension/src/components/control/index.ts

自定义控件 

<template>
  <el-container>
     <div class="container" ref="container"></div>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  mounted() {
    this.lf = new LogicFlow({
      container: this.$refs.container,
      plugins: [Control],
      grid: true,
    });
    this.init()
  },
  methods: {
    init(){//el-icon-delete是element图标 icon-delete是自定义属性
      this.lf.extension.control.addItem({
        key: 'clear',
        iconClass: 'el-icon-delete icon-delete',
        title: "清空画板",
        text: "清空画板",
        onClick: () => {
          console.log('清空画板')
        }
      })
      this.lf.render();
    }
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}
.icon-delete {
  font-size: 30px;
}
</style>

删除控件 (render之前)

<template>
  <el-container>
     <div class="container" ref="container"></div>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  mounted() {
    this.lf = new LogicFlow({
      container: this.$refs.container,
      plugins: [Control],
      grid: true,
    });
    this.init()
  },
  methods: {
    init(){//el-icon-delete是element图标 icon-delete是自定义属性
      this.lf.extension.control.addItem({
        key: 'clear',
        iconClass: 'el-icon-delete icon-delete',
        title: "清空画板",
        text: "清空画板",
        onClick: () => {
          console.log('清空画板')
        }
      })
      //删除缩小按钮
      this.lf.extension.control.removeItem('zoom-out')
      this.lf.render();
    }
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}
.icon-delete {
  font-size: 30px;
}
</style>

 添加拖拽面板

<template>
  <el-container>
     <div class="container" ref="container"></div>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control,DndPanel, SelectionSelect } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  mounted() {
    this.lf = new LogicFlow({
      container: this.$refs.container,
      plugins: [Control,DndPanel, SelectionSelect],
      grid: true,
    });
    this.init()
  },
  methods: {
    init(){
      var lf = this.lf
      lf.extension.dndPanel.setPatternItems([
        {
          label: '选区',
          icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAOVJREFUOBGtVMENwzAIjKP++2026ETdpv10iy7WFbqFyyW6GBywLCv5gI+Dw2Bluj1znuSjhb99Gkn6QILDY2imo60p8nsnc9bEo3+QJ+AKHfMdZHnl78wyTnyHZD53Zzx73MRSgYvnqgCUHj6gwdck7Zsp1VOrz0Uz8NbKunzAW+Gu4fYW28bUYutYlzSa7B84Fh7d1kjLwhcSdYAYrdkMQVpsBr5XgDGuXwQfQr0y9zwLda+DUYXLaGKdd2ZTtvbolaO87pdo24hP7ov16N0zArH1ur3iwJpXxm+v7oAJNR4JEP8DoAuSFEkYH7cAAAAASUVORK5CYII=',
          callback: () => {
            lf.extension.selectionSelect.openSelectionSelect();
            lf.once('selection:selected', () => {
              lf.extension.selectionSelect.closeSelectionSelect();
            });
          }
        },
        {
          type: 'circle',
          text: '开始',
          label: '开始节点',
          icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv+9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBg+b1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW+/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga+8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQms+xrB/nNSUWVveI48ayrFGyC2+E2C+aWrZHXvOuz+CiV6iycWe1Rd1Q6+QUG07nb5SbPrL4426d+9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47X+oepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3u+sDFMVrIju54RYYbFKpALZAo7sB6wcKyyrd+aBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0z+Pnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCd+lQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGh+l+PE/wG0rgfIUbZK5AAAAABJRU5ErkJggg==',
        },
        {
          type: 'rect',
          label: '用户任务',
          icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
          className: 'important-node'
        },
        {
          type: 'rect',
          label: '系统任务',
          icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
          className: 'import_icon'
        },
        {
          type: 'diamond',
          label: '条件判断',
          icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAAHeEJUAAAAABGdBTUEAALGPC/xhBQAAAvVJREFUOBGNVEFrE0EU/mY3bQoiFlOkaUJrQUQoWMGePLX24EH0IIoHKQiCV0G8iE1covgLiqA/QTzVm1JPogc9tIJYFaQtlhQxqYjSpunu+L7JvmUTU3AgmTfvffPNN++9WSA1DO182f6xwILzD5btfAoQmwL5KJEwiQyVbSVZ0IgRyV6PTpIJ81E5ZvqfHQR0HUOBHW4L5Et2kQ6Zf7iAOhTFAA8s0pEP7AXO1uAA52SbqGk6h/6J45LaLhO64ByfcUzM39V7ZiAdS2yCePPEIQYvTUHqM/n7dgQNfBKWPjpF4ISk8q3J4nB11qw6X8l+FsF3EhlkEMfrjIer3wJTLwS2aCNcj4DbGxXTw00JmAuO+Ni6bBxVUCvS5d9aa04+so4pHW5jLTywuXAL7jJ+D06sl82Sgl2JuVBQn498zkc2bGKxULHjCnSMadBKYDYYHAtsby1EQ5lNGrQd4Y3v4Zo0XdGEmDno46yCM9Tk+RiJmUYHS/aXHPNTcjxcbTFna000PFJHIVZ5lFRqRpJWk9/+QtlOUYJj9HG5pVFEU7zqIYDVsw2s+AJaD8wTd2umgSCCyUxgGsS1Y6TBwXQQTFuZaHcd8gAGioE90hlsY+wMcs30RduYtxanjMGal8H5dMW67dmT1JFtYUEe8LiQLRsPZ6IIc7A4J5tqco3T0pnv/4u0kyzrYUq7gASuEyI8VXKvB9Odytv6jS/PNaZBln0nioJG/AVQRZvApOdhjj3Jt8QC8Im09SafwdBdvIpztpxWxpeKCC+EsFdS8DCyuCn2munFpL7ctHKp+Xc5cMybeIyMAN33SPL3ZR9QV1XVwLyzHm6Iv0/yeUuUb7PPlZC4D4HZkeu6dpF4v9j9MreGtMbxMMRLIcjJic9yHi7WQ3yVKzZVWUr5UrViJvn1FfUlwe/KYVfYyWRLSGNu16hR01U9IacajXPei0wx/5BqgInvJN+MMNtNme7ReU9SBbgntovn0kKHpFg7UogZvaZiOue/q1SBo9ktHzQAAAAASUVORK5CYII=',
        },
        {
          type: 'circle',
          text: '结束',
          label: '结束节点',
          icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOf+tq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0M+jpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPb+mKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB+/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd+33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwS+t1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9+EKCAYhe1NGstUa6356kS9NVvt3DU2fd+Wtbm/+lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXM+XaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQ+pqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOe+bX3FClY4PwydVhthOZ9NnS+ntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth+7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIA+lwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2+MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC',
        }
      ]);
      
      this.lf.render();
    },
    
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}

</style>

 自定义拖拽节点

自定义五个节点

CustomAnd.js

import {HtmlNode, HtmlNodeModel} from '@logicflow/core'
//自定义and节点
// 提供节点 svg dom
class CustomAndNode extends HtmlNode {
  constructor(props){
    props.model.text.y = props.model.y + 20
    super(props)
  }
  setHtml(rootEl) {
    const { properties } = this.props.model;

    const el = document.createElement("div");
    el.className = "uml-wrapper";
    el.innerHTML = `
    <img width="58" src='${properties.status === 'COMPLETED' ? require('./img/and1.png') : (properties.status === 'FAILED' ? require('./img/and0.png') : require('./img/and.png'))}' alt=""/>
    `;
    rootEl.innerHTML = "";
    rootEl.appendChild(el);
  }
}

// 提供节点的样式
class CustomAndModel extends HtmlNodeModel {
  setAttributes() {
    const width = 58;
    const height = 80;
    this.width = width;
    this.height = height;
    this.anchorsOffset = [
      [width / 2, -15],
      [0, height / 2 - 30],
      [-width / 2, -15],
      [0, -height / 2],
    ];
  }
}

export default {
  type: 'CustomAnd',
  view: CustomAndNode,
  model: CustomAndModel
}

 CustomBusiness.js

import {HtmlNode, HtmlNodeModel} from '@logicflow/core'
//自定义业务节点
// 提供节点
class CustomBusinessNode extends HtmlNode {
  constructor(props){
    props.model.text.y = props.model.y + 20
    super(props)
  }
    
  setHtml(rootEl) {
    const { properties } = this.props.model;

    const el = document.createElement("div");
    el.className = "uml-wrapper";
    el.innerHTML = `
    <img width="58" src='${properties.status === 'COMPLETED' ? require('./img/node1.png') : (properties.status === 'FAILED' ? require('./img/node0.png') : require('./img/node.png'))}'  alt=""/>
    `;
    rootEl.innerHTML = "";
    rootEl.appendChild(el);
  }
}

// 提供节点的属性
class CustomBusinessModel extends HtmlNodeModel {
  setAttributes() {
    const width = 58;
    const height = 80;
    this.width = width;
    this.height = height;
    this.anchorsOffset = [
      [width / 2, -15],
      [0, height / 2 - 30],
      [-width / 2, -15],
      [0, -height / 2],
    ];
    // 业务节点文本不可编辑
    this.text.editable=false
  }
}

export default {
  type: 'CustomBusiness',
  view: CustomBusinessNode,
  model: CustomBusinessModel
}

CustomEnd.js

// CustomRectNode.js
import {HtmlNode, HtmlNodeModel} from '@logicflow/core'
//自定义结束节点
// 提供节点 svg dom
class CustomEndNode extends HtmlNode {
  constructor(props){
    props.model.text.y = props.model.y + 20
    super(props)
  }
    
  setHtml(rootEl) {
    const { properties } = this.props.model;
    const el = document.createElement("div");
    el.className = "uml-wrapper";
    el.innerHTML = `
    <img width="58" src='${properties.status === 'COMPLETED' ? require('./img/end1.png') : (properties.status === 'FAILED' ? require('./img/end0.png') : require('./img/end.png'))}' alt=""/>
    `;
    rootEl.innerHTML = "";
    rootEl.appendChild(el);
  }
}

// 提供节点的样式
class CustomEndModel extends HtmlNodeModel {
  setAttributes() {
    const width = 58;
    const height = 80;
    this.width = width;
    this.height = height;
    this.anchorsOffset = [
      [width / 2, -15],
      [0, height / 2 - 30],
      [-width / 2, -15],
      [0, -height / 2],
    ];
  }
}

export default {
  type: 'CustomEnd',
  view: CustomEndNode,
  model: CustomEndModel
}

CustomIf.js

import {HtmlNode, HtmlNodeModel} from '@logicflow/core'
//自定义条件节点
// 提供节点
class CustomIfNode extends HtmlNode {
  constructor(props){
    props.model.text.y = props.model.y + 20
    super(props)
  }
    
  setHtml(rootEl) {
    const { properties } = this.props.model;
    const el = document.createElement("div");
    el.className = "uml-wrapper";
    el.innerHTML = `
    <img width="58" src='${properties.status === 'COMPLETED' ? require('./img/if1.png') : (properties.status === 'FAILED' ? require('./img/if0.png') : require('./img/if.png'))}' alt=""/>
    `;
    rootEl.innerHTML = "";
    rootEl.appendChild(el);
  }
}

// 提供节点的属性
class CustomIfModel extends HtmlNodeModel {
  setAttributes() {
    const width = 58;
    const height = 80;
    this.width = width;
    this.height = height;
    this.anchorsOffset = [
      [width / 2, -15],
      [0, height / 2 - 30],
      [-width / 2, -15],
      [0, -height / 2],
    ];
  }
}

export default {
  type: 'CustomIf',
  view: CustomIfNode,
  model: CustomIfModel
}

CustomStart.js

import {HtmlNode, HtmlNodeModel} from '@logicflow/core'
//自定义开始节点
// 提供节点 svg dom
class CustomStartNode extends HtmlNode {
  constructor(props){
    props.model.text.y = props.model.y + 20
    super(props)
  }
  setHtml(rootEl) {
    const { properties } = this.props.model;
    const el = document.createElement("div");
    el.className = "start";
    el.innerHTML = `
    <img width="58" src='${properties.status === 'COMPLETED' ? require('./img/start1.png') : (properties.status === 'FAILED' ? require('./img/start0.png') : require('./img/start.png'))}' alt=""/>
    `;
    rootEl.innerHTML = "";
    rootEl.appendChild(el);
  }
}


class CustomStartModel extends HtmlNodeModel {
  setAttributes() {
    const width = 58;
    const height = 80;
    this.width = width;
    this.height = height;
    this.anchorsOffset = [
      [width / 2, -15],
      [0, height / 2 - 30],
      [-width / 2, -15],
      [0, -height / 2],
    ];
  }
}

export default {
  type: 'CustomStart',
  view: CustomStartNode,
  model: CustomStartModel
}

把自定义节点存放到index.js

import CustomStart from './CustomStart'
import CustomEnd from './CustomEnd'
import CustomAnd from './CustomAnd'
import CustomIf from './CustomIf'
import CustomBusiness from './CustomBusiness'
const Nodes = {
    CustomStart,
    CustomEnd,
    CustomAnd,
    CustomIf,
    CustomBusiness,
}
export default Nodes

自定义面板存放节点

CustomNodePanel.vue

<template>
  <div class="node-panel" v-if="showPanel">
    <div class="node-item" @mousedown="addStartNode()" title="开始节点只能有一个出口,不能有入口">
      <img src="../registerNode/img/start.png" width="48px" :draggable="false" alt="" >
      <span class="node-label">开始</span>
    </div>
    <div class="node-item" @mousedown="addBusinessNode()" title="业务节点至少有一个入口,至少有一个出口">
      <img src="../registerNode/img/node.png" width="48px" :draggable="false" alt="" >
      <span class="node-label">业务能力</span>
    </div>
    <div class="node-item" @mousedown="addIfNode()" title="分支条件节点只能有一个入口,可以有多个出口">
      <img src="../registerNode/img/if.png" width="48px" :draggable="false" alt="" >
      <span class="node-label">分支条件</span>
    </div>
    <div class="node-item" @mousedown="addAndNode()" title="并行汇聚节点只能有一个出口,可以有多个出口">
      <img src="../registerNode/img/and.png" width="48px" :draggable="false" alt="" >
      <span class="node-label">并行汇聚</span>
    </div>
    <div class="node-item" @mousedown="addEndNode()" title="结束节点至少有一个入口,不能有出口">
      <img src="../registerNode/img/end.png" width="48px" :draggable="false" alt="" >
      <span class="node-label">结束</span>
    </div>
  </div>
</template>
<script>
// 注册节点
import customNodeTypes from '../registerNode/index'
import CustomPolyline from '../registerLine/CustomPolyline'

export default {
  name: 'CustomNodePanel',
  data () {
    return {
      lf: null,
    }
  },
  props: {
    graphData: Object,
    showPanel: Boolean
  },
  inject: ['drawThis'],
  mounted () {
    // 注册节点
    this.registerNode()
    if (this.graphData) {
      this.lf.renderRawData(this.graphData)
    }
  },
  methods: {
    registerNode () {
      //创建画板
      this.drawThis.lf = this.drawThis.init()
      //注册自定义节点类型
      this.drawThis.lf.register(customNodeTypes.CustomAnd)
      this.drawThis.lf.register(customNodeTypes.CustomEnd)
      this.drawThis.lf.register(customNodeTypes.CustomBusiness)
      this.drawThis.lf.register(customNodeTypes.CustomIf)
      this.drawThis.lf.register(customNodeTypes.CustomStart)
      this.drawThis.lf.register(CustomPolyline)
      this.lf =this.drawThis.lf
      this.drawThis.refreshGraph()
    },
    addStartNode () {
      (this.lf).dnd.startDrag({
        type: 'CustomStart',
        text: '开始',
        properties: { type: 'start' } 
      })
    },
    addBusinessNode () {
      (this.lf).dnd.startDrag({
        type: 'CustomBusiness',
        text: '业务能力',
        properties: { type: 'business' } 
      })
    },
    addIfNode () {
      (this.lf).dnd.startDrag({
        type: 'CustomIf',
        text: '条件',
        properties: { type: 'if' }
      })
    },
    addEndNode () {
      (this.lf).dnd.startDrag({
        type: 'CustomEnd',
        text: '结束',
        properties: { type: 'end', referenceName:'end' }
      })
    },
    addAndNode () {
      (this.lf).dnd.startDrag({
        type: 'CustomAnd',
        text: '合并',
        properties: { type: 'and' }
      })
    }
  }
}
</script>
<style>
.node-panel {
  position: absolute;
  top: 54px;
  left: 24px;
  width: 50px;
  padding: 10px;
  background-color: white;
  box-shadow: 0 0 10px 1px rgb(228, 224, 219);
  border-radius: 6px;
  text-align: center;
  z-index: 101;
}
.node-item {
  margin-bottom: 10px;
}
.node-label {
  font-size: 12px;
  margin-top: 5px;
  user-select: none;
}
.lf-html-wrapper .custom-html {
  width:96px;
  height:96px;
  border:2px solid #2987ff;
}
.lf-html-wrapper .custom-head {
  color:red;
  height:26px;
  line-height:26px;
}
.lf-html-wrapper .custom-body {
  color:#2987ff;
  height:50px;
  line-height:50px;
}
</style>

使用自定义节点

<template>
  <el-container>
     <div class="container" ref="container"></div>
    <CustomNodePanel :graphData="graphData" :showPanel="showPanel" ref="drawThis"></CustomNodePanel>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control,DndPanel, SelectionSelect } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
import CustomNodePanel from "./components/CustomNodePanel.vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  components: { CustomNodePanel },
  provide() {
    return {
      drawThis: this
    }
  },
  data() {
    return {
      lf: null,
      graphData: {

      },
      showPanel: true
    }
  },

  methods: {
    init(){
      return  new LogicFlow({
        container: this.$refs.container,
        plugins: [Control,DndPanel, SelectionSelect],
        grid: true,
      });
    },
    refreshGraph(){
      this.lf.render();
    }
    
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}

</style>

系统线条 

系统自带的类型

// 直线
import { LineEdge, PolylineEdgeModel } from "@logicflow/core";
// 折线
import { PolylineEdge, PolylineEdgeModel } from "@logicflow/core";
// 贝塞尔曲线
import { BezierEdge, BezierEdgeModel } from "@logicflow/core";

对应类型名称

export type EdgeType = 'line' | 'polyline' | 'bezier' | string

使用系统线条

<template>
  <el-container>
     <div class="container" ref="container"></div>
    <CustomNodePanel :graphData="graphData" :showPanel="showPanel" ref="drawThis"></CustomNodePanel>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control,DndPanel, SelectionSelect } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
import CustomNodePanel from "./components/CustomNodePanel.vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  components: { CustomNodePanel },
  provide() {
    return {
      drawThis: this
    }
  },
  data() {
    return {
      lf: null,
      graphData: {

      },
      showPanel: true
    }
  },

  methods: {
    init(){
      return  new LogicFlow({
        container: this.$refs.container,
        plugins: [Control,DndPanel, SelectionSelect],
        grid: true,
        edgeType: 'bezier'
      });
    },
    refreshGraph(){
      this.lf.render();
    }
    
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}

</style>

自定义线条 

CustomPolyline.js

import {PolylineEdge, PolylineEdgeModel} from "@logicflow/core";

class CustomPolylineModel extends PolylineEdgeModel {
    setAttributes() {
        this.offset = 20;
        this.text.editable=false
    }
    //设置线样式
    getEdgeStyle() {
        const style = super.getEdgeStyle();
        const { properties } = this;
        if (properties.isActived) {
            style.strokeDasharray = "4 4";
        }
        style.stroke = "#d51b1b";
        return style;
    }
    //设置文字样色
    getTextStyle() {
        //设置文字样色
        const style = super.getTextStyle();
        // style.color = "#3451F1";
        // style.fontSize = 30;
        // style.background.fill = "#F2F131";
        // console.log(style)
        return style;
    }
    //鼠标悬浮时样式
    getOutlineStyle() {
        const style = super.getOutlineStyle();
        // style.stroke = "";
        // style.hover.stroke = "red";
        return style;
    }
}

export default {
    type: "CustomPolyline",
    view: PolylineEdge,
    model: CustomPolylineModel,
};

注册

import CustomPolyline from '../registerLine/CustomPolyline'
this.drawThis.lf.register(CustomPolyline)

使用

<template>
  <el-container>
     <div class="container" ref="container"></div>
    <CustomNodePanel :graphData="graphData" :showPanel="showPanel" ref="drawThis"></CustomNodePanel>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control,DndPanel, SelectionSelect } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
import CustomNodePanel from "./components/CustomNodePanel.vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  components: { CustomNodePanel },
  provide() {
    return {
      drawThis: this
    }
  },
  data() {
    return {
      lf: null,
      graphData: {

      },
      showPanel: true
    }
  },

  methods: {
    init(){
      return  new LogicFlow({
        container: this.$refs.container,
        plugins: [Control,DndPanel, SelectionSelect],
        grid: true,
        edgeType: 'CustomPolyline'
      });
    },
    refreshGraph(){
      this.lf.render();
    }
    
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}

</style>

菜单 

import LogicFlow from "@logicflow/core";
import { Menu } from "@logicflow/extension";
import "@logicflow/extension/lib/style/index.css";

LogicFlow.use(Menu);

添加默认菜单

<template>
  <el-container>
     <div class="container" ref="container"></div>
    <CustomNodePanel :graphData="graphData" :showPanel="showPanel" ref="drawThis"></CustomNodePanel>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control,DndPanel, SelectionSelect,Menu  } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
import CustomNodePanel from "./components/CustomNodePanel.vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  components: { CustomNodePanel },
  provide() {
    return {
      drawThis: this
    }
  },
  data() {
    return {
      lf: null,
      graphData: {

      },
      showPanel: true
    }
  },

  methods: {
    init(){
      return  new LogicFlow({
        container: this.$refs.container,
        plugins: [Control,DndPanel, SelectionSelect,Menu ],
        grid: true,
        edgeType: 'CustomPolyline'
      });
    },
    refreshGraph(){
      this.lf.render();
    }
    
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}

</style>

添加自定义菜单 

<template>
  <el-container>
     <div class="container" ref="container"></div>
    <CustomNodePanel :graphData="graphData" :showPanel="showPanel" ref="drawThis"></CustomNodePanel>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control,DndPanel, SelectionSelect,Menu  } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
import CustomNodePanel from "./components/CustomNodePanel.vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  components: { CustomNodePanel },
  provide() {
    return {
      drawThis: this
    }
  },
  data() {
    return {
      lf: null,
      graphData: {

      },
      showPanel: true
    }
  },

  methods: {
    init(){
      return  new LogicFlow({
        container: this.$refs.container,
        plugins: [Control,DndPanel, SelectionSelect,Menu ],
        grid: true,
        edgeType: 'CustomPolyline'
      });
    },
    refreshGraph(){
      this.addMenu()
      this.lf.render();
    },
    addMenu(){
      this.lf.extension.menu.addMenuConfig({
        nodeMenu: [
          {
            text: "分享",
            callback() {
              alert("分享成功!");
            },
          },
          {
            text: "属性",
            callback(node) {
              alert(`
          节点id:${node.id}
          节点类型:${node.type}
          节点坐标:(x: ${node.x}, y: ${node.y})`);
            },
          },
        ],
        edgeMenu: [
          {
            text: "属性",
            callback(edge) {
              alert(`
          边id:${edge.id}
          边类型:${edge.type}
          边坐标:(x: ${edge.x}, y: ${edge.y})
          源节点id:${edge.sourceNodeId}
          目标节点id:${edge.targetNodeId}`);
            },
          },
        ],
        graphMenu: [
          {
            text: "分享",
            callback() {
              alert("分享成功!");
            },
          },
        ],
      });
    }
    
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}

</style>

事件 

事件列表

添加事件

<template>
  <el-container>
     <div class="container" ref="container"></div>
    <CustomNodePanel :graphData="graphData" :showPanel="showPanel" ref="drawThis"></CustomNodePanel>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control,DndPanel, SelectionSelect,Menu  } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
import CustomNodePanel from "./components/CustomNodePanel.vue";
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  components: { CustomNodePanel },
  provide() {
    return {
      drawThis: this
    }
  },
  data() {
    return {
      lf: null,
      graphData: {

      },
      showPanel: true
    }
  },
  methods: {
    init(){
      return  new LogicFlow({
        container: this.$refs.container,
        plugins: [Control,DndPanel, SelectionSelect,Menu ],
        grid: true,
        edgeType: 'CustomPolyline'
      });
    },
    refreshGraph(){
      this.addEvent()
      this.lf.render();
    },
    addEvent(){
      this.lf.on("node:dnd-add", (data) => {
        console.log(data)
      });
    }
  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}

</style>

线条鼠标悬浮展示文字

创建LineTipsDialog.vue

<template>
    <div
        v-if="isTooltipVisible"
        :style="{ left: tooltipPosition.x + 'px', top: tooltipPosition.y + 'px' }"
        class="tooltip">
      <div v-html="showText"></div>
    </div>
</template>
<script>
export default {
  data() {
    return {
      showText: '',
      isTooltipVisible: false,  // 控制提示框的显示/隐藏
      tooltipPosition: { x: 0, y: 0 },  // 提示框的位置
    };
  },
  methods: {
    // 处理鼠标移动事件
    open(properties,event) {
      this.showText = 'flag=>'+properties.flag+'<br>resultDesc=>'+properties.resultDesc;
      this.tooltipPosition.x = event.pageX + 10;  // 提示框水平位置,略微偏移
      this.tooltipPosition.y = event.pageY + 10;  // 提示框垂直位置,略微偏移
      this.isTooltipVisible = true;  // 鼠标移动时显示提示框
    },
    // 处理鼠标离开事件
    close() {
      this.showText = '';
      this.isTooltipVisible = false;  // 鼠标离开时隐藏提示框
    },
  }
};
</script>
<style scoped>
.tooltip {
  position: absolute;
  padding: 5px;
  color: #6c7375;
  border-radius: 5px;
  pointer-events: none; /* 让鼠标事件透过悬浮框 */
}
</style>

 引用

import LineTipsDialog from './components/LineTipsDialog.vue'
components: {
    LineTipsDialog
  },
    <line-tips-dialog ref="lineTipsDialog"></line-tips-dialog>

 使用

//鼠标进入线条显示文字
      this.lf.on('edge:mouseenter', (data) => {
        if (data.data.properties.flag&&data.data.properties.flag!==''){
          this.$refs.lineTipsDialog.open(data.data.properties,data.e);
        }
      });
      //鼠标离开线条关闭文字
      this.lf.on('edge:mouseleave', (data) => {
        if (data.data.properties.flag&&data.data.properties.flag!==''){
          this.$refs.lineTipsDialog.close();
        }
      });

 

一键美化 

安装插件

npm install @antv/layout@0.3.1

创建工具beauty.js

// 引入布局模块
const { DagreLayout } = require('@antv/layout');

class BeautyUtils {
    static pluginName = 'dagre';
    lf;
    option;
    upIndexMap = {};
    downIndexMap ={};

    constructor(lf) {
        this.lf = lf;
    }

    getBytesLength(word) {
        if (!word) {
            return 0;
        }
        let totalLength = 0;
        for (let i = 0; i < word.length; i++) {
            const c = word.charCodeAt(i);
            if ((word.match(/[A-Z]/))) {
                totalLength += 1.5;
            } else if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
                totalLength += 1;
            } else {
                totalLength += 2;
            }
        }
        return totalLength;
    }

    /**
     * option: {
     *   rankdir: "TB", // layout 方向, 可选 TB, BT, LR, RL
     *   align: undefined, // 节点对齐方式,可选 UL, UR, DL, DR
     *   nodeSize: undefined, // 节点大小
     *   nodesepFunc: undefined, // 节点水平间距(px)
     *   ranksepFunc: undefined, // 每一层节点之间间距
     *   nodesep: 40, // 节点水平间距(px) 注意:如果有grid,需要保证nodesep为grid的偶数倍
     *   ranksep: 40, // 每一层节点之间间距 注意:如果有grid,需要保证ranksep为grid的偶数倍
     *   controlPoints: false, // 是否保留布局连线的控制点
     *   radial: false, // 是否基于 dagre 进行辐射布局
     *   focusNode: null, // radial 为 true 时生效,关注的节点
     * };
     */
    layout(option = {}) {
        this.upIndexMap = {};
        this.downIndexMap = {};
        const { nodes, edges, gridSize } = this.lf.graphModel;
        // 为了保证生成的节点在girdSize上,需要处理一下。
        let nodesep = 40;
        let ranksep = 40;
        if (gridSize > 20) {
            nodesep = gridSize * 2;
            ranksep = gridSize * 2;
        }
        this.option = {
            type: 'dagre',
            rankdir: 'LR',
            // align: 'UL',
            align: 'UR',
            // align: 'DR',
            nodesep,
            ranksep,
            begin: [120, 120],
            ...option,
        };
        const layoutInstance = new DagreLayout(this.option);
        const layoutData = layoutInstance.layout({
            nodes: nodes.map((node) => ({
                id: node.id,
                size: {
                    width: node.width,
                    height: node.height,
                },
                model: node,
            })),
            edges: edges.map((edge) => ({
                source: edge.sourceNodeId,
                target: edge.targetNodeId,
                model: edge,
            })),
        });
        const newGraphData = {
            nodes: [],
            edges: [],
        };
        layoutData.nodes.forEach(node => {
            const { model } = node;
            const data = model.getData();
            data.x = node.x;
            data.y = node.y;
            newGraphData.nodes.push(data);
        });
        layoutData.edges.forEach(edge => {
            const { model } = edge;
            const data = model.getData();
            data.pointsList = this.calcPointsList(model, newGraphData.nodes);
            if (data.pointsList) {
                let first = data.pointsList[0];
                const second = data.pointsList[1];
                const third = data.pointsList[2];
                let last = data.pointsList[3];
                //调整线的出口和入口位置
                if (first.y===second.y){
                    //如果是水平出线
                    if (first.x>second.x){
                        data.pointsList[0].x = data.pointsList[0].x - 29;
                    }else {
                        data.pointsList[0].x = data.pointsList[0].x + 29;
                    }
                }else {
                    //如果是垂直出线
                    if (first.y>second.y){
                        data.pointsList[0].y = data.pointsList[0].y - 15;
                    }else {
                        data.pointsList[0].y = data.pointsList[0].y + 15;
                    }
                }

                if (third.y===last.y){
                    data.pointsList[2].y = data.pointsList[2].y - 14;
                    data.pointsList[3].y = data.pointsList[3].y - 14;
                    //如果是水平入线
                    if (third.x>last.x){
                        data.pointsList[3].x = data.pointsList[3].x + 29;
                    }else {
                        data.pointsList[3].x = data.pointsList[3].x - 29;
                    }
                }else {
                    //如果是垂直出线
                    if (third.y>last.y){
                        data.pointsList[3].y = data.pointsList[3].y + 15;
                    }else {
                        data.pointsList[3].y = data.pointsList[3].y - 37;
                    }
                }

                first = data.pointsList[0];
                last = data.pointsList[3];

                // 计算文字的位置
                let textX = second.x + (third.x - second.x) / 2;
                if (second.x>=third.x){
                    textX = third.x + (second.x - third.x) / 2;
                }
                let textY = second.y + (third.y - second.y) / 2;
                if (second.y>=third.y){
                    textY = third.y + (second.y - third.y) / 2;
                }
                data.startPoint = { x: first.x, y: first.y };
                data.endPoint = { x: last.x, y: last.y };
                if (data.text && data.text.value) {
                    data.text = {
                        x: textX - this.getBytesLength(data.text.value) * 6 - 10,
                        y: textY,
                        value: data.text.value,
                    };
                }
            } else {
                data.startPoint = undefined;
                data.endPoint = undefined;
                if (data.text && data.text.value) {
                    data.text = data.text.value;
                }
            }
            newGraphData.edges.push(data);
        });
        // 调整节点上文字的位置
        newGraphData.nodes.forEach(node => {
            node.text.x = node.x
            node.text.y = node.y+20
        })
        this.lf.render(newGraphData);
    }


    // 在节点确认从左向右后,通过计算来保证节点连线清晰。
    //线比点的y少15 x多29或少29
    calcPointsList(model, nodes) {
        const pointsList = [];
        if (this.option.rankdir === 'LR' && model.modelType === 'polyline-edge') {
            const newSourceNodeData = nodes.find(node => node.id === model.sourceNodeId);
            const newTargetNodeData = nodes.find(node => node.id === model.targetNodeId);
            const sourceX = newSourceNodeData.x;
            const sourceY = newSourceNodeData.y;
            const targetX = newTargetNodeData.x;
            const targetY = newTargetNodeData.y;
            //如果节点在同一行或者同一列,则直接连线
            pointsList.push({
                x: sourceX,
                y: sourceY,
            });
            const offset = this.getOffset(targetX, targetY);
            if (sourceY===targetY){
                //偏移量
                pointsList.push({
                    x: sourceX,
                    y: targetY+offset,
                });
                pointsList.push({
                    x: targetX,
                    y: targetY+offset,
                });
            }else {
                pointsList.push({
                    x: targetX+offset,
                    y: sourceY,
                });
                pointsList.push({
                    x: targetX+offset,
                    y: targetY,
                });
            }
            pointsList.push({
                x: targetX,
                y: targetY,
            });
            return pointsList;
        }
        return undefined;
    }

    //参数是目标节点的坐标
    getOffset(x,y){
        let key = x+','+y;
        let offset = 0;
        if (!this.upIndexMap[key]){
            this.upIndexMap[key]=0
        }
        if (!this.downIndexMap[key]){
            this.downIndexMap[key]=0
        }
        if (this.upIndexMap[key]>this.downIndexMap[key]){
            this.downIndexMap[key]++
            offset = -80*this.downIndexMap[key]
        }else {
            this.upIndexMap[key]++
            offset = 80*this.upIndexMap[key]
        }
        return offset;
    }
}
module.exports = BeautyUtils;

使用

<template>
  <el-container>
     <div class="container" ref="container"></div>
    <CustomNodePanel :graphData="graphData" :showPanel="showPanel" ref="drawThis"></CustomNodePanel>
  </el-container>
</template>
<script>
import LogicFlow from "@logicflow/core";
import "@logicflow/core/lib/style/index.css";
import { Control,DndPanel, SelectionSelect,Menu,MiniMap  } from "@logicflow/extension";
import "@logicflow/extension/lib/index.css";
import '@logicflow/extension/lib/style/index.css';
import CustomNodePanel from "./components/CustomNodePanel.vue";
import beauty from './utils/beauty'
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: "draw",
  components: { CustomNodePanel },
  provide() {
    return {
      drawThis: this
    }
  },
  data() {
    return {
      lf: null,
      graphData: {
        nodes: [],
        edges: []
      },
      showPanel: true
    }
  },
  methods: {
    init(){
      return  new LogicFlow({
        container: this.$refs.container,
        plugins: [Control,DndPanel, SelectionSelect,Menu,MiniMap  ],
        grid: true,
        edgeType: 'CustomPolyline'
      });
    },
    refreshGraph(){
      this.lf.extension.control.addItem({
        key: 'clear',
        iconClass: 'el-icon-refresh icon-delete',
        title: "一键美化",
        text: "一键美化",
        onClick: () => {
          this.beauty()
        }
      })
      this.lf.render();
    },
    beauty() {
      console.log(this.lf.getGraphData())
      new beauty(this.lf).layout()
    },

  },
};
</script>
<style scoped>
.container {
  display: flex;
  height: 800px;
  flex-grow: 1; /*铺满剩余空间*/
  border: 3px solid #ababab;
  overflow: hidden;
}

</style>

测试

如果启动报错

先查看是否安装了该依赖

npm list @babel/plugin-transform-private-methods

 不存在的话就安装

npm install @babel/plugin-transform-private-methods

 在babel.config.js中添加插件

重新启动即可 

无连线

有连线

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值