Explorar o código

!39 feat: [bpm] 流程取消操作
Merge pull request !39 from Jason/master

芋道源码 hai 3 meses
pai
achega
00d3f4fbee

+ 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 type { PageParam, PageResult } from '@/http/types'
 import { http } from '@/http/http'
 import { http } from '@/http/http'
 
 
@@ -47,6 +48,7 @@ export interface ProcessInstance {
 export interface ApprovalDetail {
 export interface ApprovalDetail {
   processInstance: ProcessInstance
   processInstance: ProcessInstance
   processDefinition: ProcessDefinition
   processDefinition: ProcessDefinition
+  todoTask: Task
 }
 }
 
 
 /** 抄送流程实例 */
 /** 抄送流程实例 */

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

@@ -11,6 +11,10 @@ export interface TaskUser {
   deptName?: string
   deptName?: string
 }
 }
 
 
+export interface OperationButtonSetting {
+  displayName: string // 按钮名称
+  enable: boolean // 是否启用
+}
 /** 流程任务 */
 /** 流程任务 */
 export interface Task {
 export interface Task {
   id: string
   id: string
@@ -24,6 +28,9 @@ export interface Task {
   ownerUser?: TaskUser
   ownerUser?: TaskUser
   processInstanceId?: string // 流程实例 ID
   processInstanceId?: string // 流程实例 ID
   processInstance: ProcessInstance
   processInstance: ProcessInstance
+  reasonRequire?: boolean // 是否填写审批意见
+  buttonsSetting?: Record<number, OperationButtonSetting> // 按钮设置
+  children?: Task[] // 由加签生成,包含多层子任务
 }
 }
 
 
 /** 查询待办任务分页列表 */
 /** 查询待办任务分页列表 */
@@ -55,3 +62,33 @@ export function getTaskListByProcessInstanceId(processInstanceId: string) {
 export function getTaskManagerPage(params: PageParam) {
 export function getTaskManagerPage(params: PageParam) {
   return http.get<PageResult<Task>>('/bpm/task/manager-page', params)
   return http.get<PageResult<Task>>('/bpm/task/manager-page', params)
 }
 }
+
+/** 委派任务 */
+export function delegateTask(data: { id: string, delegateUserId: string, reason: string }) {
+  return http.put<boolean>('/bpm/task/delegate', data)
+}
+
+/** 转办任务 */
+export function transferTask(data: { id: string, assigneeUserId: string, reason: string }) {
+  return http.put<boolean>('/bpm/task/transfer', data)
+}
+
+/** 退回任务 */
+export function returnTask(data: { id: string, targetTaskDefinitionKey: string, reason: string }) {
+  return http.put<boolean>('/bpm/task/return', data)
+}
+
+/** 获取可退回的节点列表 */
+export function getTaskListByReturn(taskId: string) {
+  return http.get<any[]>(`/bpm/task/list-by-return?id=${taskId}`)
+}
+
+/** 加签任务 */
+export function signCreateTask(data: { id: string, type: string, userIds: number[], reason: string }) {
+  return http.put<boolean>('/bpm/task/create-sign', data)
+}
+
+/** 减签任务 */
+export function signDeleteTask(data: { id: string, reason: string }) {
+  return http.delete<boolean>('/bpm/task/delete-sign', data)
+}

+ 3 - 0
src/components/system-select/user-picker.vue

@@ -7,6 +7,7 @@
     value-key="id"
     value-key="id"
     label-key="nickname"
     label-key="nickname"
     :type="type"
     :type="type"
+    :prop="prop"
     filterable
     filterable
     :placeholder="placeholder"
     :placeholder="placeholder"
     @confirm="handleConfirm"
     @confirm="handleConfirm"
@@ -23,10 +24,12 @@ const props = withDefaults(defineProps<{
   type?: 'radio' | 'checkbox'
   type?: 'radio' | 'checkbox'
   label?: string
   label?: string
   placeholder?: string
   placeholder?: string
+  prop?: string
 }>(), {
 }>(), {
   type: 'checkbox',
   type: 'checkbox',
   label: '',
   label: '',
   placeholder: '请选择',
   placeholder: '请选择',
+  prop: '',
 })
 })
 
 
 const emit = defineEmits<{
 const emit = defineEmits<{

+ 150 - 0
src/pages-bpm/processInstance/detail/add-sign/index.vue

@@ -0,0 +1,150 @@
+<template>
+  <view class="yd-page-container">
+    <!-- 顶部导航栏 -->
+    <wd-navbar
+      title="加签任务"
+      left-arrow placeholder safe-area-inset-top fixed
+      @click-left="handleBack"
+    />
+
+    <!-- 操作表单 -->
+    <view class="p-24rpx">
+      <wd-form ref="formRef" :model="formData" :rules="formRules">
+        <wd-cell-group border>
+          <!-- 加签处理人 -->
+          <UserPicker
+            v-model="formData.userIds"
+            prop="userIds"
+            type="checkbox"
+            label="加签处理人:"
+            label-width="200rpx"
+            placeholder="请选择加签处理人"
+            :rules="formRules.userIds"
+          />
+
+          <!-- 审批意见 -->
+          <wd-textarea
+            v-model="formData.reason"
+            prop="reason"
+            label="审批意见:"
+            label-width="200rpx"
+            placeholder="请输入审批意见"
+            :maxlength="500"
+            show-word-limit
+            clearable
+          />
+        </wd-cell-group>
+        <!-- 提交按钮 -->
+        <view class="mt-48rpx flex gap-16rpx">
+          <wd-button
+            type="primary"
+            class="flex-1"
+            plain
+            :loading="submitting"
+            :disabled="submitting"
+            @click="handleSubmit('before')"
+          >
+            向前加签
+          </wd-button>
+          <wd-button
+            type="primary"
+            class="flex-1"
+            :loading="submitting"
+            :disabled="submitting"
+            @click="handleSubmit('after')"
+          >
+            向后加签
+          </wd-button>
+        </view>
+      </wd-form>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
+import { computed, reactive, ref } from 'vue'
+import { useToast } from 'wot-design-uni'
+import { signCreateTask } from '@/api/bpm/task'
+import UserPicker from '@/components/system-select/user-picker.vue'
+import { navigateBackPlus } from '@/utils'
+
+const props = defineProps<{
+  processInstanceId: string
+  taskId: string
+}>()
+
+definePage({
+  style: {
+    navigationBarTitleText: '',
+    navigationStyle: 'custom',
+  },
+})
+
+const taskId = computed(() => props.taskId)
+const processInstanceId = computed(() => props.processInstanceId)
+const toast = useToast()
+const submitting = ref(false)
+const formRef = ref<FormInstance>()
+
+const formData = reactive({
+  userIds: [] as number[],
+  reason: '',
+})
+
+const formRules = {
+  userIds: [
+    { required: true, message: '加签处理人不能为空', validator: (value: number[]) => value.length > 0 },
+  ],
+  reason: [
+    { required: true, message: '审批意见不能为空' },
+  ],
+}
+
+/** 返回上一页 */
+function handleBack() {
+  navigateBackPlus(`/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`)
+}
+
+/** 初始化校验 */
+if (!props.taskId || !props.processInstanceId) {
+  toast.show('参数错误')
+}
+
+/** 提交操作 */
+async function handleSubmit(type: 'before' | 'after') {
+  if (submitting.value)
+    return
+
+  // 使用 wd-form 的校验方法
+  const { valid } = await formRef.value!.validate()
+  if (!valid) {
+    return
+  }
+  submitting.value = true
+  try {
+    const result = await signCreateTask({
+      id: taskId.value as string,
+      type,
+      userIds: formData.userIds,
+      reason: formData.reason,
+    })
+
+    if (result) {
+      const actionText = type === 'before' ? '向前加签' : '向后加签'
+      toast.success(`${actionText}成功`)
+      setTimeout(() => {
+        uni.redirectTo({
+          url: `/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`,
+        })
+      }, 1500)
+    }
+  } catch (error) {
+    const actionText = type === 'before' ? '向前加签' : '向后加签'
+    console.error(`[add-sign] ${actionText}失败:`, error)
+    toast.error(`${actionText}失败`)
+  } finally {
+    submitting.value = false
+  }
+}
+</script>

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

@@ -0,0 +1,314 @@
+<!-- 操作按钮 -->
+<template>
+  <!-- 有待审批的任务 -->
+  <view v-if="runningTask" class="yd-detail-footer">
+    <view 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>
+      <!-- 更多操作 ActionSheet -->
+      <wd-action-sheet v-if="moreOperations.length > 0" v-model="showMoreActions" :actions="moreOperations" title="请选择操作" @select="handleMoreAction" />
+
+      <!-- 右侧按钮,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>
+  </view>
+  <!--  无待审批的任务 仅显示取消按钮。TODO 看看还需要显示 -->
+  <view v-if="!runningTask && isShowProcessStartCancel()" class="yd-detail-footer">
+    <wd-button
+      plain
+      type="primary"
+      :round="false"
+      block
+      @click="handleOperation(BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL)"
+    >
+      取消
+    </wd-button>
+  </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 { ProcessInstance } from '@/api/bpm/processInstance'
+import type { Task } from '@/api/bpm/task'
+import { useToast } from 'wot-design-uni'
+import { useUserStore } from '@/store'
+import {
+  BpmProcessInstanceStatus,
+  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',
+  [BpmTaskOperationButtonTypeEnum.DELETE_SIGN]: 'remove',
+  [BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL]: 'stop-circle',
+}
+
+const userStore = useUserStore()
+/** 左侧操作按钮 【最多两个】{转办, 委派, 退回, 加签, 抄送等} */
+const leftOperations = ref<LeftOperationType[]>([])
+
+/** 右侧操作按钮【最多两个】{通过,拒绝, 取消} */
+const rightOperationTypes = []
+const rightOperations = ref<RightOperationType[]>([])
+/** 更多操作 */
+const moreOperations = ref<MoreOperationType[]>([])
+const toast = useToast()
+const runningTask = ref<Task>()
+const processInstance = ref<ProcessInstance>()
+const reasonRequire = ref<boolean>(false)
+
+/** 初始化 */
+function init(theProcessInstance: ProcessInstance, task: Task) {
+  processInstance.value = theProcessInstance
+  runningTask.value = task
+  if (task) {
+    reasonRequire.value = task.reasonRequire ?? false
+    // 右侧按钮
+    if (isHandleTaskStatus() && isShowButton(BpmTaskOperationButtonTypeEnum.REJECT)) {
+      rightOperationTypes.push(BpmTaskOperationButtonTypeEnum.REJECT)
+      rightOperations.value.push({
+        operationType: BpmTaskOperationButtonTypeEnum.REJECT,
+        displayName: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.REJECT),
+        btnType: 'error',
+        plain: true,
+      })
+    }
+    if (isHandleTaskStatus() && isShowButton(BpmTaskOperationButtonTypeEnum.APPROVE)) {
+      rightOperationTypes.push(BpmTaskOperationButtonTypeEnum.APPROVE)
+      rightOperations.value.push({
+        operationType: BpmTaskOperationButtonTypeEnum.APPROVE,
+        displayName: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.APPROVE),
+        btnType: 'primary',
+        plain: false,
+      })
+    }
+
+    // 左侧操作,和更多操作
+    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),
+          })
+        }
+      }
+    })
+    /** 减签操作的显示 */
+    if (isShowDeleteSign()) {
+      if (leftOperations.value.length >= 2) {
+        moreOperations.value.push({
+          name: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.DELETE_SIGN),
+          operationType: BpmTaskOperationButtonTypeEnum.DELETE_SIGN,
+        })
+      } else {
+        leftOperations.value.push({
+          operationType: BpmTaskOperationButtonTypeEnum.DELETE_SIGN,
+          iconName: operationIconsMap[BpmTaskOperationButtonTypeEnum.DELETE_SIGN],
+          displayName: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.DELETE_SIGN),
+        })
+      }
+    }
+  }
+
+  // 是否显示流程取消
+  if (isShowProcessStartCancel()) {
+    if (rightOperationTypes.length < 2) {
+      rightOperationTypes.push(BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL)
+      rightOperations.value.push({
+        operationType: BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL,
+        displayName: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL),
+        btnType: 'primary',
+        plain: true,
+      })
+    } else {
+      if (leftOperations.value.length >= 2) {
+        moreOperations.value.push({
+          name: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL),
+          operationType: BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL,
+        })
+      } else {
+        leftOperations.value.push({
+          operationType: BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL,
+          iconName: operationIconsMap[BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL],
+          displayName: getButtonDisplayName(BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL),
+        })
+      }
+    }
+  }
+}
+/** 跳转到相应的操作页面 */
+function handleOperation(operationType: number) {
+  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:
+      uni.navigateTo({
+        url: `/pages-bpm/processInstance/detail/reassign/index?processInstanceId=${runningTask.value.processInstanceId}&taskId=${runningTask.value.id}&type=delegate`,
+      })
+      break
+    case BpmTaskOperationButtonTypeEnum.TRANSFER:
+      uni.navigateTo({
+        url: `/pages-bpm/processInstance/detail/reassign/index?processInstanceId=${runningTask.value.processInstanceId}&taskId=${runningTask.value.id}&type=transfer`,
+      })
+      break
+    case BpmTaskOperationButtonTypeEnum.ADD_SIGN:
+      uni.navigateTo({
+        url: `/pages-bpm/processInstance/detail/add-sign/index?processInstanceId=${runningTask.value.processInstanceId}&taskId=${runningTask.value.id}`,
+      })
+      break
+    case BpmTaskOperationButtonTypeEnum.RETURN:
+      uni.navigateTo({
+        url: `/pages-bpm/processInstance/detail/return/index?processInstanceId=${runningTask.value.processInstanceId}&taskId=${runningTask.value.id}`,
+      })
+      break
+    case BpmTaskOperationButtonTypeEnum.DELETE_SIGN:
+      uni.navigateTo({
+        url: `/pages-bpm/processInstance/detail/delete-sign/index?processInstanceId=${runningTask.value.processInstanceId}&taskId=${runningTask.value.id}&children=${encodeURIComponent(JSON.stringify(runningTask.value.children || []))}`,
+      })
+      break
+    case BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL:
+      uni.navigateTo({
+        url: `/pages-bpm/processInstance/detail/process-cancel/index?processInstanceId=${processInstance.value.id}&taskId=${runningTask.value?.id}`,
+      })
+      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
+}
+
+/** 流程状态是否为结束状态 */
+function isEndProcessStatus(status: number) {
+  let isEndStatus = false
+  if (
+    BpmProcessInstanceStatus.APPROVE === status
+    || BpmProcessInstanceStatus.REJECT === status
+    || BpmProcessInstanceStatus.CANCEL === status
+  ) {
+    isEndStatus = true
+  }
+  return isEndStatus
+}
+
+/** 流程发起人是否为当前用户 */
+function isProcessStartUser() {
+  let isStartUser = false
+  if (userStore.userInfo?.userId === processInstance.value?.startUser?.id) {
+    isStartUser = true
+  }
+  return isStartUser
+}
+
+/** 是否显示减签 */
+function isShowDeleteSign() {
+  return runningTask.value?.children?.length > 0
+}
+
+/** 是否显示流程发起人取消 */
+function isShowProcessStartCancel() {
+  return isProcessStartUser() && !isEndProcessStatus(processInstance.value?.status)
+}
+
+/** 暴露方法 */
+defineExpose({ init })
+</script>

