feat: 子流程支持

pull/624/head
Lesan 2024-12-17 16:36:58 +08:00
parent 5e386336b9
commit d4401919a4
10 changed files with 271 additions and 2 deletions

View File

@ -20,3 +20,9 @@ export const getProcessDefinitionList = async (params) => {
params
})
}
export const getChildProcessSimpleList = async () => {
return await request.get({
url: '/bpm/process-definition/child-process/list-all-simple'
})
}

View File

@ -0,0 +1 @@
<svg t="1734419297528" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4299" width="200" height="200"><path d="M375.466667 213.333333a85.333333 85.333333 0 1 1-85.333334 85.333334 85.333333 85.333333 0 0 1 85.333334-85.333334m0-85.333333a170.666667 170.666667 0 1 0 170.666666 170.666667 170.666667 170.666667 0 0 0-170.666666-170.666667z" fill="#ffffff" p-id="4300"></path><path d="M716.8 597.333333m-85.333333 0a85.333333 85.333333 0 1 0 170.666666 0 85.333333 85.333333 0 1 0-170.666666 0Z" fill="#ffffff" p-id="4301"></path><path d="M716.8 853.333333m-85.333333 0a85.333333 85.333333 0 1 0 170.666666 0 85.333333 85.333333 0 1 0-170.666666 0Z" fill="#ffffff" p-id="4302"></path><path d="M418.133333 384h-85.333333v426.666667a85.333333 85.333333 0 0 0 85.333333 85.333333V384zM674.133333 554.666667h-256v85.333333h256v-85.333333zM674.133333 810.666667h-256v85.333333h256v-85.333333z" fill="#ffffff" p-id="4303"></path></svg>

After

Width:  |  Height:  |  Size: 971 B

View File

@ -39,6 +39,13 @@
</div>
<div class="handler-item-text">包容分支</div>
</div>
<!-- TODO @芋艿这里需要换一下子流程的图标~ -->
<div class="handler-item" @click="addNode(NodeType.CHILD_PROCESS_NODE)">
<div class="handler-item-icon copy">
<span class="iconfont icon-size icon-copy"></span>
</div>
<div class="handler-item-text">子流程</div>
</div>
</div>
<template #reference>
<div class="add-icon"><Icon icon="ep:plus" /></div>
@ -208,6 +215,16 @@ const addNode = (type: number) => {
}
emits('update:childNode', data)
}
if (type === NodeType.CHILD_PROCESS_NODE) {
const data: SimpleFlowNode = {
id: 'Activity_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.CHILD_PROCESS_NODE) as string,
showText: '',
type: NodeType.CHILD_PROCESS_NODE,
childNode: props.childNode
}
emits('update:childNode', data)
}
}
</script>

View File

@ -17,6 +17,12 @@
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 子流程节点 -->
<ChildProcessNode
v-if="currentNode && currentNode.type === NodeType.CHILD_PROCESS_NODE"
:flow-node="currentNode"
@update:flow-node="handleModelValueUpdate"
/>
<!-- 条件节点 -->
<ExclusiveNode
v-if="currentNode && currentNode.type === NodeType.CONDITION_BRANCH_NODE"
@ -57,6 +63,7 @@ import StartUserNode from './nodes/StartUserNode.vue'
import EndEventNode from './nodes/EndEventNode.vue'
import UserTaskNode from './nodes/UserTaskNode.vue'
import CopyTaskNode from './nodes/CopyTaskNode.vue'
import ChildProcessNode from './nodes/ChildProcessNode.vue'
import ExclusiveNode from './nodes/ExclusiveNode.vue'
import ParallelNode from './nodes/ParallelNode.vue'
import InclusiveNode from './nodes/InclusiveNode.vue'

View File

