基于乐吾乐meta2d从零实现可视化流程图编辑器(十一)

概要

可视化编辑器已成为前端发展趋势,相关产品层出不穷,但是用户较难根据自身需求去完整实现一个功能较为全面的可视化编辑器,我将采用乐吾乐开源的meta2d.js可视化库来实现一个简单的流程图编辑器,通过这个案例来介绍meta2d的相关功能,并向读者展示如何用meta2d从零出发搭建一个较为完整的项目,让我们在实际项目中来体验meta2d的强大之处吧。

请添加图片描述

什么是乐吾乐meta2d.js

meta2d是乐吾乐开源的2D图元组成的可视化引擎,集实时数据展示、动态交互、数据管理等一体的全功能2D可视化引擎。能够快速实现数字孪生、大屏可视化、Web组态、SCADA等解决方案。具有实时监控、多样、变化、动态交互、高效、可扩展、支持自动算法、跨平台等特点,最大程度减少研发和运维的成本,并致力于普通业务人员 0 代码开发实现物联网、工业互联网、电力能源、水利工程、智慧农业、智慧医疗、智慧城市等可视化解决方案。

乐吾乐已将其meta2d核心库完全免费开源,本系列教程就是基于meta2d从零实现web端可视化流程图编辑器。

乐吾乐 meta2d开源项目地址:https://github.com/le5le-com/meta2d.js

乐吾乐 meta2d官方文档:https://doc.le5le.com/document/119359590

项目地址

此可视化流程图编辑器项目地址:github.com/Grnetsky/me…

在线体验地址: http://editor.xroot.top/

往期教程

  1. 基本环境搭建: juejin.cn/spost/72617…
  2. 主界面布局及其初始化: juejin.cn/post/726219…
  3. Meta2d核心库图元注册流程及相关概念: juejin.cn/spost/72629…
  4. 侧边栏功能开发:https://juejin.cn/post/7264414580776403003
  5. Nav组件功能实现:https://juejin.cn/post/7264951443344916517
  6. Nav组件扩展-添加工具栏:https://juejin.cn/post/7265692989611147283
  7. setting组件框架搭建及其Map组件实现:https://juejin.cn/post/7267418197728116748
  8. 引入自定义图元库详解:https://juejin.cn/post/7267948449966096447
  9. Global全局配置组件实现:https://juejin.cn/post/7269349453588660260
  10. 初识PenProps及其Appearance组件实现: https://juejin.cn/post/7270140695477387319

11. meta2d事件消息及Event组件实现

meta2d提供了完整的事件回调机制,我们可以在操作过程中通过监听meta2d的系统事件来做些个性化的操作,其实在之前的代码中,我们也用到了相关内容,比如监听meta2d的scale事件进行画布缩放的数据同步,监听opened事件做文件导入后的预处理等等,这一章我们来聊一聊meta2d的事件消息。

meta2d事件消息

先看看官网是怎么介绍的吧,消息事件,可见,meta2d提供了两种事件类型,分别为系统级事件和图元级事件,系统级事件通过meta2d的on方法进行监听并注册回调函数,而图元级事件则需要配置在图元的events属性列表里。

meta2d提供的系统级事件如下所示:

event(字符串)data描述
opened打开新文件
enterPen鼠标进入画笔
leavePen鼠标离开画笔
activePen[]选中一个/多个画笔
inactivePen[]取消选中
addPen[]添加一个/多个画笔
update编辑画笔
deletePen[]删除画笔
scalenumber缩放画布
translateObject: x, y平移画布
resizePensPen[]画笔大小改变
rotatePensPen[]画笔被旋转
translatePensPen[]移动画笔结束
translatingPensPen[]移动画笔中
clickPoint & Pen点击,鼠标左击 up
mousedownPoint & Pen鼠标 down
dblclickPoint & Pen双击
animateEnd单个画笔动画播放完成
mediaEndPen视频/音频(audio/video)播放结束
socketmessage监听网络消息
undo撤销后
redo恢复后
clickInput单击输入框
inputpen & text输入框键盘输入
valueUpdatepen修改属性值
contextmenue & bounding右键上下文菜单
connectLineObject:line, lineAnchor, pen, anchor,连线连接到锚点
dropobj / json拖动画笔到画布
cutpens剪切
copypens复制
pastepens粘贴

另外还可以通过meta2d.emit方法设置触发自定义事件,系统事件很简单,熟悉前端的读者肯定知道怎么做,这里就不演示了。

meta2d.emit('自定义事件',data)

相比下来,图元事件就稍微复杂,图元事件通过维护图元自身events属性,meta2d在解析图元时会自动遍历其events事件列表并绑定在相关图元上,我们先来看官方的例子

const pen = {
  name: "rectangle",
  text: "矩形",
  x: 100,
  y: 100,
  width: 100,
  height: 100,
  events: [
    {
      name: "click",
      action: EventAction.Link, // 执行动作
      where: { type:'comparison',  key: "text", comparison: "==", value: "矩形" }, // 触发条件
      value: "2d.le5le.com",
    },
  ],
};

