vue3中使用事件流bpmn

前言

最近项目规划中有使用事件流引擎,就进行了简单的调研,参考若依 RuoYi-Flowable-Plus 做了一个vue3版本的事件流引擎demo。

"bpmn-js": "^7.3.1"

js 复制代码
<template>
    <div id="container" style="width: calc(100vw - 750px); height: calc(100vh - 150px)"></div>
</template>
<script setup lang="ts">
    import { onMounted, markRaw ,ref} from 'vue';
     // bpmn-js相关
 import BpmnModeler  from 'bpmn-js/lib/Modeler' ;
 import 'bpmn-js/dist/assets/diagram-js.css'; // 左边工具栏以及编辑节点的样式
 import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';

let bpmnModeler:any=null
onMounted(() => {
    const containerEl = document.getElementById('container');
    bpmnModeler = new BpmnModeler({
        container: containerEl,
    })
})
</script>
<style>
.containerBox {
    height: calc(100vh - 160px);
    margin-top: 30px;
    display: flex;
    background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
      repeat !important;
}
</style>

初始化完成,页面已经显示 常用的几个配置项

js 复制代码
bpmnModeler = new BpmnModeler(options: Options);
interface Options {
	container: DomElement; // 渲染容器
	width:string | number;// 容器宽度
	height: string | number; // 容器高度
	moddleExtensions: object;// 需要用的扩展包
	modules:<didi.Module>[]; // 自定义且需要覆盖默认扩展包的模块列表
	additionalModules: <didi.Module>[]; // 自定义且与默认扩展包一起使用的模块列表
}

目前默认的语言是英文,需要我们在扩展里面增加语言包配置 translate.ts

js 复制代码
import translations from "./zh";
 
function customTranslate(template, replacements) {
  replacements = replacements || {};
 
  // Translate
  template = translations[template] || template;
 
  // Replace
  return template.replace(/{([^}]+)}/g, function(_, key) {
    let str = replacements[key];
    if (
      translations[replacements[key]] !== null &&
      translations[replacements[key]] !== "undefined"
    ) {
      // eslint-disable-next-line no-mixed-spaces-and-tabs
      str = translations[replacements[key]];
      // eslint-disable-next-line no-mixed-spaces-and-tabs
    }
    return str || "{" + key + "}";
  });
}

const customTranslateModule = {
  translate: ['value', customTranslate]
}

export default customTranslateModule

语言包json

