feat(bpm): 新增流程当前节点及办理人查询功能
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user