form generator

This commit is contained in:
cxc
2022-12-16 17:26:54 +08:00
parent d9451837df
commit 75538be028
180 changed files with 19443 additions and 160 deletions

View File

@ -0,0 +1,465 @@
<template>
<div class="my-process-designer">
<div class="my-process-designer__header">
<slot name="control-header"></slot>
<template v-if="!$slots['control-header']">
<el-button-group key="file-control">
<el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-edit-outline" @click="onSave">保存流程</el-button>
<el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-folder-opened" @click="$refs.refFile.click()">打开文件</el-button>
<el-tooltip effect="light">
<div slot="content">
<el-button :size="headerButtonSize" type="text" @click="downloadProcessAsXml()">下载为XML文件</el-button>
<br />
<el-button :size="headerButtonSize" type="text" @click="downloadProcessAsSvg()">下载为SVG文件</el-button>
<br />
<el-button :size="headerButtonSize" type="text" @click="downloadProcessAsBpmn()">下载为BPMN文件</el-button>
</div>
<el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-download">下载文件</el-button>
</el-tooltip>
<el-tooltip effect="light">
<div slot="content">
<el-button :size="headerButtonSize" type="text" @click="previewProcessXML">预览XML</el-button>
<br />
<el-button :size="headerButtonSize" type="text" @click="previewProcessJson">预览JSON</el-button>
</div>
<el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-view">预览</el-button>
</el-tooltip>
<el-tooltip v-if="simulation" effect="light" :content="this.simulationStatus ? '退出模拟' : '开启模拟'">
<el-button :size="headerButtonSize" :type="headerButtonType" icon="el-icon-cpu" @click="processSimulation">
模拟
</el-button>
</el-tooltip>
</el-button-group>
<el-button-group key="align-control">
<el-tooltip effect="light" content="向左对齐">
<el-button :size="headerButtonSize" class="align align-left" icon="el-icon-s-data" @click="elementsAlign('left')" />
</el-tooltip>
<el-tooltip effect="light" content="向右对齐">
<el-button :size="headerButtonSize" class="align align-right" icon="el-icon-s-data" @click="elementsAlign('right')" />
</el-tooltip>
<el-tooltip effect="light" content="向上对齐">
<el-button :size="headerButtonSize" class="align align-top" icon="el-icon-s-data" @click="elementsAlign('top')" />
</el-tooltip>
<el-tooltip effect="light" content="向下对齐">
<el-button :size="headerButtonSize" class="align align-bottom" icon="el-icon-s-data" @click="elementsAlign('bottom')" />
</el-tooltip>
<el-tooltip effect="light" content="水平居中">
<el-button :size="headerButtonSize" class="align align-center" icon="el-icon-s-data" @click="elementsAlign('center')" />
</el-tooltip>
<el-tooltip effect="light" content="垂直居中">
<el-button :size="headerButtonSize" class="align align-middle" icon="el-icon-s-data" @click="elementsAlign('middle')" />
</el-tooltip>
</el-button-group>
<el-button-group key="scale-control">
<el-tooltip effect="light" content="缩小视图">
<el-button :size="headerButtonSize" :disabled="defaultZoom <= 0.3" icon="el-icon-zoom-out" @click="processZoomOut()" />
</el-tooltip>
<el-button :size="headerButtonSize">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button>
<el-tooltip effect="light" content="放大视图">
<el-button :size="headerButtonSize" :disabled="defaultZoom >= 3.9" icon="el-icon-zoom-in" @click="processZoomIn()" />
</el-tooltip>
<el-tooltip effect="light" content="重置视图并居中">
<el-button :size="headerButtonSize" icon="el-icon-c-scale-to-original" @click="processReZoom()" />
</el-tooltip>
</el-button-group>
<el-button-group key="stack-control">
<el-tooltip effect="light" content="撤销">
<el-button :size="headerButtonSize" :disabled="!revocable" icon="el-icon-refresh-left" @click="processUndo()" />
</el-tooltip>
<el-tooltip effect="light" content="恢复">
<el-button :size="headerButtonSize" :disabled="!recoverable" icon="el-icon-refresh-right" @click="processRedo()" />
</el-tooltip>
<el-tooltip effect="light" content="重新绘制">
<el-button :size="headerButtonSize" icon="el-icon-refresh" @click="processRestart" />
</el-tooltip>
</el-button-group>
</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="bpmn-canvas"></div>
</div>
<el-dialog title="预览" width="60%" :visible.sync="previewModelVisible" append-to-body destroy-on-close>
<highlightjs :language="previewType" :code="previewResult" style="height: 60vh" />
</el-dialog>
</div>
</template>
<script>
// 生产环境时优化
// const BpmnModeler = window.BpmnJS;
import BpmnModeler from "bpmn-js/lib/Modeler";
import DefaultEmptyXML from "./plugins/defaultEmpty";
// 翻译方法
import customTranslate from "./plugins/translate/customTranslate";
import translationsCN from "./plugins/translate/zh";
// 模拟流转流程
import tokenSimulation from "bpmn-js-token-simulation";
// 标签解析构建器
// import bpmnPropertiesProvider from "bpmn-js-properties-panel/lib/provider/bpmn";
// 标签解析 Moddle
import camundaModdleDescriptor from "./plugins/descriptor/camundaDescriptor.json";
import activitiModdleDescriptor from "./plugins/descriptor/activitiDescriptor.json";
import flowableModdleDescriptor from "./plugins/descriptor/flowableDescriptor.json";
// 标签解析 Extension
import camundaModdleExtension from "./plugins/extension-moddle/camunda";
import activitiModdleExtension from "./plugins/extension-moddle/activiti";
import flowableModdleExtension from "./plugins/extension-moddle/flowable";
// 引入json转换与高亮
import convert from "xml-js";
export default {
name: "BpmnProcessDesigner",
componentName: "BpmnProcessDesigner",
props: {
value: String, // xml 字符串
processId: String,
processName: String,
translations: Object, // 自定义的翻译文件
additionalModel: [Object, Array], // 自定义model
moddleExtension: Object, // 自定义moddle
onlyCustomizeAddi: {
type: Boolean,
default: false
},
onlyCustomizeModdle: {
type: Boolean,
default: false
},
simulation: {
type: Boolean,
default: true
},
keyboard: {
type: Boolean,
default: true
},
prefix: {
type: String,
default: "flowable"
},
events: {
type: Array,
default: () => ["element.click"]
},
headerButtonSize: {
type: String,
default: "small",
validator: value => ["default", "medium", "small", "mini"].indexOf(value) !== -1
},
headerButtonType: {
type: String,
default: "primary",
validator: value => ["default", "primary", "success", "warning", "danger", "info"].indexOf(value) !== -1
}
},
data() {
return {
defaultZoom: 1,
previewModelVisible: false,
simulationStatus: false,
previewResult: "",
previewType: "xml",
recoverable: false,
revocable: false
};
},
computed: {
additionalModules() {
const Modules = [];
// 仅保留用户自定义扩展模块
if (this.onlyCustomizeAddi) {
if (Object.prototype.toString.call(this.additionalModel) === "[object Array]") {
return this.additionalModel || [];
}
return [this.additionalModel];
}
// 插入用户自定义扩展模块
if (Object.prototype.toString.call(this.additionalModel) === "[object Array]") {
Modules.push(...this.additionalModel);
} else {
this.additionalModel && Modules.push(this.additionalModel);
}
// 翻译模块
const TranslateModule = {
translate: ["value", customTranslate(this.translations || translationsCN)]
};
Modules.push(TranslateModule);
// 模拟流转模块
if (this.simulation) {
Modules.push(tokenSimulation);
}
// 根据需要的流程类型设置扩展元素构建模块
// if (this.prefix === "bpmn") {
// Modules.push(bpmnModdleExtension);
// }
if (this.prefix === "camunda") {
Modules.push(camundaModdleExtension);
}
if (this.prefix === "flowable") {
Modules.push(flowableModdleExtension);
}
if (this.prefix === "activiti") {
Modules.push(activitiModdleExtension);
}
return Modules;
},
moddleExtensions() {
const Extensions = {};
// 仅使用用户自定义模块
if (this.onlyCustomizeModdle) {
return this.moddleExtension || null;
}
// 插入用户自定义模块
if (this.moddleExtension) {
for (let key in this.moddleExtension) {
Extensions[key] = this.moddleExtension[key];
}
}
// 根据需要的 "流程类型" 设置 对应的解析文件
if (this.prefix === "activiti") {
Extensions.activiti = activitiModdleDescriptor;
}
if (this.prefix === "flowable") {
Extensions.flowable = flowableModdleDescriptor;
}
if (this.prefix === "camunda") {
Extensions.camunda = camundaModdleDescriptor;
}
return Extensions;
}
},
mounted() {
this.initBpmnModeler();
this.createNewDiagram(this.value);
this.$once("hook:beforeDestroy", () => {
if (this.bpmnModeler) this.bpmnModeler.destroy();
this.$emit("destroy", this.bpmnModeler);
this.bpmnModeler = null;
});
},
methods: {
onSave () {
return new Promise((resolve, reject) => {
if (this.bpmnModeler == null) {
reject();
}
this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
this.$emit('save', xml);
resolve(xml);
});
})
},
initBpmnModeler() {
if (this.bpmnModeler) return;
this.bpmnModeler = new BpmnModeler({
container: this.$refs["bpmn-canvas"],
keyboard: this.keyboard ? { bindTo: document } : null,
additionalModules: this.additionalModules,
moddleExtensions: this.moddleExtensions
});
this.$emit("init-finished", this.bpmnModeler);
this.initModelListeners();
},
initModelListeners() {
const EventBus = this.bpmnModeler.get("eventBus");
const that = this;
// 注册需要的监听事件, 将. 替换为 - , 避免解析异常
this.events.forEach(event => {
EventBus.on(event, function(eventObj) {
let eventName = event.replace(/\./g, "-");
let element = eventObj ? eventObj.element : null;
that.$emit(eventName, element, eventObj);
that.$emit('event', eventName, element, eventObj);
});
});
// 监听图形改变返回xml
EventBus.on("commandStack.changed", async event => {
try {
this.recoverable = this.bpmnModeler.get("commandStack").canRedo();
this.revocable = this.bpmnModeler.get("commandStack").canUndo();
let { xml } = await this.bpmnModeler.saveXML({ format: true });
this.$emit("commandStack-changed", event);
this.$emit("input", xml);
this.$emit("change", xml);
} catch (e) {
console.error(`[Process Designer Warn]: ${e.message || e}`);
}
});
// 监听视图缩放变化
this.bpmnModeler.on("canvas.viewbox.changed", ({ viewbox }) => {
this.$emit("canvas-viewbox-changed", { viewbox });
const { scale } = viewbox;
this.defaultZoom = Math.floor(scale * 100) / 100;
});
},
/* 创建新的流程图 */
async createNewDiagram(xml) {
// 将字符串转换成图显示出来
let newId = this.processId || `Process_${new Date().getTime()}`;
let newName = this.processName || `业务流程_${new Date().getTime()}`;
let xmlString = xml || DefaultEmptyXML(newId, newName, this.prefix);
try {
let { warnings } = await this.bpmnModeler.importXML(xmlString);
if (warnings && warnings.length) {
warnings.forEach(warn => console.warn(warn));
}
} catch (e) {
console.error(`[Process Designer Warn]: ${e.message || e}`);
}
},
// 下载流程图到本地
async downloadProcess(type, name) {
try {
const _this = this;
// 按需要类型创建文件并下载
if (type === "xml" || type === "bpmn") {
const { err, xml } = await this.bpmnModeler.saveXML();
// 读取异常时抛出异常
if (err) {
console.error(`[Process Designer Warn ]: ${err.message || err}`);
}
let { href, filename } = _this.setEncoded(type.toUpperCase(), name, xml);
downloadFunc(href, filename);
} else {
const { err, svg } = await this.bpmnModeler.saveSVG();
// 读取异常时抛出异常
if (err) {
return console.error(err);
}
let { href, filename } = _this.setEncoded("SVG", name, svg);
downloadFunc(href, filename);
}
} catch (e) {
console.error(`[Process Designer Warn ]: ${e.message || e}`);
}
// 文件下载方法
function downloadFunc(href, filename) {
if (href && filename) {
let a = document.createElement("a");
a.download = filename; //指定下载的文件名
a.href = href; // URL对象
a.click(); // 模拟点击
URL.revokeObjectURL(a.href); // 释放URL 对象
}
}
},
// 根据所需类型进行转码并返回下载地址
setEncoded(type, filename = "diagram", data) {
const encodedData = encodeURIComponent(data);
return {
filename: `${filename}.${type}`,
href: `data:application/${type === "svg" ? "text/xml" : "bpmn20-xml"};charset=UTF-8,${encodedData}`,
data: data
};
},
// 加载本地文件
importLocalFile() {
const that = this;
const file = this.$refs.refFile.files[0];
const reader = new FileReader();
reader.readAsText(file);
reader.onload = function() {
let xmlStr = this.result;
that.createNewDiagram(xmlStr);
};
},
/* ------------------------------------------------ refs methods ------------------------------------------------------ */
downloadProcessAsXml() {
this.downloadProcess("xml");
},
downloadProcessAsBpmn() {
this.downloadProcess("bpmn");
},
downloadProcessAsSvg() {
this.downloadProcess("svg");
},
processSimulation() {
this.simulationStatus = !this.simulationStatus;
this.simulation && this.bpmnModeler.get("toggleMode").toggleMode();
},
processRedo() {
this.bpmnModeler.get("commandStack").redo();
},
processUndo() {
this.bpmnModeler.get("commandStack").undo();
},
processZoomIn(zoomStep = 0.1) {
let newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100;
if (newZoom > 4) {
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
}
this.defaultZoom = newZoom;
this.bpmnModeler.get("canvas").zoom(this.defaultZoom);
},
processZoomOut(zoomStep = 0.1) {
let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
if (newZoom < 0.2) {
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
}
this.defaultZoom = newZoom;
this.bpmnModeler.get("canvas").zoom(this.defaultZoom);
},
processZoomTo(newZoom = 1) {
if (newZoom < 0.2) {
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be less than 0.2");
}
if (newZoom > 4) {
throw new Error("[Process Designer Warn ]: The zoom ratio cannot be greater than 4");
}
this.defaultZoom = newZoom;
this.bpmnModeler.get("canvas").zoom(newZoom);
},
processReZoom() {
this.defaultZoom = 1;
this.bpmnModeler.get("canvas").zoom("fit-viewport", "auto");
},
processRestart() {
this.recoverable = false;
this.revocable = false;
this.createNewDiagram(null).then(() => this.bpmnModeler.get("canvas").zoom(1, "auto"));
},
elementsAlign(align) {
const Align = this.bpmnModeler.get("alignElements");
const Selection = this.bpmnModeler.get("selection");
const SelectedElements = Selection.get();
if (!SelectedElements || SelectedElements.length <= 1) {
this.$message.warning("请按住 Ctrl 键选择多个元素对齐");
return;
}
this.$confirm("自动对齐可能造成图形变形,是否继续?", "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => Align.trigger(SelectedElements, align));
},
/*----------------------------- 方法结束 ---------------------------------*/
previewProcessXML() {
this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
this.previewResult = xml;
this.previewType = "xml";
this.previewModelVisible = true;
});
},
previewProcessJson() {
this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
this.previewResult = convert.xml2json(xml, { spaces: 2 });
this.previewType = "json";
this.previewModelVisible = true;
});
}
}
};
</script>

View File

