Browse Source

【工作流】- 流程实例新界面修改

jason 10 months ago
parent
commit
27d24cd02d

+ 35 - 1
src/api/bpm/processInstance/index.ts

@@ -1,6 +1,6 @@
 import request from '@/config/axios'
 import { ProcessDefinitionVO } from '@/api/bpm/model'
-
+import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
 export type Task = {
   id: string
   name: string
@@ -22,6 +22,35 @@ export type ProcessInstanceVO = {
   processDefinition?: ProcessDefinitionVO
 }
 
+// 用户信息
+export type User = {
+  id: number,
+  nickname: string,
+  avatar: string
+}
+
+// 审批任务信息
+export type ApprovalTaskInfo = {
+  id: number,
+  ownerUser: User,
+  assigneeUser: User,
+  status: number,
+  reason: string
+
+}
+
+// 审批节点信息
+export type ApprovalNodeInfo = {
+  id : number
+  name: string
+  nodeType: NodeType
+  status: number
+  startTime?: Date
+  endTime?: Date
+  candidateUserList?: User[]
+  tasks: ApprovalTaskInfo[]
+}
+
 export const getProcessInstanceMyPage = async (params: any) => {
   return await request.get({ url: '/bpm/process-instance/my-page', params })
 }
@@ -57,3 +86,8 @@ export const getProcessInstance = async (id: string) => {
 export const getProcessInstanceCopyPage = async (params: any) => {
   return await request.get({ url: '/bpm/process-instance/copy/page', params })
 }
+
+export const getApprovalDetail = async (processInstanceId?:string, processDefinitionId?:string) => {
+  const param = processInstanceId ? '?processInstanceId='+ processInstanceId : '?processDefinitionId='+ processDefinitionId
+  return await request.get({ url: 'bpm/process-instance/get-approval-detail'+ param })
+}

+ 46 - 0
src/api/bpm/task/index.ts

@@ -1,5 +1,51 @@
 import request from '@/config/axios'
 
+/**
+ * 任务状态枚举
+ */
+export enum TaskStatusEnum {
+  /**
+   * 未开始
+   */
+  NOT_START = -1,
+
+   /**
+   * 待审批
+   */
+   WAIT = 0,
+  /**
+   * 审批中
+   */
+  RUNNING = 1,
+  /**
+   * 审批通过
+   */
+  APPROVE = 2,
+
+  /**
+   * 审批不通过
+   */
+  REJECT = 3,
+  
+  /**
+   * 已取消
+   */
+  CANCEL = 4,
+  /**
+   * 已退回
+   */
+  RETURN = 5,
+  /**
+   * 委派中
+   */
+  DELEGATE = 6,
+  /**
+   * 审批通过中
+   */
+  APPROVING = 7,
+
+}
+
 export type TaskVO = {
   id: number
 }

+ 46 - 37
src/components/SimpleProcessDesignerV2/src/SimpleProcessDesigner.vue

@@ -1,6 +1,6 @@
 <template>
-  <div class="simple-flow-canvas">
-    <div class="simple-flow-container">
+  <div class="simple-flow-canvas" v-loading="loading">
+    <div class="simple-flow-container" >
       <div class="top-area-container">
         <div class="top-actions">
           <div class="canvas-control">
@@ -15,7 +15,10 @@
         </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">
@@ -46,6 +49,7 @@ import * as DeptApi from '@/api/system/dept'
 import * as PostApi from '@/api/system/post'
 import * as UserApi from '@/api/system/user'
 import * as UserGroupApi from '@/api/bpm/userGroup'
+import { fa } from 'element-plus/es/locale'
 defineOptions({
   name: 'SimpleProcessDesigner'
 })
@@ -56,7 +60,7 @@ const props = defineProps({
     required: true
   }
 })
-
+const loading = ref(true)
 const formFields = ref<string[]>([])
 const formType = ref(20)
 const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
@@ -163,44 +167,49 @@ const zoomIn = () => {
 }
 
 onMounted(async () => {
-  // 获取表单字段
-  const bpmnModel = await getModel(props.modelId)
-  if (bpmnModel) {
-    formType.value = bpmnModel.formType
-    if (formType.value === 10) {
-      const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
-      formFields.value = bpmnForm?.fields
+  try {
+    loading.value = true
+    // 获取表单字段
+    const bpmnModel = await getModel(props.modelId)
+    if (bpmnModel) {
+      formType.value = bpmnModel.formType
+      if (formType.value === 10) {
+        const bpmnForm = (await getForm(bpmnModel.formId)) as unknown as FormVO
+        formFields.value = bpmnForm?.fields
+      }
     }
-  }
-  // 获得角色列表
-  roleOptions.value = await RoleApi.getSimpleRoleList()
-  // 获得岗位列表
-  postOptions.value = await PostApi.getSimplePostList()
-  // 获得用户列表
-  userOptions.value = await UserApi.getSimpleUserList()
-  // 获得部门列表
-  deptOptions.value = await DeptApi.getSimpleDeptList()
+    // 获得角色列表
+    roleOptions.value = await RoleApi.getSimpleRoleList()
+    // 获得岗位列表
+    postOptions.value = await PostApi.getSimplePostList()
+    // 获得用户列表
+    userOptions.value = await UserApi.getSimpleUserList()
+    // 获得部门列表
+    deptOptions.value = await DeptApi.getSimpleDeptList()
 
-  deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
-  // 获取用户组列表
-  userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
+    deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id')
+    // 获取用户组列表
+    userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
 
-  // 获取 SIMPLE 设计器模型
-  const result = await getBpmSimpleModel(props.modelId)
-  if (result) {
-    processNodeTree.value = result
-  } else {
-    // 初始值
-    processNodeTree.value = {
-      name: '发起人',
-      type: NodeType.START_USER_NODE,
-      id: NodeId.START_USER_NODE_ID,
-      childNode: {
-        id: NodeId.END_EVENT_NODE_ID,
-        name: '结束',
-        type: NodeType.END_EVENT_NODE
+    // 获取 SIMPLE 设计器模型
+    const result = await getBpmSimpleModel(props.modelId)
+    if (result) {
+      processNodeTree.value = result
+    } else {
+      // 初始值
+      processNodeTree.value = {
+        name: '发起人',
+        type: NodeType.START_USER_NODE,
+        id: NodeId.START_USER_NODE_ID,
+        childNode: {
+          id: NodeId.END_EVENT_NODE_ID,
+          name: '结束',
+          type: NodeType.END_EVENT_NODE
+        }
       }
     }
+  } finally {
+    loading.value = false
   }
 })
 </script>

+ 1 - 1
src/router/modules/remaining.ts

@@ -292,7 +292,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
       },
       {
         path: 'process-instance/detail',
-        component: () => import('@/views/bpm/processInstance/detail/index.vue'),
+        component: () => import('@/views/bpm/processInstance/detail/index_new.vue'),
         name: 'BpmProcessInstanceDetail',
         meta: {
           noCache: true,

+ 4 - 2
src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue

@@ -1,6 +1,6 @@
 <template>
   <div
-    class="h-50px position-fixed bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
+    class="h-50px bottom-10 text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
   >
     <el-popover :visible="passVisible" placement="top-end" :width="500" trigger="click">
       <template #reference>
@@ -120,6 +120,7 @@
     <div @click="handleSign"> <Icon :size="14" icon="ep:plus" />&nbsp;加签 </div>
     <div @click="handleBack"> <Icon :size="14" icon="fa:mail-reply" />&nbsp;退回 </div>
   </div>
+  <!-- </div> -->
   <!-- 弹窗:转派审批人 -->
   <TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
   <!-- 弹窗:回退节点 -->
@@ -299,10 +300,11 @@ defineExpose({ loadRunningTask })
 
 .btn-container {
   > div {
+    display: flex;
     margin: 0 15px;
     cursor: pointer;
-    display: flex;
     align-items: center;
+
     &:hover {
       color: #6db5ff;
     }

+ 249 - 33
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue

@@ -1,9 +1,94 @@
 <template>
   <el-timeline class="pt-20px">
-    <el-timeline-item v-for="(activity, index) in mockData" :key="index" size="large">
+    <el-timeline-item
+      v-for="(activity, index) in approveNodes"
+      :key="index"
+      size="large"
+      :icon="getApprovalNodeIcon(activity.status, activity.nodeType)"
+      :color="getApprovalNodeColor(activity.status)"
+    >
       <div class="flex flex-col items-start">
         <div class="font-bold"> {{ activity.name }}</div>
-        <div class="color-#a1a6ae text-12px mb-10px"> {{ activity.assigneeUser.nickname }}</div>
+        <div class="flex items-center mt-1">
+          <div v-for="(task, idx) in activity.tasks" :key="idx" class="flex items-center">
+            <div class="flex items-center flex-col pr-2">
+              <div class="position-relative" v-if="task.assigneeUser || task.ownerUser">
+                <el-avatar
+                  :size="36"
+                  v-if="task.assigneeUser && task.assigneeUser.avatar"
+                  :src="task.assigneeUser.avatar"
+                />
+                <el-avatar v-else-if="task.assigneeUser && task.assigneeUser.nickname">
+                  {{ task.assigneeUser.nickname.substring(0, 1) }}</el-avatar
+                >
+                <el-avatar
+                  v-else-if="task.ownerUser && task.ownerUser.avatar"
+                  :src="task.ownerUser.avatar"
+                />
+                <el-avatar v-else-if="task.ownerUser && task.ownerUser.nickname">
+                  {{ task.ownerUser.nickname.substring(0, 1) }}</el-avatar
+                >
+                <div 
+                  class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
+                >
+                  <Icon
+                    :size="12"
+                    :icon="statusIconMap2[task.status]?.icon"
+                    :color="statusIconMap2[task.status]?.color"
+                  />
+                </div>
+              </div>
+              <div class="flex flex-col mt-1">
+                <div
+                  v-if="task.assigneeUser && task.assigneeUser.nickname"
+                  class="text-10px text-align-center"
+                  >{{ task.assigneeUser.nickname }}</div
+                >
+                <div
+                  v-else-if="task.ownerUser && task.ownerUser.nickname"
+                  class="text-10px text-align-center"
+                >
+                  {{ task.ownerUser.nickname }}</div
+                >
+                <!-- <div v-if="task.reason" :title="task.reason" class="text-13px text-truncate w-150px mt-1"> 审批意见: {{ task.reason }}</div> -->
+              </div>
+            </div>
+          </div>
+          <div
+            v-for="(user, idx1) in activity.candidateUserList"
+            :key="idx1"
+            class="flex items-center"
+          >
+            <div class="flex items-center flex-col pr-2">
+              <div class="position-relative">
+                <el-avatar :size="36" v-if="user.avatar" :src="user.avatar" />
+                <el-avatar v-else-if="user.nickname && user.nickname">
+                  {{ user.nickname.substring(0, 1) }}</el-avatar
+                >
+                <div
+                  class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
+                >
+                  <Icon
+                    :size="12"
+                    :icon="statusIconMap2['-1']?.icon"
+                    :color="statusIconMap2['-1']?.color"
+                  />
+                </div>
+              </div>
+              <div class="flex flex-col mt-1">
+                <div v-if="user.nickname" class="text-10px text-align-center">{{
+                  user.nickname
+                }}</div>
+                <!-- <div v-if="task.reason" :title="task.reason" class="text-13px text-truncate w-150px mt-1"> 审批意见: {{ task.reason }}</div> -->
+              </div>
+            </div>
+          </div>
+        </div>
+        <div v-if="activity.status !== TaskStatusEnum.NOT_START" class="text-#a5a5a5 text-13px mt-1">
+          {{ getApprovalNodeTime(activity) }}
+        </div>
+        
+        <!-- <div class="color-#a1a6ae text-12px mb-10px"> {{ activity.assigneeUser.nickname }}</div>
         <div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
           <div class="mb-5px">审批意见:</div>
           <div
@@ -14,50 +99,113 @@
         </div>
         <div v-if="activity.createTime" class="text-#a5a5a5 text-13px">
           {{ formatDate(activity.createTime) }}
-        </div>
+        </div> -->
       </div>
-      <!-- 该节点用户的头像 -->
-      <template #dot>
-        <div class="w-35px h-35px position-relative">
-          <img
-            src="@/assets/imgs/avatar.jpg"
-            class="rounded-full w-full h-full position-absolute bottom-6px right-12px"
-            alt=""
-          />
-          <div
-            class="position-absolute top-16px left-8px bg-#fff rounded-full flex items-center content-center p-2px"
-          >
-            <Icon
-              :size="12"
-              :icon="optIconMap[activity.status]?.icon"
-              :color="optIconMap[activity.status]?.color"
-            />
-          </div>
-        </div>
-      </template>
     </el-timeline-item>
   </el-timeline>
 </template>
 
 <script lang="ts" setup>
 import { formatDate } from '@/utils/formatTime'
-import { propTypes } from '@/utils/propTypes'
-
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import { TaskStatusEnum } from '@/api/bpm/task'
+import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
+import { Check, Close, Loading, Clock, Minus, Delete } from '@element-plus/icons-vue'
 defineOptions({ name: 'BpmProcessInstanceTimeline' })
-defineProps({
-  tasks: propTypes.array // 流程任务的数组
+const props = defineProps({
+  // 流程实例编号
+  processInstanceId: {
+    type: String,
+    required: false,
+    default: ''
+  },
+  // 流程定义编号
+  processDefinitionId: {
+    type: String,
+    required: false,
+    default: ''
+  }
 })
 
-const optIconMap = {
+// 审批节点
+const approveNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
+
+const statusIconMap2 = {
+  // 未开始
+  '-1': { color: '#e5e7ec', icon: 'ep-clock' },
+  // 待审批
+  '0': { color: '#e5e7ec', icon: 'ep:loading' },
   // 审批中
-  '1': {
-    color: '#00b32a',
-    icon: 'fa-solid:clock'
-  },
+  '1': { color: '#448ef7', icon: 'ep:loading'},
   // 审批通过
-  '2': { color: '#00b32a', icon: 'fa-solid:check-circle' },
+  '2': { color: '#00b32a', icon: 'ep:circle-check-filled' },
   // 审批不通过
-  '3': { color: '#f46b6c', icon: 'fa-solid:times-circle' }
+  '3': { color: '#f46b6c', icon: 'fa-solid:times-circle' },
+  // 取消
+  '4': { color: '#cccccc', icon: 'ep:delete-filled' },
+  // 回退
+  '5': { color: '#f46b6c', icon: 'ep:remove-filled' },
+  // 委派中
+  '6': { color: '#448ef7', icon: 'ep:loading' },
+  // 审批通过中
+  '7': { color: '#00b32a', icon: 'ep:circle-check-filled' },
+ 
+}
+
+const statusIconMap = {
+  // 审批未开始
+  '-1': { color: '#e5e7ec', icon: Clock },
+  '0': { color: '#e5e7ec', icon: Clock },
+  // 审批中
+  '1': { color: '#448ef7', icon: Loading },
+  // 审批通过
+  '2': { color: '#00b32a', icon: Check },
+  // 审批不通过
+  '3': { color: '#f46b6c', icon: Close },
+  // 已取消
+  '4': { color: '#cccccc', icon: Delete },
+  // 回退
+  '5' : { color: '#f46b6c', icon: Minus },
+  // 委派中
+  '6': { color: '#448ef7', icon: Loading },
+  // 审批通过中
+  '7': { color: '#00b32a', icon: Check },
+}
+
+/** 获得审批详情 */
+const getApprovalDetail = async () => {
+  const data = await ProcessInstanceApi.getApprovalDetail(
+    props.processInstanceId,
+    props.processDefinitionId
+  )
+  console.log('approveNodes is []', data)
+  approveNodes.value = data.approveNodes
+}
+
+const getApprovalNodeIcon = (taskStatus: number , nodeType: NodeType) => {
+
+  if(taskStatus == TaskStatusEnum.NOT_START) {
+    return statusIconMap[taskStatus]?.icon
+  }
+
+  if (nodeType === NodeType.START_USER_NODE || nodeType === NodeType.USER_TASK_NODE) {
+    return statusIconMap[taskStatus]?.icon
+  }
+
+}
+
+const getApprovalNodeColor = (taskStatus: number) => {
+  return statusIconMap[taskStatus]?.color
+}
+
+const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
+
+  if(node.endTime) {
+    return `结束时间:${formatDate(node.endTime)}`
+  }
+  if(node.startTime) {
+    return `创建时间:${formatDate(node.startTime)}`
+  }
 }
 
 const mockData: any = [
@@ -125,6 +273,70 @@ const mockData: any = [
     formFields: null,
     formVariables: null
   },
+  {
+    id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
+    name: '财务总监审核',
+    createTime: 1725237646192,
+    endTime: null,
+    durationInMillis: null,
+    status: 3,
+    reason: null,
+    ownerUser: null,
+    assigneeUser: {
+      id: 104,
+      nickname: '财务总监',
+      deptId: 107,
+      deptName: '运维部门'
+    },
+    taskDefinitionKey: 'task-01',
+    processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
+    processInstance: {
+      id: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
+      name: 'oa_leave',
+      createTime: null,
+      processDefinitionId: 'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd',
+      startUser: null
+    },
+    parentTaskId: null,
+    children: null,
+    formId: null,
+    formName: null,
+    formConf: null,
+    formFields: null,
+    formVariables: null
+  },
+  {
+    id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
+    name: '领导审批',
+    createTime: 1725237646192,
+    endTime: null,
+    durationInMillis: null,
+    status: 2,
+    reason: null,
+    ownerUser: null,
+    assigneeUser: {
+      id: 104,
+      nickname: '领导',
+      deptId: 107,
+      deptName: '运维部门'
+    },
+    taskDefinitionKey: 'task-01',
+    processInstanceId: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
+    processInstance: {
+      id: 'fe0c60c6-68c3-11ef-9c7d-00a6181404fd',
+      name: 'oa_leave',
+      createTime: null,
+      processDefinitionId: 'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd',
+      startUser: null
+    },
+    parentTaskId: null,
+    children: null,
+    formId: null,
+    formName: null,
+    formConf: null,
+    formFields: null,
+    formVariables: null
+  },
   {
     id: 'fe1190ee-68c3-11ef-9c7d-00a6181404fd',
     name: '财务总监审核',
@@ -158,4 +370,8 @@ const mockData: any = [
     formVariables: null
   }
 ]
+
+onMounted(async () => {
+  getApprovalDetail()
+})
 </script>

+ 117 - 78
src/views/bpm/processInstance/detail/index_new.vue

@@ -1,88 +1,104 @@
 <template>
   <ContentWrap :bodyStyle="{ padding: '10px 20px' }" class="position-relative">
-    <img
-      class="position-absolute right-20px"
-      width="150"
-      :src="auditIcons[processInstance.status]"
-      alt=""
-    />
-    <div class="text-#878c93">编号:{{ id }}</div>
-    <el-divider class="!my-8px" />
-    <div class="flex items-center gap-5 mb-10px">
-      <div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
-      <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
-    </div>
+    <div class="processInstance-wrap-main">
+      <el-scrollbar>
+        <img
+          class="position-absolute right-20px"
+          width="150"
+          :src="auditIcons[processInstance.status]"
+          alt=""
+        />
+        <div class="text-#878c93 h-15px">编号:{{ id }}</div>
+        <el-divider class="!my-8px" />
+        <div class="flex items-center gap-5 mb-10px h-40px">
+          <div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
+          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="processInstance.status" />
+        </div>
 
-    <div class="flex items-center gap-5 mb-10px text-13px">
-      <div class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600">
-        <img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
-        {{ processInstance?.startUser?.nickname }}
-      </div>
-      <div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
-    </div>
+        <div class="flex items-center gap-5 mb-10px text-13px h-35px">
+          <div
+            class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
+          >
+            <img class="rounded-full h-28px" src="@/assets/imgs/avatar.jpg" alt="" />
+            {{ processInstance?.startUser?.nickname }}
+          </div>
+          <div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
+        </div>
 
-    <el-tabs>
-      <!-- 表单信息 -->
-      <el-tab-pane label="表单信息">
-        <el-row :gutter="10">
-          <el-col :span="18" class="!flex !flex-col formCol">
-            <!-- 表单信息 -->
-            <div v-loading="processInstanceLoading" class="form-box flex flex-col mb-30px flex-1">
-              <!-- 情况一:流程表单 -->
-              <el-col
-                v-if="processInstance?.processDefinition?.formType === 10"
-                :offset="6"
-                :span="16"
-              >
-                <form-create
-                  v-model="detailForm.value"
-                  v-model:api="fApi"
-                  :option="detailForm.option"
-                  :rule="detailForm.rule"
-                />
-              </el-col>
-              <!-- 情况二:业务表单 -->
-              <div v-if="processInstance?.processDefinition?.formType === 20">
-                <BusinessFormComponent :id="processInstance.businessKey" />
-              </div>
+        <el-tabs v-model="activeTab">
+          <!-- 表单信息 -->
+          <el-tab-pane label="表单信息" name="form">
+            <div class="form-scoll-area">
+              <el-scrollbar>
+                <el-row :gutter="10">
+                  <el-col :span="18" class="!flex !flex-col formCol">
+                    <!-- 表单信息 -->
+                    <div
+                      v-loading="processInstanceLoading"
+                      class="form-box flex flex-col mb-30px flex-1"
+                    >
+                      <!-- 情况一:流程表单 -->
+                      <el-col
+                        v-if="processInstance?.processDefinition?.formType === 10"
+                        :offset="6"
+                        :span="16"
+                      >
+                        <form-create
+                          v-model="detailForm.value"
+                          v-model:api="fApi"
+                          :option="detailForm.option"
+                          :rule="detailForm.rule"
+                        />
+                      </el-col>
+                      <!-- 情况二:业务表单 -->
+                      <div v-if="processInstance?.processDefinition?.formType === 20">
+                        <BusinessFormComponent :id="processInstance.businessKey" />
+                      </div>
+                    </div>
+                  </el-col>
+                  <el-col :span="6">
+                    <!-- 审批记录时间线 -->
+                    <ProcessInstanceTimeline :process-instance-id="id" />
+                  </el-col>
+                </el-row>
+              </el-scrollbar>
             </div>
+          </el-tab-pane>
 
-            <!-- 操作栏按钮 -->
-            <ProcessInstanceOperationButton
-              ref="operationButtonRef"
-              :processInstance="processInstance"
-              :userOptions="userOptions"
-              @success="getDetail"
+          <!-- 流程图 -->
+          <el-tab-pane label="流程图" name="diagram">
+            <ProcessInstanceBpmnViewer
+              :id="`${id}`"
+              :bpmn-xml="bpmnXml"
+              :loading="processInstanceLoading"
+              :process-instance="processInstance"
+              :tasks="tasks"
             />
-          </el-col>
-          <el-col :span="6">
-            <!-- 审批记录时间线 -->
-            <ProcessInstanceTimeline :process-instance="processInstance" :tasks="tasks" />
-          </el-col>
-        </el-row>
-      </el-tab-pane>
-      <!-- 流程图 -->
-      <el-tab-pane label="流程图">
-        <ProcessInstanceBpmnViewer
-          :id="`${id}`"
-          :bpmn-xml="bpmnXml"
-          :loading="processInstanceLoading"
-          :process-instance="processInstance"
-          :tasks="tasks"
-        />
-      </el-tab-pane>
-      <!-- 流转记录 -->
-      <el-tab-pane label="流转记录">
-        <ProcessInstanceTaskList
-          :loading="tasksLoad"
-          :process-instance="processInstance"
-          :tasks="tasks"
-          @refresh="getTaskList"
-        />
-      </el-tab-pane>
-      <!-- 流转评论 -->
-      <el-tab-pane label="流转评论"> 流转评论 </el-tab-pane>
-    </el-tabs>
+          </el-tab-pane>
+          <!-- 流转记录 -->
+          <el-tab-pane label="流转记录" name="record">
+            <ProcessInstanceTaskList
+              :loading="tasksLoad"
+              :process-instance="processInstance"
+              :tasks="tasks"
+              @refresh="getTaskList"
+            />
+          </el-tab-pane>
+          <!-- 流转评论 -->
+          <el-tab-pane label="流转评论" name="comment"> 流转评论 </el-tab-pane>
+        </el-tabs>
+
+        <div class=" b-t-solid border-t-1px border-[var(--el-border-color)]" v-if="activeTab === 'form'">
+          <!-- 操作栏按钮 -->
+          <ProcessInstanceOperationButton
+            ref="operationButtonRef"
+            :processInstance="processInstance"
+            :userOptions="userOptions"
+            @success="getDetail"
+          />
+        </div>
+      </el-scrollbar>
+    </div>
   </ContentWrap>
 </template>
 <script lang="ts" setup>
@@ -209,6 +225,9 @@ const getTaskList = async () => {
   }
 }
 
+/** 当前的Tab */
+const activeTab = ref('form')
+
 /** 初始化 */
 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
 onMounted(async () => {
@@ -219,9 +238,29 @@ onMounted(async () => {
 </script>
 
 <style lang="scss" scoped>
+$wrap-padding-height: 30px ;
+$wrap-margin-height: 15px;
+$button-height: 51px;
+$process-header-height: 194px;
+
+.processInstance-wrap-main {
+  height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px);
+  max-height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px);
+  overflow: auto;
+
+  .form-scoll-area {
+    height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px - $process-header-height - 40px);
+    max-height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 45px - $process-header-height - 40px);
+    overflow: auto;
+  }
+}
+
 .form-box {
   :deep(.el-card) {
     border: none;
   }
 }
+
+
+
 </style>