소스 검색

feat: 自定义选择审批人逻辑TODO,选择用户弹窗绘制与选择交互逻辑处理

GoldenZqqq 9 달 전
부모
커밋
92314ee945

+ 92 - 0
src/components/UserSelectForm/index.vue

@@ -0,0 +1,92 @@
+<template>
+  <Dialog v-model="dialogVisible" title="人员选择" width="900">
+    <el-row>
+      <el-col :span="6">
+        <el-tree
+          ref="treeRef"
+          :data="deptList"
+          :expand-on-click-node="false"
+          :props="defaultProps"
+          default-expand-all
+          highlight-current
+          node-key="id"
+          @node-click="handleNodeClick"
+        />
+      </el-col>
+      <el-col :span="17" :offset="1">
+        <el-transfer
+          v-model="selectedUserList"
+          filterable
+          filter-placeholder="搜索成员"
+          :data="userList"
+          :props="{ label: 'nickname', key: 'id' }"
+        />
+      </el-col>
+    </el-row>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { defaultProps, handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+import * as UserApi from '@/api/system/user'
+
+defineOptions({ name: 'UserSelectForm' })
+const emit = defineEmits<{
+  confirm: [userList: any[]]
+}>()
+const { t } = useI18n() // 国际
+const deptList = ref<Tree[]>([]) // 部门树形结构化
+const userList: any = ref([]) // 用户列表
+const message = useMessage() // 消息弹窗
+const selectedUserList: any = ref([]) // 选中的用户列表
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+
+/** 打开弹窗 */
+const open = async () => {
+  resetForm()
+  deptList.value = handleTree(await DeptApi.getSimpleDeptList())
+  await getUserList()
+  // 修改时,设置数据
+  dialogVisible.value = true
+}
+
+/* 获取用户列表 */
+const getUserList = async (deptId?) => {
+  try {
+    // @ts-ignore
+    const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
+    userList.value = data.list
+  } finally {
+  }
+}
+
+const submitForm = async () => {
+  // 提交请求
+  formLoading.value = true
+  try {
+    message.success(t('common.updateSuccess'))
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('confirm', selectedUserList.value)
+  } finally {
+    formLoading.value = false
+  }
+}
+const resetForm = () => {
+  deptList.value = []
+  userList.value = []
+  selectedUserList.value = []
+}
+
+/** 处理部门被点击 */
+const handleNodeClick = async (row: { [key: string]: any }) => {
+  getUserList(row.id)
+}
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+</script>

+ 26 - 24
src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue

@@ -20,7 +20,7 @@
                       :option="detailForm.option"
                       @submit="submitForm"
                     >
-                      <template #type-startUserSelect>
+                      <!-- <template #type-startUserSelect>
                         <el-col :span="24">
                           <el-card class="mb-10px">
                             <template #header>指定审批人</template>
@@ -51,7 +51,7 @@
                             </el-form>
                           </el-card>
                         </el-col>
-                      </template>
+                      </template> -->
                     </form-create>
                   </el-col>
 
@@ -61,7 +61,7 @@
                       ref="timelineRef"
                       :activity-nodes="activityNodes"
                       :show-status-icon="false"
-                      candidateField="candidateUserList"
+                      :startUserSelectTasks="startUserSelectTasks"
                     />
                   </el-col>
                 </el-row>
@@ -104,7 +104,8 @@ import type { ApiAttrs } from '@form-create/element-ui/types/config'
 import { useTagsViewStore } from '@/store/modules/tagsView'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
 import * as DefinitionApi from '@/api/bpm/definition'
-import * as UserApi from '@/api/system/user'
+// import * as UserApi from '@/api/system/user'
+import { activityNodes as aN, startUserSelectTasks as sUs } from './mock'
 
 defineOptions({ name: 'ProcessDefinitionDetail' })
 const props = defineProps<{
@@ -125,7 +126,7 @@ const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表
 const startUserSelectTasks: any = ref([]) // 发起人需要选择审批人的用户任务列表
 const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
 const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
-const userList = ref<any[]>([]) // 用户列表
+// const userList = ref<any[]>([]) // 用户列表
 const bpmnXML: any = ref(null) // BPMN 数据
 /** 当前的Tab */
 const activeTab = ref('form')
@@ -163,25 +164,25 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
     if (processDefinitionDetail) {
       bpmnXML.value = processDefinitionDetail.bpmnXml
       startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
-
+      startUserSelectTasks.value = sUs
       // 设置指定审批人
-      if (startUserSelectTasks.value?.length > 0) {
-        detailForm.value.rule.push({
-          type: 'startUserSelect',
-          props: {
-            title: '指定审批人'
-          }
-        })
-        // 设置校验规则
-        for (const userTask of startUserSelectTasks.value) {
-          startUserSelectAssignees.value[userTask.id] = []
-          startUserSelectAssigneesFormRules.value[userTask.id] = [
-            { required: true, message: '请选择审批人', trigger: 'blur' }
-          ]
-        }
-        // 加载用户列表
-        userList.value = await UserApi.getSimpleUserList()
-      }
+      // if (startUserSelectTasks.value?.length > 0) {
+      //   detailForm.value.rule.push({
+      //     type: 'startUserSelect',
+      //     props: {
+      //       title: '指定审批人'
+      //     }
+      //   })
+      //   // 设置校验规则
+      //   for (const userTask of startUserSelectTasks.value) {
+      //     startUserSelectAssignees.value[userTask.id] = []
+      //     startUserSelectAssigneesFormRules.value[userTask.id] = [
+      //       { required: true, message: '请选择审批人', trigger: 'blur' }
+      //     ]
+      //   }
+      //   // 加载用户列表
+      //   userList.value = await UserApi.getSimpleUserList()
+      // }
     }
     // 情况二:业务表单
   } else if (row.formCustomCreatePath) {
@@ -205,6 +206,7 @@ const getApprovalDetail = async (row: any) => {
     }
     // 获取审批节点,显示 Timeline 的数据
     activityNodes.value = data.activityNodes
+    activityNodes.value = aN
   } finally {
   }
 }