@ -0,0 +1,7 @@
import BpmnProcessDesigner from "./ProcessDesigner.vue";
BpmnProcessDesigner.install = function(Vue) {
Vue.component(BpmnProcessDesigner.name, BpmnProcessDesigner);
};
export default BpmnProcessDesigner;

View File

@ -0,0 +1,390 @@
import { assign, forEach, isArray } from "min-dash";
import { is } from "bpmn-js/lib/util/ModelUtil";
import { isExpanded, isEventSubProcess } from "bpmn-js/lib/util/DiUtil";
import { isAny } from "bpmn-js/lib/features/modeling/util/ModelingUtil";
import { getChildLanes } from "bpmn-js/lib/features/modeling/util/LaneUtil";
import { hasPrimaryModifier } from "diagram-js/lib/util/Mouse";
/**
* 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,
elementRegistry
) {
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, function(event) {
var context = event.context,
shape = context.shape;
if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
return;
}
var 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) {
var contextPad = this._contextPad,
modeling = this._modeling,
elementFactory = this._elementFactory,
connect = this._connect,
create = this._create,
popupMenu = this._popupMenu,
canvas = this._canvas,
rules = this._rules,
autoPlace = this._autoPlace,
translate = this._translate;
var actions = {};
if (element.type === "label") {
return actions;
}
var businessObject = element.businessObject;
function startConnect(event, element) {
connect.start(event, element);
}
function removeElement() {
modeling.removeElements([element]);
}
function getReplaceMenuPosition(element) {
var Y_OFFSET = 5;
var diagramContainer = canvas.getContainer(),
pad = contextPad.getPad(element).html;
var diagramRect = diagramContainer.getBoundingClientRect(),
padRect = pad.getBoundingClientRect();
var top = padRect.top - diagramRect.top;
var left = padRect.left - diagramRect.left;
var 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) {
var shape = elementFactory.createShape(assign({ type: type }, options));
create.start(event, shape, {
source: element
});
}
var append = autoPlace
? function(event, element) {
var shape = elementFactory.createShape(assign({ type: type }, options));
autoPlace.append(element, shape);
}
: appendStart;
return {
group: "model",
className: className,
title: 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)) {
var 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: function(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: function(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: translate("Change type"),
action: {
click: function(event, element) {
var 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
var 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) {
var isType = eventBo.$instanceOf(type);
var isDefinition = false;
var definitions = eventBo.eventDefinitions || [];
forEach(definitions, function(def) {
if (def.$type === definition) {
isDefinition = true;
}
});
return isType && isDefinition;
}

View File

@ -0,0 +1,6 @@
import CustomContextPadProvider from "./contentPadProvider";
export default {
__init__: ["contextPadProvider"],
contextPadProvider: ["type", CustomContextPadProvider]
};

View File

@ -0,0 +1,27 @@
export default (key, name, type) => {
if (!type) type = "camunda";
const TYPE_TARGET = {
activiti: "http://activiti.org/bpmn",
camunda: "http://bpmn.io/schema/bpmn",
flowable: "http://flowable.org/bpmn"
};
return `
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd"
id="diagram_${key}"
targetNamespace="${TYPE_TARGET[type]}">
<bpmn2:process id="${key}" name="${name}" isExecutable="true">
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="${key}">
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
`;
};

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

View File

@ -0,0 +1,98 @@
"use strict";
// var some = require("min-dash").some;
import { some } from "min-dash";
var 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, function (element) {
return is(element, type);
})
);
}
function anyType(element, types) {
return some(types, function (type) {
return is(element, type);
});
}
function isAllowed(propName, propDescriptor, newElement) {
var name = propDescriptor.name,
types = ALLOWED_TYPES[name.replace(/activiti:/, "")];
return name === propName && anyType(newElement, types);
}
function ActivitiModdleExtension(eventBus) {
eventBus.on(
"property.clone",
function (context) {
var newElement = context.newElement,
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;

View File

@ -0,0 +1,9 @@
/*
* @author igdianov
* address https://github.com/igdianov/activiti-bpmn-moddle
* */
export default {
__init__: ["ActivitiModdleExtension"],
ActivitiModdleExtension: ["type", () => import("./activitiExtension")],
};

View File

@ -0,0 +1,162 @@
"use strict";
import { isFunction, isObject, some } from "min-dash";
// var isFunction = require("min-dash").isFunction,
// isObject = require("min-dash").isObject,
// some = require("min-dash").some;
var WILDCARD = "*";
function CamundaModdleExtension(eventBus) {
var self = this;
eventBus.on("moddleCopy.canCopyProperty", function (context) {
var property = context.property,
parent = context.parent;
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
var connector = getParent(parent, "camunda:Connector");
if (connector) {
return true;
}
// special rules inside bpmn:FlowNode
var flowNode = getParent(parent, "bpmn:FlowNode");
if (!flowNode) {
return false;
}
if (
isAny(flowNode, ["bpmn:StartEvent", "bpmn:Gateway", "bpmn:BoundaryEvent"])
) {
return false;
}
if (is(flowNode, "bpmn:SubProcess") && flowNode.get("triggeredByEvent")) {
return false;
}
return true;
};
CamundaModdleExtension.prototype.canHostConnector = function (parent) {
var 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) {
var callActivity = getParent(parent, "bpmn:CallActivity");
if (callActivity) {
return true;
}
var 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;
// helpers //////////
function is(element, type) {
return (
element && isFunction(element.$instanceOf) && element.$instanceOf(type)
);
}
function isAny(element, types) {
return some(types, function (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
var descriptor =
property.$type && property.$model.getTypeDescriptor(property.$type);
var 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, function (type) {
return getParent(parent, type);
});
}
function isWildcard(allowedIn) {
return allowedIn.indexOf(WILDCARD) !== -1;
}

View File

@ -0,0 +1,6 @@
"use strict";
export default {
__init__: ["camundaModdleExtension"],
camundaModdleExtension: ["type", () => import("./extension")],
};

View File

@ -0,0 +1,97 @@
"use strict";
import { some } from "min-dash";
var 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, function (element) {
return is(element, type);
})
);
}
function anyType(element, types) {
return some(types, function (type) {
return is(element, type);
});
}
function isAllowed(propName, propDescriptor, newElement) {
var name = propDescriptor.name,
types = ALLOWED_TYPES[name.replace(/flowable:/, "")];
return name === propName && anyType(newElement, types);
}
function FlowableModdleExtension(eventBus) {
eventBus.on(
"property.clone",
function (context) {
var newElement = context.newElement,
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;

View File

@ -0,0 +1,9 @@
/*
* @author igdianov
* address https://github.com/igdianov/activiti-bpmn-moddle
* */
export default {
__init__: ["FlowableModdleExtension"],
FlowableModdleExtension: ["type", () => import("./flowableExtension")],
};

View File

@ -0,0 +1,15 @@
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]
};

View File

@ -0,0 +1,160 @@
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() {
var actions = {},
create = this._create,
elementFactory = this._elementFactory,
spaceTool = this._spaceTool,
lassoTool = this._lassoTool,
handTool = this._handTool,
globalConnect = this._globalConnect,
translate = this._translate;
function createAction(type, group, className, title, options) {
function createListener(event) {
var shape = elementFactory.createShape(assign({ type: type }, options));
if (options) {
shape.businessObject.di.isExpanded = options.isExpanded;
}
create.start(event, shape);
}
var shortType = type.replace(/^bpmn:/, "");
return {
group: group,
className: className,
title: title || translate("Create {type}", { type: shortType }),
action: {
dragstart: createListener,
click: createListener
}
};
}
function createSubprocess(event) {
var subProcess = elementFactory.createShape({
type: "bpmn:SubProcess",
x: 0,
y: 0,
isExpanded: true
});
var 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: function(event) {
handTool.activateHand(event);
}
}
},
"lasso-tool": {
group: "tools",
className: "bpmn-icon-lasso-tool",
title: translate("Activate the lasso tool"),
action: {
click: function(event) {
lassoTool.activateSelection(event);
}
}
},
"space-tool": {
group: "tools",
className: "bpmn-icon-space-tool",
title: translate("Activate the create/remove space tool"),
action: {
click: function(event) {
spaceTool.activateSelection(event);
}
}
},
"global-connect-tool": {
group: "tools",
className: "bpmn-icon-connection-multi",
title: translate("Activate the global connect tool"),
action: {
click: function(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.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;
};

View File

@ -0,0 +1,41 @@
// 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 || {};
// 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 + "}";
});
};
}

View File

@ -0,0 +1,238 @@
/**
* 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 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.": "指定多个组作为逗号分隔的列表。"
};

View File

@ -0,0 +1,5 @@
const hljs = require("highlight.js/lib/core");
hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml"));
hljs.registerLanguage("json", require("highlight.js/lib/languages/json"));
export default hljs;

View File

@ -0,0 +1,7 @@
import BpmnProcessDesigner from "./designer";
import BmpnProcessPenal from "./penal";
export {
BpmnProcessDesigner,
BmpnProcessPenal
}

View File

@ -0,0 +1,193 @@
<template>
<div class="process-panel__container" :style="{ width: `${this.width}px` }">
<el-collapse v-model="activeTab">
<el-collapse-item name="base">
<div slot="title" class="panel-tab__title"><i class="el-icon-info"></i>常规</div>
<element-base-info :id-edit-disabled="idEditDisabled" :business-object="elementBusinessObject" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="condition" v-if="elementType === 'Process'" key="message">
<div slot="title" class="panel-tab__title"><i class="el-icon-s-comment"></i>消息与信号</div>
<signal-and-massage />
</el-collapse-item>
<el-collapse-item name="condition" v-if="conditionFormVisible" key="condition">
<div slot="title" class="panel-tab__title"><i class="el-icon-s-promotion"></i>流转条件</div>
<flow-condition :business-object="elementBusinessObject" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="condition" v-if="formVisible" key="form">
<div slot="title" class="panel-tab__title"><i class="el-icon-s-order"></i>表单</div>
<element-form :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="task" v-if="elementType.indexOf('Task') !== -1" key="task">
<div slot="title" class="panel-tab__title"><i class="el-icon-s-claim"></i>任务</div>
<element-task :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="multiInstance" v-if="elementType.indexOf('Task') !== -1 && elementType !== 'UserTask'" key="multiInstance">
<div slot="title" class="panel-tab__title"><i class="el-icon-s-help"></i>多实例</div>
<element-multi-instance :business-object="elementBusinessObject" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="listeners" key="listeners">
<div slot="title" class="panel-tab__title"><i class="el-icon-message-solid"></i>执行监听器</div>
<element-listeners :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="taskListeners" v-if="elementType === 'UserTask'" key="taskListeners">
<div slot="title" class="panel-tab__title"><i class="el-icon-message-solid"></i>任务监听器</div>
<user-task-listeners :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="extensions" key="extensions">
<div slot="title" class="panel-tab__title"><i class="el-icon-circle-plus"></i>扩展属性</div>
<element-properties :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="other" key="other">
<div slot="title" class="panel-tab__title"><i class="el-icon-s-promotion"></i>其他</div>
<element-other-config :id="elementId" />
</el-collapse-item>
</el-collapse>
</div>
</template>
<script>
import ElementBaseInfo from "./base/ElementBaseInfo";
import ElementOtherConfig from "./other/ElementOtherConfig";
import ElementTask from "./task/ElementTask";
import ElementMultiInstance from "./multi-instance/ElementMultiInstance";
import FlowCondition from "./flow-condition/FlowCondition";
import SignalAndMassage from "./signal-message/SignalAndMessage";
import ElementListeners from "./listeners/ElementListeners";
import ElementProperties from "./properties/ElementProperties";
import ElementForm from "./form/ElementForm";
import UserTaskListeners from "./listeners/UserTaskListeners";
/**
* 侧边栏
* @Author MiyueFE
* @Home https://github.com/miyuesc
* @Date 2021年3月31日18:57:51
*/
export default {
name: "BpmnPropertiesPanel",
components: {
UserTaskListeners,
ElementForm,
ElementProperties,
ElementListeners,
SignalAndMassage,
FlowCondition,
ElementMultiInstance,
ElementTask,
ElementOtherConfig,
ElementBaseInfo
},
componentName: "BpmnPropertiesPanel",
props: {
bpmnModeler: Object,
prefix: {
type: String,
default: "camunda"
},
width: {
type: Number,
default: 480
},
idEditDisabled: {
type: Boolean,
default: false
}
},
provide() {
return {
prefix: this.prefix,
width: this.width
};
},
data() {
return {
activeTab: "base",
elementId: "",
elementType: "",
elementBusinessObject: {}, // 元素 businessObject 镜像,提供给需要做判断的组件使用
conditionFormVisible: false, // 流转条件设置
formVisible: false // 表单配置
};
},
watch: {
elementId: {
handler() {
this.activeTab = "base";
}
}
},
created() {
this.initModels();
},
methods: {
initModels() {
// 初始化 modeler 以及其他 moddle
if (!this.bpmnModeler) {
// 避免加载时 流程图 并未加载完成
this.timer = setTimeout(() => this.initModels(), 10);
return;
}
if (this.timer) clearTimeout(this.timer);
window.bpmnInstances = {
modeler: this.bpmnModeler,
modeling: this.bpmnModeler.get("modeling"),
moddle: this.bpmnModeler.get("moddle"),
eventBus: this.bpmnModeler.get("eventBus"),
bpmnFactory: this.bpmnModeler.get("bpmnFactory"),
elementFactory: this.bpmnModeler.get("elementFactory"),
elementRegistry: this.bpmnModeler.get("elementRegistry"),
replace: this.bpmnModeler.get("replace"),
selection: this.bpmnModeler.get("selection")
};
this.getActiveElement();
},
getActiveElement() {
// 初始第一个选中元素 bpmn:Process
this.initFormOnChanged(null);
this.bpmnModeler.on("import.done", e => {
this.initFormOnChanged(null);
});
// 监听选择事件,修改当前激活的元素以及表单
this.bpmnModeler.on("selection.changed", ({ newSelection }) => {
this.initFormOnChanged(newSelection[0] || null);
});
this.bpmnModeler.on("element.changed", ({ element }) => {
// 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。
if (element && element.id === this.elementId) {
this.initFormOnChanged(element);
}
});
},
// 初始化数据
initFormOnChanged(element) {
let activatedElement = element;
if (!activatedElement) {
activatedElement =
window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Process") ??
window.bpmnInstances.elementRegistry.find(el => el.type === "bpmn:Collaboration");
}
if (!activatedElement) return;
console.log(`
----------
select element changed:
id: ${activatedElement.id}
type: ${activatedElement.businessObject.$type}
----------
`);
console.log("businessObject: ", activatedElement.businessObject);
window.bpmnInstances.bpmnElement = activatedElement;
this.bpmnElement = activatedElement;
this.elementId = activatedElement.id;
this.elementType = activatedElement.type.split(":")[1] || "";
this.elementBusinessObject = JSON.parse(JSON.stringify(activatedElement.businessObject));
this.conditionFormVisible = !!(
this.elementType === "SequenceFlow" &&
activatedElement.source &&
activatedElement.source.type.indexOf("StartEvent") === -1
);
this.formVisible = this.elementType === "UserTask" || this.elementType === "StartEvent";
},
beforeDestroy() {
window.bpmnInstances = null;
}
}
};
</script>

View File

