refactor: 重构 bpmnProcessDesigner 组件 ele => antd

This commit is contained in:
puhui999
2025-09-14 18:16:02 +08:00
parent a277f0fa03
commit 71dd9f2d88
24 changed files with 3852 additions and 4451 deletions

View File

@@ -3,112 +3,135 @@
class="process-panel__container"
:style="{ width: `${width}px`, maxHeight: '600px' }"
>
<el-collapse v-model="activeTab" v-if="isReady">
<el-collapse-item name="base">
<!-- class="panel-tab__title" -->
<Collapse v-model:activeKey="activeTab" v-if="isReady">
<CollapsePanel key="base" header="常规">
<template #title>
<Icon icon="ep:info-filled" />
常规</template
>
<Icon icon="ant-design:info-circle-filled" />
常规
</template>
<ElementBaseInfo
:id-edit-disabled="idEditDisabled"
:business-object="elementBusinessObject"
:type="elementType"
:model="model"
/>
</el-collapse-item>
<el-collapse-item
name="condition"
v-if="elementType === 'Process'"
</CollapsePanel>
<CollapsePanel
key="message"
v-if="elementType === 'Process'"
>
<template #title><Icon icon="ep:comment" />消息与信号</template>
<template #title>
<Icon icon="ant-design:message-filled" />
消息与信号
</template>
<signal-and-massage />
</el-collapse-item>
<el-collapse-item
name="condition"
v-if="conditionFormVisible"
</CollapsePanel>
<CollapsePanel
key="condition"
v-if="conditionFormVisible"
>
<template #title><Icon icon="ep:promotion" />流转条件</template>
<template #title>
<Icon icon="ant-design:swap-right" />
流转条件
</template>
<flow-condition
:business-object="elementBusinessObject"
:type="elementType"
/>
</el-collapse-item>
<el-collapse-item name="condition" v-if="formVisible" key="form">
<template #title><Icon icon="ep:list" />表单</template>
</CollapsePanel>
<CollapsePanel key="form" v-if="formVisible">
<template #title>
<Icon icon="ant-design:unordered-list-outlined" />
表单
</template>
<element-form :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item
name="task"
v-if="isTaskCollapseItemShow(elementType)"
</CollapsePanel>
<CollapsePanel
key="task"
v-if="isTaskCollapseItemShow(elementType)"
>
<template #title
><Icon icon="ep:checked" />{{
getTaskCollapseItemName(elementType)
}}</template
>
<template #title>
<Icon icon="ant-design:check-circle-filled" />
{{ getTaskCollapseItemName(elementType) }}
</template>
<element-task :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item
name="multiInstance"
v-if="elementType.indexOf('Task') !== -1"
</CollapsePanel>
<CollapsePanel
key="multiInstance"
v-if="elementType.indexOf('Task') !== -1"
>
<template #title><Icon icon="ep:help-filled" />多人审批方式</template>
<template #title>
<Icon icon="ant-design:question-circle-filled" />
多人审批方式
</template>
<element-multi-instance
:id="elementId"
:business-object="elementBusinessObject"
:type="elementType"
/>
</el-collapse-item>
<el-collapse-item name="listeners" key="listeners">
<template #title><Icon icon="ep:bell-filled" />执行监听器</template>
</CollapsePanel>
<CollapsePanel key="listeners">
<template #title>
<Icon icon="ant-design:bell-filled" />
执行监听器
</template>
<element-listeners :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item
name="taskListeners"
v-if="elementType === 'UserTask'"
</CollapsePanel>
<CollapsePanel
key="taskListeners"
v-if="elementType === 'UserTask'"
>
<template #title><Icon icon="ep:bell-filled" />任务监听器</template>
<template #title>
<Icon icon="ant-design:bell-filled" />
任务监听器
</template>
<user-task-listeners :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="extensions" key="extensions">
<template #title
><Icon icon="ep:circle-plus-filled" />扩展属性</template
>
</CollapsePanel>
<CollapsePanel key="extensions">
<template #title>
<Icon icon="ant-design:plus-circle-filled" />
扩展属性
</template>
<element-properties :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="other" key="other">
<template #title><Icon icon="ep:promotion" />其他</template>
</CollapsePanel>
<CollapsePanel key="other">
<template #title>
<Icon icon="ant-design:swap-right" />
其他
</template>
<element-other-config :id="elementId" />
</el-collapse-item>
<el-collapse-item name="customConfig" key="customConfig">
<template #title><Icon icon="ep:tools" />自定义配置</template>
</CollapsePanel>
<CollapsePanel key="customConfig">
<template #title>
<Icon icon="ant-design:tool-filled" />
自定义配置
</template>
<element-custom-config
:id="elementId"
:type="elementType"
:business-object="elementBusinessObject"
/>
</el-collapse-item>
</CollapsePanel>
<!-- 新增的时间事件配置项 -->
<el-collapse-item
<CollapsePanel
key="timeEvent"
v-if="elementType === 'IntermediateCatchEvent'"
name="timeEvent"
>
<template #title><Icon icon="ep:timer" />时间事件</template>
<template #title>
<Icon icon="ant-design:clock-circle-filled" />
时间事件
</template>
<TimeEventConfig
:businessObject="bpmnElement.value?.businessObject"
:key="elementId"
/>
</el-collapse-item>
</el-collapse>
</CollapsePanel>
</Collapse>
</div>
</template>
<script lang="ts" setup>
import { Collapse, CollapsePanel } from 'ant-design-vue';
import { Icon } from '@vben/icons';
import ElementBaseInfo from './base/ElementBaseInfo.vue';
import ElementOtherConfig from './other/ElementOtherConfig.vue';
import ElementTask from './task/ElementTask.vue';
@@ -121,7 +144,7 @@ import ElementProperties from './properties/ElementProperties.vue';
import UserTaskListeners from './listeners/UserTaskListeners.vue';
import { getTaskCollapseItemName, isTaskCollapseItemShow } from './task/data';
import TimeEventConfig from './time-event-config/TimeEventConfig.vue';
import { ref, watch, onMounted } from 'vue';
import { ref, watch, onMounted, onBeforeUnmount, provide, nextTick } from 'vue';
defineOptions({ name: 'MyPropertiesPanel' });

View File

@@ -2,6 +2,7 @@
import { onBeforeUnmount, reactive, ref, toRaw, watch } from 'vue';
import { Form, FormItem, Input } from 'ant-design-vue';
import type { FormInstance, Rule } from 'ant-design-vue';
defineOptions({ name: 'ElementBaseInfo' });
@@ -23,6 +24,7 @@ interface Model {
[key: string]: any;
}
const formRef = ref<FormInstance>();
const needProps = ref<Record<string, any>>({});
const bpmnElement = ref<any>();
const elementBaseInfo = ref<BusinessObject>({} as any);

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import { defineOptions, defineProps, ref, watch } from 'vue';
import type { Component } from 'vue';
import { CustomConfigMap } from './data';
@@ -26,7 +27,7 @@ interface BusinessObject {
}
// const bpmnInstances = () => (window as any)?.bpmnInstances;
const customConfigComponent = ref<any>(null);
const customConfigComponent = ref<Component | null>(null);
watch(
() => props.businessObject,
@@ -37,7 +38,7 @@ watch(
val +=
props.businessObject.eventDefinitions[0]?.$type.split(':')[1] || '';
}
customConfigComponent.value = (CustomConfigMap as any)[val]?.component;
customConfigComponent.value = (CustomConfigMap as Record<string, { component: Component }>)[val]?.component;
}
},
{ immediate: true },

View File