js 复制代码
export default {
    // Labels
    "Activate the global connect tool": "激活全局连接工具",
    "Append {type}": "追加 {type}",
    "Append EndEvent": "追加 结束事件 ",
    "Append Task": "追加 任务",
    "Append Gateway": "追加 网关",
    "Append Intermediate/Boundary Event": "追加 中间/边界 事件",
    "Add Lane above": "在上面添加道",
    "Divide into two Lanes": "分割成两个道",
    "Divide into three Lanes": "分割成三个道",
    "Add Lane below": "在下面添加道",
    "Append compensation activity": "追加补偿活动",
    "Change type": "修改类型",
    "Connect using Association": "使用关联连接",
    "Connect using Sequence/MessageFlow or Association":
      "使用顺序/消息流或者关联连接",
    "Connect using DataInputAssociation": "使用数据输入关联连接",
    "Remove": "移除",
    "Activate the hand tool": "激活抓手工具",
    "Activate the lasso tool": "激活套索工具",
    "Activate the create/remove space tool": "激活创建/删除空间工具",
    "Create expanded SubProcess": "创建扩展子过程",
    "Create IntermediateThrowEvent/BoundaryEvent": "创建中间抛出事件/边界事件",
    "Create Pool/Participant": "创建池/参与者",
    "Parallel Multi Instance": "并行多重事件",
    "Sequential Multi Instance": "时序多重事件",
    "DataObjectReference": "数据对象参考",
    "DataStoreReference": "数据存储参考",
    "Loop": "循环",
    "Ad-hoc": "即席",
    "Create {type}": "创建 {type}",
    "Create Task": "创建任务",
    "Create StartEvent": "创建开始事件",
    "Create EndEvent": "创建结束事件",
    "Create Group": "创建组",
    "Task": "任务",
    "Send Task": "发送任务",
    "Receive Task": "接收任务",
    "User Task": "用户任务",
    "Manual Task": "手工任务",
    "Business Rule Task": "业务规则任务",
    "Service Task": "服务任务",
    "Script Task": "脚本任务",
    "Call Activity": "调用活动",
    "Sub Process (collapsed)": "子流程(折叠的)",
    "Sub Process (expanded)": "子流程(展开的)",
    "Start Event": "开始事件",
    "StartEvent": "开始事件",
    "Intermediate Throw Event": "中间事件",
    "End Event": "结束事件",
    "EndEvent": "结束事件",
    "Create Gateway": "创建网关",
    "GateWay": "网关",
    "Create Intermediate/Boundary Event": "创建中间/边界事件",
    "Message Start Event": "消息开始事件",
    "Timer Start Event": "定时开始事件",
    "Conditional Start Event": "条件开始事件",
    "Signal Start Event": "信号开始事件",
    "Error Start Event": "错误开始事件",
    "Escalation Start Event": "升级开始事件",
    "Compensation Start Event": "补偿开始事件",
    "Message Start Event (non-interrupting)": "消息开始事件(非中断)",
    "Timer Start Event (non-interrupting)": "定时开始事件(非中断)",
    "Conditional Start Event (non-interrupting)": "条件开始事件(非中断)",
    "Signal Start Event (non-interrupting)": "信号开始事件(非中断)",
    "Escalation Start Event (non-interrupting)": "升级开始事件(非中断)",
    "Message Intermediate Catch Event": "消息中间捕获事件",
    "Message Intermediate Throw Event": "消息中间抛出事件",
    "Timer Intermediate Catch Event": "定时中间捕获事件",
    "Escalation Intermediate Throw Event": "升级中间抛出事件",
    "Conditional Intermediate Catch Event": "条件中间捕获事件",
    "Link Intermediate Catch Event": "链接中间捕获事件",
    "Link Intermediate Throw Event": "链接中间抛出事件",
    "Compensation Intermediate Throw Event": "补偿中间抛出事件",
    "Signal Intermediate Catch Event": "信号中间捕获事件",
    "Signal Intermediate Throw Event": "信号中间抛出事件",
    "Message End Event": "消息结束事件",
    "Escalation End Event": "定时结束事件",
    "Error End Event": "错误结束事件",
    "Cancel End Event": "取消结束事件",
    "Compensation End Event": "补偿结束事件",
    "Signal End Event": "信号结束事件",
    "Terminate End Event": "终止结束事件",
    "Message Boundary Event": "消息边界事件",
    "Message Boundary Event (non-interrupting)": "消息边界事件(非中断)",
    "Timer Boundary Event": "定时边界事件",
    "Timer Boundary Event (non-interrupting)": "定时边界事件(非中断)",
    "Escalation Boundary Event": "升级边界事件",
    "Escalation Boundary Event (non-interrupting)": "升级边界事件(非中断)",
    "Conditional Boundary Event": "条件边界事件",
    "Conditional Boundary Event (non-interrupting)": "条件边界事件(非中断)",
    "Error Boundary Event": "错误边界事件",
    "Cancel Boundary Event": "取消边界事件",
    "Signal Boundary Event": "信号边界事件",
    "Signal Boundary Event (non-interrupting)": "信号边界事件(非中断)",
    "Compensation Boundary Event": "补偿边界事件",
    "Exclusive Gateway": "互斥网关",
    "Parallel Gateway": "并行网关",
    "Inclusive Gateway": "相容网关",
    "Complex Gateway": "复杂网关",
    "Event based Gateway": "事件网关",
    "Transaction": "转运",
    "Sub Process": "子流程",
    "Event Sub Process": "事件子流程",
    "Collapsed Pool": "折叠池",
    "Expanded Pool": "展开池",
    // Errors
    "no parent for {element} in {parent}": "在{parent}里,{element}没有父类",
    "no shape type specified": "没有指定的形状类型",
    "flow elements must be children of pools/participants":
      "流元素必须是池/参与者的子类",
    "out of bounds release": "out of bounds release",
    "more than {count} child lanes": "子道大于{count} ",
    "element required": "元素不能为空",
    "diagram not part of bpmn:Definitions": "流程图不符合bpmn规范",
    "no diagram to display": "没有可展示的流程图",
    "no process or collaboration to display": "没有可展示的流程/协作",
    "element {element} referenced by {referenced}#{property} not yet drawn":
      "由{referenced}#{property}引用的{element}元素仍未绘制",
    "already rendered {element}": "{element} 已被渲染",
    "failed to import {element}": "导入{element}失败",
    //属性面板的参数
    "Id": "编号",
    "Name": "名称",
    "General": "常规",
    "Details": "详情",
    "Message Name": "消息名称",
    "Message": "消息",
    "Initiator": "创建者",
    "Asynchronous Continuations": "持续异步",
    "Asynchronous Before": "异步前",
    "Asynchronous After": "异步后",
    "Job Configuration": "工作配置",
    "Exclusive": "排除",
    "Job Priority": "工作优先级",
    "Retry Time Cycle": "重试时间周期",
    "Documentation": "文档",
    "Element Documentation": "元素文档",
    "History Configuration": "历史配置",
    "History Time To Live": "历史的生存时间",
    "Forms": "表单",
    "Form Key": "表单key",
    "Form Fields": "表单字段",
    "Business Key": "业务key",
    "Form Field": "表单字段",
    "ID": "编号",
    "Type": "类型",
    "Label": "名称",
    "Default Value": "默认值",
    "Validation": "校验",
    "Add Constraint": "添加约束",
    "Config": "配置",
    "Properties": "属性",
    "Add Property": "添加属性",
    "Value": "值",
    "Add": "添加",
    "Values": "值",
    "Add Value": "添加值",
    "Listeners": "监听器",
    "Execution Listener": "执行监听",
    "Event Type": "事件类型",
    "Listener Type": "监听器类型",
    "Java Class": "Java类",
    "Expression": "表达式",
    "Must provide a value": "必须提供一个值",
    "Delegate Expression": "代理表达式",
    "Script": "脚本",
    "Script Format": "脚本格式",
    "Script Type": "脚本类型",
    "Inline Script": "内联脚本",
    "External Script": "外部脚本",
    "Resource": "资源",
    "Field Injection": "字段注入",
    "Extensions": "扩展",
    "Input/Output": "输入/输出",
    "Input Parameters": "输入参数",
    "Output Parameters": "输出参数",
    "Parameters": "参数",
    "Output Parameter": "输出参数",
    "Timer Definition Type": "定时器定义类型",
    "Timer Definition": "定时器定义",
    "Date": "日期",
    "Duration": "持续",
    "Cycle": "循环",
    "Signal": "信号",
    "Signal Name": "信号名称",
    "Escalation": "升级",
    "Error": "错误",
    "Link Name": "链接名称",
    "Condition": "条件名称",
    "Variable Name": "变量名称",
    "Variable Event": "变量事件",
    "Specify more than one variable change event as a comma separated list.":
      "多个变量事件以逗号隔开",
    "Wait for Completion": "等待完成",
    "Activity Ref": "活动参考",
    "Version Tag": "版本标签",
    "Executable": "可执行文件",
    "External Task Configuration": "扩展任务配置",
    "Task Priority": "任务优先级",
    "External": "外部",
    "Connector": "连接器",
    "Must configure Connector": "必须配置连接器",
    "Connector Id": "连接器编号",
    "Implementation": "实现方式",
    "Field Injections": "字段注入",
    "Fields": "字段",
    "Result Variable": "结果变量",
    "Topic": "主题",
    "Configure Connector": "配置连接器",
    "Input Parameter": "输入参数",
    "Assignee": "代理人",
    "Candidate Users": "候选用户",
    "Candidate Groups": "候选组",
    "Due Date": "到期时间",
    "Follow Up Date": "跟踪日期",
    "Priority": "优先级",
    "The follow up date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)":
      "跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00",
    "The due date as an EL expression (e.g. ${someDate} or an ISO date (e.g. 2015-06-26T09:54:00)":
      "跟踪日期必须符合EL表达式,如: ${someDate} ,或者一个ISO标准日期,如:2015-06-26T09:54:00",
      "Variables": "变量",
    "Candidate Starter Configuration": "候选开始配置",
    "Task Listener": "任务监听器",
    "Candidate Starter Groups": "候选开始组",
    "Candidate Starter Users": "候选开始用户",
    "Tasklist Configuration": "任务列表配置",
    "Startable": "启动",
    "Specify more than one group as a comma separated list.":
      "指定多个组,用逗号分隔",
    "Specify more than one user as a comma separated list.":
      "指定多个用户,用逗号分隔",
    "This maps to the process definition key.": "这会映射为流程定义的键",
    "CallActivity Type": "调用活动类型",
    "Condition Type": "条件类型",
    "Create UserTask": "创建用户任务",
    "Create CallActivity": "创建调用活动",
    "Called Element": "调用元素",
    "Create DataObjectReference": "创建数据对象引用",
    "Create DataStoreReference": "创建数据存储引用",
    "Multi Instance": "多实例",
    "Loop Cardinality": "实例数量",
    "Collection": "任务参与人列表",
    "Element Variable": "元素变量",
    "Completion Condition": "完成条件",
    "Open minimap": "打开小地图",
    "Close minimap": "关闭小地图",
  };

