feat: [BPM 工作流] Simple 模型 - 条件节点
							parent
							
								
									1c2b247cf4
								
							
						
					
					
						commit
						ce9b6c5b88
					
				|  | @ -0,0 +1,277 @@ | |||
| <script setup lang="ts"> | ||||
| import type { SimpleFlowNode } from '../../consts'; | ||||
| 
 | ||||
| import { getCurrentInstance, inject, ref, watch } from 'vue'; | ||||
| 
 | ||||
| import { IconifyIcon } from '@vben/icons'; | ||||
| import { cloneDeep, buildShortUUID as generateUUID } from '@vben/utils'; | ||||
| 
 | ||||
| import { Button, Input } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { | ||||
|   ConditionType, | ||||
|   DEFAULT_CONDITION_GROUP_VALUE, | ||||
|   NODE_DEFAULT_TEXT, | ||||
|   NodeType, | ||||
| } from '../../consts'; | ||||
| import { getDefaultConditionNodeName, useTaskStatusClass } from '../../helpers'; | ||||
| // TODO import ConditionNodeConfig from './nodes-config/condition-node-config.vue'; | ||||
| import ProcessNodeTree from '../process-node-tree.vue'; | ||||
| import NodeHandler from './node-handler.vue'; | ||||
| 
 | ||||