@ -0,0 +1,76 @@
<template>
<div class="panel-tab__content">
<el-form size="mini" label-width="90px" @submit.native.prevent>
<el-form-item label="ID">
<el-input
v-model="elementBaseInfo.id"
:disabled="idEditDisabled || elementBaseInfo.$type === 'bpmn:Process'"
clearable
@change="updateBaseInfo('id')"
/>
</el-form-item>
<el-form-item label="名称">
<el-input v-model="elementBaseInfo.name" clearable @change="updateBaseInfo('name')" />
</el-form-item>
<!--流程的基础属性-->
<template v-if="elementBaseInfo.$type === 'bpmn:Process'">
<el-form-item label="版本标签">
<el-input v-model="elementBaseInfo.versionTag" clearable @change="updateBaseInfo('versionTag')" />
</el-form-item>
<el-form-item label="可执行">
<el-switch v-model="elementBaseInfo.isExecutable" active-text="是" inactive-text="否" @change="updateBaseInfo('isExecutable')" />
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
export default {
name: "ElementBaseInfo",
props: {
businessObject: Object,
type: String,
idEditDisabled: {
type: Boolean,
default: true
}
},
data() {
return {
elementBaseInfo: {},
};
},
watch: {
businessObject: {
immediate: false,
handler: function(val) {
if (val) {
this.$nextTick(() => this.resetBaseInfo());
}
}
}
},
methods: {
resetBaseInfo() {
this.bpmnElement = window?.bpmnInstances?.bpmnElement;
this.elementBaseInfo = JSON.parse(JSON.stringify(this.bpmnElement.businessObject));
},
updateBaseInfo(key) {
const attrObj = Object.create(null);
attrObj[key] = this.elementBaseInfo[key];
if (key === "id") {
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
id: this.elementBaseInfo[key],
di: { id: `${this.elementBaseInfo[key]}_di` }
});
} else {
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, attrObj);
}
}
},
beforeDestroy() {
this.bpmnElement = null;
}
};
</script>

View File

@ -0,0 +1,142 @@
<template>
<div class="panel-tab__content">
<el-form :model="flowConditionForm" label-width="90px" size="mini" @submit.native.prevent>
<el-form-item label="流转类型">
<el-select v-model="flowConditionForm.type" @change="updateFlowType">
<el-option label="普通流转路径" value="normal" />
<el-option label="默认流转路径" value="default" />
<el-option label="条件流转路径" value="condition" />
</el-select>
</el-form-item>
<el-form-item label="条件格式" v-if="flowConditionForm.type === 'condition'" key="condition">
<el-select v-model="flowConditionForm.conditionType">
<el-option label="表达式" value="expression" />
<el-option label="脚本" value="script" />
</el-select>
</el-form-item>
<el-form-item label="表达式" v-if="flowConditionForm.conditionType && flowConditionForm.conditionType === 'expression'" key="express">
<el-input v-model="flowConditionForm.body" clearable @change="updateFlowCondition" />
</el-form-item>
<template v-if="flowConditionForm.conditionType && flowConditionForm.conditionType === 'script'">
<el-form-item label="脚本语言" key="language">
<el-input v-model="flowConditionForm.language" clearable @change="updateFlowCondition" />
</el-form-item>
<el-form-item label="脚本类型" key="scriptType">
<el-select v-model="flowConditionForm.scriptType">
<el-option label="内联脚本" value="inlineScript" />
<el-option label="外部脚本" value="externalScript" />
</el-select>
</el-form-item>
<el-form-item label="脚本" v-if="flowConditionForm.scriptType === 'inlineScript'" key="body">
<el-input v-model="flowConditionForm.body" type="textarea" clearable @change="updateFlowCondition" />
</el-form-item>
<el-form-item label="资源地址" v-if="flowConditionForm.scriptType === 'externalScript'" key="resource">
<el-input v-model="flowConditionForm.resource" clearable @change="updateFlowCondition" />
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
export default {
name: "FlowCondition",
props: {
businessObject: Object,
type: String
},
data() {
return {
flowConditionForm: {}
};
},
watch: {
businessObject: {
immediate: true,
handler() {
this.$nextTick(() => this.resetFlowCondition());
}
}
},
methods: {
resetFlowCondition() {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.bpmnElementSource = this.bpmnElement.source;
this.bpmnElementSourceRef = this.bpmnElement.businessObject.sourceRef;
if (this.bpmnElementSourceRef && this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.bpmnElement.id) {
// 默认
this.flowConditionForm = { type: "default" };
} else if (!this.bpmnElement.businessObject.conditionExpression) {
// 普通
this.flowConditionForm = { type: "normal" };
} else {
// 带条件
const conditionExpression = this.bpmnElement.businessObject.conditionExpression;
this.flowConditionForm = { ...conditionExpression, type: "condition" };
// resource 可直接标识 是否是外部资源脚本
if (this.flowConditionForm.resource) {
this.$set(this.flowConditionForm, "conditionType", "script");
this.$set(this.flowConditionForm, "scriptType", "externalScript");
return;
}
if (conditionExpression.language) {
this.$set(this.flowConditionForm, "conditionType", "script");
this.$set(this.flowConditionForm, "scriptType", "inlineScript");
return;
}
this.$set(this.flowConditionForm, "conditionType", "expression");
}
},
updateFlowType(flowType) {
// 正常条件类
if (flowType === "condition") {
this.flowConditionRef = window.bpmnInstances.moddle.create("bpmn:FormalExpression");
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
conditionExpression: this.flowConditionRef
});
return;
}
// 默认路径
if (flowType === "default") {
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
conditionExpression: null
});
window.bpmnInstances.modeling.updateProperties(this.bpmnElementSource, {
default: this.bpmnElement
});
return;
}
// 正常路径,如果来源节点的默认路径是当前连线时,清除父元素的默认路径配置
if (this.bpmnElementSourceRef.default && this.bpmnElementSourceRef.default.id === this.bpmnElement.id) {
window.bpmnInstances.modeling.updateProperties(this.bpmnElementSource, {
default: null
});
}
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
conditionExpression: null
});
},
updateFlowCondition() {
let { conditionType, scriptType, body, resource, language } = this.flowConditionForm;
let condition;
if (conditionType === "expression") {
condition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body });
} else {
if (scriptType === "inlineScript") {
condition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body, language });
this.$set(this.flowConditionForm, "resource", "");
} else {
this.$set(this.flowConditionForm, "body", "");
condition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { resource, language });
}
}
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { conditionExpression: condition });
}
},
beforeDestroy() {
this.bpmnElement = null;
this.bpmnElementSource = null;
this.bpmnElementSourceRef = null;
}
};
</script>

View File

@ -0,0 +1,475 @@
<template>
<div class="panel-tab__content">
<el-form size="mini" label-width="80px" @submit.native.prevent>
<el-form-item label="表单" prop="formKey">
<el-select
v-model="formKey"
placeholder="请选择表单"
@change="updateElementFormKey"
clearable
>
<el-option
v-for="item in formOptions"
:key="item.formId"
:label="item.formName"
:value="`key_${item.formId}`"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="表单标识">-->
<!-- <el-input v-model="formKey" clearable @change="updateElementFormKey" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="业务标识">-->
<!-- <el-select v-model="businessKey" @change="updateElementBusinessKey">-->
<!-- <el-option v-for="i in fieldList" :key="i.id" :value="i.id" :label="i.label" />-->
<!-- <el-option label="无" value="" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
</el-form>
<!--字段列表-->
<!-- <div class="element-property list-property">-->
<!-- <el-divider><i class="el-icon-coin"></i> 表单字段</el-divider>-->
<!-- <el-table :data="fieldList" size="mini" max-height="240" border fit>-->
<!-- <el-table-column label="序号" type="index" width="50px" />-->
<!-- <el-table-column label="字段名称" prop="label" min-width="80px" show-overflow-tooltip />-->
<!-- <el-table-column label="字段类型" prop="type" min-width="80px" :formatter="row => fieldType[row.type] || row.type" show-overflow-tooltip />-->
<!-- <el-table-column label="默认值" prop="defaultValue" min-width="80px" show-overflow-tooltip />-->
<!-- <el-table-column label="操作" width="90px">-->
<!-- <template slot-scope="{ row, $index }">-->
<!-- <el-button size="mini" type="text" @click="openFieldForm(row, $index)">编辑</el-button>-->
<!-- <el-divider direction="vertical" />-->
<!-- <el-button size="mini" type="text" style="color: #ff4d4f" @click="removeField(row, $index)">移除</el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- </div>-->
<!-- <div class="element-drawer__button">-->
<!-- <el-button size="mini" type="primary" icon="el-icon-plus" @click="openFieldForm(null, -1)">添加字段</el-button>-->
<!-- </div>-->
<!--字段配置侧边栏-->
<!-- <el-drawer :visible.sync="fieldModelVisible" title="字段配置" :size="`${width}px`" append-to-body destroy-on-close>-->
<!-- <el-form :model="formFieldForm" label-width="90px" size="mini" @submit.native.prevent>-->
<!-- <el-form-item label="字段ID">-->
<!-- <el-input v-model="formFieldForm.id" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="类型">-->
<!-- <el-select v-model="formFieldForm.typeType" placeholder="请选择字段类型" clearable @change="changeFieldTypeType">-->
<!-- <el-option v-for="(value, key) of fieldType" :label="value" :value="key" :key="key" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="类型名称" v-if="formFieldForm.typeType === 'custom'">-->
<!-- <el-input v-model="formFieldForm.type" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="名称">-->
<!-- <el-input v-model="formFieldForm.label" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="时间格式" v-if="formFieldForm.typeType === 'date'">-->
<!-- <el-input v-model="formFieldForm.datePattern" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="默认值">-->
<!-- <el-input v-model="formFieldForm.defaultValue" clearable />-->
<!-- </el-form-item>-->
<!-- </el-form>-->
<!-- &lt;!&ndash; 枚举值设置 &ndash;&gt;-->
<!-- <template v-if="formFieldForm.type === 'enum'">-->
<!-- <el-divider key="enum-divider" />-->
<!-- <p class="listener-filed__title" key="enum-title">-->
<!-- <span><i class="el-icon-menu"></i>枚举值列表</span>-->
<!-- <el-button size="mini" type="primary" @click="openFieldOptionForm(null, -1, 'enum')">添加枚举值</el-button>-->
<!-- </p>-->
<!-- <el-table :data="fieldEnumList" size="mini" key="enum-table" max-height="240" border fit>-->
<!-- <el-table-column label="序号" width="50px" type="index" />-->
<!-- <el-table-column label="枚举值编号" prop="id" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="枚举值名称" prop="name" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="操作" width="90px">-->
<!-- <template slot-scope="{ row, $index }">-->
<!-- <el-button size="mini" type="text" @click="openFieldOptionForm(row, $index, 'enum')">编辑</el-button>-->
<!-- <el-divider direction="vertical" />-->
<!-- <el-button size="mini" type="text" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'enum')">移除</el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- </template>-->
<!-- &lt;!&ndash; 校验规则 &ndash;&gt;-->
<!-- <el-divider key="validation-divider" />-->
<!-- <p class="listener-filed__title" key="validation-title">-->
<!-- <span><i class="el-icon-menu"></i>约束条件列表</span>-->
<!-- <el-button size="mini" type="primary" @click="openFieldOptionForm(null, -1, 'constraint')">添加约束</el-button>-->
<!-- </p>-->
<!-- <el-table :data="fieldConstraintsList" size="mini" key="validation-table" max-height="240" border fit>-->
<!-- <el-table-column label="序号" width="50px" type="index" />-->
<!-- <el-table-column label="约束名称" prop="name" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="约束配置" prop="config" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="操作" width="90px">-->
<!-- <template slot-scope="{ row, $index }">-->
<!-- <el-button size="mini" type="text" @click="openFieldOptionForm(row, $index, 'constraint')">编辑</el-button>-->
<!-- <el-divider direction="vertical" />-->
<!-- <el-button size="mini" type="text" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'constraint')">移除</el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- &lt;!&ndash; 表单属性 &ndash;&gt;-->
<!-- <el-divider key="property-divider" />-->
<!-- <p class="listener-filed__title" key="property-title">-->
<!-- <span><i class="el-icon-menu"></i>字段属性列表</span>-->
<!-- <el-button size="mini" type="primary" @click="openFieldOptionForm(null, -1, 'property')">添加属性</el-button>-->
<!-- </p>-->
<!-- <el-table :data="fieldPropertiesList" size="mini" key="property-table" max-height="240" border fit>-->
<!-- <el-table-column label="序号" width="50px" type="index" />-->
<!-- <el-table-column label="属性编号" prop="id" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="操作" width="90px">-->
<!-- <template slot-scope="{ row, $index }">-->
<!-- <el-button size="mini" type="text" @click="openFieldOptionForm(row, $index, 'property')">编辑</el-button>-->
<!-- <el-divider direction="vertical" />-->
<!-- <el-button size="mini" type="text" style="color: #ff4d4f" @click="removeFieldOptionItem(row, $index, 'property')">移除</el-button>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- &lt;!&ndash; 底部按钮 &ndash;&gt;-->
<!-- <div class="element-drawer__button">-->
<!-- <el-button size="mini"> </el-button>-->
<!-- <el-button size="mini" type="primary" @click="saveField"> </el-button>-->
<!-- </div>-->
<!-- </el-drawer>-->
<el-dialog
:visible.sync="fieldOptionModelVisible"
:title="optionModelTitle"
width="600px"
append-to-body
destroy-on-close
>
<el-form
:model="fieldOptionForm"
size="mini"
label-width="96px"
@submit.native.prevent
>
<el-form-item
label="编号/ID"
v-if="fieldOptionType !== 'constraint'"
key="option-id"
>
<el-input v-model="fieldOptionForm.id" clearable />
</el-form-item>
<el-form-item
label="名称"
v-if="fieldOptionType !== 'property'"
key="option-name"
>
<el-input v-model="fieldOptionForm.name" clearable />
</el-form-item>
<el-form-item
label="配置"
v-if="fieldOptionType === 'constraint'"
key="option-config"
>
<el-input v-model="fieldOptionForm.config" clearable />
</el-form-item>
<el-form-item
label="值"
v-if="fieldOptionType === 'property'"
key="option-value"
>
<el-input v-model="fieldOptionForm.value" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" @click="fieldOptionModelVisible = false"
> </el-button
>
<el-button size="mini" type="primary" @click="saveFieldOption"
> </el-button
>
</template>
</el-dialog>
</div>
</template>
<script>
import { listForm } from "@/api/flowable/form";
export default {
name: "ElementForm",
props: {
id: String,
type: String,
},
inject: {
prefix: "prefix",
width: "width",
},
data() {
return {
formOptions: [],
formKey: "",
businessKey: "",
optionModelTitle: "",
fieldList: [],
formFieldForm: {},
fieldType: {
long: "长整型",
string: "字符串",
boolean: "布尔类",
date: "日期类",
enum: "枚举类",
custom: "自定义类型",
},
formFieldIndex: -1, // 编辑中的字段, -1 为新增
formFieldOptionIndex: -1, // 编辑中的字段配置项, -1 为新增
fieldModelVisible: false,
fieldOptionModelVisible: false,
fieldOptionForm: {}, // 当前激活的字段配置项数据
fieldOptionType: "", // 当前激活的字段配置项弹窗 类型
fieldEnumList: [], // 枚举值列表
fieldConstraintsList: [], // 约束条件列表
fieldPropertiesList: [], // 绑定属性列表
};
},
watch: {
id: {
immediate: true,
handler(val) {
val && val.length && this.$nextTick(() => this.resetFormList());
},
},
},
created() {
/** 查询流程分类列表 */
this.getFormList();
},
methods: {
/** 查询表单列表 */
getFormList() {
listForm().then((response) => (this.formOptions = response.rows));
},
resetFormList() {
this.bpmnELement = window.bpmnInstances.bpmnElement;
this.formKey = this.bpmnELement.businessObject.formKey;
// 获取元素扩展属性 或者 创建扩展属性
this.elExtensionElements =
this.bpmnELement.businessObject.get("extensionElements") ||
window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
values: [],
});
// 获取元素表单配置 或者 创建新的表单配置
// this.formData =
// this.elExtensionElements.values.filter(ex => ex.$type === `${this.prefix}:FormData`)?.[0] ||
// window.bpmnInstances.moddle.create(`${this.prefix}:FormData`, { fields: [] });
// 业务标识 businessKey 绑定在 formData 中
// this.businessKey = this.formData.businessKey;
// 保留剩余扩展元素,便于后面更新该元素对应属性
this.otherExtensions = this.elExtensionElements.values.filter(
(ex) => ex.$type !== `${this.prefix}:FormData`
);
// 复制原始值,填充表格
// this.fieldList = JSON.parse(JSON.stringify(this.formData.fields || []));
// 更新元素扩展属性,避免后续报错
// this.updateElementExtensions();
},
updateElementFormKey() {
window.bpmnInstances.modeling.updateProperties(this.bpmnELement, {
formKey: this.formKey,
});
},
updateElementBusinessKey() {
window.bpmnInstances.modeling.updateModdleProperties(
this.bpmnELement,
this.formData,
{ businessKey: this.businessKey }
);
},
// 根据类型调整字段type
changeFieldTypeType(type) {
this.$set(this.formFieldForm, "type", type === "custom" ? "" : type);
},
// 打开字段详情侧边栏
openFieldForm(field, index) {
this.formFieldIndex = index;
if (index !== -1) {
const FieldObject = this.formData.fields[index];
this.formFieldForm = JSON.parse(JSON.stringify(field));
// 设置自定义类型
this.$set(
this.formFieldForm,
"typeType",
!this.fieldType[field.type] ? "custom" : field.type
);
// 初始化枚举值列表
field.type === "enum" &&
(this.fieldEnumList = JSON.parse(
JSON.stringify(FieldObject?.values || [])
));
// 初始化约束条件列表
this.fieldConstraintsList = JSON.parse(
JSON.stringify(FieldObject?.validation?.constraints || [])
);
// 初始化自定义属性列表
this.fieldPropertiesList = JSON.parse(
JSON.stringify(FieldObject?.properties?.values || [])
);
} else {
this.formFieldForm = {};
// 初始化枚举值列表
this.fieldEnumList = [];
// 初始化约束条件列表
this.fieldConstraintsList = [];
// 初始化自定义属性列表
this.fieldPropertiesList = [];
}
this.fieldModelVisible = true;
},
// 打开字段 某个 配置项 弹窗
openFieldOptionForm(option, index, type) {
this.fieldOptionModelVisible = true;
this.fieldOptionType = type;
this.formFieldOptionIndex = index;
if (type === "property") {
this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
return (this.optionModelTitle = "属性配置");
}
if (type === "enum") {
this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
return (this.optionModelTitle = "枚举值配置");
}
this.fieldOptionForm = option ? JSON.parse(JSON.stringify(option)) : {};
return (this.optionModelTitle = "约束条件配置");
},
// 保存字段 某个 配置项
saveFieldOption() {
if (this.formFieldOptionIndex === -1) {
if (this.fieldOptionType === "property") {
this.fieldPropertiesList.push(this.fieldOptionForm);
}
if (this.fieldOptionType === "constraint") {
this.fieldConstraintsList.push(this.fieldOptionForm);
}
if (this.fieldOptionType === "enum") {
this.fieldEnumList.push(this.fieldOptionForm);
}
} else {
this.fieldOptionType === "property" &&
this.fieldPropertiesList.splice(
this.formFieldOptionIndex,
1,
this.fieldOptionForm
);
this.fieldOptionType === "constraint" &&
this.fieldConstraintsList.splice(
this.formFieldOptionIndex,
1,
this.fieldOptionForm
);
this.fieldOptionType === "enum" &&
this.fieldEnumList.splice(
this.formFieldOptionIndex,
1,
this.fieldOptionForm
);
}
this.fieldOptionModelVisible = false;
this.fieldOptionForm = {};
},
// 保存字段配置
saveField() {
const { id, type, label, defaultValue, datePattern } = this.formFieldForm;
const Field = window.bpmnInstances.moddle.create(
`${this.prefix}:FormField`,
{ id, type, label }
);
defaultValue && (Field.defaultValue = defaultValue);
datePattern && (Field.datePattern = datePattern);
// 构建属性
if (this.fieldPropertiesList && this.fieldPropertiesList.length) {
const fieldPropertyList = this.fieldPropertiesList.map((fp) => {
return window.bpmnInstances.moddle.create(`${this.prefix}:Property`, {
id: fp.id,
value: fp.value,
});
});
Field.properties = window.bpmnInstances.moddle.create(
`${this.prefix}:Properties`,
{ values: fieldPropertyList }
);
}
// 构建校验规则
if (this.fieldConstraintsList && this.fieldConstraintsList.length) {
const fieldConstraintList = this.fieldConstraintsList.map((fc) => {
return window.bpmnInstances.moddle.create(
`${this.prefix}:Constraint`,
{ name: fc.name, config: fc.config }
);
});
Field.validation = window.bpmnInstances.moddle.create(
`${this.prefix}:Validation`,
{ constraints: fieldConstraintList }
);
}
// 构建枚举值
if (this.fieldEnumList && this.fieldEnumList.length) {
Field.values = this.fieldEnumList.map((fe) => {
return window.bpmnInstances.moddle.create(`${this.prefix}:Value`, {
name: fe.name,
id: fe.id,
});
});
}
// 更新数组 与 表单配置实例
if (this.formFieldIndex === -1) {
this.fieldList.push(this.formFieldForm);
this.formData.fields.push(Field);
} else {
this.fieldList.splice(this.formFieldIndex, 1, this.formFieldForm);
this.formData.fields.splice(this.formFieldIndex, 1, Field);
}
this.updateElementExtensions();
this.fieldModelVisible = false;
},
// 移除某个 字段的 配置项
removeFieldOptionItem(option, index, type) {
if (type === "property") {
this.fieldPropertiesList.splice(index, 1);
return;
}
if (type === "enum") {
this.fieldEnumList.splice(index, 1);
return;
}
this.fieldConstraintsList.splice(index, 1);
},
// 移除 字段
removeField(field, index) {
this.fieldList.splice(index, 1);
this.formData.fields.splice(index, 1);
this.updateElementExtensions();
},
updateElementExtensions() {
// 更新回扩展元素
const newElExtensionElements = window.bpmnInstances.moddle.create(
`bpmn:ExtensionElements`,
{
values: this.otherExtensions.concat(this.formData),
}
);
// 更新到元素上
window.bpmnInstances.modeling.updateProperties(this.bpmnELement, {
extensionElements: newElExtensionElements,
});
},
},
};
</script>