更新初始化配置

js 复制代码
 import defaultXml from "./defaultEmpty"
 const defaultXmlStr=defaultXml()
    bpmnModeler = new BpmnModeler({
        container: containerEl,
        additionalModules: [
            customTranslateModule //翻译模块 translate.ts
        ]
    })
    bpmnModeler.importXML(defaultXmlStr) // 默认导入的xml文件,否则无法拖拽

生成默认的xmlStr defaultEmpty.ts

js 复制代码
const key=`Process_${new Date().getTime()}`
const name=`业务流程_${new Date().getTime()}`
const type='xmlStr'
export default (key, name, type) => {
  if (!type) type = "camunda";
  const TYPE_TARGET = {
    activiti: "http://activiti.org/bpmn",
    camunda: "http://bpmn.io/schema/bpmn",
    flowable: "http://flowable.org/bpmn"
  };
  return `
    <?xml version="1.0" encoding="UTF-8"?>
    <bpmn2:definitions 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
      xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
      xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
      xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
      xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
      id="diagram_${key}"
      targetNamespace="${TYPE_TARGET[type]}">
      <bpmn2:process id="${key}" name="${name}" isExecutable="true">
      </bpmn2:process>
      <bpmndi:BPMNDiagram id="BPMNDiagram_1">
        <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${key}">
        </bpmndi:BPMNPlane>
      </bpmndi:BPMNDiagram>
    </bpmn2:definitions>
  `;
};