@ -23,6 +23,11 @@ export enum NodeType {
*/
COPY_TASK_NODE = 12,
/**
*
*/
CHILD_PROCESS_NODE = 13,
/**
*
*/
@ -98,6 +103,8 @@ export interface SimpleFlowNode {
defaultFlow?: boolean
// 活动的状态,用于前端节点状态展示
activityStatus?: TaskStatusEnum
// 调用子流程
childProcess?: string
}
// 候选人策略枚举 用于审批节点。抄送节点 )
export enum CandidateStrategy {
@ -413,12 +420,14 @@ NODE_DEFAULT_TEXT.set(NodeType.USER_TASK_NODE, '请配置审批人')
NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
NODE_DEFAULT_TEXT.set(NodeType.CHILD_PROCESS_NODE, '请设置子流程')
export const NODE_DEFAULT_NAME = new Map<number, string>()
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
NODE_DEFAULT_NAME.set(NodeType.CHILD_PROCESS_NODE, '子流程')
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
export const CANDIDATE_STRATEGY: DictDataVO[] = [

View File

@ -0,0 +1,115 @@
<template>
<el-drawer
:append-to-body="true"
v-model="settingVisible"
:show-close="false"
:size="550"
:before-close="saveConfig"
>
<template #header>
<div class="config-header">
<input
v-if="showInput"
type="text"
class="config-editable-input"
@blur="blurEvent()"
v-mountedFocus
v-model="nodeName"
:placeholder="nodeName"
/>
<div v-else class="node-name">
{{ nodeName }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()" />
</div>
<div class="divide-line"></div>
</div>
</template>
<el-tabs type="border-card" v-model="activeTabName">
<el-tab-pane label="子流程" name="child">
<div>
<el-form ref="formRef" :model="configForm" label-position="top" :rules="formRules">
<el-form-item label="选择子流程" prop="childProcessKey">
<el-select v-model="configForm.childProcessKey" clearable>
<el-option
v-for="(item, index) in childProcessOptions"
:key="index"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
<el-divider />
<div>
<el-button type="primary" @click="saveConfig"> </el-button>
<el-button @click="closeDrawer"> </el-button>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { getChildProcessSimpleList } from '@/api/bpm/definition'
import { SimpleFlowNode, NodeType } from '../consts'
import { useWatchNode, useDrawer, useNodeName } from '../node'
defineOptions({
name: 'ChildProcessNodeConfig'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
//
const { settingVisible, closeDrawer, openDrawer } = useDrawer()
//
const currentNode = useWatchNode(props)
//
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.CHILD_PROCESS_NODE)
// Tab
const activeTabName = ref('child')
//
const formRef = ref() // Ref
//
const formRules = reactive({
childProcessKey: [{ required: true, message: '子流程不能为空', trigger: 'change' }]
})
const configForm = ref({
childProcessKey: ''
})
const childProcessOptions = ref()
//
const saveConfig = async () => {
activeTabName.value = 'child'
if (!formRef) return false
const valid = await formRef.value.validate()
if (!valid) return false
const childInfo = childProcessOptions.value.find(
(option) => option.key === configForm.value.childProcessKey
)
currentNode.value.name = nodeName.value!
currentNode.value.childProcess = `${childInfo.key}:${childInfo.name}`
currentNode.value.showText = `调用子流程:${childInfo.name}`
settingVisible.value = false
return true
}
//
const showChildProcessNodeConfig = (node: SimpleFlowNode) => {
nodeName.value = node.name
if (node.childProcess && node.childProcess !== '') {
configForm.value.childProcessKey = node.childProcess.split(':')[0]
}
}
defineExpose({ openDrawer, showChildProcessNodeConfig }) //
onMounted(async () => {
childProcessOptions.value = await getChildProcessSimpleList()
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,97 @@
<template>
<div class="node-wrapper">
<div class="node-container">
<div
class="node-box"
:class="[
{ 'node-config-error': !currentNode.showText },
`${useTaskStatusClass(currentNode?.activityStatus)}`
]"
>
<div class="node-title-container">
<div class="node-title-icon copy-task"><span class="iconfont icon-copy"></span></div>
<input
v-if="!readonly && showInput"
type="text"
class="editable-title-input"
@blur="blurEvent()"
v-mountedFocus
v-model="currentNode.name"
:placeholder="currentNode.name"
/>
<div v-else class="node-title" @click="clickTitle">
{{ currentNode.name }}
</div>
</div>
<div class="node-content" @click="openNodeConfig">
<div class="node-text" :title="currentNode.showText" v-if="currentNode.showText">
{{ currentNode.showText }}
</div>
<div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.CHILD_PROCESS_NODE) }}
</div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div>
<div v-if="!readonly" class="node-toolbar">
<div class="toolbar-icon"
><Icon color="#0089ff" icon="ep:circle-close-filled" :size="18" @click="deleteNode"
/></div>
</div>
</div>
<!-- 传递子节点给添加节点组件会在子节点前面添加节点 -->
<NodeHandler
v-if="currentNode"
v-model:child-node="currentNode.childNode"
:current-node="currentNode"
/>
</div>
<ChildProcessNodeConfig
v-if="!readonly && currentNode"
ref="nodeSetting"
:flow-node="currentNode"
/>
</div>
</template>
<script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
import NodeHandler from '../NodeHandler.vue'
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
import ChildProcessNodeConfig from '../nodes-config/ChildProcessNodeConfig.vue'
defineOptions({
name: 'ChildProcessNode'
})
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true
}
})
//
const emits = defineEmits<{
'update:flowNode': [node: SimpleFlowNode | undefined]
}>()
//
const readonly = inject<Boolean>('readonly')
//
const currentNode = useWatchNode(props)
//
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.CHILD_PROCESS_NODE)
const nodeSetting = ref()
//
const openNodeConfig = () => {
if (readonly) {
return
}
nodeSetting.value.showChildProcessNodeConfig(currentNode.value)
nodeSetting.value.openDrawer()
}
//
const deleteNode = () => {
emits('update:flowNode', currentNode.value.childNode)
}
</script>
<style scoped></style>

