瀏覽代碼

feat: [bpm] 审批详情操作按钮

jason 3 月之前
父節點
當前提交
51c593f319

+ 2 - 0
src/api/bpm/processInstance/index.ts

@@ -1,3 +1,4 @@
+import type { Task } from '@/api/bpm/task'
 import type { PageParam, PageResult } from '@/http/types'
 import { http } from '@/http/http'
 
@@ -47,6 +48,7 @@ export interface ProcessInstance {
 export interface ApprovalDetail {
   processInstance: ProcessInstance
   processDefinition: ProcessDefinition
+  todoTask: Task
 }
 
 /** 抄送流程实例 */

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

@@ -11,6 +11,10 @@ export interface TaskUser {
   deptName?: string
 }
 
+export interface OperationButtonSetting {
+  displayName: string // 按钮名称
+  enable: boolean // 是否启用
+}
 /** 流程任务 */
 export interface Task {
   id: string
@@ -24,6 +28,8 @@ export interface Task {
   ownerUser?: TaskUser
   processInstanceId?: string // 流程实例 ID
   processInstance: ProcessInstance
+  reasonRequire?: boolean // 是否填写审批意见
+  buttonsSetting?: Record<number, OperationButtonSetting> // 按钮设置
 }
 
 /** 查询待办任务分页列表 */

+ 207 - 0
src/pages-bpm/processInstance/detail/components/operation-button.vue

@@ -0,0 +1,207 @@
+<!-- 操作按钮 -->
+<template>
+  <view class="yd-detail-footer">
+    <!-- 有待审批的任务 -->
+    <view v-if="runningTask">
+      <view v-if="leftOperations.length > 0" class="w-full flex items-center">
+        <!-- 左侧操作按钮 -->
+        <view v-for="(action, idx) in leftOperations" :key="idx" class="mr-32rpx w-60rpx flex flex-col items-center" @click="handleOperation(action.operationType)">
+          <wd-icon :name="action.iconName" size="40rpx" color="#1890ff" />
+          <text class="mt-4rpx text-22rpx text-[#333]">{{ action.displayName }}</text>
+        </view>
+        <!-- 更多操作按钮 -->
+        <view v-if="moreOperations.length > 0" class="mr-32rpx w-60rpx flex flex-col items-center" @click="handleShowMore">
+          <wd-icon name="ellipsis" size="40rpx" color="#1890ff" />
+          <text class="mt-4rpx text-22rpx text-[#333]">更多</text>
+        </view>
+        <!-- 右侧按钮,TODO 是否一定要保留两个按钮 -->
+        <view class="flex flex-1 gap-16rpx">
+          <wd-button
+            v-for="(action, idx) in rightOperations"
+            :key="idx"
+            :plain="action.plain"
+            :type="action.btnType"
+            :round="false"
+            class="flex-1"
+            custom-style="min-width: 200rpx; width: 200rpx;"
+            @click="handleOperation(action.operationType)"
+          >
+            {{ action.displayName }}
+          </wd-button>
+        </view>
+      </view>
+      <!-- 更多操作 ActionSheet -->
+      <wd-action-sheet v-if="moreOperations.length > 0" v-model="showMoreActions" :actions="moreOperations" title="请选择操作" @select="handleMoreAction" />
+    </view>
+    <!-- TODO 无待审批的任务 需要显示什么 -->
+  </view>
+</template>
+
+<script lang="ts" setup>
+import type { Action } from 'wot-design-uni/components/wd-action-sheet/types'
+import type { ButtonType } from 'wot-design-uni/components/wd-button/types'
+import type { Task } from '@/api/bpm/task'
+import { useToast } from 'wot-design-uni'
+import {
+  BpmTaskOperationButtonTypeEnum,
+  BpmTaskStatusEnum,
+  OPERATION_BUTTON_NAME,
+} from '@/utils/constants'
+
+const showMoreActions = ref(false)
+
+type MoreOperationType = Action & {
+  operationType: number
+}
+
+interface LeftOperationType {
+  operationType: number
+  iconName: string
+  displayName: string
+}
+
+interface RightOperationType {
+  operationType: number
+  btnType: ButtonType
+  displayName: string
+  plain: boolean
+}
+const operationIconsMap: Record<number, string> = {
+  [BpmTaskOperationButtonTypeEnum.TRANSFER]: 'transfer',
+  [BpmTaskOperationButtonTypeEnum.ADD_SIGN]: 'add',
+  [BpmTaskOperationButtonTypeEnum.DELEGATE]: 'share',
+  [BpmTaskOperationButtonTypeEnum.RETURN]: 'arrow-left',
+  [BpmTaskOperationButtonTypeEnum.COPY]: 'copy',
+}
+/** 左侧操作按钮 【最多两个】{转办, 委派, 退回, 加签, 抄送等} */
+const leftOperations = ref<LeftOperationType[]>([])
+
+/** 右侧操作按钮【最多两个】{通过,拒绝, 取消,减签} */
+const rightOperationTypes = []
+const rightOperations = ref<RightOperationType[]>([])
+/** 更多操作 */
+const moreOperations = ref<MoreOperationType[]>([])
+const toast = useToast()
+const runningTask = ref<Task>()
+const reasonRequire = ref<boolean>(false)
+
+/** 加载待办任务 */
+function loadTodoTask(task: Task) {
+  runningTask.value = task
+  if (task) {
+    reasonRequire.value = task.reasonRequire ?? false
+    // 右侧按钮
+    if (isHandleTaskStatus() && task.buttonsSetting[BpmTaskOperationButtonTypeEnum.REJECT]?.enable) {
+      rightOperationTypes.push(BpmTaskOperationButtonTypeEnum.REJECT)
+      rightOperations.value.push({
+        operationType: BpmTaskOperationButtonTypeEnum.REJECT,
+        displayName: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.REJECT),
+        btnType: 'error',
+        plain: true,
+      })
+    }
+    if (isHandleTaskStatus() && task.buttonsSetting[BpmTaskOperationButtonTypeEnum.APPROVE]?.enable) {
+      rightOperationTypes.push(BpmTaskOperationButtonTypeEnum.APPROVE)
+      rightOperations.value.push({
+        operationType: BpmTaskOperationButtonTypeEnum.APPROVE,
+        displayName: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.APPROVE),
+        btnType: 'primary',
+        plain: false,
+      })
+    }
+    // TODO 减签
+    // 左侧操作,和更多操作
+    Object.keys(task.buttonsSetting || {}).forEach((key) => {
+      const operationType = Number(key)
+      if (task.buttonsSetting[key].enable && isHandleTaskStatus()
+        && !rightOperationTypes.includes(operationType)) {
+        if (leftOperations.value.length >= 2) {
+          moreOperations.value.push({
+            name: getButtonDisplayName(operationType),
+            operationType,
+          })
+        } else {
+          leftOperations.value.push({
+            operationType,
+            iconName: operationIconsMap[operationType],
+            displayName: getButtonDisplayName(operationType),
+          })
+        }
+      }
+    })
+  }
+}
+/** 跳转到相应的操作页面 */
+function handleOperation(operationType: number) {
+  if (!runningTask.value) {
+    return
+  }
+  switch (operationType) {
+    case BpmTaskOperationButtonTypeEnum.APPROVE:
+      uni.navigateTo({ url: `/pages-bpm/processInstance/detail/audit/index?id=${runningTask.value.id}&pass=true` })
+      break
+    case BpmTaskOperationButtonTypeEnum.REJECT:
+      uni.navigateTo({ url: `/pages-bpm/processInstance/detail/audit/index?id=${runningTask.value.id}&pass=false` })
+      break
+    case BpmTaskOperationButtonTypeEnum.DELEGATE:
+      toast.show('委派功能待实现')
+      break
+    case BpmTaskOperationButtonTypeEnum.TRANSFER:
+      toast.show('转办功能待实现')
+      break
+    case BpmTaskOperationButtonTypeEnum.ADD_SIGN:
+      toast.show('加签功能待实现')
+      break
+    case BpmTaskOperationButtonTypeEnum.RETURN:
+      toast.show('退回功能待实现')
+      break
+  }
+}
+
+/** 显示更多操作 */
+function handleShowMore() {
+  showMoreActions.value = true
+}
+
+/** 处理更多操作选择 */
+function handleMoreAction(action: { item: MoreOperationType }) {
+  handleOperation(action.item.operationType)
+  showMoreActions.value = false
+}
+
+/** 获取按钮的显示名称 */
+function getButtonDisplayName(btnType: BpmTaskOperationButtonTypeEnum) {
+  let displayName = OPERATION_BUTTON_NAME.get(btnType)
+  if (
+    runningTask.value?.buttonsSetting
+    && runningTask.value?.buttonsSetting[btnType]
+  ) {
+    displayName = runningTask.value.buttonsSetting[btnType].displayName
+  }
+  return displayName
+}
+
+/** 是否显示按钮 */
+function isShowButton(btnType: BpmTaskOperationButtonTypeEnum): boolean {
+  let isShow = true
+  if (
+    runningTask.value?.buttonsSetting
+    && runningTask.value?.buttonsSetting[btnType]
+  ) {
+    isShow = runningTask.value.buttonsSetting[btnType].enable
+  }
+  return isShow
+}
+
+/** 任务是否为处理中状态 */
+function isHandleTaskStatus() {
+  let canHandle = false
+  if (BpmTaskStatusEnum.RUNNING === runningTask.value?.status) {
+    canHandle = true
+  }
+  return canHandle
+}
+
+/** 暴露方法 */
+defineExpose({ loadTodoTask })
+</script>

+ 20 - 51
src/pages-bpm/processInstance/detail/index.vue

@@ -88,29 +88,15 @@
 
     <!-- TODO 待开发:区域:流程评论 -->
 
-    <!-- 区域:底部操作栏 TODO @jason:抽成类似:/Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/processInstance/detail/modules/operation-button.vue -->
-    <view v-if="runningTask" class="yd-detail-footer">
-      <view class="yd-detail-footer-actions">
-        <wd-button type="error" plain class="flex-1" @click="handleReject">
-          拒绝
-        </wd-button>
-        <wd-button type="primary" class="flex-1" @click="handleApprove">
-          同意
-        </wd-button>
-      </view>
-      <!-- TODO @jason:审批通过、不通过:缺少签名、选择审批人 -->
-      <!-- TODO @jason:取消流程、重新发起 -->
-      <!-- TODO @jason:抄送、转派、委派、退回 -->
-      <!-- TODO @jason:加签、减签 -->
-    </view>
+    <!-- 区域:底部操作栏 -->
+    <ProcessInstanceOperationButton ref="operationButtonRef" />
   </view>
 </template>
 
 <script lang="ts" setup>
 import type { ProcessDefinition, ProcessInstance } from '@/api/bpm/processInstance'
 import type { Task } from '@/api/bpm/task'