此时鼠标悬浮左侧的流程框,就可以显示对应的中文翻译,可以完成正常的拖拽添加功能。

获取流程图数据

js 复制代码
bpmnModeler.saveXML({ format: true }).then((res: any) => {
        console.log(res.xml)
    })

导入xml文件

通过 input file 来进行文件读取,获取文件内容,进行流程图渲染

js 复制代码
<template>
    <button @click="refFile.click()">导入</button>
    <!-- 用于打开本地文件-->
    <input id="files" ref="refFile" type="file" style="display: none" accept=".xml, .bpmn" @change="importXml">
</template>
<script setup lang="ts">
const refFile:any=ref(null)
const importXml=()=>{
      const file = refFile.value.files[0]
      const reader = new FileReader()
      reader.readAsText(file)
      reader.onload = function(res:any) {
        bpmnModeler.importXML(res.currentTarget.result) // bpmnModeler 为实例化的bpmn
      }
}
</script>

下载 XML,SVG

这些是从若依代码里面直接粘贴过来的

js 复制代码
<template>
 <@click="downloadProcess('xml')" >下载XML</button>
 <@click="downloadProcess('svg')" >下载SVG</button>
 <@click="downloadProcess('bpmn')" >保存BPMN</button>
</template>
// 根据所需类型进行转码并返回下载地址
const setEncoded=(type: string, filename = 'diagram', data: string | number | boolean)=> {
      const encodedData = encodeURIComponent(data)
      return {
        filename: `${filename}.${type}`,
        href: `data:application/${type === 'svg' ? 'text/xml' : 'bpmn20-xml'};charset=UTF-8,${encodedData}`,
        data: data
      }
    }