@@ -12,9 +12,13 @@ import {
import {
Divider,
Form,
FormItem,
InputNumber,
Radio,
RadioGroup,
RadioButton,
Select,
SelectOption,
Switch,
} from 'ant-design-vue';
@@ -223,35 +227,35 @@ watch(
<template>
<div>
<Divider orientation="left">审批人超时未处理时</Divider>
<Form.Item label="启用开关" name="timeoutHandlerEnable">
<FormItem label="启用开关" name="timeoutHandlerEnable">
<Switch
v-model:checked="timeoutHandlerEnable"
checked-children="开启"
un-checked-children="关闭"
@change="timeoutHandlerChange"
/>
</Form.Item>
<Form.Item
</FormItem>
<FormItem
label="执行动作"
name="timeoutHandlerType"
v-if="timeoutHandlerEnable"
>
<Radio.Group
<RadioGroup
v-model:value="timeoutHandlerType.value"
@change="onTimeoutHandlerTypeChanged"
>
<Radio.Button
<RadioButton
v-for="item in TIMEOUT_HANDLER_TYPES"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item label="超时时间设置" v-if="timeoutHandlerEnable">
</RadioButton>
</RadioGroup>
</FormItem>
<FormItem label="超时时间设置" v-if="timeoutHandlerEnable">
<span class="mr-2">当超过</span>
<Form.Item name="timeDuration">
<FormItem name="timeDuration">
<InputNumber
class="mr-2"
:style="{ width: '100px' }"
@@ -265,24 +269,24 @@ watch(
}
"
/>
</Form.Item>
</FormItem>
<Select
v-model:value="timeUnit"
class="mr-2"
:style="{ width: '100px' }"
@change="onTimeUnitChange"
>
<Select.Option
<SelectOption
v-for="item in TIME_UNIT_TYPES"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</Select.Option>
</SelectOption>
</Select>
未处理
</Form.Item>
<Form.Item
</FormItem>
<FormItem
label="最大提醒次数"
name="maxRemindCount"
v-if="timeoutHandlerEnable && timeoutHandlerType.value === 1"
@@ -298,7 +302,7 @@ watch(
}
"
/>
</Form.Item>
</FormItem>
</div>
</template>

View File

@@ -398,7 +398,7 @@ onMounted(async () => {
<template>
<div>
<Divider orientation="left">审批类型</Divider>
<Form.Item prop="approveType">
<Form.Item name="approveType" label="审批类型">
<RadioGroup v-model:value="approveType.value">
<Radio
v-for="(item, index) in APPROVE_TYPE"
@@ -411,7 +411,7 @@ onMounted(async () => {
</Form.Item>
<Divider orientation="left">审批人拒绝时</Divider>
<Form.Item prop="rejectHandlerType">
<Form.Item name="rejectHandlerType" label="处理方式">
<RadioGroup
v-model:value="rejectHandlerType"
:disabled="returnTaskList.length === 0"
@@ -428,14 +428,15 @@ onMounted(async () => {
</Form.Item>
<Form.Item
v-if="rejectHandlerType === RejectHandlerType.RETURN_USER_TASK"
name="returnNodeId"
label="驳回节点"
prop="returnNodeId"
>
<Select
v-model:value="returnNodeId"
allow-clear
style="width: 100%"
@change="updateReturnNodeId"
placeholder="请选择驳回节点"
>
<SelectOption
v-for="item in returnTaskList"

View File

@@ -2,6 +2,7 @@
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
import { Form, Input, Select } from 'ant-design-vue';
const { TextArea } = Input;
defineOptions({ name: 'FlowCondition' });
@@ -212,9 +213,9 @@ watch(
v-if="flowConditionForm.scriptType === 'inlineScript'"
key="body"
>
<Input
<TextArea
v-model:value="flowConditionForm.body"
:autosize="{ minRows: 2, maxRows: 6 }"
:auto-size="{ minRows: 2, maxRows: 6 }"
allow-clear
@change="updateFlowCondition"
/>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { computed, inject, nextTick, onMounted, ref, toRaw, watch } from 'vue';
import { Form, FormItem, Select } from 'ant-design-vue';
import { Form, FormItem, Select, Table, TableColumn, Button, Divider, Drawer, Input, Modal } from 'ant-design-vue';
import { getFormSimpleList } from '#/api/bpm/form';
@@ -313,12 +313,16 @@ watch(
:options="formOptions"
/>
</FormItem>
<!-- <FormItem label="业务标识">-->
<!-- <Select v-model:value="businessKey" @change="updateElementBusinessKey">-->
<!-- <SelectOption v-for="i in fieldList" :key="i.id" :value="i.id">{{ i.label }}</SelectOption>-->
<!-- <SelectOption value=""></SelectOption>-->
<!-- </Select>-->
<!-- </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>
<!--字段列表-->

View File

@@ -1,7 +1,12 @@
<script lang="ts" setup>
import { inject, nextTick, ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import {
IconifyIcon,
MenuOutlined,
PlusOutlined,
SelectOutlined,
} from '@vben/icons';
import {
Button,
@@ -288,7 +293,7 @@ watch(
<div class="element-drawer__button">
<Button type="primary" size="small" @click="openListenerForm(null, -1)">
<template #icon>
<IconifyIcon icon="ep:plus" />
<PlusOutlined />
</template>
添加监听器
</Button>

View File

@@ -6,7 +6,7 @@ import { reactive, ref } from 'vue';
import { CommonStatusEnum } from '@vben/constants';
import { Button, Modal, Pagination, Table, TableColumn } from 'ant-design-vue';
import { Button, Modal, Pagination, Table } from 'ant-design-vue';
import { getProcessListenerPage } from '#/api/bpm/processListener';
import { ContentWrap } from '#/components/content-wrap';
@@ -71,30 +71,30 @@ const select = async (row: BpmProcessListenerApi.ProcessListener) => {
:pagination="false"
:scroll="{ x: 'max-content' }"
>
<TableColumn title="名字" align="center" data-index="name" />
<TableColumn title="类型" align="center" data-index="type">
<Table.Column title="名字" align="center" dataIndex="name" />
<Table.Column title="类型" align="center" dataIndex="type">
<template #default="{ record }">
<DictTag
:type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE"
:value="record.type"
/>
</template>
</TableColumn>
<TableColumn title="事件" align="center" data-index="event" />
<TableColumn title="值类型" align="center" data-index="valueType">
</Table.Column>
<Table.Column title="事件" align="center" dataIndex="event" />
<Table.Column title="值类型" align="center" dataIndex="valueType">
<template #default="{ record }">
<DictTag
:type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE"
:value="record.valueType"
/>
</template>
</TableColumn>
<TableColumn title="值" align="center" data-index="value" />
<TableColumn title="操作" align="center">
</Table.Column>
<Table.Column title="值" align="center" dataIndex="value" />
<Table.Column title="操作" align="center">
<template #default="{ record }">
<Button type="primary" @click="select(record)"> 选择 </Button>
</template>
</TableColumn>
</Table.Column>
</Table>
<!-- 分页 -->
<div class="mt-4 flex justify-end">

View File

@@ -1,91 +1,44 @@
<template>
<div class="panel-tab__content">
<el-table :data="elementPropertyList" max-height="240" fit border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column
label="属性名"
prop="name"
min-width="100px"
show-overflow-tooltip
/>
<el-table-column
label="属性值"
prop="value"
min-width="100px"
show-overflow-tooltip
/>
<el-table-column label="操作" width="110px">
<template #default="scope">
<el-button
link
@click="openAttributesForm(scope.row, scope.$index)"
size="small"
>
编辑
</el-button>
<el-divider direction="vertical" />
<el-button
link
size="small"
style="color: #ff4d4f"
@click="removeAttributes(scope.row, scope.$index)"
>
移除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="element-drawer__button">
<XButton
type="primary"
preIcon="ep:plus"
title="添加属性"
@click="openAttributesForm(null, -1)"
/>
</div>
<el-dialog
v-model="propertyFormModelVisible"
title="属性配置"
width="600px"
append-to-body
destroy-on-close
>
<el-form :model="propertyForm" label-width="80px" ref="attributeFormRef">
<el-form-item label="属性名:" prop="name">
<el-input v-model="propertyForm.name" clearable />
</el-form-item>
<el-form-item label="属性值:" prop="value">
<el-input v-model="propertyForm.value" clearable />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="propertyFormModelVisible = false"> </el-button>
<el-button type="primary" @click="saveAttribute"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ElMessageBox } from 'element-plus';
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: String,
type: String,
id: {
type: String,
default: '',
},
type: {
type: String,
default: '',
},
});
const prefix = inject('prefix');
// const width = inject('width')
const elementPropertyList = ref<any[]>([]);
const propertyForm = ref<any>({});
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();
const otherExtensionList = ref();
const bpmnElementProperties = ref();
const bpmnElementPropertyList = ref();
const attributeFormRef = ref();
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 = () => {
@@ -94,7 +47,7 @@ const resetAttributesList = () => {
bpmnElementProperties.value =
// bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
(ex) => {
(ex: any) => {
if (ex.$type !== `${prefix}:Properties`) {
otherExtensionList.value.push(ex);
}
@@ -103,30 +56,38 @@ const resetAttributesList = () => {
) ?? [];
// 保存所有的 扩展属性字段
bpmnElementPropertyList.value = bpmnElementProperties.value.reduce(
(pre, current) => pre.concat(current.values),
[],
bpmnElementPropertyList.value = bpmnElementProperties.value.flatMap(
(current: any) => current.values,
);
// 复制 显示
elementPropertyList.value = JSON.parse(
JSON.stringify(bpmnElementPropertyList.value ?? []),
elementPropertyList.value = structuredClone(
bpmnElementPropertyList.value ?? [],
);
};
const openAttributesForm = (attr, index) => {
const openAttributesForm = (
attr: null | { name: string; value: string },
index: number,
) => {
editingPropertyIndex.value = index;
propertyForm.value = index === -1 ? {} : JSON.parse(JSON.stringify(attr));
// @ts-ignore
propertyForm.value = index === -1 ? {} : structuredClone(attr);
propertyFormModelVisible.value = true;
nextTick(() => {
if (attributeFormRef.value) attributeFormRef.value.clearValidate();
});
};
const removeAttributes = (attr, index) => {
console.log(attr, 'attr');
ElMessageBox.confirm('确认移除该属性吗?', '提示', {
confirmButtonText: '确 认',
cancelButtonText: '取 消',
})
.then(() => {
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);
// 新建一个属性字段的保存列表
@@ -138,22 +99,17 @@ const removeAttributes = (attr, index) => {
);
updateElementExtensions(propertiesObject);
resetAttributesList();
})
.catch(() => console.info('操作取消'));
},
onCancel() {
// console.info('操作取消');
},
});
};
const saveAttribute = () => {
console.log(propertyForm.value, 'propertyForm.value');
// console.log(propertyForm.value, 'propertyForm.value');
const { name, value } = propertyForm.value;
if (editingPropertyIndex.value !== -1) {
bpmnInstances().modeling.updateModdleProperties(
toRaw(bpmnElement.value),
toRaw(bpmnElementPropertyList.value)[toRaw(editingPropertyIndex.value)],
{
name,
value,
},
);
} else {
if (editingPropertyIndex.value === -1) {
// 新建属性字段
const newPropertyObject = bpmnInstances().moddle.create(
`${prefix}:Property`,
@@ -166,17 +122,27 @@ const saveAttribute = () => {
const propertiesObject = bpmnInstances().moddle.create(
`${prefix}:Properties`,
{
values: bpmnElementPropertyList.value.concat([newPropertyObject]),
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) => {
const updateElementExtensions = (properties: any) => {
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
values: otherExtensionList.value.concat([properties]),
values: [...otherExtensionList.value, properties],
});
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
extensionElements: extensions,
@@ -187,9 +153,85 @@ watch(
() => props.id,
(val) => {
if (val) {
val && val.length && resetAttributesList();
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>

View File

@@ -1,94 +1,20 @@
<template>
<div class="panel-tab__content">
<div class="panel-tab__content--title">
<span
><Icon
icon="ep:menu"
style="margin-right: 8px; color: #555"
/>消息列表</span
>
<XButton
type="primary"
title="创建新消息"
preIcon="ep:plus"
@click="openModel('message')"
/>
</div>
<el-table :data="messageList" border>
<el-table-column type="index" label="序号" width="60px" />
<el-table-column
label="消息ID"
prop="id"
max-width="300px"
show-overflow-tooltip
/>
<el-table-column
label="消息名称"
prop="name"
max-width="300px"
show-overflow-tooltip
/>
</el-table>
<div
class="panel-tab__content--title"
style="padding-top: 8px; margin-top: 8px; border-top: 1px solid #eee"
>
<span
><Icon
icon="ep:menu"
style="margin-right: 8px; color: #555"
/>信号列表</span
>
<XButton
type="primary"
title="创建新信号"
preIcon="ep:plus"
@click="openModel('signal')"
/>
</div>
<el-table :data="signalList" border>
<el-table-column type="index" label="序号" width="60px" />
<el-table-column
label="信号ID"
prop="id"
max-width="300px"
show-overflow-tooltip
/>
<el-table-column
label="信号名称"
prop="name"
max-width="300px"
show-overflow-tooltip
/>
</el-table>
<el-dialog
v-model="dialogVisible"
:title="modelConfig.title"
:close-on-click-modal="false"
width="400px"
append-to-body
destroy-on-close
>
<el-form :model="modelObjectForm" label-width="90px">
<el-form-item :label="modelConfig.idLabel">
<el-input v-model="modelObjectForm.id" clearable />
</el-form-item>
<el-form-item :label="modelConfig.nameLabel">
<el-input v-model="modelObjectForm.name" clearable />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="addNewObject"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
defineOptions({ name: 'SignalAndMassage' });
import { computed, onMounted, ref } from 'vue';
const message = useMessage();
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);
@@ -98,22 +24,20 @@ const rootElements = ref();
const messageIdMap = ref();
const signalIdMap = ref();
const modelConfig = computed(() => {
if (modelType.value === 'message') {
return { title: '创建消息', idLabel: '消息ID', nameLabel: '消息名称' };
} else {
return { title: '创建信号', idLabel: '信号ID', nameLabel: '信号名称' };
}
return modelType.value === 'message'
? { title: '创建消息', idLabel: '消息ID', nameLabel: '消息名称' }
: { title: '创建信号', idLabel: '信号ID', nameLabel: '信号名称' };
});
const bpmnInstances = () => (window as any)?.bpmnInstances;
const initDataList = () => {
console.log(window, 'window');
// console.log(window, 'window');
rootElements.value = bpmnInstances().modeler.getDefinitions().rootElements;
messageIdMap.value = {};
signalIdMap.value = {};
messageList.value = [];
signalList.value = [];
rootElements.value.forEach((el) => {
rootElements.value.forEach((el: any) => {
if (el.$type === 'bpmn:Message') {
messageIdMap.value[el.id] = true;
messageList.value.push({ ...el });
@@ -124,7 +48,7 @@ const initDataList = () => {
}
});
};
const openModel = (type) => {
const openModel = (type: any) => {
modelType.value = type;
modelObjectForm.value = {};
dialogVisible.value = true;
@@ -157,3 +81,98 @@ 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>

View File

@@ -1,34 +1,36 @@
<template>
<div class="panel-tab__content">
<el-form size="small" label-width="90px">
<Form :label-col="{ span: 9 }" :wrapper-col="{ span: 15 }">
<!-- add by 芋艿由于异步延续暂时用不到所以这里 display none -->
<el-form-item label="异步延续" style="display: none">
<el-checkbox
v-model="taskConfigForm.asyncBefore"
label="异步前"
value="异步前"
<Form.Item label="异步延续" style="display: none">
<Checkbox
v-model:checked="taskConfigForm.asyncBefore"
@change="changeTaskAsync"
/>
<el-checkbox
v-model="taskConfigForm.asyncAfter"
label="异步后"
value="异步后"
>
异步前
</Checkbox>
<Checkbox
v-model:checked="taskConfigForm.asyncAfter"
@change="changeTaskAsync"
/>
<el-checkbox
v-model="taskConfigForm.exclusive"
>
异步后
</Checkbox>
<Checkbox
v-model:checked="taskConfigForm.exclusive"
v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore"
label="排除"
value="排除"
@change="changeTaskAsync"
/>
</el-form-item>
>
排除
</Checkbox>
</Form.Item>
<component :is="witchTaskComponent" v-bind="$props" />
</el-form>
</Form>
</div>
</template>
<script lang="ts" setup>
import { Form } from 'ant-design-vue';
import { ref, watch } from 'vue';
import { installedComponent } from './data';
defineOptions({ name: 'ElementTaskConfig' });

View File

@@ -27,10 +27,14 @@ export const installedComponent = {
},
};
export const getTaskCollapseItemName = (elementType) => {
export const getTaskCollapseItemName = (
elementType: keyof typeof installedComponent,
) => {
return installedComponent[elementType].name;
};
export const isTaskCollapseItemShow = (elementType) => {
export const isTaskCollapseItemShow = (
elementType: keyof typeof installedComponent,
) => {
return installedComponent[elementType];
};

View File

@@ -1,189 +1,38 @@
<template>
<div>
<el-form label-width="100px">
<el-form-item label="实例名称" prop="processInstanceName">
<el-input
v-model="formData.processInstanceName"
clearable
placeholder="请输入实例名称"
@change="updateCallActivityAttr('processInstanceName')"
/>
</el-form-item>
<!-- TODO 需要可选择已存在的流程 -->
<el-form-item label="被调用流程" prop="calledElement">
<el-input
v-model="formData.calledElement"
clearable
placeholder="请输入被调用流程"
@change="updateCallActivityAttr('calledElement')"
/>
</el-form-item>
<el-form-item label="继承变量" prop="inheritVariables">
<el-switch
v-model="formData.inheritVariables"
@change="updateCallActivityAttr('inheritVariables')"
/>
</el-form-item>
<el-form-item label="继承业务键" prop="inheritBusinessKey">
<el-switch
v-model="formData.inheritBusinessKey"
@change="updateCallActivityAttr('inheritBusinessKey')"
/>
</el-form-item>
<el-form-item
v-if="!formData.inheritBusinessKey"
label="业务键表达式"
prop="businessKey"
>
<el-input
v-model="formData.businessKey"
clearable
placeholder="请输入业务键表达式"
@change="updateCallActivityAttr('businessKey')"
/>
</el-form-item>
<el-divider />
<div>
<div class="mb-10px flex">
<el-text>输入参数</el-text>
<XButton
class="ml-auto"
type="primary"
preIcon="ep:plus"
title="添加参数"
size="small"
@click="openVariableForm('in', null, -1)"
/>
</div>
<el-table :data="inVariableList" max-height="240" fit border>
<el-table-column
label="源"
prop="source"
min-width="100px"
show-overflow-tooltip
/>
<el-table-column
label="目标"
prop="target"
min-width="100px"
show-overflow-tooltip
/>
<el-table-column label="操作" width="110px">
<template #default="scope">
<el-button
link
@click="openVariableForm('in', scope.row, scope.$index)"
size="small"
>
编辑
</el-button>
<el-divider direction="vertical" />
<el-button
link
size="small"
style="color: #ff4d4f"
@click="removeVariable('in', scope.$index)"
>
移除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-divider />
<div>
<div class="mb-10px flex">
<el-text>输出参数</el-text>
<XButton
class="ml-auto"
type="primary"
preIcon="ep:plus"
title="添加参数"
size="small"
@click="openVariableForm('out', null, -1)"
/>
</div>
<el-table :data="outVariableList" max-height="240" fit border>
<el-table-column
label="源"
prop="source"
min-width="100px"
show-overflow-tooltip
/>
<el-table-column
label="目标"
prop="target"
min-width="100px"
show-overflow-tooltip
/>
<el-table-column label="操作" width="110px">
<template #default="scope">
<el-button
link
@click="openVariableForm('out', scope.row, scope.$index)"
size="small"
>
编辑
</el-button>
<el-divider direction="vertical" />
<el-button
link
size="small"
style="color: #ff4d4f"
@click="removeVariable('out', scope.$index)"
>
移除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
<!-- 添加或修改参数 -->
<el-dialog
v-model="variableDialogVisible"
title="参数配置"
width="600px"
append-to-body
destroy-on-close
>
<el-form
:model="varialbeFormData"
label-width="80px"
ref="varialbeFormRef"
>
<el-form-item label="源:" prop="source">
<el-input v-model="varialbeFormData.source" clearable />
</el-form-item>
<el-form-item label="目标:" prop="target">
<el-input v-model="varialbeFormData.target" clearable />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="variableDialogVisible = false"> </el-button>
<el-button type="primary" @click="saveVariable"> </el-button>
</template>
</el-dialog>
</div>
</template>
<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: String,
type: String,
id: { type: String, default: '' },
type: { type: String, default: '' },
});
const prefix = inject('prefix');
const message = useMessage();
const formData = ref({
const formData = ref<FormData>({
processInstanceName: '',
calledElement: '',
inheritVariables: false,
@@ -191,44 +40,51 @@ const formData = ref({
inheritBusinessKey: false,
calledElementType: 'key',
});
const inVariableList = ref();
const outVariableList = ref();
const variableType = ref(); // 参数类型
const editingVariableIndex = ref(-1); // 编辑参数下标
const variableDialogVisible = ref(false);
const varialbeFormRef = ref();
const varialbeFormData = ref({
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();
const otherExtensionList = ref();
const bpmnElement = ref<any>();
const otherExtensionList = ref<any[]>([]);
const initCallActivity = () => {
bpmnElement.value = bpmnInstances().bpmnElement;
console.log(bpmnElement.value.businessObject, 'callActivity');
// console.log(bpmnElement.value.businessObject, 'callActivity');
// 初始化所有配置项
Object.keys(formData.value).forEach((key) => {
Object.keys(formData.value).forEach((key: string) => {
// @ts-ignore
formData.value[key] =
bpmnElement.value.businessObject[key] ?? formData.value[key];
bpmnElement.value.businessObject[key] ??
formData.value[key as keyof FormData];
});
otherExtensionList.value = []; // 其他扩展配置
inVariableList.value = [];
outVariableList.value = [];
inVariableList.value.length = 0;
outVariableList.value.length = 0;
// 初始化输入参数
bpmnElement.value.businessObject?.extensionElements?.values?.forEach((ex) => {
if (ex.$type === `${prefix}:In`) {
inVariableList.value.push(ex);
} else if (ex.$type === `${prefix}:Out`) {
outVariableList.value.push(ex);
} else {
otherExtensionList.value.push(ex);
}
});
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), {
@@ -236,22 +92,22 @@ const initCallActivity = () => {
// })
};
const updateCallActivityAttr = (attr) => {
const updateCallActivityAttr = (attr: keyof FormData) => {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
[attr]: formData.value[attr],
});
};
const openVariableForm = (type, data, index) => {
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, index) => {
const removeVariable = async (type: string, index: number) => {
try {
await message.delConfirm();
await alert('是否确认删除?');
if (type === 'in') {
inVariableList.value.splice(index, 1);
}
@@ -313,7 +169,7 @@ watch(
() => props.id,
(val) => {
val &&
val.length &&
val.length > 0 &&
nextTick(() => {
initCallActivity();
});
@@ -322,4 +178,184 @@ watch(
);
</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>

View File

@@ -1,46 +1,24 @@
<!-- 表达式选择 -->
<template>
<Dialog title="请选择表达式" v-model="dialogVisible" width="1024px">
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
>
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="表达式" align="center" prop="expression" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="primary" @click="select(scope.row)">
选择
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</Dialog>
</template>
<script setup lang="ts">
import { CommonStatusEnum } from '@/utils/constants';
import {
ProcessExpressionApi,
ProcessExpressionVO,
} from '@/api/bpm/processExpression';
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<ProcessExpressionVO[]>([]); // 列表的数据
const list = ref<BpmProcessExpressionApi.ProcessExpression[]>([]); // 列表的数据
const total = ref(0); // 列表的总页数
const queryParams = reactive({
pageNo: 1,
@@ -62,8 +40,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
const getList = async () => {
loading.value = true;
try {
const data =
await ProcessExpressionApi.getProcessExpressionPage(queryParams);
const data = await getProcessExpressionPage(queryParams);
list.value = data.list;
total.value = data.total;
} finally {
@@ -71,11 +48,49 @@ const getList = async () => {
}
};
/** 提交表单 */
const emit = defineEmits(['success']); // 定义 success 事件,用于操作成功后的回调
const select = async (row) => {
// 定义 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>

View File

@@ -1,70 +1,38 @@
<template>
<div style="margin-top: 16px">
<el-form-item label="消息实例">
<div
style="
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: space-between;
"
>
<el-select v-model="bindMessageId" @change="updateTaskMessage">
<el-option
v-for="key in Object.keys(messageMap)"
:value="key"
:label="messageMap[key]"
:key="key"
/>
</el-select>
<XButton
type="primary"
preIcon="ep:plus"
style="margin-left: 8px"
@click="openMessageModel"
/>
</div>
</el-form-item>
<el-dialog
v-model="messageModelVisible"
:close-on-click-modal="false"
title="创建新消息"
width="400px"
append-to-body
destroy-on-close
>
<el-form :model="newMessageForm" size="small" label-width="90px">
<el-form-item label="消息ID">
<el-input v-model="newMessageForm.id" clearable />
</el-form-item>
<el-form-item label="消息名称">
<el-input v-model="newMessageForm.name" clearable />
</el-form-item>
</el-form>
<template #footer>
<el-button size="small" type="primary" @click="createNewMessage"
> </el-button
>
</template>
</el-dialog>
</div>
</template>
<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: String,
type: String,
id: { type: String, default: '' },
type: { type: String, default: '' },
});
const message = useMessage();
const bindMessageId = ref('');
const newMessageForm = ref<any>({});
const messageMap = ref<any>({});
const newMessageForm = ref<Record<string, any>>({});
const messageMap = ref<Record<string, any>>({});
const messageModelVisible = ref(false);
const bpmnElement = ref<any>();
const bpmnMessageRefsMap = ref<any>();
const bpmnMessageRefsMap = ref<Record<string, any>>();
const bpmnRootElements = ref<any>();
const bpmnInstances = () => (window as any).bpmnInstances;
@@ -88,17 +56,18 @@ const createNewMessage = () => {
);
bpmnRootElements.value.push(newMessage);
messageMap.value[newMessageForm.value.id] = newMessageForm.value.name;
bpmnMessageRefsMap.value[newMessageForm.value.id] = newMessage;
// @ts-ignore
bpmnMessageRefsMap.value?.[newMessageForm.value.id] = newMessage;
messageModelVisible.value = false;
};
const updateTaskMessage = (messageId) => {
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],
messageRef: bpmnMessageRefsMap.value?.[messageId],
});
}
};
@@ -108,9 +77,10 @@ onMounted(() => {
bpmnRootElements.value =
bpmnInstances().modeler.getDefinitions().rootElements;
bpmnRootElements.value
.filter((el) => el.$type === 'bpmn:Message')
.forEach((m) => {
bpmnMessageRefsMap.value[m.id] = m;
.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'] = '无';
@@ -130,3 +100,57 @@ watch(
{ 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>

View File

@@ -1,57 +1,32 @@
<template>
<div style="margin-top: 16px">
<el-form-item label="脚本格式">
<el-input
v-model="scriptTaskForm.scriptFormat"
clearable
@input="updateElementTask()"
@change="updateElementTask()"
/>
</el-form-item>
<el-form-item label="脚本类型">
<el-select v-model="scriptTaskForm.scriptType">
<el-option label="内联脚本" value="inline" />
<el-option label="外部资源" value="external" />
</el-select>
</el-form-item>
<el-form-item label="脚本" v-show="scriptTaskForm.scriptType === 'inline'">
<el-input
v-model="scriptTaskForm.script"
type="textarea"
resize="vertical"
:autosize="{ minRows: 2, maxRows: 4 }"
clearable
@input="updateElementTask()"
@change="updateElementTask()"
/>
</el-form-item>
<el-form-item
label="资源地址"
v-show="scriptTaskForm.scriptType === 'external'"
>
<el-input
v-model="scriptTaskForm.resource"
clearable
@input="updateElementTask()"
@change="updateElementTask()"
/>
</el-form-item>
<el-form-item label="结果变量">
<el-input
v-model="scriptTaskForm.resultVariable"
clearable
@input="updateElementTask()"
@change="updateElementTask()"
/>
</el-form-item>
</div>
</template>
<script 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: String,
type: String,
id: {
type: String,
default: '',
},
type: {
type: String,
default: '',
},
});
const defaultTaskForm = ref({
scriptFormat: '',
@@ -65,17 +40,19 @@ const bpmnElement = ref();
const bpmnInstances = () => (window as any)?.bpmnInstances;
const resetTaskForm = () => {
for (let key in defaultTaskForm.value) {
let value =
bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key];
scriptTaskForm.value[key] = value;
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 = () => {
let taskAttr = Object.create(null);
const taskAttr = Object.create(null);
taskAttr.scriptFormat = scriptTaskForm.value.scriptFormat || null;
taskAttr.resultVariable = scriptTaskForm.value.resultVariable || null;
if (scriptTaskForm.value.scriptType === 'inline') {
@@ -103,3 +80,50 @@ watch(
{ 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>

View File

@@ -1,56 +1,12 @@
<template>
<div>
<el-form-item label="执行类型" key="executeType">
<el-select v-model="serviceTaskForm.executeType">
<el-option label="Java类" value="class" />
<el-option label="表达式" value="expression" />
<el-option label="代理表达式" value="delegateExpression" />
</el-select>
</el-form-item>
<el-form-item
v-if="serviceTaskForm.executeType === 'class'"
label="Java类"
prop="class"
key="execute-class"
>
<el-input
v-model="serviceTaskForm.class"
clearable
@change="updateElementTask"
/>
</el-form-item>
<el-form-item
v-if="serviceTaskForm.executeType === 'expression'"
label="表达式"
prop="expression"
key="execute-expression"
>
<el-input
v-model="serviceTaskForm.expression"
clearable
@change="updateElementTask"
/>
</el-form-item>
<el-form-item
v-if="serviceTaskForm.executeType === 'delegateExpression'"
label="代理表达式"
prop="delegateExpression"
key="execute-delegate"
>
<el-input
v-model="serviceTaskForm.delegateExpression"
clearable
@change="updateElementTask"
/>
</el-form-item>
</div>
</template>
<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: String,
type: String,
id: { type: String, default: '' },
type: { type: String, default: '' },
});
const defaultTaskForm = ref({
@@ -66,8 +22,9 @@ const bpmnElement = ref();
const bpmnInstances = () => (window as any)?.bpmnInstances;
const resetTaskForm = () => {
for (let key in defaultTaskForm.value) {
let value =
for (const key in defaultTaskForm.value) {
const value =
// @ts-ignore
bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key];
serviceTaskForm.value[key] = value;
if (value) {
@@ -77,9 +34,9 @@ const resetTaskForm = () => {
};
const updateElementTask = () => {
let taskAttr = Object.create(null);
const taskAttr = Object.create(null);
const type = serviceTaskForm.value.executeType;
for (let key in serviceTaskForm.value) {
for (const key in serviceTaskForm.value) {
if (key !== 'executeType' && key !== type) taskAttr[key] = null;
}
taskAttr[type] = serviceTaskForm.value[type] || '';
@@ -101,3 +58,54 @@ watch(
{ 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>

View File

@@ -1,250 +1,60 @@
<template>
<el-form label-width="120px">
<el-form-item label="规则类型" prop="candidateStrategy">
<el-select
v-model="userTaskForm.candidateStrategy"
clearable
style="width: 100%"
@change="changeCandidateStrategy"
>
<el-option
v-for="(dict, index) in CANDIDATE_STRATEGY"
:key="index"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy == CandidateStrategy.ROLE"
label="指定角色"
prop="candidateParam"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
multiple
style="width: 100%"
@change="updateElementTask"
>
<el-option
v-for="item in roleOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="
userTaskForm.candidateStrategy == CandidateStrategy.DEPT_MEMBER ||
userTaskForm.candidateStrategy == CandidateStrategy.DEPT_LEADER ||
userTaskForm.candidateStrategy ==
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
"
label="指定部门"
prop="candidateParam"
span="24"
>
<el-tree-select
ref="treeRef"
v-model="userTaskForm.candidateParam"
:data="deptTreeOptions"
:props="defaultProps"
empty-text="加载中,请稍后"
multiple
node-key="id"
show-checkbox
@change="updateElementTask"
/>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy == CandidateStrategy.POST"
label="指定岗位"
prop="candidateParam"
span="24"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
multiple
style="width: 100%"
@change="updateElementTask"
>
<el-option
v-for="item in postOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy == CandidateStrategy.USER"
label="指定用户"
prop="candidateParam"
span="24"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
multiple
style="width: 100%"
@change="updateElementTask"
>
<el-option
v-for="item in userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy === CandidateStrategy.USER_GROUP"
label="指定用户组"
prop="candidateParam"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
multiple
style="width: 100%"
@change="updateElementTask"
>
<el-option
v-for="item in userGroupOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy === CandidateStrategy.FORM_USER"
label="表单内用户字段"
prop="formUser"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
style="width: 100%"
@change="handleFormUserChange"
>
<el-option
v-for="(item, idx) in userFieldOnFormOptions"
:key="idx"
:label="item.title"
:value="item.field"
:disabled="!item.required"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="
userTaskForm.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
"
label="表单内部门字段"
prop="formDept"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
style="width: 100%"
@change="updateElementTask"
>
<el-option
v-for="(item, idx) in deptFieldOnFormOptions"
:key="idx"
:label="item.title"
:value="item.field"
:disabled="!item.required"
/>
</el-select>
</el-form-item>
<el-form-item
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!"
prop="deptLevel"
span="24"
>
<el-select v-model="deptLevel" clearable @change="updateElementTask">
<el-option
v-for="(item, index) in MULTI_LEVEL_DEPT"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy === CandidateStrategy.EXPRESSION"
label="流程表达式"
prop="candidateParam"
>
<el-input
type="textarea"
v-model="userTaskForm.candidateParam[0]"
clearable
style="width: 100%"
@change="updateElementTask"
/>
<XButton
class="!w-1/1 mt-5px"
type="success"
preIcon="ep:select"
title="选择表达式"
size="small"
@click="openProcessExpressionDialog"
/>
<!-- 选择弹窗 -->
<ProcessExpressionDialog
ref="processExpressionDialogRef"
@select="selectProcessExpression"
/>
</el-form-item>
<el-form-item label="跳过表达式" prop="skipExpression">
<el-input
type="textarea"
v-model="userTaskForm.skipExpression"
clearable
style="width: 100%"
@change="updateSkipExpression"
/>
</el-form-item>
</el-form>
</template>
<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/SimpleProcessDesignerV2/src/consts';
import { defaultProps, handleTree } from '@/utils/tree';
import * as RoleApi from '@/api/system/role';
import * as DeptApi from '@/api/system/dept';
import * as PostApi from '@/api/system/post';
import * as UserApi from '@/api/system/user';
import * as UserGroupApi from '@/api/bpm/userGroup';
} from '#/components/simple-process-design/consts';
import { useFormFieldsPermission } from '#/components/simple-process-design/helpers';
import ProcessExpressionDialog from './ProcessExpressionDialog.vue';
import { ProcessExpressionVO } from '@/api/bpm/processExpression';
import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node';
defineOptions({ name: 'UserTask' });
const props = defineProps({
id: String,
type: String,
id: {
type: String,
default: '',
},
type: {
type: String,
default: '',
},
});
const prefix = inject('prefix');
const userTaskForm = ref({
@@ -252,16 +62,24 @@ const userTaskForm = ref({
candidateParam: [], // 分配选项
skipExpression: '', // 跳过表达式
});
const bpmnElement = ref();
const bpmnInstances = () => (window as any)?.bpmnInstances;
const bpmnElement = ref<any>();
const bpmnInstances = () => (window as Record<string, any>)?.bpmnInstances;
const roleOptions = ref<RoleApi.RoleVO[]>([]); // 角色列表
const deptTreeOptions = ref(); // 部门树
const postOptions = ref<PostApi.PostVO[]>([]); // 岗位列表
const userOptions = ref<UserApi.UserVO[]>([]); // 用户列表
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]); // 用户组列表
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');
@@ -275,21 +93,21 @@ const deptLevel = ref(1);
const deptLevelLabel = computed(() => {
let label = '部门负责人来源';
if (
userTaskForm.value.candidateStrategy ==
userTaskForm.value.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
) {
label = label + '(指定部门向上)';
label = `${label}(指定部门向上)`;
} else if (
userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
) {
label = label + '(表单内部门向上)';
label = `${label}(表单内部门向上)`;
} else {
label = label + '(发起人部门向上)';
label = `${label}(发起人部门向上)`;
}
return label;
});
const otherExtensions = ref();
const otherExtensions = ref<any>();
const resetTaskForm = () => {
const businessObject = bpmnElement.value.businessObject;
@@ -301,50 +119,54 @@ const resetTaskForm = () => {
businessObject?.extensionElements ??
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
(ex) => ex.$type === `${prefix}:CandidateStrategy`,
(ex: any) => ex.$type === `${prefix}:CandidateStrategy`,
)?.[0]?.value;
const candidateParamStr = extensionElements.values?.filter(
(ex) => ex.$type === `${prefix}:CandidateParam`,
(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 ==
userTaskForm.value.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
) {
// 特殊:多级不部门负责人,需要通过'|'分割
userTaskForm.value.candidateParam = candidateParamStr
.split('|')[0]
.split(',')
.map((item) => {
.map((item: any) => {
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
let num = Number(item);
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 ==
userTaskForm.value.candidateStrategy ===
CandidateStrategy.START_USER_DEPT_LEADER ||
userTaskForm.value.candidateStrategy ==
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.candidateStrategy ===
CandidateStrategy.FORM_DEPT_LEADER
) {
userTaskForm.value.candidateParam = candidateParamStr.split('|')[0];
deptLevel.value = +candidateParamStr.split('|')[1];
} else {
userTaskForm.value.candidateParam = candidateParamStr
.split(',')
.map((item) => {
.map((item: any) => {
// 如果数字超出了最大安全整数范围,则将其作为字符串处理
let num = Number(item);
const num = Number(item);
return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
? item
: num;
@@ -356,42 +178,41 @@ const resetTaskForm = () => {
otherExtensions.value =
extensionElements.values?.filter(
(ex) =>
(ex: any) =>
ex.$type !== `${prefix}:CandidateStrategy` &&
ex.$type !== `${prefix}:CandidateParam`,
) ?? [];
// 跳过表达式
if (businessObject.skipExpression != undefined) {
userTaskForm.value.skipExpression = businessObject.skipExpression;
} else {
userTaskForm.value.skipExpression = '';
}
userTaskForm.value.skipExpression =
businessObject.skipExpression === undefined
? ''
: businessObject.skipExpression;
// 改用通过extensionElements来存储数据
return;
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 = [];
}
// 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 图更新 */
@@ -410,27 +231,26 @@ const changeCandidateStrategy = () => {
/** 选中某个 options 时候,更新 bpmn 图 */
const updateElementTask = () => {
let candidateParam =
userTaskForm.value.candidateParam instanceof Array
? userTaskForm.value.candidateParam.join(',')
: userTaskForm.value.candidateParam;
let candidateParam = Array.isArray(userTaskForm.value.candidateParam)
? userTaskForm.value.candidateParam.join(',')
: userTaskForm.value.candidateParam;
// 特殊处理多级部门情况
if (
userTaskForm.value.candidateStrategy ==
userTaskForm.value.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER ||
userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER
userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
) {
candidateParam += '|' + deptLevel.value;
candidateParam += `|${deptLevel.value}`;
}
// 特殊处理发起人部门负责人、发起人连续部门负责人
if (
userTaskForm.value.candidateStrategy ==
userTaskForm.value.candidateStrategy ===
CandidateStrategy.START_USER_DEPT_LEADER ||
userTaskForm.value.candidateStrategy ==
userTaskForm.value.candidateStrategy ===
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
) {
candidateParam = deptLevel.value + '';
candidateParam = `${deptLevel.value}`;
}
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
@@ -449,11 +269,11 @@ const updateElementTask = () => {
});
// 改用通过extensionElements来存储数据
return;
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
candidateStrategy: userTaskForm.value.candidateStrategy,
candidateParam: userTaskForm.value.candidateParam.join(','),
});
// return;
// bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
// candidateStrategy: userTaskForm.value.candidateStrategy,
// candidateParam: userTaskForm.value.candidateParam.join(','),
// });
};
const updateSkipExpression = () => {
@@ -472,18 +292,22 @@ const updateSkipExpression = () => {
};
// 打开监听器弹窗
const processExpressionDialogRef = ref();
const processExpressionDialogRef = ref<any>();
const openProcessExpressionDialog = async () => {
processExpressionDialogRef.value.open();
};
const selectProcessExpression = (expression: ProcessExpressionVO) => {
const selectProcessExpression = (
expression: BpmProcessExpressionApi.ProcessExpression,
) => {
// @ts-ignore
userTaskForm.value.candidateParam = [expression.expression];
updateElementTask();
};
const handleFormUserChange = (e) => {
const handleFormUserChange = (e: any) => {
if (e === 'PROCESS_START_USER_ID') {
userTaskForm.value.candidateParam = [];
// @ts-ignore
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER;
}
updateElementTask();
@@ -502,19 +326,238 @@ watch(
onMounted(async () => {
// 获得角色列表
roleOptions.value = await RoleApi.getSimpleRoleList();
roleOptions.value = await getSimpleRoleList();
// 获得部门列表
const deptOptions = await DeptApi.getSimpleDeptList();
const deptOptions = await getSimpleDeptList();
deptTreeOptions.value = handleTree(deptOptions, 'id');
// 获得岗位列表
postOptions.value = await PostApi.getSimplePostList();
postOptions.value = await getSimplePostList();
// 获得用户列表
userOptions.value = await UserApi.getSimpleUserList();
userOptions.value = await getSimpleUserList();
// 获得用户组列表
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList();
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>

View File

@@ -1,223 +1,22 @@
<template>
<el-tabs v-model="tab">
<el-tab-pane label="CRON表达式" name="cron">
<div style="margin-bottom: 10px">
<el-input
v-model="cronStr"
readonly
style="width: 400px; font-weight: bold"
:key="'cronStr'"
/>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 8px">
<el-input
v-model="fields.second"
placeholder="秒"
style="width: 80px"
:key="'second'"
/>
<el-input
v-model="fields.minute"
placeholder="分"
style="width: 80px"
:key="'minute'"
/>
<el-input
v-model="fields.hour"
placeholder="时"
style="width: 80px"
:key="'hour'"
/>
<el-input
v-model="fields.day"
placeholder="天"
style="width: 80px"
:key="'day'"
/>
<el-input
v-model="fields.month"
placeholder="月"
style="width: 80px"
:key="'month'"
/>
<el-input
v-model="fields.week"
placeholder="周"
style="width: 80px"
:key="'week'"
/>
<el-input
v-model="fields.year"
placeholder="年"
style="width: 80px"
:key="'year'"
/>
</div>
<el-tabs v-model="activeField" type="card" style="margin-bottom: 8px">
<el-tab-pane
v-for="f in cronFieldList"
:label="f.label"
:name="f.key"
:key="f.key"
>
<div style="margin-bottom: 8px">
<el-radio-group v-model="cronMode[f.key]" :key="'radio-' + f.key">
<el-radio label="every" :key="'every-' + f.key"
>{{ f.label }}</el-radio
>
<el-radio label="range" :key="'range-' + f.key"
>
<el-input-number
v-model="cronRange[f.key][0]"
:min="f.min"
:max="f.max"
size="small"
style="width: 60px"
:key="'range0-' + f.key"
/>
<el-input-number
v-model="cronRange[f.key][1]"
:min="f.min"
:max="f.max"
size="small"
style="width: 60px"
:key="'range1-' + f.key"
/>
之间每{{ f.label }}</el-radio
>
<el-radio label="step" :key="'step-' + f.key"
>从第
<el-input-number
v-model="cronStep[f.key][0]"
:min="f.min"
:max="f.max"
size="small"
style="width: 60px"
:key="'step0-' + f.key"
/>
开始每
<el-input-number
v-model="cronStep[f.key][1]"
:min="1"
:max="f.max"
size="small"
style="width: 60px"
:key="'step1-' + f.key"
/>
{{ f.label }}</el-radio
>
<el-radio label="appoint" :key="'appoint-' + f.key"
>指定</el-radio
>
</el-radio-group>
</div>
<div v-if="cronMode[f.key] === 'appoint'">
<el-checkbox-group
v-model="cronAppoint[f.key]"
:key="'group-' + f.key"
>
<el-checkbox
v-for="n in f.max + 1"
:label="pad(n - 1)"
:key="'cb-' + f.key + '-' + (n - 1)"
>{{ pad(n - 1) }}</el-checkbox
>
</el-checkbox-group>
</div>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="标准格式" name="iso" :key="'iso-tab'">
<div style="margin-bottom: 10px">
<el-input
v-model="isoStr"
placeholder="如R1/2025-05-21T21:59:54/P3DT30M30S"
style="width: 400px; font-weight: bold"
:key="'isoStr'"
/>
</div>
<div style="margin-bottom: 10px">
循环次数<el-input-number
v-model="repeat"
:min="1"
style="width: 100px"
:key="'repeat'"
/>
</div>
<div style="margin-bottom: 10px">
日期时间<el-date-picker
v-model="isoDate"
type="datetime"
placeholder="选择日期时间"
style="width: 200px"
:key="'isoDate'"
/>
</div>
<div style="margin-bottom: 10px">
当前时长<el-input
v-model="isoDuration"
placeholder="如P3DT30M30S"
style="width: 200px"
:key="'isoDuration'"
/>
</div>
<div>
<div>
<el-button
v-for="s in [5, 10, 30, 50]"
@click="setDuration('S', s)"
:key="'sec-' + s"
>{{ s }}</el-button
>自定义
</div>
<div>
<el-button
v-for="m in [5, 10, 30, 50]"
@click="setDuration('M', m)"
:key="'min-' + m"
>{{ m }}</el-button
>自定义
</div>
<div>
小时<el-button
v-for="h in [4, 8, 12, 24]"
@click="setDuration('H', h)"
:key="'hour-' + h"
>{{ h }}</el-button
>自定义
</div>
<div>
<el-button
v-for="d in [1, 2, 3, 4]"
@click="setDuration('D', d)"
:key="'day-' + d"
>{{ d }}</el-button
>自定义
</div>
<div>
<el-button
v-for="mo in [1, 2, 3, 4]"
@click="setDuration('M', mo)"
:key="'mon-' + mo"
>{{ mo }}</el-button
>自定义
</div>
<div>
<el-button
v-for="y in [1, 2, 3, 4]"
@click="setDuration('Y', y)"
:key="'year-' + y"
>{{ y }}</el-button
>自定义
</div>
</div>
</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { ref, watch, computed } from 'vue';
const props = defineProps({ value: String });
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');
@@ -279,14 +78,14 @@ const cronStep = ref({
});
function pad(n) {
return n < 10 ? '0' + n : '' + n;
return n < 10 ? `0${n}` : `${n}`;
}
watch(
[fields, cronMode, cronAppoint, cronRange, cronStep],
() => {
// 组装cron表达式
let arr = cronFieldList.map((f) => {
const arr = cronFieldList.map((f) => {
if (cronMode.value[f.key] === 'every') return '*';
if (cronMode.value[f.key] === 'appoint')
return cronAppoint.value[f.key].join(',') || '*';
@@ -312,20 +111,23 @@ const isoDuration = ref('');
function setDuration(type, val) {
// 组装ISO 8601字符串
let d = isoDuration.value;
if (!d.includes(type)) d += val + type;
else d = d.replace(new RegExp(`\\d+${type}`), val + type);
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'
str += `/${
typeof isoDate.value === 'string'
? isoDate.value
: new Date(isoDate.value).toISOString());
if (isoDuration.value) str += '/' + isoDuration.value;
: new Date(isoDate.value).toISOString()
}`;
if (isoDuration.value) str += `/${isoDuration.value}`;
isoStr.value = str;
if (tab.value === 'iso') emit('change', isoStr.value);
}
@@ -340,3 +142,239 @@ watch(
{ 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>

View File

@@ -1,33 +1,14 @@
<template>
<div>
<div style="margin-bottom: 10px">
当前选择<el-input v-model="isoString" readonly style="width: 300px" />
</div>
<div v-for="unit in units" :key="unit.key" style="margin-bottom: 8px">
<span>{{ unit.label }}</span>
<el-button-group>
<el-button
v-for="val in unit.presets"
:key="val"
size="mini"
@click="setUnit(unit.key, val)"
>{{ val }}</el-button
>
<el-input
v-model.number="custom[unit.key]"
size="mini"
style="width: 60px; margin-left: 8px"
placeholder="自定义"
@change="setUnit(unit.key, custom[unit.key])"
/>
</el-button-group>
</div>
</div>
</template>
<script setup>
import { ref, watch, computed } from 'vue';
const props = defineProps({ value: String });
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 = [
@@ -42,7 +23,7 @@ const custom = ref({ Y: '', M: '', D: '', H: '', m: '', S: '' });
const isoString = ref('');
function setUnit(key, val) {
if (!val || isNaN(val)) {
if (!val || Number.isNaN(val)) {
custom.value[key] = '';
return;
}
@@ -52,13 +33,13 @@ function setUnit(key, val) {
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.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';
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);
}
@@ -84,3 +65,35 @@ watch(
{ 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>

View File

@@ -1,178 +1,51 @@
<template>
<div class="panel-tab__content">
<div style="margin-top: 10px">
<span>类型</span>
<el-button-group>
<el-button
size="mini"
:type="type === 'time' ? 'primary' : ''"
@click="setType('time')"
>时间</el-button
>
<el-button
size="mini"
:type="type === 'duration' ? 'primary' : ''"
@click="setType('duration')"
>持续</el-button
>
<el-button
size="mini"
:type="type === 'cycle' ? 'primary' : ''"
@click="setType('cycle')"
>循环</el-button
>
</el-button-group>
<el-icon v-if="valid" color="green" style="margin-left: 8px"
><CircleCheckFilled
/></el-icon>
</div>
<div style=" display: flex; align-items: center;margin-top: 10px">
<span>条件</span>
<el-input
v-model="condition"
:placeholder="placeholder"
style="width: calc(100% - 100px)"
:readonly="type !== 'duration' && type !== 'cycle'"
@focus="handleInputFocus"
@blur="updateNode"
>
<template #suffix>
<el-tooltip v-if="!valid" content="格式错误" placement="top">
<el-icon color="orange"><WarningFilled /></el-icon>
</el-tooltip>
<el-tooltip :content="helpText" placement="top">
<el-icon
color="#409EFF"
style="cursor: pointer"
@click="showHelp = true"
><QuestionFilled
/></el-icon>
</el-tooltip>
<el-button
v-if="type === 'time'"
@click="showDatePicker = true"
style="margin-left: 4px"
circle
size="small"
>
<Icon icon="ep:calendar" />
</el-button>
<el-button
v-if="type === 'duration'"
@click="showDurationDialog = true"
style="margin-left: 4px"
circle
size="small"
>
<Icon icon="ep:timer" />
</el-button>
<el-button
v-if="type === 'cycle'"
@click="showCycleDialog = true"
style="margin-left: 4px"
circle
size="small"
>
<Icon icon="ep:setting" />
</el-button>
</template>
</el-input>
</div>
<!-- 时间选择器 -->
<el-dialog
v-model="showDatePicker"
title="选择时间"
width="400px"
@close="showDatePicker = false"
>
<el-date-picker
v-model="dateValue"
type="datetime"
placeholder="选择日期时间"
style="width: 100%"
@change="onDateChange"
/>
<template #footer>
<el-button @click="showDatePicker = false">取消</el-button>
<el-button type="primary" @click="onDateConfirm">确定</el-button>
</template>
</el-dialog>
<!-- 持续时长选择器 -->
<el-dialog
v-model="showDurationDialog"
title="时间配置"
width="600px"
@close="showDurationDialog = false"
>
<DurationConfig :value="condition" @change="onDurationChange" />
<template #footer>
<el-button @click="showDurationDialog = false">取消</el-button>
<el-button type="primary" @click="onDurationConfirm">确定</el-button>
</template>
</el-dialog>
<!-- 循环配置器 -->
<el-dialog
v-model="showCycleDialog"
title="时间配置"
width="800px"
@close="showCycleDialog = false"
>
<CycleConfig :value="condition" @change="onCycleChange" />
<template #footer>
<el-button @click="showCycleDialog = false">取消</el-button>
<el-button type="primary" @click="onCycleConfirm">确定</el-button>
</template>
</el-dialog>
<!-- 帮助说明 -->
<el-dialog
v-model="showHelp"
title="格式说明"
width="600px"
@close="showHelp = false"
>
<div v-html="helpHtml"></div>
<template #footer>
<el-button @click="showHelp = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue';
import {
CircleCheckFilled,
WarningFilled,
QuestionFilled,
} from '@element-plus/icons-vue';
import DurationConfig from './DurationConfig.vue';
import CycleConfig from './CycleConfig.vue';
import { createListenerObject, updateElementExtensions } from '../../utils';
const bpmnInstances = () => (window as any).bpmnInstances;
const props = defineProps({ businessObject: Object });
const type = ref('time');
const condition = ref('');
const valid = ref(true);
const showDatePicker = ref(false);
const showDurationDialog = ref(false);
const showCycleDialog = ref(false);
const showHelp = ref(false);
const dateValue = ref(null);
const bpmnElement = ref(null);
import type { Ref } from 'vue';
const placeholder = computed(() => {
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(() => {
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(() => {
const helpHtml = computed<string>(() => {
if (type.value === 'duration') {
return `指定定时器之前要等待多长时间。S表示秒M表示分D表示天P表示时间段T表示精确到时间的时间段。<br>
时间格式依然为ISO 8601格式一年两个月三天四小时五分六秒内可以写成P1Y2M3DT4H5M6S。<br>
@@ -185,7 +58,7 @@ const helpHtml = computed(() => {
});
// 初始化和监听
function syncFromBusinessObject() {
function syncFromBusinessObject(): void {
if (props.businessObject) {
const timerDef = (props.businessObject.eventDefinitions || [])[0];
if (timerDef) {
@@ -205,7 +78,7 @@ function syncFromBusinessObject() {
onMounted(syncFromBusinessObject);
// 切换类型
function setType(t) {
function setType(t: string) {
type.value = t;
condition.value = '';
updateNode();
@@ -217,24 +90,24 @@ watch([type, condition], () => {
// updateNode() // 可以注释掉,避免频繁触发
});
function validate() {
function validate(): boolean {
if (type.value === 'time') {
return !!condition.value && !isNaN(Date.parse(condition.value));
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 /^(?:[0-9*/?, ]+|R\d*\/P.*)$/.test(condition.value);
}
return true;
}
// 选择时间
function onDateChange(val) {
function onDateChange(val: any) {
dateValue.value = val;
}
function onDateConfirm() {
function onDateConfirm(): void {
if (dateValue.value) {
condition.value = new Date(dateValue.value).toISOString();
showDatePicker.value = false;
@@ -243,35 +116,35 @@ function onDateConfirm() {
}
// 持续时长
function onDurationChange(val) {
function onDurationChange(val: string) {
condition.value = val;
}
function onDurationConfirm() {
function onDurationConfirm(): void {
showDurationDialog.value = false;
updateNode();
}
// 循环
function onCycleChange(val) {
function onCycleChange(val: string) {
condition.value = val;
}
function onCycleConfirm() {
function onCycleConfirm(): void {
showCycleDialog.value = false;
updateNode();
}
// 输入框聚焦时弹窗(可选)
function handleInputFocus() {
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() {
const moddle = window.bpmnInstances?.moddle;
const modeling = window.bpmnInstances?.modeling;
const elementRegistry = window.bpmnInstances?.elementRegistry;
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;
// 获取元素
@@ -340,6 +213,145 @@ watch(
);
</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>

View File

@@ -61,3 +61,15 @@ export const MenuOutlined = createIconifyIcon('ant-design:menu-outlined');
export const PlusOutlined = createIconifyIcon('ant-design:plus-outlined');
export const SelectOutlined = createIconifyIcon('ant-design:select-outlined');
export const CheckCircleFilled = createIconifyIcon(
'ant-design:check-circle-filled',
);
export const ExclamationCircleFilled = createIconifyIcon(
'ant-design:exclamation-circle-filled',
);
export const QuestionCircleFilled = createIconifyIcon(
'ant-design:question-circle-filled',
);

4954
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff