Ver código fonte

仿钉钉流程设计器- 优化节点配置

jason 1 ano atrás
pai
commit
22af2dc2d9

+ 33 - 12
src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue

@@ -7,9 +7,19 @@
     :before-close="saveConfig"
    >
    <template #header>
-      <div class="w-full flex flex-col">
-        <div class="mb-2 text-size-2xl">{{ currentNode.name }}</div>
-        <el-divider />
+      <div class="config-header">
+        <input
+          v-if="showInput"
+          type="text"
+          class="config-editable-input"
+          @blur="blurEvent()"
+          v-mountedFocus
+          v-model="currentNode.name"
+          :placeholder="currentNode.name"
+          />
+        <div v-else class="node-name" >{{ currentNode.name }} <Icon class="ml-1" icon="ep:edit-pen" :size="16" @click="clickIcon()"/></div>
+        
+        <div class="divide-line"></div>
       </div>
    </template>
     <div> 
@@ -65,6 +75,7 @@
 </template>
 <script setup lang="ts">
 import { SimpleFlowNode, CONDITION_CONFIG_TYPES } from '../consts'
+import { getDefaultConditionNodeName } from '../utils';
 defineOptions({
   name: 'ConditionNode'
 })
@@ -72,6 +83,10 @@ const props = defineProps({
   conditionNode: {
     type: Object as () => SimpleFlowNode,
     required: true
+  },
+  nodeIndex : {
+    type: Number,
+    required: true
   }
 })
 const settingVisible = ref(false)
@@ -81,11 +96,22 @@ const open = () => {
 
 watch(() => props.conditionNode, (newValue) => {  
   currentNode.value = newValue;  
-}); 
+});
+// 显示名称输入框
+const showInput = ref(false)
+
+const clickIcon = () => {
+  showInput.value = true;
+}
+// 输入框失去焦点
+const blurEvent = () => {
+  showInput.value = false
+  currentNode.value.name = currentNode.value.name || getDefaultConditionNodeName(props.nodeIndex, currentNode.value.attributes?.defaultFlow)
+}
 
 const currentNode = ref<SimpleFlowNode>(props.conditionNode)
