diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/child-process-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/child-process-node-config.vue new file mode 100644 index 000000000..16048bd53 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/child-process-node-config.vue @@ -0,0 +1,830 @@ + + + + + + + + + {{ nodeName }} + + + + + + + + + + + + + + {{ item.name }} + + + + + + + + + + + + + {{ field.title }} + + + + + + + + + {{ field.title }} + + + + + + + + + + + + + 添加一个 + + + + + + + + + {{ field.title }} + + + + + + + + + {{ field.title }} + + + + + + + + + + + + + 添加一个 + + + + + + {{ item.label }} + + + + + + + {{ field.title }} + + + + + + + {{ item.label }} + + + + + 超时设置 + + + + + + + + {{ item.label }} + + + + + + + 当超过 + + + + + + + + + + {{ item.label }} + + + + + 后进入下一节点 + + + + + + + + + + + 后进入下一节点 + + + + + + + 多实例设置 + + + + + + + + + + + + + + {{ item.label }} + + + + + + + + + + {{ field.title }} + + + + + + + {{ field.title }} + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/condition-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/condition-node-config.vue new file mode 100644 index 000000000..405715700 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/condition-node-config.vue @@ -0,0 +1,197 @@ + + + + + + + + {{ currentNode.name }} + + + + + + + + 未满足其它条件时,将进入此分支(该分支不可编辑和删除) + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/copy-task-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/copy-task-node-config.vue new file mode 100644 index 000000000..e018f5138 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/copy-task-node-config.vue @@ -0,0 +1,506 @@ + + + + + + + + {{ nodeName }} + + + + + + + + + + + + + + {{ dict.label }} + + + + + + + + + + {{ item.name }} + + + + + + + + + + {{ item.name }} + + + + + + + {{ item.nickname }} + + + + + + + {{ item.name }} + + + + + + + {{ item.title }} + + + + + + + {{ item.title }} + + + + + + + {{ item.label }} + + + + + + + + + + + + 字段权限 + + + + 字段名称 + + + + + 只读 + + + + + 可编辑 + + + + + 隐藏 + + + + + + + + + + + {{ item.title }} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/delay-timer-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/delay-timer-node-config.vue new file mode 100644 index 000000000..ea5f59e53 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/delay-timer-node-config.vue @@ -0,0 +1,248 @@ + + + + + + + + {{ nodeName }} + + + + + + + + + + + {{ item.label }} + + + + + + + + + + + + + + {{ item.label }} + + + + + 后进入下一节点 + + + + + + + + + + 后进入下一节点 + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition-dialog.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition-dialog.vue new file mode 100644 index 000000000..8dc1b9592 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition-dialog.vue @@ -0,0 +1,79 @@ + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition.vue new file mode 100644 index 000000000..425782d1a --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition.vue @@ -0,0 +1,318 @@ + + + + + + + {{ dict.label }} + + + + + + + 条件组关系 + + + + + + {{ condition.conditionGroups.and ? '且' : '或' }} + + + + + + + + 条件组 + + 规则关系 + + + + + + + + + + + + {{ field.title }} + + {{ field.title }} + + + + + + + + {{ operator.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-param-setting.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-param-setting.vue new file mode 100644 index 000000000..9ae2e0a52 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-param-setting.vue @@ -0,0 +1,221 @@ + + + + + + + + + + + + + {{ types.label }} + + + + + + + + + + + {{ field.title }} + + + + + + + + + + + + + + + 添加一行 + + + + + + + + + + + + + {{ types.label }} + + + + + + + + + + + {{ field.title }} + + + + + + + + + + + + + + + 添加一行 + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-setting.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-setting.vue new file mode 100644 index 000000000..f9daaaf77 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-setting.vue @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + {{ field.title }} + + + + + + + + + + + + + + + + + + + + 添加一行 + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/user-task-listener.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/user-task-listener.vue new file mode 100644 index 000000000..c2959d90f --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/user-task-listener.vue @@ -0,0 +1,109 @@ + + + + + + + {{ listener.name }} + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/router-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/router-node-config.vue new file mode 100644 index 000000000..8751b8db7 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/router-node-config.vue @@ -0,0 +1,292 @@ + + + + + + + + {{ nodeName }} + + + + + + + + + + + 路由{{ index + 1 }} + + + + {{ node.label }} + + + + + + + + + + + + (routerGroups[index] = val)" + /> + + + + + + + + + + 新增路由分支 + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/start-user-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/start-user-node-config.vue new file mode 100644 index 000000000..8a51c446b --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/start-user-node-config.vue @@ -0,0 +1,278 @@ + + + + + + + + {{ nodeName }} + + + + + + + + 全部成员可以发起流程 + + + + {{ getUserNicknames(startUserIds) }} 可发起流程 + + + + {{ getUserNicknames(startUserIds.slice(0, 2)) }} 等 + {{ startUserIds.length }} 人可发起流程 + + + + + + {{ getDeptNames(startDeptIds) }} 可发起流程 + + + + {{ getDeptNames(startDeptIds.slice(0, 2)) }} 等 + {{ startDeptIds.length }} 个部门可发起流程 + + + + + + + 字段权限 + + + + 字段名称 + + + + + 只读 + + + + + 可编辑 + + + + + 隐藏 + + + + + + + + + + + {{ item.title }} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/trigger-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/trigger-node-config.vue new file mode 100644 index 000000000..a4d45c6d8 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/trigger-node-config.vue @@ -0,0 +1,688 @@ + + + + + + + + {{ nodeName }} + + + + + + + + + + {{ item.label }} + + + + + + + + + + + + + + + 修改表单设置 {{ index + 1 }} + + + + + + + + + handleConditionUpdate(index, val)" + /> + + + + + {{ showConditionText(formSetting) }} + + + + + + + 添加条件 + + + + 修改表单字段设置 + + + + + updateFormFieldKey(formSetting, key, newKey) + " + placeholder="请选择表单字段" + :disabled="key !== ''" + allow-clear + > + + {{ field.title }} + + + + + + 的值设置为 + + + + + + + + + + + + + + + + + + + + + 添加修改字段 + + + + + + + + + + + + + + 添加设置 + + + + + + + + + + + + 删除表单设置 {{ index + 1 }} + + + + + + + + + + handleConditionUpdate(index, val)" + /> + + + + + {{ showConditionText(formSetting) }} + + + + + + + 添加条件 + + + + + 删除表单字段设置 + + + + + {{ field.title }} + + + + + + + + + + + + + + 添加设置 + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/user-task-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/user-task-node-config.vue new file mode 100644 index 000000000..af9799efc --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/user-task-node-config.vue @@ -0,0 +1,1202 @@ + + + + + + + + {{ nodeName }} + + + + + + 审批类型 : + + + {{ item.label }} + + + + + + + + + + + + + + {{ dict.label }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ item.label }} + + + + + + + + + + 审批人拒绝时 + + + + + + {{ item.label }} + + + + + + + + + + + + + + + 审批人超时未处理 + + + + + + + {{ item.label }} + + + + + + + + 当超时 + + + + + + + + + + + + + 未处理 + + + + + + + + + + + {{ nodeTypeName }}人为空时 + + + + + + + {{ item.label }} + + + + + + + + + + + + + + 审批人与提交人为同一人时 + + + + + + + {{ item.label }} + + + + + + + + + 是否需要签名 + + + + + + + 审批意见 + + + + + + 跳过表达式 + + + + + + + + + + 操作按钮 + + + + 操作按钮 + 显示名称 + + 启用 + + + + + + + + {{ OPERATION_BUTTON_NAME.get(item.id) }} + + + + + + {{ item.displayName }} + + + + + + + + + + + + + + 字段权限 + + + + 字段名称 + + + + + 只读 + + + + + 可编�? + + + + + 隐藏 + + + + + + + + + + + {{ item.title }} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/utils.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/utils.ts new file mode 100644 index 000000000..ee5e06809 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/utils.ts @@ -0,0 +1,48 @@ +import { APPROVE_TYPE, ApproveType, TimeUnitType } from '../../consts'; + +/** 获取条件节点默认的名称 */ +export function getDefaultConditionNodeName( + index: number, + defaultFlow: boolean | undefined, +): string { + if (defaultFlow) { + return '其它情况'; + } + return `条件${index + 1}`; +} + +/** 获取包容分支条件节点默认的名称 */ +export function getDefaultInclusiveConditionNodeName( + index: number, + defaultFlow: boolean | undefined, +): string { + if (defaultFlow) { + return '其它情况'; + } + return `包容条件${index + 1}`; +} + +/** 转换时间单位字符串为枚举值 */ +export function convertTimeUnit(strTimeUnit: string) { + if (strTimeUnit === 'M') { + return TimeUnitType.MINUTE; + } + if (strTimeUnit === 'H') { + return TimeUnitType.HOUR; + } + if (strTimeUnit === 'D') { + return TimeUnitType.DAY; + } + return TimeUnitType.HOUR; +} + +/** 根据审批类型获取对应的文本描述 */ +export function getApproveTypeText(approveType: ApproveType): string { + let approveTypeText = ''; + APPROVE_TYPE.forEach((item) => { + if (item.value === approveType) { + approveTypeText = item.label; + } + }); + return approveTypeText; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/child-process-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/child-process-node.vue new file mode 100644 index 000000000..1ad4bbe7b --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/child-process-node.vue @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.CHILD_PROCESS_NODE) }} + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/copy-task-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/copy-task-node.vue new file mode 100644 index 000000000..617090971 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/copy-task-node.vue @@ -0,0 +1,120 @@ + + + + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.COPY_TASK_NODE) }} + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/delay-timer-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/delay-timer-node.vue new file mode 100644 index 000000000..e38925c46 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/delay-timer-node.vue @@ -0,0 +1,117 @@ + + + + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.DELAY_TIMER_NODE) }} + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/end-event-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/end-event-node.vue new file mode 100644 index 000000000..8c30a43e7 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/end-event-node.vue @@ -0,0 +1,61 @@ + + + + + 结束 + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/exclusive-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/exclusive-node.vue new file mode 100644 index 000000000..424bc0638 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/exclusive-node.vue @@ -0,0 +1,307 @@ + + + + + + + + + 添加条件 + + + + + + + + + + + + + + + + + + + + {{ item.name }} + + 优先级{{ index + 1 }} + + + + {{ item.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.CONDITION_NODE) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/inclusive-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/inclusive-node.vue new file mode 100644 index 000000000..238180ace --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/inclusive-node.vue @@ -0,0 +1,309 @@ + + + + + + + + + 添加条件 + + + + + + + + + + + + + + + + + + + {{ item.name }} + + + + + {{ item.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.CONDITION_NODE) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-data.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-data.ts new file mode 100644 index 000000000..a9f2a06bb --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-data.ts @@ -0,0 +1,56 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 流程实例列表字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'startUser', + title: '发起人', + slots: { + default: ({ row }: { row: any }) => { + return row.startUser?.nickname; + }, + }, + minWidth: 100, + }, + { + field: 'deptName', + title: '部门', + slots: { + default: ({ row }: { row: any }) => { + return row.startUser?.deptName; + }, + }, + minWidth: 100, + }, + { + field: 'createTime', + title: '开始时间', + formatter: 'formatDateTime', + minWidth: 140, + }, + { + field: 'endTime', + title: '结束时间', + formatter: 'formatDateTime', + minWidth: 140, + }, + { + field: 'status', + title: '流程状态', + minWidth: 90, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS }, + }, + }, + { + field: 'durationInMillis', + title: '耗时', + minWidth: 100, + formatter: 'formatPast2', + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-modal.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-modal.vue new file mode 100644 index 000000000..199eae489 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-modal.vue @@ -0,0 +1,45 @@ + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-data.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-data.ts new file mode 100644 index 000000000..fbb2d0ea5 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-data.ts @@ -0,0 +1,61 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 审批记录列表字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'assigneeUser', + title: '审批人', + slots: { + default: ({ row }: { row: any }) => { + return row.assigneeUser?.nickname || row.ownerUser?.nickname; + }, + }, + minWidth: 100, + }, + { + field: 'deptName', + title: '部门', + slots: { + default: ({ row }: { row: any }) => { + return row.assigneeUser?.deptName || row.ownerUser?.deptName; + }, + }, + minWidth: 100, + }, + { + field: 'createTime', + title: '开始时间', + formatter: 'formatDateTime', + minWidth: 140, + }, + { + field: 'endTime', + title: '结束时间', + formatter: 'formatDateTime', + minWidth: 140, + }, + { + field: 'status', + title: '审批状态', + minWidth: 90, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_TASK_STATUS }, + }, + }, + { + field: 'reason', + title: '审批建议', + minWidth: 160, + }, + { + field: 'durationInMillis', + title: '耗时', + minWidth: 100, + formatter: 'formatPast2', + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-modal.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-modal.vue new file mode 100644 index 000000000..e86019dc1 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-modal.vue @@ -0,0 +1,48 @@ + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/node-handler.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/node-handler.vue new file mode 100644 index 000000000..0b8eb3fcf --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/node-handler.vue @@ -0,0 +1,355 @@ + + + + + + + + + + + 审批人 + + + + + + 办理人 + + + + + + 抄送 + + + + + + 条件分支 + + + + + + 并行分支 + + + + + + 包容分支 + + + + + + 延迟器 + + + + + + 路由分支 + + + + + + 触发器 + + + + + + 子流程 + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/parallel-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/parallel-node.vue new file mode 100644 index 000000000..99a99f537 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/parallel-node.vue @@ -0,0 +1,230 @@ + + + + + + + + + 添加分支 + + + + + + + + + + + + + + + + + + + {{ item.name }} + + 无优先级 + + + + {{ item.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.CONDITION_NODE) }} + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/router-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/router-node.vue new file mode 100644 index 000000000..8314ee695 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/router-node.vue @@ -0,0 +1,119 @@ + + + + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.ROUTER_BRANCH_NODE) }} + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/start-user-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/start-user-node.vue new file mode 100644 index 000000000..ca4d4dab5 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/start-user-node.vue @@ -0,0 +1,128 @@ + + + + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.START_USER_NODE) }} + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/trigger-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/trigger-node.vue new file mode 100644 index 000000000..06d25156a --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/trigger-node.vue @@ -0,0 +1,122 @@ + + + + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.TRIGGER_NODE) }} + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/user-task-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/user-task-node.vue new file mode 100644 index 000000000..f1a027c53 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/user-task-node.vue @@ -0,0 +1,154 @@ + + + + + + + + + + + + + {{ currentNode.name }} + + + + + {{ currentNode.showText }} + + + {{ NODE_DEFAULT_TEXT.get(currentNode.type) }} + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/process-node-tree.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/process-node-tree.vue new file mode 100644 index 000000000..cc6c210bb --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/process-node-tree.vue @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-designer.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-designer.vue new file mode 100644 index 000000000..427491e3a --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-designer.vue @@ -0,0 +1,255 @@ + + + + + + 以下节点配置不完善,请修改相关配置 + + {{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }} + + + + 知道了 + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-model.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-model.vue new file mode 100644 index 000000000..8cc8d3fdf --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-model.vue @@ -0,0 +1,267 @@ + + + + + + + + 导出 + + + 导入 + + + + + + + + + + {{ scaleValue }}% + + + + 重置 + + + + + + + + + + 以下节点内容不完善,请修改后保存 + + {{ item.name }} : {{ NODE_DEFAULT_TEXT.get(item.type) }} + + + + 知道了 + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-viewer.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-viewer.vue new file mode 100644 index 000000000..febfa604f --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-viewer.vue @@ -0,0 +1,45 @@ + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/consts.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/consts.ts new file mode 100644 index 000000000..e811314e5 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/consts.ts @@ -0,0 +1,888 @@ +import { BpmNodeTypeEnum, BpmTaskStatusEnum } from '@vben/constants'; + +interface DictDataType { + label: string; + value: number | string; +} + +// 用户任务的审批类型。 【参考飞书】 +export enum ApproveType { + /** + * 自动通过 + */ + AUTO_APPROVE = 2, + /** + * 自动拒绝 + */ + AUTO_REJECT = 3, + /** + * 人工审批 + */ + USER = 1, +} + +// 多人审批方式类型枚举 ( 用于审批节点 ) +export enum ApproveMethodType { + /** + * 多人或签(通过只需一人,拒绝只需一人) + */ + ANY_APPROVE = 3, + + /** + * 多人会签(按通过比例) + */ + APPROVE_BY_RATIO = 2, + + /** + * 随机挑选一人审批 + */ + RANDOM_SELECT_ONE_APPROVE = 1, + /** + * 多人依次审批 + */ + SEQUENTIAL_APPROVE = 4, +} + +export enum NodeId { + /** + * 发起人节点 Id + */ + END_EVENT_NODE_ID = 'EndEvent', + + /** + * 发起人节点 Id + */ + START_USER_NODE_ID = 'StartUserNode', +} + +// 条件配置类型 ( 用于条件节点配置 ) +export enum ConditionType { + /** + * 条件表达式 + */ + EXPRESSION = 1, + + /** + * 条件规则 + */ + RULE = 2, +} + +// 操作按钮类型枚举 (用于审批节点) +export enum OperationButtonType { + /** + * 加签 + */ + ADD_SIGN = 5, + /** + * 通过 + */ + APPROVE = 1, + /** + * 抄送 + */ + COPY = 7, + /** + * 委派 + */ + DELEGATE = 4, + /** + * 拒绝 + */ + REJECT = 2, + /** + * 退回 + */ + RETURN = 6, + /** + * 转办 + */ + TRANSFER = 3, +} + +// 审批拒绝类型枚举 +export enum RejectHandlerType { + /** + * 结束流程 + */ + FINISH_PROCESS = 1, + /** + * 驳回到指定节点 + */ + RETURN_USER_TASK = 2, +} + +// 用户任务超时处理类型枚举 +export enum TimeoutHandlerType { + /** + * 自动同意 + */ + APPROVE = 2, + /** + * 自动拒绝 + */ + REJECT = 3, + /** + * 自动提醒 + */ + REMINDER = 1, +} + +// 用户任务的审批人为空时,处理类型枚举 +export enum AssignEmptyHandlerType { + /** + * 自动通过 + */ + APPROVE = 1, + /** + * 转交给流程管理员 + */ + ASSIGN_ADMIN = 4, + /** + * 指定人员审批 + */ + ASSIGN_USER = 3, + /** + * 自动拒绝 + */ + REJECT = 2, +} + +// 用户任务的审批人与发起人相同时,处理类型枚举 +export enum AssignStartUserHandlerType { + /** + * 转交给部门负责人审批 + */ + ASSIGN_DEPT_LEADER = 3, + /** + * 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 + */ + SKIP = 2, + /** + * 由发起人对自己审批 + */ + START_USER_AUDIT = 1, +} + +// 时间单位枚举 +export enum TimeUnitType { + /** + * 天 + */ + DAY = 3, + /** + * 小时 + */ + HOUR = 2, + /** + * 分钟 + */ + MINUTE = 1, +} + +/** + * 表单权限的枚举 + */ +export enum FieldPermissionType { + /** + * 隐藏 + */ + NONE = '3', + /** + * 只读 + */ + READ = '1', + /** + * 编辑 + */ + WRITE = '2', +} + +/** + * 延迟类型 + */ +export enum DelayTypeEnum { + /** + * 固定日期时间 + */ + FIXED_DATE_TIME = 2, + /** + * 固定时长 + */ + FIXED_TIME_DURATION = 1, +} + +/** + * 触发器类型枚举 + */ +export enum TriggerTypeEnum { + /** + * 表单数据删除触发器 + */ + FORM_DELETE = 11, + /** + * 表单数据更新触发器 + */ + FORM_UPDATE = 10, + /** + * 接收 HTTP 回调请求触发器 + */ + HTTP_CALLBACK = 2, + /** + * 发送 HTTP 请求触发器 + */ + HTTP_REQUEST = 1, +} + +export enum ChildProcessStartUserTypeEnum { + /** + * 表单 + */ + FROM_FORM = 2, + /** + * 同主流程发起人 + */ + MAIN_PROCESS_START_USER = 1, +} + +export enum ChildProcessStartUserEmptyTypeEnum { + /** + * 子流程管理员 + */ + CHILD_PROCESS_ADMIN = 2, + /** + * 主流程管理员 + */ + MAIN_PROCESS_ADMIN = 3, + /** + * 同主流程发起人 + */ + MAIN_PROCESS_START_USER = 1, +} + +export enum ChildProcessMultiInstanceSourceTypeEnum { + /** + * 固定数量 + */ + FIXED_QUANTITY = 1, + /** + * 多选表单 + */ + MULTIPLE_FORM = 3, + /** + * 数字表单 + */ + NUMBER_FORM = 2, +} + +// 候选人策略枚举 ( 用于审批节点。抄送节点 ) +export enum CandidateStrategy { + /** + * 审批人自选 + */ + APPROVE_USER_SELECT = 34, + /** + * 部门的负责人 + */ + DEPT_LEADER = 21, + /** + * 部门成员 + */ + DEPT_MEMBER = 20, + /** + * 流程表达式 + */ + EXPRESSION = 60, + /** + * 表单内部门负责人 + */ + FORM_DEPT_LEADER = 51, + /** + * 表单内用户字段 + */ + FORM_USER = 50, + /** + * 连续多级部门的负责人 + */ + MULTI_LEVEL_DEPT_LEADER = 23, + /** + * 指定岗位 + */ + POST = 22, + /** + * 指定角色 + */ + ROLE = 10, + /** + * 发起人自己 + */ + START_USER = 36, + /** + * 发起人部门负责人 + */ + START_USER_DEPT_LEADER = 37, + /** + * 发起人连续多级部门的负责人 + */ + START_USER_MULTI_LEVEL_DEPT_LEADER = 38, + /** + * 发起人自选 + */ + START_USER_SELECT = 35, + /** + * 指定用户 + */ + USER = 30, + /** + * 指定用户组 + */ + USER_GROUP = 40, +} + +export enum BpmHttpRequestParamTypeEnum { + /** + * 固定值 + */ + FIXED_VALUE = 1, + /** + * 表单 + */ + FROM_FORM = 2, +} + +// 这里定义 HTTP 请求参数类型 +export type HttpRequestParam = { + key: string; + type: number; + value: string; +}; + +// 监听器结构定义 +export type ListenerHandler = { + body?: HttpRequestParam[]; + enable: boolean; + header?: HttpRequestParam[]; + path?: string; +}; + +/** + * 条件规则结构定义 + */ +export type ConditionRule = { + leftSide: string | undefined; + opCode: string; + rightSide: string | undefined; +}; + +/** + * 条件结构定义 + */ +export type Condition = { + // 条件规则的逻辑关系是否为且 + and: boolean; + rules: ConditionRule[]; +}; + +/** + * 条件组结构定义 + */ +export type ConditionGroup = { + // 条件组的逻辑关系是否为且 + and: boolean; + // 条件数组 + conditions: Condition[]; +}; + +/** + * 条件节点设置结构定义,用于条件节点 + */ +export type ConditionSetting = { + // 条件表达式 + conditionExpression?: string; + // 条件组 + conditionGroups?: ConditionGroup; + // 条件类型 + conditionType?: ConditionType; + // 是否默认的条件 + defaultFlow?: boolean; +}; + +/** + * 审批拒绝结构定义 + */ +export type RejectHandler = { + // 退回节点 Id + returnNodeId?: string; + // 审批拒绝类型 + type: RejectHandlerType; +}; + +/** + * 审批超时结构定义 + */ +export type TimeoutHandler = { + // 是否开启超时处理 + enable: boolean; + // 执行动作是自动提醒, 最大提醒次数 + maxRemindCount?: number; + // 超时时间设置 + timeDuration?: string; + // 超时执行的动作 + type?: number; +}; + +/** + * 审批人为空的结构定义 + */ +export type AssignEmptyHandler = { + // 审批人为空的处理类型 + type: AssignEmptyHandlerType; + // 指定用户的编号数组 + userIds?: number[]; +}; + +/** + * 延迟设置 + */ +export type DelaySetting = { + // 延迟时间表达式 + delayTime: string; + // 延迟类型 + delayType: number; +}; + +/** + * 路由分支结构定义 + */ +export type RouterSetting = { + conditionExpression: string; + conditionGroups: ConditionGroup; + conditionType: ConditionType; + nodeId: string | undefined; +}; + +/** + * 操作按钮权限结构定义 + */ +export type ButtonSetting = { + displayName: string; + enable: boolean; + id: OperationButtonType; +}; + +/** + * HTTP 请求触发器结构定义 + */ +export type HttpRequestTriggerSetting = { + // 请求体参数设置 + body?: HttpRequestParam[]; + // 请求头参数设置 + header?: HttpRequestParam[]; + // 请求响应设置 + response?: Record[]; + // 请求 URL + url: string; +}; + +/** + * 流程表单触发器配置结构定义 + */ +export type FormTriggerSetting = { + // 条件表达式 + conditionExpression?: string; + // 条件组 + conditionGroups?: ConditionGroup; + // 条件类型 + conditionType?: ConditionType; + // 删除表单字段配置 + deleteFields?: string[]; + // 更新表单字段配置 + updateFormFields?: Record; +}; + +/** + * 触发器节点结构定义 + */ +export type TriggerSetting = { + formSettings?: FormTriggerSetting[]; + httpRequestSetting?: HttpRequestTriggerSetting; + type: TriggerTypeEnum; +}; + +export type IOParameter = { + source: string; + target: string; +}; + +export type StartUserSetting = { + emptyType?: ChildProcessStartUserEmptyTypeEnum; + formField?: string; + type: ChildProcessStartUserTypeEnum; +}; + +export type TimeoutSetting = { + enable: boolean; + timeExpression?: string; + type?: DelayTypeEnum; +}; + +export type MultiInstanceSetting = { + approveRatio?: number; + enable: boolean; + sequential?: boolean; + source?: string; + sourceType?: ChildProcessMultiInstanceSourceTypeEnum; +}; + +/** + * 子流程节点结构定义 + */ +export type ChildProcessSetting = { + async: boolean; + calledProcessDefinitionKey: string; + calledProcessDefinitionName: string; + inVariables?: IOParameter[]; + multiInstanceSetting: MultiInstanceSetting; + outVariables?: IOParameter[]; + skipStartUserNode: boolean; + startUserSetting: StartUserSetting; + timeoutSetting: TimeoutSetting; +}; + +/** + * 节点结构定义 + */ +export interface SimpleFlowNode { + id: string; + type: BpmNodeTypeEnum; + name: string; + showText?: string; + // 孩子节点 + childNode?: SimpleFlowNode; + // 条件节点 + conditionNodes?: SimpleFlowNode[]; + // 审批类型 + approveType?: ApproveType; + // 候选人策略 + candidateStrategy?: number; + // 候选人参数 + candidateParam?: string; + // 多人审批方式 + approveMethod?: ApproveMethodType; + // 通过比例 + approveRatio?: number; + // 审批按钮设置 + buttonsSetting?: any[]; + // 表单权限 + fieldsPermission?: Array>; + // 审批任务超时处理 + timeoutHandler?: TimeoutHandler; + // 审批任务拒绝处理 + rejectHandler?: RejectHandler; + // 审批人为空的处理 + assignEmptyHandler?: AssignEmptyHandler; + // 审批节点的审批人与发起人相同时,对应的处理类型 + assignStartUserHandlerType?: number; + // 创建任务监听器 + taskCreateListener?: ListenerHandler; + // 创建任务监听器 + taskAssignListener?: ListenerHandler; + // 创建任务监听器 + taskCompleteListener?: ListenerHandler; + // 条件设置 + conditionSetting?: ConditionSetting; + // 活动的状态,用于前端节点状态展示 + activityStatus?: BpmTaskStatusEnum; + // 延迟设置 + delaySetting?: DelaySetting; + // 路由分支 + routerGroups?: RouterSetting[]; + defaultFlowId?: string; + // 签名 + signEnable?: boolean; + // 审批意见 + reasonRequire?: boolean; + // 跳过表达式 + skipExpression?: string; + // 触发器设置 + triggerSetting?: TriggerSetting; + // 子流程 + childProcessSetting?: ChildProcessSetting; +} + +/** + * 条件组默认值 + */ +export const DEFAULT_CONDITION_GROUP_VALUE = { + and: true, + conditions: [ + { + and: true, + rules: [ + { + opCode: '==', + leftSide: undefined, + rightSide: '', + }, + ], + }, + ], +}; + +export const NODE_DEFAULT_TEXT = new Map(); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.USER_TASK_NODE, '请配置审批人'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.COPY_TASK_NODE, '请配置抄送人'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.CONDITION_NODE, '请设置条件'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.START_USER_NODE, '请设置发起人'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.DELAY_TIMER_NODE, '请设置延迟器'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.ROUTER_BRANCH_NODE, '请设置路由节点'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.TRIGGER_NODE, '请设置触发器'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.TRANSACTOR_NODE, '请设置办理人'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.CHILD_PROCESS_NODE, '请设置子流程'); + +export const NODE_DEFAULT_NAME = new Map(); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.USER_TASK_NODE, '审批人'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.COPY_TASK_NODE, '抄送人'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.CONDITION_NODE, '条件'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.START_USER_NODE, '发起人'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.DELAY_TIMER_NODE, '延迟器'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.ROUTER_BRANCH_NODE, '路由分支'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.TRIGGER_NODE, '触发器'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.TRANSACTOR_NODE, '办理人'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.CHILD_PROCESS_NODE, '子流程'); + +// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序 +export const CANDIDATE_STRATEGY: DictDataType[] = [ + { label: '指定成员', value: CandidateStrategy.USER as any }, + { label: '指定角色', value: CandidateStrategy.ROLE as any }, + { label: '指定岗位', value: CandidateStrategy.POST as any }, + { label: '部门成员', value: CandidateStrategy.DEPT_MEMBER as any }, + { label: '部门负责人', value: CandidateStrategy.DEPT_LEADER as any }, + { + label: '连续多级部门负责人', + value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER as any, + }, + { label: '发起人自选', value: CandidateStrategy.START_USER_SELECT as any }, + { label: '审批人自选', value: CandidateStrategy.APPROVE_USER_SELECT as any }, + { label: '发起人本人', value: CandidateStrategy.START_USER as any }, + { + label: '发起人部门负责人', + value: CandidateStrategy.START_USER_DEPT_LEADER as any, + }, + { + label: '发起人连续部门负责人', + value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER as any, + }, + { label: '用户组', value: CandidateStrategy.USER_GROUP as any }, + { label: '表单内用户字段', value: CandidateStrategy.FORM_USER as any }, + { + label: '表单内部门负责人', + value: CandidateStrategy.FORM_DEPT_LEADER as any, + }, + { label: '流程表达式', value: CandidateStrategy.EXPRESSION as any }, +]; +// 审批节点 的审批类型 +export const APPROVE_TYPE: DictDataType[] = [ + { label: '人工审批', value: ApproveType.USER as any }, + { label: '自动通过', value: ApproveType.AUTO_APPROVE as any }, + { label: '自动拒绝', value: ApproveType.AUTO_REJECT as any }, +]; + +export const APPROVE_METHODS: DictDataType[] = [ + { + label: '按顺序依次审批', + value: ApproveMethodType.SEQUENTIAL_APPROVE as any, + }, + { + label: '会签(可同时审批,至少 % 人必须审批通过)', + value: ApproveMethodType.APPROVE_BY_RATIO as any, + }, + { + label: '或签(可同时审批,有一人通过即可)', + value: ApproveMethodType.ANY_APPROVE as any, + }, + { + label: '随机挑选一人审批', + value: ApproveMethodType.RANDOM_SELECT_ONE_APPROVE as any, + }, +]; + +export const CONDITION_CONFIG_TYPES: DictDataType[] = [ + { label: '条件规则', value: ConditionType.RULE as any }, + { label: '条件表达式', value: ConditionType.EXPRESSION as any }, +]; + +// 时间单位类型 +export const TIME_UNIT_TYPES: DictDataType[] = [ + { label: '分钟', value: TimeUnitType.MINUTE as any }, + { label: '小时', value: TimeUnitType.HOUR as any }, + { label: '天', value: TimeUnitType.DAY as any }, +]; +// 超时处理执行动作类型 +export const TIMEOUT_HANDLER_TYPES: DictDataType[] = [ + { label: '自动提醒', value: 1 }, + { label: '自动同意', value: 2 }, + { label: '自动拒绝', value: 3 }, +]; +export const REJECT_HANDLER_TYPES: DictDataType[] = [ + { label: '终止流程', value: RejectHandlerType.FINISH_PROCESS as any }, + { label: '驳回到指定节点', value: RejectHandlerType.RETURN_USER_TASK as any }, + // { label: '结束任务', value: RejectHandlerType.FINISH_TASK } +]; +export const ASSIGN_EMPTY_HANDLER_TYPES: DictDataType[] = [ + { label: '自动通过', value: 1 }, + { label: '自动拒绝', value: 2 }, + { label: '指定成员审批', value: 3 }, + { label: '转交给流程管理员', value: 4 }, +]; +export const ASSIGN_START_USER_HANDLER_TYPES: DictDataType[] = [ + { label: '由发起人对自己审批', value: 1 }, + { label: '自动跳过', value: 2 }, + { label: '转交给部门负责人审批', value: 3 }, +]; + +// 比较运算符 +export const COMPARISON_OPERATORS: DictDataType[] = [ + { + value: '==', + label: '等于', + }, + { + value: '!=', + label: '不等于', + }, + { + value: '>', + label: '大于', + }, + { + value: '>=', + label: '大于等于', + }, + { + value: '<', + label: '小于', + }, + { + value: '<=', + label: '小于等于', + }, +]; +// 审批操作按钮名称 +export const OPERATION_BUTTON_NAME = new Map(); +OPERATION_BUTTON_NAME.set(OperationButtonType.APPROVE, '通过'); +OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝'); +OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办'); +OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派'); +OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签'); +OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '退回'); +OPERATION_BUTTON_NAME.set(OperationButtonType.COPY, '抄送'); + +// 默认的按钮权限设置 +export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '通过', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: true }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: true }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: true }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: true }, + { id: OperationButtonType.RETURN, displayName: '退回', enable: true }, +]; + +// 办理人默认的按钮权限设置 +export const TRANSACTOR_DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '办理', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: false }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false }, + { id: OperationButtonType.RETURN, displayName: '退回', enable: false }, +]; + +// 发起人的按钮权限。暂时定死,不可以编辑 +export const START_USER_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '提交', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: false }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false }, + { id: OperationButtonType.RETURN, displayName: '退回', enable: false }, +]; + +export const MULTI_LEVEL_DEPT: DictDataType[] = [ + { label: '第 1 级部门', value: 1 }, + { label: '第 2 级部门', value: 2 }, + { label: '第 3 级部门', value: 3 }, + { label: '第 4 级部门', value: 4 }, + { label: '第 5 级部门', value: 5 }, + { label: '第 6 级部门', value: 6 }, + { label: '第 7 级部门', value: 7 }, + { label: '第 8 级部门', value: 8 }, + { label: '第 9 级部门', value: 9 }, + { label: '第 10 级部门', value: 10 }, + { label: '第 11 级部门', value: 11 }, + { label: '第 12 级部门', value: 12 }, + { label: '第 13 级部门', value: 13 }, + { label: '第 14 级部门', value: 14 }, + { label: '第 15 级部门', value: 15 }, +]; + +export const DELAY_TYPE = [ + { label: '固定时长', value: DelayTypeEnum.FIXED_TIME_DURATION }, + { label: '固定日期', value: DelayTypeEnum.FIXED_DATE_TIME }, +]; + +export const BPM_HTTP_REQUEST_PARAM_TYPES = [ + { + value: 1, + label: '固定值', + }, + { + value: 2, + label: '表单', + }, +]; + +export const TRIGGER_TYPES: DictDataType[] = [ + { label: '发送 HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST as any }, + { label: '接收 HTTP 回调', value: TriggerTypeEnum.HTTP_CALLBACK as any }, + { label: '修改表单数据', value: TriggerTypeEnum.FORM_UPDATE as any }, + { label: '删除表单数据', value: TriggerTypeEnum.FORM_DELETE as any }, +]; + +export const CHILD_PROCESS_START_USER_TYPE = [ + { + label: '同主流程发起人', + value: ChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER, + }, + { label: '从表单中获取', value: ChildProcessStartUserTypeEnum.FROM_FORM }, +]; + +export const CHILD_PROCESS_START_USER_EMPTY_TYPE = [ + { + label: '同主流程发起人', + value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER, + }, + { + label: '子流程管理员', + value: ChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN, + }, + { + label: '主流程管理员', + value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN, + }, +]; + +export const CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = [ + { + label: '固定数量', + value: ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY, + }, + { + label: '数字表单', + value: ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM, + }, + { + label: '多选表单', + value: ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM, + }, +]; diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/helpers.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/helpers.ts new file mode 100644 index 000000000..3ffc2a303 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/helpers.ts @@ -0,0 +1,792 @@ +import type { Ref } from 'vue'; + +import type { + ConditionGroup, + HttpRequestParam, + SimpleFlowNode, +} from './consts'; + +import type { BpmUserGroupApi } from '#/api/bpm/userGroup'; +import type { SystemDeptApi } from '#/api/system/dept'; +import type { SystemPostApi } from '#/api/system/post'; +import type { SystemRoleApi } from '#/api/system/role'; +import type { SystemUserApi } from '#/api/system/user'; + +import { inject, nextTick, ref, toRaw, unref, watch } from 'vue'; + +import { + BpmNodeTypeEnum, + BpmTaskStatusEnum, + ProcessVariableEnum, +} from '@vben/constants'; + +import { + ApproveMethodType, + AssignEmptyHandlerType, + AssignStartUserHandlerType, + CandidateStrategy, + COMPARISON_OPERATORS, + ConditionType, + FieldPermissionType, + NODE_DEFAULT_NAME, + RejectHandlerType, +} from './consts'; + +export function useWatchNode(props: { + flowNode: SimpleFlowNode; +}): Ref { + const node = ref(props.flowNode); + watch( + () => props.flowNode, + (newValue) => { + node.value = newValue; + }, + ); + return node; +} + +// 解析 formCreate 所有表单字段, 并返回 +function parseFormCreateFields(formFields?: string[]) { + const result: Array> = []; + if (formFields) { + formFields.forEach((fieldStr: string) => { + parseFormFields(JSON.parse(fieldStr), result); + }); + } + return result; +} + +/** + * 解析表单组件的 field, title 等字段(递归,如果组件包含子组件) + * + * @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule + * @param fields 解析后表单组件字段 + * @param parentTitle 如果是子表单,子表单的标题,默认为空 + */ +export const parseFormFields = ( + rule: Record, + fields: Array> = [], + parentTitle: string = '', +) => { + const { type, field, $required, title: tempTitle, children } = rule; + if (field && tempTitle) { + let title = tempTitle; + if (parentTitle) { + title = `${parentTitle}.${tempTitle}`; + } + let required = false; + if ($required) { + required = true; + } + fields.push({ + field, + title, + type, + required, + }); + // TODO 子表单 需要处理子表单字段 + // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { + // // 解析子表单的字段 + // rule.props.rule.forEach((item) => { + // parseFields(item, fieldsPermission, title) + // }) + // } + } + if (children && Array.isArray(children)) { + children.forEach((rule) => { + parseFormFields(rule, fields); + }); + } +}; + +/** + * @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点 + */ +export function useFormFieldsPermission( + defaultPermission: FieldPermissionType, +) { + // 字段权限配置. 需要有 field, title, permissioin 属性 + const fieldsPermissionConfig = ref>>([]); + + const formType = inject>('formType', ref()); // 表单类型 + + const formFields = inject>('formFields', ref([])); // 流程表单字段 + + function getNodeConfigFormFields( + nodeFormFields?: Array>, + ) { + nodeFormFields = toRaw(nodeFormFields); + fieldsPermissionConfig.value = + !nodeFormFields || nodeFormFields.length === 0 + ? getDefaultFieldsPermission(unref(formFields)) + : mergeFieldsPermission(nodeFormFields, unref(formFields)); + } + // 合并已经设置的表单字段权限,当前流程表单字段 (可能新增,或删除了字段) + function mergeFieldsPermission( + formFieldsPermisson: Array>, + formFields?: string[], + ) { + let mergedFieldsPermission: Array> = []; + if (formFields) { + mergedFieldsPermission = parseFormCreateFields(formFields).map((item) => { + const found = formFieldsPermisson.find( + (fieldPermission) => fieldPermission.field === item.field, + ); + return { + field: item.field, + title: item.title, + permission: found ? found.permission : defaultPermission, + }; + }); + } + return mergedFieldsPermission; + } + + // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读 + function getDefaultFieldsPermission(formFields?: string[]) { + let defaultFieldsPermission: Array> = []; + if (formFields) { + defaultFieldsPermission = parseFormCreateFields(formFields).map( + (item) => { + return { + field: item.field, + title: item.title, + permission: defaultPermission, + }; + }, + ); + } + return defaultFieldsPermission; + } + + // 获取表单的所有字段,作为下拉框选项 + const formFieldOptions = parseFormCreateFields(unref(formFields)); + + return { + formType, + fieldsPermissionConfig, + formFieldOptions, + getNodeConfigFormFields, + }; +} + +/** + * @description 获取流程表单的字段 + */ +export function useFormFields() { + const formFields = inject>('formFields', ref([])); // 流程表单字段 + return parseFormCreateFields(unref(formFields)); +} + +// TODO @芋艿:后续需要把各种类似 useFormFieldsPermission 的逻辑,抽成一个通用方法。 +/** + * @description 获取流程表单的字段和发起人字段 + */ +export function useFormFieldsAndStartUser() { + const injectFormFields = inject>('formFields', ref([])); // 流程表单字段 + const formFields = parseFormCreateFields(unref(injectFormFields)); + // 添加发起人 + formFields.unshift({ + field: ProcessVariableEnum.START_USER_ID, + title: '发起人', + required: true, + }); + return formFields; +} + +export type UserTaskFormType = { + approveMethod: ApproveMethodType; + approveRatio?: number; + assignEmptyHandlerType?: AssignEmptyHandlerType; + assignEmptyHandlerUserIds?: number[]; + assignStartUserHandlerType?: AssignStartUserHandlerType; + buttonsSetting: any[]; + candidateStrategy: CandidateStrategy; + deptIds?: number[]; // 部门 + deptLevel?: number; // 部门层级 + expression?: string; // 流程表达式 + formDept?: string; // 表单内部门字段 + formUser?: string; // 表单内用户字段 + maxRemindCount?: number; + postIds?: number[]; // 岗位 + reasonRequire: boolean; + rejectHandlerType?: RejectHandlerType; + returnNodeId?: string; + roleIds?: number[]; // 角色 + signEnable: boolean; + skipExpression?: string; // 跳过表达式 + taskAssignListener?: { + body: HttpRequestParam[]; + header: HttpRequestParam[]; + }; + taskAssignListenerEnable?: boolean; + taskAssignListenerPath?: string; + taskCompleteListener?: { + body: HttpRequestParam[]; + header: HttpRequestParam[]; + }; + taskCompleteListenerEnable?: boolean; + taskCompleteListenerPath?: string; + taskCreateListener?: { + body: HttpRequestParam[]; + header: HttpRequestParam[]; + }; + taskCreateListenerEnable?: boolean; + taskCreateListenerPath?: string; + timeDuration?: number; + timeoutHandlerEnable?: boolean; + timeoutHandlerType?: number; + userGroups?: number[]; // 用户组 + userIds?: number[]; // 用户 +}; + +export type CopyTaskFormType = { + candidateStrategy: CandidateStrategy; + deptIds?: number[]; // 部门 + deptLevel?: number; // 部门层级 + expression?: string; // 流程表达式 + formDept?: string; // 表单内部门字段 + formUser?: string; // 表单内用户字段 + postIds?: number[]; // 岗位 + roleIds?: number[]; // 角色 + userGroups?: number[]; // 用户组 + userIds?: number[]; // 用户 +}; + +/** + * @description 节点表单数据。 用于审批节点、抄送节点 + */ +export function useNodeForm(nodeType: BpmNodeTypeEnum) { + const roleOptions = inject>('roleList', ref([])); // 角色列表 + const postOptions = inject>('postList', ref([])); // 岗位列表 + const userOptions = inject>('userList', ref([])); // 用户列表 + const deptOptions = inject>('deptList', ref([])); // 部门列表 + const userGroupOptions = inject>( + 'userGroupList', + ref([]), + ); // 用户组列表 + const deptTreeOptions = inject>( + 'deptTree', + ref([]), + ); // 部门树 + const formFields = inject>('formFields', ref([])); // 流程表单字段 + const configForm = ref(); + + if ( + nodeType === BpmNodeTypeEnum.USER_TASK_NODE || + nodeType === BpmNodeTypeEnum.TRANSACTOR_NODE + ) { + configForm.value = { + candidateStrategy: CandidateStrategy.USER, + approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, + approveRatio: 100, + rejectHandlerType: RejectHandlerType.FINISH_PROCESS, + assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT, + returnNodeId: '', + timeoutHandlerEnable: false, + timeoutHandlerType: 1, + timeDuration: 6, // 默认 6小时 + maxRemindCount: 1, // 默认 提醒 1次 + buttonsSetting: [], + }; + } + configForm.value = { + candidateStrategy: CandidateStrategy.USER, + }; + + function getShowText(): string { + let showText = ''; + // 指定成员 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.USER && + configForm.value?.userIds?.length > 0 + ) { + const candidateNames: string[] = []; + userOptions?.value.forEach((item: any) => { + if (configForm.value?.userIds?.includes(item.id)) { + candidateNames.push(item.nickname); + } + }); + showText = `指定成员:${candidateNames.join(',')}`; + } + // 指定角色 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.ROLE && + configForm.value.roleIds?.length > 0 + ) { + const candidateNames: string[] = []; + roleOptions?.value.forEach((item: any) => { + if (configForm.value?.roleIds?.includes(item.id)) { + candidateNames.push(item.name); + } + }); + showText = `指定角色:${candidateNames.join(',')}`; + } + // 指定部门 + if ( + (configForm.value?.candidateStrategy === CandidateStrategy.DEPT_MEMBER || + configForm.value?.candidateStrategy === CandidateStrategy.DEPT_LEADER || + configForm.value?.candidateStrategy === + CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) && + configForm.value?.deptIds?.length > 0 + ) { + const candidateNames: string[] = []; + deptOptions?.value.forEach((item) => { + if (configForm.value?.deptIds?.includes(item.id)) { + candidateNames.push(item.name); + } + }); + if ( + configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER + ) { + showText = `部门成员:${candidateNames.join(',')}`; + } else if ( + configForm.value.candidateStrategy === CandidateStrategy.DEPT_LEADER + ) { + showText = `部门的负责人:${candidateNames.join(',')}`; + } else { + showText = `多级部门的负责人:${candidateNames.join(',')}`; + } + } + + // 指定岗位 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.POST && + configForm.value.postIds?.length > 0 + ) { + const candidateNames: string[] = []; + postOptions?.value.forEach((item) => { + if (configForm.value?.postIds?.includes(item.id)) { + candidateNames.push(item.name); + } + }); + showText = `指定岗位: ${candidateNames.join(',')}`; + } + // 指定用户组 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.USER_GROUP && + configForm.value?.userGroups?.length > 0 + ) { + const candidateNames: string[] = []; + userGroupOptions?.value.forEach((item) => { + if (configForm.value?.userGroups?.includes(item.id)) { + candidateNames.push(item.name); + } + }); + showText = `指定用户组: ${candidateNames.join(',')}`; + } + + // 表单内用户字段 + if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_USER) { + const formFieldOptions = parseFormCreateFields(unref(formFields)); + const item = formFieldOptions.find( + (item) => item.field === configForm.value?.formUser, + ); + showText = `表单用户:${item?.title}`; + } + + // 表单内部门负责人 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER + ) { + showText = `表单内部门负责人`; + } + + // 审批人自选 + if ( + configForm.value?.candidateStrategy === + CandidateStrategy.APPROVE_USER_SELECT + ) { + showText = `审批人自选`; + } + + // 发起人自选 + if ( + configForm.value?.candidateStrategy === + CandidateStrategy.START_USER_SELECT + ) { + showText = `发起人自选`; + } + // 发起人自己 + if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER) { + showText = `发起人自己`; + } + // 发起人的部门负责人 + if ( + configForm.value?.candidateStrategy === + CandidateStrategy.START_USER_DEPT_LEADER + ) { + showText = `发起人的部门负责人`; + } + // 发起人的部门负责人 + if ( + configForm.value?.candidateStrategy === + CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER + ) { + showText = `发起人连续部门负责人`; + } + // 流程表达式 + if (configForm.value?.candidateStrategy === CandidateStrategy.EXPRESSION) { + showText = `流程表达式:${configForm.value.expression}`; + } + return showText; + } + + /** + * 处理候选人参数的赋值 + */ + function handleCandidateParam() { + let candidateParam: string | undefined; + if (!configForm.value) { + return candidateParam; + } + switch (configForm.value.candidateStrategy) { + case CandidateStrategy.DEPT_LEADER: + case CandidateStrategy.DEPT_MEMBER: { + candidateParam = configForm.value.deptIds?.join(','); + break; + } + case CandidateStrategy.EXPRESSION: { + candidateParam = configForm.value.expression; + break; + } + // 表单内部门的负责人 + case CandidateStrategy.FORM_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为表单内部门字段。 右边为部门层级 + const deptFieldOnForm = configForm.value.formDept; + candidateParam = deptFieldOnForm?.concat( + `|${configForm.value.deptLevel}`, + ); + break; + } + case CandidateStrategy.FORM_USER: { + candidateParam = configForm.value?.formUser; + break; + } + // 指定连续多级部门的负责人 + case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 + const deptIds = configForm.value.deptIds?.join(','); + candidateParam = deptIds?.concat(`|${configForm.value.deptLevel}`); + break; + } + case CandidateStrategy.POST: { + candidateParam = configForm.value.postIds?.join(','); + break; + } + case CandidateStrategy.ROLE: { + candidateParam = configForm.value.roleIds?.join(','); + break; + } + // 发起人部门负责人 + case CandidateStrategy.START_USER_DEPT_LEADER: + case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER: { + candidateParam = `${configForm.value.deptLevel}`; + break; + } + case CandidateStrategy.USER: { + candidateParam = configForm.value.userIds?.join(','); + break; + } + case CandidateStrategy.USER_GROUP: { + candidateParam = configForm.value.userGroups?.join(','); + break; + } + default: { + break; + } + } + return candidateParam; + } + /** + * 解析候选人参数 + */ + function parseCandidateParam( + candidateStrategy: CandidateStrategy, + candidateParam: string | undefined, + ) { + if (!configForm.value || !candidateParam) { + return; + } + switch (candidateStrategy) { + case CandidateStrategy.DEPT_LEADER: + case CandidateStrategy.DEPT_MEMBER: { + configForm.value.deptIds = candidateParam + .split(',') + .map((item) => +item); + break; + } + case CandidateStrategy.EXPRESSION: { + configForm.value.expression = candidateParam; + break; + } + // 表单内的部门负责人 + case CandidateStrategy.FORM_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为表单内的部门字段。 右边为部门层级 + const paramArray = candidateParam.split('|'); + if (paramArray.length > 1) { + configForm.value.formDept = paramArray[0]; + if (paramArray[1]) configForm.value.deptLevel = +paramArray[1]; + } + break; + } + case CandidateStrategy.FORM_USER: { + configForm.value.formUser = candidateParam; + break; + } + // 指定连续多级部门的负责人 + case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 + const paramArray = candidateParam.split('|') as string[]; + if (paramArray.length > 1) { + configForm.value.deptIds = paramArray[0] + ?.split(',') + .map((item) => +item); + if (paramArray[1]) configForm.value.deptLevel = +paramArray[1]; + } + break; + } + case CandidateStrategy.POST: { + configForm.value.postIds = candidateParam + .split(',') + .map((item) => +item); + break; + } + case CandidateStrategy.ROLE: { + configForm.value.roleIds = candidateParam + .split(',') + .map((item) => +item); + break; + } + // 发起人部门负责人 + case CandidateStrategy.START_USER_DEPT_LEADER: + case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER: { + configForm.value.deptLevel = +candidateParam; + break; + } + case CandidateStrategy.USER: { + configForm.value.userIds = candidateParam + .split(',') + .map((item) => +item); + break; + } + case CandidateStrategy.USER_GROUP: { + configForm.value.userGroups = candidateParam + .split(',') + .map((item) => +item); + break; + } + default: { + break; + } + } + } + return { + configForm, + roleOptions, + postOptions, + userOptions, + userGroupOptions, + deptTreeOptions, + handleCandidateParam, + parseCandidateParam, + getShowText, + }; +} + +/** + * @description 抽屉配置 + */ +export function useDrawer() { + // 抽屉配置是否可见 + const settingVisible = ref(false); + // 关闭配置抽屉 + function closeDrawer() { + settingVisible.value = false; + } + // 打开配置抽屉 + function openDrawer() { + settingVisible.value = true; + } + return { + settingVisible, + closeDrawer, + openDrawer, + }; +} + +/** + * @description 节点名称配置 + */ +export function useNodeName(nodeType: BpmNodeTypeEnum) { + // 节点名称 + const nodeName = ref(); + // 节点名称输入框 + const showInput = ref(false); + // 输入框的引用 + const inputRef = ref(null); + // 点击节点名称编辑图标 + function clickIcon() { + showInput.value = true; + } + // 修改节点名称 + function changeNodeName() { + showInput.value = false; + nodeName.value = + nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string); + } + // 监听 showInput 的变化,当变为 true 时自动聚焦 + watch(showInput, (value) => { + if (value) { + nextTick(() => { + inputRef.value?.focus(); + }); + } + }); + + return { + nodeName, + showInput, + inputRef, + clickIcon, + changeNodeName, + }; +} + +export function useNodeName2( + node: Ref, + nodeType: BpmNodeTypeEnum, +) { + // 显示节点名称输入框 + const showInput = ref(false); + // 输入框的引用 + const inputRef = ref(null); + + // 监听 showInput 的变化,当变为 true 时自动聚焦 + watch(showInput, (value) => { + if (value) { + nextTick(() => { + inputRef.value?.focus(); + }); + } + }); + + // 修改节点名称 + function changeNodeName() { + showInput.value = false; + node.value.name = + node.value.name || (NODE_DEFAULT_NAME.get(nodeType) as string); + console.warn('node.value.name===>', node.value.name); + } + // 点击节点标题进行输入 + function clickTitle() { + showInput.value = true; + } + return { + showInput, + inputRef, + clickTitle, + changeNodeName, + }; +} + +/** + * @description 根据节点任务状态,获取节点任务状态样式 + */ +export function useTaskStatusClass( + taskStatus: BpmTaskStatusEnum | undefined, +): string { + if (!taskStatus) { + return ''; + } + if (taskStatus === BpmTaskStatusEnum.APPROVE) { + return 'status-pass'; + } + if (taskStatus === BpmTaskStatusEnum.RUNNING) { + return 'status-running'; + } + if (taskStatus === BpmTaskStatusEnum.REJECT) { + return 'status-reject'; + } + if (taskStatus === BpmTaskStatusEnum.CANCEL) { + return 'status-cancel'; + } + return ''; +} + +/** 条件组件文字展示 */ +export function getConditionShowText( + conditionType: ConditionType | undefined, + conditionExpression: string | undefined, + conditionGroups: ConditionGroup | undefined, + fieldOptions: Array>, +) { + let showText: string | undefined; + if (conditionType === ConditionType.EXPRESSION && conditionExpression) { + showText = `表达式:${conditionExpression}`; + } + if (conditionType === ConditionType.RULE) { + // 条件组是否为与关系 + const groupAnd = conditionGroups?.and; + let warningMessage: string | undefined; + const conditionGroup = conditionGroups?.conditions.map((item) => { + return `(${item.rules + .map((rule) => { + if (rule.leftSide && rule.rightSide) { + return `${getFormFieldTitle( + fieldOptions, + rule.leftSide, + )} ${getOpName(rule.opCode)} ${rule.rightSide}`; + } else { + // 有一条规则不完善。提示错误 + warningMessage = '请完善条件规则'; + return ''; + } + }) + .join(item.and ? ' 且 ' : ' 或 ')} ) `; + }); + showText = warningMessage + ? '' + : conditionGroup?.join(groupAnd ? ' 且 ' : ' 或 '); + } + return showText; +} + +/** 获取表单字段名称*/ +function getFormFieldTitle( + fieldOptions: Array>, + field: string, +) { + const item = fieldOptions.find((item) => item.field === field); + return item?.title; +} + +/** 获取操作符名称 */ +function getOpName(opCode: string): string | undefined { + const opName = COMPARISON_OPERATORS.find( + (item: any) => item.value === opCode, + ); + return opName?.label; +} + +/** 获取条件节点默认的名称 */ +export function getDefaultConditionNodeName( + index: number, + defaultFlow: boolean | undefined, +): string { + if (defaultFlow) { + return '其它情况'; + } + return `条件${index + 1}`; +} + +/** 获取包容分支条件节点默认的名称 */ +export function getDefaultInclusiveConditionNodeName( + index: number, + defaultFlow: boolean | undefined, +): string { + if (defaultFlow) { + return '其它情况'; + } + return `包容条件${index + 1}`; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/index.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/index.ts new file mode 100644 index 000000000..41269eae8 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/index.ts @@ -0,0 +1,11 @@ +import './styles/simple-process-designer.scss'; + +export { default as HttpRequestSetting } from './components/nodes-config/modules/http-request-setting.vue'; + +export { default as SimpleProcessDesigner } from './components/simple-process-designer.vue'; + +export { default as SimpleProcessViewer } from './components/simple-process-viewer.vue'; + +export type { SimpleFlowNode } from './consts'; + +export { parseFormFields } from './helpers'; diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.ttf b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.ttf new file mode 100644 index 000000000..06f4e31c4 Binary files /dev/null and b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.ttf differ diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff new file mode 100644 index 000000000..0724e7505 Binary files /dev/null and b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff differ diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff2 b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff2 new file mode 100644 index 000000000..c904bb67d Binary files /dev/null and b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff2 differ diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/simple-process-designer.scss b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/simple-process-designer.scss new file mode 100644 index 000000000..a36f96986 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/simple-process-designer.scss @@ -0,0 +1,759 @@ +// TODO 整个样式是不是要重新优化一下 +// iconfont 样式 +@font-face { + font-family: iconfont; /* Project id 4495938 */ + src: + url('iconfont.woff2?t=1737639517142') format('woff2'), + url('iconfont.woff?t=1737639517142') format('woff'), + url('iconfont.ttf?t=1737639517142') format('truetype'); +} +// 配置节点头部 +.config-header { + display: flex; + flex-direction: column; + + .node-name { + display: flex; + align-items: center; + height: 24px; + font-size: 16px; + line-height: 24px; + cursor: pointer; + } + + .divide-line { + width: 100%; + height: 1px; + margin-top: 16px; + background: #eee; + } + + .config-editable-input { + max-width: 510px; + height: 24px; + font-size: 16px; + line-height: 24px; + border: 1px solid #d9d9d9; + border-radius: 4px; + transition: all 0.3s; + + &:focus { + outline: 0; + border-color: #40a9ff; + box-shadow: 0 0 0 2px rgb(24 144 255 / 20%); + } + } +} +// 节点连线气泡卡片样式 +.handler-item-wrapper { + display: flex; + flex-wrap: wrap; + width: 320px; + cursor: pointer; + + .handler-item { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 12px; + } + + .handler-item-icon { + width: 50px; + height: 50px; + text-align: center; + user-select: none; + background: #fff; + border: 1px solid #e2e2e2; + border-radius: 50%; + + &:hover { + background: #e2e2e2; + box-shadow: 0 2px 4px 0 rgb(0 0 0 / 10%); + } + + .icon-size { + font-size: 25px; + line-height: 50px; + } + } + + .approve { + color: #ff943e; + } + + .copy { + color: #3296fa; + } + + .condition { + color: #67c23a; + } + + .parallel { + color: #626aef; + } + + .inclusive { + color: #345da2; + } + + .delay { + color: #e47470; + } + + .trigger { + color: #3373d2; + } + + .router { + color: #ca3a31; + } + + .transactor { + color: #309; + } + + .child-process { + color: #963; + } + + .async-child-process { + color: #066; + } + + .handler-item-text { + width: 80px; + margin-top: 4px; + font-size: 13px; + text-align: center; + } +} +// Simple 流程模型样式 +.simple-process-model-container { + width: 100%; + height: 100%; + padding-top: 32px; + overflow-x: auto; + background-color: #fafafa; + + .simple-process-model { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: fit-content; + background: url('./svg/simple-process-bg.svg') 0 0 repeat; + transform: scale(1); + transform-origin: 50% 0 0; + transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + // 节点容器 定义节点宽度 + .node-container { + width: 200px; + } + // 节点 + .node-box { + position: relative; + display: flex; + flex-direction: column; + min-height: 70px; + padding: 5px 10px 8px; + cursor: pointer; + background-color: #fff; + border: 2px solid transparent; + border-radius: 8px; + box-shadow: 0 1px 4px 0 rgb(10 30 65 / 16%); + transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1); + + &.status-pass { + background-color: #a9da90; + border-color: #67c23a; + } + + &.status-pass:hover { + border-color: #67c23a; + } + + &.status-running { + background-color: #e7f0fe; + border-color: #5a9cf8; + } + + &.status-running:hover { + border-color: #5a9cf8; + } + + &.status-reject { + background-color: #f6e5e5; + border-color: #e47470; + } + + &.status-reject:hover { + border-color: #e47470; + } + + &:hover { + border-color: #0089ff; + + .node-toolbar { + opacity: 1; + } + + .branch-node-move { + display: flex; + } + } + + // 普通节点标题 + .node-title-container { + display: flex; + align-items: center; + padding: 4px; + cursor: pointer; + border-radius: 4px 4px 0 0; + + .node-title-icon { + display: flex; + align-items: center; + + &.user-task { + color: #ff943e; + } + + &.copy-task { + color: #3296fa; + } + + &.start-user { + color: #676565; + } + + &.delay-node { + color: #e47470; + } + + &.trigger-node { + color: #3373d2; + } + + &.router-node { + color: #ca3a31; + } + + &.transactor-task { + color: #309; + } + + &.child-process { + color: #963; + } + + &.async-child-process { + color: #066; + } + } + + .node-title { + margin-left: 4px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 14px; + font-weight: 600; + line-height: 18px; + color: #1f1f1f; + white-space: nowrap; + + &:hover { + border-bottom: 1px dashed #f60; + } + } + } + + // 条件节点标题 + .branch-node-title-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: 4px 0; + cursor: pointer; + border-radius: 4px 4px 0 0; + + .input-max-width { + max-width: 115px !important; + } + + .branch-title { + overflow: hidden; + text-overflow: ellipsis; + font-size: 13px; + font-weight: 600; + color: #f60; + white-space: nowrap; + + &:hover { + border-bottom: 1px dashed #000; + } + } + + .branch-priority { + min-width: 50px; + font-size: 12px; + } + } + + .node-content { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 32px; + padding: 4px 8px; + margin-top: 4px; + line-height: 32px; + color: #111f2c; + background: rgb(0 0 0 / 3%); + border-radius: 4px; + + .node-text { + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 2; /* 这将限制文本显示为两行 */ + font-size: 14px; + line-height: 24px; + word-break: break-all; + -webkit-box-orient: vertical; + } + } + + //条件节点内容 + .branch-node-content { + display: flex; + align-items: center; + min-height: 32px; + padding: 4px 0; + margin-top: 4px; + line-height: 32px; + color: #111f2c; + background: rgb(0 0 0 / 3%); + border-radius: 4px; + + .branch-node-text { + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 1; /* 这将限制文本显示为一行 */ + font-size: 12px; + line-height: 24px; + word-break: break-all; + -webkit-box-orient: vertical; + } + } + + // 节点操作 :删除 + .node-toolbar { + position: absolute; + top: -20px; + right: 0; + display: flex; + opacity: 0; + + .toolbar-icon { + vertical-align: middle; + text-align: center; + } + } + + // 条件节点左右移动 + .branch-node-move { + position: absolute; + display: none; + align-items: center; + justify-content: center; + width: 10px; + height: 100%; + cursor: pointer; + } + + .move-node-left { + top: 0; + left: -2px; + background: rgb(126 134 142 / 8%); + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + } + + .move-node-right { + top: 0; + right: -2px; + background: rgb(126 134 142 / 8%); + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + } + } + + .node-config-error { + border-color: #ff5219 !important; + } + // 普通节点包装 + .node-wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + // 节点连线处理 + .node-handler-wrapper { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 70px; + user-select: none; + + &::before { + position: absolute; + top: 0; + z-index: 0; + width: 2px; + height: 100%; + margin: auto; + content: ''; + background-color: #dedede; + } + + .node-handler { + .add-icon { + position: relative; + top: -5px; + display: flex; + align-items: center; + justify-content: center; + width: 25px; + height: 25px; + color: #fff; + cursor: pointer; + background-color: #0089ff; + border-radius: 50%; + + &:hover { + transform: scale(1.1); + } + } + } + + .node-handler-arrow { + position: absolute; + bottom: 0; + left: 50%; + display: flex; + transform: translateX(-50%); + } + } + + // 条件节点包装 + .branch-node-wrapper { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: 16px; + + .branch-node-container { + position: relative; + display: flex; + min-width: fit-content; + + &::before { + position: absolute; + left: 50%; + width: 4px; + height: 100%; + content: ''; + background-color: #fafafa; + transform: translate(-50%); + } + + .branch-node-add { + position: absolute; + top: -18px; + left: 50%; + z-index: 1; + display: flex; + align-items: center; + height: 36px; + padding: 0 10px; + font-size: 12px; + line-height: 36px; + border: 2px solid #dedede; + border-radius: 18px; + transform: translateX(-50%); + transform-origin: center center; + } + + .branch-node-readonly { + position: absolute; + top: -18px; + left: 50%; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + background-color: #fff; + border: 2px solid #dedede; + border-radius: 50%; + transform: translateX(-50%); + transform-origin: center center; + + &.status-pass { + background-color: #e9f4e2; + border-color: #6bb63c; + } + + &.status-pass:hover { + border-color: #6bb63c; + } + + .icon-size { + font-size: 22px; + + &.condition { + color: #67c23a; + } + + &.parallel { + color: #626aef; + } + + &.inclusive { + color: #345da2; + } + } + } + + .branch-node-item { + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + align-items: center; + min-width: 280px; + padding: 40px 40px 0; + background: transparent; + border-top: 2px solid #dedede; + border-bottom: 2px solid #dedede; + + &::before { + position: absolute; + inset: 0; + width: 2px; + height: 100%; + margin: auto; + content: ''; + background-color: #dedede; + } + } + // 覆盖条件节点第一个节点左上角的线 + .branch-line-first-top { + position: absolute; + top: -5px; + left: -1px; + width: 50%; + height: 7px; + content: ''; + background-color: #fafafa; + } + // 覆盖条件节点第一个节点左下角的线 + .branch-line-first-bottom { + position: absolute; + bottom: -5px; + left: -1px; + width: 50%; + height: 7px; + content: ''; + background-color: #fafafa; + } + // 覆盖条件节点最后一个节点右上角的线 + .branch-line-last-top { + position: absolute; + top: -5px; + right: -1px; + width: 50%; + height: 7px; + content: ''; + background-color: #fafafa; + } + // 覆盖条件节点最后一个节点右下角的线 + .branch-line-last-bottom { + position: absolute; + right: -1px; + bottom: -5px; + width: 50%; + height: 7px; + content: ''; + background-color: #fafafa; + } + } + } + + .node-fixed-name { + display: inline-block; + width: auto; + padding: 0 4px; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + white-space: nowrap; + } + // 开始节点包装 + .start-node-wrapper { + position: relative; + margin-top: 16px; + + .start-node-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .start-node-box { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + width: 90px; + height: 36px; + padding: 3px 4px; + color: #212121; + cursor: pointer; + background: #fafafa; + border-radius: 30px; + box-shadow: 0 1px 5px 0 rgb(10 30 65 / 8%); + } + } + } + + // 结束节点包装 + .end-node-wrapper { + margin-bottom: 16px; + + .end-node-box { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + width: 80px; + height: 36px; + color: #212121; + background-color: #fff; + border: 2px solid transparent; + border-radius: 30px; + box-shadow: 0 1px 5px 0 rgb(10 30 65 / 8%); + + &.status-pass { + background-color: #a9da90; + border-color: #6bb63c; + } + + &.status-pass:hover { + border-color: #6bb63c; + } + + &.status-reject { + background-color: #f6e5e5; + border-color: #e47470; + } + + &.status-reject:hover { + border-color: #e47470; + } + + &.status-cancel { + background-color: #eaeaeb; + border-color: #919398; + } + + &.status-cancel:hover { + border-color: #919398; + } + } + } + + // 可编辑的 title 输入框 + .editable-title-input { + max-width: 145px; + height: 20px; + margin-left: 4px; + font-size: 12px; + line-height: 20px; + border: 1px solid #d9d9d9; + border-radius: 4px; + transition: all 0.3s; + + &:focus { + outline: 0; + border-color: #40a9ff; + box-shadow: 0 0 0 2px rgb(24 144 255 / 20%); + } + } + } +} + +.iconfont { + font-family: iconfont !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-trigger::before { + content: '\e6d3'; +} + +.icon-router::before { + content: '\e6b2'; +} + +.icon-delay::before { + content: '\e600'; +} + +.icon-start-user::before { + content: '\e679'; +} + +.icon-inclusive::before { + content: '\e602'; +} + +.icon-copy::before { + content: '\e7eb'; +} + +.icon-transactor::before { + content: '\e61c'; +} + +.icon-exclusive::before { + content: '\e717'; +} + +.icon-approve::before { + content: '\e715'; +} + +.icon-parallel::before { + content: '\e688'; +} + +.icon-async-child-process::before { + content: '\e6f2'; +} + +.icon-child-process::before { + content: '\e6c1'; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/svg/simple-process-bg.svg b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/svg/simple-process-bg.svg new file mode 100644 index 000000000..eb23ab5ae --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/svg/simple-process-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue b/apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue index d38cd7c08..8d1bdc79d 100644 --- a/apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue +++ b/apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue @@ -291,8 +291,8 @@ defineExpose({ validate }); diff --git a/apps/web-ele/src/views/bpm/model/form/modules/process-design.vue b/apps/web-ele/src/views/bpm/model/form/modules/process-design.vue index 18a2b65e2..8f2495b75 100644 --- a/apps/web-ele/src/views/bpm/model/form/modules/process-design.vue +++ b/apps/web-ele/src/views/bpm/model/form/modules/process-design.vue @@ -1,5 +1,7 @@ - + />