+ 181 - 0
src/pages-bpm/processInstance/detail/delete-sign/index.vue

@@ -0,0 +1,181 @@
+<template>
+  <view class="yd-page-container">
+    <!-- 顶部导航栏 -->
+    <wd-navbar
+      title="减签任务"
+      left-arrow placeholder safe-area-inset-top fixed
+      @click-left="handleBack"
+    />
+
+    <!-- 操作表单 -->
+    <view class="p-24rpx">
+      <wd-form ref="formRef" :model="formData" :rules="formRules">
+        <wd-cell-group border>
+          <!-- 减签人员选择 -->
+          <wd-picker
+            v-model="formData.deleteSignTaskId"
+            :columns="taskOptions"
+            value-key="id"
+            label-key="label"
+            label="减签人员:"
+            label-width="180rpx"
+            placeholder="请选择减签人员"
+            prop="deleteSignTaskId"
+          />
+
+          <!-- 审批意见 -->
+          <wd-textarea
+            v-model="formData.reason"
+            prop="reason"
+            label="审批意见:"
+            label-width="180rpx"
+            placeholder="请输入审批意见"
+            :maxlength="500"
+            show-word-limit
+            clearable
+          />
+        </wd-cell-group>
+        <!-- 提交按钮 -->
+        <view class="mt-48rpx">
+          <wd-button
+            type="primary"
+            block
+            :loading="submitting"
+            :disabled="submitting"
+            @click="handleSubmit"
+          >
+            减签
+          </wd-button>
+        </view>
+      </wd-form>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
+import { computed, onMounted, reactive, ref } from 'vue'
+import { useToast } from 'wot-design-uni'
+import { signDeleteTask } from '@/api/bpm/task'
+import { navigateBackPlus } from '@/utils'
+
+const props = defineProps<{
+  processInstanceId: string
+  taskId: string
+  children?: string // JSON 字符串格式的子任务数据
+}>()
+
+definePage({
+  style: {
+    navigationBarTitleText: '',
+    navigationStyle: 'custom',
+  },
+})
+
+const taskId = computed(() => props.taskId)
+const processInstanceId = computed(() => props.processInstanceId)
+const toast = useToast()
+const submitting = ref(false)
+const formRef = ref<FormInstance>()
+const taskOptions = ref<any[]>([])
+
+const formData = reactive({
+  deleteSignTaskId: '',
+  reason: '',
+})
+
+const formRules = {
+  deleteSignTaskId: [
+    { required: true, message: '减签人员不能为空' },
+  ],
+  reason: [
+    { required: true, message: '审批意见不能为空' },
+  ],
+}
+
+/** 返回上一页 */
+function handleBack() {
+  navigateBackPlus(`/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`)
+}
+
+/** 初始化校验 */
+if (!props.taskId || !props.processInstanceId) {
+  toast.show('参数错误')
+}
+
+/** 获取减签人员标签 */
+function getDeleteSignUserLabel(task: any): string {
+  const deptName = task?.assigneeUser?.deptName || task?.ownerUser?.deptName
+  const nickname = task?.assigneeUser?.nickname || task?.ownerUser?.nickname
+  return `${nickname} ( 所属部门:${deptName} )`
+}
+
+/** 获取可减签的任务列表 */
+async function loadDeleteSignTaskList() {
+  try {
+    let childTasks = []
+
+    // 从 URL 参数中获取子任务数据
+    if (props.children) {
+      try {
+        childTasks = JSON.parse(decodeURIComponent(props.children))
+      } catch (parseError) {
+        console.error('[delete-sign] 解析子任务数据失败:', parseError)
+      }
+    }
+
+    // 提示没有子任务数据
+    if (childTasks.length === 0) {
+      toast.show('没有可减签的任务')
+      return
+    }
+
+    taskOptions.value = childTasks.map(task => ({
+      id: task.id,
+      label: getDeleteSignUserLabel(task),
+    }))
+  } catch (error) {
+    console.error('[delete-sign] 获取可减签任务失败:', error)
+    toast.error('获取可减签任务失败')
+  }
+}
+
+/** 提交操作 */
+async function handleSubmit() {
+  if (submitting.value)
+    return
+
+  // 使用 wd-form 的校验方法
+  const { valid } = await formRef.value!.validate()
+  if (!valid) {
+    return
+  }
+
+  submitting.value = true
+  try {
+    const result = await signDeleteTask({
+      id: formData.deleteSignTaskId,
+      reason: formData.reason,
+    })
+
+    if (result) {
+      toast.success('减签成功')
+      setTimeout(() => {
+        uni.redirectTo({
+          url: `/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`,
+        })
+      }, 1500)
+    }
+  } catch (error) {
+    console.error('[delete-sign] 减签失败:', error)
+    toast.error('减签失败')
+  } finally {
+    submitting.value = false
+  }
+}
+
+/** 页面加载时获取可减签任务列表 */
+onMounted(() => {
+  loadDeleteSignTaskList()
+})
+</script>

