admin-vben5/apps/web-antd/src/views/workflow/components/approval-panel.vue

460 lines
12 KiB
Vue
Raw Normal View History

2024-12-16 09:45:00 +08:00
<script setup lang="ts">
import type { User } from '#/api/core/user';
2024-12-16 09:45:00 +08:00
import type { FlowInfoResponse } from '#/api/workflow/instance/model';
import type { TaskInfo } from '#/api/workflow/task/model';
2024-12-16 15:19:15 +08:00
import { computed, onUnmounted, ref, watch } from 'vue';
2024-12-17 19:27:42 +08:00
import { useRouter } from 'vue-router';
2024-12-16 09:45:00 +08:00
2024-12-17 09:06:46 +08:00
import { Fallback, useVbenModal, VbenAvatar } from '@vben/common-ui';
2024-12-16 09:45:00 +08:00
import { DictEnum } from '@vben/constants';
import { getPopupContainer } from '@vben/utils';
2024-12-16 09:45:00 +08:00
import { useEventListener } from '@vueuse/core';
2024-12-16 15:19:15 +08:00
import {
Card,
Divider,
Dropdown,
Menu,
MenuItem,
2024-12-17 09:06:46 +08:00
Modal,
Skeleton,
2024-12-16 15:19:15 +08:00
Space,
TabPane,
Tabs,
} from 'ant-design-vue';
2024-12-16 09:45:00 +08:00
2024-12-17 15:41:07 +08:00
import {
cancelProcessApply,
deleteByInstanceIds,
flowInfo,
} from '#/api/workflow/instance';
import {
getTaskByTaskId,
taskOperation,
terminationTask,
} from '#/api/workflow/task';
2024-12-16 09:45:00 +08:00
import { renderDict } from '#/utils/render';
2024-12-17 09:50:19 +08:00
import { approvalModal, approvalRejectionModal, ApprovalTimeline } from '.';
import userSelectModal from './user-select-modal.vue';
2024-12-16 09:45:00 +08:00
defineOptions({
name: 'ApprovalPanel',
inheritAttrs: false,
});
2024-12-16 15:19:15 +08:00
// eslint-disable-next-line no-use-before-define
const props = defineProps<{ task?: TaskInfo; type: ApprovalType }>();
2024-12-17 15:08:31 +08:00
/**
* 下面按钮点击后会触发的事件
*/
const emit = defineEmits<{ reload: [] }>();
const currentTask = ref<TaskInfo>();
/**
* 是否显示 加签/减签操作
*/
const showMultiActions = computed(() => {
if (!currentTask.value) {
return false;
}
if (Number(currentTask.value.nodeRatio) > 0) {
return true;
}
return false;
});
2024-12-16 15:19:15 +08:00
/**
* myself 我发起的
* readonly 只读 只用于查看
2024-12-17 09:06:46 +08:00
* approve 审批
2024-12-17 19:58:02 +08:00
* admin 流程监控 - 待办任务使用
2024-12-16 15:19:15 +08:00
*/
2024-12-17 19:58:02 +08:00
type ApprovalType = 'admin' | 'approve' | 'myself' | 'readonly';
2024-12-16 15:19:15 +08:00
const showFooter = computed(() => {
if (props.type === 'readonly') {
return false;
}
// 我发起的 && [已完成, 已作废] 不显示
if (
props.type === 'myself' &&
['finish', 'invalid'].includes(props.task?.flowStatus ?? '')
) {
return false;
}
return true;
});
2024-12-16 09:45:00 +08:00
const currentFlowInfo = ref<FlowInfoResponse>();
2024-12-16 14:52:05 +08:00
/**
* card的loading状态
*/
const loading = ref(false);
const iframeLoaded = ref(false);
const iframeHeight = ref(300);
useEventListener('message', (event) => {
/**
* iframe通信 加载完毕后才显示表单 解决卡顿问题
*/
if (event.data === 'mounted') {
iframeLoaded.value = true;
}
/**
* 高度与表单高度保持一致
*/
if (event.data.includes('height')) {
const height = event.data.split('height:')[1];
iframeHeight.value = height;
}
});
2024-12-16 10:18:33 +08:00
async function handleLoadInfo(task: TaskInfo | undefined) {
2024-12-16 14:52:05 +08:00
try {
if (!task) return null;
loading.value = true;
iframeLoaded.value = false;
2024-12-16 14:52:05 +08:00
const resp = await flowInfo(task.businessId);
currentFlowInfo.value = resp;
const taskResp = await getTaskByTaskId(props.task!.id);
currentTask.value = taskResp;
2024-12-16 14:52:05 +08:00
} catch (error) {
console.error(error);
} finally {
loading.value = false;
}
2024-12-16 10:18:33 +08:00
}
watch(() => props.task, handleLoadInfo);
2024-12-16 09:45:00 +08:00
onUnmounted(() => (currentFlowInfo.value = undefined));
2024-12-16 15:19:15 +08:00
2024-12-17 15:41:07 +08:00
// 进行中 可以撤销
const revocable = computed(() => props.task?.flowStatus === 'waiting');
2024-12-16 15:19:15 +08:00
async function handleCancel() {
2024-12-17 15:41:07 +08:00
Modal.confirm({
title: '提示',
content: '确定要撤销该申请吗?',
centered: true,
okButtonProps: { danger: true },
onOk: async () => {
await cancelProcessApply({
businessId: props.task!.businessId,
message: '申请人撤销流程!',
});
emit('reload');
},
});
}
2024-12-17 19:27:42 +08:00
/**
* 是否可编辑/删除
*/
2024-12-17 15:41:07 +08:00
const editableAndRemoveable = computed(() => {
if (!props.task) {
return false;
}
return ['back', 'cancel', 'draft'].includes(props.task.flowStatus);
});
2024-12-17 19:27:42 +08:00
const router = useRouter();
function handleEdit() {
const path = props.task?.formPath;
if (path) {
router.push({ path, query: { id: props.task!.businessId } });
}
}
2024-12-17 15:41:07 +08:00
function handleRemove() {
Modal.confirm({
title: '提示',
content: '确定删除该申请吗?',
centered: true,
okButtonProps: { danger: true },
onOk: async () => {
await deleteByInstanceIds([props.task!.id]);
emit('reload');
},
});
2024-12-16 15:19:15 +08:00
}
2024-12-17 09:06:46 +08:00
/**
* 审批驳回
*/
const [RejectionModal, rejectionModalApi] = useVbenModal({
connectedComponent: approvalRejectionModal,
});
function handleRejection() {
rejectionModalApi.setData({
taskId: props.task?.id,
instanceId: props.task?.instanceId,
});
rejectionModalApi.open();
}
/**
* 审批终止
*/
function handleTermination() {
Modal.confirm({
title: '审批终止',
content: '确定终止当前审批流程吗?',
centered: true,
okButtonProps: { danger: true },
onOk: async () => {
await terminationTask({ taskId: props.task!.id });
2024-12-17 15:08:31 +08:00
emit('reload');
2024-12-17 09:06:46 +08:00
},
});
}
2024-12-17 09:50:19 +08:00
/**
* 审批通过
*/
const [ApprovalModal, approvalModalApi] = useVbenModal({
connectedComponent: approvalModal,
});
function handleApproval() {
approvalModalApi.setData({ taskId: props.task?.id });
approvalModalApi.open();
}
/**
* TODO: 1提取公共函数 2原版是可以填写意见的(message参数)
*/
/**
* 委托
*/
const [DelegationModal, delegationModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleDelegation(userList: User[]) {
if (userList.length === 0) return;
const current = userList[0];
Modal.confirm({
title: '委托',
content: `确定委托给${current?.nickName}吗?`,
centered: true,
onOk: async () => {
await taskOperation(
{ taskId: props.task!.id, userId: current!.userId },
'delegateTask',
);
2024-12-17 15:08:31 +08:00
emit('reload');
},
});
}
/**
* 转办
*/
const [TransferModal, transferModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleTransfer(userList: User[]) {
if (userList.length === 0) return;
const current = userList[0];
Modal.confirm({
title: '转办',
content: `确定转办给${current?.nickName}吗?`,
centered: true,
onOk: async () => {
await taskOperation(
{ taskId: props.task!.id, userId: current!.userId },
'transferTask',
);
2024-12-17 15:08:31 +08:00
emit('reload');
},
});
}
const [AddSignatureModal, addSignatureModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleAddSignature(userList: User[]) {
if (userList.length === 0) return;
const userIds = userList.map((user) => user.userId);
Modal.confirm({
title: '提示',
content: '确认加签吗?',
centered: true,
onOk: async () => {
await taskOperation({ taskId: props.task!.id, userIds }, 'addSignature');
2024-12-17 15:08:31 +08:00
emit('reload');
},
});
}
const [ReductionSignatureModal, reductionSignatureModalApi] = useVbenModal({
connectedComponent: userSelectModal,
});
function handleReductionSignature(userList: User[]) {
if (userList.length === 0) return;
const userIds = userList.map((user) => user.userId);
Modal.confirm({
title: '提示',
content: '确认加签吗?',
centered: true,
onOk: async () => {
await taskOperation(
{ taskId: props.task!.id, userIds },
'reductionSignature',
);
2024-12-17 15:08:31 +08:00
emit('reload');
},
});
}
2024-12-16 09:45:00 +08:00
</script>
<template>
<Card
v-if="task"
:body-style="{ overflowY: 'auto', height: '100%' }"
2024-12-16 14:52:05 +08:00
:loading="loading"
2024-12-16 09:45:00 +08:00
:title="`编号: ${task.id}`"
class="thin-scrollbar flex-1 overflow-y-hidden"
size="small"
>
2024-12-16 10:18:33 +08:00
<template #extra>
<a-button size="small" @click="() => handleLoadInfo(task)">
<div class="flex items-center justify-center">
<span class="icon-[material-symbols--refresh] size-24px"></span>
</div>
</a-button>
</template>
2024-12-16 09:45:00 +08:00
<div class="flex flex-col gap-5 p-4">
<div class="flex flex-col gap-3">
<div class="flex items-center gap-2">
<div class="text-2xl font-bold">{{ task.flowName }}</div>
<div>
<component
:is="renderDict(task.flowStatus, DictEnum.WF_BUSINESS_STATUS)"
/>
</div>
</div>
<div class="flex items-center gap-2">
2024-12-16 17:23:05 +08:00
<VbenAvatar
:alt="task.createByName"
class="bg-primary size-[24px] rounded-full text-white"
src=""
/>
2024-12-16 09:45:00 +08:00
<span>{{ task.createByName }}</span>
<div class="flex items-center opacity-50">
<span>XXXX有限公司</span>
<Divider type="vertical" />
<span>提交于: {{ task.createTime }}</span>
</div>
</div>
</div>
<Tabs v-if="currentFlowInfo" class="flex-1">
<TabPane key="1" tab="审批详情">
<div class="h-fulloverflow-y-auto">
2024-12-16 17:42:18 +08:00
<!-- 约定${task.formPath}/frame 为内嵌表单 用于展示 需要在本地路由添加 -->
2024-12-16 09:45:00 +08:00
<iframe
v-show="iframeLoaded"
2024-12-16 17:42:18 +08:00
:src="`${task.formPath}/iframe?readonly=true&id=${task.businessId}`"
:style="{ height: `${iframeHeight}px` }"
class="w-full"
2024-12-16 09:45:00 +08:00
></iframe>
<Skeleton v-show="!iframeLoaded" :paragraph="{ rows: 6 }" active />
2024-12-16 09:45:00 +08:00
<Divider />
<ApprovalTimeline :list="currentFlowInfo.list" />
</div>
</TabPane>
<TabPane key="2" tab="审批流程图">
<img
:src="`data:image/png;base64,${currentFlowInfo.image}`"
class="rounded-lg border"
/>
</TabPane>
</Tabs>
</div>
<!-- 固定底部 -->
2024-12-16 15:19:15 +08:00
<div class="h-[57px]"></div>
2024-12-16 09:45:00 +08:00
<div
2024-12-16 15:19:15 +08:00
v-if="showFooter"
2024-12-16 09:45:00 +08:00
class="border-t-solid bg-background absolute bottom-0 left-0 w-full border-t-[1px] p-3"
>
<div class="flex justify-end">
2024-12-16 15:19:15 +08:00
<Space v-if="type === 'myself'">
2024-12-17 15:41:07 +08:00
<a-button
v-if="revocable"
danger
type="primary"
@click="handleCancel"
>
撤销申请
</a-button>
2024-12-17 19:27:42 +08:00
<a-button v-if="editableAndRemoveable" @click="handleEdit">
重新编辑
</a-button>
2024-12-17 15:41:07 +08:00
<a-button
v-if="editableAndRemoveable"
danger
type="primary"
@click="handleRemove"
2024-12-16 15:19:15 +08:00
>
2024-12-17 15:41:07 +08:00
删除
</a-button>
2024-12-16 15:19:15 +08:00
</Space>
2024-12-17 09:06:46 +08:00
<Space v-if="type === 'approve'">
2024-12-17 09:50:19 +08:00
<a-button type="primary" @click="handleApproval">通过</a-button>
2024-12-17 09:06:46 +08:00
<a-button danger type="primary" @click="handleTermination">
终止
</a-button>
<a-button danger type="primary" @click="handleRejection">
驳回
</a-button>
<Dropdown
:get-popup-container="getPopupContainer"
placement="bottomRight"
>
<template #overlay>
<Menu>
<MenuItem key="1" @click="() => delegationModalApi.open()">
委托
</MenuItem>
<MenuItem key="2" @click="() => transferModalApi.open()">
转办
</MenuItem>
<MenuItem
v-if="showMultiActions"
key="3"
@click="() => addSignatureModalApi.open()"
>
加签
</MenuItem>
<MenuItem
v-if="showMultiActions"
key="4"
@click="() => reductionSignatureModalApi.open()"
>
减签
</MenuItem>
</Menu>
</template>
<a-button> 其他 </a-button>
</Dropdown>
2024-12-17 15:08:31 +08:00
<ApprovalModal @complete="$emit('reload')" />
<RejectionModal @complete="$emit('reload')" />
<DelegationModal mode="single" @finish="handleDelegation" />
<TransferModal mode="single" @finish="handleTransfer" />
<AddSignatureModal mode="multiple" @finish="handleAddSignature" />
<ReductionSignatureModal
mode="multiple"
@finish="handleReductionSignature"
/>
2024-12-16 09:45:00 +08:00
</Space>
2024-12-17 19:58:02 +08:00
<Space v-if="type === 'admin'">
<a-button>流程干预</a-button>
<a-button>修改办理人</a-button>
</Space>
2024-12-16 09:45:00 +08:00
</div>
</div>
</Card>
<Fallback v-else title="点击左侧选择" />
</template>