-// TODO nodeInfo 测试
-defineExpose({ open, nodeInfo: currentNode }) // 提供 open 方法,用于打开弹窗
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
 // 关闭
 const closeDrawer = () => {
@@ -117,10 +143,5 @@ const changeConditionType = () => {
 </script>
 
 <style lang="scss" scoped>
-::v-deep(.el-divider--horizontal) {
-  display: block;
-  height: 1px;
-  margin: 0;
-  border-top: 1px var(--el-border-color) var(--el-border-style);
-}
+
 </style>

+ 31 - 10
src/components/SimpleProcessDesignerV2/src/nodes-config/CopyTaskNodeConfig.vue

@@ -7,9 +7,19 @@
     :before-close="saveConfig"
   >
     <template #header>
-      <div class="w-full flex flex-col">
-        <div class="mb-2 text-size-2xl">{{ currentNode.name }}</div>
-        <el-divider />
+      <div class="config-header">
+        <input
+          v-if="showInput"
+          type="text"
+          class="config-editable-input"
+          @blur="blurEvent()"
+          v-mountedFocus
+          v-model="currentNode.name"
+          :placeholder="currentNode.name"
+          />
+        <div v-else class="node-name" >{{ currentNode.name }} <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">
@@ -144,7 +154,7 @@
   </el-drawer>
 </template>
 <script setup lang='ts'>
-import { SimpleFlowNode, CandidateStrategy } from '../consts'
+import { SimpleFlowNode, CandidateStrategy,NodeType, NODE_DEFAULT_NAME } from '../consts'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import * as RoleApi from '@/api/system/role'
 import * as DeptApi from '@/api/system/dept'
@@ -282,6 +292,17 @@ const getShowText = () : string => {
   }
    return showText
 }
+// 显示名称输入框
+const showInput = ref(false)
+
+const clickIcon = () => {
+  showInput.value = true;
+}
+// 输入框失去焦点
+const blurEvent = () => {
+  showInput.value = false
+  currentNode.value.name = currentNode.value.name || NODE_DEFAULT_NAME.get(NodeType.COPY_TASK_NODE) as string
+}
 
 onMounted(async () => {
   console.log('candidateParam', currentNode.value.attributes?.candidateParam)
@@ -291,10 +312,10 @@ onMounted(async () => {
 </script>
 
 <style lang="scss" scoped>
-::v-deep(.el-divider--horizontal) {
-  display: block;
-  height: 1px;
-  margin: 0;
-  border-top: 1px var(--el-border-color) var(--el-border-style);
-}
+// ::v-deep(.el-divider--horizontal) {
+//   display: block;
+//   height: 1px;
+//   margin: 0;
+//   border-top: 1px var(--el-border-color) var(--el-border-style);
+// }
 </style>

+ 25 - 21
src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue

@@ -8,9 +8,19 @@
     class="justify-start"
   >
     <template #header>
-      <div class="w-full flex flex-col">
-        <div class="mb-2 text-size-2xl">{{ currentNode.name }}</div>
-        <el-divider />
+      <div class="config-header">
+        <input
+          v-if="showInput"
+          type="text"
+          class="config-editable-input"
+          @blur="blurEvent()"
+          v-mountedFocus
+          v-model="currentNode.name"
+          :placeholder="currentNode.name"
+          />
+        <div v-else class="node-name" >{{ currentNode.name }} <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">
@@ -162,7 +172,7 @@
 </template>
 
 <script setup lang="ts">
-import { SimpleFlowNode, APPROVE_METHODS,CandidateStrategy } from '../consts'
+import { SimpleFlowNode, APPROVE_METHODS, CandidateStrategy, NodeType, NODE_DEFAULT_NAME } from '../consts'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { defaultProps } from '@/utils/tree'
 import * as RoleApi from '@/api/system/role'
@@ -309,18 +319,18 @@ const changedCandidateUsers = () => {
     notAllowedMultiApprovers.value = false
   }
 }
+// 显示名称输入框
+const showInput = ref(false)
+
+const clickIcon = () => {
+  showInput.value = true;
+}
+// 节点名称输入框失去焦点
+const blurEvent = () => {
+  showInput.value = false
+  currentNode.value.name = currentNode.value.name || NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string
+}
 onMounted(async () => {
-  // 获得角色列表
-  // roleOptions.value = await RoleApi.getSimpleRoleList()
-  // postOptions.value = await PostApi.getSimplePostList()
-  // // 获得用户列表
-  // userOptions.value = await UserApi.getSimpleUserList()
-  // // 获得部门列表
-  // deptOptions = await DeptApi.getSimpleDeptList()
-  // deptTreeOptions.value = handleTree(deptOptions, 'id')
-  // // 获得用户组列表
-  // userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
-  console.log('candidateParam', currentNode.value.attributes?.candidateParam)
   candidateParamArray.value = currentNode.value.attributes?.candidateParam?.split(',').map(item=> +item)
   console.log('candidateParamArray.value', candidateParamArray.value)
   if (currentNode.value.attributes?.candidateStrategy === CandidateStrategy.USER && candidateParamArray.value?.length <= 1) {
@@ -332,10 +342,4 @@ onMounted(async () => {
 </script>
 
 <style lang="scss" scoped>
-::v-deep(.el-divider--horizontal) {
-  display: block;
-  height: 1px;
-  margin: 0;
-  border-top: 1px var(--el-border-color) var(--el-border-style);
-}
 </style>

+ 1 - 2
src/components/SimpleProcessDesignerV2/src/nodes/CopyTaskNode.vue

@@ -27,8 +27,7 @@
           <Icon icon="ep:arrow-right-bold" />
         </div>
         <div class="node-toolbar">
-          <!-- <div class="toolbar-icon"><Icon icon="ep:document-copy" @click="copyNode" /></div> -->
-          <div class="toolbar-icon"><Icon icon="ep:delete" :size="16"  @click="deleteNode" /></div>
+          <div class="toolbar-icon"><Icon  color="#0089ff"  icon="ep:circle-close-filled" :size="18"  @click="deleteNode" /></div>
         </div>
       </div>
 

+ 12 - 13
src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue

@@ -15,10 +15,13 @@
           <div class="node-container">
             <div class="node-box" :class="{ 'node-config-error': !item.showText }">
               <div class="branch-node-title-container">
-                <div class="branch-title" v-if="showInputs[index]">
+                <div  v-if="showInputs[index]">
                   <input
-type="text" class="input-max-width editable-title-input" @blur="blurEvent(index)"
-                    v-mountedFocus v-model="item.name" />
+                    type="text"
+                    class="input-max-width 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 class="branch-priority"> 优先级{{ index + 1 }} </div>
@@ -33,7 +36,7 @@ type="text" class="input-max-width editable-title-input" @blur="blurEvent(index)
               </div>
               <div class="node-toolbar" v-if="index + 1 !== currentNode.conditionNodes?.length">
                 <div class="toolbar-icon">
-                  <Icon icon="ep:delete" :size="16" @click="deleteCondition(index)" />
+                  <Icon color="#0089ff"  icon="ep:circle-close-filled" :size="18"  @click="deleteCondition(index)" />
                 </div>
               </div>
               <div 
@@ -52,7 +55,7 @@ type="text" class="input-max-width editable-title-input" @blur="blurEvent(index)
             <NodeHandler v-model:child-node="item.childNode" />
           </div>
         </div>
-        <ConditionNodeConfig :condition-node="item" :ref="item.id" />
+        <ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
         <!-- 递归显示子节点  -->
         <ProcessNodeTree v-if="item && item.childNode" v-model:flow-node="item.childNode" />
       </div>
@@ -65,6 +68,7 @@ type="text" class="input-max-width editable-title-input" @blur="blurEvent(index)
 import NodeHandler from '../NodeHandler.vue'
 import ProcessNodeTree from '../ProcessNodeTree.vue'
 import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
+import { getDefaultConditionNodeName } from '../utils'
 import { generateUUID } from '@/utils'
 import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
 const { proxy } = getCurrentInstance() as any
@@ -88,29 +92,24 @@ const currentNode = ref<SimpleFlowNode>(props.flowNode)
 watch(() => props.flowNode, (newValue) => {
   currentNode.value = newValue;
 });
-// TODO 测试后续去掉
-// watch(() => conditionNodes, (newValue) => {  
-//   console.log('new conditionNodes is ', newValue);
-// },{ deep: true }); 
+
 
 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 || '条件' + index
+  conditionNode.name = conditionNode.name || getDefaultConditionNodeName(index, conditionNode.attributes?.defaultFlow)
 }
+
 // 点击条件名称
 const clickEvent = (index: number) => {
   showInputs.value[index] = true
 }
 
 const conditionNodeConfig = (nodeId: string) => {
-  console.log('nodeId', nodeId);
   console.log("proxy.$refs", proxy.$refs);
-  // TODO 测试后续去掉
   const conditionNode = proxy.$refs[nodeId][0];
-  console.log("node inf is ", conditionNode.nodeInfo);
   conditionNode.open()
 }
 

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

@@ -27,8 +27,7 @@
           <Icon icon="ep:arrow-right-bold" />
         </div>
         <div class="node-toolbar">
-          <!-- <div class="toolbar-icon"><Icon icon="ep:document-copy" @click="copyNode" /></div> -->
-          <div class="toolbar-icon"><Icon icon="ep:delete"  :size="18" @click="deleteNode" /></div>
+          <div class="toolbar-icon"><Icon color="#0089ff"  icon="ep:circle-close-filled"  :size="18" @click="deleteNode" /></div>
         </div>
       </div>
       <!-- 传递子节点给添加节点组件。会在子节点前面添加节点 -->

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

@@ -0,0 +1,7 @@
+// 获取条件节点默认的名称
+export const getDefaultConditionNodeName = (index:number, defaultFlow: boolean) : string => {
+  if ( defaultFlow ){
+    return "其它情况"
+  }
+  return '条件' + (index+1)
+}

+ 45 - 3
src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss

@@ -91,7 +91,7 @@
         transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
 
         &:hover {
-          // border-color: #0089ff;
+          border-color: #0089ff;
           .node-toolbar {
             opacity: 1;
           }
@@ -130,6 +130,9 @@
             text-overflow: ellipsis;
             color: #1f1f1f;
             line-height: 18px;
+            &:hover {
+              border-bottom: 1px dashed #f60;
+            }
           }
         }
 
@@ -153,6 +156,9 @@
             overflow: hidden;
             text-overflow: ellipsis;
             color: #f60;
+            &:hover {
+              border-bottom: 1px dashed #000;
+            }
           }
 
           .branch-priority {
@@ -211,14 +217,13 @@
         .node-toolbar {
           opacity: 0;
           position: absolute;
-          top: -25px;
+          top: -20px;
           right: 0px;
           display: flex;
 
           .toolbar-icon {
             text-align: center;
             vertical-align: middle;
-            color:  #000;
           }
         }
 
@@ -501,10 +506,47 @@
       }
     }
   }
+}
+
+// 配置节点头部
+.config-header {
+  display: flex;
+  flex-direction: column;
 
+  .node-name {
+    display: flex;
+    height: 24px;
+    line-height: 24px;
+    cursor: pointer;
+    align-items: center;
+  }
 
+  .divide-line {
+    width: 100%;
+    height: 1px;
+    margin-top: 16px;
+    background: #eee;
+  }
+
+  .config-editable-input {
+    height: 24px;
+    max-width: 510px;
+    font-size: 16px;
+    line-height: 24px;
+    border: 1px solid #d9d9d9;
+    border-radius: 4px;
+    transition: all 0.3s;
+  
+    &:focus   {
+      border-color: #40a9ff;
+      outline: 0;
+      box-shadow: 0 0 0 2px rgba(24, 144, 255, .2)
+    }
+  }
 }
 
+
+
 // 节点连线气泡卡片样式
 .handler-item-wrapper {
   display: flex;