Quellcode durchsuchen

仿钉钉流程设计器- 审批节点配置新增拒绝处理方式

jason vor 1 Jahr
Ursprung
Commit
0e7dbbb04d

+ 29 - 21
src/components/SimpleProcessDesignerV2/src/NodeHandler.vue

@@ -1,37 +1,42 @@
 <template>
   <div class="node-handler-wrapper">
     <div class="node-handler" v-if="props.showAdd">
-     <el-popover  trigger="hover" v-model:visible="popoverShow"  placement="right-start"  width="auto"> 
-          <div class="handler-item-wrapper">
-            <div class="handler-item"  @click="addNode(NodeType.USER_TASK_NODE)">
-              <div class="approve handler-item-icon">
-                <span class="iconfont icon-approve icon-size"></span>
-              </div>
-              <div class="handler-item-text">审批人</div>
+      <el-popover
+        trigger="hover"
+        v-model:visible="popoverShow"
+        placement="right-start"
+        width="auto"
+      >
+        <div class="handler-item-wrapper">
+          <div class="handler-item" @click="addNode(NodeType.USER_TASK_NODE)">
+            <div class="approve handler-item-icon">
+              <span class="iconfont icon-approve icon-size"></span>
             </div>
-            <div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
-              <div class="handler-item-icon copy">
-                <span class="iconfont icon-size icon-copy"></span>
-              </div>
-              <div class="handler-item-text">抄送</div>
+            <div class="handler-item-text">审批人</div>
+          </div>
+          <div class="handler-item" @click="addNode(NodeType.COPY_TASK_NODE)">
+            <div class="handler-item-icon copy">
+              <span class="iconfont icon-size icon-copy"></span>
             </div>
-            <div class="handler-item" @click="addNode(NodeType.EXCLUSIVE_NODE)">
-              <div class="handler-item-icon condition">
-                <span class="iconfont icon-size icon-exclusive"></span>
-              </div>
-              <div class="handler-item-text">条件分支</div>
+            <div class="handler-item-text">抄送</div>
+          </div>
+          <div class="handler-item" @click="addNode(NodeType.EXCLUSIVE_NODE)">
+            <div class="handler-item-icon condition">
+              <span class="iconfont icon-size icon-exclusive"></span>
             </div>
+            <div class="handler-item-text">条件分支</div>
           </div>
-          <template #reference>
-            <div class="add-icon"><Icon icon="ep:plus" /></div>
-          </template>
+        </div>
+        <template #reference>
+          <div class="add-icon"><Icon icon="ep:plus" /></div>
+        </template>
       </el-popover>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { SimpleFlowNode, NodeType, NODE_DEFAULT_NAME, ApproveMethodType, CandidateStrategy } from './consts'
