Explorar o código

【功能修改】 新增包容分支

jason hai 10 meses
pai
achega
520fc784bc

+ 34 - 2
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue

@@ -27,11 +27,17 @@
             <div class="handler-item-text">条件分支</div>
           </div>
           <div class="handler-item" @click="addNode(NodeType.PARALLEL_BRANCH_NODE)">
-            <div class="handler-item-icon condition">
+            <div class="handler-item-icon parallel">
               <span class="iconfont icon-size icon-parallel"></span>
             </div>
             <div class="handler-item-text">并行分支</div>
           </div>
+          <div class="handler-item" @click="addNode(NodeType.INCLUSIVE_BRANCH_NODE)">
+            <div class="handler-item-icon inclusive">
+              <span class="iconfont icon-size icon-inclusive"></span>
+            </div>
+            <div class="handler-item-text">包容分支</div>
+          </div>
         </div>
         <template #reference>
           <div class="add-icon"><Icon icon="ep:plus" /></div>
@@ -127,7 +133,7 @@ const addNode = (type: number) => {
         {
           id: 'Flow_' + generateUUID(),
           name: '其它情况',
-          showText: '其它情况进入此流程',
+          showText: '未满足其它条件时,将进入此分支',
           type: NodeType.CONDITION_NODE,
           childNode: undefined,
           conditionType: undefined,
@@ -162,6 +168,32 @@ const addNode = (type: number) => {
     }
     emits('update:childNode', data)
   }
+  if (type === NodeType.INCLUSIVE_BRANCH_NODE) {
+    const data: SimpleFlowNode = {
+      name: '包容分支',
+      type: NodeType.INCLUSIVE_BRANCH_NODE,
+      id: 'GateWay_' + generateUUID(),
+      childNode: props.childNode,
+      conditionNodes: [
+        {
+          id: 'Flow_' + generateUUID(),
+          name: '包容条件1',
+          showText: '',
+          type: NodeType.CONDITION_NODE,
+          childNode: undefined
+        },
+        {
+          id: 'Flow_' + generateUUID(),
+          name: '其它情况',
+          showText: '未满足其它条件时,将进入此分支',
+          type: NodeType.CONDITION_NODE,
+          childNode: undefined,
+          defaultFlow: true
+        }
+      ]
+    }
+    emits('update:childNode', data)
+  }
 }
 </script>
 

+ 8 - 0
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue

@@ -31,6 +31,13 @@
     @update:model-value="handleModelValueUpdate"
     @find:parent-node="findFromParentNode"
   />
+  <!-- 包容分支节点 -->
+  <InclusiveNode
+    v-if="currentNode && currentNode.type === NodeType.INCLUSIVE_BRANCH_NODE"
+    :flow-node="currentNode"
+    @update:model-value="handleModelValueUpdate"
+    @find:parent-node="findFromParentNode"
+  />
   <!-- 递归显示孩子节点  -->
   <ProcessNodeTree
     v-if="currentNode && currentNode.childNode"
@@ -49,6 +56,7 @@ import UserTaskNode from './nodes/UserTaskNode.vue'
 import CopyTaskNode from './nodes/CopyTaskNode.vue'
 import ExclusiveNode from './nodes/ExclusiveNode.vue'
 import ParallelNode from './nodes/ParallelNode.vue'