+ 31 - 63
src/pages-bpm/processInstance/detail/index.vue

@@ -12,32 +12,31 @@
       <view class="p-24rpx">
       <view class="p-24rpx">
         <!-- 标题和状态 -->
         <!-- 标题和状态 -->
         <view class="mb-16rpx flex items-center justify-between">
         <view class="mb-16rpx flex items-center justify-between">
-          <text class="text-32rpx text-[#333] font-bold">{{ processInstance.name }}</text>
-          <wd-tag :type="getStatusType(processInstance.status)">
-            {{ getStatusText(processInstance.status) }}
+          <text class="text-32rpx text-[#333] font-bold">{{ processInstance?.name }}</text>
+          <wd-tag :type="getStatusType(processInstance?.status)">
+            {{ getStatusText(processInstance?.status) }}
           </wd-tag>
           </wd-tag>
         </view>
         </view>
         <!-- 发起人信息 -->
         <!-- 发起人信息 -->
         <view class="flex items-center">
         <view class="flex items-center">
           <view class="mr-12rpx h-64rpx w-64rpx flex items-center justify-center rounded-full bg-[#1890ff] text-white">
           <view class="mr-12rpx h-64rpx w-64rpx flex items-center justify-center rounded-full bg-[#1890ff] text-white">
-            {{ processInstance.startUser?.nickname?.[0] || '?' }}
+            {{ processInstance?.startUser?.nickname?.[0] || '?' }}
           </view>
           </view>
           <view>
           <view>
