Angular 中引入 bpmn.js
最近的工作中有 workflow 相关的需求,初步讨论决定用 Camunda 实现。
前端需要做的工作是提供一个页面查看所有的工作流列表,同时需要绘制工作流程并保存,效果图如下:
图片.png
1. 安装依赖
npm install --save bpmn-js
// package.json -- dependencies
"diagram-js": "^8.2.1",
"bpmn-js": "^9.0.3",
"bpmn-js-properties-panel": "^0.46.0",
"camunda-bpmn-moddle": "^6.1.2",
2. 在 angular.json 中引入样式
"styles": [
"src/theme.less",
"src/styles.scss",
"node_modules/bpmn-js/dist/assets/diagram-js.css",
"node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css",
"node_modules/bpmn-js-properties-panel/styles/properties.less"
],
3. 创建组件
// component
import { AfterContentInit, Component,ElementRef, Input,OnChanges,OnDestroy,Output,ViewChild,SimpleChanges,OnInit} from'@angular/core';
import { HttpClient } from '@angular/common/http';
import { from, Observable } from 'rxjs';
// bpmn module
import Modeler from 'bpmn-js/lib/Modeler.js';
import propertiesPanelModule from 'bpmn-js-properties-panel';
import CamundaPlatformPropertiesProviderModule from 'bpmn-js-properties-panel';
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';
import CamundaExtensionModule from 'camunda-bpmn-moddle/lib';
import { default as camundaModdleDescriptor } from 'camunda-bpmn-moddle/resources/camunda.json';
@Component({
selector: 'app-diagram',
templateUrl: './diagram.component.html',
styleUrls: ['./diagram.component.scss']
})
export class DiagramComponent implements AfterContentInit, OnChanges, OnInit, OnDestroy {
@ViewChild('canvas', { static: true }) private el: ElementRef;
@Input() private diagramXML: string;
private modeler: Modeler;
constructor(private http: HttpClient) {
this.modeler = new Modeler();
this.modeler.on('import.done', ({ error }) => {
if (!error) {
this.modeler.get('canvas').zoom('fit-viewport');
}
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes.diagramXML) {
this.diagramXML = changes.diagramXML.currentValue;
if (this.diagramXML) {
this.importDiagram(this.diagramXML);
}
}
}
ngOnInit() {
this.modeler = new Modeler({
container: '#canvas',
width: '100%',
height: '600px',
propertiesPanel: {
parent: '#properties-panel'
},
additionalModules: [
// 右边工具栏
propertiesPanelModule,
// 左边工具栏以及节点
propertiesProviderModule,
CamundaPlatformPropertiesProviderModule,
CamundaExtensionModule
],
moddleExtensions: {
camunda: camundaModdleDescriptor
}
});
// 如果没有传入 xml 文件,则新建一个 workflow
if (!this.diagramXML) {
this.createWorkflow();
}
}
ngAfterContentInit(): void {
this.modeler.attachTo(this.el.nativeElement);
}
ngOnDestroy(): void {
this.modeler.destroy();
}
/**
* 创建空白工作流
*/
private createWorkflow() {
// 获取本地 bpmn 文件并导入
// this.http.get('assets/bpmn/initial.bpmn', { responseType: 'text' }).subscribe(output => {
// this.importDiagram(output);
// });
this.modeler.createDiagram();
}
public save() {
return this.modeler.saveXML();
}
/**
* 导出文件到本地
*/
public saveDiagram() {
this.modeler.saveXML({ format: true }).then(res => {
const encodeXML = encodeURIComponent(res.xml);
this.downloadToLocal(encodeXML);
});
}
/**
* 保存 bpmn文件到本地
*/
private downloadToLocal(encodedData, name = 'diagram.bpmn') {
// 创建虚拟a标签
const eleLink = document.createElement('a');
eleLink.download = name;
eleLink.style.display = 'none';
eleLink.href = 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData;
// 触发点击
document.body.appendChild(eleLink);
eleLink.click();
// 然后移除
document.body.removeChild(eleLink);
}
/**
* 导入 bpmn 文件
*/
public beforeUpload = (file: NzUploadFile): boolean => {
this.onFileChange(file);
return false;
}
/**
* 读取导入的 bpmn 文件并渲染
*/
public onFileChange(file) {
const reader = new FileReader();
reader.readAsText(file);
reader.onload = () => {
const xmlData = reader.result;
this.diagramXML = xmlData;
};
reader.onerror = () => {
this.messageService.error('error is occured while reading file!');
};
}
/**
* modeler 中导入 xml 文件
* @see https://github.com/bpmn-io/bpmn-js-callbacks-to-promises#importxml
*/
private importDiagram(xml: string): Observable<{ warnings: Array<any> }> {
return from(this.modeler.importXML(xml) as Promise<{ warnings: Array<any> }>);
}
/**
* 获取服务器端存储的 bpmn 文件
*/
// private loadUrl(url: string): Subscription {
// return (
// this.http.get(url, { responseType: 'text' }).pipe(
// switchMap((xml: string) => this.importDiagram(xml)),
// map(result => result.warnings),
// ).subscribe(
// (warnings) => {
// this.importDone.emit({
// type: 'success',
// warnings
// });
// },
// (err) => {
// this.importDone.emit({
// type: 'error',
// error: err
// });
// }
// )
// );
// }
}
// html
<div nz-row>
<nz-upload class="mr-15" [nzShowUploadList]="false" [nzBeforeUpload]="beforeUpload" nzAccept=".bpmn">
<button nz-button nzSize="small" nzType="primary">
<i nz-icon nzType="upload"></i>import
</button>
</nz-upload>
<button nz-button nzSize="small" nzType="primary" (click)="saveDiagram()">
<i nz-icon nzType="download" nzTheme="outline"></i>export
</button>
</div>
<div class="diagram-container">
<div id="canvas" #canvas class="canvas"></div>
<!-- 右侧属性栏 -->
<div class="properties-panel" id="properties-panel"></div>
</div>
.diagram-container,
.canvas {
height: 100%;
width: 100%;
}
:host .canvas ::ng-deep > .bjs-container {
height: 100% !important;
}
.properties-panel {
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 260px;
z-index: 10;
border-left: 1px solid #ccc;
overflow: auto;
}
.properties-panel:empty {
display: none;
}
.properties-panel > .djs-properties-panel {
padding-bottom: 70px;
min-height: 100%;
}
注意
导入 JSON 文件时使用这种方式
import { default as camundaModdleDescriptor } from 'camunda-bpmn-moddle/resources'
需要在 tsconfig.base.json 文件中加入下面这两个配置项
"compilerOptions": {
"resolveJsonModule": true,
"esModuleInterop": true,
}
网友评论