refactor: 重构 bpmnProcessDesigner => bpmn-process-designer
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Collapse } from 'ant-design-vue';
|
||||
|
||||
import ElementCustomConfig from '#/components/bpmn-process-designer/package/penal/custom-config/ElementCustomConfig.vue';
|
||||
import ElementForm from '#/components/bpmn-process-designer/package/penal/form/ElementForm.vue';
|
||||
|
||||
import ElementBaseInfo from './base/ElementBaseInfo.vue';
|
||||
import FlowCondition from './flow-condition/FlowCondition.vue';
|
||||
import ElementListeners from './listeners/ElementListeners.vue';
|
||||
// import ElementForm from './form/ElementForm.vue'
|
||||
import UserTaskListeners from './listeners/UserTaskListeners.vue';
|
||||
import ElementMultiInstance from './multi-instance/ElementMultiInstance.vue';
|
||||
import ElementOtherConfig from './other/ElementOtherConfig.vue';
|
||||
import ElementProperties from './properties/ElementProperties.vue';
|
||||
import SignalAndMassage from './signal-message/SignalAndMessage.vue';
|
||||
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data';
|
||||
import ElementTask from './task/ElementTask.vue';
|
||||
import TimeEventConfig from './time-event-config/TimeEventConfig.vue';
|
||||
|
||||
defineOptions({ name: 'MyPropertiesPanel' });
|
||||
|
||||
/**
|
||||
* 侧边栏
|
||||
* @Author MiyueFE
|
||||
* @Home https://github.com/miyuesc
|
||||
* @Date 2021年3月31日18:57:51
|
||||
*/
|
||||
const props = defineProps({
|
||||
bpmnModeler: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'camunda',
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 480,
|
||||
},
|
||||
idEditDisabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
businessObject: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
}, // 流程模型的数据
|
||||
});
|
||||
|
||||
const CollapsePanel = Collapse.Panel;
|
||||
|
||||
const activeTab = ref('base');
|
||||
const elementId = ref('');
|
||||
const elementType = ref<any>('');
|
||||
const elementBusinessObject = ref<any>({}); // 元素 businessObject 镜像,提供给需要做判断的组件使用
|
||||
const conditionFormVisible = ref(false); // 流转条件设置
|
||||
const formVisible = ref(false); // 表单配置
|
||||
const bpmnElement = ref();
|
||||
const isReady = ref(false);
|
||||
|
||||
const type = ref('time');
|
||||
const condition = ref('');
|
||||
provide('prefix', props.prefix);
|
||||
provide('width', props.width);
|
||||
|
||||
// 初始化 bpmnInstances
|
||||
const initBpmnInstances = () => {
|
||||
if (!props.bpmnModeler) return false;
|
||||
try {
|
||||
const instances = {
|
||||
modeler: props.bpmnModeler,
|
||||
modeling: props.bpmnModeler.get('modeling'),
|
||||
moddle: props.bpmnModeler.get('moddle'),
|
||||
eventBus: props.bpmnModeler.get('eventBus'),
|
||||
bpmnFactory: props.bpmnModeler.get('bpmnFactory'),
|
||||
elementFactory: props.bpmnModeler.get('elementFactory'),
|
||||
elementRegistry: props.bpmnModeler.get('elementRegistry'),
|
||||
replace: props.bpmnModeler.get('replace'),
|
||||
selection: props.bpmnModeler.get('selection'),
|
||||
};
|
||||
|
||||
// 检查所有实例是否都存在
|
||||
const allInstancesExist = Object.values(instances).every(Boolean);
|
||||
if (allInstancesExist) {
|
||||
const w = window as any;
|
||||
w.bpmnInstances = instances;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('初始化 bpmnInstances 失败:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
// 监听 props.bpmnModeler 然后 initModels
|
||||
watch(
|
||||
() => props.bpmnModeler,
|
||||
async () => {
|
||||
// 避免加载时 流程图 并未加载完成
|
||||
if (!props.bpmnModeler) {
|
||||
// console.log('缺少props.bpmnModeler');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 等待 modeler 初始化完成
|
||||
await nextTick();
|
||||
if (initBpmnInstances()) {
|
||||
isReady.value = true;
|
||||
await nextTick();
|
||||
getActiveElement();
|
||||
} else {
|
||||
console.error('modeler 实例未完全初始化');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('初始化失败:', error);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const getActiveElement = () => {
|
||||
if (!isReady.value || !props.bpmnModeler) return;
|
||||
|
||||
// 初始第一个选中元素 bpmn:Process
|
||||
initFormOnChanged(null);
|
||||
props.bpmnModeler.on('import.done', (_: any) => {
|
||||
// console.log(e, 'eeeee');
|
||||
initFormOnChanged(null);
|
||||
});
|
||||
// 监听选择事件,修改当前激活的元素以及表单
|
||||
props.bpmnModeler.on(
|
||||
'selection.changed',
|
||||
({ newSelection }: { newSelection: any }) => {
|
||||
initFormOnChanged(newSelection[0] || null);
|
||||
},
|
||||
);
|
||||
props.bpmnModeler.on('element.changed', ({ element }: { element: any }) => {
|
||||
// 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。
|
||||
if (element && element.id === elementId.value) {
|
||||
initFormOnChanged(element);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化数据
|
||||
const initFormOnChanged = (element: any) => {
|
||||
if (!isReady.value || !bpmnInstances()) return;
|
||||
|
||||
let activatedElement = element;
|
||||
if (!activatedElement) {
|
||||
activatedElement =
|
||||
bpmnInstances().elementRegistry.find(
|
||||
(el: any) => el.type === 'bpmn:Process',
|
||||
) ??
|
||||
bpmnInstances().elementRegistry.find(
|
||||
(el: any) => el.type === 'bpmn:Collaboration',
|
||||
);
|
||||
}
|
||||
if (!activatedElement) return;
|
||||
|
||||
try {
|
||||
// console.log(`
|
||||
// ----------
|
||||
// select element changed:
|
||||
// id: ${activatedElement.id}
|
||||
// type: ${activatedElement.businessObject.$type}
|
||||
// ----------
|
||||
// `);
|
||||
// console.log('businessObject:', activatedElement.businessObject);
|
||||
bpmnInstances().bpmnElement = activatedElement;
|
||||
bpmnElement.value = activatedElement;
|
||||
elementId.value = activatedElement.id;
|
||||
elementType.value = activatedElement.type.split(':')[1] || '';
|
||||
elementBusinessObject.value = structuredClone(
|
||||
activatedElement.businessObject,
|
||||
);
|
||||
conditionFormVisible.value =
|
||||
elementType.value === 'SequenceFlow' &&
|
||||
activatedElement.source &&
|
||||
(activatedElement.source.type as string).includes('StartEvent');
|
||||
formVisible.value =
|
||||
elementType.value === 'UserTask' || elementType.value === 'StartEvent';
|
||||
} catch (error) {
|
||||
console.error('初始化表单数据失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const w = window as any;
|
||||
w.bpmnInstances = null;
|
||||
isReady.value = false;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => elementId.value,
|
||||
() => {
|
||||
activeTab.value = 'base';
|
||||
},
|
||||
);
|
||||
//
|
||||
// function updateNode() {
|
||||
// const moddle = window.bpmnInstances?.moddle;
|
||||
// const modeling = window.bpmnInstances?.modeling;
|
||||
// const elementRegistry = window.bpmnInstances?.elementRegistry;
|
||||
// if (!moddle || !modeling || !elementRegistry) return;
|
||||
//
|
||||
// const element = elementRegistry.get(props.businessObject.id);
|
||||
// if (!element) return;
|
||||
//
|
||||
// const timerDef = moddle.create('bpmn:TimerEventDefinition', {});
|
||||
// switch (type.value) {
|
||||
// case 'cycle': {
|
||||
// timerDef.timeCycle = moddle.create('bpmn:FormalExpression', {
|
||||
// body: condition.value,
|
||||
// });
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// case 'duration': {
|
||||
// timerDef.timeDuration = moddle.create('bpmn:FormalExpression', {
|
||||
// body: condition.value,
|
||||
// });
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// case 'time': {
|
||||
// timerDef.timeDate = moddle.create('bpmn:FormalExpression', {
|
||||
// body: condition.value,
|
||||
// });
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// // No default
|
||||
// }
|
||||
//
|
||||
// modeling.updateModdleProperties(element, element.businessObject, {
|
||||
// eventDefinitions: [timerDef],
|
||||
// });
|
||||
// }
|
||||
|
||||
// 初始化和监听
|
||||
function syncFromBusinessObject() {
|
||||
if (props.businessObject) {
|
||||
const timerDef = (props.businessObject.eventDefinitions || [])[0];
|
||||
if (timerDef) {
|
||||
if (timerDef.timeDate) {
|
||||
type.value = 'time';
|
||||
condition.value = timerDef.timeDate.body;
|
||||
} else if (timerDef.timeDuration) {
|
||||
type.value = 'duration';
|
||||
condition.value = timerDef.timeDuration.body;
|
||||
} else if (timerDef.timeCycle) {
|
||||
type.value = 'cycle';
|
||||
condition.value = timerDef.timeCycle.body;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(syncFromBusinessObject);
|
||||
watch(() => props.businessObject, syncFromBusinessObject, { deep: true });
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="process-panel__container"
|
||||
:style="{ width: `${width}px`, maxHeight: '600px' }"
|
||||
>
|
||||
<Collapse v-model:active-key="activeTab" v-if="isReady">
|
||||
<CollapsePanel key="base" header="常规">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:info-filled" />
|
||||
</template>
|
||||
<ElementBaseInfo
|
||||
:id-edit-disabled="idEditDisabled"
|
||||
:business-object="elementBusinessObject"
|
||||
:type="elementType"
|
||||
:model="model"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="message"
|
||||
header="消息与信号"
|
||||
v-if="elementType === 'Process'"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:comment" />
|
||||
</template>
|
||||
<SignalAndMassage />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="condition"
|
||||
header="流转条件"
|
||||
v-if="conditionFormVisible"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:promotion" />
|
||||
</template>
|
||||
<FlowCondition
|
||||
:business-object="elementBusinessObject"
|
||||
:type="elementType"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="form" header="表单" v-if="formVisible">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:list" />
|
||||
</template>
|
||||
<ElementForm :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="task"
|
||||
:header="getTaskCollapseItemName(elementType)"
|
||||
v-if="isTaskCollapseItemShow(elementType)"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:checked" />
|
||||
</template>
|
||||
<ElementTask :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="multiInstance"
|
||||
header="多人审批方式"
|
||||
v-if="elementType.includes('Task')"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:help-filled" />
|
||||
</template>
|
||||
<ElementMultiInstance
|
||||
:id="elementId"
|
||||
:business-object="elementBusinessObject"
|
||||
:type="elementType"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="listeners" header="执行监听器">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:bell-filled" />
|
||||
</template>
|
||||
<ElementListeners :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel
|
||||
key="taskListeners"
|
||||
header="任务监听器"
|
||||
v-if="elementType === 'UserTask'"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:bell-filled" />
|
||||
</template>
|
||||
<UserTaskListeners :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="extensions" header="扩展属性">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:circle-plus-filled" />
|
||||
</template>
|
||||
<ElementProperties :id="elementId" :type="elementType" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="other" header="其他">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:promotion" />
|
||||
</template>
|
||||
<ElementOtherConfig :id="elementId" />
|
||||
</CollapsePanel>
|
||||
<CollapsePanel key="customConfig" header="自定义配置">
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:tools" />
|
||||
</template>
|
||||
<ElementCustomConfig
|
||||
:id="elementId"
|
||||
:type="elementType"
|
||||
:business-object="elementBusinessObject"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
<!-- 新增的时间事件配置项 -->
|
||||
<CollapsePanel
|
||||
key="timeEvent"
|
||||
header="时间事件"
|
||||
v-if="elementType === 'IntermediateCatchEvent'"
|
||||
>
|
||||
<template #extra>
|
||||
<IconifyIcon icon="ep:timer" />
|
||||
</template>
|
||||
<TimeEventConfig
|
||||
:business-object="bpmnElement.value?.businessObject"
|
||||
:key="elementId"
|
||||
/>
|
||||
</CollapsePanel>
|
||||
</Collapse>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,221 @@
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, reactive, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { Form, FormItem, Input } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ElementBaseInfo' });
|
||||
|
||||
const props = defineProps<{
|
||||
businessObject?: BusinessObject;
|
||||
model?: Model;
|
||||
}>();
|
||||
|
||||
interface BusinessObject {
|
||||
id?: string;
|
||||
name?: string;
|
||||
$type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Model {
|
||||
key?: string;
|
||||
name?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const needProps = ref<Record<string, any>>({});
|
||||
const bpmnElement = ref<any>();
|
||||
const elementBaseInfo = ref<BusinessObject>({} as any);
|
||||
// 流程表单的下拉框的数据
|
||||
// const forms = ref([])
|
||||
// 流程模型的校验
|
||||
const rules = reactive<any>({
|
||||
id: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
const bpmnInstances = () =>
|
||||
(window as any)?.bpmnInstances as {
|
||||
bpmnElement: any;
|
||||
modeling: {
|
||||
updateProperties: (element: any, properties: any) => void;
|
||||
};
|
||||
};
|
||||
const resetBaseInfo = () => {
|
||||
// console.log(window, 'window');
|
||||
// console.log(bpmnElement.value, 'bpmnElement');
|
||||
|
||||
bpmnElement.value = bpmnInstances()?.bpmnElement;
|
||||
// console.log(bpmnElement.value, 'resetBaseInfo11111111111')
|
||||
if (bpmnElement.value?.businessObject) {
|
||||
elementBaseInfo.value = bpmnElement.value.businessObject;
|
||||
needProps.value.type = bpmnElement.value.businessObject.$type;
|
||||
}
|
||||
// elementBaseInfo.value['typess'] = bpmnElement.value.businessObject.$type
|
||||
|
||||
// elementBaseInfo.value = JSON.parse(JSON.stringify(bpmnElement.value.businessObject))
|
||||
// console.log(elementBaseInfo.value, 'elementBaseInfo22222222222')
|
||||
};
|
||||
const handleKeyUpdate = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const value = target.value;
|
||||
|
||||
// 校验 value 的值,只有 XML NCName 通过的情况下,才进行赋值。否则,会导致流程图报错,无法绘制的问题
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
if (!/[a-z_][-\w.$]*/i.test(value)) {
|
||||
// console.log('key 不满足 XML NCName 规则,所以不进行赋值');
|
||||
return;
|
||||
}
|
||||
// console.log('key 满足 XML NCName 规则,所以进行赋值');
|
||||
|
||||
// 在 BPMN 的 XML 中,流程标识 key,其实对应的是 id 节点
|
||||
elementBaseInfo.value.id = value;
|
||||
|
||||
setTimeout(() => {
|
||||
updateBaseInfo('id');
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const handleNameUpdate = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const value = target.value;
|
||||
|
||||
// console.log(elementBaseInfo, 'elementBaseInfo');
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
elementBaseInfo.value.name = value;
|
||||
|
||||
setTimeout(() => {
|
||||
updateBaseInfo('name');
|
||||
}, 100);
|
||||
};
|
||||
// const handleDescriptionUpdate=(value)=> {
|
||||
// TODO 芋艿:documentation 暂时无法修改,后续在看看
|
||||
// this.elementBaseInfo['documentation'] = value;
|
||||
// this.updateBaseInfo('documentation');
|
||||
// }
|
||||
const updateBaseInfo = (key: string) => {
|
||||
// console.log(key, 'key');
|
||||
// 触发 elementBaseInfo 对应的字段
|
||||
const attrObj: Record<string, any> = Object.create(null);
|
||||
// console.log(attrObj, 'attrObj')
|
||||
attrObj[key] = elementBaseInfo.value[key];
|
||||
// console.log(attrObj, 'attrObj111')
|
||||
// const attrObj = {
|
||||
// id: elementBaseInfo.value[key]
|
||||
// // di: { id: `${elementBaseInfo.value[key]}_di` }
|
||||
// }
|
||||
// console.log(elementBaseInfo, 'elementBaseInfo11111111111')
|
||||
needProps.value = { ...elementBaseInfo.value, ...needProps.value };
|
||||
|
||||
if (key === 'id') {
|
||||
// console.log('jinru')
|
||||
// console.log(window, 'window');
|
||||
// console.log(bpmnElement.value, 'bpmnElement');
|
||||
// console.log(toRaw(bpmnElement.value), 'bpmnElement');
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
id: elementBaseInfo.value[key],
|
||||
di: { id: `${elementBaseInfo.value[key]}_di` },
|
||||
});
|
||||
} else {
|
||||
// console.log(attrObj, 'attrObj');
|
||||
bpmnInstances().modeling.updateProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
attrObj,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
(val) => {
|
||||
// console.log(val, 'val11111111111111111111')
|
||||
if (val) {
|
||||
// nextTick(() => {
|
||||
resetBaseInfo();
|
||||
// })
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.model?.key,
|
||||
(val) => {
|
||||
// 针对上传的 bpmn 流程图时,保证 key 和 name 的更新
|
||||
if (val) {
|
||||
handleKeyUpdate(props.model?.key as any);
|
||||
handleNameUpdate(props.model?.name as any);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
// watch(
|
||||
// () => ({ ...props }),
|
||||
// (oldVal, newVal) => {
|
||||
// console.log(oldVal, 'oldVal')
|
||||
// console.log(newVal, 'newVal')
|
||||
// if (newVal) {
|
||||
// needProps.value = newVal
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// immediate: true
|
||||
// }
|
||||
// )
|
||||
// 'model.key': {
|
||||
// immediate: false,
|
||||
// handler: function (val) {
|
||||
// this.handleKeyUpdate(val)
|
||||
// }
|
||||
// }
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form :model="needProps" :rules="rules" layout="vertical">
|
||||
<div v-if="needProps.type === 'bpmn:Process'">
|
||||
<!-- 如果是 Process 信息的时候,使用自定义表单 -->
|
||||
<FormItem label="流程标识" name="id">
|
||||
<Input
|
||||
v-model:value="needProps.id"
|
||||
placeholder="请输入流标标识"
|
||||
:disabled="needProps.id !== undefined && needProps.id.length > 0"
|
||||
@change="handleKeyUpdate"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="流程名称" name="name">
|
||||
<Input
|
||||
v-model:value="needProps.name"
|
||||
placeholder="请输入流程名称"
|
||||
allow-clear
|
||||
@change="handleNameUpdate"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
<div v-else>
|
||||
<FormItem label="ID">
|
||||
<Input
|
||||
v-model:value="elementBaseInfo.id"
|
||||
allow-clear
|
||||
@change="updateBaseInfo('id')"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="名称">
|
||||
<Input
|
||||
v-model:value="elementBaseInfo.name"
|
||||
allow-clear
|
||||
@change="updateBaseInfo('name')"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { defineOptions, defineProps, ref, watch } from 'vue';
|
||||
|
||||
import { CustomConfigMap } from './data';
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
businessObject: {
|
||||
type: Object as () => BusinessObject,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
interface BusinessObject {
|
||||
eventDefinitions?: Array<{ $type: string }>;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
const customConfigComponent = ref<Component | null>(null);
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
() => {
|
||||
if (props.type && props.businessObject) {
|
||||
let val = props.type;
|
||||
if (props.businessObject.eventDefinitions) {
|
||||
val +=
|
||||
props.businessObject.eventDefinitions[0]?.$type.split(':')[1] || '';
|
||||
}
|
||||
// @ts-ignore
|
||||
customConfigComponent.value = (
|
||||
CustomConfigMap as Record<string, { component: Component }>
|
||||
)[val]?.component;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<component :is="customConfigComponent" v-bind="$props" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,307 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
defineOptions,
|
||||
defineProps,
|
||||
inject,
|
||||
nextTick,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
Divider,
|
||||
FormItem,
|
||||
InputNumber,
|
||||
RadioButton,
|
||||
RadioGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
Switch,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { convertTimeUnit } from '#/components/simple-process-design/components/nodes-config/utils';
|
||||
import {
|
||||
TIME_UNIT_TYPES,
|
||||
TIMEOUT_HANDLER_TYPES,
|
||||
TimeUnitType,
|
||||
} from '#/components/simple-process-design/consts';
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig4BoundaryEventTimer' });
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnInstances = () => (window as Record<string, any>)?.bpmnInstances;
|
||||
|
||||
const timeoutHandlerEnable = ref(false);
|
||||
const boundaryEventType = ref<any>();
|
||||
const timeoutHandlerType = ref<{
|
||||
value: number | undefined;
|
||||
}>({
|
||||
value: undefined,
|
||||
});
|
||||
const timeModdle = ref<any>();
|
||||
const timeDuration = ref(6);
|
||||
const timeUnit = ref(TimeUnitType.HOUR);
|
||||
const maxRemindCount = ref(1);
|
||||
|
||||
const elExtensionElements = ref<any>();
|
||||
const otherExtensions = ref<any[]>();
|
||||
const configExtensions = ref<any[]>([]);
|
||||
const eventDefinition = ref<any>();
|
||||
|
||||
const resetElement = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
eventDefinition.value = bpmnElement.value.businessObject.eventDefinitions[0];
|
||||
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
|
||||
// 是否开启自定义用户任务超时处理
|
||||
boundaryEventType.value = elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:BoundaryEventType`,
|
||||
)?.[0];
|
||||
if (boundaryEventType.value && boundaryEventType.value.value === 1) {
|
||||
timeoutHandlerEnable.value = true;
|
||||
configExtensions.value.push(boundaryEventType.value);
|
||||
}
|
||||
|
||||
// 执行动作
|
||||
timeoutHandlerType.value = elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:TimeoutHandlerType`,
|
||||
)?.[0];
|
||||
if (timeoutHandlerType.value) {
|
||||
configExtensions.value.push(timeoutHandlerType.value);
|
||||
if (eventDefinition.value.timeCycle) {
|
||||
const timeStr = eventDefinition.value.timeCycle.body;
|
||||
const maxRemindCountStr = timeStr.split('/')[0];
|
||||
const timeDurationStr = timeStr.split('/')[1];
|
||||
maxRemindCount.value = Number.parseInt(maxRemindCountStr.slice(1));
|
||||
timeDuration.value = Number.parseInt(timeDurationStr.slice(2, -1));
|
||||
timeUnit.value = convertTimeUnit(timeDurationStr.slice(-1));
|
||||
timeModdle.value = eventDefinition.value.timeCycle;
|
||||
}
|
||||
if (eventDefinition.value.timeDuration) {
|
||||
const timeDurationStr = eventDefinition.value.timeDuration.body;
|
||||
timeDuration.value = Number.parseInt(timeDurationStr.slice(2, -1));
|
||||
timeUnit.value = convertTimeUnit(timeDurationStr.slice(-1));
|
||||
timeModdle.value = eventDefinition.value.timeDuration;
|
||||
}
|
||||
}
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) =>
|
||||
ex.$type !== `${prefix}:BoundaryEventType` &&
|
||||
ex.$type !== `${prefix}:TimeoutHandlerType`,
|
||||
) ?? [];
|
||||
};
|
||||
|
||||
const timeoutHandlerChange = (checked: any) => {
|
||||
timeoutHandlerEnable.value = checked;
|
||||
if (checked) {
|
||||
// 启用自定义用户任务超时处理
|
||||
// 边界事件类型 --- 超时
|
||||
boundaryEventType.value = bpmnInstances().moddle.create(
|
||||
`${prefix}:BoundaryEventType`,
|
||||
{
|
||||
value: 1,
|
||||
},
|
||||
);
|
||||
configExtensions.value.push(boundaryEventType.value);
|
||||
// 超时处理类型
|
||||
timeoutHandlerType.value = bpmnInstances().moddle.create(
|
||||
`${prefix}:TimeoutHandlerType`,
|
||||
{
|
||||
value: 1,
|
||||
},
|
||||
);
|
||||
configExtensions.value.push(timeoutHandlerType.value);
|
||||
// 超时时间表达式
|
||||
timeDuration.value = 6;
|
||||
timeUnit.value = 2;
|
||||
maxRemindCount.value = 1;
|
||||
timeModdle.value = bpmnInstances().moddle.create(`bpmn:Expression`, {
|
||||
body: 'PT6H',
|
||||
});
|
||||
eventDefinition.value.timeDuration = timeModdle.value;
|
||||
} else {
|
||||
// 关闭自定义用户任务超时处理
|
||||
configExtensions.value = [];
|
||||
delete eventDefinition.value.timeDuration;
|
||||
delete eventDefinition.value.timeCycle;
|
||||
}
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const onTimeoutHandlerTypeChanged = () => {
|
||||
maxRemindCount.value = 1;
|
||||
updateElementExtensions();
|
||||
updateTimeModdle();
|
||||
};
|
||||
|
||||
const onTimeUnitChange = () => {
|
||||
// 分钟,默认是 60 分钟
|
||||
if (timeUnit.value === TimeUnitType.MINUTE) {
|
||||
timeDuration.value = 60;
|
||||
}
|
||||
// 小时,默认是 6 个小时
|
||||
if (timeUnit.value === TimeUnitType.HOUR) {
|
||||
timeDuration.value = 6;
|
||||
}
|
||||
// 天, 默认 1天
|
||||
if (timeUnit.value === TimeUnitType.DAY) {
|
||||
timeDuration.value = 1;
|
||||
}
|
||||
updateTimeModdle();
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateTimeModdle = () => {
|
||||
if (maxRemindCount.value > 1) {
|
||||
timeModdle.value.body = `R${maxRemindCount.value}/${isoTimeDuration()}`;
|
||||
if (!eventDefinition.value.timeCycle) {
|
||||
delete eventDefinition.value.timeDuration;
|
||||
eventDefinition.value.timeCycle = timeModdle.value;
|
||||
}
|
||||
} else {
|
||||
timeModdle.value.body = isoTimeDuration();
|
||||
if (!eventDefinition.value.timeDuration) {
|
||||
delete eventDefinition.value.timeCycle;
|
||||
eventDefinition.value.timeDuration = timeModdle.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isoTimeDuration = () => {
|
||||
let strTimeDuration = 'PT';
|
||||
if (timeUnit.value === TimeUnitType.MINUTE) {
|
||||
strTimeDuration += `${timeDuration.value}M`;
|
||||
}
|
||||
if (timeUnit.value === TimeUnitType.HOUR) {
|
||||
strTimeDuration += `${timeDuration.value}H`;
|
||||
}
|
||||
if (timeUnit.value === TimeUnitType.DAY) {
|
||||
strTimeDuration += `${timeDuration.value}D`;
|
||||
}
|
||||
return strTimeDuration;
|
||||
};
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [...(otherExtensions.value || []), ...configExtensions.value],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
resetElement();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Divider orientation="left">审批人超时未处理时</Divider>
|
||||
<FormItem label="启用开关" name="timeoutHandlerEnable">
|
||||
<Switch
|
||||
v-model:checked="timeoutHandlerEnable"
|
||||
checked-children="开启"
|
||||
un-checked-children="关闭"
|
||||
@change="timeoutHandlerChange"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="执行动作"
|
||||
name="timeoutHandlerType"
|
||||
v-if="timeoutHandlerEnable"
|
||||
>
|
||||
<RadioGroup
|
||||
v-model:value="timeoutHandlerType.value"
|
||||
@change="onTimeoutHandlerTypeChanged"
|
||||
>
|
||||
<RadioButton
|
||||
v-for="item in TIMEOUT_HANDLER_TYPES"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</RadioButton>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="超时时间设置" v-if="timeoutHandlerEnable">
|
||||
<span class="mr-2">当超过</span>
|
||||
<FormItem name="timeDuration">
|
||||
<InputNumber
|
||||
class="mr-2"
|
||||
:style="{ width: '100px' }"
|
||||
v-model:value="timeDuration"
|
||||
:min="1"
|
||||
:controls="true"
|
||||
@change="
|
||||
() => {
|
||||
updateTimeModdle();
|
||||
updateElementExtensions();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
<Select
|
||||
v-model:value="timeUnit"
|
||||
class="mr-2"
|
||||
:style="{ width: '100px' }"
|
||||
@change="onTimeUnitChange"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in TIME_UNIT_TYPES"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
未处理
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="最大提醒次数"
|
||||
name="maxRemindCount"
|
||||
v-if="timeoutHandlerEnable && timeoutHandlerType.value === 1"
|
||||
>
|
||||
<InputNumber
|
||||
v-model:value="maxRemindCount"
|
||||
:min="1"
|
||||
:max="10"
|
||||
@change="
|
||||
() => {
|
||||
updateTimeModdle();
|
||||
updateElementExtensions();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,783 @@
|
||||
<!-- UserTask 自定义配置:
|
||||
1. 审批人与提交人为同一人时
|
||||
2. 审批人拒绝时
|
||||
3. 审批人为空时
|
||||
4. 操作按钮
|
||||
5. 字段权限
|
||||
6. 审批类型
|
||||
7. 是否需要签名
|
||||
-->
|
||||
<script lang="ts" setup>
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
import type { ButtonSetting } from '#/components/simple-process-design/consts';
|
||||
|
||||
import { inject, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { BpmModelFormType } from '@vben/constants';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Form,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
Switch,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import {
|
||||
APPROVE_TYPE,
|
||||
ApproveType,
|
||||
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||
ASSIGN_START_USER_HANDLER_TYPES,
|
||||
AssignEmptyHandlerType,
|
||||
DEFAULT_BUTTON_SETTING,
|
||||
FieldPermissionType,
|
||||
OPERATION_BUTTON_NAME,
|
||||
REJECT_HANDLER_TYPES,
|
||||
RejectHandlerType,
|
||||
} from '#/components/simple-process-design/consts';
|
||||
import { useFormFieldsPermission } from '#/components/simple-process-design/helpers';
|
||||
|
||||
defineOptions({ name: 'ElementCustomConfig4UserTask' });
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
const assignStartUserHandlerTypeEl = ref<any>();
|
||||
const assignStartUserHandlerType = ref<any>();
|
||||
|
||||
// 审批人拒绝时
|
||||
const rejectHandlerTypeEl = ref<any>();
|
||||
const rejectHandlerType = ref<any>();
|
||||
const returnNodeIdEl = ref<any>();
|
||||
const returnNodeId = ref<any>();
|
||||
const returnTaskList = ref<any[]>([]);
|
||||
|
||||
// 审批人为空时
|
||||
const assignEmptyHandlerTypeEl = ref<any>();
|
||||
const assignEmptyHandlerType = ref<any>();
|
||||
const assignEmptyUserIdsEl = ref<any>();
|
||||
const assignEmptyUserIds = ref<any>();
|
||||
|
||||
// 操作按钮
|
||||
const buttonsSettingEl = ref<any>();
|
||||
const { btnDisplayNameEdit, changeBtnDisplayName, btnDisplayNameBlurEvent } =
|
||||
useButtonsSetting();
|
||||
|
||||
// 字段权限
|
||||
const fieldsPermissionEl = ref<any[]>([]);
|
||||
const { formType, fieldsPermissionConfig, getNodeConfigFormFields } =
|
||||
useFormFieldsPermission(FieldPermissionType.READ);
|
||||
|
||||
// 审批类型
|
||||
const approveType = ref({ value: ApproveType.USER });
|
||||
|
||||
// 是否需要签名
|
||||
const signEnable = ref({ value: false });
|
||||
|
||||
// 审批意见
|
||||
const reasonRequire = ref({ value: false });
|
||||
|
||||
const elExtensionElements = ref<any>();
|
||||
const otherExtensions = ref<any>();
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetCustomConfigList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
|
||||
// 获取可回退的列表
|
||||
returnTaskList.value = findAllPredecessorsExcludingStart(
|
||||
bpmnElement.value.id,
|
||||
bpmnInstances().modeler,
|
||||
);
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
|
||||
// 审批类型
|
||||
approveType.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:ApproveType`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:ApproveType`, {
|
||||
value: ApproveType.USER,
|
||||
});
|
||||
|
||||
// 审批人与提交人为同一人时
|
||||
assignStartUserHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignStartUserHandlerType`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignStartUserHandlerType`, {
|
||||
value: 1,
|
||||
});
|
||||
assignStartUserHandlerType.value = assignStartUserHandlerTypeEl.value.value;
|
||||
|
||||
// 审批人拒绝时
|
||||
rejectHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:RejectHandlerType`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:RejectHandlerType`, { value: 1 });
|
||||
rejectHandlerType.value = rejectHandlerTypeEl.value.value;
|
||||
returnNodeIdEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:RejectReturnTaskId`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:RejectReturnTaskId`, {
|
||||
value: '',
|
||||
});
|
||||
returnNodeId.value = returnNodeIdEl.value.value;
|
||||
|
||||
// 审批人为空时
|
||||
assignEmptyHandlerTypeEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignEmptyHandlerType`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyHandlerType`, {
|
||||
value: 1,
|
||||
});
|
||||
assignEmptyHandlerType.value = assignEmptyHandlerTypeEl.value.value;
|
||||
assignEmptyUserIdsEl.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:AssignEmptyUserIds`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:AssignEmptyUserIds`, {
|
||||
value: '',
|
||||
});
|
||||
assignEmptyUserIds.value = assignEmptyUserIdsEl.value.value
|
||||
?.split(',')
|
||||
.map((item: string) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
const num = Number(item);
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
|
||||
? item
|
||||
: num;
|
||||
});
|
||||
|
||||
// 操作按钮
|
||||
buttonsSettingEl.value = elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:ButtonsSetting`,
|
||||
);
|
||||
if (buttonsSettingEl.value.length === 0) {
|
||||
DEFAULT_BUTTON_SETTING.forEach((item) => {
|
||||
buttonsSettingEl.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:ButtonsSetting`, {
|
||||
'flowable:id': item.id,
|
||||
'flowable:displayName': item.displayName,
|
||||
'flowable:enable': item.enable,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 字段权限
|
||||
if (formType.value === BpmModelFormType.NORMAL) {
|
||||
const fieldsPermissionList = elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:FieldsPermission`,
|
||||
);
|
||||
fieldsPermissionEl.value = [];
|
||||
getNodeConfigFormFields();
|
||||
fieldsPermissionConfig.value.forEach((element: any) => {
|
||||
element.permission =
|
||||
fieldsPermissionList?.find((obj: any) => obj.field === element.field)
|
||||
?.permission ?? '1';
|
||||
fieldsPermissionEl.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:FieldsPermission`, element),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 是否需要签名
|
||||
signEnable.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:SignEnable`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false });
|
||||
|
||||
// 审批意见
|
||||
reasonRequire.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:ReasonRequire`,
|
||||
)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:ReasonRequire`, { value: false });
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value =
|
||||
elExtensionElements.value.values?.filter(
|
||||
(ex: any) =>
|
||||
ex.$type !== `${prefix}:AssignStartUserHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectHandlerType` &&
|
||||
ex.$type !== `${prefix}:RejectReturnTaskId` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyHandlerType` &&
|
||||
ex.$type !== `${prefix}:AssignEmptyUserIds` &&
|
||||
ex.$type !== `${prefix}:ButtonsSetting` &&
|
||||
ex.$type !== `${prefix}:FieldsPermission` &&
|
||||
ex.$type !== `${prefix}:ApproveType` &&
|
||||
ex.$type !== `${prefix}:SignEnable` &&
|
||||
ex.$type !== `${prefix}:ReasonRequire`,
|
||||
) ?? [];
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateAssignStartUserHandlerType = () => {
|
||||
assignStartUserHandlerTypeEl.value.value = assignStartUserHandlerType.value;
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateRejectHandlerType = () => {
|
||||
rejectHandlerTypeEl.value.value = rejectHandlerType.value;
|
||||
|
||||
returnNodeId.value = returnTaskList.value[0]?.id;
|
||||
returnNodeIdEl.value.value = returnNodeId.value;
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateReturnNodeId = () => {
|
||||
returnNodeIdEl.value.value = returnNodeId.value;
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateAssignEmptyHandlerType = () => {
|
||||
assignEmptyHandlerTypeEl.value.value = assignEmptyHandlerType.value;
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateAssignEmptyUserIds = () => {
|
||||
assignEmptyUserIdsEl.value.value = assignEmptyUserIds.value.toString();
|
||||
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
assignStartUserHandlerTypeEl.value,
|
||||
rejectHandlerTypeEl.value,
|
||||
returnNodeIdEl.value,
|
||||
assignEmptyHandlerTypeEl.value,
|
||||
assignEmptyUserIdsEl.value,
|
||||
approveType.value,
|
||||
...buttonsSettingEl.value,
|
||||
...fieldsPermissionEl.value,
|
||||
signEnable.value,
|
||||
reasonRequire.value,
|
||||
],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
resetCustomConfigList();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function findAllPredecessorsExcludingStart(elementId: string, modeler: any) {
|
||||
const elementRegistry = modeler.get('elementRegistry');
|
||||
const allConnections = elementRegistry.filter(
|
||||
(element: any) => element.type === 'bpmn:SequenceFlow',
|
||||
);
|
||||
const predecessors = new Set(); // 使用 Set 来避免重复节点
|
||||
const visited = new Set(); // 用于记录已访问的节点
|
||||
|
||||
// 检查是否是开始事件节点
|
||||
function isStartEvent(element: any) {
|
||||
return element.type === 'bpmn:StartEvent';
|
||||
}
|
||||
|
||||
function findPredecessorsRecursively(element: any) {
|
||||
// 如果该节点已经访问过,直接返回,避免循环
|
||||
if (visited.has(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 标记当前节点为已访问
|
||||
visited.add(element);
|
||||
|
||||
// 获取与当前节点相连的所有连接
|
||||
const incomingConnections = allConnections.filter(
|
||||
(connection: any) => connection.target === element,
|
||||
);
|
||||
|
||||
incomingConnections.forEach((connection: any) => {
|
||||
const source = connection.source; // 获取前置节点
|
||||
|
||||
// 只添加不是开始事件的前置节点
|
||||
if (!isStartEvent(source)) {
|
||||
predecessors.add(source.businessObject);
|
||||
// 递归查找前置节点
|
||||
findPredecessorsRecursively(source);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const targetElement = elementRegistry.get(elementId);
|
||||
if (targetElement) {
|
||||
findPredecessorsRecursively(targetElement);
|
||||
}
|
||||
|
||||
return [...predecessors]; // 返回前置节点数组
|
||||
}
|
||||
|
||||
function useButtonsSetting() {
|
||||
const buttonsSetting = ref<ButtonSetting[]>();
|
||||
// 操作按钮显示名称可编辑
|
||||
const btnDisplayNameEdit = ref<boolean[]>([]);
|
||||
const changeBtnDisplayName = (index: number) => {
|
||||
btnDisplayNameEdit.value[index] = true;
|
||||
};
|
||||
const btnDisplayNameBlurEvent = (index: number) => {
|
||||
btnDisplayNameEdit.value[index] = false;
|
||||
const buttonItem = buttonsSetting.value?.[index];
|
||||
if (buttonItem) {
|
||||
buttonItem.displayName =
|
||||
buttonItem.displayName || OPERATION_BUTTON_NAME.get(buttonItem.id)!;
|
||||
}
|
||||
};
|
||||
return {
|
||||
buttonsSetting,
|
||||
btnDisplayNameEdit,
|
||||
changeBtnDisplayName,
|
||||
btnDisplayNameBlurEvent,
|
||||
};
|
||||
}
|
||||
|
||||
/** 批量更新权限 */
|
||||
// TODO @lesan:这个页面,有一些 idea 红色报错,咱要不要 fix 下!
|
||||
const updatePermission = (type: string) => {
|
||||
fieldsPermissionEl.value.forEach((field: any) => {
|
||||
if (type === 'READ') {
|
||||
field.permission = FieldPermissionType.READ;
|
||||
} else if (type === 'WRITE') {
|
||||
field.permission = FieldPermissionType.WRITE;
|
||||
} else {
|
||||
field.permission = FieldPermissionType.NONE;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获得用户列表
|
||||
userOptions.value = await getSimpleUserList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Divider orientation="left">审批类型</Divider>
|
||||
<Form.Item name="approveType" label="审批类型">
|
||||
<RadioGroup v-model:value="approveType.value">
|
||||
<Radio
|
||||
v-for="(item, index) in APPROVE_TYPE"
|
||||
:key="index"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">审批人拒绝时</Divider>
|
||||
<Form.Item name="rejectHandlerType" label="处理方式">
|
||||
<RadioGroup
|
||||
v-model:value="rejectHandlerType"
|
||||
:disabled="returnTaskList.length === 0"
|
||||
@change="updateRejectHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in REJECT_HANDLER_TYPES" :key="index">
|
||||
<Radio :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
v-if="rejectHandlerType === RejectHandlerType.RETURN_USER_TASK"
|
||||
name="returnNodeId"
|
||||
label="驳回节点"
|
||||
>
|
||||
<Select
|
||||
v-model:value="returnNodeId"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="updateReturnNodeId"
|
||||
placeholder="请选择驳回节点"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in returnTaskList"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">审批人为空时</Divider>
|
||||
<Form.Item prop="assignEmptyHandlerType">
|
||||
<RadioGroup
|
||||
v-model:value="assignEmptyHandlerType"
|
||||
@change="updateAssignEmptyHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in ASSIGN_EMPTY_HANDLER_TYPES" :key="index">
|
||||
<Radio :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
v-if="assignEmptyHandlerType === AssignEmptyHandlerType.ASSIGN_USER"
|
||||
label="指定用户"
|
||||
prop="assignEmptyHandlerUserIds"
|
||||
>
|
||||
<Select
|
||||
v-model:value="assignEmptyUserIds"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateAssignEmptyUserIds"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.nickname }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">审批人与提交人为同一人时</Divider>
|
||||
<RadioGroup
|
||||
v-model:value="assignStartUserHandlerType"
|
||||
@change="updateAssignStartUserHandlerType"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div
|
||||
v-for="(item, index) in ASSIGN_START_USER_HANDLER_TYPES"
|
||||
:key="index"
|
||||
>
|
||||
<Radio :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
|
||||
<Divider orientation="left">操作按钮</Divider>
|
||||
<div class="button-setting-pane">
|
||||
<div class="button-setting-title">
|
||||
<div class="button-title-label">操作按钮</div>
|
||||
<div class="button-title-label pl-4">显示名称</div>
|
||||
<div class="button-title-label">启用</div>
|
||||
</div>
|
||||
<div
|
||||
class="button-setting-item"
|
||||
v-for="(item, index) in buttonsSettingEl"
|
||||
:key="index"
|
||||
>
|
||||
<div class="button-setting-item-label">
|
||||
{{ OPERATION_BUTTON_NAME.get(item.id) }}
|
||||
</div>
|
||||
<div class="button-setting-item-label">
|
||||
<input
|
||||
type="text"
|
||||
class="editable-title-input"
|
||||
@blur="btnDisplayNameBlurEvent(index)"
|
||||
v-mounted-focus
|
||||
v-model="item.displayName"
|
||||
:placeholder="item.displayName"
|
||||
v-if="btnDisplayNameEdit[index]"
|
||||
/>
|
||||
<Button v-else type="text" @click="changeBtnDisplayName(index)">
|
||||
{{ item.displayName }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="button-setting-item-label">
|
||||
<Switch v-model:checked="item.enable" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider orientation="left">字段权限</Divider>
|
||||
<div class="field-setting-pane" v-if="formType === BpmModelFormType.NORMAL">
|
||||
<div class="field-permit-title">
|
||||
<div class="setting-title-label first-title">字段名称</div>
|
||||
<div class="other-titles">
|
||||
<span
|
||||
class="setting-title-label cursor-pointer"
|
||||
@click="updatePermission('READ')"
|
||||
>只读
|
||||
</span>
|
||||
<span
|
||||
class="setting-title-label cursor-pointer"
|
||||
@click="updatePermission('WRITE')"
|
||||
>
|
||||
可编辑
|
||||
</span>
|
||||
<span
|
||||
class="setting-title-label cursor-pointer"
|
||||
@click="updatePermission('NONE')"
|
||||
>隐藏
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="field-setting-item"
|
||||
v-for="(item, index) in fieldsPermissionEl"
|
||||
:key="index"
|
||||
>
|
||||
<div class="field-setting-item-label">{{ item.title }}</div>
|
||||
<RadioGroup
|
||||
class="field-setting-item-group"
|
||||
v-model:value="item.permission"
|
||||
>
|
||||
<div class="item-radio-wrap">
|
||||
<Radio
|
||||
:value="FieldPermissionType.READ"
|
||||
size="large"
|
||||
@change="updateElementExtensions"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
<div class="item-radio-wrap">
|
||||
<Radio
|
||||
:value="FieldPermissionType.WRITE"
|
||||
size="large"
|
||||
@change="updateElementExtensions"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
<div class="item-radio-wrap">
|
||||
<Radio
|
||||
:value="FieldPermissionType.NONE"
|
||||
size="large"
|
||||
@change="updateElementExtensions"
|
||||
>
|
||||
<span></span>
|
||||
</Radio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider orientation="left">是否需要签名</Divider>
|
||||
<Form.Item prop="signEnable">
|
||||
<Switch
|
||||
v-model:checked="signEnable.value"
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
@change="updateElementExtensions"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">审批意见</Divider>
|
||||
<Form.Item prop="reasonRequire">
|
||||
<Switch
|
||||
v-model:checked="reasonRequire.value"
|
||||
checked-children="必填"
|
||||
un-checked-children="非必填"
|
||||
@change="updateElementExtensions"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.button-setting-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
|
||||
.button-setting-desc {
|
||||
padding-right: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.button-setting-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 45px;
|
||||
padding-left: 12px;
|
||||
background-color: #f8fafc0a;
|
||||
border: 1px solid #1f38581a;
|
||||
|
||||
& > :first-child {
|
||||
width: 100px !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.button-title-label {
|
||||
width: 150px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.button-setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 38px;
|
||||
padding-left: 12px;
|
||||
border: 1px solid #1f38581a;
|
||||
border-top: 0;
|
||||
|
||||
& > :first-child {
|
||||
width: 100px !important;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.button-setting-item-label {
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.editable-title-input {
|
||||
max-width: 130px;
|
||||
height: 24px;
|
||||
margin-left: 4px;
|
||||
line-height: 24px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
border-color: #40a9ff;
|
||||
box-shadow: 0 0 0 2px rgb(24 144 255 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.field-setting-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 14px;
|
||||
|
||||
.field-setting-desc {
|
||||
padding-right: 8px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.field-permit-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 45px;
|
||||
padding-left: 12px;
|
||||
line-height: 45px;
|
||||
background-color: #f8fafc0a;
|
||||
border: 1px solid #1f38581a;
|
||||
|
||||
.first-title {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.other-titles {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.setting-title-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
padding: 5px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.field-setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 38px;
|
||||
padding-left: 12px;
|
||||
border: 1px solid #1f38581a;
|
||||
border-top: 0;
|
||||
|
||||
.field-setting-item-label {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
min-height: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.field-setting-item-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.item-radio-wrap {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
import BoundaryEventTimer from './components/BoundaryEventTimer.vue';
|
||||
import UserTaskCustomConfig from './components/UserTaskCustomConfig.vue';
|
||||
|
||||
export const CustomConfigMap = {
|
||||
UserTask: {
|
||||
name: '用户任务',
|
||||
component: UserTaskCustomConfig,
|
||||
},
|
||||
BoundaryEventTimerEventDefinition: {
|
||||
name: '定时边界事件(非中断)',
|
||||
component: BoundaryEventTimer,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,238 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'FlowCondition' });
|
||||
|
||||
const props = defineProps({
|
||||
businessObject: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
const flowConditionForm = ref<any>({});
|
||||
const bpmnElement = ref();
|
||||
const bpmnElementSource = ref();
|
||||
const bpmnElementSourceRef = ref();
|
||||
const flowConditionRef = ref();
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetFlowCondition = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
bpmnElementSource.value = bpmnElement.value.source;
|
||||
bpmnElementSourceRef.value = bpmnElement.value.businessObject.sourceRef;
|
||||
// 初始化默认type为default
|
||||
flowConditionForm.value = { type: 'default' };
|
||||
if (
|
||||
bpmnElementSourceRef.value &&
|
||||
bpmnElementSourceRef.value.default &&
|
||||
bpmnElementSourceRef.value.default.id === bpmnElement.value.id
|
||||
) {
|
||||
flowConditionForm.value = { type: 'default' };
|
||||
} else if (bpmnElement.value.businessObject.conditionExpression) {
|
||||
// 带条件
|
||||
const conditionExpression =
|
||||
bpmnElement.value.businessObject.conditionExpression;
|
||||
flowConditionForm.value = { ...conditionExpression, type: 'condition' };
|
||||
// resource 可直接标识 是否是外部资源脚本
|
||||
if (flowConditionForm.value.resource) {
|
||||
// this.$set(this.flowConditionForm, "conditionType", "script");
|
||||
// this.$set(this.flowConditionForm, "scriptType", "externalScript");
|
||||
flowConditionForm.value.conditionType = 'script';
|
||||
flowConditionForm.value.scriptType = 'externalScript';
|
||||
return;
|
||||
}
|
||||
if (conditionExpression.language) {
|
||||
// this.$set(this.flowConditionForm, "conditionType", "script");
|
||||
// this.$set(this.flowConditionForm, "scriptType", "inlineScript");
|
||||
flowConditionForm.value.conditionType = 'script';
|
||||
flowConditionForm.value.scriptType = 'inlineScript';
|
||||
|
||||
return;
|
||||
}
|
||||
// this.$set(this.flowConditionForm, "conditionType", "expression");
|
||||
flowConditionForm.value.conditionType = 'expression';
|
||||
} else {
|
||||
// 普通
|
||||
flowConditionForm.value = { type: 'normal' };
|
||||
}
|
||||
};
|
||||
|
||||
const updateFlowType = (flowType: any) => {
|
||||
// 正常条件类
|
||||
if (flowType === 'condition') {
|
||||
flowConditionRef.value = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
);
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
conditionExpression: flowConditionRef.value,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 默认路径
|
||||
if (flowType === 'default') {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
conditionExpression: null,
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElementSource.value), {
|
||||
default: toRaw(bpmnElement.value),
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 正常路径,如果来源节点的默认路径是当前连线时,清除父元素的默认路径配置
|
||||
if (
|
||||
bpmnElementSourceRef.value.default &&
|
||||
bpmnElementSourceRef.value.default.id === bpmnElement.value.id
|
||||
) {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElementSource.value), {
|
||||
default: null,
|
||||
});
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
conditionExpression: null,
|
||||
});
|
||||
};
|
||||
|
||||
const updateFlowCondition = () => {
|
||||
const { conditionType, scriptType, body, resource, language } =
|
||||
flowConditionForm.value;
|
||||
let condition;
|
||||
if (conditionType === 'expression') {
|
||||
condition = bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
if (scriptType === 'inlineScript') {
|
||||
condition = bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
body,
|
||||
language,
|
||||
});
|
||||
// this.$set(this.flowConditionForm, "resource", "");
|
||||
flowConditionForm.value.resource = '';
|
||||
} else {
|
||||
// this.$set(this.flowConditionForm, "body", "");
|
||||
flowConditionForm.value.body = '';
|
||||
condition = bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
resource,
|
||||
language,
|
||||
});
|
||||
}
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
conditionExpression: condition,
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
bpmnElementSource.value = null;
|
||||
bpmnElementSourceRef.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
(_) => {
|
||||
// console.log(val, 'val');
|
||||
nextTick(() => {
|
||||
resetFlowCondition();
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form
|
||||
:model="flowConditionForm"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form.Item label="流转类型">
|
||||
<Select v-model:value="flowConditionForm.type" @change="updateFlowType">
|
||||
<Select.Option value="normal">普通流转路径</Select.Option>
|
||||
<Select.Option value="default">默认流转路径</Select.Option>
|
||||
<Select.Option value="condition">条件流转路径</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="条件格式"
|
||||
v-if="flowConditionForm.type === 'condition'"
|
||||
key="condition"
|
||||
>
|
||||
<Select v-model:value="flowConditionForm.conditionType">
|
||||
<Select.Option value="expression">表达式</Select.Option>
|
||||
<Select.Option value="script">脚本</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="表达式"
|
||||
v-if="
|
||||
flowConditionForm.conditionType &&
|
||||
flowConditionForm.conditionType === 'expression'
|
||||
"
|
||||
key="express"
|
||||
>
|
||||
<Input
|
||||
v-model:value="flowConditionForm.body"
|
||||
style="width: 192px"
|
||||
allow-clear
|
||||
@change="updateFlowCondition"
|
||||
/>
|
||||
</Form.Item>
|
||||
<template
|
||||
v-if="
|
||||
flowConditionForm.conditionType &&
|
||||
flowConditionForm.conditionType === 'script'
|
||||
"
|
||||
>
|
||||
<Form.Item label="脚本语言" key="language">
|
||||
<Input
|
||||
v-model:value="flowConditionForm.language"
|
||||
allow-clear
|
||||
@change="updateFlowCondition"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="脚本类型" key="scriptType">
|
||||
<Select v-model:value="flowConditionForm.scriptType">
|
||||
<Select.Option value="inlineScript">内联脚本</Select.Option>
|
||||
<Select.Option value="externalScript">外部脚本</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="脚本"
|
||||
v-if="flowConditionForm.scriptType === 'inlineScript'"
|
||||
key="body"
|
||||
>
|
||||
<TextArea
|
||||
v-model:value="flowConditionForm.body"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
allow-clear
|
||||
@change="updateFlowCondition"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="资源地址"
|
||||
v-if="flowConditionForm.scriptType === 'externalScript'"
|
||||
key="resource"
|
||||
>
|
||||
<Input
|
||||
v-model:value="flowConditionForm.resource"
|
||||
allow-clear
|
||||
@change="updateFlowCondition"
|
||||
/>
|
||||
</Form.Item>
|
||||
</template>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,536 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { Form, FormItem, Select } from 'ant-design-vue';
|
||||
|
||||
import { getFormSimpleList } from '#/api/bpm/form';
|
||||
|
||||
defineOptions({ name: 'ElementForm' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
|
||||
const formKey = ref<number | string | undefined>(undefined);
|
||||
const businessKey = ref('');
|
||||
const optionModelTitle = ref('');
|
||||
const fieldList = ref<any[]>([]);
|
||||
const formFieldForm = ref<any>({});
|
||||
const fieldType = ref({
|
||||
long: '长整型',
|
||||
string: '字符串',
|
||||
boolean: '布尔类',
|
||||
date: '日期类',
|
||||
enum: '枚举类',
|
||||
custom: '自定义类型',
|
||||
});
|
||||
const formFieldIndex = ref(-1); // 编辑中的字段, -1 为新增
|
||||
const formFieldOptionIndex = ref(-1); // 编辑中的字段配置项, -1 为新增
|
||||
const fieldModelVisible = ref(false);
|
||||
const fieldOptionModelVisible = ref(false);
|
||||
const fieldOptionForm = ref<any>({}); // 当前激活的字段配置项数据
|
||||
const fieldOptionType = ref(''); // 当前激活的字段配置项弹窗 类型
|
||||
const fieldEnumList = ref<any[]>([]); // 枚举值列表
|
||||
const fieldConstraintsList = ref<any[]>([]); // 约束条件列表
|
||||
const fieldPropertiesList = ref<any[]>([]); // 绑定属性列表
|
||||
const bpmnELement = ref();
|
||||
const elExtensionElements = ref();
|
||||
const formData = ref();
|
||||
const otherExtensions = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
const resetFormList = () => {
|
||||
bpmnELement.value = bpmnInstances().bpmnElement;
|
||||
formKey.value = bpmnELement.value.businessObject.formKey;
|
||||
// if (formKey.value?.length > 0) {
|
||||
// formKey.value = parseInt(formKey.value)
|
||||
// }
|
||||
// 获取元素扩展属性 或者 创建扩展属性
|
||||
elExtensionElements.value =
|
||||
bpmnELement.value.businessObject.get('extensionElements') ||
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
// 获取元素表单配置 或者 创建新的表单配置
|
||||
formData.value =
|
||||
elExtensionElements.value.values.find(
|
||||
(ex: any) => ex.$type === `${prefix}:FormData`,
|
||||
) || bpmnInstances().moddle.create(`${prefix}:FormData`, { fields: [] });
|
||||
|
||||
// 业务标识 businessKey, 绑定在 formData 中
|
||||
businessKey.value = formData.value.businessKey;
|
||||
|
||||
// 保留剩余扩展元素,便于后面更新该元素对应属性
|
||||
otherExtensions.value = elExtensionElements.value.values.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:FormData`,
|
||||
);
|
||||
|
||||
// 复制原始值,填充表格
|
||||
fieldList.value = structuredClone(formData.value.fields || []);
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
updateElementExtensions();
|
||||
};
|
||||
const updateElementFormKey = () => {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnELement.value), {
|
||||
formKey: formKey.value,
|
||||
});
|
||||
};
|
||||
const _updateElementBusinessKey = () => {
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnELement.value),
|
||||
formData.value,
|
||||
{
|
||||
businessKey: businessKey.value,
|
||||
},
|
||||
);
|
||||
};
|
||||
// 根据类型调整字段type
|
||||
const _changeFieldTypeType = (type: any) => {
|
||||
formFieldForm.value.type = type === 'custom' ? '' : type;
|
||||
};
|
||||
|
||||
// 打开字段详情侧边栏
|
||||
const _openFieldForm = (field: any, index: any) => {
|
||||
formFieldIndex.value = index;
|
||||
if (index === -1) {
|
||||
formFieldForm.value = {};
|
||||
// 初始化枚举值列表
|
||||
fieldEnumList.value = [];
|
||||
// 初始化约束条件列表
|
||||
fieldConstraintsList.value = [];
|
||||
// 初始化自定义属性列表
|
||||
fieldPropertiesList.value = [];
|
||||
} else {
|
||||
const FieldObject = formData.value.fields[index];
|
||||
formFieldForm.value = structuredClone(field);
|
||||
// 设置自定义类型
|
||||
// this.$set(this.formFieldForm, "typeType", !this.fieldType[field.type] ? "custom" : field.type);
|
||||
formFieldForm.value.typeType = fieldType.value[
|
||||
field.type as keyof typeof fieldType.value
|
||||
]
|
||||
? field.type
|
||||
: 'custom';
|
||||
// 初始化枚举值列表
|
||||
field.type === 'enum' &&
|
||||
(fieldEnumList.value = structuredClone(FieldObject?.values || []));
|
||||
// 初始化约束条件列表
|
||||
fieldConstraintsList.value = structuredClone(
|
||||
FieldObject?.validation?.constraints || [],
|
||||
);
|
||||
// 初始化自定义属性列表
|
||||
fieldPropertiesList.value = structuredClone(
|
||||
FieldObject?.properties?.values || [],
|
||||
);
|
||||
}
|
||||
fieldModelVisible.value = true;
|
||||
};
|
||||
// 打开字段 某个 配置项 弹窗
|
||||
const _openFieldOptionForm = (option: any, index: any, type: any) => {
|
||||
fieldOptionModelVisible.value = true;
|
||||
fieldOptionType.value = type;
|
||||
formFieldOptionIndex.value = index;
|
||||
if (type === 'property') {
|
||||
fieldOptionForm.value = option ? structuredClone(option) : {};
|
||||
return (optionModelTitle.value = '属性配置');
|
||||
}
|
||||
if (type === 'enum') {
|
||||
fieldOptionForm.value = option ? structuredClone(option) : {};
|
||||
return (optionModelTitle.value = '枚举值配置');
|
||||
}
|
||||
fieldOptionForm.value = option ? structuredClone(option) : {};
|
||||
return (optionModelTitle.value = '约束条件配置');
|
||||
};
|
||||
|
||||
// 保存字段 某个 配置项
|
||||
const _saveFieldOption = () => {
|
||||
if (formFieldOptionIndex.value === -1) {
|
||||
if (fieldOptionType.value === 'property') {
|
||||
fieldPropertiesList.value.push(fieldOptionForm.value);
|
||||
}
|
||||
if (fieldOptionType.value === 'constraint') {
|
||||
fieldConstraintsList.value.push(fieldOptionForm.value);
|
||||
}
|
||||
if (fieldOptionType.value === 'enum') {
|
||||
fieldEnumList.value.push(fieldOptionForm.value);
|
||||
}
|
||||
} else {
|
||||
fieldOptionType.value === 'property' &&
|
||||
fieldPropertiesList.value.splice(
|
||||
formFieldOptionIndex.value,
|
||||
1,
|
||||
fieldOptionForm.value,
|
||||
);
|
||||
fieldOptionType.value === 'constraint' &&
|
||||
fieldConstraintsList.value.splice(
|
||||
formFieldOptionIndex.value,
|
||||
1,
|
||||
fieldOptionForm.value,
|
||||
);
|
||||
fieldOptionType.value === 'enum' &&
|
||||
fieldEnumList.value.splice(
|
||||
formFieldOptionIndex.value,
|
||||
1,
|
||||
fieldOptionForm.value,
|
||||
);
|
||||
}
|
||||
fieldOptionModelVisible.value = false;
|
||||
fieldOptionForm.value = {};
|
||||
};
|
||||
// 保存字段配置
|
||||
const _saveField = () => {
|
||||
const { id, type, label, defaultValue, datePattern } = formFieldForm.value;
|
||||
const Field = bpmnInstances().moddle.create(`${prefix}:FormField`, {
|
||||
id,
|
||||
type,
|
||||
label,
|
||||
});
|
||||
defaultValue && (Field.defaultValue = defaultValue);
|
||||
datePattern && (Field.datePattern = datePattern);
|
||||
// 构建属性
|
||||
if (fieldPropertiesList.value && fieldPropertiesList.value.length > 0) {
|
||||
const fieldPropertyList = fieldPropertiesList.value.map((fp: any) => {
|
||||
return bpmnInstances().moddle.create(`${prefix}:Property`, {
|
||||
id: fp.id,
|
||||
value: fp.value,
|
||||
});
|
||||
});
|
||||
Field.properties = bpmnInstances().moddle.create(`${prefix}:Properties`, {
|
||||
values: fieldPropertyList,
|
||||
});
|
||||
}
|
||||
// 构建校验规则
|
||||
if (fieldConstraintsList.value && fieldConstraintsList.value.length > 0) {
|
||||
const fieldConstraintList = fieldConstraintsList.value.map((fc: any) => {
|
||||
return bpmnInstances().moddle.create(`${prefix}:Constraint`, {
|
||||
name: fc.name,
|
||||
config: fc.config,
|
||||
});
|
||||
});
|
||||
Field.validation = bpmnInstances().moddle.create(`${prefix}:Validation`, {
|
||||
constraints: fieldConstraintList,
|
||||
});
|
||||
}
|
||||
// 构建枚举值
|
||||
if (fieldEnumList.value && fieldEnumList.value.length > 0) {
|
||||
Field.values = fieldEnumList.value.map((fe: any) => {
|
||||
return bpmnInstances().moddle.create(`${prefix}:Value`, {
|
||||
name: fe.name,
|
||||
id: fe.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
// 更新数组 与 表单配置实例
|
||||
if (formFieldIndex.value === -1) {
|
||||
fieldList.value.push(formFieldForm.value);
|
||||
formData.value.fields.push(Field);
|
||||
} else {
|
||||
fieldList.value.splice(formFieldIndex.value, 1, formFieldForm.value);
|
||||
formData.value.fields.splice(formFieldIndex.value, 1, Field);
|
||||
}
|
||||
updateElementExtensions();
|
||||
fieldModelVisible.value = false;
|
||||
};
|
||||
|
||||
// 移除某个 字段的 配置项
|
||||
const _removeFieldOptionItem = (_option: any, index: any, type: any) => {
|
||||
// console.log(option, 'option')
|
||||
if (type === 'property') {
|
||||
fieldPropertiesList.value.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
if (type === 'enum') {
|
||||
fieldEnumList.value.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
fieldConstraintsList.value.splice(index, 1);
|
||||
};
|
||||
// 移除 字段
|
||||
const _removeField = (field: any, index: any) => {
|
||||
console.warn(field, 'field');
|
||||
fieldList.value.splice(index, 1);
|
||||
formData.value.fields.splice(index, 1);
|
||||
updateElementExtensions();
|
||||
};
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
// 更新回扩展元素
|
||||
const newElExtensionElements = bpmnInstances().moddle.create(
|
||||
`bpmn:ExtensionElements`,
|
||||
{
|
||||
values: [...otherExtensions.value, formData.value],
|
||||
},
|
||||
);
|
||||
// 更新到元素上
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnELement.value), {
|
||||
extensionElements: newElExtensionElements,
|
||||
});
|
||||
};
|
||||
|
||||
const formList = ref<any[]>([]); // 流程表单的下拉框的数据
|
||||
const formOptions = computed(() => {
|
||||
return formList.value.map((form: any) => ({
|
||||
value: form.id,
|
||||
label: form.name,
|
||||
}));
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
formList.value = await getFormSimpleList();
|
||||
formKey.value = formKey.value
|
||||
? Number.parseInt(formKey.value as string)
|
||||
: undefined;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val: any) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
resetFormList();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form :label-col="{ style: { width: '80px' } }">
|
||||
<FormItem label="流程表单">
|
||||
<!-- <Input v-model:value="formKey" @change="updateElementFormKey" />-->
|
||||
<Select
|
||||
v-model:value="formKey"
|
||||
allow-clear
|
||||
@change="updateElementFormKey"
|
||||
:options="formOptions"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="业务标识">
|
||||
<Select
|
||||
v-model:value="businessKey"
|
||||
@change="_updateElementBusinessKey"
|
||||
allow-clear
|
||||
>
|
||||
<Select.Option v-for="i in fieldList" :key="i.id" :value="i.id">
|
||||
{{ i.label }}
|
||||
</Select.Option>
|
||||
<Select.Option value="">无</Select.Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<!--字段列表-->
|
||||
<!-- <div class="element-property list-property">-->
|
||||
<!-- <Divider><Icon icon="ep:coin" /> 表单字段</Divider>-->
|
||||
<!-- <Table :data-source="fieldList" :scroll="{ y: 240 }" bordered>-->
|
||||
<!-- <TableColumn title="序号" type="index" width="50px" />-->
|
||||
<!-- <TableColumn title="字段名称" dataIndex="label" width="80px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn-->
|
||||
<!-- title="字段类型"-->
|
||||
<!-- dataIndex="type"-->
|
||||
<!-- width="80px"-->
|
||||
<!-- :customRender="({ text }) => fieldType[text] || text"-->
|
||||
<!-- :ellipsis="true"-->
|
||||
<!-- />-->
|
||||
<!-- <TableColumn-->
|
||||
<!-- title="默认值"-->
|
||||
<!-- dataIndex="defaultValue"-->
|
||||
<!-- width="80px"-->
|
||||
<!-- :ellipsis="true"-->
|
||||
<!-- />-->
|
||||
<!-- <TableColumn title="操作" width="90px">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <Button type="link" @click="openFieldForm(scope, scope.$index)">-->
|
||||
<!-- 编辑-->
|
||||
<!-- </Button>-->
|
||||
<!-- <Divider type="vertical" />-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- danger-->
|
||||
<!-- @click="removeField(scope, scope.$index)"-->
|
||||
<!-- >-->
|
||||
<!-- 移除-->
|
||||
<!-- </Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </TableColumn>-->
|
||||
<!-- </Table>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="element-drawer__button">-->
|
||||
<!-- <Button type="primary" @click="openFieldForm(null, -1)">添加字段</Button>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!--字段配置侧边栏-->
|
||||
<!-- <Drawer-->
|
||||
<!-- v-model:open="fieldModelVisible"-->
|
||||
<!-- title="字段配置"-->
|
||||
<!-- :width="`${width}px`"-->
|
||||
<!-- destroyOnClose-->
|
||||
<!-- >-->
|
||||
<!-- <Form :model="formFieldForm" :label-col="{ style: { width: '90px' } }">-->
|
||||
<!-- <FormItem label="字段ID">-->
|
||||
<!-- <Input v-model:value="formFieldForm.id" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="类型">-->
|
||||
<!-- <Select-->
|
||||
<!-- v-model:value="formFieldForm.typeType"-->
|
||||
<!-- placeholder="请选择字段类型"-->
|
||||
<!-- allowClear-->
|
||||
<!-- @change="changeFieldTypeType"-->
|
||||
<!-- >-->
|
||||
<!-- <SelectOption v-for="(value, key) of fieldType" :key="key" :value="key">{{ value }}</SelectOption>-->
|
||||
<!-- </Select>-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="类型名称" v-if="formFieldForm.typeType === 'custom'">-->
|
||||
<!-- <Input v-model:value="formFieldForm.type" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="名称">-->
|
||||
<!-- <Input v-model:value="formFieldForm.label" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="时间格式" v-if="formFieldForm.typeType === 'date'">-->
|
||||
<!-- <Input v-model:value="formFieldForm.datePattern" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="默认值">-->
|
||||
<!-- <Input v-model:value="formFieldForm.defaultValue" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- </Form>-->
|
||||
|
||||
<!-- <!– 枚举值设置 –>-->
|
||||
<!-- <template v-if="formFieldForm.type === 'enum'">-->
|
||||
<!-- <Divider key="enum-divider" />-->
|
||||
<!-- <p class="listener-filed__title" key="enum-title">-->
|
||||
<!-- <span><Icon icon="ep:menu" />枚举值列表:</span>-->
|
||||
<!-- <Button type="primary" @click="openFieldOptionForm(null, -1, 'enum')"-->
|
||||
<!-- >添加枚举值</Button-->
|
||||
<!-- >-->
|
||||
<!-- </p>-->
|
||||
<!-- <Table :data-source="fieldEnumList" key="enum-table" :scroll="{ y: 240 }" bordered>-->
|
||||
<!-- <TableColumn title="序号" width="50px" type="index" />-->
|
||||
<!-- <TableColumn title="枚举值编号" dataIndex="id" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="枚举值名称" dataIndex="name" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="操作" width="90px">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- @click="openFieldOptionForm(scope, scope.$index, 'enum')"-->
|
||||
<!-- >-->
|
||||
<!-- 编辑-->
|
||||
<!-- </Button>-->
|
||||
<!-- <Divider type="vertical" />-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- danger-->
|
||||
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'enum')"-->
|
||||
<!-- >-->
|
||||
<!-- 移除-->
|
||||
<!-- </Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </TableColumn>-->
|
||||
<!-- </Table>-->
|
||||
<!-- </template>-->
|
||||
|
||||
<!-- <!– 校验规则 –>-->
|
||||
<!-- <Divider key="validation-divider" />-->
|
||||
<!-- <p class="listener-filed__title" key="validation-title">-->
|
||||
<!-- <span><Icon icon="ep:menu" />约束条件列表:</span>-->
|
||||
<!-- <Button type="primary" @click="openFieldOptionForm(null, -1, 'constraint')"-->
|
||||
<!-- >添加约束</Button-->
|
||||
<!-- >-->
|
||||
<!-- </p>-->
|
||||
<!-- <Table :data-source="fieldConstraintsList" key="validation-table" :scroll="{ y: 240 }" bordered>-->
|
||||
<!-- <TableColumn title="序号" width="50px" type="index" />-->
|
||||
<!-- <TableColumn title="约束名称" dataIndex="name" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="约束配置" dataIndex="config" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="操作" width="90px">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- @click="openFieldOptionForm(scope, scope.$index, 'constraint')"-->
|
||||
<!-- >-->
|
||||
<!-- 编辑-->
|
||||
<!-- </Button>-->
|
||||
<!-- <Divider type="vertical" />-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- danger-->
|
||||
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'constraint')"-->
|
||||
<!-- >-->
|
||||
<!-- 移除-->
|
||||
<!-- </Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </TableColumn>-->
|
||||
<!-- </Table>-->
|
||||
|
||||
<!-- <!– 表单属性 –>-->
|
||||
<!-- <Divider key="property-divider" />-->
|
||||
<!-- <p class="listener-filed__title" key="property-title">-->
|
||||
<!-- <span><Icon icon="ep:menu" />字段属性列表:</span>-->
|
||||
<!-- <Button type="primary" @click="openFieldOptionForm(null, -1, 'property')"-->
|
||||
<!-- >添加属性</Button-->
|
||||
<!-- >-->
|
||||
<!-- </p>-->
|
||||
<!-- <Table :data-source="fieldPropertiesList" key="property-table" :scroll="{ y: 240 }" bordered>-->
|
||||
<!-- <TableColumn title="序号" width="50px" type="index" />-->
|
||||
<!-- <TableColumn title="属性编号" dataIndex="id" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="属性值" dataIndex="value" width="100px" :ellipsis="true" />-->
|
||||
<!-- <TableColumn title="操作" width="90px">-->
|
||||
<!-- <template #default="scope">-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- @click="openFieldOptionForm(scope, scope.$index, 'property')"-->
|
||||
<!-- >-->
|
||||
<!-- 编辑-->
|
||||
<!-- </Button>-->
|
||||
<!-- <Divider type="vertical" />-->
|
||||
<!-- <Button-->
|
||||
<!-- type="link"-->
|
||||
<!-- danger-->
|
||||
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'property')"-->
|
||||
<!-- >-->
|
||||
<!-- 移除-->
|
||||
<!-- </Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </TableColumn>-->
|
||||
<!-- </Table>-->
|
||||
|
||||
<!-- <!– 底部按钮 –>-->
|
||||
<!-- <div class="element-drawer__button">-->
|
||||
<!-- <Button>取 消</Button>-->
|
||||
<!-- <Button type="primary" @click="saveField">保 存</Button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </Drawer>-->
|
||||
|
||||
<!-- <Modal-->
|
||||
<!-- v-model:open="fieldOptionModelVisible"-->
|
||||
<!-- :title="optionModelTitle"-->
|
||||
<!-- width="600px"-->
|
||||
<!-- destroyOnClose-->
|
||||
<!-- >-->
|
||||
<!-- <Form :model="fieldOptionForm" :label-col="{ style: { width: '96px' } }">-->
|
||||
<!-- <FormItem label="编号/ID" v-if="fieldOptionType !== 'constraint'" key="option-id">-->
|
||||
<!-- <Input v-model:value="fieldOptionForm.id" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="名称" v-if="fieldOptionType !== 'property'" key="option-name">-->
|
||||
<!-- <Input v-model:value="fieldOptionForm.name" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="配置" v-if="fieldOptionType === 'constraint'" key="option-config">-->
|
||||
<!-- <Input v-model:value="fieldOptionForm.config" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- <FormItem label="值" v-if="fieldOptionType === 'property'" key="option-value">-->
|
||||
<!-- <Input v-model:value="fieldOptionForm.value" allowClear />-->
|
||||
<!-- </FormItem>-->
|
||||
<!-- </Form>-->
|
||||
<!-- <template #footer>-->
|
||||
<!-- <Button @click="fieldOptionModelVisible = false">取 消</Button>-->
|
||||
<!-- <Button type="primary" @click="saveFieldOption">确 定</Button>-->
|
||||
<!-- </template>-->
|
||||
<!-- </Modal>-->
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
import MyPropertiesPanel from './PropertiesPanel.vue';
|
||||
|
||||
MyPropertiesPanel.install = function (Vue) {
|
||||
Vue.component(MyPropertiesPanel.name, MyPropertiesPanel);
|
||||
};
|
||||
|
||||
export default MyPropertiesPanel;
|
||||
@@ -0,0 +1,621 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon, PlusOutlined } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Drawer,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
SelectOption,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { createListenerObject, updateElementExtensions } from '../../utils';
|
||||
import ProcessListenerDialog from './ProcessListenerDialog.vue';
|
||||
import {
|
||||
fieldType,
|
||||
initListenerForm,
|
||||
initListenerForm2,
|
||||
initListenerType,
|
||||
listenerType,
|
||||
} from './utilSelf';
|
||||
|
||||
defineOptions({ name: 'ElementListeners' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
const width = inject('width');
|
||||
const elementListenersList = ref<any[]>([]); // 监听器列表
|
||||
const listenerForm = ref<any>({}); // 监听器详情表单
|
||||
const listenerFormModelVisible = ref(false); // 监听器 编辑 侧边栏显示状态
|
||||
const fieldsListOfListener = ref<any[]>([]);
|
||||
const listenerFieldForm = ref<any>({}); // 监听器 注入字段 详情表单
|
||||
const listenerFieldFormModelVisible = ref(false); // 监听器 注入字段表单弹窗 显示状态
|
||||
const editingListenerIndex = ref(-1); // 监听器所在下标,-1 为新增
|
||||
const editingListenerFieldIndex = ref(-1); // 字段所在下标,-1 为新增
|
||||
const listenerTypeObject = ref(listenerType);
|
||||
const fieldTypeObject = ref(fieldType);
|
||||
const bpmnElement = ref();
|
||||
const otherExtensionList = ref();
|
||||
const bpmnElementListeners = ref();
|
||||
const listenerFormRef = ref();
|
||||
const listenerFieldFormRef = ref();
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetListenersList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
otherExtensionList.value = [];
|
||||
bpmnElementListeners.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:ExecutionListener`,
|
||||
) ?? [];
|
||||
elementListenersList.value = bpmnElementListeners.value.map((listener: any) =>
|
||||
initListenerType(listener),
|
||||
);
|
||||
};
|
||||
// 打开 监听器详情 侧边栏
|
||||
const openListenerForm = (listener: any, index: number) => {
|
||||
// debugger
|
||||
if (listener) {
|
||||
listenerForm.value = initListenerForm(listener);
|
||||
editingListenerIndex.value = index;
|
||||
} else {
|
||||
listenerForm.value = {};
|
||||
editingListenerIndex.value = -1; // 标记为新增
|
||||
}
|
||||
if (listener && listener.fields) {
|
||||
fieldsListOfListener.value = listener.fields.map((field: any) => ({
|
||||
...field,
|
||||
fieldType: field.string ? 'string' : 'expression',
|
||||
}));
|
||||
} else {
|
||||
fieldsListOfListener.value = [];
|
||||
listenerForm.value.fields = [];
|
||||
}
|
||||
// 打开侧边栏并清楚验证状态
|
||||
listenerFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (listenerFormRef.value) {
|
||||
listenerFormRef.value.clearValidate();
|
||||
}
|
||||
});
|
||||
};
|
||||
// 打开监听器字段编辑弹窗
|
||||
const openListenerFieldForm = (field: any, index: number) => {
|
||||
listenerFieldForm.value = field ? structuredClone(field) : {};
|
||||
editingListenerFieldIndex.value = field ? index : -1;
|
||||
listenerFieldFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (listenerFieldFormRef.value) {
|
||||
listenerFieldFormRef.value.clearValidate();
|
||||
}
|
||||
});
|
||||
};
|
||||
// 保存监听器注入字段
|
||||
const saveListenerFiled = async () => {
|
||||
// debugger
|
||||
const validateStatus = await listenerFieldFormRef.value.validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
if (editingListenerFieldIndex.value === -1) {
|
||||
fieldsListOfListener.value.push(listenerFieldForm.value);
|
||||
listenerForm.value.fields.push(listenerFieldForm.value);
|
||||
} else {
|
||||
fieldsListOfListener.value.splice(
|
||||
editingListenerFieldIndex.value,
|
||||
1,
|
||||
listenerFieldForm.value,
|
||||
);
|
||||
listenerForm.value.fields.splice(
|
||||
editingListenerFieldIndex.value,
|
||||
1,
|
||||
listenerFieldForm.value,
|
||||
);
|
||||
}
|
||||
listenerFieldFormModelVisible.value = false;
|
||||
nextTick(() => {
|
||||
listenerFieldForm.value = {};
|
||||
});
|
||||
};
|
||||
// 移除监听器字段
|
||||
const removeListenerField = (index: number) => {
|
||||
// debugger
|
||||
Modal.confirm({
|
||||
title: '确认移除该字段吗?',
|
||||
content: '此操作不可撤销',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
fieldsListOfListener.value.splice(index, 1);
|
||||
listenerForm.value.fields.splice(index, 1);
|
||||
},
|
||||
onCancel() {
|
||||
console.warn('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
// 移除监听器
|
||||
const removeListener = (index: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认移除该监听器吗?',
|
||||
content: '此操作不可撤销',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
bpmnElementListeners.value.splice(index, 1);
|
||||
elementListenersList.value.splice(index, 1);
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
},
|
||||
onCancel() {
|
||||
console.warn('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
// 保存监听器配置
|
||||
const saveListenerConfig = async () => {
|
||||
// debugger
|
||||
const validateStatus = await listenerFormRef.value.validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
const listenerObject = createListenerObject(
|
||||
listenerForm.value,
|
||||
false,
|
||||
prefix,
|
||||
);
|
||||
if (editingListenerIndex.value === -1) {
|
||||
bpmnElementListeners.value.push(listenerObject);
|
||||
elementListenersList.value.push(listenerForm.value);
|
||||
} else {
|
||||
bpmnElementListeners.value.splice(
|
||||
editingListenerIndex.value,
|
||||
1,
|
||||
listenerObject,
|
||||
);
|
||||
elementListenersList.value.splice(
|
||||
editingListenerIndex.value,
|
||||
1,
|
||||
listenerForm.value,
|
||||
);
|
||||
}
|
||||
// 保存其他配置
|
||||
otherExtensionList.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:ExecutionListener`,
|
||||
) ?? [];
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
// 4. 隐藏侧边栏
|
||||
listenerFormModelVisible.value = false;
|
||||
listenerForm.value = {};
|
||||
};
|
||||
|
||||
// 打开监听器弹窗
|
||||
const processListenerDialogRef = ref();
|
||||
const openProcessListenerDialog = async () => {
|
||||
processListenerDialogRef.value.open('execution');
|
||||
};
|
||||
const selectProcessListener = (listener: any) => {
|
||||
const listenerForm = initListenerForm2(listener);
|
||||
const listenerObject = createListenerObject(listenerForm, false, prefix);
|
||||
bpmnElementListeners.value.push(listenerObject);
|
||||
elementListenersList.value.push(listenerForm);
|
||||
|
||||
// 保存其他配置
|
||||
otherExtensionList.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:ExecutionListener`,
|
||||
) ?? [];
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val: string) => {
|
||||
if (val && val.length > 0) {
|
||||
nextTick(() => {
|
||||
resetListenersList();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Table
|
||||
:data-source="elementListenersList"
|
||||
size="small"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<TableColumn title="序号" width="50px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn title="事件类型" width="100px" data-index="event" />
|
||||
<TableColumn
|
||||
title="监听器类型"
|
||||
width="100px"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
listenerTypeObject[record.listenerType as keyof typeof listenerType]
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="100px">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openListenerForm(record, index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
@click="removeListener(index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
<div class="element-drawer__button">
|
||||
<Button type="primary" size="small" @click="openListenerForm(null, -1)">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
添加监听器
|
||||
</Button>
|
||||
<Button size="small" @click="openProcessListenerDialog">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:select" />
|
||||
</template>
|
||||
选择监听器
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 监听器 编辑/创建 部分 -->
|
||||
<Drawer
|
||||
v-model:open="listenerFormModelVisible"
|
||||
title="执行监听器"
|
||||
:width="width as any"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerForm"
|
||||
ref="listenerFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<FormItem
|
||||
label="事件类型"
|
||||
name="event"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择事件类型',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.event">
|
||||
<SelectOption value="start">start</SelectOption>
|
||||
<SelectOption value="end">end</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="监听器类型"
|
||||
name="listenerType"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择监听器类型',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.listenerType">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(listenerTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ listenerTypeObject[i as keyof typeof listenerType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'classListener'"
|
||||
label="Java类"
|
||||
name="class"
|
||||
key="listener-class"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写Java类',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.class" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'expressionListener'"
|
||||
label="表达式"
|
||||
name="expression"
|
||||
key="listener-expression"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写表达式',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.expression" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
|
||||
label="代理表达式"
|
||||
name="delegateExpression"
|
||||
key="listener-delegate"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写代理表达式',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.delegateExpression" allow-clear />
|
||||
</FormItem>
|
||||
<template v-if="listenerForm.listenerType === 'scriptListener'">
|
||||
<FormItem
|
||||
label="脚本格式"
|
||||
name="scriptFormat"
|
||||
key="listener-script-format"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请填写脚本格式',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.scriptFormat" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="脚本类型"
|
||||
name="scriptType"
|
||||
key="listener-script-type"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请选择脚本类型',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.scriptType">
|
||||
<SelectOption value="inlineScript">内联脚本</SelectOption>
|
||||
<SelectOption value="externalScript">外部脚本</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.scriptType === 'inlineScript'"
|
||||
label="脚本内容"
|
||||
name="value"
|
||||
key="listener-script"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请填写脚本内容',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.value" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.scriptType === 'externalScript'"
|
||||
label="资源地址"
|
||||
name="resource"
|
||||
key="listener-resource"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
trigger: ['blur', 'change'],
|
||||
message: '请填写资源地址',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.resource" allow-clear />
|
||||
</FormItem>
|
||||
</template>
|
||||
</Form>
|
||||
<Divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><IconifyIcon icon="ep:menu" />注入字段:</span>
|
||||
<Button type="primary" @click="openListenerFieldForm(null, -1)">
|
||||
添加字段
|
||||
</Button>
|
||||
</p>
|
||||
<Table
|
||||
:data-source="fieldsListOfListener"
|
||||
size="small"
|
||||
:scroll="{ y: 240 }"
|
||||
:pagination="false"
|
||||
bordered
|
||||
style="flex: none"
|
||||
>
|
||||
<TableColumn title="序号" width="50px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn title="字段名称" width="100px" data-index="name" />
|
||||
<TableColumn
|
||||
title="字段类型"
|
||||
width="80px"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
fieldTypeObject[record.fieldType as keyof typeof fieldType]
|
||||
"
|
||||
/>
|
||||
<TableColumn
|
||||
title="字段值/表达式"
|
||||
width="100px"
|
||||
:custom-render="
|
||||
({ record }: any) => record.string || record.expression
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="130px">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openListenerFieldForm(record, index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
@click="removeListenerField(index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
|
||||
<div class="element-drawer__button">
|
||||
<Button @click="listenerFormModelVisible = false">取 消</Button>
|
||||
<Button type="primary" @click="saveListenerConfig">保 存</Button>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<!-- 注入字段 编辑/创建 部分 -->
|
||||
<Modal
|
||||
title="字段配置"
|
||||
v-model:open="listenerFieldFormModelVisible"
|
||||
width="600px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerFieldForm"
|
||||
ref="listenerFieldFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
style="height: 136px"
|
||||
>
|
||||
<FormItem
|
||||
label="字段名称:"
|
||||
name="name"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写字段名称',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.name" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="字段类型:"
|
||||
name="fieldType"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择字段类型',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Select v-model:value="listenerFieldForm.fieldType">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(fieldTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ fieldTypeObject[i as keyof typeof fieldType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerFieldForm.fieldType === 'string'"
|
||||
label="字段值:"
|
||||
name="string"
|
||||
key="field-string"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写字段值',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.string" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerFieldForm.fieldType === 'expression'"
|
||||
label="表达式:"
|
||||
name="expression"
|
||||
key="field-expression"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请填写表达式',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.expression" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button size="small" @click="listenerFieldFormModelVisible = false">
|
||||
取 消
|
||||
</Button>
|
||||
<Button size="small" type="primary" @click="saveListenerFiled">
|
||||
确 定
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<!-- 选择弹窗 -->
|
||||
<ProcessListenerDialog
|
||||
ref="processListenerDialogRef"
|
||||
@select="selectProcessListener"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,111 @@
|
||||
<!-- 执行器选择 -->
|
||||
<script setup lang="ts">
|
||||
import type { BpmProcessListenerApi } from '#/api/bpm/processListener';
|
||||
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import { CommonStatusEnum } from '@vben/constants';
|
||||
|
||||
import { Button, Modal, Pagination, Table } from 'ant-design-vue';
|
||||
|
||||
import { getProcessListenerPage } from '#/api/bpm/processListener';
|
||||
import { ContentWrap } from '#/components/content-wrap';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
import { DICT_TYPE } from '#/utils/dict';
|
||||
|
||||
/** BPM 流程 表单 */
|
||||
defineOptions({ name: 'ProcessListenerDialog' });
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success', 'select']);
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const loading = ref(true); // 列表的加载中
|
||||
const list = ref<BpmProcessListenerApi.ProcessListener[]>([]); // 列表的数据
|
||||
const total = ref(0); // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
});
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string) => {
|
||||
queryParams.pageNo = 1;
|
||||
queryParams.type = type;
|
||||
await getList();
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getProcessListenerPage(queryParams);
|
||||
list.value = data.list;
|
||||
total.value = data.total;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 定义 success 事件,用于操作成功后的回调
|
||||
const select = async (row: BpmProcessListenerApi.ProcessListener) => {
|
||||
dialogVisible.value = false;
|
||||
// 发送操作成功的事件
|
||||
emit('select', row);
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<Modal
|
||||
title="请选择监听器"
|
||||
v-model:open="dialogVisible"
|
||||
width="1024px"
|
||||
:footer="null"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:loading="loading"
|
||||
:data-source="list"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
>
|
||||
<Table.Column title="名字" align="center" data-index="name" />
|
||||
<Table.Column title="类型" align="center" data-index="type">
|
||||
<template #default="{ record }">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE"
|
||||
:value="record.type"
|
||||
/>
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column title="事件" align="center" data-index="event" />
|
||||
<Table.Column title="值类型" align="center" data-index="valueType">
|
||||
<template #default="{ record }">
|
||||
<DictTag
|
||||
:type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE"
|
||||
:value="record.valueType"
|
||||
/>
|
||||
</template>
|
||||
</Table.Column>
|
||||
<Table.Column title="值" align="center" data-index="value" />
|
||||
<Table.Column title="操作" align="center">
|
||||
<template #default="{ record }">
|
||||
<Button type="primary" @click="select(record)"> 选择 </Button>
|
||||
</template>
|
||||
</Table.Column>
|
||||
</Table>
|
||||
<!-- 分页 -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current="queryParams.pageNo"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
show-size-changer
|
||||
@change="getList"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,598 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, watch } from 'vue';
|
||||
|
||||
import { MenuOutlined, PlusOutlined, SelectOutlined } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Drawer,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
SelectOption,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import ProcessListenerDialog from '#/components/bpmn-process-designer/package/penal/listeners/ProcessListenerDialog.vue';
|
||||
|
||||
import { createListenerObject, updateElementExtensions } from '../../utils';
|
||||
import {
|
||||
eventType,
|
||||
fieldType,
|
||||
initListenerForm,
|
||||
initListenerForm2,
|
||||
initListenerType,
|
||||
listenerType,
|
||||
} from './utilSelf';
|
||||
|
||||
defineOptions({ name: 'UserTaskListeners' });
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const prefix = inject<string>('prefix');
|
||||
const width = inject<number>('width');
|
||||
const elementListenersList = ref<any[]>([]);
|
||||
const listenerEventTypeObject = ref(eventType);
|
||||
const listenerTypeObject = ref(listenerType);
|
||||
const listenerFormModelVisible = ref(false);
|
||||
const listenerForm = ref<any>({});
|
||||
const fieldTypeObject = ref(fieldType);
|
||||
const fieldsListOfListener = ref<any[]>([]);
|
||||
const listenerFieldFormModelVisible = ref(false); // 监听器 注入字段表单弹窗 显示状态
|
||||
const editingListenerIndex = ref(-1); // 监听器所在下标,-1 为新增
|
||||
const editingListenerFieldIndex = ref<any>(-1); // 字段所在下标,-1 为新增
|
||||
const listenerFieldForm = ref<any>({}); // 监听器 注入字段 详情表单
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnElementListeners = ref<any[]>([]);
|
||||
const otherExtensionList = ref<any[]>([]);
|
||||
const listenerFormRef = ref<any>({});
|
||||
const listenerFieldFormRef = ref<any>({});
|
||||
|
||||
interface BpmnInstances {
|
||||
bpmnElement: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
bpmnInstances?: BpmnInstances;
|
||||
}
|
||||
}
|
||||
|
||||
const bpmnInstances = () => window.bpmnInstances;
|
||||
|
||||
const resetListenersList = () => {
|
||||
// console.log(
|
||||
// bpmnInstances().bpmnElement,
|
||||
// 'window.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElement',
|
||||
// );
|
||||
bpmnElement.value = bpmnInstances()?.bpmnElement;
|
||||
otherExtensionList.value = [];
|
||||
bpmnElementListeners.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:TaskListener`,
|
||||
) ?? [];
|
||||
elementListenersList.value = bpmnElementListeners.value.map((listener) =>
|
||||
initListenerType(listener),
|
||||
);
|
||||
};
|
||||
const openListenerForm = (listener: any, index?: number) => {
|
||||
if (listener) {
|
||||
listenerForm.value = initListenerForm(listener);
|
||||
editingListenerIndex.value = index || -1;
|
||||
} else {
|
||||
listenerForm.value = {};
|
||||
editingListenerIndex.value = -1; // 标记为新增
|
||||
}
|
||||
if (listener && listener.fields) {
|
||||
fieldsListOfListener.value = listener.fields.map((field: any) => ({
|
||||
...field,
|
||||
fieldType: field.string ? 'string' : 'expression',
|
||||
}));
|
||||
} else {
|
||||
fieldsListOfListener.value = [];
|
||||
listenerForm.value.fields = [];
|
||||
}
|
||||
// 打开侧边栏并清楚验证状态
|
||||
listenerFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (listenerFormRef.value) listenerFormRef.value.clearValidate();
|
||||
});
|
||||
};
|
||||
// 移除监听器
|
||||
const removeListener = (_: any, index: number) => {
|
||||
// console.log(listener, 'listener');
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确认移除该监听器吗?',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
bpmnElementListeners.value.splice(index, 1);
|
||||
elementListenersList.value.splice(index, 1);
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
},
|
||||
onCancel() {
|
||||
// console.info('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
// 保存监听器
|
||||
const saveListenerConfig = async () => {
|
||||
const validateStatus = await listenerFormRef.value.validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
const listenerObject = createListenerObject(listenerForm.value, true, prefix);
|
||||
if (editingListenerIndex.value === -1) {
|
||||
bpmnElementListeners.value.push(listenerObject);
|
||||
elementListenersList.value.push(listenerForm.value);
|
||||
} else {
|
||||
bpmnElementListeners.value.splice(
|
||||
editingListenerIndex.value,
|
||||
1,
|
||||
listenerObject,
|
||||
);
|
||||
elementListenersList.value.splice(
|
||||
editingListenerIndex.value,
|
||||
1,
|
||||
listenerForm.value,
|
||||
);
|
||||
}
|
||||
// 保存其他配置
|
||||
otherExtensionList.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:TaskListener`,
|
||||
) ?? [];
|
||||
updateElementExtensions(bpmnElement.value, [
|
||||
...otherExtensionList.value,
|
||||
...bpmnElementListeners.value,
|
||||
]);
|
||||
// 4. 隐藏侧边栏
|
||||
listenerFormModelVisible.value = false;
|
||||
listenerForm.value = {};
|
||||
};
|
||||
// 打开监听器字段编辑弹窗
|
||||
const openListenerFieldForm = (field: any, index?: number) => {
|
||||
listenerFieldForm.value = field ? structuredClone(field) : {};
|
||||
editingListenerFieldIndex.value = field ? index : -1;
|
||||
listenerFieldFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (listenerFieldFormRef.value) listenerFieldFormRef.value.clearValidate();
|
||||
});
|
||||
};
|
||||
// 保存监听器注入字段
|
||||
const saveListenerFiled = async () => {
|
||||
const validateStatus = await listenerFieldFormRef.value.validate();
|
||||
if (!validateStatus) return; // 验证不通过直接返回
|
||||
if (editingListenerFieldIndex.value === -1) {
|
||||
fieldsListOfListener.value.push(listenerFieldForm.value);
|
||||
listenerForm.value.fields.push(listenerFieldForm.value);
|
||||
} else {
|
||||
fieldsListOfListener.value.splice(
|
||||
editingListenerFieldIndex.value,
|
||||
1,
|
||||
listenerFieldForm.value,
|
||||
);
|
||||
listenerForm.value.fields.splice(
|
||||
editingListenerFieldIndex.value,
|
||||
1,
|
||||
listenerFieldForm.value,
|
||||
);
|
||||
}
|
||||
listenerFieldFormModelVisible.value = false;
|
||||
nextTick(() => {
|
||||
listenerFieldForm.value = {};
|
||||
});
|
||||
};
|
||||
// 移除监听器字段
|
||||
const removeListenerField = (_: any, index: number) => {
|
||||
// console.log(field, 'field');
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确认移除该字段吗?',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
fieldsListOfListener.value.splice(index, 1);
|
||||
listenerForm.value.fields.splice(index, 1);
|
||||
},
|
||||
onCancel() {
|
||||
// console.info('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 打开监听器弹窗
|
||||
const processListenerDialogRef = ref<any>();
|
||||
const openProcessListenerDialog = async () => {
|
||||
processListenerDialogRef.value.open('task');
|
||||
};
|
||||
const selectProcessListener = (listener: any) => {
|
||||
const listenerForm = initListenerForm2(listener);
|
||||
const listenerObject = createListenerObject(listenerForm, true, prefix);
|
||||
bpmnElementListeners.value.push(listenerObject);
|
||||
elementListenersList.value.push(listenerForm);
|
||||
|
||||
// 保存其他配置
|
||||
otherExtensionList.value =
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:TaskListener`,
|
||||
) ?? [];
|
||||
updateElementExtensions(
|
||||
bpmnElement.value,
|
||||
otherExtensionList.value?.concat(bpmnElementListeners.value),
|
||||
);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
resetListenersList();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Table :data="elementListenersList" size="small" bordered>
|
||||
<TableColumn title="序号" width="50px" type="index" />
|
||||
<TableColumn
|
||||
title="事件类型"
|
||||
width="80px"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
listenerEventTypeObject[record.event as keyof typeof eventType]
|
||||
"
|
||||
/>
|
||||
<TableColumn
|
||||
title="事件id"
|
||||
width="80px"
|
||||
data-index="id"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="监听器类型"
|
||||
width="80px"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
listenerTypeObject[record.listenerType as keyof typeof listenerType]
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="90px">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openListenerForm(record, index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
@click="removeListener(record, index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
<div class="element-drawer__button">
|
||||
<Button size="small" type="primary" @click="openListenerForm(null)">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
添加监听器
|
||||
</Button>
|
||||
<Button size="small" @click="openProcessListenerDialog">
|
||||
<template #icon><SelectOutlined /></template>
|
||||
选择监听器
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 监听器 编辑/创建 部分 -->
|
||||
<Drawer
|
||||
v-model:open="listenerFormModelVisible"
|
||||
title="任务监听器"
|
||||
:width="width"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerForm"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
ref="listenerFormRef"
|
||||
>
|
||||
<FormItem
|
||||
label="事件类型"
|
||||
name="event"
|
||||
:rules="[{ required: true, message: '请选择事件类型' }]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.event">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(listenerEventTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ listenerEventTypeObject[i as keyof typeof eventType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="监听器ID"
|
||||
name="id"
|
||||
:rules="[{ required: true, message: '请输入监听器ID' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.id" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="监听器类型"
|
||||
name="listenerType"
|
||||
:rules="[{ required: true, message: '请选择监听器类型' }]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.listenerType">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(listenerTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ listenerTypeObject[i as keyof typeof listenerType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'classListener'"
|
||||
label="Java类"
|
||||
name="class"
|
||||
key="listener-class"
|
||||
:rules="[{ required: true, message: '请输入Java类' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.class" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'expressionListener'"
|
||||
label="表达式"
|
||||
name="expression"
|
||||
key="listener-expression"
|
||||
:rules="[{ required: true, message: '请输入表达式' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.expression" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.listenerType === 'delegateExpressionListener'"
|
||||
label="代理表达式"
|
||||
name="delegateExpression"
|
||||
key="listener-delegate"
|
||||
:rules="[{ required: true, message: '请输入代理表达式' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.delegateExpression" allow-clear />
|
||||
</FormItem>
|
||||
<template v-if="listenerForm.listenerType === 'scriptListener'">
|
||||
<FormItem
|
||||
label="脚本格式"
|
||||
name="scriptFormat"
|
||||
key="listener-script-format"
|
||||
:rules="[{ required: true, message: '请填写脚本格式' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.scriptFormat" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="脚本类型"
|
||||
name="scriptType"
|
||||
key="listener-script-type"
|
||||
:rules="[{ required: true, message: '请选择脚本类型' }]"
|
||||
>
|
||||
<Select v-model:value="listenerForm.scriptType">
|
||||
<SelectOption value="inlineScript">内联脚本</SelectOption>
|
||||
<SelectOption value="externalScript">外部脚本</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.scriptType === 'inlineScript'"
|
||||
label="脚本内容"
|
||||
name="value"
|
||||
key="listener-script"
|
||||
:rules="[{ required: true, message: '请填写脚本内容' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.value" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerForm.scriptType === 'externalScript'"
|
||||
label="资源地址"
|
||||
name="resource"
|
||||
key="listener-resource"
|
||||
:rules="[{ required: true, message: '请填写资源地址' }]"
|
||||
>
|
||||
<Input v-model:value="listenerForm.resource" allow-clear />
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
<template v-if="listenerForm.event === 'timeout'">
|
||||
<FormItem
|
||||
label="定时器类型"
|
||||
name="eventDefinitionType"
|
||||
key="eventDefinitionType"
|
||||
>
|
||||
<Select v-model:value="listenerForm.eventDefinitionType">
|
||||
<SelectOption value="date">日期</SelectOption>
|
||||
<SelectOption value="duration">持续时长</SelectOption>
|
||||
<SelectOption value="cycle">循环</SelectOption>
|
||||
<SelectOption value="null">无</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
!!listenerForm.eventDefinitionType &&
|
||||
listenerForm.eventDefinitionType !== 'null'
|
||||
"
|
||||
label="定时器"
|
||||
name="eventTimeDefinitions"
|
||||
key="eventTimeDefinitions"
|
||||
:rules="[{ required: true, message: '请填写定时器配置' }]"
|
||||
>
|
||||
<Input
|
||||
v-model:value="listenerForm.eventTimeDefinitions"
|
||||
allow-clear
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
</Form>
|
||||
|
||||
<Divider />
|
||||
<p class="listener-filed__title">
|
||||
<span><MenuOutlined />注入字段:</span>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openListenerFieldForm(null)"
|
||||
>
|
||||
添加字段
|
||||
</Button>
|
||||
</p>
|
||||
<Table
|
||||
:data="fieldsListOfListener"
|
||||
size="small"
|
||||
:scroll="{ y: 240 }"
|
||||
bordered
|
||||
style="flex: none"
|
||||
>
|
||||
<TableColumn title="序号" width="50px" type="index" />
|
||||
<TableColumn title="字段名称" width="100px" data-index="name" />
|
||||
<TableColumn
|
||||
title="字段类型"
|
||||
width="80px"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
:custom-render="
|
||||
({ record }: any) =>
|
||||
fieldTypeObject[record.fieldType as keyof typeof fieldType]
|
||||
"
|
||||
/>
|
||||
<TableColumn
|
||||
title="字段值/表达式"
|
||||
width="100px"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
:custom-render="
|
||||
({ record }: any) => record.string || record.expression
|
||||
"
|
||||
/>
|
||||
<TableColumn title="操作" width="100px">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
@click="openListenerFieldForm(record, index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
danger
|
||||
@click="removeListenerField(record, index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
|
||||
<div class="element-drawer__button">
|
||||
<Button size="small" @click="listenerFormModelVisible = false">
|
||||
取 消
|
||||
</Button>
|
||||
<Button size="small" type="primary" @click="saveListenerConfig">
|
||||
保 存
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<!-- 注入字段 编辑/创建 部分 -->
|
||||
<Modal
|
||||
title="字段配置"
|
||||
v-model:open="listenerFieldFormModelVisible"
|
||||
:width="600"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="listenerFieldForm"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
ref="listenerFieldFormRef"
|
||||
style="height: 136px"
|
||||
>
|
||||
<FormItem
|
||||
label="字段名称:"
|
||||
name="name"
|
||||
:rules="[{ required: true, message: '请输入字段名称' }]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.name" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="字段类型:"
|
||||
name="fieldType"
|
||||
:rules="[{ required: true, message: '请选择字段类型' }]"
|
||||
>
|
||||
<Select v-model:value="listenerFieldForm.fieldType">
|
||||
<SelectOption
|
||||
v-for="i in Object.keys(fieldTypeObject)"
|
||||
:key="i"
|
||||
:value="i"
|
||||
>
|
||||
{{ fieldTypeObject[i as keyof typeof fieldType] }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerFieldForm.fieldType === 'string'"
|
||||
label="字段值:"
|
||||
name="string"
|
||||
key="field-string"
|
||||
:rules="[{ required: true, message: '请输入字段值' }]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.string" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="listenerFieldForm.fieldType === 'expression'"
|
||||
label="表达式:"
|
||||
name="expression"
|
||||
key="field-expression"
|
||||
:rules="[{ required: true, message: '请输入表达式' }]"
|
||||
>
|
||||
<Input v-model:value="listenerFieldForm.expression" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button size="small" @click="listenerFieldFormModelVisible = false">
|
||||
取 消
|
||||
</Button>
|
||||
<Button size="small" type="primary" @click="saveListenerFiled">
|
||||
确 定
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<!-- 选择弹窗 -->
|
||||
<ProcessListenerDialog
|
||||
ref="processListenerDialogRef"
|
||||
@select="selectProcessListener"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,178 @@
|
||||
export const template = (isTaskListener) => {
|
||||
return `
|
||||
<div class="panel-tab__content">
|
||||
<el-table :data="elementListenersList" size="small" 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 #default="scope">
|
||||
<el-button size="small" type="primary" link @click="openListenerForm(scope, scope.$index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="small" type="primary" link style="color: #ff4d4f" @click="removeListener(scope, scope.$index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="small" 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="small" :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="small" type="primary" @click="openListenerFieldForm(null)">添加字段</el-button>
|
||||
</p>
|
||||
<el-table :data="fieldsListOfListener" size="small" 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 #default="scope">
|
||||
<el-button size="small" type="primary" link @click="openListenerFieldForm(scope, scope.$index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="small" type="primary" link style="color: #ff4d4f" @click="removeListenerField(scope, scope.$index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="element-drawer__button">
|
||||
<el-button size="small" @click="listenerFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="small" 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="small" 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 #footer>
|
||||
<el-button size="small" @click="listenerFieldFormModelVisible = false">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="saveListenerFiled">确 定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
// 初始化表单数据
|
||||
export function initListenerForm(listener: any) {
|
||||
let self = {
|
||||
...listener,
|
||||
};
|
||||
if (listener.script) {
|
||||
self = {
|
||||
...listener,
|
||||
...listener.script,
|
||||
scriptType: listener.script.resource ? 'externalScript' : 'inlineScript',
|
||||
};
|
||||
}
|
||||
if (
|
||||
listener.event === 'timeout' &&
|
||||
listener.eventDefinitions &&
|
||||
listener.eventDefinitions.length > 0
|
||||
) {
|
||||
let k = '';
|
||||
for (const key in listener.eventDefinitions[0]) {
|
||||
// console.log(listener.eventDefinitions, key);
|
||||
if (key.includes('time')) {
|
||||
k = key;
|
||||
self.eventDefinitionType = key.replace('time', '').toLowerCase();
|
||||
}
|
||||
}
|
||||
// console.log(k);
|
||||
self.eventTimeDefinitions = listener.eventDefinitions[0][k].body;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
export function initListenerType(listener: any) {
|
||||
let listenerType;
|
||||
if (listener.class) listenerType = 'classListener';
|
||||
if (listener.expression) listenerType = 'expressionListener';
|
||||
if (listener.delegateExpression) listenerType = 'delegateExpressionListener';
|
||||
if (listener.script) listenerType = 'scriptListener';
|
||||
return {
|
||||
...structuredClone(listener),
|
||||
...listener.script,
|
||||
listenerType,
|
||||
};
|
||||
}
|
||||
|
||||
/** 将 ProcessListenerDO 转换成 initListenerForm 想同的 Form 对象 */
|
||||
export function initListenerForm2(processListener: any) {
|
||||
switch (processListener.valueType) {
|
||||
case 'class': {
|
||||
return {
|
||||
listenerType: 'classListener',
|
||||
class: processListener.value,
|
||||
event: processListener.event,
|
||||
fields: [],
|
||||
};
|
||||
}
|
||||
case 'delegateExpression': {
|
||||
return {
|
||||
listenerType: 'delegateExpressionListener',
|
||||
delegateExpression: processListener.value,
|
||||
event: processListener.event,
|
||||
fields: [],
|
||||
};
|
||||
}
|
||||
case 'expression': {
|
||||
return {
|
||||
listenerType: 'expressionListener',
|
||||
expression: processListener.value,
|
||||
event: processListener.event,
|
||||
fields: [],
|
||||
};
|
||||
}
|
||||
// No default
|
||||
}
|
||||
throw new Error('未知的监听器类型');
|
||||
}
|
||||
|
||||
export const listenerType = {
|
||||
classListener: 'Java 类',
|
||||
expressionListener: '表达式',
|
||||
delegateExpressionListener: '代理表达式',
|
||||
scriptListener: '脚本',
|
||||
};
|
||||
|
||||
export const eventType = {
|
||||
create: '创建',
|
||||
assignment: '指派',
|
||||
complete: '完成',
|
||||
delete: '删除',
|
||||
update: '更新',
|
||||
timeout: '超时',
|
||||
};
|
||||
|
||||
export const fieldType = {
|
||||
string: '字符串',
|
||||
expression: '表达式',
|
||||
};
|
||||
@@ -0,0 +1,526 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
InputNumber,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
APPROVE_METHODS,
|
||||
ApproveMethodType,
|
||||
} from '#/components/simple-process-design/consts';
|
||||
|
||||
defineOptions({ name: 'ElementMultiInstance' });
|
||||
|
||||
const props = defineProps({
|
||||
businessObject: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject<string>('prefix');
|
||||
const loopCharacteristics = ref('');
|
||||
// 默认配置,用来覆盖原始不存在的选项,避免报错
|
||||
const defaultLoopInstanceForm = ref({
|
||||
completionCondition: '',
|
||||
loopCardinality: '',
|
||||
extensionElements: [],
|
||||
asyncAfter: false,
|
||||
asyncBefore: false,
|
||||
exclusive: false,
|
||||
});
|
||||
interface LoopInstanceForm {
|
||||
completionCondition?: string;
|
||||
loopCardinality?: string;
|
||||
extensionElements?: any[];
|
||||
asyncAfter?: boolean;
|
||||
asyncBefore?: boolean;
|
||||
exclusive?: boolean;
|
||||
collection?: string;
|
||||
elementVariable?: string;
|
||||
timeCycle?: string;
|
||||
}
|
||||
|
||||
const loopInstanceForm = ref<LoopInstanceForm>({});
|
||||
const bpmnElement = ref<any>(null);
|
||||
const multiLoopInstance = ref<any>(null);
|
||||
declare global {
|
||||
interface Window {
|
||||
bpmnInstances?: () => any;
|
||||
}
|
||||
}
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const getElementLoop = (businessObject: any): void => {
|
||||
if (!businessObject.loopCharacteristics) {
|
||||
loopCharacteristics.value = 'Null';
|
||||
loopInstanceForm.value = {};
|
||||
return;
|
||||
}
|
||||
if (
|
||||
businessObject.loopCharacteristics.$type ===
|
||||
'bpmn:StandardLoopCharacteristics'
|
||||
) {
|
||||
loopCharacteristics.value = 'StandardLoop';
|
||||
loopInstanceForm.value = {};
|
||||
return;
|
||||
}
|
||||
loopCharacteristics.value = businessObject.loopCharacteristics.isSequential
|
||||
? 'SequentialMultiInstance'
|
||||
: 'ParallelMultiInstance';
|
||||
// 合并配置
|
||||
loopInstanceForm.value = {
|
||||
...defaultLoopInstanceForm.value,
|
||||
...businessObject.loopCharacteristics,
|
||||
completionCondition:
|
||||
businessObject.loopCharacteristics?.completionCondition?.body ?? '',
|
||||
loopCardinality:
|
||||
businessObject.loopCharacteristics?.loopCardinality?.body ?? '',
|
||||
};
|
||||
// 保留当前元素 businessObject 上的 loopCharacteristics 实例
|
||||
multiLoopInstance.value =
|
||||
bpmnInstances().bpmnElement.businessObject.loopCharacteristics;
|
||||
// 更新表单
|
||||
if (
|
||||
businessObject.loopCharacteristics.extensionElements &&
|
||||
businessObject.loopCharacteristics.extensionElements.values &&
|
||||
businessObject.loopCharacteristics.extensionElements.values.length > 0
|
||||
) {
|
||||
loopInstanceForm.value.timeCycle =
|
||||
businessObject.loopCharacteristics.extensionElements.values[0].body;
|
||||
}
|
||||
};
|
||||
|
||||
const changeLoopCharacteristicsType = (type: any): void => {
|
||||
// this.loopInstanceForm = { ...this.defaultLoopInstanceForm }; // 切换类型取消原表单配置
|
||||
// 取消多实例配置
|
||||
if (type === 'Null') {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 配置循环
|
||||
if (type === 'StandardLoop') {
|
||||
const loopCharacteristicsObject = bpmnInstances().moddle.create(
|
||||
'bpmn:StandardLoopCharacteristics',
|
||||
);
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: loopCharacteristicsObject,
|
||||
});
|
||||
multiLoopInstance.value = null;
|
||||
return;
|
||||
}
|
||||
// 时序
|
||||
multiLoopInstance.value =
|
||||
type === 'SequentialMultiInstance'
|
||||
? bpmnInstances().moddle.create('bpmn:MultiInstanceLoopCharacteristics', {
|
||||
isSequential: true,
|
||||
})
|
||||
: bpmnInstances().moddle.create('bpmn:MultiInstanceLoopCharacteristics', {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
collection: '${coll_userList}',
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: toRaw(multiLoopInstance.value),
|
||||
});
|
||||
};
|
||||
|
||||
// 循环基数
|
||||
const updateLoopCardinality = (cardinality: string): void => {
|
||||
let loopCardinality = null;
|
||||
if (cardinality && cardinality.length > 0) {
|
||||
loopCardinality = bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
body: cardinality,
|
||||
});
|
||||
}
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
{
|
||||
loopCardinality,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 完成条件
|
||||
const updateLoopCondition = (condition: string): void => {
|
||||
let completionCondition = null;
|
||||
if (condition && condition.length > 0) {
|
||||
completionCondition = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: condition,
|
||||
},
|
||||
);
|
||||
}
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
{
|
||||
completionCondition,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 重试周期
|
||||
const updateLoopTimeCycle = (timeCycle: string): void => {
|
||||
const extensionElements = bpmnInstances().moddle.create(
|
||||
'bpmn:ExtensionElements',
|
||||
{
|
||||
values: [
|
||||
bpmnInstances().moddle.create(`${prefix}:FailedJobRetryTimeCycle`, {
|
||||
body: timeCycle,
|
||||
}),
|
||||
],
|
||||
},
|
||||
);
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
{
|
||||
extensionElements,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 直接更新的基础信息
|
||||
const updateLoopBase = (): void => {
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
{
|
||||
collection: loopInstanceForm.value.collection || null,
|
||||
elementVariable: loopInstanceForm.value.elementVariable || null,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// 各异步状态
|
||||
const updateLoopAsync = (key: any): void => {
|
||||
const { asyncBefore, asyncAfter } = loopInstanceForm.value;
|
||||
let asyncAttr = Object.create(null);
|
||||
if (!asyncBefore && !asyncAfter) {
|
||||
// this.$set(this.loopInstanceForm, "exclusive", false);
|
||||
loopInstanceForm.value.exclusive = false;
|
||||
asyncAttr = {
|
||||
asyncBefore: false,
|
||||
asyncAfter: false,
|
||||
exclusive: false,
|
||||
extensionElements: null,
|
||||
};
|
||||
} else {
|
||||
// @ts-ignore
|
||||
asyncAttr[key] = loopInstanceForm.value[key];
|
||||
}
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
multiLoopInstance.value,
|
||||
asyncAttr,
|
||||
);
|
||||
};
|
||||
|
||||
const changeConfig = (config: string): void => {
|
||||
switch (config) {
|
||||
case '会签': {
|
||||
changeLoopCharacteristicsType('ParallelMultiInstance');
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }');
|
||||
|
||||
break;
|
||||
}
|
||||
case '依次审批': {
|
||||
changeLoopCharacteristicsType('SequentialMultiInstance');
|
||||
updateLoopCardinality('1');
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }');
|
||||
|
||||
break;
|
||||
}
|
||||
case '或签': {
|
||||
changeLoopCharacteristicsType('ParallelMultiInstance');
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
updateLoopCondition('${ nrOfCompletedInstances > 0 }');
|
||||
|
||||
break;
|
||||
}
|
||||
// No default
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* -----新版本多实例-----
|
||||
*/
|
||||
const approveMethod = ref<ApproveMethodType | undefined>();
|
||||
const approveRatio = ref<number>(100);
|
||||
const otherExtensions = ref<any[]>([]);
|
||||
const getElementLoopNew = (): void => {
|
||||
if (props.type === 'UserTask') {
|
||||
const extensionElements =
|
||||
bpmnElement.value.businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
approveMethod.value = extensionElements.values.find(
|
||||
(ex: any) => ex.$type === `${prefix}:ApproveMethod`,
|
||||
)?.value;
|
||||
|
||||
otherExtensions.value =
|
||||
extensionElements.values.filter(
|
||||
(ex: any) => ex.$type !== `${prefix}:ApproveMethod`,
|
||||
) ?? [];
|
||||
|
||||
if (!approveMethod.value) {
|
||||
approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE;
|
||||
updateLoopCharacteristics();
|
||||
}
|
||||
}
|
||||
};
|
||||
const onApproveMethodChange = (): void => {
|
||||
approveRatio.value = 100;
|
||||
updateLoopCharacteristics();
|
||||
};
|
||||
const onApproveRatioChange = (): void => {
|
||||
updateLoopCharacteristics();
|
||||
};
|
||||
const updateLoopCharacteristics = (): void => {
|
||||
// 根据ApproveMethod生成multiInstanceLoopCharacteristics节点
|
||||
if (approveMethod.value === ApproveMethodType.RANDOM_SELECT_ONE_APPROVE) {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: null,
|
||||
});
|
||||
} else {
|
||||
if (approveMethod.value === ApproveMethodType.APPROVE_BY_RATIO) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
{ isSequential: false, collection: '${coll_userList}' },
|
||||
);
|
||||
multiLoopInstance.value.completionCondition =
|
||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
body: `\${ nrOfCompletedInstances/nrOfInstances >= ${
|
||||
approveRatio.value / 100
|
||||
}}`,
|
||||
});
|
||||
}
|
||||
if (approveMethod.value === ApproveMethodType.ANY_APPROVE) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
{ isSequential: false, collection: '${coll_userList}' },
|
||||
);
|
||||
multiLoopInstance.value.completionCondition =
|
||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
body: '${ nrOfCompletedInstances > 0 }',
|
||||
});
|
||||
}
|
||||
if (approveMethod.value === ApproveMethodType.SEQUENTIAL_APPROVE) {
|
||||
multiLoopInstance.value = bpmnInstances().moddle.create(
|
||||
'bpmn:MultiInstanceLoopCharacteristics',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
{ isSequential: true, collection: '${coll_userList}' },
|
||||
);
|
||||
multiLoopInstance.value.loopCardinality = bpmnInstances().moddle.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: '1',
|
||||
},
|
||||
);
|
||||
multiLoopInstance.value.completionCondition =
|
||||
bpmnInstances().moddle.create('bpmn:FormalExpression', {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
body: '${ nrOfCompletedInstances >= nrOfInstances }',
|
||||
});
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
loopCharacteristics: toRaw(multiLoopInstance.value),
|
||||
});
|
||||
}
|
||||
|
||||
// 添加ApproveMethod到ExtensionElements
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
bpmnInstances().moddle.create(`${prefix}:ApproveMethod`, {
|
||||
value: approveMethod.value,
|
||||
}),
|
||||
],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
multiLoopInstance.value = null;
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
if (val) {
|
||||
nextTick(() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
// getElementLoop(val)
|
||||
getElementLoopNew();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<RadioGroup
|
||||
v-if="type === 'UserTask'"
|
||||
v-model:value="approveMethod"
|
||||
@change="onApproveMethodChange"
|
||||
>
|
||||
<div class="flex-col">
|
||||
<div v-for="(item, index) in APPROVE_METHODS" :key="index">
|
||||
<Radio :value="item.value">
|
||||
{{ item.label }}
|
||||
</Radio>
|
||||
<FormItem prop="approveRatio">
|
||||
<InputNumber
|
||||
v-model:value="approveRatio"
|
||||
:min="10"
|
||||
:max="100"
|
||||
:step="10"
|
||||
size="small"
|
||||
v-if="
|
||||
item.value === ApproveMethodType.APPROVE_BY_RATIO &&
|
||||
approveMethod === ApproveMethodType.APPROVE_BY_RATIO
|
||||
"
|
||||
@change="onApproveRatioChange"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<div v-else>除了UserTask以外节点的多实例待实现</div>
|
||||
<!-- 与Simple设计器配置合并,保留以前的代码 -->
|
||||
<Form :label-col="{ span: 6 }" style="display: none">
|
||||
<FormItem label="快捷配置">
|
||||
<Button size="small" @click="() => changeConfig('依次审批')">
|
||||
依次审批
|
||||
</Button>
|
||||
<Button size="small" @click="() => changeConfig('会签')">会签</Button>
|
||||
<Button size="small" @click="() => changeConfig('或签')">或签</Button>
|
||||
</FormItem>
|
||||
<FormItem label="会签类型">
|
||||
<Select
|
||||
v-model:value="loopCharacteristics"
|
||||
@change="changeLoopCharacteristicsType"
|
||||
>
|
||||
<Select.Option value="ParallelMultiInstance">
|
||||
并行多重事件
|
||||
</Select.Option>
|
||||
<Select.Option value="SequentialMultiInstance">
|
||||
时序多重事件
|
||||
</Select.Option>
|
||||
<Select.Option value="Null">无</Select.Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<template
|
||||
v-if="
|
||||
loopCharacteristics === 'ParallelMultiInstance' ||
|
||||
loopCharacteristics === 'SequentialMultiInstance'
|
||||
"
|
||||
>
|
||||
<FormItem label="循环数量" key="loopCardinality">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.loopCardinality"
|
||||
allow-clear
|
||||
@change="
|
||||
() =>
|
||||
updateLoopCardinality(loopInstanceForm.loopCardinality || '')
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="集合" key="collection" v-show="false">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.collection"
|
||||
allow-clear
|
||||
@change="() => updateLoopBase()"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- add by 芋艿:由于「元素变量」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="元素变量" key="elementVariable" style="display: none">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.elementVariable"
|
||||
allow-clear
|
||||
@change="() => updateLoopBase()"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="完成条件" key="completionCondition">
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.completionCondition"
|
||||
allow-clear
|
||||
@change="
|
||||
() =>
|
||||
updateLoopCondition(loopInstanceForm.completionCondition || '')
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
<!-- add by 芋艿:由于「异步状态」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="异步状态" key="async" style="display: none">
|
||||
<Checkbox
|
||||
v-model:checked="loopInstanceForm.asyncBefore"
|
||||
@change="() => updateLoopAsync('asyncBefore')"
|
||||
>
|
||||
异步前
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
v-model:checked="loopInstanceForm.asyncAfter"
|
||||
@change="() => updateLoopAsync('asyncAfter')"
|
||||
>
|
||||
异步后
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
v-model:checked="loopInstanceForm.exclusive"
|
||||
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
|
||||
@change="() => updateLoopAsync('exclusive')"
|
||||
>
|
||||
排除
|
||||
</Checkbox>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="重试周期"
|
||||
prop="timeCycle"
|
||||
v-if="loopInstanceForm.asyncAfter || loopInstanceForm.asyncBefore"
|
||||
key="timeCycle"
|
||||
>
|
||||
<Input
|
||||
v-model:value="loopInstanceForm.timeCycle"
|
||||
allow-clear
|
||||
@change="
|
||||
() => updateLoopTimeCycle(loopInstanceForm.timeCycle || '')
|
||||
"
|
||||
/>
|
||||
</FormItem>
|
||||
</template>
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,74 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { Input } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ElementOtherConfig' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { Textarea } = Input;
|
||||
|
||||
const documentation = ref('');
|
||||
const bpmnElement = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||
|
||||
const updateDocumentation = () => {
|
||||
(bpmnElement.value && bpmnElement.value.id === props.id) ||
|
||||
(bpmnElement.value = bpmnInstances().elementRegistry.get(props.id));
|
||||
const documentations = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:Documentation',
|
||||
{
|
||||
text: documentation.value,
|
||||
},
|
||||
);
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
documentation: [documentations],
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(id) => {
|
||||
if (id && id.length > 0) {
|
||||
nextTick(() => {
|
||||
const documentations =
|
||||
bpmnInstances().bpmnElement.businessObject?.documentation;
|
||||
documentation.value =
|
||||
documentations && documentations.length > 0
|
||||
? documentations[0].text
|
||||
: '';
|
||||
});
|
||||
} else {
|
||||
documentation.value = '';
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div class="element-property input-property">
|
||||
<div class="element-property__label">元素文档:</div>
|
||||
<div class="element-property__value">
|
||||
<Textarea
|
||||
v-model:value="documentation"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
@change="updateDocumentation"
|
||||
@blur="updateDocumentation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,237 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Modal,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ElementProperties' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const prefix = inject('prefix');
|
||||
// const width = inject('width')
|
||||
|
||||
const elementPropertyList = ref<Array<{ name: string; value: string }>>([]);
|
||||
const propertyForm = ref<{ name?: string; value?: string }>({});
|
||||
const editingPropertyIndex = ref(-1);
|
||||
const propertyFormModelVisible = ref(false);
|
||||
const bpmnElement = ref<any>();
|
||||
const otherExtensionList = ref<any[]>([]);
|
||||
const bpmnElementProperties = ref<any[]>([]);
|
||||
const bpmnElementPropertyList = ref<any[]>([]);
|
||||
const attributeFormRef = ref<any>();
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetAttributesList = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
otherExtensionList.value = []; // 其他扩展配置
|
||||
bpmnElementProperties.value =
|
||||
// bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
|
||||
(ex: any) => {
|
||||
if (ex.$type !== `${prefix}:Properties`) {
|
||||
otherExtensionList.value.push(ex);
|
||||
}
|
||||
return ex.$type === `${prefix}:Properties`;
|
||||
},
|
||||
) ?? [];
|
||||
|
||||
// 保存所有的 扩展属性字段
|
||||
bpmnElementPropertyList.value = bpmnElementProperties.value.flatMap(
|
||||
(current: any) => current.values,
|
||||
);
|
||||
// 复制 显示
|
||||
elementPropertyList.value = structuredClone(
|
||||
bpmnElementPropertyList.value ?? [],
|
||||
);
|
||||
};
|
||||
|
||||
const openAttributesForm = (
|
||||
attr: null | { name: string; value: string },
|
||||
index: number,
|
||||
) => {
|
||||
editingPropertyIndex.value = index;
|
||||
// @ts-ignore
|
||||
propertyForm.value = index === -1 ? {} : structuredClone(attr);
|
||||
propertyFormModelVisible.value = true;
|
||||
nextTick(() => {
|
||||
if (attributeFormRef.value) attributeFormRef.value.clearValidate();
|
||||
});
|
||||
};
|
||||
|
||||
const removeAttributes = (
|
||||
_attr: { name: string; value: string },
|
||||
index: number,
|
||||
) => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确认移除该属性吗?',
|
||||
okText: '确 认',
|
||||
cancelText: '取 消',
|
||||
onOk() {
|
||||
elementPropertyList.value.splice(index, 1);
|
||||
bpmnElementPropertyList.value.splice(index, 1);
|
||||
// 新建一个属性字段的保存列表
|
||||
const propertiesObject = bpmnInstances().moddle.create(
|
||||
`${prefix}:Properties`,
|
||||
{
|
||||
values: bpmnElementPropertyList.value,
|
||||
},
|
||||
);
|
||||
updateElementExtensions(propertiesObject);
|
||||
resetAttributesList();
|
||||
},
|
||||
onCancel() {
|
||||
// console.info('操作取消');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const saveAttribute = () => {
|
||||
// console.log(propertyForm.value, 'propertyForm.value');
|
||||
const { name, value } = propertyForm.value;
|
||||
if (editingPropertyIndex.value === -1) {
|
||||
// 新建属性字段
|
||||
const newPropertyObject = bpmnInstances().moddle.create(
|
||||
`${prefix}:Property`,
|
||||
{
|
||||
name,
|
||||
value,
|
||||
},
|
||||
);
|
||||
// 新建一个属性字段的保存列表
|
||||
const propertiesObject = bpmnInstances().moddle.create(
|
||||
`${prefix}:Properties`,
|
||||
{
|
||||
values: [...bpmnElementPropertyList.value, newPropertyObject],
|
||||
},
|
||||
);
|
||||
updateElementExtensions(propertiesObject);
|
||||
} else {
|
||||
bpmnInstances().modeling.updateModdleProperties(
|
||||
toRaw(bpmnElement.value),
|
||||
toRaw(bpmnElementPropertyList.value)[toRaw(editingPropertyIndex.value)],
|
||||
{
|
||||
name,
|
||||
value,
|
||||
},
|
||||
);
|
||||
}
|
||||
propertyFormModelVisible.value = false;
|
||||
resetAttributesList();
|
||||
};
|
||||
|
||||
const updateElementExtensions = (properties: any) => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [...otherExtensionList.value, properties],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
if (val) {
|
||||
val && val.length > 0 && resetAttributesList();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Table :data="elementPropertyList" :scroll="{ y: 240 }" bordered>
|
||||
<TableColumn title="序号" width="50">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="属性名"
|
||||
data-index="name"
|
||||
:min-width="100"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="属性值"
|
||||
data-index="value"
|
||||
:min-width="100"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn title="操作" width="110">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
type="link"
|
||||
@click="openAttributesForm(record, index)"
|
||||
size="small"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="removeAttributes(record, index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
<div class="element-drawer__button">
|
||||
<Button type="primary" @click="openAttributesForm(null, -1)">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
添加属性
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
v-model:open="propertyFormModelVisible"
|
||||
title="属性配置"
|
||||
:width="600"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="propertyForm"
|
||||
ref="attributeFormRef"
|
||||
:label-col="{ span: 6 }"
|
||||
>
|
||||
<FormItem label="属性名:" name="name">
|
||||
<Input v-model:value="propertyForm.name" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem label="属性值:" name="value">
|
||||
<Input v-model:value="propertyForm.value" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button @click="propertyFormModelVisible = false">取 消</Button>
|
||||
<Button type="primary" @click="saveAttribute">确 定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,178 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
message,
|
||||
Modal,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'SignalAndMassage' });
|
||||
const signalList = ref<any[]>([]);
|
||||
const messageList = ref<any[]>([]);
|
||||
const dialogVisible = ref(false);
|
||||
const modelType = ref('');
|
||||
const modelObjectForm = ref<any>({});
|
||||
const rootElements = ref();
|
||||
const messageIdMap = ref();
|
||||
const signalIdMap = ref();
|
||||
const modelConfig = computed(() => {
|
||||
return modelType.value === 'message'
|
||||
? { title: '创建消息', idLabel: '消息ID', nameLabel: '消息名称' }
|
||||
: { title: '创建信号', idLabel: '信号ID', nameLabel: '信号名称' };
|
||||
});
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const initDataList = () => {
|
||||
// console.log(window, 'window');
|
||||
rootElements.value = bpmnInstances().modeler.getDefinitions().rootElements;
|
||||
messageIdMap.value = {};
|
||||
signalIdMap.value = {};
|
||||
messageList.value = [];
|
||||
signalList.value = [];
|
||||
rootElements.value.forEach((el: any) => {
|
||||
if (el.$type === 'bpmn:Message') {
|
||||
messageIdMap.value[el.id] = true;
|
||||
messageList.value.push({ ...el });
|
||||
}
|
||||
if (el.$type === 'bpmn:Signal') {
|
||||
signalIdMap.value[el.id] = true;
|
||||
signalList.value.push({ ...el });
|
||||
}
|
||||
});
|
||||
};
|
||||
const openModel = (type: any) => {
|
||||
modelType.value = type;
|
||||
modelObjectForm.value = {};
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
const addNewObject = () => {
|
||||
if (modelType.value === 'message') {
|
||||
if (messageIdMap.value[modelObjectForm.value.id]) {
|
||||
message.error('该消息已存在,请修改id后重新保存');
|
||||
}
|
||||
const messageRef = bpmnInstances().moddle.create(
|
||||
'bpmn:Message',
|
||||
modelObjectForm.value,
|
||||
);
|
||||
rootElements.value.push(messageRef);
|
||||
} else {
|
||||
if (signalIdMap.value[modelObjectForm.value.id]) {
|
||||
message.error('该信号已存在,请修改id后重新保存');
|
||||
}
|
||||
const signalRef = bpmnInstances().moddle.create(
|
||||
'bpmn:Signal',
|
||||
modelObjectForm.value,
|
||||
);
|
||||
rootElements.value.push(signalRef);
|
||||
}
|
||||
dialogVisible.value = false;
|
||||
initDataList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initDataList();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div class="panel-tab__content--title">
|
||||
<span>
|
||||
<IconifyIcon icon="ep:menu" style="margin-right: 8px; color: #555" />
|
||||
消息列表
|
||||
</span>
|
||||
<Button type="primary" title="创建新消息" @click="openModel('message')">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
创建新消息
|
||||
</Button>
|
||||
</div>
|
||||
<Table :data-source="messageList" :bordered="true" :pagination="false">
|
||||
<TableColumn title="序号" width="60px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="消息ID"
|
||||
data-index="id"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="消息名称"
|
||||
data-index="name"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
</Table>
|
||||
<div
|
||||
class="panel-tab__content--title"
|
||||
style="padding-top: 8px; margin-top: 8px; border-top: 1px solid #eee"
|
||||
>
|
||||
<span>
|
||||
<IconifyIcon icon="ep:menu" style="margin-right: 8px; color: #555">
|
||||
信号列表
|
||||
</IconifyIcon>
|
||||
</span>
|
||||
<Button type="primary" title="创建新信号" @click="openModel('signal')">
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ep:plus" />
|
||||
</template>
|
||||
创建新信号
|
||||
</Button>
|
||||
</div>
|
||||
<Table :data-source="signalList" :bordered="true" :pagination="false">
|
||||
<TableColumn title="序号" width="60px">
|
||||
<template #default="{ index }">
|
||||
{{ index + 1 }}
|
||||
</template>
|
||||
</TableColumn>
|
||||
<TableColumn
|
||||
title="信号ID"
|
||||
data-index="id"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
<TableColumn
|
||||
title="信号名称"
|
||||
data-index="name"
|
||||
:width="300"
|
||||
:ellipsis="{ showTitle: true }"
|
||||
/>
|
||||
</Table>
|
||||
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="modelConfig.title"
|
||||
:mask-closable="false"
|
||||
width="400px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form
|
||||
:model="modelObjectForm"
|
||||
:label-col="{ span: 9 }"
|
||||
:wrapper-col="{ span: 15 }"
|
||||
>
|
||||
<FormItem :label="modelConfig.idLabel">
|
||||
<Input v-model:value="modelObjectForm.id" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem :label="modelConfig.nameLabel">
|
||||
<Input v-model:value="modelObjectForm.name" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button @click="dialogVisible = false">取 消</Button>
|
||||
<Button type="primary" @click="addNewObject">保 存</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,92 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { Checkbox, Form, FormItem } from 'ant-design-vue';
|
||||
|
||||
import { installedComponent } from './data';
|
||||
|
||||
defineOptions({ name: 'ElementTaskConfig' });
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const taskConfigForm = ref({
|
||||
asyncAfter: false,
|
||||
asyncBefore: false,
|
||||
exclusive: false,
|
||||
});
|
||||
const witchTaskComponent = ref();
|
||||
|
||||
const bpmnElement = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||
const changeTaskAsync = () => {
|
||||
if (!taskConfigForm.value.asyncBefore && !taskConfigForm.value.asyncAfter) {
|
||||
taskConfigForm.value.exclusive = false;
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(bpmnInstances().bpmnElement, {
|
||||
...taskConfigForm.value,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
taskConfigForm.value.asyncBefore =
|
||||
bpmnElement.value?.businessObject?.asyncBefore;
|
||||
taskConfigForm.value.asyncAfter =
|
||||
bpmnElement.value?.businessObject?.asyncAfter;
|
||||
taskConfigForm.value.exclusive =
|
||||
bpmnElement.value?.businessObject?.exclusive;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
watch(
|
||||
() => props.type,
|
||||
() => {
|
||||
if (props.type) {
|
||||
// @ts-ignore
|
||||
witchTaskComponent.value = installedComponent[props.type].component;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<Form :label-col="{ span: 9 }" :wrapper-col="{ span: 15 }">
|
||||
<!-- add by 芋艿:由于「异步延续」暂时用不到,所以这里 display 为 none -->
|
||||
<FormItem label="异步延续" style="display: none">
|
||||
<Checkbox
|
||||
v-model:checked="taskConfigForm.asyncBefore"
|
||||
@change="changeTaskAsync"
|
||||
>
|
||||
异步前
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
v-model:checked="taskConfigForm.asyncAfter"
|
||||
@change="changeTaskAsync"
|
||||
>
|
||||
异步后
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
v-model:checked="taskConfigForm.exclusive"
|
||||
v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore"
|
||||
@change="changeTaskAsync"
|
||||
>
|
||||
排除
|
||||
</Checkbox>
|
||||
</FormItem>
|
||||
<component :is="witchTaskComponent" v-bind="$props" />
|
||||
</Form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
import CallActivity from './task-components/CallActivity.vue';
|
||||
import ReceiveTask from './task-components/ReceiveTask.vue';
|
||||
import ScriptTask from './task-components/ScriptTask.vue';
|
||||
import ServiceTask from './task-components/ServiceTask.vue';
|
||||
import UserTask from './task-components/UserTask.vue';
|
||||
|
||||
export const installedComponent = {
|
||||
UserTask: {
|
||||
name: '用户任务',
|
||||
component: UserTask,
|
||||
},
|
||||
ServiceTask: {
|
||||
name: '服务任务',
|
||||
component: ServiceTask,
|
||||
},
|
||||
ScriptTask: {
|
||||
name: '脚本任务',
|
||||
component: ScriptTask,
|
||||
},
|
||||
ReceiveTask: {
|
||||
name: '接收任务',
|
||||
component: ReceiveTask,
|
||||
},
|
||||
CallActivity: {
|
||||
name: '调用活动',
|
||||
component: CallActivity,
|
||||
},
|
||||
};
|
||||
|
||||
export const getTaskCollapseItemName = (
|
||||
elementType: keyof typeof installedComponent,
|
||||
) => {
|
||||
return installedComponent[elementType].name;
|
||||
};
|
||||
|
||||
export const isTaskCollapseItemShow = (
|
||||
elementType: keyof typeof installedComponent,
|
||||
) => {
|
||||
return installedComponent[elementType];
|
||||
};
|
||||
@@ -0,0 +1,361 @@
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { alert } from '@vben/common-ui';
|
||||
import { PlusOutlined } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Modal,
|
||||
Switch,
|
||||
Table,
|
||||
TableColumn,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
interface FormData {
|
||||
processInstanceName: string;
|
||||
calledElement: string;
|
||||
inheritVariables: boolean;
|
||||
businessKey: string;
|
||||
inheritBusinessKey: boolean;
|
||||
calledElementType: string;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'CallActivity' });
|
||||
const props = defineProps({
|
||||
id: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
|
||||
const formData = ref<FormData>({
|
||||
processInstanceName: '',
|
||||
calledElement: '',
|
||||
inheritVariables: false,
|
||||
businessKey: '',
|
||||
inheritBusinessKey: false,
|
||||
calledElementType: 'key',
|
||||
});
|
||||
const inVariableList = ref<any[]>([]);
|
||||
const outVariableList = ref<any[]>([]);
|
||||
const variableType = ref<string>(); // 参数类型
|
||||
const editingVariableIndex = ref<number>(-1); // 编辑参数下标
|
||||
const variableDialogVisible = ref<boolean>(false);
|
||||
const varialbeFormRef = ref<any>();
|
||||
const varialbeFormData = ref<{
|
||||
source: string;
|
||||
target: string;
|
||||
}>({
|
||||
source: '',
|
||||
target: '',
|
||||
});
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
const bpmnElement = ref<any>();
|
||||
const otherExtensionList = ref<any[]>([]);
|
||||
|
||||
const initCallActivity = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
// console.log(bpmnElement.value.businessObject, 'callActivity');
|
||||
|
||||
// 初始化所有配置项
|
||||
Object.keys(formData.value).forEach((key: string) => {
|
||||
// @ts-ignore
|
||||
formData.value[key] =
|
||||
bpmnElement.value.businessObject[key] ??
|
||||
formData.value[key as keyof FormData];
|
||||
});
|
||||
|
||||
otherExtensionList.value = []; // 其他扩展配置
|
||||
inVariableList.value.length = 0;
|
||||
outVariableList.value.length = 0;
|
||||
// 初始化输入参数
|
||||
bpmnElement.value.businessObject?.extensionElements?.values?.forEach(
|
||||
(ex: any) => {
|
||||
if (ex.$type === `${prefix}:In`) {
|
||||
inVariableList.value.push(ex);
|
||||
} else if (ex.$type === `${prefix}:Out`) {
|
||||
outVariableList.value.push(ex);
|
||||
} else {
|
||||
otherExtensionList.value.push(ex);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 默认添加
|
||||
// bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
// calledElementType: 'key'
|
||||
// })
|
||||
};
|
||||
|
||||
const updateCallActivityAttr = (attr: keyof FormData) => {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
[attr]: formData.value[attr],
|
||||
});
|
||||
};
|
||||
|
||||
const openVariableForm = (type: string, data: any, index: number) => {
|
||||
editingVariableIndex.value = index;
|
||||
variableType.value = type;
|
||||
varialbeFormData.value = index === -1 ? {} : { ...data };
|
||||
variableDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const removeVariable = async (type: string, index: number) => {
|
||||
try {
|
||||
await alert('是否确认删除?');
|
||||
if (type === 'in') {
|
||||
inVariableList.value.splice(index, 1);
|
||||
}
|
||||
if (type === 'out') {
|
||||
outVariableList.value.splice(index, 1);
|
||||
}
|
||||
updateElementExtensions();
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const saveVariable = () => {
|
||||
if (editingVariableIndex.value === -1) {
|
||||
if (variableType.value === 'in') {
|
||||
inVariableList.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:In`, {
|
||||
...varialbeFormData.value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (variableType.value === 'out') {
|
||||
outVariableList.value.push(
|
||||
bpmnInstances().moddle.create(`${prefix}:Out`, {
|
||||
...varialbeFormData.value,
|
||||
}),
|
||||
);
|
||||
}
|
||||
updateElementExtensions();
|
||||
} else {
|
||||
if (variableType.value === 'in') {
|
||||
inVariableList.value[editingVariableIndex.value].source =
|
||||
varialbeFormData.value.source;
|
||||
inVariableList.value[editingVariableIndex.value].target =
|
||||
varialbeFormData.value.target;
|
||||
}
|
||||
if (variableType.value === 'out') {
|
||||
outVariableList.value[editingVariableIndex.value].source =
|
||||
varialbeFormData.value.source;
|
||||
outVariableList.value[editingVariableIndex.value].target =
|
||||
varialbeFormData.value.target;
|
||||
}
|
||||
}
|
||||
variableDialogVisible.value = false;
|
||||
};
|
||||
|
||||
const updateElementExtensions = () => {
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...inVariableList.value,
|
||||
...outVariableList.value,
|
||||
...otherExtensionList.value,
|
||||
],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
(val) => {
|
||||
val &&
|
||||
val.length > 0 &&
|
||||
nextTick(() => {
|
||||
initCallActivity();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<FormItem label="实例名称">
|
||||
<Input
|
||||
v-model:value="formData.processInstanceName"
|
||||
allow-clear
|
||||
placeholder="请输入实例名称"
|
||||
@change="updateCallActivityAttr('processInstanceName')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<!-- TODO 需要可选择已存在的流程 -->
|
||||
<FormItem label="被调用流程">
|
||||
<Input
|
||||
v-model:value="formData.calledElement"
|
||||
allow-clear
|
||||
placeholder="请输入被调用流程"
|
||||
@change="updateCallActivityAttr('calledElement')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="继承变量">
|
||||
<Switch
|
||||
v-model:checked="formData.inheritVariables"
|
||||
@change="updateCallActivityAttr('inheritVariables')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="继承业务键">
|
||||
<Switch
|
||||
v-model:checked="formData.inheritBusinessKey"
|
||||
@change="updateCallActivityAttr('inheritBusinessKey')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem v-if="!formData.inheritBusinessKey" label="业务键表达式">
|
||||
<Input
|
||||
v-model:value="formData.businessKey"
|
||||
allow-clear
|
||||
placeholder="请输入业务键表达式"
|
||||
@change="updateCallActivityAttr('businessKey')"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<Divider />
|
||||
<div>
|
||||
<div class="mb-10px flex">
|
||||
<span>输入参数</span>
|
||||
<Button
|
||||
class="ml-auto"
|
||||
type="primary"
|
||||
:icon="PlusOutlined"
|
||||
title="添加参数"
|
||||
size="small"
|
||||
@click="openVariableForm('in', null, -1)"
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
:data-source="inVariableList"
|
||||
:scroll="{ y: 240 }"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<TableColumn
|
||||
title="源"
|
||||
data-index="source"
|
||||
:min-width="100"
|
||||
:ellipsis="true"
|
||||
/>
|
||||
<TableColumn
|
||||
title="目标"
|
||||
data-index="target"
|
||||
:min-width="100"
|
||||
:ellipsis="true"
|
||||
/>
|
||||
<TableColumn title="操作" :width="110">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
type="link"
|
||||
@click="openVariableForm('in', record, index)"
|
||||
size="small"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="removeVariable('in', index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<div>
|
||||
<div class="mb-10px flex">
|
||||
<span>输出参数</span>
|
||||
<Button
|
||||
class="ml-auto"
|
||||
type="primary"
|
||||
:icon="PlusOutlined"
|
||||
title="添加参数"
|
||||
size="small"
|
||||
@click="openVariableForm('out', null, -1)"
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
:data-source="outVariableList"
|
||||
:scroll="{ y: 240 }"
|
||||
bordered
|
||||
:pagination="false"
|
||||
>
|
||||
<TableColumn
|
||||
title="源"
|
||||
data-index="source"
|
||||
:min-width="100"
|
||||
:ellipsis="true"
|
||||
/>
|
||||
<TableColumn
|
||||
title="目标"
|
||||
data-index="target"
|
||||
:min-width="100"
|
||||
:ellipsis="true"
|
||||
/>
|
||||
<TableColumn title="操作" :width="110">
|
||||
<template #default="{ record, index }">
|
||||
<Button
|
||||
type="link"
|
||||
@click="openVariableForm('out', record, index)"
|
||||
size="small"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
@click="removeVariable('out', index)"
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<!-- 添加或修改参数 -->
|
||||
<Modal
|
||||
v-model:open="variableDialogVisible"
|
||||
title="参数配置"
|
||||
:width="600"
|
||||
:destroy-on-close="true"
|
||||
@ok="saveVariable"
|
||||
@cancel="variableDialogVisible = false"
|
||||
>
|
||||
<Form
|
||||
:model="varialbeFormData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
ref="varialbeFormRef"
|
||||
>
|
||||
<FormItem label="源:" name="source">
|
||||
<Input v-model:value="varialbeFormData.source" allow-clear />
|
||||
</FormItem>
|
||||
<FormItem label="目标:" name="target">
|
||||
<Input v-model:value="varialbeFormData.target" allow-clear />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<!-- 表达式选择 -->
|
||||
<script setup lang="ts">
|
||||
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
|
||||
|
||||
import { reactive, ref } from 'vue';
|
||||
|
||||
import { CommonStatusEnum } from '@vben/constants';
|
||||
|
||||
import { Button, Modal, Pagination, Table, TableColumn } from 'ant-design-vue';
|
||||
|
||||
import { getProcessExpressionPage } from '#/api/bpm/processExpression';
|
||||
import { ContentWrap } from '#/components/content-wrap';
|
||||
|
||||
/** BPM 流程 表单 */
|
||||
defineOptions({ name: 'ProcessExpressionDialog' });
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['select']);
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const loading = ref(true); // 列表的加载中
|
||||
const list = ref<BpmProcessExpressionApi.ProcessExpression[]>([]); // 列表的数据
|
||||
const total = ref(0); // 列表的总页数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
});
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = (type: string) => {
|
||||
queryParams.pageNo = 1;
|
||||
queryParams.type = type;
|
||||
getList();
|
||||
dialogVisible.value = true;
|
||||
};
|
||||
defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getProcessExpressionPage(queryParams);
|
||||
list.value = data.list;
|
||||
total.value = data.total;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 定义 select 事件,用于操作成功后的回调
|
||||
const select = async (row: BpmProcessExpressionApi.ProcessExpression) => {
|
||||
dialogVisible.value = false;
|
||||
// 发送操作成功的事件
|
||||
emit('select', row);
|
||||
};
|
||||
|
||||
// const handleCancel = () => {
|
||||
// dialogVisible.value = false;
|
||||
// };
|
||||
</script>
|
||||
<template>
|
||||
<Modal
|
||||
title="请选择表达式"
|
||||
v-model:open="dialogVisible"
|
||||
width="1024px"
|
||||
:footer="null"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:loading="loading"
|
||||
:data-source="list"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 'max-content' }"
|
||||
>
|
||||
<TableColumn title="名字" align="center" data-index="name" />
|
||||
<TableColumn title="表达式" align="center" data-index="expression" />
|
||||
<TableColumn title="操作" align="center">
|
||||
<template #default="{ record }">
|
||||
<Button type="primary" @click="select(record)"> 选择 </Button>
|
||||
</template>
|
||||
</TableColumn>
|
||||
</Table>
|
||||
<!-- 分页 -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current="queryParams.pageNo"
|
||||
v-model:page-size="queryParams.pageSize"
|
||||
show-size-changer
|
||||
@change="getList"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,156 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
h,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { PlusOutlined } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
message,
|
||||
Modal,
|
||||
Select,
|
||||
SelectOption,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ReceiveTask' });
|
||||
const props = defineProps({
|
||||
id: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const bindMessageId = ref('');
|
||||
const newMessageForm = ref<Record<string, any>>({});
|
||||
const messageMap = ref<Record<string, any>>({});
|
||||
const messageModelVisible = ref(false);
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnMessageRefsMap = ref<Record<string, any>>();
|
||||
const bpmnRootElements = ref<any>();
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||
const getBindMessage = () => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
bindMessageId.value =
|
||||
bpmnElement.value.businessObject?.messageRef?.id || '-1';
|
||||
};
|
||||
const openMessageModel = () => {
|
||||
messageModelVisible.value = true;
|
||||
newMessageForm.value = {};
|
||||
};
|
||||
const createNewMessage = () => {
|
||||
if (messageMap.value[newMessageForm.value.id]) {
|
||||
message.error('该消息已存在,请修改id后重新保存');
|
||||
return;
|
||||
}
|
||||
const newMessage = bpmnInstances().moddle.create(
|
||||
'bpmn:Message',
|
||||
newMessageForm.value,
|
||||
);
|
||||
bpmnRootElements.value.push(newMessage);
|
||||
messageMap.value[newMessageForm.value.id] = newMessageForm.value.name;
|
||||
// @ts-ignore
|
||||
bpmnMessageRefsMap.value?.[newMessageForm.value.id] = newMessage;
|
||||
messageModelVisible.value = false;
|
||||
};
|
||||
const updateTaskMessage = (messageId: string) => {
|
||||
if (messageId === '-1') {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
messageRef: null,
|
||||
});
|
||||
} else {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
messageRef: bpmnMessageRefsMap.value?.[messageId],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
bpmnMessageRefsMap.value = Object.create(null);
|
||||
bpmnRootElements.value =
|
||||
bpmnInstances().modeler.getDefinitions().rootElements;
|
||||
bpmnRootElements.value
|
||||
.filter((el: any) => el.$type === 'bpmn:Message')
|
||||
.forEach((m: any) => {
|
||||
// @ts-ignore
|
||||
bpmnMessageRefsMap.value?.[m.id] = m;
|
||||
messageMap.value[m.id] = m.name;
|
||||
});
|
||||
messageMap.value['-1'] = '无';
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
// bpmnElement.value = bpmnInstances().bpmnElement
|
||||
nextTick(() => {
|
||||
getBindMessage();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="margin-top: 16px">
|
||||
<Form.Item label="消息实例">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
"
|
||||
>
|
||||
<Select
|
||||
v-model:value="bindMessageId"
|
||||
@change="(value: any) => updateTaskMessage(value)"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="key in Object.keys(messageMap)"
|
||||
:value="key"
|
||||
:label="messageMap[key]"
|
||||
:key="key"
|
||||
/>
|
||||
</Select>
|
||||
<Button
|
||||
type="primary"
|
||||
:icon="h(PlusOutlined)"
|
||||
style="margin-left: 8px"
|
||||
@click="openMessageModel"
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Modal
|
||||
v-model:open="messageModelVisible"
|
||||
:mask-closable="false"
|
||||
title="创建新消息"
|
||||
width="400px"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<Form :model="newMessageForm" size="small" :label-col="{ span: 6 }">
|
||||
<Form.Item label="消息ID">
|
||||
<Input v-model:value="newMessageForm.id" allow-clear />
|
||||
</Form.Item>
|
||||
<Form.Item label="消息名称">
|
||||
<Input v-model:value="newMessageForm.name" allow-clear />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button size="small" type="primary" @click="createNewMessage">
|
||||
确 认
|
||||
</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,129 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
defineOptions,
|
||||
defineProps,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
FormItem,
|
||||
Input,
|
||||
Select,
|
||||
SelectOption,
|
||||
Textarea,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ScriptTask' });
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const defaultTaskForm = ref({
|
||||
scriptFormat: '',
|
||||
script: '',
|
||||
resource: '',
|
||||
resultVariable: '',
|
||||
});
|
||||
const scriptTaskForm = ref<any>({});
|
||||
const bpmnElement = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetTaskForm = () => {
|
||||
for (const key in defaultTaskForm.value) {
|
||||
// @ts-ignore
|
||||
scriptTaskForm.value[key] =
|
||||
bpmnElement.value?.businessObject[
|
||||
key as keyof typeof defaultTaskForm.value
|
||||
] || defaultTaskForm.value[key as keyof typeof defaultTaskForm.value];
|
||||
}
|
||||
scriptTaskForm.value.scriptType = scriptTaskForm.value.script
|
||||
? 'inline'
|
||||
: 'external';
|
||||
};
|
||||
const updateElementTask = () => {
|
||||
const taskAttr = Object.create(null);
|
||||
taskAttr.scriptFormat = scriptTaskForm.value.scriptFormat || null;
|
||||
taskAttr.resultVariable = scriptTaskForm.value.resultVariable || null;
|
||||
if (scriptTaskForm.value.scriptType === 'inline') {
|
||||
taskAttr.script = scriptTaskForm.value.script || null;
|
||||
taskAttr.resource = null;
|
||||
} else {
|
||||
taskAttr.resource = scriptTaskForm.value.resource || null;
|
||||
taskAttr.script = null;
|
||||
}
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), taskAttr);
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
nextTick(() => {
|
||||
resetTaskForm();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-4">
|
||||
<FormItem label="脚本格式">
|
||||
<Input
|
||||
v-model:value="scriptTaskForm.scriptFormat"
|
||||
allow-clear
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="脚本类型">
|
||||
<Select v-model:value="scriptTaskForm.scriptType">
|
||||
<SelectOption value="inline">内联脚本</SelectOption>
|
||||
<SelectOption value="external">外部资源</SelectOption>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="脚本" v-show="scriptTaskForm.scriptType === 'inline'">
|
||||
<Textarea
|
||||
v-model:value="scriptTaskForm.script"
|
||||
:auto-size="{ minRows: 2, maxRows: 4 }"
|
||||
allow-clear
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="资源地址"
|
||||
v-show="scriptTaskForm.scriptType === 'external'"
|
||||
>
|
||||
<Input
|
||||
v-model:value="scriptTaskForm.resource"
|
||||
allow-clear
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="结果变量">
|
||||
<Input
|
||||
v-model:value="scriptTaskForm.resultVariable"
|
||||
allow-clear
|
||||
@input="updateElementTask()"
|
||||
@change="updateElementTask()"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,111 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { FormItem, Input, Select } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'ServiceTask' });
|
||||
const props = defineProps({
|
||||
id: { type: String, default: '' },
|
||||
type: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const defaultTaskForm = ref({
|
||||
executeType: '',
|
||||
class: '',
|
||||
expression: '',
|
||||
delegateExpression: '',
|
||||
});
|
||||
|
||||
const serviceTaskForm = ref<any>({});
|
||||
const bpmnElement = ref();
|
||||
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances;
|
||||
|
||||
const resetTaskForm = () => {
|
||||
for (const key in defaultTaskForm.value) {
|
||||
const value =
|
||||
// @ts-ignore
|
||||
bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key];
|
||||
serviceTaskForm.value[key] = value;
|
||||
if (value) {
|
||||
serviceTaskForm.value.executeType = key;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateElementTask = () => {
|
||||
const taskAttr = Object.create(null);
|
||||
const type = serviceTaskForm.value.executeType;
|
||||
for (const key in serviceTaskForm.value) {
|
||||
if (key !== 'executeType' && key !== type) taskAttr[key] = null;
|
||||
}
|
||||
taskAttr[type] = serviceTaskForm.value[type] || '';
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), taskAttr);
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
nextTick(() => {
|
||||
resetTaskForm();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<FormItem label="执行类型" key="executeType">
|
||||
<Select
|
||||
v-model:value="serviceTaskForm.executeType"
|
||||
:options="[
|
||||
{ label: 'Java类', value: 'class' },
|
||||
{ label: '表达式', value: 'expression' },
|
||||
{ label: '代理表达式', value: 'delegateExpression' },
|
||||
]"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="serviceTaskForm.executeType === 'class'"
|
||||
label="Java类"
|
||||
name="class"
|
||||
key="execute-class"
|
||||
>
|
||||
<Input
|
||||
v-model:value="serviceTaskForm.class"
|
||||
allow-clear
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="serviceTaskForm.executeType === 'expression'"
|
||||
label="表达式"
|
||||
name="expression"
|
||||
key="execute-expression"
|
||||
>
|
||||
<Input
|
||||
v-model:value="serviceTaskForm.expression"
|
||||
allow-clear
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="serviceTaskForm.executeType === 'delegateExpression'"
|
||||
label="代理表达式"
|
||||
name="delegateExpression"
|
||||
key="execute-delegate"
|
||||
>
|
||||
<Input
|
||||
v-model:value="serviceTaskForm.delegateExpression"
|
||||
allow-clear
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,563 @@
|
||||
<script lang="ts" setup>
|
||||
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
|
||||
import type { BpmUserGroupApi } from '#/api/bpm/userGroup';
|
||||
import type { SystemPostApi } from '#/api/system/post';
|
||||
import type { SystemRoleApi } from '#/api/system/role';
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import {
|
||||
computed,
|
||||
h,
|
||||
inject,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
toRaw,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { SelectOutlined } from '@vben/icons';
|
||||
import { handleTree } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormItem,
|
||||
Select,
|
||||
SelectOption,
|
||||
Textarea,
|
||||
TreeSelect,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { getUserGroupSimpleList } from '#/api/bpm/userGroup';
|
||||
import { getSimpleDeptList } from '#/api/system/dept';
|
||||
import { getSimplePostList } from '#/api/system/post';
|
||||
import { getSimpleRoleList } from '#/api/system/role';
|
||||
import { getSimpleUserList } from '#/api/system/user';
|
||||
import {
|
||||
CANDIDATE_STRATEGY,
|
||||
CandidateStrategy,
|
||||
FieldPermissionType,
|
||||
MULTI_LEVEL_DEPT,
|
||||
} from '#/components/simple-process-design/consts';
|
||||
import { useFormFieldsPermission } from '#/components/simple-process-design/helpers';
|
||||
|
||||
import ProcessExpressionDialog from './ProcessExpressionDialog.vue';
|
||||
|
||||
defineOptions({ name: 'UserTask' });
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const prefix = inject('prefix');
|
||||
const userTaskForm = ref({
|
||||
candidateStrategy: undefined, // 分配规则
|
||||
candidateParam: [], // 分配选项
|
||||
skipExpression: '', // 跳过表达式
|
||||
});
|
||||
const bpmnElement = ref<any>();
|
||||
const bpmnInstances = () => (window as Record<string, any>)?.bpmnInstances;
|
||||
|
||||
const roleOptions = ref<SystemRoleApi.Role[]>([]); // 角色列表
|
||||
const deptTreeOptions = ref<any>(); // 部门树
|
||||
const postOptions = ref<SystemPostApi.Post[]>([]); // 岗位列表
|
||||
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||
const userGroupOptions = ref<BpmUserGroupApi.UserGroup[]>([]); // 用户组列表
|
||||
const treeRef = ref<any>();
|
||||
|
||||
const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ);
|
||||
|
||||
// 定义 TreeSelect 的默认属性映射
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
};
|
||||
// 表单内用户字段选项, 必须是必填和用户选择器
|
||||
const userFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'UserSelect');
|
||||
});
|
||||
// 表单内部门字段选项, 必须是必填和部门选择器
|
||||
const deptFieldOnFormOptions = computed(() => {
|
||||
return formFieldOptions.filter((item) => item.type === 'DeptSelect');
|
||||
});
|
||||
|
||||
const deptLevel = ref(1);
|
||||
const deptLevelLabel = computed(() => {
|
||||
let label = '部门负责人来源';
|
||||
if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
label = `${label}(指定部门向上)`;
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
|
||||
) {
|
||||
label = `${label}(表单内部门向上)`;
|
||||
} else {
|
||||
label = `${label}(发起人部门向上)`;
|
||||
}
|
||||
return label;
|
||||
});
|
||||
|
||||
const otherExtensions = ref<any>();
|
||||
|
||||
const resetTaskForm = () => {
|
||||
const businessObject = bpmnElement.value.businessObject;
|
||||
if (!businessObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionElements =
|
||||
businessObject?.extensionElements ??
|
||||
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
|
||||
userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:CandidateStrategy`,
|
||||
)?.[0]?.value;
|
||||
const candidateParamStr = extensionElements.values?.filter(
|
||||
(ex: any) => ex.$type === `${prefix}:CandidateParam`,
|
||||
)?.[0]?.value;
|
||||
if (candidateParamStr && candidateParamStr.length > 0) {
|
||||
// eslint-disable-next-line unicorn/prefer-switch
|
||||
if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
|
||||
// 特殊:流程表达式,只有一个 input 输入框
|
||||
// @ts-ignore
|
||||
userTaskForm.value.candidateParam = [candidateParamStr];
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
// 特殊:多级不部门负责人,需要通过'|'分割
|
||||
userTaskForm.value.candidateParam = candidateParamStr
|
||||
.split('|')[0]
|
||||
.split(',')
|
||||
.map((item: any) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
const num = Number(item);
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
|
||||
? item
|
||||
: num;
|
||||
});
|
||||
deptLevel.value = +candidateParamStr.split('|')[1];
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
// @ts-ignore
|
||||
userTaskForm.value.candidateParam = +candidateParamStr;
|
||||
deptLevel.value = +candidateParamStr;
|
||||
} else if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.FORM_DEPT_LEADER
|
||||
) {
|
||||
userTaskForm.value.candidateParam = candidateParamStr.split('|')[0];
|
||||
deptLevel.value = +candidateParamStr.split('|')[1];
|
||||
} else {
|
||||
userTaskForm.value.candidateParam = candidateParamStr
|
||||
.split(',')
|
||||
.map((item: any) => {
|
||||
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
|
||||
const num = Number(item);
|
||||
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
|
||||
? item
|
||||
: num;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
userTaskForm.value.candidateParam = [];
|
||||
}
|
||||
|
||||
otherExtensions.value =
|
||||
extensionElements.values?.filter(
|
||||
(ex: any) =>
|
||||
ex.$type !== `${prefix}:CandidateStrategy` &&
|
||||
ex.$type !== `${prefix}:CandidateParam`,
|
||||
) ?? [];
|
||||
|
||||
// 跳过表达式
|
||||
userTaskForm.value.skipExpression =
|
||||
businessObject.skipExpression === undefined
|
||||
? ''
|
||||
: businessObject.skipExpression;
|
||||
|
||||
// 改用通过extensionElements来存储数据
|
||||
|
||||
// if (businessObject.candidateStrategy != undefined) {
|
||||
// userTaskForm.value.candidateStrategy = parseInt(
|
||||
// businessObject.candidateStrategy,
|
||||
// ) as any;
|
||||
// } else {
|
||||
// userTaskForm.value.candidateStrategy = undefined;
|
||||
// }
|
||||
// if (
|
||||
// businessObject.candidateParam &&
|
||||
// businessObject.candidateParam.length > 0
|
||||
// ) {
|
||||
// if (userTaskForm.value.candidateStrategy === 60) {
|
||||
// // 特殊:流程表达式,只有一个 input 输入框
|
||||
// userTaskForm.value.candidateParam = [businessObject.candidateParam];
|
||||
// } else {
|
||||
// userTaskForm.value.candidateParam = businessObject.candidateParam
|
||||
// .split(',')
|
||||
// .map((item) => item);
|
||||
// }
|
||||
// } else {
|
||||
// userTaskForm.value.candidateParam = [];
|
||||
// }
|
||||
};
|
||||
|
||||
/** 更新 candidateStrategy 字段时,需要清空 candidateParam,并触发 bpmn 图更新 */
|
||||
const changeCandidateStrategy = () => {
|
||||
userTaskForm.value.candidateParam = [];
|
||||
deptLevel.value = 1;
|
||||
// 注释 by 芋艿:这个交互很多用户反馈费解,https://t.zsxq.com/xNmas 所以暂时屏蔽
|
||||
// if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
|
||||
// // 特殊处理表单内用户字段,当只有发起人选项时应选中发起人
|
||||
// if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {
|
||||
// userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER
|
||||
// }
|
||||
// }
|
||||
updateElementTask();
|
||||
};
|
||||
|
||||
/** 选中某个 options 时候,更新 bpmn 图 */
|
||||
const updateElementTask = () => {
|
||||
let candidateParam = Array.isArray(userTaskForm.value.candidateParam)
|
||||
? userTaskForm.value.candidateParam.join(',')
|
||||
: userTaskForm.value.candidateParam;
|
||||
|
||||
// 特殊处理多级部门情况
|
||||
if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
|
||||
) {
|
||||
candidateParam += `|${deptLevel.value}`;
|
||||
}
|
||||
// 特殊处理发起人部门负责人、发起人连续部门负责人
|
||||
if (
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.value.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
|
||||
) {
|
||||
candidateParam = `${deptLevel.value}`;
|
||||
}
|
||||
|
||||
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
|
||||
values: [
|
||||
...otherExtensions.value,
|
||||
bpmnInstances().moddle.create(`${prefix}:CandidateStrategy`, {
|
||||
value: userTaskForm.value.candidateStrategy,
|
||||
}),
|
||||
bpmnInstances().moddle.create(`${prefix}:CandidateParam`, {
|
||||
value: candidateParam,
|
||||
}),
|
||||
],
|
||||
});
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
extensionElements: extensions,
|
||||
});
|
||||
|
||||
// 改用通过extensionElements来存储数据
|
||||
// return;
|
||||
// bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
// candidateStrategy: userTaskForm.value.candidateStrategy,
|
||||
// candidateParam: userTaskForm.value.candidateParam.join(','),
|
||||
// });
|
||||
};
|
||||
|
||||
const updateSkipExpression = () => {
|
||||
if (
|
||||
userTaskForm.value.skipExpression &&
|
||||
userTaskForm.value.skipExpression !== ''
|
||||
) {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
skipExpression: userTaskForm.value.skipExpression,
|
||||
});
|
||||
} else {
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
skipExpression: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 打开监听器弹窗
|
||||
const processExpressionDialogRef = ref<any>();
|
||||
const openProcessExpressionDialog = async () => {
|
||||
processExpressionDialogRef.value.open();
|
||||
};
|
||||
const selectProcessExpression = (
|
||||
expression: BpmProcessExpressionApi.ProcessExpression,
|
||||
) => {
|
||||
// @ts-ignore
|
||||
userTaskForm.value.candidateParam = [expression.expression];
|
||||
updateElementTask();
|
||||
};
|
||||
|
||||
const handleFormUserChange = (e: any) => {
|
||||
if (e === 'PROCESS_START_USER_ID') {
|
||||
userTaskForm.value.candidateParam = [];
|
||||
// @ts-ignore
|
||||
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER;
|
||||
}
|
||||
updateElementTask();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.id,
|
||||
() => {
|
||||
bpmnElement.value = bpmnInstances().bpmnElement;
|
||||
nextTick(() => {
|
||||
resetTaskForm();
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
// 获得角色列表
|
||||
roleOptions.value = await getSimpleRoleList();
|
||||
// 获得部门列表
|
||||
const deptOptions = await getSimpleDeptList();
|
||||
deptTreeOptions.value = handleTree(deptOptions, 'id');
|
||||
// 获得岗位列表
|
||||
postOptions.value = await getSimplePostList();
|
||||
// 获得用户列表
|
||||
userOptions.value = await getSimpleUserList();
|
||||
// 获得用户组列表
|
||||
userGroupOptions.value = await getUserGroupSimpleList();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
bpmnElement.value = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<FormItem label="规则类型" name="candidateStrategy">
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateStrategy"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="changeCandidateStrategy"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(dict, index) in CANDIDATE_STRATEGY"
|
||||
:key="index"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.ROLE"
|
||||
label="指定角色"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in roleOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
userTaskForm.candidateStrategy === CandidateStrategy.DEPT_MEMBER ||
|
||||
userTaskForm.candidateStrategy === CandidateStrategy.DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
|
||||
"
|
||||
label="指定部门"
|
||||
name="candidateParam"
|
||||
>
|
||||
<TreeSelect
|
||||
ref="treeRef"
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
:tree-data="deptTreeOptions"
|
||||
:field-names="defaultProps"
|
||||
placeholder="加载中,请稍后"
|
||||
multiple
|
||||
tree-checkable
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.POST"
|
||||
label="指定岗位"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in postOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER"
|
||||
label="指定用户"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in userOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP"
|
||||
label="指定用户组"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in userGroupOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER"
|
||||
label="表单内用户字段"
|
||||
name="formUser"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="handleFormUserChange"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(item, idx) in userFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled="!item.required"
|
||||
/>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
label="表单内部门字段"
|
||||
name="formDept"
|
||||
>
|
||||
<Select
|
||||
v-model:value="userTaskForm.candidateParam"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(item, idx) in deptFieldOnFormOptions"
|
||||
:key="idx"
|
||||
:label="item.title"
|
||||
:value="item.field"
|
||||
:disabled="!item.required"
|
||||
/>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="
|
||||
userTaskForm.candidateStrategy ===
|
||||
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy ===
|
||||
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER ||
|
||||
userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
|
||||
"
|
||||
:label="deptLevelLabel!"
|
||||
name="deptLevel"
|
||||
>
|
||||
<Select v-model:value="deptLevel" allow-clear @change="updateElementTask">
|
||||
<SelectOption
|
||||
v-for="(item, index) in MULTI_LEVEL_DEPT"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
v-if="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION"
|
||||
label="流程表达式"
|
||||
name="candidateParam"
|
||||
>
|
||||
<Textarea
|
||||
v-model:value="userTaskForm.candidateParam[0]"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="updateElementTask"
|
||||
/>
|
||||
<Button
|
||||
class="!w-1/1 mt-5px"
|
||||
type="primary"
|
||||
:icon="h(SelectOutlined)"
|
||||
@click="openProcessExpressionDialog"
|
||||
>
|
||||
选择表达式
|
||||
</Button>
|
||||
<!-- 选择弹窗 -->
|
||||
<ProcessExpressionDialog
|
||||
ref="processExpressionDialogRef"
|
||||
@select="selectProcessExpression"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem label="跳过表达式" name="skipExpression">
|
||||
<Textarea
|
||||
v-model:value="userTaskForm.skipExpression"
|
||||
allow-clear
|
||||
style="width: 100%"
|
||||
@change="updateSkipExpression"
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</template>
|
||||
@@ -0,0 +1,380 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Input,
|
||||
InputNumber,
|
||||
Radio,
|
||||
Tabs,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const tab = ref('cron');
|
||||
const cronStr = ref(props.value || '* * * * * ?');
|
||||
const fields = ref({
|
||||
second: '*',
|
||||
minute: '*',
|
||||
hour: '*',
|
||||
day: '*',
|
||||
month: '*',
|
||||
week: '?',
|
||||
year: '',
|
||||
});
|
||||
const cronFieldList = [
|
||||
{ key: 'second', label: '秒', min: 0, max: 59 },
|
||||
{ key: 'minute', label: '分', min: 0, max: 59 },
|
||||
{ key: 'hour', label: '时', min: 0, max: 23 },
|
||||
{ key: 'day', label: '天', min: 1, max: 31 },
|
||||
{ key: 'month', label: '月', min: 1, max: 12 },
|
||||
{ key: 'week', label: '周', min: 1, max: 7 },
|
||||
{ key: 'year', label: '年', min: 1970, max: 2099 },
|
||||
];
|
||||
const activeField = ref('second');
|
||||
const cronMode = ref({
|
||||
second: 'appoint',
|
||||
minute: 'every',
|
||||
hour: 'every',
|
||||
day: 'every',
|
||||
month: 'every',
|
||||
week: 'every',
|
||||
year: 'every',
|
||||
});
|
||||
const cronAppoint = ref({
|
||||
second: ['00', '01'],
|
||||
minute: [],
|
||||
hour: [],
|
||||
day: [],
|
||||
month: [],
|
||||
week: [],
|
||||
year: [],
|
||||
});
|
||||
const cronRange = ref({
|
||||
second: [0, 1],
|
||||
minute: [0, 1],
|
||||
hour: [0, 1],
|
||||
day: [1, 2],
|
||||
month: [1, 2],
|
||||
week: [1, 2],
|
||||
year: [1970, 1971],
|
||||
});
|
||||
const cronStep = ref({
|
||||
second: [1, 1],
|
||||
minute: [1, 1],
|
||||
hour: [1, 1],
|
||||
day: [1, 1],
|
||||
month: [1, 1],
|
||||
week: [1, 1],
|
||||
year: [1970, 1],
|
||||
});
|
||||
|
||||
function pad(n) {
|
||||
return n < 10 ? `0${n}` : `${n}`;
|
||||
}
|
||||
|
||||
watch(
|
||||
[fields, cronMode, cronAppoint, cronRange, cronStep],
|
||||
() => {
|
||||
// 组装cron表达式
|
||||
const arr = cronFieldList.map((f) => {
|
||||
if (cronMode.value[f.key] === 'every') return '*';
|
||||
if (cronMode.value[f.key] === 'appoint')
|
||||
return cronAppoint.value[f.key].join(',') || '*';
|
||||
if (cronMode.value[f.key] === 'range')
|
||||
return `${cronRange.value[f.key][0]}-${cronRange.value[f.key][1]}`;
|
||||
if (cronMode.value[f.key] === 'step')
|
||||
return `${cronStep.value[f.key][0]}/${cronStep.value[f.key][1]}`;
|
||||
return fields.value[f.key] || '*';
|
||||
});
|
||||
// week和year特殊处理
|
||||
arr[5] = arr[5] || '?';
|
||||
cronStr.value = arr.join(' ');
|
||||
if (tab.value === 'cron') emit('change', cronStr.value);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 标准格式
|
||||
const isoStr = ref('');
|
||||
const repeat = ref(1);
|
||||
const isoDate = ref('');
|
||||
const isoDuration = ref('');
|
||||
function setDuration(type, val) {
|
||||
// 组装ISO 8601字符串
|
||||
let d = isoDuration.value;
|
||||
if (d.includes(type)) {
|
||||
d = d.replace(new RegExp(`\\d+${type}`), val + type);
|
||||
} else {
|
||||
d += val + type;
|
||||
}
|
||||
isoDuration.value = d;
|
||||
updateIsoStr();
|
||||
}
|
||||
function updateIsoStr() {
|
||||
let str = `R${repeat.value}`;
|
||||
if (isoDate.value)
|
||||
str += `/${
|
||||
typeof isoDate.value === 'string'
|
||||
? isoDate.value
|
||||
: new Date(isoDate.value).toISOString()
|
||||
}`;
|
||||
if (isoDuration.value) str += `/${isoDuration.value}`;
|
||||
isoStr.value = str;
|
||||
if (tab.value === 'iso') emit('change', isoStr.value);
|
||||
}
|
||||
watch([repeat, isoDate, isoDuration], updateIsoStr);
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
if (tab.value === 'cron') cronStr.value = val;
|
||||
if (tab.value === 'iso') isoStr.value = val;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<Tabs v-model:active-key="tab">
|
||||
<Tabs.TabPane key="cron" tab="CRON表达式">
|
||||
<div style="margin-bottom: 10px">
|
||||
<Input
|
||||
v-model:value="cronStr"
|
||||
readonly
|
||||
style="width: 400px; font-weight: bold"
|
||||
key="cronStr"
|
||||
/>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; margin-bottom: 8px">
|
||||
<Input
|
||||
v-model:value="fields.second"
|
||||
placeholder="秒"
|
||||
style="width: 80px"
|
||||
key="second"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.minute"
|
||||
placeholder="分"
|
||||
style="width: 80px"
|
||||
key="minute"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.hour"
|
||||
placeholder="时"
|
||||
style="width: 80px"
|
||||
key="hour"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.day"
|
||||
placeholder="天"
|
||||
style="width: 80px"
|
||||
key="day"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.month"
|
||||
placeholder="月"
|
||||
style="width: 80px"
|
||||
key="month"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.week"
|
||||
placeholder="周"
|
||||
style="width: 80px"
|
||||
key="week"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="fields.year"
|
||||
placeholder="年"
|
||||
style="width: 80px"
|
||||
key="year"
|
||||
/>
|
||||
</div>
|
||||
<Tabs
|
||||
v-model:active-key="activeField"
|
||||
type="card"
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<Tabs.TabPane v-for="f in cronFieldList" :key="f.key" :tab="f.label">
|
||||
<div style="margin-bottom: 8px">
|
||||
<Radio.Group
|
||||
v-model:value="cronMode[f.key]"
|
||||
:key="`radio-${f.key}`"
|
||||
>
|
||||
<Radio value="every" :key="`every-${f.key}`">
|
||||
每{{ f.label }}
|
||||
</Radio>
|
||||
<Radio value="range" :key="`range-${f.key}`">
|
||||
从
|
||||
<InputNumber
|
||||
v-model:value="cronRange[f.key][0]"
|
||||
:min="f.min"
|
||||
:max="f.max"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
:key="`range0-${f.key}`"
|
||||
/>
|
||||
到
|
||||
<InputNumber
|
||||
v-model:value="cronRange[f.key][1]"
|
||||
:min="f.min"
|
||||
:max="f.max"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
:key="`range1-${f.key}`"
|
||||
/>
|
||||
之间每{{ f.label }}
|
||||
</Radio>
|
||||
<Radio value="step" :key="`step-${f.key}`">
|
||||
从第
|
||||
<InputNumber
|
||||
v-model:value="cronStep[f.key][0]"
|
||||
:min="f.min"
|
||||
:max="f.max"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
:key="`step0-${f.key}`"
|
||||
/>
|
||||
开始每
|
||||
<InputNumber
|
||||
v-model:value="cronStep[f.key][1]"
|
||||
:min="1"
|
||||
:max="f.max"
|
||||
size="small"
|
||||
style="width: 60px"
|
||||
:key="`step1-${f.key}`"
|
||||
/>
|
||||
{{ f.label }}
|
||||
</Radio>
|
||||
<Radio value="appoint" :key="`appoint-${f.key}`"> 指定 </Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div v-if="cronMode[f.key] === 'appoint'">
|
||||
<Checkbox.Group
|
||||
v-model:value="cronAppoint[f.key]"
|
||||
:key="`group-${f.key}`"
|
||||
>
|
||||
<Checkbox
|
||||
v-for="n in f.max + 1"
|
||||
:key="`cb-${f.key}-${n - 1}`"
|
||||
:value="pad(n - 1)"
|
||||
>
|
||||
{{ pad(n - 1) }}
|
||||
</Checkbox>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="iso" title="标准格式" tab="iso-tab">
|
||||
<div style="margin-bottom: 10px">
|
||||
<Input
|
||||
v-model:value="isoStr"
|
||||
placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S"
|
||||
style="width: 400px; font-weight: bold"
|
||||
key="isoStr"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px">
|
||||
循环次数:<InputNumber
|
||||
v-model:value="repeat"
|
||||
:min="1"
|
||||
style="width: 100px"
|
||||
key="repeat"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px">
|
||||
日期时间:<DatePicker
|
||||
v-model:value="isoDate"
|
||||
show-time
|
||||
placeholder="选择日期时间"
|
||||
style="width: 200px"
|
||||
key="isoDate"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px">
|
||||
当前时长:<Input
|
||||
v-model:value="isoDuration"
|
||||
placeholder="如P3DT30M30S"
|
||||
style="width: 200px"
|
||||
key="isoDuration"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
秒:
|
||||
<Button
|
||||
v-for="s in [5, 10, 30, 50]"
|
||||
@click="setDuration('S', s)"
|
||||
:key="`sec-${s}`"
|
||||
>
|
||||
{{ s }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
分:
|
||||
<Button
|
||||
v-for="m in [5, 10, 30, 50]"
|
||||
@click="setDuration('M', m)"
|
||||
:key="`min-${m}`"
|
||||
>
|
||||
{{ m }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
小时:
|
||||
<Button
|
||||
v-for="h in [4, 8, 12, 24]"
|
||||
@click="setDuration('H', h)"
|
||||
:key="`hour-${h}`"
|
||||
>
|
||||
{{ h }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
天:
|
||||
<Button
|
||||
v-for="d in [1, 2, 3, 4]"
|
||||
@click="setDuration('D', d)"
|
||||
:key="`day-${d}`"
|
||||
>
|
||||
{{ d }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
月:
|
||||
<Button
|
||||
v-for="mo in [1, 2, 3, 4]"
|
||||
@click="setDuration('M', mo)"
|
||||
:key="`mon-${mo}`"
|
||||
>
|
||||
{{ mo }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
<div>
|
||||
年:
|
||||
<Button
|
||||
v-for="y in [1, 2, 3, 4]"
|
||||
@click="setDuration('Y', y)"
|
||||
:key="`year-${y}`"
|
||||
>
|
||||
{{ y }}
|
||||
</Button>
|
||||
自定义
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</template>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { Button, Input } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const units = [
|
||||
{ key: 'Y', label: '年', presets: [1, 2, 3, 4] },
|
||||
{ key: 'M', label: '月', presets: [1, 2, 3, 4] },
|
||||
{ key: 'D', label: '天', presets: [1, 2, 3, 4] },
|
||||
{ key: 'H', label: '时', presets: [4, 8, 12, 24] },
|
||||
{ key: 'm', label: '分', presets: [5, 10, 30, 50] },
|
||||
{ key: 'S', label: '秒', presets: [5, 10, 30, 50] },
|
||||
];
|
||||
const custom = ref({ Y: '', M: '', D: '', H: '', m: '', S: '' });
|
||||
const isoString = ref('');
|
||||
|
||||
function setUnit(key, val) {
|
||||
if (!val || Number.isNaN(val)) {
|
||||
custom.value[key] = '';
|
||||
return;
|
||||
}
|
||||
custom.value[key] = val;
|
||||
updateIsoString();
|
||||
}
|
||||
|
||||
function updateIsoString() {
|
||||
let str = 'P';
|
||||
if (custom.value.Y) str += `${custom.value.Y}Y`;
|
||||
if (custom.value.M) str += `${custom.value.M}M`;
|
||||
if (custom.value.D) str += `${custom.value.D}D`;
|
||||
if (custom.value.H || custom.value.m || custom.value.S) str += 'T';
|
||||
if (custom.value.H) str += `${custom.value.H}H`;
|
||||
if (custom.value.m) str += `${custom.value.m}M`;
|
||||
if (custom.value.S) str += `${custom.value.S}S`;
|
||||
isoString.value = str === 'P' ? '' : str;
|
||||
emit('change', isoString.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
// 解析ISO 8601字符串到custom
|
||||
const match = val.match(
|
||||
/^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/,
|
||||
);
|
||||
if (match) {
|
||||
custom.value.Y = match[1] || '';
|
||||
custom.value.M = match[2] || '';
|
||||
custom.value.D = match[3] || '';
|
||||
custom.value.H = match[4] || '';
|
||||
custom.value.m = match[5] || '';
|
||||
custom.value.S = match[6] || '';
|
||||
updateIsoString();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 10px">
|
||||
当前选择:<Input
|
||||
v-model:value="isoString"
|
||||
readonly
|
||||
style="width: 300px"
|
||||
/>
|
||||
</div>
|
||||
<div v-for="unit in units" :key="unit.key" style="margin-bottom: 8px">
|
||||
<span>{{ unit.label }}:</span>
|
||||
<Button.Group>
|
||||
<Button
|
||||
v-for="val in unit.presets"
|
||||
:key="val"
|
||||
size="small"
|
||||
@click="setUnit(unit.key, val)"
|
||||
>
|
||||
{{ val }}
|
||||
</Button>
|
||||
<Input
|
||||
v-model:value="custom[unit.key]"
|
||||
size="small"
|
||||
style="width: 60px; margin-left: 8px"
|
||||
placeholder="自定义"
|
||||
@change="setUnit(unit.key, custom[unit.key])"
|
||||
/>
|
||||
</Button.Group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,357 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { computed, nextTick, onMounted, ref, toRaw, watch } from 'vue';
|
||||
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
ExclamationCircleFilled,
|
||||
IconifyIcon,
|
||||
QuestionCircleFilled,
|
||||
} from '@vben/icons';
|
||||
|
||||
import { Button, DatePicker, Input, Modal, Tooltip } from 'ant-design-vue';
|
||||
|
||||
import CycleConfig from './CycleConfig.vue';
|
||||
import DurationConfig from './DurationConfig.vue';
|
||||
|
||||
const props = defineProps({
|
||||
businessObject: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const bpmnInstances = () => (window as any).bpmnInstances;
|
||||
const type: Ref<string> = ref('time');
|
||||
const condition: Ref<string> = ref('');
|
||||
const valid: Ref<boolean> = ref(true);
|
||||
const showDatePicker: Ref<boolean> = ref(false);
|
||||
const showDurationDialog: Ref<boolean> = ref(false);
|
||||
const showCycleDialog: Ref<boolean> = ref(false);
|
||||
const showHelp: Ref<boolean> = ref(false);
|
||||
const dateValue: Ref<Date | null> = ref(null);
|
||||
// const bpmnElement = ref(null);
|
||||
|
||||
const placeholder = computed<string>(() => {
|
||||
if (type.value === 'time') return '请输入时间';
|
||||
if (type.value === 'duration') return '请输入持续时长';
|
||||
if (type.value === 'cycle') return '请输入循环表达式';
|
||||
return '';
|
||||
});
|
||||
const helpText = computed<string>(() => {
|
||||
if (type.value === 'time') return '选择具体时间';
|
||||
if (type.value === 'duration') return 'ISO 8601格式,如PT1H';
|
||||
if (type.value === 'cycle') return 'CRON表达式或ISO 8601周期';
|
||||
return '';
|
||||
});
|
||||
const helpHtml = computed<string>(() => {
|
||||
if (type.value === 'duration') {
|
||||
return `指定定时器之前要等待多长时间。S表示秒,M表示分,D表示天;P表示时间段,T表示精确到时间的时间段。<br>
|
||||
时间格式依然为ISO 8601格式,一年两个月三天四小时五分六秒内,可以写成P1Y2M3DT4H5M6S。<br>
|
||||
P是开始标记,T是时间和日期分割标记,没有日期只有时间T是不能省去的,比如1小时执行一次应写成PT1H。`;
|
||||
}
|
||||
if (type.value === 'cycle') {
|
||||
return `支持CRON表达式(如0 0/30 * * * ?)或ISO 8601周期(如R3/PT10M)。`;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// 初始化和监听
|
||||
function syncFromBusinessObject(): void {
|
||||
if (props.businessObject) {
|
||||
const timerDef = (props.businessObject.eventDefinitions || [])[0];
|
||||
if (timerDef) {
|
||||
if (timerDef.timeDate) {
|
||||
type.value = 'time';
|
||||
condition.value = timerDef.timeDate.body;
|
||||
} else if (timerDef.timeDuration) {
|
||||
type.value = 'duration';
|
||||
condition.value = timerDef.timeDuration.body;
|
||||
} else if (timerDef.timeCycle) {
|
||||
type.value = 'cycle';
|
||||
condition.value = timerDef.timeCycle.body;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onMounted(syncFromBusinessObject);
|
||||
|
||||
// 切换类型
|
||||
function setType(t: string) {
|
||||
type.value = t;
|
||||
condition.value = '';
|
||||
updateNode();
|
||||
}
|
||||
|
||||
// 输入校验
|
||||
watch([type, condition], () => {
|
||||
valid.value = validate();
|
||||
// updateNode() // 可以注释掉,避免频繁触发
|
||||
});
|
||||
|
||||
function validate(): boolean {
|
||||
if (type.value === 'time') {
|
||||
return !!condition.value && !Number.isNaN(Date.parse(condition.value));
|
||||
}
|
||||
if (type.value === 'duration') {
|
||||
return /^P.*$/.test(condition.value);
|
||||
}
|
||||
if (type.value === 'cycle') {
|
||||
return /^(?:[0-9*/?, ]+|R\d*\/P.*)$/.test(condition.value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 选择时间
|
||||
function onDateChange(val: any) {
|
||||
dateValue.value = val;
|
||||
}
|
||||
function onDateConfirm(): void {
|
||||
if (dateValue.value) {
|
||||
condition.value = new Date(dateValue.value).toISOString();
|
||||
showDatePicker.value = false;
|
||||
updateNode();
|
||||
}
|
||||
}
|
||||
|
||||
// 持续时长
|
||||
function onDurationChange(val: string) {
|
||||
condition.value = val;
|
||||
}
|
||||
function onDurationConfirm(): void {
|
||||
showDurationDialog.value = false;
|
||||
updateNode();
|
||||
}
|
||||
|
||||
// 循环
|
||||
function onCycleChange(val: string) {
|
||||
condition.value = val;
|
||||
}
|
||||
function onCycleConfirm(): void {
|
||||
showCycleDialog.value = false;
|
||||
updateNode();
|
||||
}
|
||||
|
||||
// 输入框聚焦时弹窗(可选)
|
||||
function handleInputFocus(): void {
|
||||
if (type.value === 'time') showDatePicker.value = true;
|
||||
if (type.value === 'duration') showDurationDialog.value = true;
|
||||
if (type.value === 'cycle') showCycleDialog.value = true;
|
||||
}
|
||||
|
||||
// 同步到节点
|
||||
function updateNode(): void {
|
||||
const moddle = (window.bpmnInstances as any)?.moddle;
|
||||
const modeling = (window.bpmnInstances as any)?.modeling;
|
||||
const elementRegistry = (window.bpmnInstances as any)?.elementRegistry;
|
||||
if (!moddle || !modeling || !elementRegistry) return;
|
||||
|
||||
// 获取元素
|
||||
if (!props.businessObject || !props.businessObject.id) return;
|
||||
const element = elementRegistry.get(props.businessObject.id);
|
||||
if (!element) return;
|
||||
|
||||
// 1. 复用原有 timerDef,或新建
|
||||
let timerDef =
|
||||
element.businessObject.eventDefinitions &&
|
||||
element.businessObject.eventDefinitions[0];
|
||||
if (!timerDef) {
|
||||
timerDef = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:TimerEventDefinition',
|
||||
{},
|
||||
);
|
||||
modeling.updateProperties(element, {
|
||||
eventDefinitions: [timerDef],
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 清空原有
|
||||
delete timerDef.timeDate;
|
||||
delete timerDef.timeDuration;
|
||||
delete timerDef.timeCycle;
|
||||
|
||||
// 3. 设置新的
|
||||
if (type.value === 'time' && condition.value) {
|
||||
timerDef.timeDate = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: condition.value,
|
||||
},
|
||||
);
|
||||
} else if (type.value === 'duration' && condition.value) {
|
||||
timerDef.timeDuration = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: condition.value,
|
||||
},
|
||||
);
|
||||
} else if (type.value === 'cycle' && condition.value) {
|
||||
timerDef.timeCycle = bpmnInstances().bpmnFactory.create(
|
||||
'bpmn:FormalExpression',
|
||||
{
|
||||
body: condition.value,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bpmnInstances().modeling.updateProperties(toRaw(element), {
|
||||
eventDefinitions: [timerDef],
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.businessObject,
|
||||
(val) => {
|
||||
if (val) {
|
||||
nextTick(() => {
|
||||
syncFromBusinessObject();
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="panel-tab__content">
|
||||
<div style="margin-top: 10px">
|
||||
<span>类型:</span>
|
||||
<Button.Group>
|
||||
<Button
|
||||
size="small"
|
||||
:type="type === 'time' ? 'primary' : 'default'"
|
||||
@click="setType('time')"
|
||||
>
|
||||
时间
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
:type="type === 'duration' ? 'primary' : 'default'"
|
||||
@click="setType('duration')"
|
||||
>
|
||||
持续
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
:type="type === 'cycle' ? 'primary' : 'default'"
|
||||
@click="setType('cycle')"
|
||||
>
|
||||
循环
|
||||
</Button>
|
||||
</Button.Group>
|
||||
<CheckCircleFilled v-if="valid" style="color: green; margin-left: 8px" />
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; margin-top: 10px">
|
||||
<span>条件:</span>
|
||||
<Input
|
||||
v-model:value="condition"
|
||||
:placeholder="placeholder"
|
||||
style="width: calc(100% - 100px)"
|
||||
:readonly="type !== 'duration' && type !== 'cycle'"
|
||||
@focus="handleInputFocus"
|
||||
@blur="updateNode"
|
||||
>
|
||||
<template #suffix>
|
||||
<Tooltip v-if="!valid" title="格式错误" placement="top">
|
||||
<ExclamationCircleFilled style="color: orange" />
|
||||
</Tooltip>
|
||||
<Tooltip :title="helpText" placement="top">
|
||||
<QuestionCircleFilled
|
||||
style="color: #409eff; cursor: pointer"
|
||||
@click="showHelp = true"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
v-if="type === 'time'"
|
||||
@click="showDatePicker = true"
|
||||
style="margin-left: 4px"
|
||||
shape="circle"
|
||||
size="small"
|
||||
>
|
||||
<IconifyIcon icon="ep:calendar" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="type === 'duration'"
|
||||
@click="showDurationDialog = true"
|
||||
style="margin-left: 4px"
|
||||
shape="circle"
|
||||
size="small"
|
||||
>
|
||||
<IconifyIcon icon="ep:timer" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="type === 'cycle'"
|
||||
@click="showCycleDialog = true"
|
||||
style="margin-left: 4px"
|
||||
shape="circle"
|
||||
size="small"
|
||||
>
|
||||
<IconifyIcon icon="ep:setting" />
|
||||
</Button>
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
<!-- 时间选择器 -->
|
||||
<Modal
|
||||
v-model:open="showDatePicker"
|
||||
title="选择时间"
|
||||
width="400px"
|
||||
@cancel="showDatePicker = false"
|
||||
>
|
||||
<DatePicker
|
||||
v-model:value="dateValue"
|
||||
show-time
|
||||
placeholder="选择日期时间"
|
||||
style="width: 100%"
|
||||
@change="onDateChange"
|
||||
/>
|
||||
<template #footer>
|
||||
<Button @click="showDatePicker = false">取消</Button>
|
||||
<Button type="primary" @click="onDateConfirm">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<!-- 持续时长选择器 -->
|
||||
<Modal
|
||||
v-model:open="showDurationDialog"
|
||||
title="时间配置"
|
||||
width="600px"
|
||||
@cancel="showDurationDialog = false"
|
||||
>
|
||||
<DurationConfig :value="condition" @change="onDurationChange" />
|
||||
<template #footer>
|
||||
<Button @click="showDurationDialog = false">取消</Button>
|
||||
<Button type="primary" @click="onDurationConfirm">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<!-- 循环配置器 -->
|
||||
<Modal
|
||||
v-model:open="showCycleDialog"
|
||||
title="时间配置"
|
||||
width="800px"
|
||||
@cancel="showCycleDialog = false"
|
||||
>
|
||||
<CycleConfig :value="condition" @change="onCycleChange" />
|
||||
<template #footer>
|
||||
<Button @click="showCycleDialog = false">取消</Button>
|
||||
<Button type="primary" @click="onCycleConfirm">确定</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<!-- 帮助说明 -->
|
||||
<Modal
|
||||
v-model:open="showHelp"
|
||||
title="格式说明"
|
||||
width="600px"
|
||||
@cancel="showHelp = false"
|
||||
>
|
||||
<div v-html="helpHtml"></div>
|
||||
<template #footer>
|
||||
<Button @click="showHelp = false">关闭</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 相关样式 */
|
||||
</style>
|
||||
Reference in New Issue
Block a user