@@ -250,7 +252,7 @@ defineExpose({ initProcessInfo })
 $wrap-padding-height: 20px;
 $wrap-margin-height: 15px;
 $button-height: 51px;
-$process-header-height: 194px;
+$process-header-height: 105px;
 
 .processInstance-wrap-main {
   height: calc(

+ 86 - 0
src/views/bpm/processInstance/create/mock.ts

@@ -0,0 +1,86 @@
+import {  ProcessInstanceVO, User, ApprovalTaskInfo, ApprovalNodeInfo } from '@/api/bpm/processInstance';
+import { NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
+
+const users: User[] = [
+  { id: 1, nickname: 'Alice', avatar: 'https://picsum.photos/200?r=1' },
+  { id: 2, nickname: 'Bob', avatar: 'https://picsum.photos/200?r=2' },
+  { id: 3, nickname: 'Charlie', avatar: 'https://picsum.photos/200?r=3' },
+  { id: 4, nickname: 'David', avatar: 'https://picsum.photos/200?r=4' }
+];
+
+const approvalTask1: ApprovalTaskInfo = {
+  id: 1,
+  ownerUser: users[0], // Alice is the owner (initiator)
+  assigneeUser: users[1], // Bob is the assignee
+  status: 1, // In Progress
+  reason: 'Please review and approve the request.'
+};
+
+const approvalTask2: ApprovalTaskInfo = {
+  id: 2,
+  ownerUser: users[1], // Bob is the owner (approver)
+  assigneeUser: users[2], // Charlie is the assignee
+  status: 0, // Pending approval
+  reason: 'Awaiting Bob’s decision.'
+};
+
+const approvalTask3: ApprovalTaskInfo = {
+  id: 3,
+  ownerUser: users[2], // Charlie is the owner (approver)
+  assigneeUser: users[3], // David is the assignee
+  status: 0, // Pending approval
+  reason: 'Awaiting Charlie’s decision.'
+};
+
+const approvalNode1: ApprovalNodeInfo = {
+  id: 101,
+  name: 'Start Review',
+  nodeType: NodeType.START_USER_NODE,
+  status: 1, // In Progress
+  startTime: new Date('2024-11-01T10:00:00Z'),
+  tasks: [approvalTask1]
+};
+
+const approvalNode2: ApprovalNodeInfo = {
+  id: 102,
+  name: 'First Review',
+  nodeType: NodeType.USER_TASK_NODE,
+  status: 0, // Pending approval
+  startTime: new Date('2024-11-01T11:00:00Z'),
+  tasks: [approvalTask2],
+  candidateUsers: [users[2], users[3]] // Candidate users: Charlie and David
+};
+
+const approvalNode3: ApprovalNodeInfo = {
+  id: 103,
+  name: 'Second Review',
+  nodeType: NodeType.USER_TASK_NODE,
+  status: 0, // Pending approval
+  startTime: new Date('2024-11-01T12:00:00Z'),
+  tasks: [approvalTask3],
+  candidateUsers: [users[1], users[3]] // Candidate users: Bob and David
+};
+
+const processInstance: ProcessInstanceVO = {
+  id: 1001,
+  name: 'Request Approval Process',
+  processDefinitionId: 'proc-2024-001',
+  category: 'Approval Process',
+  result: 0, // Ongoing
+  tasks: [{ id: '1', name: 'Start Review' }, { id: '2', name: 'First Review' }, { id: '3', name: 'Second Review' }],
+  fields: ['field1', 'field2'],
+  status: 1, // In Progress
+  remark: 'This is a sample approval process.',
+  businessKey: 'BUS-12345',
+  createTime: '2024-11-01T09:00:00Z',
+  endTime: '',
+  processDefinition: undefined // Not populated in this example
+};
+
+// 模拟的 activityNodes 数据,传递给 ProcessInstanceTimeline 组件
+const activityNodes: ApprovalNodeInfo[] = [approvalNode1, approvalNode2, approvalNode3];
+
+export { processInstance, activityNodes, users };
+
+
+export const startUserSelectTasks = users.map(user => ({id: user.id,name:user.nickname}))

+ 24 - 2
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue

@@ -36,7 +36,15 @@
             {{ getApprovalNodeTime(activity) }}
           </div>
         </div>
-        <div class="flex items-center flex-wrap mt-1 gap2">
+        <!-- 需要自定义选择审批人 -->
+        <div
+          v-if="startUserSelectTasks?.length > 0 && activity.nodeType === NodeType.USER_TASK_NODE"
+        >
+          <el-button class="!px-8px" @click="handleSelectUser">
+            <Icon icon="fa:user-plus" />
+          </el-button>
+        </div>
+        <div v-else class="flex items-center flex-wrap mt-1 gap2">
           <!-- 情况一:遍历每个审批节点下的【进行中】task 任务 -->
           <div v-for="(task, idx) in activity.tasks" :key="idx" class="flex flex-col pr-2 gap2">
             <div
@@ -121,6 +129,8 @@
       </div>
     </el-timeline-item>
   </el-timeline>
+  <!-- 用户选择弹窗 -->
+  <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
 </template>
 
 <script lang="ts" setup>
@@ -141,9 +151,11 @@ withDefaults(
   defineProps<{
     activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // 审批节点信息
     showStatusIcon?: boolean // 是否显示头像右下角状态图标
+    startUserSelectTasks?: any[] // 发起人需要选择审批人的用户任务列表
   }>(),
   {
-    showStatusIcon: true // 默认值为 true
+    showStatusIcon: true, // 默认值为 true
+    startUserSelectTasks: () => [] // 默认值为空数组
   }
 )
 
@@ -241,4 +253,14 @@ const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
     return `${formatDate(node.startTime)}`
   }
 }
+
+// 选择自定义审批人
+const userSelectFormRef = ref()
+const handleSelectUser = () => {
+  userSelectFormRef.value.open()
+}
+// 选择完成
+const handleUserSelectConfirm = (userList) => {
+  console.log('[ userList ] >', userList)
+}
 </script>