!84 feat(bpm): 新增流程当前节点及办理人查询功能

Merge pull request !84 from wsm/feature/query_current_assignee
This commit is contained in:
wsm
2026-07-06 06:17:28 +00:00
committed by Gitee
3 changed files with 221 additions and 2 deletions
+7
View File
@@ -20,6 +20,7 @@ enum Api {
deleteSingleProcess = '/tasktask/taskTask/deleteSingleProcess',
deletePendingTask = '/tasktask/taskTask/deletePendingTask',
updateTitleAndDeadline = '/tasktask/taskTask/updateTitleAndDeadline',
currentNodeInfo = '/act/task/currentNodeInfo',
}
/**
@@ -202,6 +203,12 @@ export const updateTitleAndDeadline = (params) => {
return defHttp.post({ url: Api.updateTitleAndDeadline, params }, { joinParamsToUrl: true, isTransformResponse: false });
};
/**
* 查询流程当前节点及办理人信息
* @param params { procInstId: string }
*/
export const queryCurrentNodeInfo = (params) => defHttp.get({ url: Api.currentNodeInfo, params });
/**
* 【用于评论功能】自定义文件上传-方法
*/
@@ -8,6 +8,7 @@
</BasicTable>
</div>
<BpmPictureModal @register="registerBpmModal" />
<CurrentNodeModal ref="currentNodeModalRef" />
<j-modal
title="修改流程信息"
:width="460"
@@ -57,6 +58,7 @@
import { deleteSingleProcess, deletePendingTask, updateTitleAndDeadline, queryByBusinessId } from '/@/api/common/api';
import { useMessage } from '/@/hooks/web/useMessage';
import TaskApprovalHistoryContent from '/@/views/super/bpm/process/personalOffice/myHandleTask/content/TaskApprovalHistoryContent.vue';
import CurrentNodeModal from './CurrentNodeModal.vue';
interface OpenParams {
businessId?: string;
@@ -71,7 +73,7 @@
const modalWidth = 'min(1280px, calc(100vw - 64px))';
const approvalHistoryModalWidth = 'min(1100px, calc(100vw - 96px))';
// 按当前列宽合计收敛横向滚动距离,避免少量列被默认宽度撑得过大。
const tableScrollX = 1680;
const tableScrollX = 1760;
// 固定表体高度,让横向滚动条停留在弹窗底部,而不是跟随少量数据行出现在中间。
const tableBodyHeight = 500;
const { createMessage } = useMessage();
@@ -86,6 +88,7 @@
requireFinishDate: undefined as string | undefined,
});
const [registerBpmModal, { openModal: bpmPicModal }] = useModal();
const currentNodeModalRef = ref();
const [registerTable, { setColumns, setTableData, reload, clearSelectedRowKeys }] = useTable({
rowKey: 'id',
@@ -110,7 +113,7 @@
actionColumn: {
title: '操作',
dataIndex: 'action',
width: 320,
width: 400,
fixed: 'right',
slots: { customRender: 'action' },
},
@@ -259,8 +262,21 @@
}
}
function handlePreviewCurrentNode(record) {
const processInstId = record?.processInstId;
if (!processInstId) {
createMessage.error('流程尚未发起,无法查询');
return;
}
currentNodeModalRef.value?.open({ processInstId });
}
function getTableAction(record) {
return [
{
label: '当前节点',
onClick: handlePreviewCurrentNode.bind(null, record),
},
{
label: '流程图',
onClick: handlePreviewPic.bind(null, record),
@@ -0,0 +1,196 @@
<template>
<j-modal
title="当前办理人"
:width="520"
:visible="visible"
:footer="null"
@cancel="handleCancel"
cancelText="关闭"
>
<div v-if="loading" style="text-align: center; padding: 40px 0;">
<a-spin tip="查询中..." />
</div>
<div v-else-if="errorMsg" style="text-align: center; padding: 40px 0;">
<a-result status="error" :title="errorMsg" />
</div>
<div v-else class="current-node-content">
<!-- 流程已结束 卡片布局与进行中统一 -->
<div v-if="nodeInfo?.finished" class="task-card finished-card">
<div class="task-card-header">
<a-tag color="success">已结束</a-tag>
<span class="task-name">该流程已结束</span>
</div>
<div class="task-card-body">
<div v-if="nodeInfo?.endTime" class="info-row">
<span class="label">结束时间</span>
<span class="value">{{ nodeInfo.endTime }}</span>
</div>
<div class="info-row">
<span class="label">状态</span>
<span class="value" style="color: #22c55e;">所有节点已完成</span>
</div>
</div>
</div>
<!-- 流程进行中 -->
<div v-else class="running-state">
<div
v-for="(task, index) in nodeInfo?.currentTasks || []"
:key="index"
class="task-card"
>
<div class="task-card-header">
<a-tag color="processing">进行中</a-tag>
<span class="task-name">{{ task.taskName }}</span>
</div>
<div class="task-card-body">
<div class="info-row">
<span class="label">当前办理人</span>
<span class="value">
<a-avatar size="small" style="margin-right: 6px; background-color: #637d8f;">
{{ (task.assigneeName || '待签收')[0] }}
</a-avatar>
{{ task.assigneeName || '待签收(无具体办理人)' }}
</span>
</div>
<div v-if="task.deptNames?.length" class="info-row">
<span class="label">所属部门</span>
<span class="value">{{ task.deptNames.join('、') }}</span>
</div>
<div v-if="task.startTime" class="info-row">
<span class="label">转入时间</span>
<span class="value">{{ task.startTime }}</span>
</div>
</div>
</div>
<div v-if="!nodeInfo?.currentTasks?.length" style="text-align: center; padding: 20px;">
暂无任务信息
</div>
</div>
</div>
</j-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
import { queryCurrentNodeInfo } from '/@/api/common/api';
interface TaskInfo {
taskId?: string;
taskName?: string;
taskDefinitionKey?: string;
startTime?: string;
assignee?: string;
assigneeName?: string;
deptNames?: string[];
}
interface NodeInfo {
finished: boolean;
endTime?: string;
currentTasks?: TaskInfo[];
}
const visible = ref(false);
const loading = ref(false);
const errorMsg = ref('');
const nodeInfo = ref<NodeInfo | null>(null);
let currentProcessInstId = '';
async function open(params: { processInstId: string; title?: string }) {
currentProcessInstId = params.processInstId;
visible.value = true;
loading.value = true;
errorMsg.value = '';
nodeInfo.value = null;
if (!currentProcessInstId) {
loading.value = false;
errorMsg.value = '流程尚未发起,无法查询';
return;
}
try {
// defHttp.get 默认自动解包 Result 包装,res 即为后端返回的 result 对象
const res = await queryCurrentNodeInfo({ procInstId: currentProcessInstId });
nodeInfo.value = res as NodeInfo;
} catch {
errorMsg.value = '查询失败,请重试';
} finally {
loading.value = false;
}
}
function handleCancel() {
visible.value = false;
loading.value = false;
errorMsg.value = '';
nodeInfo.value = null;
}
defineExpose({ open });
</script>
<style lang="less" scoped>
.current-node-content {
padding: 8px 0;
}
.finished-state {
padding: 16px 0;
}
.task-card {
margin-bottom: 12px;
padding: 16px;
background: #f8fafc;
border: 1px solid #e5e7eb;
border-radius: 6px;
&:last-child {
margin-bottom: 0;
}
.task-card-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
.task-name {
font-size: 15px;
font-weight: 600;
color: #1e293b;
}
}
.task-card-body {
.info-row {
display: flex;
align-items: center;
margin-bottom: 8px;
font-size: 13px;
&:last-child {
margin-bottom: 0;
}
.label {
width: 96px;
color: #64748b;
flex-shrink: 0;
}
.value {
color: #334155;
display: flex;
align-items: center;
}
}
}
}
</style>