Procházet zdrojové kódy

Merge branch 'feature/bpm' of https://github.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm

YunaiV před 8 měsíci
rodič
revize
397c09a195

+ 4 - 2
src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue

@@ -162,8 +162,10 @@ const getApprovalDetail = async (row: any) => {
     startUserSelectTasks.value = data.activityNodes?.filter(
       (node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
     )
-    for (const node of startUserSelectTasks.value) {
-      startUserSelectAssignees.value[node.id] = []
+    if (startUserSelectTasks.value?.length > 0) {
+      for (const node of startUserSelectTasks.value) {
+        startUserSelectAssignees.value[node.id] = []
+      }
     }
 
     // 获取审批节点,显示 Timeline 的数据

+ 59 - 6
src/views/bpm/processInstance/create/index.vue

@@ -22,7 +22,7 @@
         <el-col :span="5">
           <div class="flex flex-col">
             <div
-              v-for="category in categoryList"
+              v-for="category in availableCategories"
               :key="category.code"
               class="flex items-center p-10px cursor-pointer text-14px rounded-md"
               :class="categoryActive.code === category.code ? 'text-#3e7bff bg-#e8eeff' : ''"
@@ -33,7 +33,7 @@
           </div>
         </el-col>
         <el-col :span="19">
-          <el-scrollbar ref="scrollWrapper" height="700">
+          <el-scrollbar ref="scrollWrapper" height="700" @scroll="handleScroll">
             <div
               class="mb-20px pl-10px"
               v-for="(definitions, categoryCode) in processDefinitionGroup"
@@ -137,10 +137,6 @@ const getCategoryList = async () => {
   try {
     // 流程分类
     categoryList.value = await CategoryApi.getCategorySimpleList()
-    // 选中首个分类
-    if (categoryList.value.length > 0) {
-      categoryActive.value = categoryList.value[0]
-    }
   } finally {
   }
 }
@@ -154,6 +150,11 @@ const getProcessDefinitionList = async () => {
     })
     // 初始化过滤列表为全部流程定义
     filteredProcessDefinitionList.value = processDefinitionList.value
+
+    // 在获取完所有数据后,设置第一个有效分类为激活状态
+    if (availableCategories.value.length > 0 && !categoryActive.value?.code) {
+      categoryActive.value = availableCategories.value[0]
+    }
   } finally {
   }
 }
@@ -220,10 +221,62 @@ const handleSelect = async (row, formVariables?) => {
   processDefinitionDetailRef.value?.initProcessInfo(row, formVariables)
 }
 
+/** 处理滚动事件 */
+const handleScroll = (e) => {
+  // 直接使用事件对象获取滚动位置
+  const scrollTop = e.scrollTop
+
+  // 获取所有分类区域的位置信息
+  const categoryPositions = categoryList.value
+    .map((category) => {
+      const categoryRef = proxy.$refs[`category-${category.code}`]
+      if (categoryRef?.[0]) {
+        return {
+          code: category.code,
+          offsetTop: categoryRef[0].offsetTop,
+          height: categoryRef[0].offsetHeight
+        }
+      }
+      return null
+    })
+    .filter(Boolean)
+
+  // 查找当前滚动位置对应的分类
+  let currentCategory = categoryPositions[0]
+  for (const position of categoryPositions) {
+    // 为了更好的用户体验,可以添加一个缓冲区域(比如 50px)
+    if (scrollTop >= position.offsetTop - 50) {
+      currentCategory = position
+    } else {
+      break
+    }
+  }
+
+  // 更新当前 active 的分类
+  if (currentCategory && categoryActive.value.code !== currentCategory.code) {
+    categoryActive.value = categoryList.value.find((c) => c.code === currentCategory.code)
+  }
+}
+
 /** 初始化 */
 onMounted(() => {
   getList()
 })
+
+/** 过滤出有流程的分类列表 */
+const availableCategories = computed(() => {
+  if (!categoryList.value?.length || !processDefinitionGroup.value) {
+    return []
+  }
+  
+  // 获取所有有流程的分类代码
+  const availableCategoryCodes = Object.keys(processDefinitionGroup.value)
+  
+  // 过滤出有流程的分类
+  return categoryList.value.filter(category => 
+    availableCategoryCodes.includes(category.code)
+  )
+})
 </script>
 
 <style lang="scss" scoped>

+ 14 - 6
src/views/bpm/processInstance/detail/ProcessInstanceBpmnViewer.vue