-import { onLoad } from '@dcloudio/uni-app'
-import { computed, ref } from 'vue'
+import { computed, onMounted, ref } from 'vue'
 import { useToast } from 'wot-design-uni'
 import { getApprovalDetail } from '@/api/bpm/processInstance'
 import { getTaskListByProcessInstanceId } from '@/api/bpm/task'
@@ -118,6 +104,12 @@ import { useUserStore } from '@/store'
 import { navigateBackPlus } from '@/utils'
 import { formatDateTime, formatPast } from '@/utils/date'
 import FormDetail from './components/form-detail.vue'
+import ProcessInstanceOperationButton from './components/operation-button.vue'
+
+const props = defineProps<{
+  id: string // 流程实例的编号
+  taskId?: string // 任务编号
+}>()
 
 definePage({
   style: {
@@ -128,23 +120,13 @@ definePage({
 
 const userStore = useUserStore()
 const toast = useToast()
-const processInstanceId = ref('')
 const processInstance = ref<Partial<ProcessInstance>>({})
 const processDefinition = ref<Partial<ProcessDefinition>>({})
 const tasks = ref<Task[]>([])
 const orderAsc = ref(true)
 
-/** 当前用户需要处理的任务 */
-const runningTask = computed(() => {
-  return tasks.value.find((task) => {
-    // 待处理状态
-    if (task.status !== 1 && task.status !== 6) {
-      return false
-    }
-    // 当前用户是处理人
-    return task.assigneeUser?.id === userStore.userInfo?.id
-  })
-})
+// 操作按钮组件 ref
+const operationButtonRef = ref()
 
 /** 排序后的任务列表 */
 const sortedTasks = computed(() => {
@@ -244,46 +226,33 @@ function getStatusTextClass(status: number) {
   return 'text-[#999]'
 }
 
-/** 同意 */
-function handleApprove() {
-  if (!runningTask.value) {
-    return
-  }
-  uni.navigateTo({ url: `/pages-bpm/processInstance/detail/audit/index?id=${runningTask.value.id}&pass=true` })
-}
-
-/** 拒绝 */
-function handleReject() {
-  if (!runningTask.value) {
-    return
-  }
-  uni.navigateTo({ url: `/pages-bpm/processInstance/detail/audit/index?id=${runningTask.value.id}&pass=false` })
-}
-
 /** 加载流程实例 */
 async function loadProcessInstance() {
-  const data = await getApprovalDetail({ processInstanceId: processInstanceId.value })
+  const param = {
+    processInstanceId: props.id,
+    taskId: props.taskId,
+  }
+  const data = await getApprovalDetail(param)
   if (!data || !data.processInstance) {
     toast.show('查询不到审批详情信息')
     return
   }
   processInstance.value = data.processInstance
   processDefinition.value = data.processDefinition || {}
+  operationButtonRef.value?.loadTodoTask(data.todoTask)
 }
 
 /** 加载任务列表 */
 async function loadTasks() {
-  tasks.value = await getTaskListByProcessInstanceId(processInstanceId.value)
+  tasks.value = await getTaskListByProcessInstanceId(props.id)
 }
 
 /** 初始化 */
-onLoad(async (options) => {
-  // TODO @jason:通过 props id 处理;
-  if (!options?.id) {
+onMounted(async () => {
+  if (!props.id) {
     toast.show('参数错误')
     return
   }
-  processInstanceId.value = options.id
   await Promise.all([loadProcessInstance(), loadTasks()])
 })
 </script>

+ 1 - 1
src/pages/bpm/components/todo-list.vue

@@ -118,7 +118,7 @@ function handleReset() {
 
 /** 查看详情 */
 function handleDetail(item: Task) {
-  uni.navigateTo({ url: `/pages-bpm/processInstance/detail/index?id=${item.processInstance.id}` })
+  uni.navigateTo({ url: `/pages-bpm/processInstance/detail/index?id=${item.processInstance.id}&taskId=${item.id}` })
 }
 
 /** 同意 */