-            <text class="text-28rpx text-[#333]">{{ processInstance.startUser?.nickname }}</text>
-            <text v-if="processInstance.startUser?.deptName" class="ml-8rpx text-24rpx text-[#999]">
-              {{ processInstance.startUser?.deptName }}
+            <text class="text-28rpx text-[#333]">{{ processInstance?.startUser?.nickname }}</text>
+            <text v-if="processInstance?.startUser?.deptName" class="ml-8rpx text-24rpx text-[#999]">
+              {{ processInstance?.startUser?.deptName }}
             </text>
             </text>
           </view>
           </view>
         </view>
         </view>
         <!-- 提交时间 -->
         <!-- 提交时间 -->
         <view class="mt-16rpx text-24rpx text-[#999]">
         <view class="mt-16rpx text-24rpx text-[#999]">
-          提交于 {{ formatDateTime(processInstance.startTime) }}
+          提交于 {{ formatDateTime(processInstance?.startTime) }}
         </view>
         </view>
       </view>
       </view>
     </view>
     </view>
 
 
     <!-- 区域:审批详情(表单) -->
     <!-- 区域:审批详情(表单) -->
-    <!-- TODO @jason:看看 idea 告警,怎么优化下 -->
     <FormDetail :process-definition="processDefinition" :process-instance="processInstance" />
     <FormDetail :process-definition="processDefinition" :process-instance="processInstance" />
 
 
     <!-- 区域:审批记录 TODO @jason:抽成类似 /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/processInstance/detail/modules/task-list.vue -->
     <!-- 区域:审批记录 TODO @jason:抽成类似 /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/processInstance/detail/modules/task-list.vue -->