View File

@ -144,6 +144,7 @@ export enum DICT_TYPE {
// ========== BPM 模块 ==========
BPM_MODEL_TYPE = 'bpm_model_type',
BPM_PROCESS_TYPE = 'bpm_process_type',
BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy',
BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',

View File

@ -108,6 +108,19 @@ const setSimpleModelNodeTaskStatus = (
simpleModel.activityStatus = TaskStatusEnum.NOT_START
}
}
//
if (simpleModel.type === NodeType.CHILD_PROCESS_NODE) {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
if (rejectedTaskActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.REJECT
} else if (unfinishedTaskActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.RUNNING
} else if (finishedActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.APPROVE
}
}
// SequenceFlow
if (simpleModel.type === NodeType.CONDITION_NODE) {
//
@ -157,7 +170,7 @@ const setSimpleModelNodeTaskStatus = (
.process-viewer-container {
height: 100%;
width: 100%;
:deep(.process-viewer) {
height: 100% !important;
min-height: 100%;

View File

@ -168,6 +168,7 @@ import copySvg from '@/assets/svgs/bpm/copy.svg'
import conditionSvg from '@/assets/svgs/bpm/condition.svg'
import parallelSvg from '@/assets/svgs/bpm/parallel.svg'
import finishSvg from '@/assets/svgs/bpm/finish.svg'
import childProcessSvg from '@/assets/svgs/bpm/childProcess.svg'
defineOptions({ name: 'BpmProcessInstanceTimeline' })
withDefaults(
@ -234,7 +235,9 @@ const nodeTypeSvgMap = {
//
[NodeType.CONDITION_NODE]: { color: '#14bb83', svg: conditionSvg },
//
[NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg }
[NodeType.PARALLEL_BRANCH_NODE]: { color: '#14bb83', svg: parallelSvg },
//
[NodeType.CHILD_PROCESS_NODE]: { color: '#3296fb', svg: childProcessSvg }
}
// -101 icon