View File

@ -0,0 +1,7 @@
import BpmnPropertiesPanel from "./PropertiesPanel.vue";
BpmnPropertiesPanel.install = function(Vue) {
Vue.component(BpmnPropertiesPanel.name, BpmnPropertiesPanel);
};
export default BpmnPropertiesPanel;

View File

@ -0,0 +1,296 @@
<template>
<div class="panel-tab__content">
<el-table :data="elementListenersList" size="mini" border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="事件类型" min-width="100px" prop="event" />
<el-table-column label="监听器类型" min-width="100px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
<el-table-column label="操作" width="90px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openListenerForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListener(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openListenerForm(null)">添加监听器</el-button>
</div>
<!-- 监听器 编辑/创建 部分 -->
<el-drawer :visible.sync="listenerFormModelVisible" title="执行监听器" :size="`${width}px`" append-to-body destroy-on-close>
<el-form size="mini" :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.native.prevent>
<el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerForm.event">
<el-option label="start" value="start" />
<el-option label="end" value="end" />
</el-select>
</el-form-item>
<el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerForm.listenerType">
<el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'classListener'"
label="Java类"
prop="class"
key="listener-class"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.class" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'expressionListener'"
label="表达式"
prop="expression"
key="listener-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.expression" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
label="代理表达式"
prop="delegateExpression"
key="listener-delegate"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.delegateExpression" clearable />
</el-form-item>
<template v-if="listenerForm.listenerType === 'scriptListener'">
<el-form-item
label="脚本格式"
prop="scriptFormat"
key="listener-script-format"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
>
<el-input v-model="listenerForm.scriptFormat" clearable />
</el-form-item>
<el-form-item
label="脚本类型"
prop="scriptType"
key="listener-script-type"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
>
<el-select v-model="listenerForm.scriptType">
<el-option label="内联脚本" value="inlineScript" />
<el-option label="外部脚本" value="externalScript" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'inlineScript'"
label="脚本内容"
prop="value"
key="listener-script"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
>
<el-input v-model="listenerForm.value" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'externalScript'"
label="资源地址"
prop="resource"
key="listener-resource"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
>
<el-input v-model="listenerForm.resource" clearable />
</el-form-item>
</template>
</el-form>
<el-divider />
<p class="listener-filed__title">
<span><i class="el-icon-menu"></i>注入字段</span>
<el-button size="mini" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
</p>
<el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="字段名称" min-width="100px" prop="name" />
<el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
<el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
<el-table-column label="操作" width="100px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openListenerFieldForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListenerField(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="mini" @click="listenerFormModelVisible = false"> </el-button>
<el-button size="mini" type="primary" @click="saveListenerConfig"> </el-button>
</div>
</el-drawer>
<!-- 注入西段 编辑/创建 部分 -->
<el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
<el-form :model="listenerFieldForm" size="mini" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
<el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-input v-model="listenerFieldForm.name" clearable />
</el-form-item>
<el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerFieldForm.fieldType">
<el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'string'"
label="字段值:"
prop="string"
key="field-string"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.string" clearable />
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'expression'"
label="表达式:"
prop="expression"
key="field-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.expression" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" @click="listenerFieldFormModelVisible = false"> </el-button>
<el-button size="mini" type="primary" @click="saveListenerFiled"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { createListenerObject, updateElementExtensions } from "../../utils";
import { initListenerType, initListenerForm, listenerType, fieldType } from "./utilSelf";
export default {
name: "ElementListeners",
props: {
id: String,
type: String
},
inject: {
prefix: "prefix",
width: "width"
},
data() {
return {
elementListenersList: [], // 监听器列表
listenerForm: {}, // 监听器详情表单
listenerFormModelVisible: false, // 监听器 编辑 侧边栏显示状态
fieldsListOfListener: [],
listenerFieldForm: {}, // 监听器 注入字段 详情表单
listenerFieldFormModelVisible: false, // 监听器 注入字段表单弹窗 显示状态
editingListenerIndex: -1, // 监听器所在下标,-1 为新增
editingListenerFieldIndex: -1, // 字段所在下标,-1 为新增
listenerTypeObject: listenerType,
fieldTypeObject: fieldType
};
},
watch: {
id: {
immediate: true,
handler(val) {
val && val.length && this.$nextTick(() => this.resetListenersList());
}
}
},
methods: {
resetListenersList() {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.otherExtensionList = [];
this.bpmnElementListeners =
this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `${this.prefix}:ExecutionListener`) ?? [];
this.elementListenersList = this.bpmnElementListeners.map(listener => initListenerType(listener));
},
// 打开 监听器详情 侧边栏
openListenerForm(listener, index) {
if (listener) {
this.listenerForm = initListenerForm(listener);
this.editingListenerIndex = index;
} else {
this.listenerForm = {};
this.editingListenerIndex = -1; // 标记为新增
}
if (listener && listener.fields) {
this.fieldsListOfListener = listener.fields.map(field => ({ ...field, fieldType: field.string ? "string" : "expression" }));
} else {
this.fieldsListOfListener = [];
this.$set(this.listenerForm, "fields", []);
}
// 打开侧边栏并清楚验证状态
this.listenerFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
});
},
// 打开监听器字段编辑弹窗
openListenerFieldForm(field, index) {
this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
this.editingListenerFieldIndex = field ? index : -1;
this.listenerFieldFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
});
},
// 保存监听器注入字段
async saveListenerFiled() {
let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
if (!validateStatus) return; // 验证不通过直接返回
if (this.editingListenerFieldIndex === -1) {
this.fieldsListOfListener.push(this.listenerFieldForm);
this.listenerForm.fields.push(this.listenerFieldForm);
} else {
this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
}
this.listenerFieldFormModelVisible = false;
this.$nextTick(() => (this.listenerFieldForm = {}));
},
// 移除监听器字段
removeListenerField(field, index) {
this.$confirm("确认移除该字段吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
})
.then(() => {
this.fieldsListOfListener.splice(index, 1);
this.listenerForm.fields.splice(index, 1);
})
.catch(() => console.info("操作取消"));
},
// 移除监听器
removeListener(listener, index) {
this.$confirm("确认移除该监听器吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
})
.then(() => {
this.bpmnElementListeners.splice(index, 1);
this.elementListenersList.splice(index, 1);
updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
})
.catch(() => console.info("操作取消"));
},
// 保存监听器配置
async saveListenerConfig() {
let validateStatus = await this.$refs["listenerFormRef"].validate();
if (!validateStatus) return; // 验证不通过直接返回
const listenerObject = createListenerObject(this.listenerForm, false, this.prefix);
if (this.editingListenerIndex === -1) {
this.bpmnElementListeners.push(listenerObject);
this.elementListenersList.push(this.listenerForm);
} else {
this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
}
// 保存其他配置
this.otherExtensionList = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `${this.prefix}:ExecutionListener`) ?? [];
updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
// 4. 隐藏侧边栏
this.listenerFormModelVisible = false;
this.listenerForm = {};
}
}
};
</script>

View File

