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

This commit is contained in:
jason
2026-01-09 14:23:37 +08:00
parent 17d014421a
commit 51c593f319
5 changed files with 236 additions and 52 deletions

View File

@@ -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
}
/** 抄送流程实例 */

View File

@@ -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> // 按钮设置
}
/** 查询待办任务分页列表 */

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}` })
}
/** 同意 */