@@ -88,29 +87,15 @@
 
 
     <!-- TODO 待开发:区域:流程评论 -->
     <!-- 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" :process-instance="processInstance" />
   </view>
   </view>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import type { ProcessDefinition, ProcessInstance } from '@/api/bpm/processInstance'
 import type { ProcessDefinition, ProcessInstance } from '@/api/bpm/processInstance'
 import type { Task } from '@/api/bpm/task'
 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 { useToast } from 'wot-design-uni'
 import { getApprovalDetail } from '@/api/bpm/processInstance'
 import { getApprovalDetail } from '@/api/bpm/processInstance'
 import { getTaskListByProcessInstanceId } from '@/api/bpm/task'
 import { getTaskListByProcessInstanceId } from '@/api/bpm/task'
@@ -118,6 +103,12 @@ import { useUserStore } from '@/store'
 import { navigateBackPlus } from '@/utils'
 import { navigateBackPlus } from '@/utils'
 import { formatDateTime, formatPast } from '@/utils/date'
 import { formatDateTime, formatPast } from '@/utils/date'
 import FormDetail from './components/form-detail.vue'
 import FormDetail from './components/form-detail.vue'
+import ProcessInstanceOperationButton from './components/operation-button.vue'
+
+const props = defineProps<{
+  id: string // 流程实例的编号
+  taskId?: string // 任务编号
+}>()
 
 
 definePage({
 definePage({
   style: {
   style: {
@@ -128,23 +119,13 @@ definePage({
 
 
 const userStore = useUserStore()
 const userStore = useUserStore()
 const toast = useToast()
 const toast = useToast()
-const processInstanceId = ref('')
-const processInstance = ref<Partial<ProcessInstance>>({})
-const processDefinition = ref<Partial<ProcessDefinition>>({})
+const processInstance = ref<ProcessInstance>()
+const processDefinition = ref<ProcessDefinition>()
 const tasks = ref<Task[]>([])
 const tasks = ref<Task[]>([])
 const orderAsc = ref(true)
 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(() => {
 const sortedTasks = computed(() => {
@@ -244,46 +225,33 @@ function getStatusTextClass(status: number) {
   return 'text-[#999]'
   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() {
 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) {
   if (!data || !data.processInstance) {
     toast.show('查询不到审批详情信息')
     toast.show('查询不到审批详情信息')
     return
     return
   }
   }
   processInstance.value = data.processInstance
   processInstance.value = data.processInstance
-  processDefinition.value = data.processDefinition || {}
+  processDefinition.value = data.processDefinition
+  operationButtonRef.value?.init(data.processInstance, data.todoTask)
 }
 }
 
 
 /** 加载任务列表 */
 /** 加载任务列表 */
 async function loadTasks() {
 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('参数错误')
     toast.show('参数错误')
     return
     return
   }
   }
-  processInstanceId.value = options.id
   await Promise.all([loadProcessInstance(), loadTasks()])
   await Promise.all([loadProcessInstance(), loadTasks()])
 })
 })
 </script>
 </script>

+ 133 - 0
src/pages-bpm/processInstance/detail/process-cancel/index.vue

@@ -0,0 +1,133 @@
+<template>
+  <view class="yd-page-container">
+    <!-- 顶部导航栏 -->
+    <wd-navbar
+      title="取消流程"
+      left-arrow placeholder safe-area-inset-top fixed
+      @click-left="handleBack"
+    />
+
+    <!-- 操作表单 -->
+    <view class="p-24rpx">
+      <wd-form ref="formRef" :model="formData" :rules="formRules">
+        <wd-cell-group border>
+          <!-- 友情提醒 -->
+          <view class="mb-24rpx border border-[#ffd591] rounded-16rpx bg-[#fff7e6] p-24rpx">
+            <view class="mb-12rpx flex items-center">
+              <wd-icon name="warning" color="#faad14" size="32rpx" />
+              <text class="ml-12rpx text-28rpx text-[#faad14] font-bold">友情提醒</text>
+            </view>
+            <text class="text-26rpx text-[#666]">取消后,该审批流程将自动结束。</text>
+          </view>
+
+          <!-- 取消理由 -->
+          <wd-textarea
+            v-model="formData.cancelReason"
+            prop="cancelReason"
+            label="取消理由:"
+            label-width="180rpx"
+            placeholder="请输入取消理由"
+            :maxlength="500"
+            show-word-limit
+            clearable
+          />
+        </wd-cell-group>
+        <!-- 提交按钮 -->
+        <view class="mt-48rpx">
+          <wd-button
+            type="primary"
+            block
+            :loading="submitting"
+            :disabled="submitting"
+            @click="handleSubmit"
+          >
+            确认取消
+          </wd-button>
+        </view>
+      </wd-form>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
+import { computed, reactive, ref } from 'vue'
+import { useToast } from 'wot-design-uni'
+import { cancelProcessInstanceByStartUser } from '@/api/bpm/processInstance'
+import { navigateBackPlus } from '@/utils'
+
+const props = defineProps<{
+  processInstanceId: string
+  taskId?: string
+}>()
+
+definePage({
+  style: {
+    navigationBarTitleText: '',
+    navigationStyle: 'custom',
+  },
+})
+
+const processInstanceId = computed(() => props.processInstanceId)
+const taskId = computed(() => props.taskId)
+const toast = useToast()
+const submitting = ref(false)
+const formRef = ref<FormInstance>()
+
+const formData = reactive({
+  cancelReason: '',
+})
+
+const formRules = {
+  cancelReason: [
+    { required: true, message: '取消理由不能为空' },
+  ],
+}
+
+/** 返回上一页 */
+function handleBack() {
+  const backUrl = taskId.value
+    ? `/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`
+    : `/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}`
+  navigateBackPlus(backUrl)
+}
+
+/** 初始化校验 */
+if (!props.processInstanceId) {
+  toast.show('参数错误')
+}
+
+/** 提交操作 */
+async function handleSubmit() {
+  if (submitting.value)
+    return
+
+  // 使用 wd-form 的校验方法
+  const { valid } = await formRef.value!.validate()
+  if (!valid) {
+    return
+  }
+
+  submitting.value = true
+  try {
+    const result = await cancelProcessInstanceByStartUser(
+      processInstanceId.value,
+      formData.cancelReason,
+    )
+
+    if (result) {
+      toast.success('流程取消成功')
+      setTimeout(() => {
+        uni.redirectTo({
+          url: `/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}`,
+        })
+      }, 1500)
+    }
+  } catch (error) {
+    console.error('[process-cancel] 取消流程失败:', error)
+    toast.error('取消流程失败')
+  } finally {
+    submitting.value = false
+  }
+}
+</script>

+ 153 - 0
src/pages-bpm/processInstance/detail/reassign/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <view class="yd-page-container">
+    <!-- 顶部导航栏 -->
+    <wd-navbar
+      :title="isDelegate ? '委派任务' : '转办任务'"
+      left-arrow placeholder safe-area-inset-top fixed
+      @click-left="handleBack"
+    />
+
+    <!-- 操作表单 -->
+    <view class="p-24rpx">
+      <wd-form ref="formRef" :model="formData" :rules="formRules">
+        <wd-cell-group border>
+          <!-- 用户选择 -->
+          <UserPicker
+            v-model="formData.userId"
+            prop="userId"
+            type="radio"
+            :label="`${isDelegate ? '接收人' : '新审批人'}:`"
+            :placeholder="`请选择${isDelegate ? '接收人' : '新审批人'}`"
+          />
+
+          <!-- 审批意见 -->
+          <wd-textarea
+            v-model="formData.reason"
+            prop="reason"
+            label="审批意见:"
+            label-width="180rpx"
+            placeholder="请输入审批意见"
+            :maxlength="500"
+            show-word-limit
+            clearable
+          />
+        </wd-cell-group>
+        <!-- 提交按钮 -->
+        <view class="mt-48rpx">
+          <wd-button
+            type="primary"
+            block
+            :loading="submitting"
+            :disabled="submitting"
+            @click="handleSubmit"
+          >
+            {{ isDelegate ? '委派' : '转办' }}
+          </wd-button>
+        </view>
+      </wd-form>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
+import { computed, reactive, ref } from 'vue'
+import { useToast } from 'wot-design-uni'
+import { delegateTask, transferTask } from '@/api/bpm/task'
+import UserPicker from '@/components/system-select/user-picker.vue'
+import { navigateBackPlus } from '@/utils'
+
+const props = defineProps<{
+  processInstanceId: string
+  taskId: string
+  type: string // 'delegate' 或 'transfer'
+}>()
+
+definePage({
+  style: {
+    navigationBarTitleText: '',
+    navigationStyle: 'custom',
+  },
+})
+
+const taskId = computed(() => props.taskId)
+const processInstanceId = computed(() => props.processInstanceId)
+const operationType = computed(() => props.type || 'transfer') // 默认转办
+const isDelegate = computed(() => operationType.value === 'delegate')
+const toast = useToast()
+const submitting = ref(false)
+const formRef = ref<FormInstance>()
+
+const formData = reactive({
+  userId: undefined as number | undefined,
+  reason: '',
+})
+
+const formRules = {
+  userId: [
+    { required: true, message: `请选择${isDelegate.value ? '接收人' : '新审批人'}` },
+  ],
+  reason: [
+    { required: true, message: '审批意见不能为空' },
+  ],
+}
+
+/** 返回上一页 */
+function handleBack() {
+  navigateBackPlus(`/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`)
+}
+
+/** 初始化校验 */
+if (!props.taskId || !props.processInstanceId) {
+  toast.show('参数错误')
+}
+
+/** 提交操作 */
+async function handleSubmit() {
+  if (submitting.value)
+    return
+
+  // 使用 wd-form 的校验方法
+  const { valid } = await formRef.value!.validate()
+  if (!valid) {
+    return
+  }
+
+  submitting.value = true
+  try {
+    const data = {
+      id: taskId.value as string,
+      reason: formData.reason,
+    }
+
+    let result
+    if (isDelegate.value) {
+      // 委派
+      result = await delegateTask({
+        ...data,
+        delegateUserId: String(formData.userId),
+      })
+    } else {
+      // 转办
+      result = await transferTask({
+        ...data,
+        assigneeUserId: String(formData.userId),
+      })
+    }
+
+    if (result) {
+      toast.success(`${isDelegate.value ? '委派' : '转办'}成功`)
+      setTimeout(() => {
+        uni.redirectTo({
+          url: `/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`,
+        })
+      }, 1500)
+    }
+  } catch (error) {
+    console.error(`[reassign] ${isDelegate.value ? '委派' : '转办'}失败:`, error)
+    toast.error(`${isDelegate.value ? '委派' : '转办'}失败`)
+  } finally {
+    submitting.value = false
+  }
+}
+</script>

+ 156 - 0
src/pages-bpm/processInstance/detail/return/index.vue

@@ -0,0 +1,156 @@
+<template>
+  <view class="yd-page-container">
+    <!-- 顶部导航栏 -->
+    <wd-navbar
+      title="退回任务"
+      left-arrow placeholder safe-area-inset-top fixed
+      @click-left="handleBack"
+    />
+
+    <!-- 操作表单 -->
+    <view class="p-24rpx">
+      <wd-form ref="formRef" :model="formData" :rules="formRules">
+        <wd-cell-group border>
+          <!-- 退回节点选择 -->
+          <wd-picker
+            v-model="formData.targetActivityId"
+            label="退回节点:"
+            prop="targetActivityId"
+            :columns="activityOptions"
+            value-key="taskDefinitionKey"
+            label-key="name"
+            placeholder="请选择退回节点"
+          />
+
+          <!-- 退回原因 -->
+          <wd-textarea
+            v-model="formData.reason"
+            prop="reason"
+            label="退回原因:"
+            label-width="180rpx"
+            placeholder="请输入退回原因"
+            :maxlength="500"
+            show-word-limit
+            clearable
+          />
+        </wd-cell-group>
+        <!-- 提交按钮 -->
+        <view class="mt-48rpx">
+          <wd-button
+            type="primary"
+            block
+            :loading="submitting"
+            :disabled="submitting"
+            @click="handleSubmit"
+          >
+            退回
+          </wd-button>
+        </view>
+      </wd-form>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
+import { computed, onMounted, reactive, ref } from 'vue'
+import { useToast } from 'wot-design-uni'
+import { getTaskListByReturn, returnTask } from '@/api/bpm/task'
+import { navigateBackPlus } from '@/utils'
+
+const props = defineProps<{
+  processInstanceId: string
+  taskId: string
+}>()
+
+definePage({
+  style: {
+    navigationBarTitleText: '',
+    navigationStyle: 'custom',
+  },
+})
+
+const taskId = computed(() => props.taskId)
+const processInstanceId = computed(() => props.processInstanceId)
+const toast = useToast()
+const submitting = ref(false)
+const formRef = ref<FormInstance>()
+const activityOptions = ref<any[]>([])
+
+const formData = reactive({
+  targetActivityId: '',
+  reason: '',
+})
+
+const formRules = {
+  targetActivityId: [
+    { required: true, message: '退回节点不能为空' },
+  ],
+  reason: [
+    { required: true, message: '退回原因不能为空' },
+  ],
+}
+
+/** 返回上一页 */
+function handleBack() {
+  navigateBackPlus(`/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`)
+}
+
+/** 初始化校验 */
+if (!props.taskId || !props.processInstanceId) {
+  toast.show('参数错误')
+}
+
+/** 获取可退回的节点列表 */
+async function loadReturnTaskList() {
+  try {
+    const result = await getTaskListByReturn(taskId.value)
+    if (result && Array.isArray(result)) {
+      activityOptions.value = result
+    }
+  } catch (error) {
+    console.error('[return] 获取可退回节点失败:', error)
+    toast.error('获取可退回节点失败')
+  }
+}
+
+/** 提交操作 */
+async function handleSubmit() {
+  if (submitting.value)
+    return
+
+  // 使用 wd-form 的校验方法
+  const { valid } = await formRef.value!.validate()
+  if (!valid) {
+    return
+  }
+
+  submitting.value = true
+  try {
+    const result = await returnTask({
+      id: taskId.value as string,
+      targetTaskDefinitionKey: formData.targetActivityId,
+      reason: formData.reason,
+    })
+
+    if (result) {
+      toast.success('退回成功')
+      setTimeout(() => {
+        uni.redirectTo({
+          url: `/pages-bpm/processInstance/detail/index?id=${processInstanceId.value}&taskId=${taskId.value}`,
+        })
+      }, 1500)
+    }
+  } catch (error) {
+    console.error('[return] 退回失败:', error)
+    toast.error('退回失败')
+  } finally {
+    submitting.value = false
+  }
+}
+
+/** 页面加载时获取可退回节点列表 */
+onMounted(() => {
+  loadReturnTaskList()
+})
+</script>

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

@@ -118,7 +118,7 @@ function handleReset() {
 
 
 /** 查看详情 */
 /** 查看详情 */
 function handleDetail(item: Task) {
 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}` })
 }
 }
 
 
 /** 同意 */
 /** 同意 */

+ 2 - 0
src/store/user.ts

@@ -63,6 +63,8 @@ export const useUserStore = defineStore(
     /** 获取用户信息 */
     /** 获取用户信息 */
     const fetchUserInfo = async () => {
     const fetchUserInfo = async () => {
       const res = await getAuthPermissionInfo()
       const res = await getAuthPermissionInfo()
+      // 后端返回的用户 Id 字段为 id.
+      res.user.userId = res.user.id
       setUserInfo(res)
       setUserInfo(res)
       return res
       return res
     }
     }

+ 10 - 0
src/utils/constants/biz-bpm-enum.ts

@@ -147,6 +147,14 @@ export enum BpmTaskOperationButtonTypeEnum {
    * 委派
    * 委派
    */
    */
   DELEGATE = 4,
   DELEGATE = 4,
+  /**
+   * 减签
+   */
+  DELETE_SIGN = 8,
+  /**
+   * 流程发起人取消
+   */
+  PROCESS_START_CANCEL = 9,
   /**
   /**
    * 拒绝
    * 拒绝
    */
    */
@@ -286,6 +294,8 @@ OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.DELEGATE, '委派')
 OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.ADD_SIGN, '加签')
 OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.ADD_SIGN, '加签')
 OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.RETURN, '退回')
 OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.RETURN, '退回')
 OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.COPY, '抄送')
 OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.COPY, '抄送')
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.DELETE_SIGN, '减签')
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.PROCESS_START_CANCEL, '取消')
 
 
 /**
 /**
  * 流程实例的变量枚举
  * 流程实例的变量枚举