events列表中每一项都是绑定的一个事件对象,可知我们的图元支持同时绑定多个事件,我们来看看事件对象的相关属性:

  • name:该属性是事件类型,即监听什么事件,具体有哪些事件见官网
  • action:事件行为,表示事件发生后执行什么动作,类似回调函数。该值是个枚举值(对象),meta2d内部提供了13种事件行为,见官网
  • where:触发条件,即当满足什么条件下发生的相关事件才有效,见官网,非必要
  • value:事件附带数据,比如绑定id值,非必要
  • params:事件参数,与value类似,非必要

接下来,我们就围绕图元事件编写Event组件。

Event组件实现

作为示范,作者就不设定定义多个事件了,将每个图元设为仅能定义一个事件,先看看最终Event组件的的代码:

<script setup>
import {computed, onMounted, reactive, ref} from "vue";
import {eventType, eventBehavior} from "../../data/defaultsConfig.js";

let activePen = reactive({})
let event = reactive({
  name:"",
  action:"",
  value:""
})
let otherParams = []
let depList = computed(()=>{
  let _b =  b.find(i=>i.behavior===event.action)
  otherParams = _b?.depend.map(i=>i.params) || []
  return _b
})
let b = reactive(eventBehavior)
onMounted(()=>{
  meta2d.on('active',(pen)=>{
    if(pen.length === 1){
      activePen = pen[0]
      if(!activePen.events){
        activePen.events = []
      }else {
        // 有事件?
        const actEvent = activePen.events[0]
        if(actEvent){
          event.name = actEvent.name
          event.action = actEvent.action
          otherParams.forEach(i=>event[i]=actEvent[i])
        }else {
          event.name = ""
          event.action = ""
          event.value = ""
        }
      }
    }else {
      activePen = undefined
    }
  })
})
function removeEvent() {
  activePen.events = []
  event.name = ""
  event.action = ""
  event.value = ""

}
function confirmEvent() {
  let otherProps = depList.value.depend?.map(i=>{
    let p = {}
    p[i.bindProp] = i.bindData
    return p
  }
  ) || []
  const e = {
    name:event.name,
    action:event.action,
  }
  Object.assign(e,...otherProps)

  activePen.events = [e]
}
</script>

<template>
  <div class="event">
      <el-form @submit="(e)=>e.preventDefault()">
        <el-form-item label="事件类型" >
          <el-select v-model="event.name" placeholder="选择事件类型" >
          <el-option
              v-for="e in eventType"
              :key="e.event"
              :label="e.name"
              :value="e.event"
          >
          </el-option>
        </el-select>
        </el-form-item>

        <!--        行为类型-->
        <el-form-item label="行为类型">
          <el-select  v-model="event.action" placeholder="选择行为类型">
            <el-option
                v-for="i in eventBehavior"
                :key="i.behavior"
                :label="i.name"
                :value="i.behavior"
            >
            </el-option>
          </el-select>
        </el-form-item>

<!--        事件类型依赖表单-->
        <el-form-item :label="dep.name" v-for="dep in depList?.depend">
          <el-input v-model="dep.bindData" :placeholder="dep.option?.placeholder || '请输入'" v-if="dep.type==='input'" @[dep.event]="dep.func" :type="dep.option?.type||'text'"/>
          <!--          选择框-->
          <el-select v-model="dep.bindData" :placeholder="dep.option.placeholder" v-else-if="dep.type==='select'">
            <el-option
                v-for="item in dep.option.list"
                :key="item.value"
                :label="item.name"
                :value="item.value"
                :disabled="item.disabled"
            >
            </el-option>
          </el-select>
        </el-form-item>



        <div class="event_button">
          <el-button @click="confirmEvent" type="primary" style="width: 100%;margin-right: 14px">确认事件</el-button>
          <el-button @click="removeEvent" type="danger" style="width: 100%;margin-right: 14px">清除事件</el-button>

        </div>
      </el-form>
  </div>
</template>