| defineOptions({ name: 'ExclusiveNode' }); | ||||
| const props = defineProps({ | ||||
|   flowNode: { | ||||
|     type: Object as () => SimpleFlowNode, | ||||
|     required: true, | ||||
|   }, | ||||
| }); | ||||
| // 定义事件,更新父组件 | ||||
| const emits = defineEmits<{ | ||||
|   findParentNode: [nodeList: SimpleFlowNode[], nodeType: number]; | ||||
|   recursiveFindParentNode: [ | ||||
|     nodeList: SimpleFlowNode[], | ||||
|     curentNode: SimpleFlowNode, | ||||
|     nodeType: number, | ||||
|   ]; | ||||
|   'update:modelValue': [node: SimpleFlowNode | undefined]; | ||||
| }>(); | ||||
| const { proxy } = getCurrentInstance() as any; | ||||
| // 是否只读 | ||||
| const readonly = inject<Boolean>('readonly'); | ||||
| const currentNode = ref<SimpleFlowNode>(props.flowNode); | ||||
| watch( | ||||
|   () => props.flowNode, | ||||
|   (newValue) => { | ||||
|     currentNode.value = newValue; | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| const showInputs = ref<boolean[]>([]); | ||||
| // 失去焦点 | ||||
| const blurEvent = (index: number) => { | ||||
|   showInputs.value[index] = false; | ||||
|   const conditionNode = currentNode.value.conditionNodes?.at( | ||||
|     index, | ||||
|   ) as SimpleFlowNode; | ||||
|   conditionNode.name = | ||||
|     conditionNode.name || | ||||
|     getDefaultConditionNodeName( | ||||
|       index, | ||||
|       conditionNode.conditionSetting?.defaultFlow, | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| // 点击条件名称 | ||||
| const clickEvent = (index: number) => { | ||||
|   showInputs.value[index] = true; | ||||
| }; | ||||
| 
 | ||||
| const conditionNodeConfig = (nodeId: string) => { | ||||
|   if (readonly) { | ||||
|     return; | ||||
|   } | ||||
|   const conditionNode = proxy.$refs[nodeId][0]; | ||||
|   conditionNode.open(); | ||||
| }; | ||||
| 
 | ||||
| // 新增条件 | ||||
| const addCondition = () => { | ||||
|   const conditionNodes = currentNode.value.conditionNodes; | ||||
|   if (conditionNodes) { | ||||
|     const len = conditionNodes.length; | ||||
|     const lastIndex = len - 1; | ||||
|     const conditionData: SimpleFlowNode = { | ||||
|       id: `Flow_${generateUUID()}`, | ||||
|       name: `条件${len}`, | ||||
|       showText: '', | ||||
|       type: NodeType.CONDITION_NODE, | ||||
|       childNode: undefined, | ||||
|       conditionNodes: [], | ||||
|       conditionSetting: { | ||||
|         defaultFlow: false, | ||||
|         conditionType: ConditionType.RULE, | ||||
|         conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE), | ||||
|       }, | ||||
|     }; | ||||
|     conditionNodes.splice(lastIndex, 0, conditionData); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // 删除条件 | ||||
| const deleteCondition = (index: number) => { | ||||
|   const conditionNodes = currentNode.value.conditionNodes; | ||||
|   if (conditionNodes) { | ||||
|     conditionNodes.splice(index, 1); | ||||
|     if (conditionNodes.length === 1) { | ||||
|       const childNode = currentNode.value.childNode; | ||||
|       // 更新此节点为后续孩子节点 | ||||
|       emits('update:modelValue', childNode); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // 移动节点 | ||||
| const moveNode = (index: number, to: number) => { | ||||
|   // -1 :向左  1: 向右 | ||||
|   if ( | ||||
|     currentNode.value.conditionNodes && | ||||
|     currentNode.value.conditionNodes[index] | ||||
|   ) { | ||||
|     currentNode.value.conditionNodes[index] = | ||||
|       currentNode.value.conditionNodes.splice( | ||||
|         index + to, | ||||
|         1, | ||||
|         currentNode.value.conditionNodes[index], | ||||
|       )[0] as SimpleFlowNode; | ||||
|   } | ||||
| }; | ||||
| // 递归从父节点中查询匹配的节点 | ||||
| const recursiveFindParentNode = ( | ||||
|   nodeList: SimpleFlowNode[], | ||||
|   node: SimpleFlowNode, | ||||
|   nodeType: number, | ||||
| ) => { | ||||
|   if (!node || node.type === NodeType.START_USER_NODE) { | ||||
|     return; | ||||
|   } | ||||
|   if (node.type === nodeType) { | ||||
|     nodeList.push(node); | ||||
|   } | ||||
|   // 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.EXCLUSIVE_NODE) 继续查找 | ||||
|   emits('findParentNode', nodeList, nodeType); | ||||
| }; | ||||
| </script> | ||||
| <template> | ||||
|   <div class="branch-node-wrapper"> | ||||
|     <div class="branch-node-container"> | ||||
|       <div | ||||
|         v-if="readonly" | ||||
|         class="branch-node-readonly" | ||||
|         :class="`${useTaskStatusClass(currentNode?.activityStatus)}`" | ||||
|       > | ||||
|         <span class="iconfont icon-exclusive icon-size condition"></span> | ||||
|       </div> | ||||
|       <Button v-else class="branch-node-add" @click="addCondition"> | ||||
|         添加条件 | ||||
|       </Button> | ||||
| 
 | ||||
|       <div | ||||
|         class="branch-node-item" | ||||
|         v-for="(item, index) in currentNode.conditionNodes" | ||||
|         :key="index" | ||||
|       > | ||||
|         <template v-if="index === 0"> | ||||
|           <div class="branch-line-first-top"></div> | ||||
|           <div class="branch-line-first-bottom"></div> | ||||
|         </template> | ||||
|         <template v-if="index + 1 === currentNode.conditionNodes?.length"> | ||||
|           <div class="branch-line-last-top"></div> | ||||
|           <div class="branch-line-last-bottom"></div> | ||||
|         </template> | ||||
|         <div class="node-wrapper"> | ||||
|           <div class="node-container"> | ||||
|             <div | ||||
|               class="node-box" | ||||
|               :class="[ | ||||
|                 { 'node-config-error': !item.showText }, | ||||
|                 `${useTaskStatusClass(item.activityStatus)}`, | ||||
|               ]" | ||||
|             > | ||||
|               <div class="branch-node-title-container"> | ||||
|                 <div v-if="!readonly && showInputs[index]"> | ||||
|                   <Input | ||||
|                     type="text" | ||||
|                     class="editable-title-input" | ||||
|                     @blur="blurEvent(index)" | ||||
|                     v-model="item.name" | ||||
|                   /> | ||||
|                 </div> | ||||
|                 <div v-else class="branch-title" @click="clickEvent(index)"> | ||||
|                   {{ item.name }} | ||||
|                 </div> | ||||
|                 <div class="branch-priority">优先级{{ index + 1 }}</div> | ||||
|               </div> | ||||
|               <div | ||||
|                 class="branch-node-content" | ||||
|                 @click="conditionNodeConfig(item.id)" | ||||
|               > | ||||
|                 <div | ||||
|                   class="branch-node-text" | ||||
|                   :title="item.showText" | ||||
|                   v-if="item.showText" | ||||
|                 > | ||||
|                   {{ item.showText }} | ||||
|                 </div> | ||||
|                 <div class="branch-node-text" v-else> | ||||
|                   {{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }} | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div | ||||
|                 class="node-toolbar" | ||||
|                 v-if=" | ||||
|                   !readonly && index + 1 !== currentNode.conditionNodes?.length | ||||
|                 " | ||||
|               > | ||||
|                 <div class="toolbar-icon"> | ||||
|                   <IconifyIcon | ||||
|                     color="#0089ff" | ||||
|                     icon="ep:circle-close-filled" | ||||
|                     :size="18" | ||||
|                     @click="deleteCondition(index)" | ||||
|                   /> | ||||
|                 </div> | ||||
|               </div> | ||||
|               <div | ||||
|                 class="branch-node-move move-node-left" | ||||
|                 v-if=" | ||||
|                   !readonly && | ||||
|                   index !== 0 && | ||||
|                   index + 1 !== currentNode.conditionNodes?.length | ||||
|                 " | ||||
|                 @click="moveNode(index, -1)" | ||||
|               > | ||||
|                 <IconifyIcon icon="ep:arrow-left" /> | ||||
|               </div> | ||||
| 
 | ||||
|               <div | ||||
|                 class="branch-node-move move-node-right" | ||||
|                 v-if=" | ||||
|                   !readonly && | ||||
|                   currentNode.conditionNodes && | ||||
|                   index < currentNode.conditionNodes.length - 2 | ||||
|                 " | ||||
|                 @click="moveNode(index, 1)" | ||||
|               > | ||||
|                 <IconifyIcon icon="ep:arrow-right" /> | ||||
|               </div> | ||||
|             </div> | ||||
|             <NodeHandler | ||||
|               v-model:child-node="item.childNode" | ||||
|               :current-node="item" | ||||
|             /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <!-- TODO 条件节点配置--> | ||||
|         <!-- <ConditionNodeConfig | ||||
|           :node-index="index" | ||||
|           :condition-node="item" | ||||
|           :ref="item.id" | ||||
|         /> --> | ||||
|         <!-- 递归显示子节点  --> | ||||
|         <ProcessNodeTree | ||||
|           v-if="item && item.childNode" | ||||
|           :parent-node="item" | ||||
|           v-model:flow-node="item.childNode" | ||||
|           @recursive-find-parent-node="recursiveFindParentNode" | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
|     <NodeHandler | ||||
|       v-if="currentNode" | ||||
|       v-model:child-node="currentNode.childNode" | ||||
|       :current-node="currentNode" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
| <style lang="scss" scoped></style> | ||||
|  | @ -5,6 +5,7 @@ import { NodeType } from '../consts'; | |||
| import { useWatchNode } from '../helpers'; | ||||
| import CopyTaskNode from './nodes/copy-task-node.vue'; | ||||
| import EndEventNode from './nodes/end-event-node.vue'; | ||||
| import ExclusiveNode from './nodes/exclusive-node.vue'; | ||||
| import InclusiveNode from './nodes/inclusive-node.vue'; | ||||
| import StartUserNode from './nodes/start-user-node.vue'; | ||||
| import TriggerNode from './nodes/trigger-node.vue'; | ||||
|  | @ -86,12 +87,12 @@ const recursiveFindParentNode = ( | |||
|     @update:flow-node="handleModelValueUpdate" | ||||
|   /> | ||||
|   <!-- 条件节点 --> | ||||
|   <!-- <ExclusiveNode | ||||
|   <ExclusiveNode | ||||
|     v-if="currentNode && currentNode.type === NodeType.CONDITION_BRANCH_NODE" | ||||
|     :flow-node="currentNode" | ||||
|     @update:model-value="handleModelValueUpdate" | ||||
|     @find:parent-node="findFromParentNode" | ||||
|   /> --> | ||||
|     @find-parent-node="findParentNode" | ||||
|   /> | ||||
|   <!-- 并行节点 --> | ||||
|   <!-- <ParallelNode | ||||
|     v-if="currentNode && currentNode.type === NodeType.PARALLEL_BRANCH_NODE" | ||||
|  |  | |||
|  | @ -480,6 +480,8 @@ | |||
|           top: -18px; | ||||
|           left: 50%; | ||||
|           z-index: 1; | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|           height: 36px; | ||||
|           padding: 0 10px; | ||||
|           font-size: 12px; | ||||
|  | @ -488,8 +490,6 @@ | |||
|           border-radius: 18px; | ||||
|           transform: translateX(-50%); | ||||
|           transform-origin: center center; | ||||
|           display: flex; | ||||
|           align-items: center; | ||||
|         } | ||||
| 
 | ||||
|         .branch-node-readonly { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 jason
						jason