feat: 子流程支持
parent
5e386336b9
commit
d4401919a4
|
|
@ -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'
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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[] = [
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
||||
// 只有只有状态是 -1、0、1 才展示头像右小角状态小icon
|
||||
|
|
|
|||
Loading…
Reference in New Issue