feat: 【BPM 工作流 完善流程实例详情页功能 ,新增流转记录列表
parent
18c4e92418
commit
a0b40bf928
|
@ -13,6 +13,18 @@ export namespace BpmTaskApi {
|
||||||
valueType: string; // 监听器值类型
|
valueType: string; // 监听器值类型
|
||||||
value: string; // 监听器值
|
value: string; // 监听器值
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 流程实例任务 VO */
|
||||||
|
export interface ProcessInstanceTaskVO {
|
||||||
|
id: number; // 编号
|
||||||
|
name: string; // 任务名称
|
||||||
|
assigneeUser: any; // 审批人
|
||||||
|
ownerUser: any; // 创建人
|
||||||
|
durationInMillis: number; // 耗时
|
||||||
|
formConf?: any; // 表单配置
|
||||||
|
formFields?: any; // 表单字段
|
||||||
|
formVariables?: any; // 表单变量
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询待办任务分页 */
|
/** 查询待办任务分页 */
|
||||||
|
@ -54,8 +66,12 @@ export const rejectTask = async (data: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 根据流程实例 ID 查询任务列表 */
|
/** 根据流程实例 ID 查询任务列表 */
|
||||||
export const getTaskListByProcessInstanceId = async (data: any) => {
|
export const getTaskListByProcessInstanceId = async (
|
||||||
return await requestClient.get('/bpm/task/list-by-process-instance-id', data);
|
processInstanceId: string,
|
||||||
|
) => {
|
||||||
|
return await requestClient.get(
|
||||||
|
`/bpm/task/list-by-process-instance-id?processInstanceId=${processInstanceId}`,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 获取所有可退回的节点 */
|
/** 获取所有可退回的节点 */
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
|
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
|
||||||
import type { SystemUserApi } from '#/api/system/user';
|
import type { SystemUserApi } from '#/api/system/user';
|
||||||
|
|
||||||
import { nextTick, onMounted, ref } from 'vue';
|
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import { formatDateTime } from '@vben/utils';
|
import { formatDateTime } from '@vben/utils';
|
||||||
|
@ -39,6 +39,9 @@ import {
|
||||||
SvgBpmRunningIcon,
|
SvgBpmRunningIcon,
|
||||||
} from '#/views/bpm/processInstance/detail/modules/icons';
|
} from '#/views/bpm/processInstance/detail/modules/icons';
|
||||||
|
|
||||||
|
import ProcessInstanceBpmnViewer from './modules/bpm-viewer.vue';
|
||||||
|
import ProcessInstanceSimpleViewer from './modules/simple-bpm-viewer.vue';
|
||||||
|
import BpmProcessInstanceTaskList from './modules/task-list.vue';
|
||||||
import ProcessInstanceTimeline from './modules/time-line.vue';
|
import ProcessInstanceTimeline from './modules/time-line.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'BpmProcessInstanceDetail' });
|
defineOptions({ name: 'BpmProcessInstanceDetail' });
|
||||||
|
@ -221,6 +224,20 @@ const setFieldPermission = (field: string, permission: string) => {
|
||||||
|
|
||||||
/** 当前的Tab */
|
/** 当前的Tab */
|
||||||
const activeTab = ref('form');
|
const activeTab = ref('form');
|
||||||
|
const taskListRef = ref();
|
||||||
|
|
||||||
|
// 监听 Tab 切换,当切换到 "record" 标签时刷新任务列表
|
||||||
|
watch(
|
||||||
|
() => activeTab.value,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal === 'record') {
|
||||||
|
// 如果切换到流转记录标签,刷新任务列表
|
||||||
|
nextTick(() => {
|
||||||
|
taskListRef.value?.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
const userOptions = ref<SystemUserApi.User[]>([]); // 用户列表
|
||||||
|
@ -291,18 +308,25 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 流程操作 -->
|
<!-- 流程操作 -->
|
||||||
<div class="flex-1">
|
<div class="process-tabs-container flex flex-1 flex-col">
|
||||||
<Tabs v-model:active-key="activeTab" class="mt-0">
|
<Tabs v-model:active-key="activeTab" class="mt-0 h-full">
|
||||||
<TabPane tab="审批详情" key="form">
|
<TabPane tab="审批详情" key="form" class="tab-pane-content">
|
||||||
<Row :gutter="[48, 24]">
|
<Row :gutter="[48, 24]" class="h-full">
|
||||||
<Col :xs="24" :sm="24" :md="18" :lg="18" :xl="16">
|
<Col
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="18"
|
||||||
|
:lg="18"
|
||||||
|
:xl="16"
|
||||||
|
class="h-full"
|
||||||
|
>
|
||||||
<!-- 流程表单 -->
|
<!-- 流程表单 -->
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
processDefinition?.formType === BpmModelFormType.NORMAL
|
processDefinition?.formType === BpmModelFormType.NORMAL
|
||||||
"
|
"
|
||||||
|
class="h-full"
|
||||||
>
|
>
|
||||||
<!-- v-model="detailForm.value" -->
|
|
||||||
<form-create
|
<form-create
|
||||||
v-model="detailForm.value"
|
v-model="detailForm.value"
|
||||||
v-model:api="fApi"
|
v-model:api="fApi"
|
||||||
|
@ -315,29 +339,58 @@ onMounted(async () => {
|
||||||
v-if="
|
v-if="
|
||||||
processDefinition?.formType === BpmModelFormType.CUSTOM
|
processDefinition?.formType === BpmModelFormType.CUSTOM
|
||||||
"
|
"
|
||||||
|
class="h-full"
|
||||||
>
|
>
|
||||||
<BusinessFormComponent :id="processInstance?.businessKey" />
|
<BusinessFormComponent :id="processInstance?.businessKey" />
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col :xs="24" :sm="24" :md="6" :lg="6" :xl="8">
|
<Col :xs="24" :sm="24" :md="6" :lg="6" :xl="8" class="h-full">
|
||||||
<div class="mt-2">
|
<div class="mt-4 h-full">
|
||||||
<ProcessInstanceTimeline :activity-nodes="activityNodes" />
|
<ProcessInstanceTimeline :activity-nodes="activityNodes" />
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tab="流程图" key="diagram">
|
<TabPane tab="流程图" key="diagram" class="tab-pane-content">
|
||||||
<div>待开发</div>
|
<div class="h-full">
|
||||||
|
<ProcessInstanceSimpleViewer
|
||||||
|
v-show="
|
||||||
|
processDefinition.modelType &&
|
||||||
|
processDefinition.modelType === BpmModelType.SIMPLE
|
||||||
|
"
|
||||||
|
:loading="processInstanceLoading"
|
||||||
|
:model-view="processModelView"
|
||||||
|
/>
|
||||||
|
<ProcessInstanceBpmnViewer
|
||||||
|
v-show="
|
||||||
|
processDefinition.modelType &&
|
||||||
|
processDefinition.modelType === BpmModelType.BPMN
|
||||||
|
"
|
||||||
|
:loading="processInstanceLoading"
|
||||||
|
:model-view="processModelView"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tab="流转记录" key="record">
|
<TabPane tab="流转记录" key="record" class="tab-pane-content">
|
||||||
<div>待开发</div>
|
<div class="h-full">
|
||||||
|
<BpmProcessInstanceTaskList
|
||||||
|
ref="taskListRef"
|
||||||
|
:loading="processInstanceLoading"
|
||||||
|
:id="id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<!-- TODO 待开发 -->
|
<!-- TODO 待开发 -->
|
||||||
<TabPane tab="流转评论" key="comment" v-if="false">
|
<TabPane
|
||||||
<div>待开发</div>
|
tab="流转评论"
|
||||||
|
key="comment"
|
||||||
|
v-if="false"
|
||||||
|
class="tab-pane-content"
|
||||||
|
>
|
||||||
|
<div class="h-full">待开发</div>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
@ -352,3 +405,35 @@ onMounted(async () => {
|
||||||
</Card>
|
</Card>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ant-tabs-content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-tabs-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-content) {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-tabs-tabpane) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane-content {
|
||||||
|
height: calc(100vh - 440px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'ProcessInstanceBpmViewer',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>BPM 流程实例查看</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'BpmProcessInstanceOperationButton',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>BPM 流程操作按钮</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'BpmProcessInstanceSignature',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>BPM 流程签名</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'BpmProcessInstanceSimpleViewer',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>BPM 简单流程设计图查看</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,218 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { formCreate } from '@form-create/antd-designer';
|
||||||
|
|
||||||
|
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
||||||
|
|
||||||
|
import type { BpmTaskApi } from '#/api/bpm/task';
|
||||||
|
|
||||||
|
import { nextTick, onMounted, ref, shallowRef } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getTaskListByProcessInstanceId } from '#/api/bpm/task';
|
||||||
|
import { DICT_TYPE, formatPast2, setConfAndFields2 } from '#/utils';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'BpmProcessInstanceTaskList',
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
id: string;
|
||||||
|
loading: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 使用shallowRef减少不必要的深度响应
|
||||||
|
const columns = shallowRef([
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '审批节点',
|
||||||
|
minWidth: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'approver',
|
||||||
|
title: '审批人',
|
||||||
|
slots: {
|
||||||
|
default: ({ row }: { row: BpmTaskApi.ProcessInstanceTaskVO }) => {
|
||||||
|
return row.assigneeUser?.nickname || row.ownerUser?.nickname;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
minWidth: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createTime',
|
||||||
|
title: '开始时间',
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
minWidth: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'endTime',
|
||||||
|
title: '结束时间',
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
minWidth: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '审批状态',
|
||||||
|
minWidth: 150,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellDict',
|
||||||
|
props: { type: DICT_TYPE.BPM_TASK_STATUS },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'reason',
|
||||||
|
title: '审批建议',
|
||||||
|
slots: {
|
||||||
|
default: 'slot-reason',
|
||||||
|
},
|
||||||
|
minWidth: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'durationInMillis',
|
||||||
|
title: '耗时',
|
||||||
|
minWidth: 180,
|
||||||
|
slots: {
|
||||||
|
default: ({ row }: { row: BpmTaskApi.ProcessInstanceTaskVO }) => {
|
||||||
|
return formatPast2(row.durationInMillis);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Grid配置和API
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: columns.value,
|
||||||
|
keepSource: true,
|
||||||
|
showFooter: true,
|
||||||
|
border: true,
|
||||||
|
height: 'auto',
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async () => {
|
||||||
|
return await getTaskListByProcessInstanceId(props.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
cellConfig: {
|
||||||
|
height: 60,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新表格数据
|
||||||
|
*/
|
||||||
|
const refresh = (): void => {
|
||||||
|
gridApi.query();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表单相关
|
||||||
|
interface TaskForm {
|
||||||
|
rule: any[];
|
||||||
|
option: Record<string, any>;
|
||||||
|
value: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义表单组件引用类型
|
||||||
|
|
||||||
|
// 使用明确的类型定义
|
||||||
|
const formRef = ref<formCreate>();
|
||||||
|
const taskForm = ref<TaskForm>({
|
||||||
|
rule: [],
|
||||||
|
option: {},
|
||||||
|
value: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示表单详情
|
||||||
|
* @param row 任务数据
|
||||||
|
*/
|
||||||
|
async function showFormDetail(
|
||||||
|
row: BpmTaskApi.ProcessInstanceTaskVO,
|
||||||
|
): Promise<void> {
|
||||||
|
// 设置表单配置和表单字段
|
||||||
|
taskForm.value = {
|
||||||
|
rule: [],
|
||||||
|
option: {},
|
||||||
|
value: row,
|
||||||
|
};
|
||||||
|
setConfAndFields2(taskForm, row.formConf, row.formFields, row.formVariables);
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
modalApi.open();
|
||||||
|
|
||||||
|
// 等待表单渲染
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// 获取表单API实例
|
||||||
|
const fapi = formRef.value?.fapi;
|
||||||
|
if (!fapi) return;
|
||||||
|
|
||||||
|
// 设置表单不可编辑
|
||||||
|
fapi.btn.show(false);
|
||||||
|
fapi.resetBtn.show(false);
|
||||||
|
fapi.disabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单查看模态框
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
title: '查看表单',
|
||||||
|
footer: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露刷新方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
refresh,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex h-full flex-col">
|
||||||
|
<Grid>
|
||||||
|
<template #slot-reason="{ row }">
|
||||||
|
<div class="flex flex-wrap items-center justify-center">
|
||||||
|
<span v-if="row.reason">{{ row.reason }}</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
v-if="row.formId > 0"
|
||||||
|
type="primary"
|
||||||
|
@click="showFormDetail(row)"
|
||||||
|
size="small"
|
||||||
|
ghost
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
<IconifyIcon icon="ep:document" />
|
||||||
|
<span class="!ml-[2px] text-[12px]">查看表单</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
<Modal class="w-[800px]">
|
||||||
|
<form-create
|
||||||
|
ref="formRef"
|
||||||
|
v-model="taskForm.value"
|
||||||
|
:option="taskForm.option"
|
||||||
|
:rule="taskForm.rule"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
Loading…
Reference in New Issue