+import InclusiveNode from './nodes/InclusiveNode.vue'
 import { SimpleFlowNode, NodeType } from './consts'
 import { useWatchNode } from './node'
 defineOptions({

+ 14 - 15
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue

@@ -111,32 +111,31 @@ const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNo
       return
     }
     if (type == NodeType.START_USER_NODE) {
+      // 发起人节点暂时不用校验,直接校验孩子节点
       validateNode(node.childNode, errorNodes)
     }
 
-    if (type === NodeType.USER_TASK_NODE) {
+    if (
+      type === NodeType.USER_TASK_NODE ||
+      type === NodeType.COPY_TASK_NODE ||
+      type === NodeType.CONDITION_NODE
+    ) {
       if (!showText) {
         errorNodes.push(node)
       }
       validateNode(node.childNode, errorNodes)
     }
-    if (type === NodeType.COPY_TASK_NODE) {
-      if (!showText) {
-        errorNodes.push(node)
-      }
-      validateNode(node.childNode, errorNodes)
-    }
-    if (type === NodeType.CONDITION_NODE) {
-      if (!showText) {
-        errorNodes.push(node)
-      }
-      validateNode(node.childNode, errorNodes)
-    }
-
-    if (type == NodeType.CONDITION_BRANCH_NODE) {
+    
+    if (
+      type == NodeType.CONDITION_BRANCH_NODE ||
+      type == NodeType.PARALLEL_BRANCH_NODE ||
+      type == NodeType.INCLUSIVE_BRANCH_NODE
+    ) { // 分支节点
+      // 1. 先校验各个分支
       conditionNodes?.forEach((item) => {
         validateNode(item, errorNodes)
       })
+      // 2. 校验孩子节点
       validateNode(node.childNode, errorNodes)
     }
   }

+ 1 - 1
src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue

@@ -26,7 +26,7 @@
       </div>
     </template>
     <div>
-      <div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">其它条件不满足进入此分支(该分支不可编辑和删除)</div>
+      <div class="mb-3 font-size-16px" v-if="currentNode.defaultFlow">未满足其它条件时,将进入此分支(该分支不可编辑和删除)</div>
       <div v-else>
         <el-form
           ref="formRef"

+ 2 - 6
src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="branch-node-wrapper">
     <div class="branch-node-container">
-      <div class="branch-node-add" @click="addCondition">添加条件</div>
+      <el-button class="branch-node-add" color="#67c23a" @click="addCondition"  plain>添加条件</el-button>
       <div
         class="branch-node-item"
         v-for="(item, index) in currentNode.conditionNodes"
@@ -94,10 +94,6 @@ defineOptions({
   name: 'ExclusiveNode'
 })
 const props = defineProps({
-  // parentNode : {
-  //   type: Object as () => SimpleFlowNode,
-  //   required: true
-  // },
   flowNode: {
     type: Object as () => SimpleFlowNode,
     required: true
@@ -193,7 +189,7 @@ const recursiveFindParentNode = (
   node: SimpleFlowNode,
   nodeType: number
 ) => {
-  if (!node || node.type === NodeType.START_EVENT_NODE) {
+  if (!node || node.type === NodeType.START_USER_NODE) {
     return
   }
   if (node.type === nodeType) {

+ 201 - 0
src/components/SimpleProcessDesignerV2/src/nodes/InclusiveNode.vue

@@ -0,0 +1,201 @@
+<template>
+  <div class="branch-node-wrapper">
+    <div class="branch-node-container">
+      <el-button class="branch-node-add" color="#345da2" @click="addCondition"  plain>添加条件</el-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 }">
+              <div class="branch-node-title-container">
+                <div v-if="showInputs[index]">
+                  <input
+                    type="text"
+                    class="editable-title-input"
+                    @blur="blurEvent(index)"
+                    v-mountedFocus
+                    v-model="item.name"
+                  />
+                </div>
+                <div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </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="index + 1 !== currentNode.conditionNodes?.length">
+                <div class="toolbar-icon">
+                  <Icon
+                    color="#0089ff"
+                    icon="ep:circle-close-filled"
+                    :size="18"
+                    @click="deleteCondition(index)"
+                  />
+                </div>
+              </div>
+              <div
+                class="branch-node-move move-node-left"
+                v-if="index != 0 && index + 1 !== currentNode.conditionNodes?.length"
+                @click="moveNode(index, -1)"
+              >
+                <Icon icon="ep:arrow-left" />
+              </div>
+
+              <div
+                class="branch-node-move move-node-right"
+                v-if="currentNode.conditionNodes && index < currentNode.conditionNodes.length - 2"
+                @click="moveNode(index, 1)"
+              >
+                <Icon icon="ep:arrow-right" />
+              </div>
+            </div>
+            <NodeHandler v-model:child-node="item.childNode" />
+          </div>
+        </div>
+        <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"
+          @find:recursive-find-parent-node="recursiveFindParentNode"
+        />
+      </div>
+    </div>
+    <NodeHandler v-if="currentNode" v-model:child-node="currentNode.childNode" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import NodeHandler from '../NodeHandler.vue'
+import ProcessNodeTree from '../ProcessNodeTree.vue'
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
+import { getDefaultInclusiveConditionNodeName } from '../utils'
+import { generateUUID } from '@/utils'
+import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
+const { proxy } = getCurrentInstance() as any
+defineOptions({
+  name: 'InclusiveNode'
+})
+const props = defineProps({
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    required: true
+  }
+})
+// 定义事件,更新父组件
+const emits = defineEmits<{
+  'update:modelValue': [node: SimpleFlowNode | undefined]
+  'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
+  'find:recursiveFindParentNode': [
+    nodeList: SimpleFlowNode[],
+    curentNode: SimpleFlowNode,
+    nodeType: number
+  ]
+}>()
+
+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 || getDefaultInclusiveConditionNodeName(index, conditionNode.defaultFlow)
+}
+
+// 点击条件名称
+const clickEvent = (index: number) => {
+  showInputs.value[index] = true
+}
+
+const conditionNodeConfig = (nodeId: string) => {
+  const conditionNode = proxy.$refs[nodeId][0]
+  conditionNode.open()
+}
+
+// 新增条件
+const addCondition = () => {
+  const conditionNodes = currentNode.value.conditionNodes
+  if (conditionNodes) {
+    const len = conditionNodes.length
+    let lastIndex = len - 1
+    const conditionData: SimpleFlowNode = {
+      id: 'Flow_' + generateUUID(),
+      name: '包容条件' + len,
+      showText: '',
+      type: NodeType.CONDITION_NODE,
+      childNode: undefined,
+      conditionNodes: [],
+      conditionType: 1,
+      defaultFlow: false
+    }
+    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.splice(
+      index + to,
+      1,
+      currentNode.value.conditionNodes[index]
+    )[0]
+  }
+}
+// 递归从父节点中查询匹配的节点
+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.INCLUSIVE_BRANCH_NODE) 继续查找
+  emits('find:parentNode', nodeList, nodeType)
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 2 - 2
src/components/SimpleProcessDesignerV2/src/nodes/ParallelNode.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="branch-node-wrapper">
     <div class="branch-node-container">
-      <div class="branch-node-add" @click="addCondition">添加分支</div>
+      <el-button class="branch-node-add" color="#626aef" @click="addCondition"  plain>添加分支</el-button>
       <div
         class="branch-node-item"
         v-for="(item, index) in currentNode.conditionNodes"
@@ -169,7 +169,7 @@ const recursiveFindParentNode = (
   node: SimpleFlowNode,
   nodeType: number
 ) => {
-  if (!node || node.type === NodeType.START_EVENT_NODE) {
+  if (!node || node.type === NodeType.START_USER_NODE) {
     return
   }
   if (node.type === nodeType) {

+ 8 - 0
src/components/SimpleProcessDesignerV2/src/utils.ts

@@ -8,6 +8,14 @@ export const getDefaultConditionNodeName = (index: number, defaultFlow: boolean
   return '条件' + (index + 1)
 }
 
+// 获取包容分支条件节点默认的名称
+export const getDefaultInclusiveConditionNodeName = (index: number, defaultFlow: boolean | undefined): string => {
+  if (defaultFlow) {
+    return '其它情况'
+  }
+  return '包容条件' + (index + 1)
+}
+
 export const convertTimeUnit = (strTimeUnit: string) => {
   if (strTimeUnit === 'M') {
     return TimeUnitType.MINUTE

+ 20 - 14
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss

@@ -166,7 +166,7 @@
 
           .branch-priority {
             min-width: 50px;
-            font-size: 13px;
+            font-size: 12px;
           }
         }
 
@@ -198,7 +198,7 @@
         .branch-node-content {
           display: flex;
           min-height: 32px;
-          padding: 4px 8px;
+          padding: 4px 0;
           margin-top: 4px;
           line-height: 32px;
           align-items: center;
@@ -207,7 +207,7 @@
 
           .branch-node-text {
             overflow: hidden;
-            font-size: 14px;
+            font-size: 12px;
             line-height: 24px;
             text-overflow: ellipsis;
             word-break: break-all;
@@ -355,14 +355,10 @@
             padding: 0 10px;
             font-size: 12px;
             line-height: 36px;
-            color: #222;
-            cursor: pointer;
-            background: #fff;
             border: 2px solid #dedede;
             border-radius: 18px;
             transform: translateX(-50%);
             transform-origin: center center;
-            transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
           }
 
           .branch-node-item {
@@ -626,16 +622,17 @@
   cursor: pointer;
 
   .handler-item {
-    margin-right: 8px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
   }
 
   .handler-item-icon {
-    width: 80px;
-    height: 80px;
+    width: 60px;
+    height: 60px;
     background: #fff;
     border: 1px solid #e2e2e2;
     border-radius: 50%;
-    transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
     user-select: none;
     text-align: center;
 
@@ -645,8 +642,8 @@
     }
 
     .icon-size {
-      font-size: 35px;
-      line-height: 80px;
+      font-size: 25px;
+      line-height: 60px;
     }
   }
 
@@ -658,13 +655,22 @@
   }
 
   .condition {
-    color: #15bc83;
+    color: #67c23a;
+  }
+
+  .parallel {
+    color: #626aef;
+  }
+
+  .inclusive {
+    color: #345da2;
   }
 
   .handler-item-text {
     margin-top: 4px;
     width: 80px;
     text-align: center;
+    font-size: 13px;
   }
 }