// 下载流程图到本地
const downloadProcess=async(type: string)=> {
      try {
        // 按需要类型创建文件并下载
        if (type === 'xml' || type === 'bpmn') {
          const { err, xml } = await bpmnModeler.saveXML()
          // 读取异常时抛出异常
          if (err) {
            console.error(`[Process Designer Warn ]: ${err.message || err}`)
          }
          const { href, filename } = setEncoded(type.toUpperCase(), 'bpmn', xml)
          downloadFunc(href, filename)
        } else {
          const { err, svg } = await bpmnModeler.saveSVG()
          // 读取异常时抛出异常
          if (err) {
            return console.error(err)
          }
          const { href, filename } = setEncoded('SVG', 'bpmn', svg)
          downloadFunc(href, filename)
        }
      } catch (e) {
        console.error(`[Process Designer Warn ]: ${e.message || e}`)
      }
      // 文件下载方法
      function downloadFunc(href: string, filename: string) {
        if (href && filename) {
          const a = document.createElement('a')
          a.download = filename // 指定下载的文件名
          a.href = href //  URL对象
          a.click() // 模拟点击
          URL.revokeObjectURL(a.href) // 释放URL 对象
        }
      }
}

预览

使用 highlight.js进行预览

"highlight.js": "10.5.0";"@highlightjs/vue-plugin": "^2.1.0"

在main.ts中进行注册

js 复制代码
import 'highlight.js/styles/atom-one-dark.css'
import 'highlight.js/lib'
import hljsVuePlugin from '@highlightjs/vue-plugin'
...
app.use(hljsVuePlugin)

在弹窗中使用,还是使用 saveXML 获取 xml 文件进行数据填充,highlightjs 中 language 可以换成 json 等配置,等后面再深入去学习

js 复制代码
    <el-dialog v-model="dialogVisible" title="事件流预览">
      <highlightjs language='xml' :code="showHtml" style="height: 60vh;overflow-y: auto" />
    </el-dialog>
    ...
    const dialogVisible=ref(false)
    const saveXml=()=>{
    bpmnModeler.saveXML({ format: true }).then((res: any) => {
        // const json = xmlJs.xml2json(res.xml, { compact: true, spaces: 4 });
        // // console.log(json);
        // showHtml.value=json
        dialogVisible.value=true
        showHtml.value=res.xml
    })
}
...
:deep(.hljs) {
      word-break: break-word;
      white-space: pre-wrap;
    }
    :deep(.hljs *) {
      font-family: Consolas, Monaco, monospace;
    }

预览效果,改下样式,字体看着更舒服一点

最后

这样基本实现了最基的事件流引擎 demo。具体还没有具体的项目需求,只能根据文档去学习。下一篇会对一些 api进行了解学习

相关推荐
云草桑7 分钟前
逆向工程 反编译 C# net core
前端·c#·反编译·逆向工程
布丁椰奶冻13 分钟前
解决使用nvm管理node版本时提示npm下载失败的问题
前端·npm·node.js
Leyla39 分钟前
【代码重构】好的重构与坏的重构
前端
影子落人间42 分钟前
已解决npm ERR! request to https://registry.npm.taobao.org/@vant%2farea-data failed
前端·npm·node.js
世俗ˊ1 小时前
CSS入门笔记
前端·css·笔记
子非鱼9211 小时前
【前端】ES6:Set与Map
前端·javascript·es6
6230_1 小时前
git使用“保姆级”教程1——简介及配置项设置
前端·git·学习·html·web3·学习方法·改行学it
想退休的搬砖人1 小时前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
加勒比海涛2 小时前
HTML 揭秘:HTML 编码快速入门
前端·html
啥子花道2 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js