@ -0,0 +1,319 @@
<template>
<div class="panel-tab__content">
<el-table :data="elementListenersList" size="mini" border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="事件类型" min-width="80px" show-overflow-tooltip :formatter="row => listenerEventTypeObject[row.event]" />
<el-table-column label="事件id" min-width="80px" prop="id" show-overflow-tooltip />
<el-table-column label="监听器类型" min-width="80px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
<el-table-column label="操作" width="90px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openListenerForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListener(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openListenerForm(null)">添加监听器</el-button>
</div>
<!-- 监听器 编辑/创建 部分 -->
<el-drawer :visible.sync="listenerFormModelVisible" title="任务监听器" :size="`${width}px`" append-to-body destroy-on-close>
<el-form size="mini" :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.native.prevent>
<el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerForm.event">
<el-option v-for="i in Object.keys(listenerEventTypeObject)" :key="i" :label="listenerEventTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item label="监听器ID" prop="id" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-input v-model="listenerForm.id" clearable />
</el-form-item>
<el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerForm.listenerType">
<el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'classListener'"
label="Java类"
prop="class"
key="listener-class"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.class" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'expressionListener'"
label="表达式"
prop="expression"
key="listener-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.expression" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
label="代理表达式"
prop="delegateExpression"
key="listener-delegate"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.delegateExpression" clearable />
</el-form-item>
<template v-if="listenerForm.listenerType === 'scriptListener'">
<el-form-item
label="脚本格式"
prop="scriptFormat"
key="listener-script-format"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
>
<el-input v-model="listenerForm.scriptFormat" clearable />
</el-form-item>
<el-form-item
label="脚本类型"
prop="scriptType"
key="listener-script-type"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
>
<el-select v-model="listenerForm.scriptType">
<el-option label="内联脚本" value="inlineScript" />
<el-option label="外部脚本" value="externalScript" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'inlineScript'"
label="脚本内容"
prop="value"
key="listener-script"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
>
<el-input v-model="listenerForm.value" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'externalScript'"
label="资源地址"
prop="resource"
key="listener-resource"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
>
<el-input v-model="listenerForm.resource" clearable />
</el-form-item>
</template>
<template v-if="listenerForm.event === 'timeout'">
<el-form-item label="定时器类型" prop="eventDefinitionType" key="eventDefinitionType">
<el-select v-model="listenerForm.eventDefinitionType">
<el-option label="日期" value="date" />
<el-option label="持续时长" value="duration" />
<el-option label="循环" value="cycle" />
<el-option label="无" value="null" />
</el-select>
</el-form-item>
<el-form-item
v-if="!!listenerForm.eventDefinitionType && listenerForm.eventDefinitionType !== 'null'"
label="定时器"
prop="eventTimeDefinitions"
key="eventTimeDefinitions"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写定时器配置' }"
>
<el-input v-model="listenerForm.eventTimeDefinitions" clearable />
</el-form-item>
</template>
</el-form>
<el-divider />
<p class="listener-filed__title">
<span><i class="el-icon-menu"></i>注入字段</span>
<el-button size="mini" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
</p>
<el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="字段名称" min-width="100px" prop="name" />
<el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
<el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
<el-table-column label="操作" width="100px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openListenerFieldForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListenerField(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="mini" @click="listenerFormModelVisible = false"> </el-button>
<el-button size="mini" type="primary" @click="saveListenerConfig"> </el-button>
</div>
</el-drawer>
<!-- 注入西段 编辑/创建 部分 -->
<el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
<el-form :model="listenerFieldForm" size="mini" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
<el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-input v-model="listenerFieldForm.name" clearable />
</el-form-item>
<el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerFieldForm.fieldType">
<el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'string'"
label="字段值:"
prop="string"
key="field-string"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.string" clearable />
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'expression'"
label="表达式:"
prop="expression"
key="field-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.expression" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" @click="listenerFieldFormModelVisible = false"> </el-button>
<el-button size="mini" type="primary" @click="saveListenerFiled"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { createListenerObject, updateElementExtensions } from "../../utils";
import { initListenerForm, initListenerType, eventType, listenerType, fieldType } from "./utilSelf";
export default {
name: "UserTaskListeners",
props: {
id: String,
type: String
},
inject: {
prefix: "prefix",
width: "width"
},
data() {
return {
elementListenersList: [],
listenerEventTypeObject: eventType,
listenerTypeObject: listenerType,
listenerFormModelVisible: false,
listenerForm: {},
fieldTypeObject: fieldType,
fieldsListOfListener: [],
listenerFieldFormModelVisible: false, // 监听器 注入字段表单弹窗 显示状态
editingListenerIndex: -1, // 监听器所在下标,-1 为新增
editingListenerFieldIndex: -1, // 字段所在下标,-1 为新增
listenerFieldForm: {} // 监听器 注入字段 详情表单
};
},
watch: {
id: {
immediate: true,
handler(val) {
val && val.length && this.$nextTick(() => this.resetListenersList());
}
}
},
methods: {
resetListenersList() {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.otherExtensionList = [];
this.bpmnElementListeners = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type === `${this.prefix}:TaskListener`) ?? [];
this.elementListenersList = this.bpmnElementListeners.map(listener => initListenerType(listener));
},
openListenerForm(listener, index) {
if (listener) {
this.listenerForm = initListenerForm(listener);
this.editingListenerIndex = index;
} else {
this.listenerForm = {};
this.editingListenerIndex = -1; // 标记为新增
}
if (listener && listener.fields) {
this.fieldsListOfListener = listener.fields.map(field => ({ ...field, fieldType: field.string ? "string" : "expression" }));
} else {
this.fieldsListOfListener = [];
this.$set(this.listenerForm, "fields", []);
}
// 打开侧边栏并清楚验证状态
this.listenerFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["listenerFormRef"]) this.$refs["listenerFormRef"].clearValidate();
});
},
// 移除监听器
removeListener(listener, index) {
this.$confirm("确认移除该监听器吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
})
.then(() => {
this.bpmnElementListeners.splice(index, 1);
this.elementListenersList.splice(index, 1);
updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
})
.catch(() => console.info("操作取消"));
},
// 保存监听器
async saveListenerConfig() {
let validateStatus = await this.$refs["listenerFormRef"].validate();
if (!validateStatus) return; // 验证不通过直接返回
const listenerObject = createListenerObject(this.listenerForm, true, this.prefix);
if (this.editingListenerIndex === -1) {
this.bpmnElementListeners.push(listenerObject);
this.elementListenersList.push(this.listenerForm);
} else {
this.bpmnElementListeners.splice(this.editingListenerIndex, 1, listenerObject);
this.elementListenersList.splice(this.editingListenerIndex, 1, this.listenerForm);
}
// 保存其他配置
this.otherExtensionList = this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => ex.$type !== `${this.prefix}:TaskListener`) ?? [];
updateElementExtensions(this.bpmnElement, this.otherExtensionList.concat(this.bpmnElementListeners));
// 4. 隐藏侧边栏
this.listenerFormModelVisible = false;
this.listenerForm = {};
},
// 打开监听器字段编辑弹窗
openListenerFieldForm(field, index) {
this.listenerFieldForm = field ? JSON.parse(JSON.stringify(field)) : {};
this.editingListenerFieldIndex = field ? index : -1;
this.listenerFieldFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["listenerFieldFormRef"]) this.$refs["listenerFieldFormRef"].clearValidate();
});
},
// 保存监听器注入字段
async saveListenerFiled() {
let validateStatus = await this.$refs["listenerFieldFormRef"].validate();
if (!validateStatus) return; // 验证不通过直接返回
if (this.editingListenerFieldIndex === -1) {
this.fieldsListOfListener.push(this.listenerFieldForm);
this.listenerForm.fields.push(this.listenerFieldForm);
} else {
this.fieldsListOfListener.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
this.listenerForm.fields.splice(this.editingListenerFieldIndex, 1, this.listenerFieldForm);
}
this.listenerFieldFormModelVisible = false;
this.$nextTick(() => (this.listenerFieldForm = {}));
},
// 移除监听器字段
removeListenerField(field, index) {
this.$confirm("确认移除该字段吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
})
.then(() => {
this.fieldsListOfListener.splice(index, 1);
this.listenerForm.fields.splice(index, 1);
})
.catch(() => console.info("操作取消"));
}
}
};
</script>

View File

@ -0,0 +1,178 @@
export const template = isTaskListener => {
return `
<div class="panel-tab__content">
<el-table :data="elementListenersList" size="mini" border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="事件类型" min-width="100px" prop="event" />
<el-table-column label="监听器类型" min-width="100px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
<el-table-column label="操作" width="90px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openListenerForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListener(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openListenerForm(null)">添加监听器</el-button>
</div>
<!-- 监听器 编辑/创建 部分 -->
<el-drawer :visible.sync="listenerFormModelVisible" title="执行监听器" :size="width + 'px'" append-to-body destroy-on-close>
<el-form size="mini" :model="listenerForm" label-width="96px" ref="listenerFormRef" @submit.native.prevent>
<el-form-item label="事件类型" prop="event" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerForm.event">
<el-option label="start" value="start" />
<el-option label="end" value="end" />
</el-select>
</el-form-item>
<el-form-item label="监听器类型" prop="listenerType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerForm.listenerType">
<el-option v-for="i in Object.keys(listenerTypeObject)" :key="i" :label="listenerTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'classListener'"
label="Java类"
prop="class"
key="listener-class"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.class" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'expressionListener'"
label="表达式"
prop="expression"
key="listener-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.expression" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
label="代理表达式"
prop="delegateExpression"
key="listener-delegate"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerForm.delegateExpression" clearable />
</el-form-item>
<template v-if="listenerForm.listenerType === 'scriptListener'">
<el-form-item
label="脚本格式"
prop="scriptFormat"
key="listener-script-format"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本格式' }"
>
<el-input v-model="listenerForm.scriptFormat" clearable />
</el-form-item>
<el-form-item
label="脚本类型"
prop="scriptType"
key="listener-script-type"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请选择脚本类型' }"
>
<el-select v-model="listenerForm.scriptType">
<el-option label="内联脚本" value="inlineScript" />
<el-option label="外部脚本" value="externalScript" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'inlineScript'"
label="脚本内容"
prop="value"
key="listener-script"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写脚本内容' }"
>
<el-input v-model="listenerForm.value" clearable />
</el-form-item>
<el-form-item
v-if="listenerForm.scriptType === 'externalScript'"
label="资源地址"
prop="resource"
key="listener-resource"
:rules="{ required: true, trigger: ['blur', 'change'], message: '请填写资源地址' }"
>
<el-input v-model="listenerForm.resource" clearable />
</el-form-item>
</template>
${
isTaskListener
? "<el-form-item label='定时器类型' prop='eventDefinitionType' key='eventDefinitionType'>" +
"<el-select v-model='listenerForm.eventDefinitionType'>" +
"<el-option label='日期' value='date' />" +
"<el-option label='持续时长' value='duration' />" +
"<el-option label='循环' value='cycle' />" +
"<el-option label='无' value='' />" +
"</el-select>" +
"</el-form-item>" +
"<el-form-item v-if='!!listenerForm.eventDefinitionType' label='定时器' prop='eventDefinitions' key='eventDefinitions'>" +
"<el-input v-model='listenerForm.eventDefinitions' clearable />" +
"</el-form-item>"
: ""
}
</el-form>
<el-divider />
<p class="listener-filed__title">
<span><i class="el-icon-menu"></i>注入字段:</span>
<el-button size="mini" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
</p>
<el-table :data="fieldsListOfListener" size="mini" max-height="240" border fit style="flex: none">
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="字段名称" min-width="100px" prop="name" />
<el-table-column label="字段类型" min-width="80px" show-overflow-tooltip :formatter="row => fieldTypeObject[row.fieldType]" />
<el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
<el-table-column label="操作" width="100px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openListenerFieldForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeListenerField(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="mini" @click="listenerFormModelVisible = false">取 消</el-button>
<el-button size="mini" type="primary" @click="saveListenerConfig">保 存</el-button>
</div>
</el-drawer>
<!-- 注入西段 编辑/创建 部分 -->
<el-dialog title="字段配置" :visible.sync="listenerFieldFormModelVisible" width="600px" append-to-body destroy-on-close>
<el-form :model="listenerFieldForm" size="mini" label-width="96px" ref="listenerFieldFormRef" style="height: 136px" @submit.native.prevent>
<el-form-item label="字段名称:" prop="name" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-input v-model="listenerFieldForm.name" clearable />
</el-form-item>
<el-form-item label="字段类型:" prop="fieldType" :rules="{ required: true, trigger: ['blur', 'change'] }">
<el-select v-model="listenerFieldForm.fieldType">
<el-option v-for="i in Object.keys(fieldTypeObject)" :key="i" :label="fieldTypeObject[i]" :value="i" />
</el-select>
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'string'"
label="字段值:"
prop="string"
key="field-string"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.string" clearable />
</el-form-item>
<el-form-item
v-if="listenerFieldForm.fieldType === 'expression'"
label="表达式:"
prop="expression"
key="field-expression"
:rules="{ required: true, trigger: ['blur', 'change'] }"
>
<el-input v-model="listenerFieldForm.expression" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" @click="listenerFieldFormModelVisible = false">取 消</el-button>
<el-button size="mini" type="primary" @click="saveListenerFiled">确 定</el-button>
</template>
</el-dialog>
</div>
`;
};

View File

@ -0,0 +1,62 @@
// 初始化表单数据
export function initListenerForm(listener) {
let self = {
...listener
};
if (listener.script) {
self = {
...listener,
...listener.script,
scriptType: listener.script.resource ? "externalScript" : "inlineScript"
};
}
if (listener.event === "timeout" && listener.eventDefinitions) {
if (listener.eventDefinitions.length) {
let k = "";
for (let key in listener.eventDefinitions[0]) {
console.log(listener.eventDefinitions, key);
if (key.indexOf("time") !== -1) {
k = key;
self.eventDefinitionType = key.replace("time", "").toLowerCase();
}
}
console.log(k);
self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
}
}
return self;
}
export function initListenerType(listener) {
let listenerType;
if (listener.class) listenerType = "classListener";
if (listener.expression) listenerType = "expressionListener";
if (listener.delegateExpression) listenerType = "delegateExpressionListener";
if (listener.script) listenerType = "scriptListener";
return {
...JSON.parse(JSON.stringify(listener)),
...(listener.script ?? {}),
listenerType: listenerType
};
}
export const listenerType = {
classListener: "Java 类",
expressionListener: "表达式",
delegateExpressionListener: "代理表达式",
scriptListener: "脚本"
};
export const eventType = {
create: "创建",
assignment: "指派",
complete: "完成",
delete: "删除",
update: "更新",
timeout: "超时"
};
export const fieldType = {
string: "字符串",
expression: "表达式"
};

View File

