feat: [BPM 工作流] Simple 浏览模式弹窗显示审批记录

pull/154/head
jason 2025-06-21 22:05:40 +08:00
parent 4cc6cc45b1
commit ae95eb3367
7 changed files with 283 additions and 25 deletions

View File

@ -5,7 +5,10 @@ import type { SimpleFlowNode } from '../../consts';
import { inject, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useTaskStatusClass, useWatchNode } from '../../helpers';
import ProcessInstanceModal from './modules/process-instance-modal.vue';
defineOptions({ name: 'EndEventNode' });
const props = defineProps({
@ -20,15 +23,26 @@ const currentNode = useWatchNode(props);
const readonly = inject<Boolean>('readonly');
const processInstance = inject<Ref<any>>('processInstance', ref({}));
const processInstanceInfos = ref<any[]>([]); //
const [Modal, modalApi] = useVbenModal({
connectedComponent: ProcessInstanceModal,
destroyOnClose: true,
});
function nodeClick() {
if (readonly && processInstance && processInstance.value) {
console.warn(
'TODO 只读模式,弹窗显示审批信息',
processInstance.value,
processInstanceInfos.value,
);
const processInstanceInfo = [
{
startUser: processInstance.value.startUser,
createTime: processInstance.value.startTime,
endTime: processInstance.value.endTime,
status: processInstance.value.status,
durationInMillis: processInstance.value.durationInMillis,
},
];
modalApi
.setData(processInstanceInfo)
.setState({ title: '流程信息' })
.open();
}
}
</script>
@ -42,5 +56,6 @@ function nodeClick() {
<span class="node-fixed-name" title="结束">结束</span>
</div>
</div>
<!-- TODO 审批信息 -->
<!-- 流程信息弹窗 -->
<Modal />
</template>

View File

@ -0,0 +1,56 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { DICT_TYPE } from '#/utils';
/** 流程实例列表字段 */
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'startUser',
title: '发起人',
slots: {
default: ({ row }: { row: any }) => {
return row.startUser?.nickname;
},
},
minWidth: 100,
},
{
field: 'deptName',
title: '部门',
slots: {
default: ({ row }: { row: any }) => {
return row.startUser?.deptName;
},
},
minWidth: 100,
},
{
field: 'createTime',
title: '开始时间',
formatter: 'formatDateTime',
minWidth: 140,
},
{
field: 'endTime',
title: '结束时间',
formatter: 'formatDateTime',
minWidth: 140,
},
{
field: 'status',
title: '流程状态',
minWidth: 90,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS },
},
},
{
field: 'durationInMillis',
title: '耗时',
minWidth: 100,
formatter: 'formatPast2',
},
];
}

View File

@ -0,0 +1,44 @@
<script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { useGridColumns } from './process-instance-data';
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useGridColumns(),
border: true,
height: 'auto',
pagerConfig: {
enabled: false,
},
toolbarConfig: {
enabled: false,
},
},
});
const [Modal, modalApi] = useVbenModal({
footer: false,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
return;
}
modalApi.lock();
try {
const data = modalApi.getData<any[]>();
//
await gridApi.setGridOptions({ data });
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal class="w-3/4">
<Grid />
</Modal>
</template>

View File

@ -0,0 +1,61 @@
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
import { DICT_TYPE } from '#/utils';
/** 审批记录列表字段 */
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'assigneeUser',
title: '审批人',
slots: {
default: ({ row }: { row: any }) => {
return row.assigneeUser?.nickname || row.ownerUser?.nickname;
},
},
minWidth: 100,
},
{
field: 'deptName',
title: '部门',
slots: {
default: ({ row }: { row: any }) => {
return row.assigneeUser?.deptName || row.ownerUser?.deptName;
},
},
minWidth: 100,
},
{
field: 'createTime',
title: '开始时间',
formatter: 'formatDateTime',
minWidth: 140,
},
{
field: 'endTime',
title: '结束时间',
formatter: 'formatDateTime',
minWidth: 140,
},
{
field: 'status',
title: '审批状态',
minWidth: 90,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.BPM_TASK_STATUS },
},
},
{
field: 'reason',
title: '审批建议',
minWidth: 160,
},
{
field: 'durationInMillis',
title: '耗时',
minWidth: 100,
formatter: 'formatPast2',
},
];
}

View File