<style scoped>
.event_button{
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.event {
  margin: 10px;
}
</style>

在前端代码中,我们可以看到行为类型和事件类型为静态,还有个事件类型依赖表单为动态渲染,前面说到,event事件对象需要name、action、value、params、where五个属性,其中name和action属性是必要的,所以我们先将name和action写死,其他表单我们通过选择的事件行为来动态渲染出来。首先我们先定义好事件类型

// defaultsConfig.js
export const eventType = [
  {
      name: "鼠标移入",
      event: "enter"
  },
  {
      name: "鼠标移出",
      event: "leave"
  },
  {
      name: "选中",
      event: "active"
  },
  {
      name: "取消选中",
      event: "inactive"
  },
  {
      name:"单击",
      event:"click"
  },
  {
      name:"双击",
      event: "dbclick"
  }
]

然后定义好事件行为

// defaultsConfig.js
export const eventBehavior = [
  {
      name: "打开链接",
      behavior: EventAction.Link,
      depend:[
          {
              name:"链接地址",
              type:"input",
              bindProp:"value",
              option:{
                  placeholder:"URL"
              },
              bindData:""
          },
          {
              name:"打开方式",
              type:"select",
              bindProp:"params",
              option:{
                  list:[
                      {
                          name:"新窗口打开",
                          value:"_blank"
                      },{
                          name:"覆盖当前页面",
                          value:"self"
                      }
                  ]
              },
              bindData:""
          }
      ]
  },
  {
      name:"执行动画",
      behavior: EventAction.StartAnimate,
      depend:[
          {
              name:"目标id/tag",
              type:"input",
              bindProp:"value",
              option:{
                  placeholder:"id/tag"
              },
              bindData:""
          },
      ]
  },
  {
      name:"暂停动画",
      behavior: EventAction.PauseAnimate,
      depend:[
          {
              name:"目标id/tag",
              type:"input",
              bindProp:"value",
              option:{
                  placeholder:"id/tag"
              },
              bindData:""
          },
      ]
  },
  {
      name:"停止动画",
      behavior: EventAction.StopAnimate,
      depend:[
          {
              name:"目标id/tag",
              type:"input",
              bindProp:"value",
              option:{
                  placeholder:"id/tag"
              },
              bindData:""
          },
      ]
  },
  {
      name:"播放视频",
      behavior: EventAction.StartVideo,
      depend:[
          {
              name:"目标id/tag",
              type:"input",
              bindProp:"value",
              option:{
                  placeholder:"id/tag"
              },
              bindData:""
          },
      ]
  },
  {
      name:"暂停视频",
      behavior: EventAction.PauseVideo,
      depend:[
          {
              name:"目标id/tag",
              type:"input",
              bindProp:"value",
              option:{
                  placeholder:"id/tag"
              },
              bindData:""
          },
      ]
  },
  {
      name: "停止视频",
      behavior: EventAction.StopVideo,
      depend:[
          {
              name:"目标id/tag",
              type:"input",
              bindProp:"value",
              option:{
                  placeholder:"id/tag"
              },
              bindData:""
          },
      ]
  }
]

其中,EventAction对象是meta2d提供的事件行为枚举对象,depend属性为该事件行为依赖的表单数据,即我们选择事件行为后,就能将对应的行为将依赖表单渲染到界面上,看看depend的内容是不是很熟悉,与我们之前渲染其他表单都一样的原理,这里就不赘述了。

先让我们看看相关效果

event1 00_00_00-00_00_30.gif

可以看到,效果符合预期

接下来我们需要编写函数,将选定事件数据及其参数赋值给图元,这也很简单,只需要直接赋值即可。像下面这样

function confirmEvent() {
  let otherProps = depList.value.depend?.map(i=>{
    let p = {}
    p[i.bindProp] = i.bindData
    return p
  }
  ) || []  // 是否有其他附加属性
  const e = {
    name:event.name,
    action:event.action,
  }
  Object.assign(e,...otherProps) // 注册属性

  activePen.events = [e]
}

由于我们的项目比较简单,不涉及复杂的数据交互,所以决定在每个图元中只注册一个事件。然后通过给“确认事件”按钮绑定点击事件,就实现了这个简单的赋值功能,我们来看看,我们赋值后的图元数据长啥样。

imagepng

imagepng

成功将数据写入图元的events属性上,来看看具体效果吧。

events 000000000030gif

需要注意的是 需要将图纸锁定才有效,官网已经说明了这点

事件绑定是写好了,没问题,可是事件回显还没实现呢,当我们点击不同图元会在界面上显示不同图元的事件信息,其实实现这个功能也很简单,这里就要依赖meta2d的系统事件了,active事件,该事件返回每次选中图元的信息,我们只需要拿到这些信息后,进行一次赋值就好,像下面这样:

meta2d.on('active',(pen)=>{
  if(pen.length === 1){ // 长度唯一 说明选中了一个
    activePen = pen[0]   // 赋值
    if(!activePen.events){ 
      activePen.events = []  // 若没有events属性,则初始化一个
    }else { // 若有事件属性则赋值
      const actEvent = activePen.events[0] // 只处理第一个事件
      if(actEvent){ // 赋值
        event.name = actEvent.name
        event.action = actEvent.action
        otherParams.forEach(i=>event[i]=actEvent[i])
      }else {  // 否者初始化值
        event.name = ""
        event.action = ""
        event.value = ""
      }
    }
  }else {
    activePen = undefined
  }
})

来看看效果

events2 000000000030gif

总结

本章,我们较为详细的介绍了meta2d事件消息相关内容,并且在此基础上我们为项目实现了简单的事件绑定功能,meta2d支持绑定多个事件,能够应对足够复杂的应用场景,由于面向的读者较广,为了更多人能够理解和接受这部分内容,我的示例较为简单,读者可以尝试去实现更为复杂的场景。下一章,我们进入PenProps的最后一个组件动效(Animate)组件的开发,我们下章再回。

有问题欢迎在评论区反馈,欢迎持续关注。

Meta2d.js 开源地址

给大家推荐一下 Meta2d.js是一个实时数据响应和交互的2d引擎,可用于Web组态,物联网,数字孪生等场景。

Github:github.com/le5le-com/m…

Gitee: gitee.com/le5le/meta2…

如果本篇文章帮助到了你,欢迎为meta2d项目star点星。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值