refactor: 重构 bpmnProcessDesigner => bpmn-process-designer
This commit is contained in:
@@ -0,0 +1,684 @@
|
||||
<script lang="ts" setup>
|
||||
// import 'bpmn-js/dist/assets/diagram-js.css' // 左边工具栏以及编辑节点的样式
|
||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
|
||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
|
||||
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
|
||||
// import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右侧框样式
|
||||
import {
|
||||
computed,
|
||||
defineEmits,
|
||||
defineOptions,
|
||||
defineProps,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
provide,
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
AlignLeftOutlined,
|
||||
ApiOutlined,
|
||||
DownloadOutlined,
|
||||
EyeOutlined,
|
||||
FolderOpenOutlined,
|
||||
RedoOutlined,
|
||||
ReloadOutlined,
|
||||
UndoOutlined,
|
||||
WarningOutlined,
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined,
|
||||
} from '@vben/icons';
|
||||
|
||||
import { Button, ButtonGroup, message, Modal, Tooltip } from 'ant-design-vue';
|
||||
// 模拟流转流程
|
||||
// @ts-ignore
|
||||
import tokenSimulation from 'bpmn-js-token-simulation';
|
||||
import BpmnModeler from 'bpmn-js/lib/Modeler';
|
||||
// 代码高亮插件
|
||||
// import hljs from 'highlight.js/lib/highlight'
|
||||
// import 'highlight.js/styles/github-gist.css'
|
||||
// hljs.registerLanguage('xml', 'highlight.js/lib/languages/xml')
|
||||
// hljs.registerLanguage('json', 'highlight.js/lib/languages/json')
|
||||
// const eventName = reactive({
|
||||
// name: ''
|
||||
// })
|
||||
import hljs from 'highlight.js'; // 导入代码高亮文件
|
||||
// 引入json转换与高亮
|
||||
// import xml2js from 'xml-js'
|
||||
// import xml2js from 'fast-xml-parser'
|
||||
import { parseXmlString, XmlNode } from 'steady-xml';
|
||||
|
||||
import DefaultEmptyXML from './plugins/defaultEmpty';
|
||||
import activitiModdleDescriptor from './plugins/descriptor/activitiDescriptor.json';
|
||||
// 标签解析构建器
|
||||
// import bpmnPropertiesProvider from "bpmn-js-properties-panel/lib/provider/bpmn";
|
||||
// import propertiesPanelModule from 'bpmn-js-properties-panel'
|
||||
// import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
|
||||
// 标签解析 Moddle
|
||||
import camundaModdleDescriptor from './plugins/descriptor/camundaDescriptor.json';
|
||||
import flowableModdleDescriptor from './plugins/descriptor/flowableDescriptor.json';
|
||||
import activitiModdleExtension from './plugins/extension-moddle/activiti';
|
||||
// 标签解析 Extension
|
||||
import camundaModdleExtension from './plugins/extension-moddle/camunda';
|
||||
import flowableModdleExtension from './plugins/extension-moddle/flowable';
|
||||
// 翻译方法
|
||||
import customTranslate from './plugins/translate/customTranslate';
|
||||
import translationsCN from './plugins/translate/zh';
|
||||
|
||||
import 'highlight.js/styles/github.css';
|
||||
|
||||
defineOptions({ name: 'MyProcessDesigner' });
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: String, default: '' }, // xml 字符串
|
||||
// valueWatch: true, // xml 字符串的 watch 状态
|
||||
processId: { type: String, default: '' }, // 流程 key 标识
|
||||
processName: { type: String, default: '' }, // 流程 name 名字
|
||||
formId: { type: Number, default: undefined }, // 流程 form 表单编号
|
||||
translations: {
|
||||
// 自定义的翻译文件
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
// eslint-disable-next-line vue/require-default-prop
|
||||
additionalModel: [Object, Array], // 自定义model
|
||||
moddleExtension: {
|
||||
// 自定义moddle
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
onlyCustomizeAddi: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
onlyCustomizeModdle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
simulation: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
keyboard: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'camunda',
|
||||
},
|
||||
events: {
|
||||
type: Array,
|
||||
default: () => ['element.click'],
|
||||
},
|
||||
headerButtonSize: {
|
||||
type: String,
|
||||
default: 'small',
|
||||
validator: (value: string) =>
|
||||
['default', 'medium', 'mini', 'small'].includes(value),
|
||||
},
|
||||
headerButtonType: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
validator: (value: string) =>
|
||||
['danger', 'default', 'info', 'primary', 'success', 'warning'].includes(
|
||||
value,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
// 导入代码高亮样式
|
||||
|
||||
const emit = defineEmits([
|
||||
'destroy',
|
||||
'init-finished',
|
||||
'save',
|
||||
'commandStack-changed',
|
||||
'input',
|
||||
'change',
|
||||
'canvas-viewbox-changed',
|
||||
// eventName.name
|
||||
'element-click',
|
||||
]);
|
||||
|
||||
const bpmnCanvas = ref();
|
||||
const refFile = ref();
|
||||
|
||||
/**
|
||||
* 代码高亮
|
||||
*/
|
||||
const highlightedCode = (code: string) => {
|
||||
// 高亮
|
||||
if (previewType.value === 'json') {
|
||||
code = JSON.stringify(code, null, 2);
|
||||
}
|
||||
const result = hljs.highlight(code, {
|
||||
language: previewType.value,
|
||||
ignoreIllegals: true,
|
||||
});
|
||||
return result.value || ' ';
|
||||
};
|
||||
|
||||
provide('configGlobal', props);
|
||||
let bpmnModeler: any = null;
|
||||
const defaultZoom = ref(1);
|
||||
const previewModelVisible = ref(false);
|
||||
const simulationStatus = ref(false);
|
||||
const previewResult = ref('');
|
||||
const previewType = ref('xml');
|
||||
const recoverable = ref(false);
|
||||
const revocable = ref(false);
|
||||
const additionalModules = computed(() => {
|
||||
// console.log(props.additionalModel, 'additionalModel');
|
||||
const Modules: any[] = [];
|
||||
// 仅保留用户自定义扩展模块
|
||||
if (props.onlyCustomizeAddi) {
|
||||
if (
|
||||
Object.prototype.toString.call(props.additionalModel) === '[object Array]'
|
||||
) {
|
||||
return props.additionalModel || [];
|
||||
}
|
||||
return [props.additionalModel];
|
||||
}
|
||||
|
||||
// 插入用户自定义扩展模块
|
||||
if (
|
||||
Object.prototype.toString.call(props.additionalModel) === '[object Array]'
|
||||
) {
|
||||
Modules.push(...(props.additionalModel as any[]));
|
||||
} else {
|
||||
props.additionalModel && Modules.push(props.additionalModel);
|
||||
}
|
||||
|
||||
// 翻译模块
|
||||
const TranslateModule = {
|
||||
translate: ['value', customTranslate(props.translations || translationsCN)],
|
||||
};
|
||||
Modules.push(TranslateModule);
|
||||
|
||||
// 模拟流转模块
|
||||
if (props.simulation) {
|
||||
Modules.push(tokenSimulation);
|
||||
}
|
||||
|
||||
// 根据需要的流程类型设置扩展元素构建模块
|
||||
// if (this.prefix === "bpmn") {
|
||||
// Modules.push(bpmnModdleExtension);
|
||||
// }
|
||||
// console.log(props.prefix, 'props.prefix ');
|
||||
if (props.prefix === 'camunda') {
|
||||
Modules.push(camundaModdleExtension);
|
||||
}
|
||||
if (props.prefix === 'flowable') {
|
||||
Modules.push(flowableModdleExtension);
|
||||
}
|
||||
if (props.prefix === 'activiti') {
|
||||
Modules.push(activitiModdleExtension);
|
||||
}
|
||||
|
||||
return Modules;
|
||||
});
|
||||
const moddleExtensions = computed(() => {
|
||||
// console.log(props.onlyCustomizeModdle, 'props.onlyCustomizeModdle');
|
||||
// console.log(props.moddleExtension, 'props.moddleExtension');
|
||||
// console.log(props.prefix, 'props.prefix');
|
||||
const Extensions: any = {};
|
||||
// 仅使用用户自定义模块
|
||||
if (props.onlyCustomizeModdle) {
|
||||
return props.moddleExtension || null;
|
||||
}
|
||||
|
||||
// 插入用户自定义模块
|
||||
if (props.moddleExtension) {
|
||||
for (const key in props.moddleExtension) {
|
||||
Extensions[key] = props.moddleExtension[key];
|
||||
}
|
||||
}
|
||||
|
||||
// 根据需要的 "流程类型" 设置 对应的解析文件
|
||||
if (props.prefix === 'activiti') {
|
||||
Extensions.activiti = activitiModdleDescriptor;
|
||||
}
|
||||
if (props.prefix === 'flowable') {
|
||||
Extensions.flowable = flowableModdleDescriptor;
|
||||
}
|
||||
if (props.prefix === 'camunda') {
|
||||
Extensions.camunda = camundaModdleDescriptor;
|
||||
}
|
||||
return Extensions;
|
||||
});
|
||||
// console.log(additionalModules, 'additionalModules()');
|
||||
// console.log(moddleExtensions, 'moddleExtensions()');
|
||||
const initBpmnModeler = () => {
|
||||
if (bpmnModeler) return;
|
||||
const data: any = document.querySelector('#bpmnCanvas');
|
||||
// console.log(data, 'data');
|
||||
// console.log(props.keyboard, 'props.keyboard');
|
||||
// console.log(additionalModules, 'additionalModules()');
|
||||
// console.log(moddleExtensions, 'moddleExtensions()');
|
||||
|
||||
bpmnModeler = new BpmnModeler({
|
||||
// container: this.$refs['bpmn-canvas'],
|
||||
// container: getCurrentInstance(),
|
||||
// container: needClass,
|
||||
// container: bpmnCanvas.value,
|
||||
container: data,
|
||||
// width: '100%',
|
||||
// 添加控制板
|
||||
// propertiesPanel: {
|
||||
// parent: '#js-properties-panel'
|
||||
// },
|
||||
keyboard: props.keyboard ? { bindTo: document } : null,
|
||||
// additionalModules: additionalModules.value,
|
||||
additionalModules: additionalModules.value as any[],
|
||||
moddleExtensions: moddleExtensions.value,
|
||||
|
||||
// additionalModules: [
|
||||
// additionalModules.value
|
||||
// propertiesPanelModule,
|
||||
// propertiesProviderModule
|
||||
// propertiesProviderModule
|
||||
// ],
|
||||
// moddleExtensions: { camunda: moddleExtensions.value }
|
||||
});
|
||||
|
||||
// bpmnModeler.createDiagram()
|
||||
|
||||
// console.log(bpmnModeler, 'bpmnModeler111111')
|
||||
// eslint-disable-next-line vue/custom-event-name-casing
|
||||
emit('init-finished', bpmnModeler);
|
||||
initModelListeners();
|
||||
};
|
||||
|
||||
const initModelListeners = () => {
|
||||
const EventBus = bpmnModeler.get('eventBus');
|
||||
// console.log(EventBus, 'EventBus');
|
||||
// 注册需要的监听事件, 将. 替换为 - , 避免解析异常
|
||||
props.events.forEach((event: any) => {
|
||||
EventBus.on(event, (eventObj: any) => {
|
||||
// const eventName = event.replaceAll('.', '-');
|
||||
// eventName.name = eventName
|
||||
const element = eventObj ? eventObj.element : null;
|
||||
// console.log(eventName, 'eventName');
|
||||
// console.log(element, 'element');
|
||||
// eslint-disable-next-line vue/custom-event-name-casing
|
||||
emit('element-click', element, eventObj);
|
||||
// emit(eventName, element, eventObj)
|
||||
});
|
||||
});
|
||||
// 监听图形改变返回xml
|
||||
EventBus.on('commandStack.changed', async (event: any) => {
|
||||
try {
|
||||
recoverable.value = bpmnModeler.get('commandStack').canRedo();
|
||||
revocable.value = bpmnModeler.get('commandStack').canUndo();
|
||||
const { xml } = await bpmnModeler.saveXML({ format: true });
|
||||
// eslint-disable-next-line vue/custom-event-name-casing
|
||||
emit('commandStack-changed', event);
|
||||
emit('input', xml);
|
||||
emit('change', xml);
|
||||
emit('save', xml);
|
||||
} catch {
|
||||
// console.error(`[Process Designer Warn]: ${e.message || e}`);
|
||||
}
|
||||
});
|
||||
// 监听视图缩放变化
|
||||
bpmnModeler.on('canvas.viewbox.changed', ({ viewbox }: { viewbox: any }) => {
|
||||
// eslint-disable-next-line vue/custom-event-name-casing
|
||||
emit('canvas-viewbox-changed', { viewbox });
|
||||
const { scale } = viewbox;
|
||||
defaultZoom.value = Math.floor(scale * 100) / 100;
|
||||
});
|
||||
};
|
||||
/* 创建新的流程图 */
|
||||
const createNewDiagram = async (xml: any) => {
|
||||
// console.log(xml, 'xml');
|
||||
// 将字符串转换成图显示出来
|
||||
const newId = props.processId || `Process_${Date.now()}`;
|
||||
const newName = props.processName || `业务流程_${Date.now()}`;
|
||||
const xmlString = xml || DefaultEmptyXML(newId, newName, props.prefix);
|
||||
try {
|
||||
// console.log(xmlString, 'xmlString')
|
||||
// console.log(this.bpmnModeler.importXML);
|
||||
const { warnings } = await bpmnModeler.importXML(xmlString);
|
||||
// console.log(warnings, 'warnings');
|
||||
if (warnings && warnings.length > 0) {
|
||||
// warnings.forEach((warn: any) => console.warn(warn));
|
||||
}
|
||||
} catch {
|
||||
// console.error(`[Process Designer Warn]: ${e.message || e}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 下载流程图到本地
|
||||
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(), xml);
|
||||
downloadFunc(href, filename);
|
||||
} else {
|
||||
const { err, svg } = await bpmnModeler.saveSVG();
|
||||
// 读取异常时抛出异常
|
||||
if (err) {
|
||||
// return console.error(err);
|
||||
}
|
||||
const { href, filename } = setEncoded('SVG', svg);
|
||||
downloadFunc(href, filename);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`[Process Designer Warn ]: ${error.message || error}`);
|
||||
}
|
||||
// 文件下载方法
|
||||
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 对象
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 根据所需类型进行转码并返回下载地址
|
||||
const setEncoded = (type: string, data: string) => {
|
||||
const filename = 'diagram';
|
||||
const encodedData = encodeURIComponent(data);
|
||||
return {
|
||||
filename: `${filename}.${type}`,
|
||||
href: `data:application/${
|
||||
type === 'svg' ? 'text/xml' : 'bpmn20-xml'
|
||||
};charset=UTF-8,${encodedData}`,
|
||||
data,
|
||||
};
|
||||
};
|
||||
|
||||
// 加载本地文件
|
||||
const importLocalFile = () => {
|
||||
const file = refFile.value.files[0];
|
||||
const reader = new FileReader();
|
||||
// eslint-disable-next-line unicorn/prefer-blob-reading-methods
|
||||
reader.readAsText(file);
|
||||
reader.addEventListener('load', function () {
|
||||
const xmlStr = this.result;
|
||||
createNewDiagram(xmlStr);
|
||||
emit('save', xmlStr);
|
||||
});
|
||||
};
|
||||
/* ------------------------------------------------ refs methods ------------------------------------------------------ */
|
||||
const downloadProcessAsXml = () => {
|
||||
downloadProcess('xml');
|
||||
};
|
||||
const downloadProcessAsBpmn = () => {
|
||||
downloadProcess('bpmn');
|
||||
};
|
||||
const downloadProcessAsSvg = () => {
|
||||
downloadProcess('svg');
|
||||
};
|
||||
const processSimulation = () => {
|
||||
simulationStatus.value = !simulationStatus.value;
|
||||
// console.log(
|
||||
// bpmnModeler.get('toggleMode', 'strict'),
|
||||
// "bpmnModeler.get('toggleMode')",
|
||||
// );
|
||||
props.simulation && bpmnModeler.get('toggleMode', 'strict').toggleMode();
|
||||
};
|
||||
const processRedo = () => {
|
||||
bpmnModeler.get('commandStack').redo();
|
||||
};
|
||||
const processUndo = () => {
|
||||
bpmnModeler.get('commandStack').undo();
|
||||
};
|
||||
const processZoomIn = (zoomStep = 0.1) => {
|
||||
const newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100;
|
||||
if (newZoom > 4) {
|
||||
throw new Error(
|
||||
'[Process Designer Warn ]: The zoom ratio cannot be greater than 4',
|
||||
);
|
||||
}
|
||||
defaultZoom.value = newZoom;
|
||||
bpmnModeler.get('canvas').zoom(defaultZoom.value);
|
||||
};
|
||||
const processZoomOut = (zoomStep = 0.1) => {
|
||||
const newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100;
|
||||
if (newZoom < 0.2) {
|
||||
throw new Error(
|
||||
'[Process Designer Warn ]: The zoom ratio cannot be less than 0.2',
|
||||
);
|
||||
}
|
||||
defaultZoom.value = newZoom;
|
||||
bpmnModeler.get('canvas').zoom(defaultZoom.value);
|
||||
};
|
||||
const processReZoom = () => {
|
||||
defaultZoom.value = 1;
|
||||
bpmnModeler.get('canvas').zoom('fit-viewport', 'auto');
|
||||
};
|
||||
const processRestart = () => {
|
||||
recoverable.value = false;
|
||||
revocable.value = false;
|
||||
createNewDiagram(null);
|
||||
};
|
||||
const elementsAlign = (align: string) => {
|
||||
const Align = bpmnModeler.get('alignElements');
|
||||
const Selection = bpmnModeler.get('selection');
|
||||
const SelectedElements = Selection.get();
|
||||
if (!SelectedElements || SelectedElements.length <= 1) {
|
||||
message.warning('请按住 Shift 键选择多个元素对齐');
|
||||
// alert('请按住 Ctrl 键选择多个元素对齐
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '警告',
|
||||
content: '自动对齐可能造成图形变形,是否继续?',
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
icon: WarningOutlined as any,
|
||||
onOk() {
|
||||
Align.trigger(SelectedElements, align);
|
||||
},
|
||||
});
|
||||
};
|
||||
/* ----------------------------- 方法结束 ---------------------------------*/
|
||||
const previewProcessXML = () => {
|
||||
// console.log(bpmnModeler.saveXML, 'bpmnModeler');
|
||||
bpmnModeler.saveXML({ format: true }).then(({ xml }: { xml: string }) => {
|
||||
// console.log(xml, 'xml111111')
|
||||
previewResult.value = xml;
|
||||
previewType.value = 'xml';
|
||||
previewModelVisible.value = true;
|
||||
});
|
||||
};
|
||||
const previewProcessJson = () => {
|
||||
bpmnModeler.saveXML({ format: true }).then(({ xml }: { xml: string }) => {
|
||||
const rootNodes = new XmlNode('root' as any, parseXmlString(xml));
|
||||
previewResult.value = rootNodes.parent?.toJSON() as unknown as string;
|
||||
previewType.value = 'json';
|
||||
previewModelVisible.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
|
||||
onMounted(() => {
|
||||
initBpmnModeler();
|
||||
createNewDiagram(props.value);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (bpmnModeler) bpmnModeler.destroy();
|
||||
emit('destroy', bpmnModeler);
|
||||
bpmnModeler = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="my-process-designer">
|
||||
<div
|
||||
class="my-process-designer__header"
|
||||
style="z-index: 999; display: table-row-group"
|
||||
>
|
||||
<slot name="control-header"></slot>
|
||||
<template v-if="!$slots['control-header']">
|
||||
<ButtonGroup key="file-control">
|
||||
<Button
|
||||
:icon="FolderOpenOutlined"
|
||||
title="打开文件"
|
||||
@click="refFile.click()"
|
||||
/>
|
||||
<Tooltip placement="bottom">
|
||||
<template #title>
|
||||
<div>
|
||||
<Button type="link" @click="downloadProcessAsXml()">
|
||||
下载为XML文件
|
||||
</Button>
|
||||
<br />
|
||||
<Button type="link" @click="downloadProcessAsSvg()">
|
||||
下载为SVG文件
|
||||
</Button>
|
||||
<br />
|
||||
<Button type="link" @click="downloadProcessAsBpmn()">
|
||||
下载为BPMN文件
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<Button :icon="DownloadOutlined" title="下载文件" />
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<template #title>
|
||||
<Button type="link" @click="previewProcessXML">预览XML</Button>
|
||||
<br />
|
||||
<Button type="link" @click="previewProcessJson">预览JSON</Button>
|
||||
</template>
|
||||
<Button :icon="EyeOutlined" title="浏览" />
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
v-if="props.simulation"
|
||||
:title="simulationStatus ? '退出模拟' : '开启模拟'"
|
||||
>
|
||||
<Button
|
||||
:icon="ApiOutlined"
|
||||
title="模拟"
|
||||
@click="processSimulation"
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup key="align-control">
|
||||
<Tooltip title="向左对齐">
|
||||
<Button
|
||||
:icon="AlignLeftOutlined"
|
||||
class="align align-bottom"
|
||||
@click="elementsAlign('left')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="向右对齐">
|
||||
<Button
|
||||
:icon="AlignLeftOutlined"
|
||||
class="align align-top"
|
||||
@click="elementsAlign('right')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="向上对齐">
|
||||
<Button
|
||||
:icon="AlignLeftOutlined"
|
||||
class="align align-left"
|
||||
@click="elementsAlign('top')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="向下对齐">
|
||||
<Button
|
||||
:icon="AlignLeftOutlined"
|
||||
class="align align-right"
|
||||
@click="elementsAlign('bottom')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="水平居中">
|
||||
<Button
|
||||
:icon="AlignLeftOutlined"
|
||||
class="align align-center"
|
||||
@click="elementsAlign('center')"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="垂直居中">
|
||||
<Button
|
||||
:icon="AlignLeftOutlined"
|
||||
class="align align-middle"
|
||||
@click="elementsAlign('middle')"
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup key="scale-control">
|
||||
<Tooltip title="缩小视图">
|
||||
<Button
|
||||
:icon="ZoomOutOutlined"
|
||||
@click="processZoomOut()"
|
||||
:disabled="defaultZoom < 0.2"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button>{{ `${Math.floor(defaultZoom * 10 * 10)}%` }}</Button>
|
||||
<Tooltip title="放大视图">
|
||||
<Button
|
||||
:icon="ZoomInOutlined"
|
||||
@click="processZoomIn()"
|
||||
:disabled="defaultZoom > 4"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="重置视图并居中">
|
||||
<Button :icon="ReloadOutlined" @click="processReZoom()" />
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup key="stack-control">
|
||||
<Tooltip title="撤销">
|
||||
<Button
|
||||
:icon="UndoOutlined"
|
||||
@click="processUndo()"
|
||||
:disabled="!revocable"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="恢复">
|
||||
<Button
|
||||
:icon="RedoOutlined"
|
||||
@click="processRedo()"
|
||||
:disabled="!recoverable"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="重新绘制">
|
||||
<Button :icon="ReloadOutlined" @click="processRestart()" />
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</template>
|
||||
<!-- 用于打开本地文件-->
|
||||
<input
|
||||
type="file"
|
||||
id="files"
|
||||
ref="refFile"
|
||||
style="display: none"
|
||||
accept=".xml, .bpmn"
|
||||
@change="importLocalFile"
|
||||
/>
|
||||
</div>
|
||||
<div class="my-process-designer__container">
|
||||
<div
|
||||
class="my-process-designer__canvas"
|
||||
ref="bpmnCanvas"
|
||||
id="bpmnCanvas"
|
||||
style="width: 1680px; height: 800px"
|
||||
></div>
|
||||
<!-- <div id="js-properties-panel" class="panel"></div> -->
|
||||
<!-- <div class="my-process-designer__canvas" ref="bpmn-canvas"></div> -->
|
||||
</div>
|
||||
<Dialog
|
||||
title="预览"
|
||||
v-model:open="previewModelVisible"
|
||||
width="80%"
|
||||
:scroll="true"
|
||||
style="max-height: 600px"
|
||||
>
|
||||
<div>
|
||||
<pre><code v-dompurify-html="highlightedCode(previewResult)" class="hljs"></code></pre>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,418 @@
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { BpmProcessInstanceStatus } from '@vben/constants';
|
||||
import { UndoOutlined, ZoomInOutlined, ZoomOutOutlined } from '@vben/icons';
|
||||
import { dateFormatter, formatPast2 } from '@vben/utils';
|
||||
|
||||
import { Button, ButtonGroup, Modal, Row, Table } from 'ant-design-vue';
|
||||
import BpmnViewer from 'bpmn-js/lib/Viewer';
|
||||
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
|
||||
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
import { DICT_TYPE } from '#/utils/dict';
|
||||
|
||||
import '../theme/index.scss';
|
||||
|
||||
const props = defineProps({
|
||||
xml: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
view: {
|
||||
type: Object,
|
||||
require: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const processCanvas = ref();
|
||||
const bpmnViewer = ref<any | BpmnViewer>(null);
|
||||
const customDefs = ref();
|
||||
const defaultZoom = ref(1); // 默认缩放比例
|
||||
const isLoading = ref(false); // 是否加载中
|
||||
|
||||
const processInstance = ref<any>({}); // 流程实例
|
||||
const tasks = ref([]); // 流程任务
|
||||
|
||||
const dialogVisible = ref(false); // 弹窗可见性
|
||||
const dialogTitle = ref<string | undefined>(undefined); // 弹窗标题
|
||||
const selectActivityType = ref<string | undefined>(undefined); // 选中 Task 的活动编号
|
||||
const selectTasks = ref<any[]>([]); // 选中的任务数组
|
||||
|
||||
/** Zoom:恢复 */
|
||||
const processReZoom = () => {
|
||||
defaultZoom.value = 1;
|
||||
bpmnViewer.value?.get('canvas').zoom('fit-viewport', 'auto');
|
||||
};
|
||||
|
||||
/** Zoom:放大 */
|
||||
const processZoomIn = (zoomStep = 0.1) => {
|
||||
const newZoom = Math.floor(defaultZoom.value * 100 + zoomStep * 100) / 100;
|
||||
if (newZoom > 4) {
|
||||
throw new Error(
|
||||
'[Process Designer Warn ]: The zoom ratio cannot be greater than 4',
|
||||
);
|
||||
}
|
||||
defaultZoom.value = newZoom;
|
||||
bpmnViewer.value?.get('canvas').zoom(defaultZoom.value);
|
||||
};
|
||||
|
||||
/** Zoom:缩小 */
|
||||
const processZoomOut = (zoomStep = 0.1) => {
|
||||
const newZoom = Math.floor(defaultZoom.value * 100 - zoomStep * 100) / 100;
|
||||
if (newZoom < 0.2) {
|
||||
throw new Error(
|
||||
'[Process Designer Warn ]: The zoom ratio cannot be less than 0.2',
|
||||
);
|
||||
}
|
||||
defaultZoom.value = newZoom;
|
||||
bpmnViewer.value?.get('canvas').zoom(defaultZoom.value);
|
||||
};
|
||||
|
||||
/** 流程图预览清空 */
|
||||
const clearViewer = () => {
|
||||
if (processCanvas.value) {
|
||||
processCanvas.value.innerHTML = '';
|
||||
}
|
||||
if (bpmnViewer.value) {
|
||||
bpmnViewer.value.destroy();
|
||||
}
|
||||
bpmnViewer.value = null;
|
||||
};
|
||||
|
||||
/** 添加自定义箭头 */
|
||||
// TODO 芋艿:自定义箭头不生效,有点奇怪!!!!相关的 marker-end、marker-start 暂时也注释了!!!
|
||||
const addCustomDefs = () => {
|
||||
if (!bpmnViewer.value) {
|
||||
return;
|
||||
}
|
||||
const canvas = bpmnViewer.value?.get('canvas');
|
||||
const svg = canvas?._svg;
|
||||
svg.append(customDefs.value);
|
||||
};
|
||||
|
||||
/** 节点选中 */
|
||||
const onSelectElement = (element: any) => {
|
||||
// 清空原选中
|
||||
selectActivityType.value = undefined;
|
||||
dialogTitle.value = undefined;
|
||||
if (!element || !processInstance.value?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// UserTask 的情况
|
||||
const activityType = element.type;
|
||||
selectActivityType.value = activityType;
|
||||
if (activityType === 'bpmn:UserTask') {
|
||||
dialogTitle.value = element.businessObject
|
||||
? element.businessObject.name
|
||||
: undefined;
|
||||
selectTasks.value = tasks.value.filter(
|
||||
(item: any) => item?.taskDefinitionKey === element.id,
|
||||
);
|
||||
dialogVisible.value = true;
|
||||
} else if (
|
||||
activityType === 'bpmn:EndEvent' ||
|
||||
activityType === 'bpmn:StartEvent'
|
||||
) {
|
||||
dialogTitle.value = '审批信息';
|
||||
selectTasks.value = [
|
||||
{
|
||||
assigneeUser: processInstance.value.startUser,
|
||||
createTime: processInstance.value.startTime,
|
||||
endTime: processInstance.value.endTime,
|
||||
status: processInstance.value.status,
|
||||
durationInMillis: processInstance.value.durationInMillis,
|
||||
},
|
||||
];
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
/** 初始化 BPMN 视图 */
|
||||
const importXML = async (xml: string) => {
|
||||
// 清空流程图
|
||||
clearViewer();
|
||||
|
||||
// 初始化流程图
|
||||
if (xml !== null && xml !== '') {
|
||||
try {
|
||||
bpmnViewer.value = new BpmnViewer({
|
||||
additionalModules: [MoveCanvasModule],
|
||||
container: processCanvas.value,
|
||||
});
|
||||
// 增加点击事件
|
||||
bpmnViewer.value.on('element.click', ({ element }: { element: any }) => {
|
||||
onSelectElement(element);
|
||||
});
|
||||
|
||||
// 初始化 BPMN 视图
|
||||
isLoading.value = true;
|
||||
await bpmnViewer.value.importXML(xml);
|
||||
// 自定义成功的箭头
|
||||
addCustomDefs();
|
||||
} catch {
|
||||
clearViewer();
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
// 高亮流程
|
||||
setProcessStatus(props.view);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 高亮流程 */
|
||||
const setProcessStatus = (view: any) => {
|
||||
// 设置相关变量
|
||||
if (!view || !view.processInstance) {
|
||||
return;
|
||||
}
|
||||
processInstance.value = view.processInstance;
|
||||
tasks.value = view.tasks;
|
||||
if (isLoading.value || !bpmnViewer.value) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
unfinishedTaskActivityIds,
|
||||
finishedTaskActivityIds,
|
||||
finishedSequenceFlowActivityIds,
|
||||
rejectedTaskActivityIds,
|
||||
} = view;
|
||||
const canvas: any = bpmnViewer.value.get('canvas');
|
||||
const elementRegistry: any = bpmnViewer.value.get('elementRegistry');
|
||||
|
||||
// 已完成节点
|
||||
if (Array.isArray(finishedSequenceFlowActivityIds)) {
|
||||
finishedSequenceFlowActivityIds.forEach((item: any) => {
|
||||
if (item !== null) {
|
||||
canvas.addMarker(item, 'success');
|
||||
const element = elementRegistry.get(item);
|
||||
const conditionExpression = element.businessObject.conditionExpression;
|
||||
if (conditionExpression) {
|
||||
canvas.addMarker(item, 'condition-expression');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Array.isArray(finishedTaskActivityIds)) {
|
||||
finishedTaskActivityIds.forEach((item: any) =>
|
||||
canvas.addMarker(item, 'success'),
|
||||
);
|
||||
}
|
||||
|
||||
// 未完成节点
|
||||
if (Array.isArray(unfinishedTaskActivityIds)) {
|
||||
unfinishedTaskActivityIds.forEach((item: any) =>
|
||||
canvas.addMarker(item, 'primary'),
|
||||
);
|
||||
}
|
||||
|
||||
// 被拒绝节点
|
||||
if (Array.isArray(rejectedTaskActivityIds)) {
|
||||
rejectedTaskActivityIds.forEach((item: any) => {
|
||||
if (item !== null) {
|
||||
canvas.addMarker(item, 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 特殊:处理 end 节点的高亮。因为 end 在拒绝、取消时,被后端计算成了 finishedTaskActivityIds 里
|
||||
if (
|
||||
[BpmProcessInstanceStatus.CANCEL, BpmProcessInstanceStatus.REJECT].includes(
|
||||
processInstance.value.status,
|
||||
)
|
||||
) {
|
||||
const endNodes = elementRegistry.filter(
|
||||
(element: any) => element.type === 'bpmn:EndEvent',
|
||||
);
|
||||
endNodes.forEach((item: any) => {
|
||||
canvas.removeMarker(item.id, 'success');
|
||||
if (processInstance.value.status === BpmProcessInstanceStatus.CANCEL) {
|
||||
canvas.addMarker(item.id, 'cancel');
|
||||
} else {
|
||||
canvas.addMarker(item.id, 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.xml,
|
||||
(newXml) => {
|
||||
importXML(newXml);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.view,
|
||||
(newView) => {
|
||||
setProcessStatus(newView);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** mounted:初始化 */
|
||||
onMounted(() => {
|
||||
importXML(props.xml);
|
||||
setProcessStatus(props.view);
|
||||
});
|
||||
|
||||
/** unmount:销毁 */
|
||||
onBeforeUnmount(() => {
|
||||
clearViewer();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="process-viewer">
|
||||
<div style="height: 100%" ref="processCanvas" v-show="!isLoading"></div>
|
||||
<!-- 自定义箭头样式,用于已完成状态下流程连线箭头 -->
|
||||
<defs ref="customDefs">
|
||||
<marker
|
||||
id="sequenceflow-end-white-success"
|
||||
viewBox="0 0 20 20"
|
||||
refX="11"
|
||||
refY="10"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto"
|
||||
>
|
||||
<path
|
||||
class="success-arrow"
|
||||
d="M 1 5 L 11 10 L 1 15 Z"
|
||||
style="
|
||||
stroke-width: 1px;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 10000, 1;
|
||||
"
|
||||
/>
|
||||
</marker>
|
||||
<marker
|
||||
id="conditional-flow-marker-white-success"
|
||||
viewBox="0 0 20 20"
|
||||
refX="-1"
|
||||
refY="10"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto"
|
||||
>
|
||||
<path
|
||||
class="success-conditional"
|
||||
d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
|
||||
style="
|
||||
stroke-width: 1px;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 10000, 1;
|
||||
"
|
||||
/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- 审批记录 -->
|
||||
<Modal
|
||||
:title="dialogTitle || '审批记录'"
|
||||
v-model:open="dialogVisible"
|
||||
:width="1000"
|
||||
>
|
||||
<Row>
|
||||
<Table :data-source="selectTasks" size="small" :bordered="true">
|
||||
<Table.Column title="序号" align="center" width="50">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
title="审批人"
|
||||
width="100"
|
||||
align="center"
|
||||
v-if="selectActivityType === 'bpmn:UserTask'"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
{{ record.assigneeUser?.nickname || record.ownerUser?.nickname }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
title="发起人"
|
||||
data-index="assigneeUser.nickname"
|
||||
width="100"
|
||||
align="center"
|
||||
v-else
|
||||
/>
|
||||
<Table.Column title="部门" width="100" align="center">
|
||||
<template #default="{ record }">
|
||||
{{ record.assigneeUser?.deptName || record.ownerUser?.deptName }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
:custom-render="({ text }) => dateFormatter(text)"
|
||||
align="center"
|
||||
title="开始时间"
|
||||
data-index="createTime"
|
||||
width="140"
|
||||
/>
|
||||
<Table.Column
|
||||
:custom-render="({ text }) => dateFormatter(text)"
|
||||
align="center"
|
||||
title="结束时间"
|
||||
data-index="endTime"
|
||||
width="140"
|
||||
/>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="审批状态"
|
||||
data-index="status"
|
||||
width="90"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.BPM_TASK_STATUS"
|
||||
:value="record.status"
|
||||
/>
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="审批建议"
|
||||
data-index="reason"
|
||||
width="120"
|
||||
v-if="selectActivityType === 'bpmn:UserTask'"
|
||||
/>
|
||||
<Table.Column
|
||||
align="center"
|
||||
title="耗时"
|
||||
data-index="durationInMillis"
|
||||
width="100"
|
||||
>
|
||||
<template #default="{ record }">
|
||||
{{ formatPast2(record.durationInMillis) }}
|
||||
</template>
|
||||
</Table.Column>
|
||||
</Table>
|
||||
</Row>
|
||||
</Modal>
|
||||
|
||||
<!-- Zoom:放大、缩小 -->
|
||||
<div style="position: absolute; top: 0; left: 0; width: 100%">
|
||||
<Row justify="end">
|
||||
<ButtonGroup key="scale-control">
|
||||
<Button
|
||||
:disabled="defaultZoom <= 0.3"
|
||||
:icon="ZoomOutOutlined"
|
||||
@click="processZoomOut()"
|
||||
/>
|
||||
<Button style="width: 90px">
|
||||
{{ `${Math.floor(defaultZoom * 10 * 10)}%` }}
|
||||
</Button>
|
||||
<Button
|
||||
:disabled="defaultZoom >= 3.9"
|
||||
:icon="ZoomInOutlined"
|
||||
@click="processZoomIn()"
|
||||
/>
|
||||
<Button :icon="UndoOutlined" @click="processReZoom()" />
|
||||
</ButtonGroup>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,8 @@
|
||||
import MyProcessDesigner from './ProcessDesigner.vue';
|
||||
|
||||
MyProcessDesigner.install = function (Vue: any) {
|
||||
Vue.component(MyProcessDesigner.name, MyProcessDesigner);
|
||||
};
|
||||
|
||||
// 流程图的设计器,可编辑
|
||||
export default MyProcessDesigner;
|
||||
@@ -0,0 +1,8 @@
|
||||
import MyProcessViewer from './ProcessViewer.vue';
|
||||
|
||||
MyProcessViewer.install = function (Vue: any) {
|
||||
Vue.component(MyProcessViewer.name, MyProcessViewer);
|
||||
};
|
||||
|
||||
// 流程图的查看器,不可编辑
|
||||
export default MyProcessViewer;
|
||||
@@ -0,0 +1,440 @@
|
||||
import { getChildLanes } from 'bpmn-js/lib/features/modeling/util/LaneUtil';
|
||||
import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
|
||||
import { isEventSubProcess, isExpanded } from 'bpmn-js/lib/util/DiUtil';
|
||||
import { is } from 'bpmn-js/lib/util/ModelUtil';
|
||||
import { hasPrimaryModifier } from 'diagram-js/lib/util/Mouse';
|
||||
import { assign, forEach, isArray } from 'min-dash';
|
||||
|
||||
/**
|
||||
* A provider for BPMN 2.0 elements context pad
|
||||
*/
|
||||
export default function ContextPadProvider(
|
||||
config,
|
||||
injector,
|
||||
eventBus,
|
||||
contextPad,
|
||||
modeling,
|
||||
elementFactory,
|
||||
connect,
|
||||
create,
|
||||
popupMenu,
|
||||
canvas,
|
||||
rules,
|
||||
translate,
|
||||
) {
|
||||
config = config || {};
|
||||
|
||||
contextPad.registerProvider(this);
|
||||
|
||||
this._contextPad = contextPad;
|
||||
|
||||
this._modeling = modeling;
|
||||
|
||||
this._elementFactory = elementFactory;
|
||||
this._connect = connect;
|
||||
this._create = create;
|
||||
this._popupMenu = popupMenu;
|
||||
this._canvas = canvas;
|
||||
this._rules = rules;
|
||||
this._translate = translate;
|
||||
|
||||
if (config.autoPlace !== false) {
|
||||
this._autoPlace = injector.get('autoPlace', false);
|
||||
}
|
||||
|
||||
eventBus.on('create.end', 250, (event) => {
|
||||
const context = event.context;
|
||||
const shape = context.shape;
|
||||
|
||||
if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = contextPad.getEntries(shape);
|
||||
|
||||
if (entries.replace) {
|
||||
entries.replace.action.click(event, shape);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ContextPadProvider.$inject = [
|
||||
'config.contextPad',
|
||||
'injector',
|
||||
'eventBus',
|
||||
'contextPad',
|
||||
'modeling',
|
||||
'elementFactory',
|
||||
'connect',
|
||||
'create',
|
||||
'popupMenu',
|
||||
'canvas',
|
||||
'rules',
|
||||
'translate',
|
||||
'elementRegistry',
|
||||
];
|
||||
|
||||
ContextPadProvider.prototype.getContextPadEntries = function (element) {
|
||||
const autoPlace = this._autoPlace;
|
||||
const canvas = this._canvas;
|
||||
const connect = this._connect;
|
||||
const contextPad = this._contextPad;
|
||||
const create = this._create;
|
||||
const elementFactory = this._elementFactory;
|
||||
const modeling = this._modeling;
|
||||
const popupMenu = this._popupMenu;
|
||||
const rules = this._rules;
|
||||
const translate = this._translate;
|
||||
|
||||
const actions = {};
|
||||
|
||||
if (element.type === 'label') {
|
||||
return actions;
|
||||
}
|
||||
|
||||
const businessObject = element.businessObject;
|
||||
|
||||
function startConnect(event, element) {
|
||||
connect.start(event, element);
|
||||
}
|
||||
|
||||
function removeElement() {
|
||||
modeling.removeElements([element]);
|
||||
}
|
||||
|
||||
function getReplaceMenuPosition(element) {
|
||||
const Y_OFFSET = 5;
|
||||
|
||||
const diagramContainer = canvas.getContainer();
|
||||
const pad = contextPad.getPad(element).html;
|
||||
|
||||
const diagramRect = diagramContainer.getBoundingClientRect();
|
||||
const padRect = pad.getBoundingClientRect();
|
||||
|
||||
const top = padRect.top - diagramRect.top;
|
||||
const left = padRect.left - diagramRect.left;
|
||||
|
||||
const pos = {
|
||||
x: left,
|
||||
y: top + padRect.height + Y_OFFSET,
|
||||
};
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an append action
|
||||
*
|
||||
* @param {string} type
|
||||
* @param {string} className
|
||||
* @param {string} [title]
|
||||
* @param {object} [options]
|
||||
*
|
||||
* @return {object} descriptor
|
||||
*/
|
||||
function appendAction(type, className, title, options) {
|
||||
if (typeof title !== 'string') {
|
||||
options = title;
|
||||
title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
|
||||
}
|
||||
|
||||
function appendStart(event, element) {
|
||||
const shape = elementFactory.createShape(assign({ type }, options));
|
||||
create.start(event, shape, {
|
||||
source: element,
|
||||
});
|
||||
}
|
||||
|
||||
const append = autoPlace
|
||||
? function (event, element) {
|
||||
const shape = elementFactory.createShape(assign({ type }, options));
|
||||
|
||||
autoPlace.append(element, shape);
|
||||
}
|
||||
: appendStart;
|
||||
|
||||
return {
|
||||
group: 'model',
|
||||
className,
|
||||
title,
|
||||
action: {
|
||||
dragstart: appendStart,
|
||||
click: append,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function splitLaneHandler(count) {
|
||||
return function (event, element) {
|
||||
// actual split
|
||||
modeling.splitLane(element, count);
|
||||
|
||||
// refresh context pad after split to
|
||||
// get rid of split icons
|
||||
contextPad.open(element, true);
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(businessObject, ['bpmn:Lane', 'bpmn:Participant']) &&
|
||||
isExpanded(businessObject)
|
||||
) {
|
||||
const childLanes = getChildLanes(element);
|
||||
|
||||
assign(actions, {
|
||||
'lane-insert-above': {
|
||||
group: 'lane-insert-above',
|
||||
className: 'bpmn-icon-lane-insert-above',
|
||||
title: translate('Add Lane above'),
|
||||
action: {
|
||||
click(event, element) {
|
||||
modeling.addLane(element, 'top');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (childLanes.length < 2) {
|
||||
if (element.height >= 120) {
|
||||
assign(actions, {
|
||||
'lane-divide-two': {
|
||||
group: 'lane-divide',
|
||||
className: 'bpmn-icon-lane-divide-two',
|
||||
title: translate('Divide into two Lanes'),
|
||||
action: {
|
||||
click: splitLaneHandler(2),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (element.height >= 180) {
|
||||
assign(actions, {
|
||||
'lane-divide-three': {
|
||||
group: 'lane-divide',
|
||||
className: 'bpmn-icon-lane-divide-three',
|
||||
title: translate('Divide into three Lanes'),
|
||||
action: {
|
||||
click: splitLaneHandler(3),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'lane-insert-below': {
|
||||
group: 'lane-insert-below',
|
||||
className: 'bpmn-icon-lane-insert-below',
|
||||
title: translate('Add Lane below'),
|
||||
action: {
|
||||
click(event, element) {
|
||||
modeling.addLane(element, 'bottom');
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:FlowNode')) {
|
||||
if (is(businessObject, 'bpmn:EventBasedGateway')) {
|
||||
assign(actions, {
|
||||
'append.receive-task': appendAction(
|
||||
'bpmn:ReceiveTask',
|
||||
'bpmn-icon-receive-task',
|
||||
translate('Append ReceiveTask'),
|
||||
),
|
||||
'append.message-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-message',
|
||||
translate('Append MessageIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:MessageEventDefinition' },
|
||||
),
|
||||
'append.timer-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-timer',
|
||||
translate('Append TimerIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:TimerEventDefinition' },
|
||||
),
|
||||
'append.condition-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-condition',
|
||||
translate('Append ConditionIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:ConditionalEventDefinition' },
|
||||
),
|
||||
'append.signal-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-signal',
|
||||
translate('Append SignalIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:SignalEventDefinition' },
|
||||
),
|
||||
});
|
||||
} else if (
|
||||
isEventType(
|
||||
businessObject,
|
||||
'bpmn:BoundaryEvent',
|
||||
'bpmn:CompensateEventDefinition',
|
||||
)
|
||||
) {
|
||||
assign(actions, {
|
||||
'append.compensation-activity': appendAction(
|
||||
'bpmn:Task',
|
||||
'bpmn-icon-task',
|
||||
translate('Append compensation activity'),
|
||||
{
|
||||
isForCompensation: true,
|
||||
},
|
||||
),
|
||||
});
|
||||
} else if (
|
||||
!is(businessObject, 'bpmn:EndEvent') &&
|
||||
!businessObject.isForCompensation &&
|
||||
!isEventType(
|
||||
businessObject,
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'bpmn:LinkEventDefinition',
|
||||
) &&
|
||||
!isEventSubProcess(businessObject)
|
||||
) {
|
||||
assign(actions, {
|
||||
'append.end-event': appendAction(
|
||||
'bpmn:EndEvent',
|
||||
'bpmn-icon-end-event-none',
|
||||
translate('Append EndEvent'),
|
||||
),
|
||||
'append.gateway': appendAction(
|
||||
'bpmn:ExclusiveGateway',
|
||||
'bpmn-icon-gateway-none',
|
||||
translate('Append Gateway'),
|
||||
),
|
||||
'append.append-task': appendAction(
|
||||
'bpmn:UserTask',
|
||||
'bpmn-icon-user-task',
|
||||
translate('Append Task'),
|
||||
),
|
||||
'append.intermediate-event': appendAction(
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'bpmn-icon-intermediate-event-none',
|
||||
translate('Append Intermediate/Boundary Event'),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
|
||||
// Replace menu entry
|
||||
assign(actions, {
|
||||
replace: {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-screw-wrench',
|
||||
title: '修改类型',
|
||||
action: {
|
||||
click(event, element) {
|
||||
const position = assign(getReplaceMenuPosition(element), {
|
||||
cursor: { x: event.x, y: event.y },
|
||||
});
|
||||
|
||||
popupMenu.open(element, 'bpmn-replace', position);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(businessObject, [
|
||||
'bpmn:FlowNode',
|
||||
'bpmn:InteractionNode',
|
||||
'bpmn:DataObjectReference',
|
||||
'bpmn:DataStoreReference',
|
||||
])
|
||||
) {
|
||||
assign(actions, {
|
||||
'append.text-annotation': appendAction(
|
||||
'bpmn:TextAnnotation',
|
||||
'bpmn-icon-text-annotation',
|
||||
),
|
||||
|
||||
connect: {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate(
|
||||
`Connect using ${
|
||||
businessObject.isForCompensation ? '' : 'Sequence/MessageFlow or '
|
||||
}Association`,
|
||||
),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(businessObject, [
|
||||
'bpmn:DataObjectReference',
|
||||
'bpmn:DataStoreReference',
|
||||
])
|
||||
) {
|
||||
assign(actions, {
|
||||
connect: {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Connect using DataInputAssociation'),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:Group')) {
|
||||
assign(actions, {
|
||||
'append.text-annotation': appendAction(
|
||||
'bpmn:TextAnnotation',
|
||||
'bpmn-icon-text-annotation',
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// delete element entry, only show if allowed by rules
|
||||
let deleteAllowed = rules.allowed('elements.delete', { elements: [element] });
|
||||
|
||||
if (isArray(deleteAllowed)) {
|
||||
// was the element returned as a deletion candidate?
|
||||
deleteAllowed = deleteAllowed[0] === element;
|
||||
}
|
||||
|
||||
if (deleteAllowed) {
|
||||
assign(actions, {
|
||||
delete: {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-trash',
|
||||
title: translate('Remove'),
|
||||
action: {
|
||||
click: removeElement,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
// helpers /////////
|
||||
|
||||
function isEventType(eventBo, type, definition) {
|
||||
const isType = eventBo.$instanceOf(type);
|
||||
let isDefinition = false;
|
||||
|
||||
const definitions = eventBo.eventDefinitions || [];
|
||||
forEach(definitions, (def) => {
|
||||
if (def.$type === definition) {
|
||||
isDefinition = true;
|
||||
}
|
||||
});
|
||||
|
||||
return isType && isDefinition;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import CustomContextPadProvider from './contentPadProvider';
|
||||
|
||||
export default {
|
||||
__init__: ['contextPadProvider'],
|
||||
contextPadProvider: ['type', CustomContextPadProvider],
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
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"
|
||||
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>`;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
import { some } from 'min-dash';
|
||||
|
||||
// const some = require('min-dash').some
|
||||
// const some = some
|
||||
|
||||
const ALLOWED_TYPES = {
|
||||
FailedJobRetryTimeCycle: [
|
||||
'bpmn:StartEvent',
|
||||
'bpmn:BoundaryEvent',
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn:Activity',
|
||||
],
|
||||
Connector: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
|
||||
Field: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
|
||||
};
|
||||
|
||||
function is(element, type) {
|
||||
return (
|
||||
element &&
|
||||
typeof element.$instanceOf === 'function' &&
|
||||
element.$instanceOf(type)
|
||||
);
|
||||
}
|
||||
|
||||
function exists(element) {
|
||||
return element && element.length;
|
||||
}
|
||||
|
||||
function includesType(collection, type) {
|
||||
return (
|
||||
exists(collection) &&
|
||||
some(collection, (element) => {
|
||||
return is(element, type);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function anyType(element, types) {
|
||||
return some(types, (type) => {
|
||||
return is(element, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isAllowed(propName, propDescriptor, newElement) {
|
||||
const name = propDescriptor.name;
|
||||
const types = ALLOWED_TYPES[name.replace(/activiti:/, '')];
|
||||
|
||||
return name === propName && anyType(newElement, types);
|
||||
}
|
||||
|
||||
function ActivitiModdleExtension(eventBus) {
|
||||
eventBus.on(
|
||||
'property.clone',
|
||||
function (context) {
|
||||
const newElement = context.newElement;
|
||||
const propDescriptor = context.propertyDescriptor;
|
||||
|
||||
this.canCloneProperty(newElement, propDescriptor);
|
||||
},
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
ActivitiModdleExtension.$inject = ['eventBus'];
|
||||
|
||||
ActivitiModdleExtension.prototype.canCloneProperty = function (
|
||||
newElement,
|
||||
propDescriptor,
|
||||
) {
|
||||
if (
|
||||
isAllowed('activiti:FailedJobRetryTimeCycle', propDescriptor, newElement)
|
||||
) {
|
||||
return (
|
||||
includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') ||
|
||||
includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') ||
|
||||
is(
|
||||
newElement.loopCharacteristics,
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed('activiti:Connector', propDescriptor, newElement)) {
|
||||
return includesType(
|
||||
newElement.eventDefinitions,
|
||||
'bpmn:MessageEventDefinition',
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed('activiti:Field', propDescriptor, newElement)) {
|
||||
return includesType(
|
||||
newElement.eventDefinitions,
|
||||
'bpmn:MessageEventDefinition',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// module.exports = ActivitiModdleExtension;
|
||||
export default ActivitiModdleExtension;
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* @author igdianov
|
||||
* address https://github.com/igdianov/activiti-bpmn-moddle
|
||||
* */
|
||||
|
||||
import activitiExtension from './activitiExtension';
|
||||
|
||||
export default {
|
||||
__init__: ['ActivitiModdleExtension'],
|
||||
ActivitiModdleExtension: ['type', activitiExtension],
|
||||
};
|
||||
@@ -0,0 +1,165 @@
|
||||
'use strict';
|
||||
|
||||
import { isFunction, isObject, some } from 'min-dash';
|
||||
|
||||
// const isFunction = isFunction,
|
||||
// isObject = isObject,
|
||||
// some = some
|
||||
// const isFunction = require('min-dash').isFunction,
|
||||
// isObject = require('min-dash').isObject,
|
||||
// some = require('min-dash').some
|
||||
|
||||
const WILDCARD = '*';
|
||||
|
||||
function CamundaModdleExtension(eventBus) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
|
||||
eventBus.on('moddleCopy.canCopyProperty', (context) => {
|
||||
const parent = context.parent;
|
||||
const property = context.property;
|
||||
|
||||
return self.canCopyProperty(property, parent);
|
||||
});
|
||||
}
|
||||
|
||||
CamundaModdleExtension.$inject = ['eventBus'];
|
||||
|
||||
/**
|
||||
* Check wether to disallow copying property.
|
||||
*/
|
||||
CamundaModdleExtension.prototype.canCopyProperty = function (property, parent) {
|
||||
// (1) check wether property is allowed in parent
|
||||
if (isObject(property) && !isAllowedInParent(property, parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// (2) check more complex scenarios
|
||||
|
||||
if (is(property, 'camunda:InputOutput') && !this.canHostInputOutput(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(property, ['camunda:Connector', 'camunda:Field']) &&
|
||||
!this.canHostConnector(parent)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is(property, 'camunda:In') && !this.canHostIn(parent)) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostInputOutput = function (parent) {
|
||||
// allowed in camunda:Connector
|
||||
const connector = getParent(parent, 'camunda:Connector');
|
||||
|
||||
if (connector) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// special rules inside bpmn:FlowNode
|
||||
const flowNode = getParent(parent, 'bpmn:FlowNode');
|
||||
|
||||
if (!flowNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isAny(flowNode, ['bpmn:StartEvent', 'bpmn:Gateway', 'bpmn:BoundaryEvent'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !(is(flowNode, 'bpmn:SubProcess') && flowNode.get('triggeredByEvent'));
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostConnector = function (parent) {
|
||||
const serviceTaskLike = getParent(parent, 'camunda:ServiceTaskLike');
|
||||
|
||||
if (is(serviceTaskLike, 'bpmn:MessageEventDefinition')) {
|
||||
// only allow on throw and end events
|
||||
return (
|
||||
getParent(parent, 'bpmn:IntermediateThrowEvent') ||
|
||||
getParent(parent, 'bpmn:EndEvent')
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
CamundaModdleExtension.prototype.canHostIn = function (parent) {
|
||||
const callActivity = getParent(parent, 'bpmn:CallActivity');
|
||||
|
||||
if (callActivity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const signalEventDefinition = getParent(parent, 'bpmn:SignalEventDefinition');
|
||||
|
||||
if (signalEventDefinition) {
|
||||
// only allow on throw and end events
|
||||
return (
|
||||
getParent(parent, 'bpmn:IntermediateThrowEvent') ||
|
||||
getParent(parent, 'bpmn:EndEvent')
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// module.exports = CamundaModdleExtension;
|
||||
export default CamundaModdleExtension;
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function is(element, type) {
|
||||
return (
|
||||
element && isFunction(element.$instanceOf) && element.$instanceOf(type)
|
||||
);
|
||||
}
|
||||
|
||||
function isAny(element, types) {
|
||||
return some(types, (t) => {
|
||||
return is(element, t);
|
||||
});
|
||||
}
|
||||
|
||||
function getParent(element, type) {
|
||||
if (!type) {
|
||||
return element.$parent;
|
||||
}
|
||||
|
||||
if (is(element, type)) {
|
||||
return element;
|
||||
}
|
||||
|
||||
if (!element.$parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
return getParent(element.$parent, type);
|
||||
}
|
||||
|
||||
function isAllowedInParent(property, parent) {
|
||||
// (1) find property descriptor
|
||||
const descriptor =
|
||||
property.$type && property.$model.getTypeDescriptor(property.$type);
|
||||
|
||||
const allowedIn = descriptor && descriptor.meta && descriptor.meta.allowedIn;
|
||||
|
||||
if (!allowedIn || isWildcard(allowedIn)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// (2) check wether property has parent of allowed type
|
||||
return some(allowedIn, (type) => {
|
||||
return getParent(parent, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isWildcard(allowedIn) {
|
||||
return allowedIn.includes(WILDCARD);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
import extension from './extension';
|
||||
|
||||
export default {
|
||||
__init__: ['camundaModdleExtension'],
|
||||
camundaModdleExtension: ['type', extension],
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
import { some } from 'min-dash';
|
||||
|
||||
// const some = some
|
||||
// const some = require('min-dash').some
|
||||
|
||||
const ALLOWED_TYPES = {
|
||||
FailedJobRetryTimeCycle: [
|
||||
'bpmn:StartEvent',
|
||||
'bpmn:BoundaryEvent',
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn:Activity',
|
||||
],
|
||||
Connector: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
|
||||
Field: ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent'],
|
||||
};
|
||||
|
||||
function is(element, type) {
|
||||
return (
|
||||
element &&
|
||||
typeof element.$instanceOf === 'function' &&
|
||||
element.$instanceOf(type)
|
||||
);
|
||||
}
|
||||
|
||||
function exists(element) {
|
||||
return element && element.length;
|
||||
}
|
||||
|
||||
function includesType(collection, type) {
|
||||
return (
|
||||
exists(collection) &&
|
||||
some(collection, (element) => {
|
||||
return is(element, type);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function anyType(element, types) {
|
||||
return some(types, (type) => {
|
||||
return is(element, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isAllowed(propName, propDescriptor, newElement) {
|
||||
const name = propDescriptor.name;
|
||||
const types = ALLOWED_TYPES[name.replace(/flowable:/, '')];
|
||||
|
||||
return name === propName && anyType(newElement, types);
|
||||
}
|
||||
|
||||
function FlowableModdleExtension(eventBus) {
|
||||
eventBus.on(
|
||||
'property.clone',
|
||||
function (context) {
|
||||
const newElement = context.newElement;
|
||||
const propDescriptor = context.propertyDescriptor;
|
||||
|
||||
this.canCloneProperty(newElement, propDescriptor);
|
||||
},
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
FlowableModdleExtension.$inject = ['eventBus'];
|
||||
|
||||
FlowableModdleExtension.prototype.canCloneProperty = function (
|
||||
newElement,
|
||||
propDescriptor,
|
||||
) {
|
||||
if (
|
||||
isAllowed('flowable:FailedJobRetryTimeCycle', propDescriptor, newElement)
|
||||
) {
|
||||
return (
|
||||
includesType(newElement.eventDefinitions, 'bpmn:TimerEventDefinition') ||
|
||||
includesType(newElement.eventDefinitions, 'bpmn:SignalEventDefinition') ||
|
||||
is(
|
||||
newElement.loopCharacteristics,
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed('flowable:Connector', propDescriptor, newElement)) {
|
||||
return includesType(
|
||||
newElement.eventDefinitions,
|
||||
'bpmn:MessageEventDefinition',
|
||||
);
|
||||
}
|
||||
|
||||
if (isAllowed('flowable:Field', propDescriptor, newElement)) {
|
||||
return includesType(
|
||||
newElement.eventDefinitions,
|
||||
'bpmn:MessageEventDefinition',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// module.exports = FlowableModdleExtension;
|
||||
export default FlowableModdleExtension;
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* @author igdianov
|
||||
* address https://github.com/igdianov/activiti-bpmn-moddle
|
||||
* */
|
||||
import flowableExtension from './flowableExtension';
|
||||
|
||||
export default {
|
||||
__init__: ['FlowableModdleExtension'],
|
||||
FlowableModdleExtension: ['type', flowableExtension],
|
||||
};
|
||||
@@ -0,0 +1,233 @@
|
||||
import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider';
|
||||
import { assign } from 'min-dash';
|
||||
|
||||
export default function CustomPalette(
|
||||
palette,
|
||||
create,
|
||||
elementFactory,
|
||||
spaceTool,
|
||||
lassoTool,
|
||||
handTool,
|
||||
globalConnect,
|
||||
translate,
|
||||
) {
|
||||
PaletteProvider.call(
|
||||
this,
|
||||
palette,
|
||||
create,
|
||||
elementFactory,
|
||||
spaceTool,
|
||||
lassoTool,
|
||||
handTool,
|
||||
globalConnect,
|
||||
translate,
|
||||
2000,
|
||||
);
|
||||
}
|
||||
|
||||
const F = function () {}; // 核心,利用空对象作为中介;
|
||||
F.prototype = PaletteProvider.prototype; // 核心,将父类的原型赋值给空对象F;
|
||||
|
||||
// 利用中介函数重写原型链方法
|
||||
F.prototype.getPaletteEntries = function () {
|
||||
const actions = {};
|
||||
const create = this._create;
|
||||
const elementFactory = this._elementFactory;
|
||||
const spaceTool = this._spaceTool;
|
||||
const lassoTool = this._lassoTool;
|
||||
const handTool = this._handTool;
|
||||
const globalConnect = this._globalConnect;
|
||||
const translate = this._translate;
|
||||
|
||||
function createAction(type, group, className, title, options) {
|
||||
function createListener(event) {
|
||||
const shape = elementFactory.createShape(assign({ type }, options));
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
|
||||
create.start(event, shape);
|
||||
}
|
||||
|
||||
const shortType = type.replace(/^bpmn:/, '');
|
||||
|
||||
return {
|
||||
group,
|
||||
className,
|
||||
title: title || translate('Create {type}', { type: shortType }),
|
||||
action: {
|
||||
dragstart: createListener,
|
||||
click: createListener,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createSubprocess(event) {
|
||||
const subProcess = elementFactory.createShape({
|
||||
type: 'bpmn:SubProcess',
|
||||
x: 0,
|
||||
y: 0,
|
||||
isExpanded: true,
|
||||
});
|
||||
|
||||
const startEvent = elementFactory.createShape({
|
||||
type: 'bpmn:StartEvent',
|
||||
x: 40,
|
||||
y: 82,
|
||||
parent: subProcess,
|
||||
});
|
||||
|
||||
create.start(event, [subProcess, startEvent], {
|
||||
hints: {
|
||||
autoSelect: [startEvent],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createParticipant(event) {
|
||||
create.start(event, elementFactory.createParticipantShape());
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'hand-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-hand-tool',
|
||||
title: '激活抓手工具',
|
||||
// title: translate("Activate the hand tool"),
|
||||
action: {
|
||||
click(event) {
|
||||
handTool.activateHand(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'lasso-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-lasso-tool',
|
||||
title: translate('Activate the lasso tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
lassoTool.activateSelection(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'space-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-space-tool',
|
||||
title: translate('Activate the create/remove space tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
spaceTool.activateSelection(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'global-connect-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Activate the global connect tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
globalConnect.toggle(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'tool-separator': {
|
||||
group: 'tools',
|
||||
separator: true,
|
||||
},
|
||||
'create.start-event': createAction(
|
||||
'bpmn:StartEvent',
|
||||
'event',
|
||||
'bpmn-icon-start-event-none',
|
||||
translate('Create StartEvent'),
|
||||
),
|
||||
'create.intermediate-event': createAction(
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'event',
|
||||
'bpmn-icon-intermediate-event-none',
|
||||
translate('Create Intermediate/Boundary Event'),
|
||||
),
|
||||
'create.end-event': createAction(
|
||||
'bpmn:EndEvent',
|
||||
'event',
|
||||
'bpmn-icon-end-event-none',
|
||||
translate('Create EndEvent'),
|
||||
),
|
||||
'create.exclusive-gateway': createAction(
|
||||
'bpmn:ExclusiveGateway',
|
||||
'gateway',
|
||||
'bpmn-icon-gateway-none',
|
||||
translate('Create Gateway'),
|
||||
),
|
||||
'create.user-task': createAction(
|
||||
'bpmn:UserTask',
|
||||
'activity',
|
||||
'bpmn-icon-user-task',
|
||||
translate('Create User Task'),
|
||||
),
|
||||
'create.call-activity': createAction(
|
||||
'bpmn:CallActivity',
|
||||
'activity',
|
||||
'bpmn-icon-call-activity',
|
||||
translate('Create Call Activity'),
|
||||
),
|
||||
'create.service-task': createAction(
|
||||
'bpmn:ServiceTask',
|
||||
'activity',
|
||||
'bpmn-icon-service',
|
||||
translate('Create Service Task'),
|
||||
),
|
||||
'create.data-object': createAction(
|
||||
'bpmn:DataObjectReference',
|
||||
'data-object',
|
||||
'bpmn-icon-data-object',
|
||||
translate('Create DataObjectReference'),
|
||||
),
|
||||
'create.data-store': createAction(
|
||||
'bpmn:DataStoreReference',
|
||||
'data-store',
|
||||
'bpmn-icon-data-store',
|
||||
translate('Create DataStoreReference'),
|
||||
),
|
||||
'create.subprocess-expanded': {
|
||||
group: 'activity',
|
||||
className: 'bpmn-icon-subprocess-expanded',
|
||||
title: translate('Create expanded SubProcess'),
|
||||
action: {
|
||||
dragstart: createSubprocess,
|
||||
click: createSubprocess,
|
||||
},
|
||||
},
|
||||
'create.participant-expanded': {
|
||||
group: 'collaboration',
|
||||
className: 'bpmn-icon-participant',
|
||||
title: translate('Create Pool/Participant'),
|
||||
action: {
|
||||
dragstart: createParticipant,
|
||||
click: createParticipant,
|
||||
},
|
||||
},
|
||||
'create.group': createAction(
|
||||
'bpmn:Group',
|
||||
'artifact',
|
||||
'bpmn-icon-group',
|
||||
translate('Create Group'),
|
||||
),
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
CustomPalette.$inject = [
|
||||
'palette',
|
||||
'create',
|
||||
'elementFactory',
|
||||
'spaceTool',
|
||||
'lassoTool',
|
||||
'handTool',
|
||||
'globalConnect',
|
||||
'translate',
|
||||
];
|
||||
|
||||
CustomPalette.prototype = new F(); // 核心,将 F的实例赋值给子类;
|
||||
CustomPalette.prototype.constructor = CustomPalette; // 修复子类CustomPalette的构造器指向,防止原型链的混乱;
|
||||
@@ -0,0 +1,22 @@
|
||||
// import PaletteModule from "diagram-js/lib/features/palette";
|
||||
// import CreateModule from "diagram-js/lib/features/create";
|
||||
// import SpaceToolModule from "diagram-js/lib/features/space-tool";
|
||||
// import LassoToolModule from "diagram-js/lib/features/lasso-tool";
|
||||
// import HandToolModule from "diagram-js/lib/features/hand-tool";
|
||||
// import GlobalConnectModule from "diagram-js/lib/features/global-connect";
|
||||
// import translate from "diagram-js/lib/i18n/translate";
|
||||
//
|
||||
// import PaletteProvider from "./paletteProvider";
|
||||
//
|
||||
// export default {
|
||||
// __depends__: [PaletteModule, CreateModule, SpaceToolModule, LassoToolModule, HandToolModule, GlobalConnectModule, translate],
|
||||
// __init__: ["paletteProvider"],
|
||||
// paletteProvider: ["type", PaletteProvider]
|
||||
// };
|
||||
|
||||
import CustomPalette from './CustomPalette';
|
||||
|
||||
export default {
|
||||
__init__: ['paletteProvider'],
|
||||
paletteProvider: ['type', CustomPalette],
|
||||
};
|
||||
@@ -0,0 +1,219 @@
|
||||
import { assign } from 'min-dash';
|
||||
|
||||
/**
|
||||
* A palette provider for BPMN 2.0 elements.
|
||||
*/
|
||||
export default function PaletteProvider(
|
||||
palette,
|
||||
create,
|
||||
elementFactory,
|
||||
spaceTool,
|
||||
lassoTool,
|
||||
handTool,
|
||||
globalConnect,
|
||||
translate,
|
||||
) {
|
||||
this._palette = palette;
|
||||
this._create = create;
|
||||
this._elementFactory = elementFactory;
|
||||
this._spaceTool = spaceTool;
|
||||
this._lassoTool = lassoTool;
|
||||
this._handTool = handTool;
|
||||
this._globalConnect = globalConnect;
|
||||
this._translate = translate;
|
||||
|
||||
palette.registerProvider(this);
|
||||
}
|
||||
|
||||
PaletteProvider.$inject = [
|
||||
'palette',
|
||||
'create',
|
||||
'elementFactory',
|
||||
'spaceTool',
|
||||
'lassoTool',
|
||||
'handTool',
|
||||
'globalConnect',
|
||||
'translate',
|
||||
];
|
||||
|
||||
PaletteProvider.prototype.getPaletteEntries = function () {
|
||||
const actions = {};
|
||||
const create = this._create;
|
||||
const elementFactory = this._elementFactory;
|
||||
const spaceTool = this._spaceTool;
|
||||
const lassoTool = this._lassoTool;
|
||||
const handTool = this._handTool;
|
||||
const globalConnect = this._globalConnect;
|
||||
const translate = this._translate;
|
||||
|
||||
function createAction(type, group, className, title, options) {
|
||||
function createListener(event) {
|
||||
const shape = elementFactory.createShape(assign({ type }, options));
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
|
||||
create.start(event, shape);
|
||||
}
|
||||
|
||||
const shortType = type.replace(/^bpmn:/, '');
|
||||
|
||||
return {
|
||||
group,
|
||||
className,
|
||||
title: title || translate('Create {type}', { type: shortType }),
|
||||
action: {
|
||||
dragstart: createListener,
|
||||
click: createListener,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createSubprocess(event) {
|
||||
const subProcess = elementFactory.createShape({
|
||||
type: 'bpmn:SubProcess',
|
||||
x: 0,
|
||||
y: 0,
|
||||
isExpanded: true,
|
||||
});
|
||||
|
||||
const startEvent = elementFactory.createShape({
|
||||
type: 'bpmn:StartEvent',
|
||||
x: 40,
|
||||
y: 82,
|
||||
parent: subProcess,
|
||||
});
|
||||
|
||||
create.start(event, [subProcess, startEvent], {
|
||||
hints: {
|
||||
autoSelect: [startEvent],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createParticipant(event) {
|
||||
create.start(event, elementFactory.createParticipantShape());
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'hand-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-hand-tool',
|
||||
title: translate('Activate the hand tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
handTool.activateHand(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'lasso-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-lasso-tool',
|
||||
title: translate('Activate the lasso tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
lassoTool.activateSelection(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'space-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-space-tool',
|
||||
title: translate('Activate the create/remove space tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
spaceTool.activateSelection(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'global-connect-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Activate the global connect tool'),
|
||||
action: {
|
||||
click(event) {
|
||||
globalConnect.toggle(event);
|
||||
},
|
||||
},
|
||||
},
|
||||
'tool-separator': {
|
||||
group: 'tools',
|
||||
separator: true,
|
||||
},
|
||||
'create.start-event': createAction(
|
||||
'bpmn:StartEvent',
|
||||
'event',
|
||||
'bpmn-icon-start-event-none',
|
||||
translate('Create StartEvent'),
|
||||
),
|
||||
'create.intermediate-event': createAction(
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'event',
|
||||
'bpmn-icon-intermediate-event-none',
|
||||
translate('Create Intermediate/Boundary Event'),
|
||||
),
|
||||
'create.end-event': createAction(
|
||||
'bpmn:EndEvent',
|
||||
'event',
|
||||
'bpmn-icon-end-event-none',
|
||||
translate('Create EndEvent'),
|
||||
),
|
||||
'create.exclusive-gateway': createAction(
|
||||
'bpmn:ExclusiveGateway',
|
||||
'gateway',
|
||||
'bpmn-icon-gateway-none',
|
||||
translate('Create Gateway'),
|
||||
),
|
||||
'create.user-task': createAction(
|
||||
'bpmn:UserTask',
|
||||
'activity',
|
||||
'bpmn-icon-user-task',
|
||||
translate('Create User Task'),
|
||||
),
|
||||
'create.service-task': createAction(
|
||||
'bpmn:ServiceTask',
|
||||
'activity',
|
||||
'bpmn-icon-service',
|
||||
translate('Create Service Task'),
|
||||
),
|
||||
'create.data-object': createAction(
|
||||
'bpmn:DataObjectReference',
|
||||
'data-object',
|
||||
'bpmn-icon-data-object',
|
||||
translate('Create DataObjectReference'),
|
||||
),
|
||||
'create.data-store': createAction(
|
||||
'bpmn:DataStoreReference',
|
||||
'data-store',
|
||||
'bpmn-icon-data-store',
|
||||
translate('Create DataStoreReference'),
|
||||
),
|
||||
'create.subprocess-expanded': {
|
||||
group: 'activity',
|
||||
className: 'bpmn-icon-subprocess-expanded',
|
||||
title: translate('Create expanded SubProcess'),
|
||||
action: {
|
||||
dragstart: createSubprocess,
|
||||
click: createSubprocess,
|
||||
},
|
||||
},
|
||||
'create.participant-expanded': {
|
||||
group: 'collaboration',
|
||||
className: 'bpmn-icon-participant',
|
||||
title: translate('Create Pool/Participant'),
|
||||
action: {
|
||||
dragstart: createParticipant,
|
||||
click: createParticipant,
|
||||
},
|
||||
},
|
||||
'create.group': createAction(
|
||||
'bpmn:Group',
|
||||
'artifact',
|
||||
'bpmn-icon-group',
|
||||
translate('Create Group'),
|
||||
),
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// import translations from "./zh";
|
||||
//
|
||||
// export default 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 + "}";
|
||||
// });
|
||||
// }
|
||||
|
||||
export default function customTranslate(translations) {
|
||||
return function (template, replacements) {
|
||||
replacements = replacements || {};
|
||||
// 将模板和翻译字典的键统一转换为小写进行匹配
|
||||
const lowerTemplate = template.toLowerCase();
|
||||
const translation = Object.keys(translations).find(
|
||||
(key) => key.toLowerCase() === lowerTemplate,
|
||||
);
|
||||
|
||||
// 如果找到匹配的翻译,使用翻译后的模板
|
||||
if (translation) {
|
||||
template = translations[translation];
|
||||
}
|
||||
|
||||
// 替换模板中的占位符
|
||||
return template.replaceAll(/\{([^}]+)\}/g, (_, key) => {
|
||||
// 如果替换值存在,返回替换值;否则返回原始占位符
|
||||
return replacements[key] === undefined ? `{${key}}` : replacements[key];
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* This is a sample file that should be replaced with the actual translation.
|
||||
*
|
||||
* Checkout https://github.com/bpmn-io/bpmn-js-i18n for a list of available
|
||||
* translations and labels to translate.
|
||||
*/
|
||||
export default {
|
||||
// 添加部分
|
||||
'Append EndEvent': '追加结束事件',
|
||||
'Append Gateway': '追加网关',
|
||||
'Append Task': '追加任务',
|
||||
'Append Intermediate/Boundary Event': '追加中间抛出事件/边界事件',
|
||||
|
||||
'Activate the global connect tool': '激活全局连接工具',
|
||||
'Append {type}': '添加 {type}',
|
||||
'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}',
|
||||
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 StartEvent': '创建开始事件',
|
||||
'Create EndEvent': '创建结束事件',
|
||||
'Create Task': '创建任务',
|
||||
'Create User Task': '创建用户任务',
|
||||
'Create Call Activity': '创建调用活动',
|
||||
'Create Service Task': '创建服务任务',
|
||||
'Create Gateway': '创建网关',
|
||||
'Create DataObjectReference': '创建数据对象',
|
||||
'Create DataStoreReference': '创建数据存储',
|
||||
'Create Group': '创建分组',
|
||||
'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': '默认值',
|
||||
'Default Flow': '默认流转路径',
|
||||
'Conditional Flow': '条件流转路径',
|
||||
'Sequence Flow': '普通流转路径',
|
||||
Validation: '校验',
|
||||
'Add Constraint': '添加约束',
|
||||
Config: '配置',
|
||||
Properties: '属性',
|
||||
'Add Property': '添加属性',
|
||||
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': '候选人起动器配置',
|
||||
'Candidate Starter Groups': '候选人起动器组',
|
||||
'This maps to the process definition key.': '这映射到流程定义键。',
|
||||
'Candidate Starter Users': '候选人起动器的用户',
|
||||
'Specify more than one user as a comma separated list.':
|
||||
'指定多个用户作为逗号分隔的列表。',
|
||||
'Tasklist Configuration': 'Tasklist配置',
|
||||
Startable: '启动',
|
||||
'Specify more than one group as a comma separated list.':
|
||||
'指定多个组作为逗号分隔的列表。',
|
||||
};
|
||||
Reference in New Issue
Block a user