@ -0,0 +1,47 @@
<script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { useGridColumns } from './task-list-data';
const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: useGridColumns(),
border: true,
height: 'auto',
rowConfig: {
keyField: 'id',
},
pagerConfig: {
enabled: false,
},
toolbarConfig: {
enabled: false,
},
},
});
const [Modal, modalApi] = useVbenModal({
footer: false,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
return;
}
modalApi.lock();
try {
const data = modalApi.getData<any[]>();
//
await gridApi.setGridOptions({ data });
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal class="w-3/4">
<Grid />
</Modal>
</template>

View File

@ -6,6 +6,7 @@ import type { SimpleFlowNode } from '../../consts';
import { inject, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Input } from 'ant-design-vue';
@ -15,6 +16,7 @@ import { BpmNodeTypeEnum } from '#/utils';
import { NODE_DEFAULT_TEXT } from '../../consts';
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
import StartUserNodeConfig from '../nodes-config/start-user-node-config.vue';
import TaskListModal from './modules/task-list-modal.vue';
import NodeHandler from './node-handler.vue';
defineOptions({ name: 'StartUserNode' });
@ -27,7 +29,6 @@ const props = defineProps({
});
//
// const emits = defineEmits<{
defineEmits<{
'update:modelValue': [node: SimpleFlowNode | undefined];
}>();
@ -44,24 +45,25 @@ const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
const nodeSetting = ref();
//
const selectTasks = ref<any[] | undefined>([]); //
const [Modal, modalApi] = useVbenModal({
connectedComponent: TaskListModal,
destroyOnClose: true,
});
function nodeClick() {
if (readonly) {
//
if (tasks && tasks.value) {
console.warn(
'TODO 只读模式,弹窗显示任务信息',
tasks.value,
selectTasks.value,
//
const nodeTasks = tasks.value.filter(
(task) => task.taskDefinitionKey === currentNode.value.id,
);
//
modalApi
.setData(nodeTasks)
.setState({ title: currentNode.value.name })
.open();
}
} else {
console.warn(
'TODO 编辑模式,打开节点配置、把当前节点传递给配置组件',
nodeSetting.value,
);
nodeSetting.value.showStartUserNodeConfig(currentNode.value);
}
}
@ -122,5 +124,6 @@ function nodeClick() {
ref="nodeSetting"
:flow-node="currentNode"
/>
<!-- 审批记录 TODO -->
<!-- 审批记录弹窗 -->
<Modal />
</template>

View File

@ -5,6 +5,7 @@ import type { SimpleFlowNode } from '../../consts';
import { inject, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Input } from 'ant-design-vue';
@ -14,6 +15,26 @@ import { BpmNodeTypeEnum } from '#/utils';
import { NODE_DEFAULT_TEXT } from '../../consts';
import { useNodeName2, useTaskStatusClass, useWatchNode } from '../../helpers';
import UserTaskNodeConfig from '../nodes-config/user-task-node-config.vue';
import TaskListModal from './modules/task-list-modal.vue';
// // 使useVbenVxeGrid
// const [Grid, gridApi] = useVbenVxeGrid({
// gridOptions: {
// columns: columns.value,
// keepSource: true,
// border: true,
// height: 'auto',
// data: selectTasks.value,
// rowConfig: {
// keyField: 'id',
// },
// pagerConfig: {
// enabled: false,
// },
// toolbarConfig: {
// enabled: false,
// },
// } as VxeTableGridOptions<any>,
// });
import NodeHandler from './node-handler.vue';
defineOptions({ name: 'UserTaskNode' });
@ -42,11 +63,23 @@ const { showInput, changeNodeName, clickTitle, inputRef } = useNodeName2(
);
const nodeSetting = ref();
const [Modal, modalApi] = useVbenModal({
connectedComponent: TaskListModal,
destroyOnClose: true,
});
function nodeClick() {
if (readonly) {
if (tasks && tasks.value) {
// TODO
console.warn('只读模式,弹窗显示任务信息待实现');
//
const nodeTasks = tasks.value.filter(
(task) => task.taskDefinitionKey === currentNode.value.id,
);
//
modalApi
.setData(nodeTasks)
.setState({ title: currentNode.value.name })
.open();
}
} else {
//
@ -64,8 +97,6 @@ function findReturnTaskNodes(
//
emits('findParentNode', matchNodeList, BpmNodeTypeEnum.USER_TASK_NODE);
}
// const selectTasks = ref<any[] | undefined>([]); //
</script>
<template>
<div class="node-wrapper">
@ -138,5 +169,6 @@ function findReturnTaskNodes(
:flow-node="currentNode"
@find-return-task-nodes="findReturnTaskNodes"
/>
<!-- TODO 审批记录 -->
<!-- 审批记录弹窗 -->
<Modal />
</template>