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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import { reactive, ref } from 'vue';
import { CommonStatusEnum } from '@vben/constants'; 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 { getProcessListenerPage } from '#/api/bpm/processListener';
import { ContentWrap } from '#/components/content-wrap'; import { ContentWrap } from '#/components/content-wrap';
@@ -71,30 +71,30 @@ const select = async (row: BpmProcessListenerApi.ProcessListener) => {
:pagination="false" :pagination="false"
:scroll="{ x: 'max-content' }" :scroll="{ x: 'max-content' }"
> >
<TableColumn title="名字" align="center" data-index="name" /> <Table.Column title="名字" align="center" dataIndex="name" />
<TableColumn title="类型" align="center" data-index="type"> <Table.Column title="类型" align="center" dataIndex="type">
<template #default="{ record }"> <template #default="{ record }">
<DictTag <DictTag
:type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE" :type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE"
:value="record.type" :value="record.type"
/> />
</template> </template>
</TableColumn> </Table.Column>
<TableColumn title="事件" align="center" data-index="event" /> <Table.Column title="事件" align="center" dataIndex="event" />
<TableColumn title="值类型" align="center" data-index="valueType"> <Table.Column title="值类型" align="center" dataIndex="valueType">
<template #default="{ record }"> <template #default="{ record }">
<DictTag <DictTag
:type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE" :type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE"
:value="record.valueType" :value="record.valueType"
/> />
</template> </template>
</TableColumn> </Table.Column>
<TableColumn title="值" align="center" data-index="value" /> <Table.Column title="值" align="center" dataIndex="value" />
<TableColumn title="操作" align="center"> <Table.Column title="操作" align="center">
<template #default="{ record }"> <template #default="{ record }">
<Button type="primary" @click="select(record)"> 选择 </Button> <Button type="primary" @click="select(record)"> 选择 </Button>
</template> </template>
</TableColumn> </Table.Column>
</Table> </Table>
<!-- 分页 --> <!-- 分页 -->
<div class="mt-4 flex justify-end"> <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> <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' }); defineOptions({ name: 'ElementProperties' });
const props = defineProps({ const props = defineProps({
id: String, id: {
type: String, type: String,
default: '',
},
type: {
type: String,
default: '',
},
}); });
const prefix = inject('prefix'); const prefix = inject('prefix');
// const width = inject('width') // const width = inject('width')
const elementPropertyList = ref<any[]>([]); const elementPropertyList = ref<Array<{ name: string; value: string }>>([]);
const propertyForm = ref<any>({}); const propertyForm = ref<{ name?: string; value?: string }>({});
const editingPropertyIndex = ref(-1); const editingPropertyIndex = ref(-1);
const propertyFormModelVisible = ref(false); const propertyFormModelVisible = ref(false);
const bpmnElement = ref(); const bpmnElement = ref<any>();
const otherExtensionList = ref(); const otherExtensionList = ref<any[]>([]);
const bpmnElementProperties = ref(); const bpmnElementProperties = ref<any[]>([]);
const bpmnElementPropertyList = ref(); const bpmnElementPropertyList = ref<any[]>([]);
const attributeFormRef = ref(); const attributeFormRef = ref<any>();
const bpmnInstances = () => (window as any)?.bpmnInstances; const bpmnInstances = () => (window as any)?.bpmnInstances;
const resetAttributesList = () => { const resetAttributesList = () => {
@@ -94,7 +47,7 @@ const resetAttributesList = () => {
bpmnElementProperties.value = bpmnElementProperties.value =
// bpmnElement.value.businessObject?.extensionElements?.filter((ex) => { // bpmnElement.value.businessObject?.extensionElements?.filter((ex) => {
bpmnElement.value.businessObject?.extensionElements?.values?.filter( bpmnElement.value.businessObject?.extensionElements?.values?.filter(
(ex) => { (ex: any) => {
if (ex.$type !== `${prefix}:Properties`) { if (ex.$type !== `${prefix}:Properties`) {
otherExtensionList.value.push(ex); otherExtensionList.value.push(ex);
} }
@@ -103,30 +56,38 @@ const resetAttributesList = () => {
) ?? []; ) ?? [];
// 保存所有的 扩展属性字段 // 保存所有的 扩展属性字段
bpmnElementPropertyList.value = bpmnElementProperties.value.reduce( bpmnElementPropertyList.value = bpmnElementProperties.value.flatMap(
(pre, current) => pre.concat(current.values), (current: any) => current.values,
[],
); );
// 复制 显示 // 复制 显示
elementPropertyList.value = JSON.parse( elementPropertyList.value = structuredClone(
JSON.stringify(bpmnElementPropertyList.value ?? []), bpmnElementPropertyList.value ?? [],
); );
}; };
const openAttributesForm = (attr, index) => {
const openAttributesForm = (
attr: null | { name: string; value: string },
index: number,
) => {
editingPropertyIndex.value = index; editingPropertyIndex.value = index;
propertyForm.value = index === -1 ? {} : JSON.parse(JSON.stringify(attr)); // @ts-ignore
propertyForm.value = index === -1 ? {} : structuredClone(attr);
propertyFormModelVisible.value = true; propertyFormModelVisible.value = true;
nextTick(() => { nextTick(() => {
if (attributeFormRef.value) attributeFormRef.value.clearValidate(); if (attributeFormRef.value) attributeFormRef.value.clearValidate();
}); });
}; };
const removeAttributes = (attr, index) => {
console.log(attr, 'attr'); const removeAttributes = (
ElMessageBox.confirm('确认移除该属性吗?', '提示', { _attr: { name: string; value: string },
confirmButtonText: '确 认', index: number,
cancelButtonText: '取 消', ) => {
}) Modal.confirm({
.then(() => { title: '提示',
content: '确认移除该属性吗?',
okText: '确 认',
cancelText: '取 消',
onOk() {
elementPropertyList.value.splice(index, 1); elementPropertyList.value.splice(index, 1);
bpmnElementPropertyList.value.splice(index, 1); bpmnElementPropertyList.value.splice(index, 1);
// 新建一个属性字段的保存列表 // 新建一个属性字段的保存列表
@@ -138,22 +99,17 @@ const removeAttributes = (attr, index) => {
); );
updateElementExtensions(propertiesObject); updateElementExtensions(propertiesObject);
resetAttributesList(); resetAttributesList();
})
.catch(() => console.info('操作取消'));
};
const saveAttribute = () => {
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,
}, },
); onCancel() {
} else { // console.info('操作取消');
},
});
};
const saveAttribute = () => {
// console.log(propertyForm.value, 'propertyForm.value');
const { name, value } = propertyForm.value;
if (editingPropertyIndex.value === -1) {
// 新建属性字段 // 新建属性字段
const newPropertyObject = bpmnInstances().moddle.create( const newPropertyObject = bpmnInstances().moddle.create(
`${prefix}:Property`, `${prefix}:Property`,
@@ -166,17 +122,27 @@ const saveAttribute = () => {
const propertiesObject = bpmnInstances().moddle.create( const propertiesObject = bpmnInstances().moddle.create(
`${prefix}:Properties`, `${prefix}:Properties`,
{ {
values: bpmnElementPropertyList.value.concat([newPropertyObject]), values: [...bpmnElementPropertyList.value, newPropertyObject],
}, },
); );
updateElementExtensions(propertiesObject); updateElementExtensions(propertiesObject);
} else {
bpmnInstances().modeling.updateModdleProperties(
toRaw(bpmnElement.value),
toRaw(bpmnElementPropertyList.value)[toRaw(editingPropertyIndex.value)],
{
name,
value,
},
);
} }
propertyFormModelVisible.value = false; propertyFormModelVisible.value = false;
resetAttributesList(); resetAttributesList();
}; };
const updateElementExtensions = (properties) => {
const updateElementExtensions = (properties: any) => {
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', { const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
values: otherExtensionList.value.concat([properties]), values: [...otherExtensionList.value, properties],
}); });
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
extensionElements: extensions, extensionElements: extensions,
@@ -187,9 +153,85 @@ watch(
() => props.id, () => props.id,
(val) => { (val) => {
if (val) { if (val) {
val && val.length && resetAttributesList(); val && val.length > 0 && resetAttributesList();
} }
}, },
{ immediate: true }, { immediate: true },
); );
</script> </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> <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 signalList = ref<any[]>([]);
const messageList = ref<any[]>([]); const messageList = ref<any[]>([]);
const dialogVisible = ref(false); const dialogVisible = ref(false);
@@ -98,22 +24,20 @@ const rootElements = ref();
const messageIdMap = ref(); const messageIdMap = ref();
const signalIdMap = ref(); const signalIdMap = ref();
const modelConfig = computed(() => { const modelConfig = computed(() => {
if (modelType.value === 'message') { return modelType.value === 'message'
return { title: '创建消息', idLabel: '消息ID', nameLabel: '消息名称' }; ? { title: '创建消息', idLabel: '消息ID', nameLabel: '消息名称' }
} else { : { title: '创建信号', idLabel: '信号ID', nameLabel: '信号名称' };
return { title: '创建信号', idLabel: '信号ID', nameLabel: '信号名称' };
}
}); });
const bpmnInstances = () => (window as any)?.bpmnInstances; const bpmnInstances = () => (window as any)?.bpmnInstances;
const initDataList = () => { const initDataList = () => {
console.log(window, 'window'); // console.log(window, 'window');
rootElements.value = bpmnInstances().modeler.getDefinitions().rootElements; rootElements.value = bpmnInstances().modeler.getDefinitions().rootElements;
messageIdMap.value = {}; messageIdMap.value = {};
signalIdMap.value = {}; signalIdMap.value = {};
messageList.value = []; messageList.value = [];
signalList.value = []; signalList.value = [];
rootElements.value.forEach((el) => { rootElements.value.forEach((el: any) => {
if (el.$type === 'bpmn:Message') { if (el.$type === 'bpmn:Message') {
messageIdMap.value[el.id] = true; messageIdMap.value[el.id] = true;
messageList.value.push({ ...el }); messageList.value.push({ ...el });
@@ -124,7 +48,7 @@ const initDataList = () => {
} }
}); });
}; };
const openModel = (type) => { const openModel = (type: any) => {
modelType.value = type; modelType.value = type;
modelObjectForm.value = {}; modelObjectForm.value = {};
dialogVisible.value = true; dialogVisible.value = true;
@@ -157,3 +81,98 @@ onMounted(() => {
initDataList(); initDataList();
}); });
</script> </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> <template>
<div class="panel-tab__content"> <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 --> <!-- add by 芋艿由于异步延续暂时用不到所以这里 display none -->
<el-form-item label="异步延续" style="display: none"> <Form.Item label="异步延续" style="display: none">
<el-checkbox <Checkbox
v-model="taskConfigForm.asyncBefore" v-model:checked="taskConfigForm.asyncBefore"
label="异步前"
value="异步前"
@change="changeTaskAsync" @change="changeTaskAsync"
/> >
<el-checkbox 异步前
v-model="taskConfigForm.asyncAfter" </Checkbox>
label="异步后" <Checkbox
value="异步后" v-model:checked="taskConfigForm.asyncAfter"
@change="changeTaskAsync" @change="changeTaskAsync"
/> >
<el-checkbox 异步后
v-model="taskConfigForm.exclusive" </Checkbox>
<Checkbox
v-model:checked="taskConfigForm.exclusive"
v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore" v-if="taskConfigForm.asyncAfter || taskConfigForm.asyncBefore"
label="排除"
value="排除"
@change="changeTaskAsync" @change="changeTaskAsync"
/> >
</el-form-item> 排除
</Checkbox>
</Form.Item>
<component :is="witchTaskComponent" v-bind="$props" /> <component :is="witchTaskComponent" v-bind="$props" />
</el-form> </Form>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Form } from 'ant-design-vue';
import { ref, watch } from 'vue';
import { installedComponent } from './data'; import { installedComponent } from './data';
defineOptions({ name: 'ElementTaskConfig' }); 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; return installedComponent[elementType].name;
}; };
export const isTaskCollapseItemShow = (elementType) => { export const isTaskCollapseItemShow = (
elementType: keyof typeof installedComponent,
) => {
return installedComponent[elementType]; 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> <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' }); defineOptions({ name: 'CallActivity' });
const props = defineProps({ const props = defineProps({
id: String, id: { type: String, default: '' },
type: String, type: { type: String, default: '' },
}); });
const prefix = inject('prefix'); const prefix = inject('prefix');
const message = useMessage();
const formData = ref({ const formData = ref<FormData>({
processInstanceName: '', processInstanceName: '',
calledElement: '', calledElement: '',
inheritVariables: false, inheritVariables: false,
@@ -191,36 +40,42 @@ const formData = ref({
inheritBusinessKey: false, inheritBusinessKey: false,
calledElementType: 'key', calledElementType: 'key',
}); });
const inVariableList = ref(); const inVariableList = ref<any[]>([]);
const outVariableList = ref(); const outVariableList = ref<any[]>([]);
const variableType = ref(); // 参数类型 const variableType = ref<string>(); // 参数类型
const editingVariableIndex = ref(-1); // 编辑参数下标 const editingVariableIndex = ref<number>(-1); // 编辑参数下标
const variableDialogVisible = ref(false); const variableDialogVisible = ref<boolean>(false);
const varialbeFormRef = ref(); const varialbeFormRef = ref<any>();
const varialbeFormData = ref({ const varialbeFormData = ref<{
source: string;
target: string;
}>({
source: '', source: '',
target: '', target: '',
}); });
const bpmnInstances = () => (window as any)?.bpmnInstances; const bpmnInstances = () => (window as any)?.bpmnInstances;
const bpmnElement = ref(); const bpmnElement = ref<any>();
const otherExtensionList = ref(); const otherExtensionList = ref<any[]>([]);
const initCallActivity = () => { const initCallActivity = () => {
bpmnElement.value = bpmnInstances().bpmnElement; 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] = formData.value[key] =
bpmnElement.value.businessObject[key] ?? formData.value[key]; bpmnElement.value.businessObject[key] ??
formData.value[key as keyof FormData];
}); });
otherExtensionList.value = []; // 其他扩展配置 otherExtensionList.value = []; // 其他扩展配置
inVariableList.value = []; inVariableList.value.length = 0;
outVariableList.value = []; outVariableList.value.length = 0;
// 初始化输入参数 // 初始化输入参数
bpmnElement.value.businessObject?.extensionElements?.values?.forEach((ex) => { bpmnElement.value.businessObject?.extensionElements?.values?.forEach(
(ex: any) => {
if (ex.$type === `${prefix}:In`) { if (ex.$type === `${prefix}:In`) {
inVariableList.value.push(ex); inVariableList.value.push(ex);
} else if (ex.$type === `${prefix}:Out`) { } else if (ex.$type === `${prefix}:Out`) {
@@ -228,7 +83,8 @@ const initCallActivity = () => {
} else { } else {
otherExtensionList.value.push(ex); otherExtensionList.value.push(ex);
} }
}); },
);
// 默认添加 // 默认添加
// bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { // 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), { bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
[attr]: formData.value[attr], [attr]: formData.value[attr],
}); });
}; };
const openVariableForm = (type, data, index) => { const openVariableForm = (type: string, data: any, index: number) => {
editingVariableIndex.value = index; editingVariableIndex.value = index;
variableType.value = type; variableType.value = type;
varialbeFormData.value = index === -1 ? {} : { ...data }; varialbeFormData.value = index === -1 ? {} : { ...data };
variableDialogVisible.value = true; variableDialogVisible.value = true;
}; };
const removeVariable = async (type, index) => { const removeVariable = async (type: string, index: number) => {
try { try {
await message.delConfirm(); await alert('是否确认删除?');
if (type === 'in') { if (type === 'in') {
inVariableList.value.splice(index, 1); inVariableList.value.splice(index, 1);
} }
@@ -313,7 +169,7 @@ watch(
() => props.id, () => props.id,
(val) => { (val) => {
val && val &&
val.length && val.length > 0 &&
nextTick(() => { nextTick(() => {
initCallActivity(); initCallActivity();
}); });
@@ -322,4 +178,184 @@ watch(
); );
</script> </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> <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"> <script setup lang="ts">
import { CommonStatusEnum } from '@/utils/constants'; import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
import {
ProcessExpressionApi, import { reactive, ref } from 'vue';
ProcessExpressionVO,
} from '@/api/bpm/processExpression'; 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 流程 表单 */ /** BPM 流程 表单 */
defineOptions({ name: 'ProcessExpressionDialog' }); defineOptions({ name: 'ProcessExpressionDialog' });
/** 提交表单 */
const emit = defineEmits(['select']);
const dialogVisible = ref(false); // 弹窗的是否展示 const dialogVisible = ref(false); // 弹窗的是否展示
const loading = ref(true); // 列表的加载中 const loading = ref(true); // 列表的加载中
const list = ref<ProcessExpressionVO[]>([]); // 列表的数据 const list = ref<BpmProcessExpressionApi.ProcessExpression[]>([]); // 列表的数据
const total = ref(0); // 列表的总页数 const total = ref(0); // 列表的总页数
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
@@ -62,8 +40,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
const getList = async () => { const getList = async () => {
loading.value = true; loading.value = true;
try { try {
const data = const data = await getProcessExpressionPage(queryParams);
await ProcessExpressionApi.getProcessExpressionPage(queryParams);
list.value = data.list; list.value = data.list;
total.value = data.total; total.value = data.total;
} finally { } finally {
@@ -71,11 +48,49 @@ const getList = async () => {
} }
}; };
/** 提交表单 */ // 定义 select 事件,用于操作成功后的回调
const emit = defineEmits(['success']); // 定义 success 事件,用于操作成功后的回调 const select = async (row: BpmProcessExpressionApi.ProcessExpression) => {
const select = async (row) => {
dialogVisible.value = false; dialogVisible.value = false;
// 发送操作成功的事件 // 发送操作成功的事件
emit('select', row); emit('select', row);
}; };
// const handleCancel = () => {
// dialogVisible.value = false;
// };
</script> </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> <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' }); defineOptions({ name: 'ReceiveTask' });
const props = defineProps({ const props = defineProps({
id: String, id: { type: String, default: '' },
type: String, type: { type: String, default: '' },
}); });
const message = useMessage();
const bindMessageId = ref(''); const bindMessageId = ref('');
const newMessageForm = ref<any>({}); const newMessageForm = ref<Record<string, any>>({});
const messageMap = ref<any>({}); const messageMap = ref<Record<string, any>>({});
const messageModelVisible = ref(false); const messageModelVisible = ref(false);
const bpmnElement = ref<any>(); const bpmnElement = ref<any>();
const bpmnMessageRefsMap = ref<any>(); const bpmnMessageRefsMap = ref<Record<string, any>>();
const bpmnRootElements = ref<any>(); const bpmnRootElements = ref<any>();
const bpmnInstances = () => (window as any).bpmnInstances; const bpmnInstances = () => (window as any).bpmnInstances;
@@ -88,17 +56,18 @@ const createNewMessage = () => {
); );
bpmnRootElements.value.push(newMessage); bpmnRootElements.value.push(newMessage);
messageMap.value[newMessageForm.value.id] = newMessageForm.value.name; 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; messageModelVisible.value = false;
}; };
const updateTaskMessage = (messageId) => { const updateTaskMessage = (messageId: string) => {
if (messageId === '-1') { if (messageId === '-1') {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
messageRef: null, messageRef: null,
}); });
} else { } else {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
messageRef: bpmnMessageRefsMap.value[messageId], messageRef: bpmnMessageRefsMap.value?.[messageId],
}); });
} }
}; };
@@ -108,9 +77,10 @@ onMounted(() => {
bpmnRootElements.value = bpmnRootElements.value =
bpmnInstances().modeler.getDefinitions().rootElements; bpmnInstances().modeler.getDefinitions().rootElements;
bpmnRootElements.value bpmnRootElements.value
.filter((el) => el.$type === 'bpmn:Message') .filter((el: any) => el.$type === 'bpmn:Message')
.forEach((m) => { .forEach((m: any) => {
bpmnMessageRefsMap.value[m.id] = m; // @ts-ignore
bpmnMessageRefsMap.value?.[m.id] = m;
messageMap.value[m.id] = m.name; messageMap.value[m.id] = m.name;
}); });
messageMap.value['-1'] = '无'; messageMap.value['-1'] = '无';
@@ -130,3 +100,57 @@ watch(
{ immediate: true }, { immediate: true },
); );
</script> </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> <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' }); defineOptions({ name: 'ScriptTask' });
const props = defineProps({ const props = defineProps({
id: String, id: {
type: String, type: String,
default: '',
},
type: {
type: String,
default: '',
},
}); });
const defaultTaskForm = ref({ const defaultTaskForm = ref({
scriptFormat: '', scriptFormat: '',
@@ -65,17 +40,19 @@ const bpmnElement = ref();
const bpmnInstances = () => (window as any)?.bpmnInstances; const bpmnInstances = () => (window as any)?.bpmnInstances;
const resetTaskForm = () => { const resetTaskForm = () => {
for (let key in defaultTaskForm.value) { for (const key in defaultTaskForm.value) {
let value = // @ts-ignore
bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key]; scriptTaskForm.value[key] =
scriptTaskForm.value[key] = value; bpmnElement.value?.businessObject[
key as keyof typeof defaultTaskForm.value
] || defaultTaskForm.value[key as keyof typeof defaultTaskForm.value];
} }
scriptTaskForm.value.scriptType = scriptTaskForm.value.script scriptTaskForm.value.scriptType = scriptTaskForm.value.script
? 'inline' ? 'inline'
: 'external'; : 'external';
}; };
const updateElementTask = () => { const updateElementTask = () => {
let taskAttr = Object.create(null); const taskAttr = Object.create(null);
taskAttr.scriptFormat = scriptTaskForm.value.scriptFormat || null; taskAttr.scriptFormat = scriptTaskForm.value.scriptFormat || null;
taskAttr.resultVariable = scriptTaskForm.value.resultVariable || null; taskAttr.resultVariable = scriptTaskForm.value.resultVariable || null;
if (scriptTaskForm.value.scriptType === 'inline') { if (scriptTaskForm.value.scriptType === 'inline') {
@@ -103,3 +80,50 @@ watch(
{ immediate: true }, { immediate: true },
); );
</script> </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> <script lang="ts" setup>
import { nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
import { FormItem, Input, Select } from 'ant-design-vue';
defineOptions({ name: 'ServiceTask' }); defineOptions({ name: 'ServiceTask' });
const props = defineProps({ const props = defineProps({
id: String, id: { type: String, default: '' },
type: String, type: { type: String, default: '' },
}); });
const defaultTaskForm = ref({ const defaultTaskForm = ref({
@@ -66,8 +22,9 @@ const bpmnElement = ref();
const bpmnInstances = () => (window as any)?.bpmnInstances; const bpmnInstances = () => (window as any)?.bpmnInstances;
const resetTaskForm = () => { const resetTaskForm = () => {
for (let key in defaultTaskForm.value) { for (const key in defaultTaskForm.value) {
let value = const value =
// @ts-ignore
bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key]; bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key];
serviceTaskForm.value[key] = value; serviceTaskForm.value[key] = value;
if (value) { if (value) {
@@ -77,9 +34,9 @@ const resetTaskForm = () => {
}; };
const updateElementTask = () => { const updateElementTask = () => {
let taskAttr = Object.create(null); const taskAttr = Object.create(null);
const type = serviceTaskForm.value.executeType; 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; if (key !== 'executeType' && key !== type) taskAttr[key] = null;
} }
taskAttr[type] = serviceTaskForm.value[type] || ''; taskAttr[type] = serviceTaskForm.value[type] || '';
@@ -101,3 +58,54 @@ watch(
{ immediate: true }, { immediate: true },
); );
</script> </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> <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 { import {
CANDIDATE_STRATEGY, CANDIDATE_STRATEGY,
CandidateStrategy, CandidateStrategy,
FieldPermissionType, FieldPermissionType,
MULTI_LEVEL_DEPT, MULTI_LEVEL_DEPT,
} from '@/components/SimpleProcessDesignerV2/src/consts'; } from '#/components/simple-process-design/consts';
import { defaultProps, handleTree } from '@/utils/tree'; import { useFormFieldsPermission } from '#/components/simple-process-design/helpers';
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';
import ProcessExpressionDialog from './ProcessExpressionDialog.vue'; import ProcessExpressionDialog from './ProcessExpressionDialog.vue';
import { ProcessExpressionVO } from '@/api/bpm/processExpression';
import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node';
defineOptions({ name: 'UserTask' }); defineOptions({ name: 'UserTask' });
const props = defineProps({ const props = defineProps({
id: String, id: {
type: String, type: String,
default: '',
},
type: {
type: String,
default: '',
},
}); });
const prefix = inject('prefix'); const prefix = inject('prefix');
const userTaskForm = ref({ const userTaskForm = ref({
@@ -252,16 +62,24 @@ const userTaskForm = ref({
candidateParam: [], // 分配选项 candidateParam: [], // 分配选项
skipExpression: '', // 跳过表达式 skipExpression: '', // 跳过表达式
}); });
const bpmnElement = ref(); const bpmnElement = ref<any>();
const bpmnInstances = () => (window as any)?.bpmnInstances; const bpmnInstances = () => (window as Record<string, any>)?.bpmnInstances;
const roleOptions = ref<RoleApi.RoleVO[]>([]); // 角色列表 const roleOptions = ref<SystemRoleApi.Role[]>([]); // 角色列表
const deptTreeOptions = ref(); // 部门树 const deptTreeOptions = ref<any>(); // 部门树
const postOptions = ref<PostApi.PostVO[]>([]); // 岗位列表 const postOptions = ref<SystemPostApi.Post[]>([]); // 岗位列表
const userOptions = ref<UserApi.UserVO[]>([]); // 用户列表 const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]); // 用户组列表 const userGroupOptions = ref<BpmUserGroupApi.UserGroup[]>([]); // 用户组列表
const treeRef = ref<any>();
const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ); const { formFieldOptions } = useFormFieldsPermission(FieldPermissionType.READ);
// 定义 TreeSelect 的默认属性映射
const defaultProps = {
children: 'children',
label: 'name',
value: 'id',
};
// 表单内用户字段选项, 必须是必填和用户选择器 // 表单内用户字段选项, 必须是必填和用户选择器
const userFieldOnFormOptions = computed(() => { const userFieldOnFormOptions = computed(() => {
return formFieldOptions.filter((item) => item.type === 'UserSelect'); return formFieldOptions.filter((item) => item.type === 'UserSelect');
@@ -275,21 +93,21 @@ const deptLevel = ref(1);
const deptLevelLabel = computed(() => { const deptLevelLabel = computed(() => {
let label = '部门负责人来源'; let label = '部门负责人来源';
if ( if (
userTaskForm.value.candidateStrategy == userTaskForm.value.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
) { ) {
label = label + '(指定部门向上)'; label = `${label}(指定部门向上)`;
} else if ( } else if (
userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER
) { ) {
label = label + '(表单内部门向上)'; label = `${label}(表单内部门向上)`;
} else { } else {
label = label + '(发起人部门向上)'; label = `${label}(发起人部门向上)`;
} }
return label; return label;
}); });
const otherExtensions = ref(); const otherExtensions = ref<any>();
const resetTaskForm = () => { const resetTaskForm = () => {
const businessObject = bpmnElement.value.businessObject; const businessObject = bpmnElement.value.businessObject;
@@ -301,50 +119,54 @@ const resetTaskForm = () => {
businessObject?.extensionElements ?? businessObject?.extensionElements ??
bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] }); bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] });
userTaskForm.value.candidateStrategy = extensionElements.values?.filter( userTaskForm.value.candidateStrategy = extensionElements.values?.filter(
(ex) => ex.$type === `${prefix}:CandidateStrategy`, (ex: any) => ex.$type === `${prefix}:CandidateStrategy`,
)?.[0]?.value; )?.[0]?.value;
const candidateParamStr = extensionElements.values?.filter( const candidateParamStr = extensionElements.values?.filter(
(ex) => ex.$type === `${prefix}:CandidateParam`, (ex: any) => ex.$type === `${prefix}:CandidateParam`,
)?.[0]?.value; )?.[0]?.value;
if (candidateParamStr && candidateParamStr.length > 0) { if (candidateParamStr && candidateParamStr.length > 0) {
// eslint-disable-next-line unicorn/prefer-switch
if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) { if (userTaskForm.value.candidateStrategy === CandidateStrategy.EXPRESSION) {
// 特殊:流程表达式,只有一个 input 输入框 // 特殊:流程表达式,只有一个 input 输入框
// @ts-ignore
userTaskForm.value.candidateParam = [candidateParamStr]; userTaskForm.value.candidateParam = [candidateParamStr];
} else if ( } else if (
userTaskForm.value.candidateStrategy == userTaskForm.value.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER CandidateStrategy.MULTI_LEVEL_DEPT_LEADER
) { ) {
// 特殊:多级不部门负责人,需要通过'|'分割 // 特殊:多级不部门负责人,需要通过'|'分割
userTaskForm.value.candidateParam = candidateParamStr userTaskForm.value.candidateParam = candidateParamStr
.split('|')[0] .split('|')[0]
.split(',') .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 return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
? item ? item
: num; : num;
}); });
deptLevel.value = +candidateParamStr.split('|')[1]; deptLevel.value = +candidateParamStr.split('|')[1];
} else if ( } else if (
userTaskForm.value.candidateStrategy == userTaskForm.value.candidateStrategy ===
CandidateStrategy.START_USER_DEPT_LEADER || CandidateStrategy.START_USER_DEPT_LEADER ||
userTaskForm.value.candidateStrategy == userTaskForm.value.candidateStrategy ===
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
) { ) {
// @ts-ignore
userTaskForm.value.candidateParam = +candidateParamStr; userTaskForm.value.candidateParam = +candidateParamStr;
deptLevel.value = +candidateParamStr; deptLevel.value = +candidateParamStr;
} else if ( } else if (
userTaskForm.value.candidateStrategy == CandidateStrategy.FORM_DEPT_LEADER userTaskForm.value.candidateStrategy ===
CandidateStrategy.FORM_DEPT_LEADER
) { ) {
userTaskForm.value.candidateParam = candidateParamStr.split('|')[0]; userTaskForm.value.candidateParam = candidateParamStr.split('|')[0];
deptLevel.value = +candidateParamStr.split('|')[1]; deptLevel.value = +candidateParamStr.split('|')[1];
} else { } else {
userTaskForm.value.candidateParam = candidateParamStr userTaskForm.value.candidateParam = candidateParamStr
.split(',') .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 return num > Number.MAX_SAFE_INTEGER || num < -Number.MAX_SAFE_INTEGER
? item ? item
: num; : num;
@@ -356,42 +178,41 @@ const resetTaskForm = () => {
otherExtensions.value = otherExtensions.value =
extensionElements.values?.filter( extensionElements.values?.filter(
(ex) => (ex: any) =>
ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateStrategy` &&
ex.$type !== `${prefix}:CandidateParam`, ex.$type !== `${prefix}:CandidateParam`,
) ?? []; ) ?? [];
// 跳过表达式 // 跳过表达式
if (businessObject.skipExpression != undefined) { userTaskForm.value.skipExpression =
userTaskForm.value.skipExpression = businessObject.skipExpression; businessObject.skipExpression === undefined
} else { ? ''
userTaskForm.value.skipExpression = ''; : businessObject.skipExpression;
}
// 改用通过extensionElements来存储数据 // 改用通过extensionElements来存储数据
return;
if (businessObject.candidateStrategy != undefined) { // if (businessObject.candidateStrategy != undefined) {
userTaskForm.value.candidateStrategy = parseInt( // userTaskForm.value.candidateStrategy = parseInt(
businessObject.candidateStrategy, // businessObject.candidateStrategy,
) as any; // ) as any;
} else { // } else {
userTaskForm.value.candidateStrategy = undefined; // userTaskForm.value.candidateStrategy = undefined;
} // }
if ( // if (
businessObject.candidateParam && // businessObject.candidateParam &&
businessObject.candidateParam.length > 0 // businessObject.candidateParam.length > 0
) { // ) {
if (userTaskForm.value.candidateStrategy === 60) { // if (userTaskForm.value.candidateStrategy === 60) {
// 特殊:流程表达式,只有一个 input 输入框 // // 特殊:流程表达式,只有一个 input 输入框
userTaskForm.value.candidateParam = [businessObject.candidateParam]; // userTaskForm.value.candidateParam = [businessObject.candidateParam];
} else { // } else {
userTaskForm.value.candidateParam = businessObject.candidateParam // userTaskForm.value.candidateParam = businessObject.candidateParam
.split(',') // .split(',')
.map((item) => item); // .map((item) => item);
} // }
} else { // } else {
userTaskForm.value.candidateParam = []; // userTaskForm.value.candidateParam = [];
} // }
}; };
/** 更新 candidateStrategy 字段时,需要清空 candidateParam并触发 bpmn 图更新 */ /** 更新 candidateStrategy 字段时,需要清空 candidateParam并触发 bpmn 图更新 */
@@ -410,27 +231,26 @@ const changeCandidateStrategy = () => {
/** 选中某个 options 时候,更新 bpmn 图 */ /** 选中某个 options 时候,更新 bpmn 图 */
const updateElementTask = () => { const updateElementTask = () => {
let candidateParam = let candidateParam = Array.isArray(userTaskForm.value.candidateParam)
userTaskForm.value.candidateParam instanceof Array
? userTaskForm.value.candidateParam.join(',') ? userTaskForm.value.candidateParam.join(',')
: userTaskForm.value.candidateParam; : userTaskForm.value.candidateParam;
// 特殊处理多级部门情况 // 特殊处理多级部门情况
if ( if (
userTaskForm.value.candidateStrategy == userTaskForm.value.candidateStrategy ===
CandidateStrategy.MULTI_LEVEL_DEPT_LEADER || 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 ( if (
userTaskForm.value.candidateStrategy == userTaskForm.value.candidateStrategy ===
CandidateStrategy.START_USER_DEPT_LEADER || CandidateStrategy.START_USER_DEPT_LEADER ||
userTaskForm.value.candidateStrategy == userTaskForm.value.candidateStrategy ===
CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER
) { ) {
candidateParam = deptLevel.value + ''; candidateParam = `${deptLevel.value}`;
} }
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', { const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
@@ -449,11 +269,11 @@ const updateElementTask = () => {
}); });
// 改用通过extensionElements来存储数据 // 改用通过extensionElements来存储数据
return; // return;
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { // bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
candidateStrategy: userTaskForm.value.candidateStrategy, // candidateStrategy: userTaskForm.value.candidateStrategy,
candidateParam: userTaskForm.value.candidateParam.join(','), // candidateParam: userTaskForm.value.candidateParam.join(','),
}); // });
}; };
const updateSkipExpression = () => { const updateSkipExpression = () => {
@@ -472,18 +292,22 @@ const updateSkipExpression = () => {
}; };
// 打开监听器弹窗 // 打开监听器弹窗
const processExpressionDialogRef = ref(); const processExpressionDialogRef = ref<any>();
const openProcessExpressionDialog = async () => { const openProcessExpressionDialog = async () => {
processExpressionDialogRef.value.open(); processExpressionDialogRef.value.open();
}; };
const selectProcessExpression = (expression: ProcessExpressionVO) => { const selectProcessExpression = (
expression: BpmProcessExpressionApi.ProcessExpression,
) => {
// @ts-ignore
userTaskForm.value.candidateParam = [expression.expression]; userTaskForm.value.candidateParam = [expression.expression];
updateElementTask(); updateElementTask();
}; };
const handleFormUserChange = (e) => { const handleFormUserChange = (e: any) => {
if (e === 'PROCESS_START_USER_ID') { if (e === 'PROCESS_START_USER_ID') {
userTaskForm.value.candidateParam = []; userTaskForm.value.candidateParam = [];
// @ts-ignore
userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER; userTaskForm.value.candidateStrategy = CandidateStrategy.START_USER;
} }
updateElementTask(); updateElementTask();
@@ -502,19 +326,238 @@ watch(
onMounted(async () => { 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'); 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(() => { onBeforeUnmount(() => {
bpmnElement.value = null; bpmnElement.value = null;
}); });
</script> </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> <script setup>
import { ref, watch, computed } from 'vue'; import { ref, watch } from 'vue';
const props = defineProps({ value: String });
import {
Button,
Checkbox,
DatePicker,
Input,
InputNumber,
Radio,
Tabs,
} from 'ant-design-vue';
const props = defineProps({
value: {
type: String,
default: '',
},
});
const emit = defineEmits(['change']); const emit = defineEmits(['change']);
const tab = ref('cron'); const tab = ref('cron');
@@ -279,14 +78,14 @@ const cronStep = ref({
}); });
function pad(n) { function pad(n) {
return n < 10 ? '0' + n : '' + n; return n < 10 ? `0${n}` : `${n}`;
} }
watch( watch(
[fields, cronMode, cronAppoint, cronRange, cronStep], [fields, cronMode, cronAppoint, cronRange, cronStep],
() => { () => {
// 组装cron表达式 // 组装cron表达式
let arr = cronFieldList.map((f) => { const arr = cronFieldList.map((f) => {
if (cronMode.value[f.key] === 'every') return '*'; if (cronMode.value[f.key] === 'every') return '*';
if (cronMode.value[f.key] === 'appoint') if (cronMode.value[f.key] === 'appoint')
return cronAppoint.value[f.key].join(',') || '*'; return cronAppoint.value[f.key].join(',') || '*';
@@ -312,20 +111,23 @@ const isoDuration = ref('');
function setDuration(type, val) { function setDuration(type, val) {
// 组装ISO 8601字符串 // 组装ISO 8601字符串
let d = isoDuration.value; let d = isoDuration.value;
if (!d.includes(type)) d += val + type; if (d.includes(type)) {
else d = d.replace(new RegExp(`\\d+${type}`), val + type); d = d.replace(new RegExp(`\\d+${type}`), val + type);
} else {
d += val + type;
}
isoDuration.value = d; isoDuration.value = d;
updateIsoStr(); updateIsoStr();
} }
function updateIsoStr() { function updateIsoStr() {
let str = `R${repeat.value}`; let str = `R${repeat.value}`;
if (isoDate.value) if (isoDate.value)
str += str += `/${
'/' + typeof isoDate.value === 'string'
(typeof isoDate.value === 'string'
? isoDate.value ? isoDate.value
: new Date(isoDate.value).toISOString()); : new Date(isoDate.value).toISOString()
if (isoDuration.value) str += '/' + isoDuration.value; }`;
if (isoDuration.value) str += `/${isoDuration.value}`;
isoStr.value = str; isoStr.value = str;
if (tab.value === 'iso') emit('change', isoStr.value); if (tab.value === 'iso') emit('change', isoStr.value);
} }
@@ -340,3 +142,239 @@ watch(
{ immediate: true }, { immediate: true },
); );
</script> </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> <script setup>
import { ref, watch, computed } from 'vue'; import { ref, watch } from 'vue';
const props = defineProps({ value: String });
import { Button, Input } from 'ant-design-vue';
const props = defineProps({
value: {
type: String,
default: '',
},
});
const emit = defineEmits(['change']); const emit = defineEmits(['change']);
const units = [ const units = [
@@ -42,7 +23,7 @@ const custom = ref({ Y: '', M: '', D: '', H: '', m: '', S: '' });
const isoString = ref(''); const isoString = ref('');
function setUnit(key, val) { function setUnit(key, val) {
if (!val || isNaN(val)) { if (!val || Number.isNaN(val)) {
custom.value[key] = ''; custom.value[key] = '';
return; return;
} }
@@ -52,13 +33,13 @@ function setUnit(key, val) {
function updateIsoString() { function updateIsoString() {
let str = 'P'; let str = 'P';
if (custom.value.Y) str += custom.value.Y + 'Y'; if (custom.value.Y) str += `${custom.value.Y}Y`;
if (custom.value.M) str += custom.value.M + 'M'; if (custom.value.M) str += `${custom.value.M}M`;
if (custom.value.D) str += custom.value.D + 'D'; 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 || custom.value.m || custom.value.S) str += 'T';
if (custom.value.H) str += custom.value.H + 'H'; if (custom.value.H) str += `${custom.value.H}H`;
if (custom.value.m) str += custom.value.m + 'M'; if (custom.value.m) str += `${custom.value.m}M`;
if (custom.value.S) str += custom.value.S + 'S'; if (custom.value.S) str += `${custom.value.S}S`;
isoString.value = str === 'P' ? '' : str; isoString.value = str === 'P' ? '' : str;
emit('change', isoString.value); emit('change', isoString.value);
} }
@@ -84,3 +65,35 @@ watch(
{ immediate: true }, { immediate: true },
); );
</script> </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> <script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'; import type { Ref } 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);
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 === 'time') return '请输入时间';
if (type.value === 'duration') return '请输入持续时长'; if (type.value === 'duration') return '请输入持续时长';
if (type.value === 'cycle') return '请输入循环表达式'; if (type.value === 'cycle') return '请输入循环表达式';
return ''; return '';
}); });
const helpText = computed(() => { const helpText = computed<string>(() => {
if (type.value === 'time') return '选择具体时间'; if (type.value === 'time') return '选择具体时间';
if (type.value === 'duration') return 'ISO 8601格式如PT1H'; if (type.value === 'duration') return 'ISO 8601格式如PT1H';
if (type.value === 'cycle') return 'CRON表达式或ISO 8601周期'; if (type.value === 'cycle') return 'CRON表达式或ISO 8601周期';
return ''; return '';
}); });
const helpHtml = computed(() => { const helpHtml = computed<string>(() => {
if (type.value === 'duration') { if (type.value === 'duration') {
return `指定定时器之前要等待多长时间。S表示秒M表示分D表示天P表示时间段T表示精确到时间的时间段。<br> return `指定定时器之前要等待多长时间。S表示秒M表示分D表示天P表示时间段T表示精确到时间的时间段。<br>
时间格式依然为ISO 8601格式一年两个月三天四小时五分六秒内可以写成P1Y2M3DT4H5M6S。<br> 时间格式依然为ISO 8601格式一年两个月三天四小时五分六秒内可以写成P1Y2M3DT4H5M6S。<br>
@@ -185,7 +58,7 @@ const helpHtml = computed(() => {
}); });
// 初始化和监听 // 初始化和监听
function syncFromBusinessObject() { function syncFromBusinessObject(): void {
if (props.businessObject) { if (props.businessObject) {
const timerDef = (props.businessObject.eventDefinitions || [])[0]; const timerDef = (props.businessObject.eventDefinitions || [])[0];
if (timerDef) { if (timerDef) {
@@ -205,7 +78,7 @@ function syncFromBusinessObject() {
onMounted(syncFromBusinessObject); onMounted(syncFromBusinessObject);
// 切换类型 // 切换类型
function setType(t) { function setType(t: string) {
type.value = t; type.value = t;
condition.value = ''; condition.value = '';
updateNode(); updateNode();
@@ -217,24 +90,24 @@ watch([type, condition], () => {
// updateNode() // 可以注释掉,避免频繁触发 // updateNode() // 可以注释掉,避免频繁触发
}); });
function validate() { function validate(): boolean {
if (type.value === 'time') { 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') { if (type.value === 'duration') {
return /^P.*$/.test(condition.value); return /^P.*$/.test(condition.value);
} }
if (type.value === 'cycle') { if (type.value === 'cycle') {
return /^([0-9*\/?, ]+|R\d*\/P.*)$/.test(condition.value); return /^(?:[0-9*/?, ]+|R\d*\/P.*)$/.test(condition.value);
} }
return true; return true;
} }
// 选择时间 // 选择时间
function onDateChange(val) { function onDateChange(val: any) {
dateValue.value = val; dateValue.value = val;
} }
function onDateConfirm() { function onDateConfirm(): void {
if (dateValue.value) { if (dateValue.value) {
condition.value = new Date(dateValue.value).toISOString(); condition.value = new Date(dateValue.value).toISOString();
showDatePicker.value = false; showDatePicker.value = false;
@@ -243,35 +116,35 @@ function onDateConfirm() {
} }
// 持续时长 // 持续时长
function onDurationChange(val) { function onDurationChange(val: string) {
condition.value = val; condition.value = val;
} }
function onDurationConfirm() { function onDurationConfirm(): void {
showDurationDialog.value = false; showDurationDialog.value = false;
updateNode(); updateNode();
} }
// 循环 // 循环
function onCycleChange(val) { function onCycleChange(val: string) {
condition.value = val; condition.value = val;
} }
function onCycleConfirm() { function onCycleConfirm(): void {
showCycleDialog.value = false; showCycleDialog.value = false;
updateNode(); updateNode();
} }
// 输入框聚焦时弹窗(可选) // 输入框聚焦时弹窗(可选)
function handleInputFocus() { function handleInputFocus(): void {
if (type.value === 'time') showDatePicker.value = true; if (type.value === 'time') showDatePicker.value = true;
if (type.value === 'duration') showDurationDialog.value = true; if (type.value === 'duration') showDurationDialog.value = true;
if (type.value === 'cycle') showCycleDialog.value = true; if (type.value === 'cycle') showCycleDialog.value = true;
} }
// 同步到节点 // 同步到节点
function updateNode() { function updateNode(): void {
const moddle = window.bpmnInstances?.moddle; const moddle = (window.bpmnInstances as any)?.moddle;
const modeling = window.bpmnInstances?.modeling; const modeling = (window.bpmnInstances as any)?.modeling;
const elementRegistry = window.bpmnInstances?.elementRegistry; const elementRegistry = (window.bpmnInstances as any)?.elementRegistry;
if (!moddle || !modeling || !elementRegistry) return; if (!moddle || !modeling || !elementRegistry) return;
// 获取元素 // 获取元素
@@ -340,6 +213,145 @@ watch(
); );
</script> </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 scoped>
/* 相关样式 */ /* 相关样式 */
</style> </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 PlusOutlined = createIconifyIcon('ant-design:plus-outlined');
export const SelectOutlined = createIconifyIcon('ant-design:select-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