@ -0,0 +1,192 @@
<template>
<div class="panel-tab__content">
<el-form size="mini" label-width="90px" @submit.native.prevent>
<el-form-item label="回路特性">
<el-select v-model="loopCharacteristics" @change="changeLoopCharacteristicsType">
<!--bpmn:MultiInstanceLoopCharacteristics-->
<el-option label="并行多重事件" value="ParallelMultiInstance" />
<el-option label="时序多重事件" value="SequentialMultiInstance" />
<!--bpmn:StandardLoopCharacteristics-->
<el-option label="循环事件" value="StandardLoop" />
<el-option label="无" value="Null" />
</el-select>
</el-form-item>
<template v-if="loopCharacteristics === 'ParallelMultiInstance' || loopCharacteristics === 'SequentialMultiInstance'">
<el-form-item label="循环基数" key="loopCardinality">
<el-input v-model="loopInstanceForm.loopCardinality" clearable @change="updateLoopCardinality" />
</el-form-item>
<el-form-item label="集合" key="collection">
<el-input v-model="loopInstanceForm.collection" clearable @change="updateLoopBase" />
</el-form-item>
<el-form-item label="元素变量" key="elementVariable">
<el-input v-model="loopInstanceForm.elementVariable" clearable @change="updateLoopBase" />
</el-form-item>
<el-form-item label="完成条件" key="completionCondition">
<el-input v-model="loopInstanceForm.completionCondition" clearable @change="updateLoopCondition" />
</el-form-item>
<el-form-item label="异步状态" key="async">
<el-checkbox v-model="loopInstanceForm.asyncBefore" label="异步前" @change="updateLoopAsync('asyncBefore')" />
<el-checkbox v-model="loopInstanceForm.asyncAfter" label="异步后" @change="updateLoopAsync('asyncAfter')" />
<el-checkbox
v-model="loopInstanceForm.exclusive"
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
label="排除"
@change="updateLoopAsync('exclusive')"
/>
</el-form-item>
<el-form-item label="重试周期" prop="timeCycle" v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore" key="timeCycle">
<el-input v-model="loopInstanceForm.timeCycle" clearable @change="updateLoopTimeCycle" />
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
export default {
name: "ElementMultiInstance",
props: {
businessObject: Object,
type: String
},
inject: {
prefix: "prefix"
},
data() {
return {
loopCharacteristics: "",
//默认配置,用来覆盖原始不存在的选项,避免报错
defaultLoopInstanceForm: {
completionCondition: "",
loopCardinality: "",
extensionElements: [],
asyncAfter: false,
asyncBefore: false,
exclusive: false
},
loopInstanceForm: {}
};
},
watch: {
businessObject: {
immediate: true,
handler(val) {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.getElementLoop(val);
}
}
},
methods: {
getElementLoop(businessObject) {
if (!businessObject.loopCharacteristics) {
this.loopCharacteristics = "Null";
this.loopInstanceForm = {};
return;
}
if (businessObject.loopCharacteristics.$type === "bpmn:StandardLoopCharacteristics") {
this.loopCharacteristics = "StandardLoop";
this.loopInstanceForm = {};
return;
}
if (businessObject.loopCharacteristics.isSequential) {
this.loopCharacteristics = "SequentialMultiInstance";
} else {
this.loopCharacteristics = "ParallelMultiInstance";
}
// 合并配置
this.loopInstanceForm = {
...this.defaultLoopInstanceForm,
...businessObject.loopCharacteristics,
completionCondition: businessObject.loopCharacteristics?.completionCondition?.body ?? "",
loopCardinality: businessObject.loopCharacteristics?.loopCardinality?.body ?? ""
};
// 保留当前元素 businessObject 上的 loopCharacteristics 实例
this.multiLoopInstance = window.bpmnInstances.bpmnElement.businessObject.loopCharacteristics;
// 更新表单
if (
businessObject.loopCharacteristics.extensionElements &&
businessObject.loopCharacteristics.extensionElements.values &&
businessObject.loopCharacteristics.extensionElements.values.length
) {
this.$set(this.loopInstanceForm, "timeCycle", businessObject.loopCharacteristics.extensionElements.values[0].body);
}
},
changeLoopCharacteristicsType(type) {
// this.loopInstanceForm = { ...this.defaultLoopInstanceForm }; // 切换类型取消原表单配置
// 取消多实例配置
if (type === "Null") {
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { loopCharacteristics: null });
return;
}
// 配置循环
if (type === "StandardLoop") {
const loopCharacteristicsObject = window.bpmnInstances.moddle.create("bpmn:StandardLoopCharacteristics");
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
loopCharacteristics: loopCharacteristicsObject
});
this.multiLoopInstance = null;
return;
}
// 时序
if (type === "SequentialMultiInstance") {
this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: true });
} else {
this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics");
}
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
loopCharacteristics: this.multiLoopInstance
});
},
// 循环基数
updateLoopCardinality(cardinality) {
let loopCardinality = null;
if (cardinality && cardinality.length) {
loopCardinality = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: cardinality });
}
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, { loopCardinality });
},
// 完成条件
updateLoopCondition(condition) {
let completionCondition = null;
if (condition && condition.length) {
completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: condition });
}
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, { completionCondition });
},
// 重试周期
updateLoopTimeCycle(timeCycle) {
const extensionElements = window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
values: [
window.bpmnInstances.moddle.create(`${this.prefix}:FailedJobRetryTimeCycle`, {
body: timeCycle
})
]
});
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, { extensionElements });
},
// 直接更新的基础信息
updateLoopBase() {
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, {
collection: this.loopInstanceForm.collection || null,
elementVariable: this.loopInstanceForm.elementVariable || null
});
},
// 各异步状态
updateLoopAsync(key) {
const { asyncBefore, asyncAfter } = this.loopInstanceForm;
let asyncAttr = Object.create(null);
if (!asyncBefore && !asyncAfter) {
this.$set(this.loopInstanceForm, "exclusive", false);
asyncAttr = { asyncBefore: false, asyncAfter: false, exclusive: false, extensionElements: null };
} else {
asyncAttr[key] = this.loopInstanceForm[key];
}
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, asyncAttr);
}
},
beforeDestroy() {
this.multiLoopInstance = null;
this.bpmnElement = null;
}
};
</script>

View File

@ -0,0 +1,59 @@
<template>
<div class="panel-tab__content">
<div class="element-property input-property">
<div class="element-property__label">元素文档</div>
<div class="element-property__value">
<el-input
type="textarea"
v-model="documentation"
size="mini"
resize="vertical"
:autosize="{ minRows: 2, maxRows: 4 }"
@input="updateDocumentation"
@blur="updateDocumentation"
/>
</div>
</div>
</div>
</template>
<script>
export default {
name: "ElementOtherConfig",
props: {
id: String
},
data() {
return {
documentation: ""
};
},
watch: {
id: {
immediate: true,
handler: function(id) {
if (id && id.length) {
this.$nextTick(() => {
const documentations = window.bpmnInstances.bpmnElement.businessObject?.documentation;
this.documentation = documentations && documentations.length ? documentations[0].text : "";
});
} else {
this.documentation = "";
}
}
}
},
methods: {
updateDocumentation() {
(this.bpmnElement && this.bpmnElement.id === this.id) || (this.bpmnElement = window.bpmnInstances.elementRegistry.get(this.id));
const documentation = window.bpmnInstances.bpmnFactory.create("bpmn:Documentation", { text: this.documentation });
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
documentation: [documentation]
});
}
},
beforeDestroy() {
this.bpmnElement = null;
}
};
</script>

View File

@ -0,0 +1,134 @@
<template>
<div class="panel-tab__content">
<el-table :data="elementPropertyList" size="mini" max-height="240" border fit>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
<el-table-column label="操作" width="90px">
<template slot-scope="{ row, $index }">
<el-button size="mini" type="text" @click="openAttributesForm(row, $index)">编辑</el-button>
<el-divider direction="vertical" />
<el-button size="mini" type="text" style="color: #ff4d4f" @click="removeAttributes(row, $index)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openAttributesForm(null, -1)">添加属性</el-button>
</div>
<el-dialog :visible.sync="propertyFormModelVisible" title="属性配置" width="600px" append-to-body destroy-on-close>
<el-form :model="propertyForm" label-width="80px" size="mini" ref="attributeFormRef" @submit.native.prevent>
<el-form-item label="属性名:" prop="name">
<el-input v-model="propertyForm.name" clearable />
</el-form-item>
<el-form-item label="属性值:" prop="value">
<el-input v-model="propertyForm.value" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" @click="propertyFormModelVisible = false"> </el-button>
<el-button size="mini" type="primary" @click="saveAttribute"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
export default {
name: "ElementProperties",
props: {
id: String,
type: String
},
inject: {
prefix: "prefix",
width: "width"
},
data() {
return {
elementPropertyList: [],
propertyForm: {},
editingPropertyIndex: -1,
propertyFormModelVisible: false
};
},
watch: {
id: {
immediate: true,
handler(val) {
val && val.length && this.resetAttributesList();
}
}
},
methods: {
resetAttributesList() {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.otherExtensionList = []; // 其他扩展配置
this.bpmnElementProperties =
this.bpmnElement.businessObject?.extensionElements?.values?.filter(ex => {
if (ex.$type !== `${this.prefix}:Properties`) {
this.otherExtensionList.push(ex);
}
return ex.$type === `${this.prefix}:Properties`;
}) ?? [];
// 保存所有的 扩展属性字段
this.bpmnElementPropertyList = this.bpmnElementProperties.reduce((pre, current) => pre.concat(current.values), []);
// 复制 显示
this.elementPropertyList = JSON.parse(JSON.stringify(this.bpmnElementPropertyList ?? []));
},
openAttributesForm(attr, index) {
this.editingPropertyIndex = index;
this.propertyForm = index === -1 ? {} : JSON.parse(JSON.stringify(attr));
this.propertyFormModelVisible = true;
this.$nextTick(() => {
if (this.$refs["attributeFormRef"]) this.$refs["attributeFormRef"].clearValidate();
});
},
removeAttributes(attr, index) {
this.$confirm("确认移除该属性吗?", "提示", {
confirmButtonText: "确 认",
cancelButtonText: "取 消"
})
.then(() => {
this.elementPropertyList.splice(index, 1);
this.bpmnElementPropertyList.splice(index, 1);
// 新建一个属性字段的保存列表
const propertiesObject = window.bpmnInstances.moddle.create(`${this.prefix}:Properties`, {
values: this.bpmnElementPropertyList
});
this.updateElementExtensions(propertiesObject);
this.resetAttributesList();
})
.catch(() => console.info("操作取消"));
},
saveAttribute() {
const { name, value } = this.propertyForm;
if (this.editingPropertyIndex !== -1) {
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.bpmnElementPropertyList[this.editingPropertyIndex], {
name,
value
});
} else {
// 新建属性字段
const newPropertyObject = window.bpmnInstances.moddle.create(`${this.prefix}:Property`, { name, value });
// 新建一个属性字段的保存列表
const propertiesObject = window.bpmnInstances.moddle.create(`${this.prefix}:Properties`, {
values: this.bpmnElementPropertyList.concat([newPropertyObject])
});
this.updateElementExtensions(propertiesObject);
}
this.propertyFormModelVisible = false;
this.resetAttributesList();
},
updateElementExtensions(properties) {
const extensions = window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
values: this.otherExtensionList.concat([properties])
});
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
extensionElements: extensions
});
}
}
};
</script>

View File

@ -0,0 +1,104 @@
<template>
<div class="panel-tab__content">
<div class="panel-tab__content--title">
<span><i class="el-icon-menu" style="margin-right: 8px; color: #555555"></i>消息列表</span>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openModel('message')">创建新消息</el-button>
</div>
<el-table :data="messageList" size="mini" border>
<el-table-column type="index" label="序号" width="60px" />
<el-table-column label="消息ID" prop="id" max-width="300px" show-overflow-tooltip />
<el-table-column label="消息名称" prop="name" max-width="300px" show-overflow-tooltip />
</el-table>
<div class="panel-tab__content--title" style="padding-top: 8px; margin-top: 8px; border-top: 1px solid #eeeeee">
<span><i class="el-icon-menu" style="margin-right: 8px; color: #555555"></i>信号列表</span>
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openModel('signal')">创建新信号</el-button>
</div>
<el-table :data="signalList" size="mini" border>
<el-table-column type="index" label="序号" width="60px" />
<el-table-column label="信号ID" prop="id" max-width="300px" show-overflow-tooltip />
<el-table-column label="信号名称" prop="name" max-width="300px" show-overflow-tooltip />
</el-table>
<el-dialog :visible.sync="modelVisible" :title="modelConfig.title" :close-on-click-modal="false" width="400px" append-to-body destroy-on-close>
<el-form :model="modelObjectForm" size="mini" label-width="90px" @submit.native.prevent>
<el-form-item :label="modelConfig.idLabel">
<el-input v-model="modelObjectForm.id" clearable />
</el-form-item>
<el-form-item :label="modelConfig.nameLabel">
<el-input v-model="modelObjectForm.name" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" @click="modelVisible = false"> </el-button>
<el-button size="mini" type="primary" @click="addNewObject"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
export default {
name: "SignalAndMassage",
data() {
return {
signalList: [],
messageList: [],
modelVisible: false,
modelType: "",
modelObjectForm: {}
};
},
computed: {
modelConfig() {
if (this.modelType === "message") {
return { title: "创建消息", idLabel: "消息ID", nameLabel: "消息名称" };
} else {
return { title: "创建信号", idLabel: "信号ID", nameLabel: "信号名称" };
}
}
},
mounted() {
this.initDataList();
},
methods: {
initDataList() {
this.rootElements = window.bpmnInstances.modeler.getDefinitions().rootElements;
this.messageIdMap = {};
this.signalIdMap = {};
this.messageList = [];
this.signalList = [];
this.rootElements.forEach(el => {
if (el.$type === "bpmn:Message") {
this.messageIdMap[el.id] = true;
this.messageList.push({ ...el });
}
if (el.$type === "bpmn:Signal") {
this.signalIdMap[el.id] = true;
this.signalList.push({ ...el });
}
});
},
openModel(type) {
this.modelType = type;
this.modelObjectForm = {};
this.modelVisible = true;
},
addNewObject() {
if (this.modelType === "message") {
if (this.messageIdMap[this.modelObjectForm.id]) {
return this.$message.error("该消息已存在请修改id后重新保存");
}
const messageRef = window.bpmnInstances.moddle.create("bpmn:Message", this.modelObjectForm);
this.rootElements.push(messageRef);
} else {
if (this.signalIdMap[this.modelObjectForm.id]) {
return this.$message.error("该信号已存在请修改id后重新保存");
}
const signalRef = window.bpmnInstances.moddle.create("bpmn:Signal", this.modelObjectForm);
this.rootElements.push(signalRef);
}
this.modelVisible = false;
this.initDataList();
}
}
};
</script>

View File

@ -0,0 +1,72 @@
<template>
<div class="panel-tab__content">
<el-form size="mini" label-width="90px" @submit.native.prevent>
<!-- <el-form-item label="异步延续">-->
<!-- <el-checkbox v-model="taskConfigForm.asyncBefore" label="异步前" @change="changeTaskAsync" />-->
<!-- <el-checkbox v-model="taskConfigForm.asyncAfter" label="异步后" @change="changeTaskAsync" />-->
<!-- <el-checkbox v-model="taskConfigForm.exclusive" v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore" label="排除" @change="changeTaskAsync" />-->
<!-- </el-form-item>-->
<component :is="witchTaskComponent" v-bind="$props" />
</el-form>
</div>
</template>
<script>
import UserTask from "./task-components/UserTask";
import ScriptTask from "./task-components/ScriptTask";
import ReceiveTask from "./task-components/ReceiveTask";
export default {
name: "ElementTaskConfig",
components: { UserTask, ScriptTask, ReceiveTask },
props: {
id: String,
type: String
},
data() {
return {
taskConfigForm: {
asyncAfter: false,
asyncBefore: false,
exclusive: false
},
witchTaskComponent: "",
installedComponent: {
// 手工任务与普通任务一致,不需要其他配置
// 接收消息任务,需要在全局下插入新的消息实例,并在该节点下的 messageRef 属性绑定该实例
// 发送任务、服务任务、业务规则任务共用一个相同配置
UserTask: "UserTask", // 用户任务配置
ScriptTask: "ScriptTask", // 脚本任务配置
ReceiveTask: "ReceiveTask" // 消息接收任务
}
};
},
watch: {
id: {
immediate: true,
handler() {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.taskConfigForm.asyncBefore = this.bpmnElement?.businessObject?.asyncBefore;
this.taskConfigForm.asyncAfter = this.bpmnElement?.businessObject?.asyncAfter;
this.taskConfigForm.exclusive = this.bpmnElement?.businessObject?.exclusive;
}
},
type: {
immediate: true,
handler() {
this.witchTaskComponent = this.installedComponent[this.type];
}
}
},
methods: {
changeTaskAsync() {
if (!this.taskConfigForm.asyncBefore && !this.taskConfigForm.asyncAfter) {
this.taskConfigForm.exclusive = false;
}
window.bpmnInstances.modeling.updateProperties(window.bpmnInstances.bpmnElement, {
...this.taskConfigForm
});
}
}
};
</script>

View File

@ -0,0 +1,97 @@
<template>
<div style="margin-top: 16px">
<el-form-item label="消息实例">
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: nowrap">
<el-select v-model="bindMessageId" @change="updateTaskMessage">
<el-option v-for="id in Object.keys(messageMap)" :value="id" :label="messageMap[id]" :key="id" />
</el-select>
<el-button size="mini" type="primary" icon="el-icon-plus" style="margin-left: 8px" @click="openMessageModel" />
</div>
</el-form-item>
<el-dialog :visible.sync="messageModelVisible" :close-on-click-modal="false" title="创建新消息" width="400px" append-to-body destroy-on-close>
<el-form :model="newMessageForm" size="mini" label-width="90px" @submit.native.prevent>
<el-form-item label="消息ID">
<el-input v-model="newMessageForm.id" clearable />
</el-form-item>
<el-form-item label="消息名称">
<el-input v-model="newMessageForm.name" clearable />
</el-form-item>
</el-form>
<template slot="footer">
<el-button size="mini" type="primary" @click="createNewMessage"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script>
export default {
name: "ReceiveTask",
props: {
id: String,
type: String
},
data() {
return {
bindMessageId: "",
newMessageForm: {},
messageMap: {},
messageModelVisible: false
};
},
watch: {
id: {
immediate: true,
handler() {
this.$nextTick(() => this.getBindMessage());
}
}
},
created() {
this.bpmnMessageRefsMap = Object.create(null);
this.bpmnRootElements = window.bpmnInstances.modeler.getDefinitions().rootElements;
this.bpmnRootElements
.filter(el => el.$type === "bpmn:Message")
.forEach(m => {
this.bpmnMessageRefsMap[m.id] = m;
this.$set(this.messageMap, m.id, m.name);
});
this.$set(this.messageMap, "-1", "无"); // 添加一个空对象,保证可以取消原消息绑定
},
methods: {
getBindMessage() {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.bindMessageId = this.bpmnElement.businessObject?.messageRef?.id || "-1";
},
openMessageModel() {
this.messageModelVisible = true;
this.newMessageForm = {};
},
createNewMessage() {
if (this.messageMap[this.newMessageForm.id]) {
this.$message.error("该消息已存在请修改id后重新保存");
return;
}
const newMessage = window.bpmnInstances.moddle.create("bpmn:Message", this.newMessageForm);
this.bpmnRootElements.push(newMessage);
this.$set(this.messageMap, this.newMessageForm.id, this.newMessageForm.name);
this.bpmnMessageRefsMap[this.newMessageForm.id] = newMessage;
this.messageModelVisible = false;
},
updateTaskMessage(messageId) {
if (messageId === "-1") {
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
messageRef: null
});
} else {
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
messageRef: this.bpmnMessageRefsMap[messageId]
});
}
}
},
beforeDestroy() {
this.bpmnElement = null;
}
};
</script>

View File

@ -0,0 +1,85 @@
<template>
<div style="margin-top: 16px">
<el-form-item label="脚本格式">
<el-input v-model="scriptTaskForm.scriptFormat" clearable @input="updateElementTask()" @change="updateElementTask()" />
</el-form-item>
<el-form-item label="脚本类型">
<el-select v-model="scriptTaskForm.scriptType">
<el-option label="内联脚本" value="inline" />
<el-option label="外部资源" value="external" />
</el-select>
</el-form-item>
<el-form-item label="脚本" v-show="scriptTaskForm.scriptType === 'inline'">
<el-input
v-model="scriptTaskForm.script"
type="textarea"
resize="vertical"
:autosize="{ minRows: 2, maxRows: 4 }"
clearable
@input="updateElementTask()"
@change="updateElementTask()"
/>
</el-form-item>
<el-form-item label="资源地址" v-show="scriptTaskForm.scriptType === 'external'">
<el-input v-model="scriptTaskForm.resource" clearable @input="updateElementTask()" @change="updateElementTask()" />
</el-form-item>
<el-form-item label="结果变量">
<el-input v-model="scriptTaskForm.resultVariable" clearable @input="updateElementTask()" @change="updateElementTask()" />
</el-form-item>
</div>
</template>
<script>
export default {
name: "ScriptTask",
props: {
id: String,
type: String
},
data() {
return {
defaultTaskForm: {
scriptFormat: "",
script: "",
resource: "",
resultVariable: ""
},
scriptTaskForm: {}
};
},
watch: {
id: {
immediate: true,
handler() {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.$nextTick(() => this.resetTaskForm());
}
}
},
methods: {
resetTaskForm() {
for (let key in this.defaultTaskForm) {
let value = this.bpmnElement?.businessObject[key] || this.defaultTaskForm[key];
this.$set(this.scriptTaskForm, key, value);
}
this.$set(this.scriptTaskForm, "scriptType", this.scriptTaskForm.script ? "inline" : "external");
},
updateElementTask() {
let taskAttr = Object.create(null);
taskAttr.scriptFormat = this.scriptTaskForm.scriptFormat || null;
taskAttr.resultVariable = this.scriptTaskForm.resultVariable || null;
if (this.scriptTaskForm.scriptType === "inline") {
taskAttr.script = this.scriptTaskForm.script || null;
taskAttr.resource = null;
} else {
taskAttr.resource = this.scriptTaskForm.resource || null;
taskAttr.script = null;
}
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);
}
},
beforeDestroy() {
this.bpmnElement = null;
}
};
</script>

View File

@ -0,0 +1,491 @@
<template>
<div>
<el-row>
<h4><b>审批人设置</b></h4>
<el-radio-group v-model="dataType" @change="changeDataType">
<el-radio label="USERS">指定用户</el-radio>
<el-radio label="ROLES">角色</el-radio>
<el-radio label="DEPTS">部门</el-radio>
<el-radio label="INITIATOR">发起人</el-radio>
</el-radio-group>
</el-row>
<el-row>
<div v-if="dataType === 'USERS'">
<el-tag v-for="userText in selectedUser.text" :key="userText" effect="plain">
{{userText}}
</el-tag>
<div class="element-drawer__button">
<el-button size="mini" type="primary" icon="el-icon-plus" @click="onSelectUsers()">添加用户</el-button>
</div>
</div>
<div v-if="dataType === 'ROLES'">
<el-select v-model="roleIds" multiple size="mini" placeholder="请选择 角色" @change="changeSelectRoles">
<el-option
v-for="item in roleOptions"
:key="item.roleId"
:label="item.roleName"
:value="`ROLE${item.roleId}`"
:disabled="item.status === 1">
</el-option>
</el-select>
</div>
<div v-if="dataType === 'DEPTS'">
<tree-select
:width="320"
:height="400"
size="mini"
:data="deptTreeData"
:defaultProps="deptProps"
multiple
clearable
checkStrictly
nodeKey="id"
:checkedKeys="deptIds"
@checked-change="checkedDeptChange">
</tree-select>
</div>
</el-row>
<el-row>
<div v-show="showMultiFlog">
<el-divider />
<h4><b>多实例审批方式</b></h4>
<el-row>
<el-radio-group v-model="multiLoopType" @change="changeMultiLoopType">
<el-row><el-radio label="Null">无</el-radio></el-row>
<el-row><el-radio label="SequentialMultiInstance">会签(需所有审批人同意)</el-radio></el-row>
<el-row><el-radio label="ParallelMultiInstance">或签(一名审批人同意即可)</el-radio></el-row>
</el-radio-group>
</el-row>
<el-row v-if="multiLoopType !== 'Null'">
<el-tooltip content="开启后,实例需按顺序轮流审批" placement="top-start" @click.stop.prevent>
<i class="header-icon el-icon-info"></i>
</el-tooltip>
<span class="custom-label">顺序审批:</span>
<el-switch v-model="isSequential" @change="changeMultiLoopType" />
</el-row>
</div>
</el-row>
<!-- 候选用户弹窗 -->
<el-dialog title="候选用户" :visible.sync="userOpen" width="60%" append-to-body>
<el-row type="flex" :gutter="20">
<!--部门数据-->
<el-col :span="7">
<el-card shadow="never" style="height: 100%">
<div slot="header">
<span>部门列表</span>
</div>
<div class="head-container">
<el-input
v-model="deptName"
placeholder="请输入部门名称"
clearable
size="small"
prefix-icon="el-icon-search"
style="margin-bottom: 20px"
/>
<el-tree
:data="deptOptions"
:props="deptProps"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="tree"
default-expand-all
@node-click="handleNodeClick"
/>
</div>
</el-card>
</el-col>
<el-col :span="17">
<el-table ref="multipleTable" height="600" :data="userTableList" border @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="用户名" align="center" prop="nickName" />
<el-table-column label="部门" align="center" prop="dept.deptName" />
</el-table>
<pagination
:total="userTotal"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getUserList"
/>
</el-col>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleTaskUserComplete"> </el-button>
<el-button @click="userOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listUser, deptTreeSelect } from "@/api/system/user";
import { listRole } from "@/api/system/role";
import TreeSelect from "@/components/TreeSelect";
const userTaskForm = {
dataType: '',
assignee: '',
candidateUsers: '',
candidateGroups: '',
text: '',
// dueDate: '',
// followUpDate: '',
// priority: ''
}
export default {
name: "UserTask",
props: {
id: String,
type: String
},
components: { TreeSelect },
data() {
return {
loading: false,
dataType: 'USERS',
selectedUser: {
ids: [],
text: []
},
userOpen: false,
deptName: undefined,
deptOptions: [],
deptProps: {
children: "children",
label: "label"
},
deptTempOptions: [],
userTableList: [],
userTotal: 0,
selectedUserDate: [],
roleOptions: [],
roleIds: [],
deptTreeData: [],
deptIds: [],
// 查询参数
queryParams: {
deptId: undefined
},
showMultiFlog: false,
isSequential: false,
multiLoopType: 'Null',
};
},
watch: {
id: {
immediate: true,
handler() {
this.bpmnElement = window.bpmnInstances.bpmnElement;
this.$nextTick(() => this.resetTaskForm());
}
}
},
beforeDestroy() {
this.bpmnElement = null;
},
methods: {
resetTaskForm() {
const bpmnElementObj = this.bpmnElement?.businessObject;
if (!bpmnElementObj) {
return;
}
this.clearOptionsData()
this.dataType = bpmnElementObj['dataType'];
if (this.dataType === 'USERS') {
let userIdData = bpmnElementObj['candidateUsers'] || bpmnElementObj['assignee'];
let userText = bpmnElementObj['text'] || [];
if (userIdData && userIdData.toString().length > 0 && userText && userText.length > 0) {
this.selectedUser.ids = userIdData?.toString().split(',');
this.selectedUser.text = userText?.split(',');
}
if (this.selectedUser.ids.length > 1) {
this.showMultiFlog = true;
}
} else if (this.dataType === 'ROLES') {
this.getRoleOptions();
let roleIdData = bpmnElementObj['candidateGroups'] || [];
if (roleIdData && roleIdData.length > 0) {
this.roleIds = roleIdData.split(',')
}
this.showMultiFlog = true;
} else if (this.dataType === 'DEPTS') {
this.getDeptTreeData().then(() => {
let deptIdData = bpmnElementObj['candidateGroups'] || [];
if (deptIdData && deptIdData.length > 0) {
this.deptIds = deptIdData.split(',');
}
});
this.showMultiFlog = true;
}
this.getElementLoop(bpmnElementObj);
},
/**
* 清空选项数据
*/
clearOptionsData() {
this.selectedUser.ids = [];
this.selectedUser.text = [];
this.roleIds = [];
this.deptIds = [];
},
/**
* 跟新节点数据
*/
updateElementTask() {
const taskAttr = Object.create(null);
for (let key in userTaskForm) {
taskAttr[key] = userTaskForm[key];
}
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);
},
/**
* 查询部门下拉树结构
*/
getDeptOptions() {
return new Promise((resolve, reject) => {
if (!this.deptOptions || this.deptOptions.length <= 0) {
deptTreeSelect().then(response => {
this.deptTempOptions = response.data;
this.deptOptions = response.data;
resolve()
})
} else {
reject()
}
});
},
/**
* 查询部门下拉树结构(含部门前缀)
*/
getDeptTreeData() {
function refactorTree(data) {
return data.map(node => {
let treeData = { id: `DEPT${node.id}`, label: node.label, parentId: node.parentId, weight: node.weight };
if (node.children && node.children.length > 0) {
treeData.children = refactorTree(node.children);
}
return treeData;
});
}
return new Promise((resolve, reject) => {
if (!this.deptTreeData || this.deptTreeData.length <= 0) {
this.getDeptOptions().then(() => {
this.deptTreeData = refactorTree(this.deptOptions);
resolve()
}).catch(() => {
reject()
})
} else {
resolve()
}
})
},
/**
* 查询部门下拉树结构
*/
getRoleOptions() {
if (!this.roleOptions || this.roleOptions.length <= 0) {
listRole().then(response => this.roleOptions = response.rows);
}
},
/** 查询用户列表 */
getUserList() {
listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.userTableList = response.rows;
this.userTotal = response.total;
});
},
// 筛选节点
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
// 节点单击事件
handleNodeClick(data) {
this.queryParams.deptId = data.id;
this.getUserList();
},
// 关闭标签
handleClose(tag) {
this.selectedUserDate.splice(this.selectedUserDate.indexOf(tag), 1);
this.$refs.multipleTable.toggleRowSelection(tag);
},
// 多选框选中数据
handleSelectionChange(selection) {
this.selectedUserDate = selection;
},
onSelectUsers() {
this.selectedUserDate = []
this.$refs.multipleTable?.clearSelection();
this.getDeptOptions();
this.userOpen = true;
},
handleTaskUserComplete() {
if (!this.selectedUserDate || this.selectedUserDate.length <= 0) {
this.$modal.msgError('请选择用户');
return;
}
userTaskForm.dataType = 'USERS';
this.selectedUser.text = this.selectedUserDate.map(k => k.nickName) || [];
if (this.selectedUserDate.length === 1) {
let data = this.selectedUserDate[0];
userTaskForm.assignee = data.userId;
userTaskForm.text = data.nickName;
userTaskForm.candidateUsers = null;
this.showMultiFlog = false;
this.multiLoopType = 'Null';
this.changeMultiLoopType(this.multiLoopType);
} else {
userTaskForm.candidateUsers = this.selectedUserDate.map(k => k.userId).join() || null;
userTaskForm.text = this.selectedUserDate.map(k => k.nickName).join() || null;
userTaskForm.assignee = null;
this.showMultiFlog = true;
}
this.updateElementTask()
this.userOpen = false;
},
changeSelectRoles(val) {
userTaskForm.dataType = 'ROLES';
userTaskForm.candidateGroups = val.join() || null;
let textArr = this.roleOptions.filter(k => val.indexOf(`ROLE${k.roleId}`) >= 0);
userTaskForm.text = textArr?.map(k => k.roleName).join() || null;
this.updateElementTask();
},
checkedDeptChange(checkedIds, checkedData) {
userTaskForm.dataType = 'DEPTS';
if (checkedIds && checkedIds.length > 0) {
this.deptIds = checkedIds;
}
if (checkedData && checkedData.length > 0) {
userTaskForm.candidateGroups = checkedData.map(k => k.id).join() || null
userTaskForm.text = checkedData.map(k => k.label).join() || null
this.updateElementTask();
}
},
changeDataType(val) {
// 清空 userTaskForm 所有属性值
Object.keys(userTaskForm).forEach(key => userTaskForm[key] = null);
userTaskForm.dataType = val;
if (val === 'USERS') {
if (this.selectedUser && this.selectedUser.ids && this.selectedUser.ids.length > 0) {
if (this.selectedUser.ids.length === 1) {
userTaskForm.assignee = this.selectedUser.ids[0];
} else {
userTaskForm.candidateUsers = this.selectedUser.ids.join()
}
userTaskForm.text = this.selectedUser.text?.join() || null
}
} else if (val === 'ROLES') {
this.getRoleOptions();
if (this.roleIds && this.roleIds.length > 0) {
userTaskForm.candidateGroups = this.roleIds.join() || null;
let textArr = this.roleOptions.filter(k => this.roleIds.indexOf(`ROLE${k.roleId}`) >= 0);
userTaskForm.text = textArr?.map(k => k.roleName).join() || null;
}
} else if (val === 'DEPTS') {
this.getDeptTreeData();
if (this.deptIds && this.deptIds.length > 0) {
userTaskForm.candidateGroups = this.deptIds.join() || null;
let textArr = []
let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
this.deptIds.forEach(id => {
let stark = []
stark = stark.concat(treeStarkData);
while(stark.length) {
let temp = stark.shift();
if(temp.children) {
stark = temp.children.concat(stark);
}
if(id === temp.id) {
textArr.push(temp);
}
}
})
userTaskForm.text = textArr?.map(k => k.label).join() || null;
}
} else if (val === 'INITIATOR') {
userTaskForm.assignee = "${initiator}";
userTaskForm.text = "流程发起人";
}
this.updateElementTask();
if (val === 'ROLES' || val === 'DEPTS' || (val === 'USERS' && this.selectedUser.ids.length > 1)) {
this.showMultiFlog = true;
} else {
this.showMultiFlog = false;
}
this.multiLoopType = 'Null';
this.changeMultiLoopType(this.multiLoopType);
},
getElementLoop(businessObject) {
if (!businessObject.loopCharacteristics) {
this.multiLoopType = "Null";
return;
}
this.isSequential = businessObject.loopCharacteristics.isSequential;
if (businessObject.loopCharacteristics.completionCondition) {
if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances >= nrOfInstances}") {
this.multiLoopType = "SequentialMultiInstance";
} else {
this.multiLoopType = "ParallelMultiInstance";
}
}
},
changeMultiLoopType(type) {
// 取消多实例配置
if (type === "Null") {
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { loopCharacteristics: null });
return;
}
this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: this.isSequential });
// 更新多实例配置
window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
loopCharacteristics: this.multiLoopInstance,
assignee: '${assignee}'
});
// 完成条件
let completionCondition = null;
// 会签
if (type === "SequentialMultiInstance") {
completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances >= nrOfInstances}" });
}
// 或签
if (type === "ParallelMultiInstance") {
completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances > 0}" });
}
// 更新模块属性信息
window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, {
collection: '${multiInstanceHandler.getUserIds(execution)}',
elementVariable: 'assignee',
completionCondition
});
},
}
};
</script>
<style scoped lang="scss">
.el-row .el-radio-group {
margin-bottom: 15px;
.el-radio {
line-height: 28px;
}
}
.el-tag {
margin-bottom: 10px;
+ .el-tag {
margin-left: 10px;
}
}
.custom-label {
padding-left: 5px;
font-weight: 500;
font-size: 14px;
color: #606266;
}
</style>

View File

@ -0,0 +1,63 @@
/* 改变主题色变量 */
// $--color-primary: #1890ff;
// $--color-danger: #ff4d4f;
/* 改变 icon 字体路径变量,必需 */
.process-drawer .el-drawer__header {
padding: 16px 16px 8px 16px;
margin: 0;
line-height: 24px;
font-size: 18px;
color: #303133;
box-sizing: border-box;
border-bottom: 1px solid #e8e8e8;
}
div[class^="el-drawer"]:focus,
span:focus {
outline: none;
}
.process-drawer .el-drawer__body {
box-sizing: border-box;
padding: 16px;
width: 100%;
overflow-y: auto;
}
.process-design {
.el-table td,
.el-table th {
color: #333;
}
.el-dialog__header {
padding: 16px 16px 8px 16px;
box-sizing: border-box;
border-bottom: 1px solid #e8e8e8;
}
.el-dialog__body {
padding: 16px;
max-height: 80vh;
box-sizing: border-box;
overflow-y: auto;
}
.el-dialog__footer {
padding: 16px;
box-sizing: border-box;
border-top: 1px solid #e8e8e8;
}
.el-dialog__close {
font-weight: 600;
}
.el-select {
width: 100%;
}
.el-divider:not(.el-divider--horizontal) {
margin: 0 8px ;
}
.el-divider.el-divider--horizontal {
margin: 16px 0;
}
}

View File

@ -0,0 +1,171 @@
@import "./flow-element-variables.scss";
@import "bpmn-js-token-simulation/assets/css/bpmn-js-token-simulation.css";
@import "bpmn-js-token-simulation/assets/css/font-awesome.min.css";
@import "bpmn-js-token-simulation/assets/css/normalize.css";
@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 "./process-designer.scss";
@import "./process-panel.scss";
$success-color: #4eb819;
$primary-color: #409EFF;
$warning-color: #E6A23C;
$danger-color: #F56C6C;
$cancel-color: #909399;
.process-viewer {
position: relative;
border: 1px solid #EFEFEF;
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+') repeat!important;
.success-arrow {
fill: $success-color;
stroke: $success-color;
}
.success-conditional {
fill: white;
stroke: $success-color;
}
.fail-arrow {
fill: $warning-color;
stroke: $warning-color;
}
.fail-conditional {
fill: white;
stroke: $warning-color;
}
.success.djs-connection {
.djs-visual path {
stroke: $success-color!important;
marker-end: url(#sequenceflow-end-white-success)!important;
}
}
.success.djs-connection.condition-expression {
.djs-visual path {
marker-start: url(#conditional-flow-marker-white-success)!important;
}
}
.success.djs-shape {
.djs-visual rect {
stroke: $success-color!important;
fill: $success-color!important;
fill-opacity: 0.15!important;
}
.djs-visual polygon {
stroke: $success-color!important;
}
.djs-visual path:nth-child(2) {
stroke: $success-color!important;
fill: $success-color!important;
}
.djs-visual circle {
stroke: $success-color!important;
fill: $success-color!important;
fill-opacity: 0.15!important;
}
}
.primary.djs-shape {
.djs-visual rect {
stroke: $primary-color!important;
fill: $primary-color!important;
fill-opacity: 0.15!important;
}
.djs-visual polygon {
stroke: $primary-color!important;
}
.djs-visual circle {
stroke: $primary-color!important;
fill: $primary-color!important;
fill-opacity: 0.15!important;
}
}
.warning.djs-connection {
.djs-visual path {
stroke: $warning-color!important;
marker-end: url(#sequenceflow-end-white-fail)!important;
}
}
.warning.djs-connection.condition-expression {
.djs-visual path {
marker-start: url(#conditional-flow-marker-white-fail)!important;
}
}
.warning.djs-shape {
.djs-visual rect {
stroke: $warning-color!important;
fill: $warning-color!important;
fill-opacity: 0.15!important;
}
.djs-visual polygon {
stroke: $warning-color!important;
}
.djs-visual path:nth-child(2) {
stroke: $warning-color!important;
fill: $warning-color!important;
}
.djs-visual circle {
stroke: $warning-color!important;
fill: $warning-color!important;
fill-opacity: 0.15!important;
}
}
.danger.djs-shape {
.djs-visual rect {
stroke: $danger-color!important;
fill: $danger-color!important;
fill-opacity: 0.15!important;
}
.djs-visual polygon {
stroke: $danger-color!important;
}
.djs-visual circle {
stroke: $danger-color!important;
fill: $danger-color!important;
fill-opacity: 0.15!important;
}
}
.cancel.djs-shape {
.djs-visual rect {
stroke: $cancel-color!important;
fill: $cancel-color!important;
fill-opacity: 0.15!important;
}
.djs-visual polygon {
stroke: $cancel-color!important;
}
.djs-visual circle {
stroke: $cancel-color!important;
fill: $cancel-color!important;
fill-opacity: 0.15!important;
}
}
}
.process-viewer .djs-tooltip-container, .process-viewer .djs-overlay-container, .process-viewer .djs-palette {
display: none;
}

View File

@ -0,0 +1,153 @@
// 边框被 token-simulation 样式覆盖了
.djs-palette {
background: var(--palette-background-color);
border: solid 1px var(--palette-border-color) !important;
border-radius: 2px;
}
.my-process-designer {
padding: 5px 0 10px 10px;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
box-sizing: border-box;
.my-process-designer__header {
width: 100%;
min-height: 36px;
.el-button {
text-align: center;
}
.el-button-group {
margin: 4px;
}
.el-tooltip__popper {
.el-button {
width: 100%;
text-align: left;
padding-left: 8px;
padding-right: 8px;
}
.el-button:hover {
background: rgba(64, 158, 255, 0.8);
color: #ffffff;
}
}
.align {
position: relative;
i {
&:after {
content: "|";
position: absolute;
transform: rotate(90deg) translate(200%, 60%);
}
}
}
.align.align-left i {
transform: rotate(90deg);
}
.align.align-right i {
transform: rotate(-90deg);
}
.align.align-top i {
transform: rotate(180deg);
}
.align.align-bottom i {
transform: rotate(0deg);
}
.align.align-center i {
transform: rotate(90deg);
&:after {
transform: rotate(90deg) translate(0, 60%);
}
}
.align.align-middle i {
transform: rotate(0deg);
&:after {
transform: rotate(90deg) translate(0, 60%);
}
}
}
.my-process-designer__container {
display: inline-flex;
width: 100%;
flex: 1;
.my-process-designer__canvas {
flex: 1;
height: 100%;
position: relative;
background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PHBhdHRlcm4gaWQ9ImEiIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHBhdGggZD0iTTAgMTBoNDBNMTAgMHY0ME0wIDIwaDQwTTIwIDB2NDBNMCAzMGg0ME0zMCAwdjQwIiBmaWxsPSJub25lIiBzdHJva2U9IiNlMGUwZTAiIG9wYWNpdHk9Ii4yIi8+PHBhdGggZD0iTTQwIDBIMHY0MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZTBlMGUwIi8+PC9wYXR0ZXJuPjwvZGVmcz48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJ1cmwoI2EpIi8+PC9zdmc+")
repeat !important;
div.toggle-mode {
display: none;
}
}
.my-process-designer__property-panel {
height: 100%;
overflow: scroll;
overflow-y: auto;
z-index: 10;
* {
box-sizing: border-box;
}
}
svg {
width: 100%;
height: 100%;
min-height: 100%;
overflow: hidden;
}
}
}
//侧边栏配置
.djs-palette.open {
.djs-palette-entries {
div[class^="bpmn-icon-"]:before,
div[class*="bpmn-icon-"]:before {
line-height: unset;
}
div.entry {
position: relative;
}
div.entry:hover {
&::after {
width: max-content;
content: attr(title);
vertical-align: text-bottom;
position: absolute;
right: -10px;
top: 0;
bottom: 0;
overflow: hidden;
transform: translateX(100%);
font-size: 0.5em;
display: inline-block;
text-decoration: inherit;
font-variant: normal;
text-transform: none;
background: #fafafa;
box-shadow: 0 0 6px #eeeeee;
border: 1px solid #cccccc;
box-sizing: border-box;
padding: 0 16px;
border-radius: 4px;
z-index: 100;
}
}
}
}
pre {
margin: 0;
height: 100%;
overflow: hidden;
max-height: calc(80vh - 32px);
overflow-y: auto;
}
.hljs {
word-break: break-word;
white-space: pre-wrap;
}
.hljs * {
font-family: Consolas, Monaco, monospace;
}

View File

@ -0,0 +1,110 @@
.process-design {
.process-panel__container {
box-sizing: border-box;
padding: 0 8px;
border-left: 1px solid #eeeeee;
box-shadow: 0 0 8px #cccccc;
max-height: 100%;
overflow-y: scroll;
}
.panel-tab__title {
font-weight: 600;
padding: 0 8px;
font-size: 1.1em;
line-height: 1.2em;
i {
margin-right: 8px;
font-size: 1.2em;
}
}
.panel-tab__content {
width: 100%;
box-sizing: border-box;
border-top: 1px solid #eeeeee;
padding: 8px 16px;
.panel-tab__content--title {
display: flex;
justify-content: space-between;
padding-bottom: 8px;
span {
flex: 1;
text-align: left;
}
}
}
.element-property {
width: 100%;
display: flex;
align-items: flex-start;
margin: 8px 0;
.element-property__label {
display: block;
width: 90px;
text-align: right;
overflow: hidden;
padding-right: 12px;
line-height: 32px;
font-size: 14px;
box-sizing: border-box;
}
.element-property__value {
flex: 1;
line-height: 32px;
}
.el-form-item {
width: 100%;
margin-bottom: 0;
padding-bottom: 18px;
}
}
.list-property {
flex-direction: column;
.element-listener-item {
width: 100%;
display: inline-grid;
grid-template-columns: 16px auto 32px 32px;
grid-column-gap: 8px;
}
.element-listener-item + .element-listener-item {
margin-top: 8px;
}
}
.listener-filed__title {
display: inline-flex;
width: 100%;
justify-content: space-between;
align-items: center;
margin-top: 0;
span {
width: 200px;
text-align: left;
font-size: 14px;
}
i {
margin-right: 8px;
}
}
.element-drawer__button {
margin-top: 8px;
width: 100%;
display: inline-flex;
justify-content: space-around;
}
.element-drawer__button > .el-button {
width: 100%;
}
.el-collapse-item__content {
padding-bottom: 0;
}
.el-input.is-disabled .el-input__inner {
color: #999999;
}
.el-form-item.el-form-item--mini {
margin-bottom: 0;
& + .el-form-item {
margin-top: 16px;
}
}
}

View File

@ -0,0 +1,69 @@
// 创建监听器实例
export function createListenerObject(options, isTask, prefix) {
const listenerObj = Object.create(null);
listenerObj.event = options.event;
isTask && (listenerObj.id = options.id); // 任务监听器特有的 id 字段
switch (options.listenerType) {
case "scriptListener":
listenerObj.script = createScriptObject(options, prefix);
break;
case "expressionListener":
listenerObj.expression = options.expression;
break;
case "delegateExpressionListener":
listenerObj.delegateExpression = options.delegateExpression;
break;
default:
listenerObj.class = options.class;
}
// 注入字段
if (options.fields) {
listenerObj.fields = options.fields.map(field => {
return createFieldObject(field, prefix);
});
}
// 任务监听器的 定时器 设置
if (isTask && options.event === "timeout" && !!options.eventDefinitionType) {
const timeDefinition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: options.eventTimeDefinitions });
const TimerEventDefinition = window.bpmnInstances.moddle.create("bpmn:TimerEventDefinition", {
id: `TimerEventDefinition_${uuid(8)}`,
[`time${options.eventDefinitionType.replace(/^\S/, s => s.toUpperCase())}`]: timeDefinition
});
listenerObj.eventDefinitions = [TimerEventDefinition];
}
return window.bpmnInstances.moddle.create(`${prefix}:${isTask ? "TaskListener" : "ExecutionListener"}`, listenerObj);
}
// 创建 监听器的注入字段 实例
export function createFieldObject(option, prefix) {
const { name, fieldType, string, expression } = option;
const fieldConfig = fieldType === "string" ? { name, string } : { name, expression };
return window.bpmnInstances.moddle.create(`${prefix}:Field`, fieldConfig);
}
// 创建脚本实例
export function createScriptObject(options, prefix) {
const { scriptType, scriptFormat, value, resource } = options;
const scriptConfig = scriptType === "inlineScript" ? { scriptFormat, value } : { scriptFormat, resource };
return window.bpmnInstances.moddle.create(`${prefix}:Script`, scriptConfig);
}
// 更新元素扩展属性
export function updateElementExtensions(element, extensionList) {
const extensions = window.bpmnInstances.moddle.create("bpmn:ExtensionElements", {
values: extensionList
});
window.bpmnInstances.modeling.updateProperties(element, {
extensionElements: extensions
});
}
// 创建一个id
export function uuid(length = 8, chars) {
let result = "";
let charsString = chars || "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (let i = length; i > 0; --i) {
result += charsString[Math.floor(Math.random() * charsString.length)];
}
return result;
}