@@ -40,14 +40,22 @@ watch(
   }
 )
 </script>
-<style>
+<style lang="scss" scoped>
 .box-card {
+  height: 100%;
   width: 100%;
-  margin-bottom: 20px;
-}
+  margin-bottom: 0;
+
+  :deep(.el-card__body) {
+    height: 100%;
+    padding: 0;
+  }
 
-:deep(.process-viewer) {
-  height: 100% !important;
-  min-height: 500px;
+  :deep(.process-viewer) {
+    height: 100% !important;
+    min-height: 100%;
+    width: 100%;
+    overflow: auto;
+  }
 }
 </style>

+ 11 - 4
src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-loading="loading" class="mb-20px">
+  <div v-loading="loading" class="process-viewer-container">
     <SimpleProcessViewer
       :flow-node="simpleModel"
       :tasks="tasks"
@@ -154,8 +154,15 @@ const setSimpleModelNodeTaskStatus = (
 </script>
 
 <style lang="scss" scoped>
-:deep(.process-viewer) {
-  height: 100% !important;
-  min-height: 500px;
+.process-viewer-container {
+  height: 100%;
+  width: 100%;
+  
+  :deep(.process-viewer) {
+    height: 100% !important;
+    min-height: 100%;
+    width: 100%;
+    overflow: auto;
+  }
 }
 </style>

+ 6 - 6
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue

@@ -16,10 +16,10 @@
           <img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
           <div
             v-if="showStatusIcon"
-            class="position-absolute top-17px left-17px rounded-full flex items-center p-2px"
+            class="position-absolute top-17px left-17px rounded-full flex items-center p-1px border-2 border-white border-solid"
             :style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
           >
-            <el-icon :size="12" color="#fff">
+            <el-icon :size="11" color="#fff">
               <component :is="getApprovalNodeIcon(activity.status, activity.nodeType)" />
             </el-icon>
           </div>
@@ -106,10 +106,10 @@
                 <!-- 信息:任务 ICON -->
                 <div
                   v-if="showStatusIcon && onlyStatusIconShow.includes(task.status)"
-                  class="position-absolute top-19px left-23px rounded-full flex items-center p-2px"
+                  class="position-absolute top-19px left-23px rounded-full flex items-center p-1px border-2 border-white border-solid"
                   :style="{ backgroundColor: statusIconMap2[task.status]?.color }"
                 >
-                  <Icon :size="12" :icon="statusIconMap2[task.status]?.icon" color="#FFFFFF" />
+                  <Icon :size="11" :icon="statusIconMap2[task.status]?.icon" color="#FFFFFF" />
                 </div>
               </div>
             </div>
@@ -140,10 +140,10 @@
             <!-- 信息:任务 ICON -->
             <div
               v-if="showStatusIcon"
-              class="position-absolute top-19px left-23px rounded-full flex items-center p-2px"
+              class="position-absolute top-20px left-24px rounded-full flex items-center p-1px border-2 border-white border-solid"
               :style="{ backgroundColor: statusIconMap2['-1']?.color }"
             >
-              <Icon :size="12" :icon="statusIconMap2['-1']?.icon" color="#FFFFFF" />
+              <Icon :size="11" :icon="statusIconMap2['-1']?.icon" color="#FFFFFF" />
             </div>
           </div>
         </div>

+ 7 - 0
src/views/bpm/processInstance/detail/index.vue

@@ -128,6 +128,7 @@ import { formatDate } from '@/utils/formatTime'
 import { DICT_TYPE } from '@/utils/dict'
 import { BpmModelType } from '@/utils/constants'
 import { setConfAndFields2 } from '@/utils/formCreate'
+import { registerComponent } from '@/utils/routerHelper'
 import type { ApiAttrs } from '@form-create/element-ui/types/config'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 import * as UserApi from '@/api/system/user'
@@ -228,6 +229,9 @@ const getApprovalDetail = async () => {
           })
         }
       })
+    } else {
+      // 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
+      BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
     }
 
     // 获取审批节点,显示 Timeline 的数据
@@ -319,9 +323,12 @@ $process-header-height: 194px;
         $process-header-height - 40px
     );
     overflow: auto;
+    display: flex;
+    flex-direction: column;
 
     :deep(.box-card) {
       height: 100%;
+      flex: 1;
 
       .el-card__body {
         height: 100%;