+import { SimpleFlowNode, NodeType, NODE_DEFAULT_NAME, ApproveMethodType, RejectHandlerType, CandidateStrategy } from './consts'
 import { generateUUID } from '@/utils'
 defineOptions({
   name: 'NodeHandler'
@@ -71,6 +76,9 @@ const addNode = (type: number) => {
          // 超时处理
         timeoutHandler: {
           enable: false
+        },
+        rejectHandler: {
+          type: RejectHandlerType.TERMINATION
         }
       },
       childNode: props.childNode

+ 78 - 33
src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue

@@ -1,58 +1,103 @@
 <template>
   <!-- 开始节点 -->
   <StartEventNode
-        v-if="currentNode && currentNode.type === NodeType.START_EVENT_NODE" 
-        :flow-node ="currentNode" 
-        @update:model-value="handleModelValueUpdate" />
+    v-if="currentNode && currentNode.type === NodeType.START_EVENT_NODE"
+    :flow-node="currentNode"
+    @update:model-value="handleModelValueUpdate"
+  />
   <!-- 审批节点 -->
-  <UserTaskNode 
-        v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE" 
-        :flow-node ="currentNode" @update:model-value="handleModelValueUpdate"/>
+  <UserTaskNode
+    v-if="currentNode && currentNode.type === NodeType.USER_TASK_NODE"
+    :flow-node="currentNode"
+    @update:model-value="handleModelValueUpdate"
+    @find:parent-node="findFromParentNode"
+  />
   <!-- 抄送节点 -->
   <CopyTaskNode
-        v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE" 
-        :flow-node ="currentNode" @update:model-value="handleModelValueUpdate"/>
+    v-if="currentNode && currentNode.type === NodeType.COPY_TASK_NODE"
+    :flow-node="currentNode"
+    @update:model-value="handleModelValueUpdate"
+  />
   <!-- 条件节点 -->
-  <ExclusiveNode 
-        v-if="currentNode && currentNode.type === NodeType.EXCLUSIVE_NODE" 
-        :flow-node ="currentNode" @update:model-value="handleModelValueUpdate"/>
+  <ExclusiveNode
+    v-if="currentNode && currentNode.type === NodeType.EXCLUSIVE_NODE"
+    :flow-node="currentNode"
+    @update:model-value="handleModelValueUpdate"
+    @find:parent-node="findFromParentNode"
+  />
   <!-- 递归显示孩子节点  -->
-  <ProcessNodeTree v-if="currentNode && currentNode.childNode"  v-model:flow-node="currentNode.childNode"/>
+  <ProcessNodeTree
+    v-if="currentNode && currentNode.childNode"
+    v-model:flow-node="currentNode.childNode"
+    :parent-node= "currentNode"
+    @find:recursive-find-parent-node="recursiveFindParentNode"
+  />
 
   <!-- 结束节点 -->
-  <EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE"/>
+  <EndEventNode v-if="currentNode && currentNode.type === NodeType.END_EVENT_NODE" />
 </template>
-<script setup lang='ts'>
-import StartEventNode from './nodes/StartEventNode.vue';
-import EndEventNode from './nodes/EndEventNode.vue';
-import UserTaskNode from './nodes/UserTaskNode.vue';
-import CopyTaskNode from './nodes/CopyTaskNode.vue';
-import ExclusiveNode from './nodes/ExclusiveNode.vue';
-import { SimpleFlowNode, NodeType }  from './consts';
+<script setup lang="ts">
+import StartEventNode from './nodes/StartEventNode.vue'
+import EndEventNode from './nodes/EndEventNode.vue'
+import UserTaskNode from './nodes/UserTaskNode.vue'
+import CopyTaskNode from './nodes/CopyTaskNode.vue'
+import ExclusiveNode from './nodes/ExclusiveNode.vue'
+import { SimpleFlowNode, NodeType } from './consts'
 defineOptions({
   name: 'ProcessNodeTree'
 })
 const props = defineProps({
-  flowNode : {
+  parentNode: {
     type: Object as () => SimpleFlowNode,
-    default: () => null 
+    default: () => null
+  },
+  flowNode: {
+    type: Object as () => SimpleFlowNode,
+    default: () => null
   }
 })
-const emits = defineEmits(['update:flowNode'])
+const emits = defineEmits<{
+  'update:flowNode',
+  'find:recursiveFindParentNode': [nodeList: SimpleFlowNode[], curentNode: SimpleFlowNode, nodeType: number]
+}>()
+
 
-const currentNode = ref<SimpleFlowNode>(props.flowNode);
+const currentNode = ref<SimpleFlowNode>(props.flowNode)
 
 // 重要:监控节点变化. 重新绘制节点
-watch(() => props.flowNode, (newValue) => {  
-  currentNode.value = newValue;  
-}
-);
+watch(
+  () => props.flowNode,
+  (newValue) => {
+    currentNode.value = newValue
+  }
+)
 
 const handleModelValueUpdate = (updateValue) => {
   console.log('Process Node Tree handleModelValueUpdate', updateValue)
-  emits('update:flowNode', updateValue);
-} 
-</script>
-<style lang='scss' scoped>
+  emits('update:flowNode', updateValue)
+}
 
-</style>
+const findFromParentNode = (
+  nodeList: SimpleFlowNode[],
+  nodeType: number
+) => {
+  emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
+}
+
+// 递归从父节点中查询匹配的节点
+const recursiveFindParentNode = (
+  nodeList: SimpleFlowNode[],
+  findNode: SimpleFlowNode,
+  nodeType: number
+) => {
+  if (!findNode || findNode.type === NodeType.START_EVENT_NODE) {
+    return
+  }
+
+  if (findNode.type === nodeType) {
+    nodeList.push(findNode)
+  }
+  emits('find:recursiveFindParentNode', nodeList, props.parentNode, nodeType)
+}
+</script>
+<style lang="scss" scoped></style>

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

@@ -15,7 +15,7 @@
         </div>
       </div>
       <div class="scale-container" :style="`transform: scale(${scaleValue / 100});`">
-        <ProcessNodeTree v-if="processNodeTree" v-model:flow-node="processNodeTree" />
+        <ProcessNodeTree v-if="processNodeTree"  v-model:flow-node="processNodeTree" />
       </div>
     </div>
     <Dialog v-model="errorDialogVisible" title="保存失败" width="400" :fullscreen="false">
@@ -55,6 +55,18 @@ const processNodeTree = ref<SimpleFlowNode>({
   }
 })
 
+// const rootNode = ref<SimpleFlowNode>({
+//   name: '开始',
+//   type: NodeType.START_EVENT_NODE,
+//   id: 'StartEvent_1'
+// })
+
+// const childNode = ref<SimpleFlowNode>({
+//   id: 'EndEvent_1',
+//   name: '结束',
+//   type: NodeType.END_EVENT_NODE
+// })
+
 const errorDialogVisible = ref(false)
 let errorNodes: SimpleFlowNode[] = []
 const saveSimpleFlowModel = async () => {
@@ -148,6 +160,8 @@ onMounted(async () => {
   if (result) {
     console.log('the result is ', result)
     processNodeTree.value = result
+    // rootNode.value = result
+    // childNode.value = result.childNode
   }
 })
 </script>

+ 15 - 6
src/components/SimpleProcessDesignerV2/src/consts.ts

@@ -62,6 +62,17 @@ export enum TimeUnitType {
   DAY = 3
 }
 
+export enum RejectHandlerType {
+  /**
+   * 结束流程
+   */
+  TERMINATION = 1,
+  /**
+   * 驳回到指定节点
+   */
+  RETURN_PRE_USER_TASK = 2
+}
+
 // 条件配置类型 ( 用于条件节点配置 )
 export enum ConditionConfigType  {
 
@@ -186,12 +197,6 @@ NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
 NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
 NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
 
-export const TIME_UNIT_MAP = new Map<number,string>()
-NODE_DEFAULT_NAME.set(1, 'M')
-NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
-NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
-
-
 export const APPROVE_METHODS: DictDataVO [] = [
   { label: '单人审批', value: 1 },
   { label: '多人会签(需所有审批人同意)', value: 2 },
@@ -216,6 +221,10 @@ export const TIMEOUT_HANDLER_ACTION_TYPES: DictDataVO [] = [
   { label: '自动同意', value: 2 },
   { label: '自动拒绝', value: 3 },
 ]
+export const REJECT_HANDLER_TYPES: DictDataVO [] = [
+  { label: '结束流程', value: RejectHandlerType.TERMINATION },
+  { label: '驳回到指定节点', value: RejectHandlerType.RETURN_PRE_USER_TASK }
+]
 
 // 比较运算符
 export const COMPARISON_OPERATORS : DictDataVO = [

+ 45 - 5
src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue

@@ -131,7 +131,6 @@
                 />
               </el-select>
             </el-form-item>
-
             <el-form-item
               v-if="currentNode.attributes.candidateStrategy === CandidateStrategy.EXPRESSION"
               label="流程表达式"
@@ -144,7 +143,6 @@
                 style="width: 100%"
               />
             </el-form-item>
-
             <el-form-item label="审批方式" prop="approveMethod">
               <el-radio-group v-model="currentNode.attributes.approveMethod">
                 <div class="flex-col">
@@ -163,8 +161,35 @@
                 </div>
               </el-radio-group>
             </el-form-item>
-
-            <el-form-item label="超时处理" prop="timeoutHandlerEnable">
+            <el-divider content-position="left">审批人拒绝时</el-divider>
+            <el-form-item label="处理方式" prop="rejectHandler">
+              <el-radio-group v-model="currentNode.attributes.rejectHandler.type" @change="rejectHandlerTypeChange">
+                <el-radio
+                  :border="true"
+                  v-for="item in REJECT_HANDLER_TYPES"
+                  :key="item.value"
+                  :value="item.value"
+                  :label="item.label"
+                />
+              </el-radio-group>
+            </el-form-item>
+            
+            <el-form-item
+              v-if="currentNode.attributes.rejectHandler.type == RejectHandlerType.RETURN_PRE_USER_TASK"
+              label="驳回节点"
+              prop="rejectHandlerNode"
+            >
+              <el-select v-model="currentNode.attributes.rejectHandler.returnNodeId" clearable style="width: 100%">
+                <el-option
+                  v-for="item in returnTaskList"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </el-form-item>
+            <el-divider content-position="left">审批人超时未处理时</el-divider>
+            <el-form-item label="启用开关" prop="timeoutHandlerEnable">
               <el-switch
                 v-model="currentNode.attributes.timeoutHandler.enable"
                 active-text="开启"
@@ -281,8 +306,10 @@ import {
   NodeType,
   ApproveMethodType,
   TimeUnitType,
+  RejectHandlerType,
   TIMEOUT_HANDLER_ACTION_TYPES,
   TIME_UNIT_TYPES,
+  REJECT_HANDLER_TYPES,
   NODE_DEFAULT_NAME
 } from '../consts'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
@@ -303,6 +330,9 @@ const props = defineProps({
     required: true
   }
 })
+const emits = defineEmits<{
+  'find:returnTaskNodes': [nodeList: SimpleFlowNode[]]
+}>()
 
 const notAllowedMultiApprovers = ref(false)
 const currentNode = ref<SimpleFlowNode>(props.flowNode)
@@ -316,7 +346,7 @@ const deptTreeOptions = inject('deptTree') // 部门树
 const formType = inject('formType') // 表单类型
 const formFields = inject<Ref<string[]>>('formFields')
 const candidateParamArray = ref<any[]>([])
-
+const returnTaskList = ref<SimpleFlowNode[]>([])
 const closeDrawer = () => {
   settingVisible.value = false
 }
@@ -443,6 +473,10 @@ const setCurrentNode = (node: SimpleFlowNode) => {
     timeDuration.value = parseInt(parseTime)
     timeUnit.value = convertTimeUnit(parseTimeUnit)
   }
+  // 查找可以驳回的用户节点
+  const matchNodeList = [];
+  emits('find:returnTaskNodes', matchNodeList);
+  returnTaskList.value = matchNodeList;
 }
 
 defineExpose({ open, setCurrentNode }) // 暴露方法给父组件
@@ -483,6 +517,12 @@ const blurEvent = () => {
   currentNode.value.name =
     currentNode.value.name || (NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string)
 }
+const rejectHandlerTypeChange = () => {
+  if (currentNode.value.attributes?.rejectHandler.type === RejectHandlerType.RETURN_PRE_USER_TASK) {
+    
+    console.log('nodeList is {}', returnTaskList.value);
+  }
+}
 // 默认 6小时
 const timeDuration = ref(6)
 const timeUnit = ref(TimeUnitType.HOUR)

+ 27 - 3
src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue

@@ -57,7 +57,11 @@
         </div>
         <ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
         <!-- 递归显示子节点  -->
-        <ProcessNodeTree v-if="item && item.childNode" v-model:flow-node="item.childNode" />
+        <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" />
@@ -76,6 +80,10 @@ defineOptions({
   name: 'ExclusiveNode'
 })
 const props = defineProps({
+  // parentNode : {
+  //   type: Object as () => SimpleFlowNode,
+  //   required: true
+  // },
   flowNode: {
     type: Object as () => SimpleFlowNode,
     required: true
@@ -83,7 +91,9 @@ const props = defineProps({
 })
 // 定义事件,更新父组件
 const emits = defineEmits<{
-  'update:modelValue': [node: SimpleFlowNode | undefined]
+  '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)
@@ -156,7 +166,21 @@ const moveNode = (index: number, to: number) => {
   }
 
 }
-
+// 递归从父节点中查询匹配的节点
+const recursiveFindParentNode = (
+  nodeList: SimpleFlowNode[],
+  node: SimpleFlowNode,
+  nodeType: number
+) => {
+  if (!node || node.type === NodeType.START_EVENT_NODE) {
+    return
+  }
+  if (node.type === nodeType) {
+    nodeList.push(node)
+  }
+  // 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.EXCLUSIVE_NODE) 继续查找
+  emits('find:parentNode', nodeList, nodeType)
+}
 
 </script>
 

+ 10 - 1
src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue

@@ -38,6 +38,7 @@
     v-if="currentNode"
     ref="nodeSetting"
     :flow-node="currentNode"
+    @find:return-task-nodes="findReturnTaskNodes"
   />
 </template>
 <script setup lang="ts">
@@ -55,7 +56,8 @@ const props = defineProps({
   }
 })
 const emits = defineEmits<{
-  'update:modelValue': [node: SimpleFlowNode | undefined]
+  'update:modelValue': [node: SimpleFlowNode | undefined],
+  'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: NodeType]
 }>()
 
 const currentNode = ref<SimpleFlowNode>(props.flowNode)
@@ -106,5 +108,12 @@ const copyNode = () => {
   currentNode.value = newCopyNode
   emits('update:modelValue', currentNode.value)
 }
+// 查找可以驳回用户节点
+const findReturnTaskNodes = (
+  matchNodeList: SimpleFlowNode[], // 匹配的节点
+) => {
+  // 从父节点查找
+  emits('find:parentNode', matchNodeList, NodeType.USER_TASK_NODE);
+}
 </script>
 <style lang="scss" scoped></style>