bpmn.js--流程编排设计

429 阅读3分钟

2024.7.23

开始

上周五接到临时需求,要求用bpmn.js技术,做一个简单的流程编排设计页面,gitHub项目地址

开发过程

网上教程都很详细,但是版本号必须对应上

安装bpmn-js

yarn add bpmn-js@7.3.1 
yarn add bpmn-js-properties-panel@0.37.2
yarn add camunda-bpmn-moddle@^4.5.0
yarn add  codemirror@^6.0.1

问题

1. 解决 bpmn-js-properties-panel 缺少文件问题

原因:版本问题

解决:

  • 版本要对应
bpmn-js@7.3.1 
bpmn-js-properties-panel@0.37.2
  • 右侧属性面板div需要设置样式
.panel {
  width: 320px;
  position: absolute;
  top: 0px;
  right: 0px;
  height: 100%;
  overflow: auto;
}

2. Error: Passing callbacks to importXML is deprecated and will be removed in a future major release

原因:bpmnModeler.importXML不支持回调函数

解决:7.3.0之后版本的回调写法

import BpmnModeler from 'bpmn-js/lib/Modeler';

const bpmnModeler = new BpmnModeler();
 
try {
    const result = await bpmnModeler.importXML(xml);
    const { warnings } = result;
    console.log(warnings);
} catch (err) {
    console.log(err.message, err.warnings);
}

参考文档:

3. bpmnModeler.createDiagram也不支持回调函数

用importXML和createDiagram两个API二选一

代码

<template>
  <div class="design-container">
    <div>
      <a-button @click="handleBack()">返回</a-button>
      <a-button @click="handleSave(0)">保存</a-button>
      <a-button @click="handleRedo()">前进</a-button>
      <a-button @click="handleUndo()">后退</a-button>
      <a-button @click="handleZoom(1)">放大</a-button>
      <a-button @click="handleZoom(-1)">缩小</a-button>
      <a-button @click="handleDownload()">下载</a-button>
    </div>

    <div class="wf-container">
      <div id="wf-designer" ref="container"></div>
      <div id="js-properties-panel" class="panel"></div>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { markRaw, watch } from 'vue';
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';
// bpmn-js-properties-panel相关
import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css';
import propertiesPanelModule from 'bpmn-js-properties-panel';
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';
import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';
// BPMN国际化
import customTranslate from '@/components/bpmn/custom/CustomTranslate.js';
import type { WorkflowModel } from '@/api/workflowDesignApi';
import { getXmlData, save } from '@/api/workflowDesignApi';
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();

//流程图 保存内容
const modelBpmn = ref<WorkflowModel>({
  id: '',
  keycode: '',
  name: '',
  nameEn: '',
  group: 'general',
  model: '',
  status: '0',
});

// bpmn建模器
const container = ref(null);
const type = ref(route.query.type || '');

onMounted(() => {
  initModeler();
});

watch(
  () => route.query,
  newVal => {
    type.value = newVal.type || '';
    if (newVal) {
      createNewDiagram();
    }
    console.log(type.value, 'newVal');
  },
);
let bpmnModeler: any = null;
const initModeler = () => {
  // 加markRaw去除双向绑定作用域
  bpmnModeler = markRaw(
    new BpmnModeler({
      container: container.value,
      // 添加控制板
      propertiesPanel: {
        parent: '#js-properties-panel',
      },
      // 右侧属性面板
      additionalModules: [
        propertiesPanelModule,
        propertiesProviderModule,
        // 汉化
        { translate: ['value', customTranslate] },
      ],
      moddleExtensions: {
        camunda: camundaModdleDescriptor,
      },
    }),
  );
  //判断是否编辑还是创建状态
  createNewDiagram();
  // 监听节点变化
  watchBpmnChanged();
};
// 创建空白流程图 == 渲染
const createNewDiagram = async () => {
  if (type.value == 'edit') {
    //根据请求数据渲染流程图
    await getXmlData().then(res => {
      modelBpmn.value = res.data || {};
      // 将字符串转换成图显示出来
      try {
        const bpmnXmlStr = JSON.parse(modelBpmn.value?.model || '');
        const result = bpmnModeler.importXML(bpmnXmlStr);
        const { warnings } = result;
        console.log(warnings);
      } catch (err: any) {
        console.log(err.message, err.warnings);
      }
    });
  } else {
    //创建空白流程图
    await bpmnModeler.createDiagram();
  }
};
//保存
const handleSave = (isDeploy: number) => {
  const model: WorkflowModel = {
    id: Math.random().toString().slice(2),
    keycode: '',
    name: '',
    nameEn: '',
    group: 'general',
    model: '',
    status: isDeploy ? '1' : '0',
  };
  bpmnModeler.saveXML({ format: true }).then(data => {
    model.model = JSON.stringify(data.xml);
    console.log(JSON.stringify(model));
    alert(JSON.stringify(model));
    // save(model);
  });
};
// 监听节点变化
const watchBpmnChanged = () => {
  // 监听节点选择变化
  bpmnModeler.on('selection.changed', e => {
    const element = e.newSelection[0];
    if (element) {
      // 渲染节点参数
    } else {
      // 渲染主表单配置参数
    }
    // console.log(element);
  });

  //  监听节点属性变化
  bpmnModeler.on('element.changed', e => {
    const { element } = e;
    // console.log(element);
  });
};

// 放大缩放
let scale = ref<number>(1);
const handleZoom = (zoom: number) => {
  if (scale.value + zoom < 1) {
    console.log('workflow desinger zoom limit 1');
    return;
  }
  scale.value = scale.value + zoom;
  bpmnModeler.get('canvas').zoom(scale.value);
};
//前进
const handleRedo = () => {
  bpmnModeler.get('commandStack').redo();
};
//后退
const handleUndo = () => {
  bpmnModeler.get('commandStack').undo();
};
//返回
const handleBack = () => {
  router.push({ name: 'workflowList' });
};
// 下载
const handleDownload = () => {
  bpmnModeler.saveXML({ format: true }, (err, data) => {
    const dataTrack = 'bpmn';
    const a = document.createElement('a');
    const name = `diagram.${dataTrack}`;
    a.setAttribute('href', `data:application/bpmn20-xml;charset=UTF-8,${encodeURIComponent(data)}`);
    a.setAttribute('target', '_blank');
    a.setAttribute('dataTrack', `diagram:download-${dataTrack}`);
    a.setAttribute('download', name);
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    console.log(err);
  });
};

</script>

<style>
.design-container {
  padding: 16px;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}
.wf-container {
  position: relative;
  flex: auto;
  background: #fff;
  position: relative;
  margin-top: 16px;
  border: 1px solid #d9d9d9;
}

.wf-container #wf-designer {
  height: 100%;
}

.bjs-powered-by {
  display: none;
}
svg.new-parent {
  background-color: #f7f9ff !important;
}

.panel {
  width: 320px;
  position: absolute;
  top: 0px;
  right: 0px;
  height: 100%;
  overflow: auto;
}

</style>


最后效果图

image.png

自定义Palette

参考链接

总结

没有定制化开发的话其实相对简单,但是版本号必须要对应才行,因为在开发中遇到版本不一致,导致右侧属性面板不显示。