Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vben into dev
						commit
						3a740b5abd
					
				|  | @ -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> | ||||
|  |  | |||
|  | @ -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', | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -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> | ||||
|  | @ -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', | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -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> | ||||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -249,7 +249,7 @@ onMounted(() => { | |||
|       /> | ||||
|     </div> | ||||
|   </div> | ||||
|   <!-- TODO 这个好像暂时没有用到。保存失败弹窗 --> | ||||
| 
 | ||||
|   <Modal | ||||
|     v-model:open="errorDialogVisible" | ||||
|     title="保存失败" | ||||
|  |  | |||
|  | @ -0,0 +1,45 @@ | |||
| <script setup lang="ts"> | ||||
| import type { SimpleFlowNode } from '../consts'; | ||||
| 
 | ||||
| import { provide, ref, watch } from 'vue'; | ||||
| 
 | ||||
| import { useWatchNode } from '../helpers'; | ||||
| import SimpleProcessModel from './simple-process-model.vue'; | ||||
| 
 | ||||
| defineOptions({ name: 'SimpleProcessViewer' }); | ||||
| 
 | ||||
| const props = withDefaults( | ||||
|   defineProps<{ | ||||
|     flowNode: SimpleFlowNode; | ||||
|     // 流程实例 | ||||
|     processInstance?: any; | ||||
|     // 流程任务 | ||||
|     tasks?: any[]; | ||||
|   }>(), | ||||
|   { | ||||
|     processInstance: undefined, | ||||
|     tasks: () => [] as any[], | ||||
|   }, | ||||
| ); | ||||
| const approveTasks = ref<any[]>(props.tasks); | ||||
| const currentProcessInstance = ref(props.processInstance); | ||||
| const simpleModel = useWatchNode(props); | ||||
| watch( | ||||
|   () => props.tasks, | ||||
|   (newValue) => { | ||||
|     approveTasks.value = newValue; | ||||
|   }, | ||||
| ); | ||||
| watch( | ||||
|   () => props.processInstance, | ||||
|   (newValue) => { | ||||
|     currentProcessInstance.value = newValue; | ||||
|   }, | ||||
| ); | ||||
| // 提供给后代组件使用 | ||||
| provide('tasks', approveTasks); | ||||
| provide('processInstance', currentProcessInstance); | ||||
| </script> | ||||
| <template> | ||||
|   <SimpleProcessModel :flow-node="simpleModel" :readonly="true" /> | ||||
| </template> | ||||
|  | @ -4,4 +4,8 @@ export { default as HttpRequestSetting } from './components/nodes-config/modules | |||
| 
 | ||||
| export { default as SimpleProcessDesigner } from './components/simple-process-designer.vue'; | ||||
| 
 | ||||
| export { default as SimpleProcessViewer } from './components/simple-process-viewer.vue'; | ||||
| 
 | ||||
| export type { SimpleFlowNode } from './consts'; | ||||
| 
 | ||||
| export { parseFormFields } from './helpers'; | ||||
|  |  | |||
|  | @ -41,11 +41,12 @@ const props = defineProps({ | |||
| 
 | ||||
| const { hasAccessByCodes } = useAccess(); | ||||
| 
 | ||||
| /** 缓存处理后的actions */ | ||||
| /** 缓存处理后的 actions */ | ||||
| const processedActions = ref<any[]>([]); | ||||
| const processedDropdownActions = ref<any[]>([]); | ||||
| 
 | ||||
| /** 用于比较的字符串化版本 */ | ||||
| // TODO @xingyu:下面的拼写错误,需要修改 | ||||
| const actionsStringified = ref(''); | ||||
| const dropdownActionsStringified = ref(''); | ||||
| 
 | ||||
|  | @ -65,7 +66,7 @@ function isIfShow(action: ActionItem): boolean { | |||
|   return isIfShow; | ||||
| } | ||||
| 
 | ||||
| /** 处理actions的纯函数 */ | ||||
| /** 处理 actions 的纯函数 */ | ||||
| function processActions(actions: ActionItem[]): any[] { | ||||
|   return actions | ||||
|     .filter((action: ActionItem) => { | ||||
|  | @ -84,7 +85,7 @@ function processActions(actions: ActionItem[]): any[] { | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| /** 处理下拉菜单actions的纯函数 */ | ||||
| /** 处理下拉菜单 actions 的纯函数 */ | ||||
| function processDropdownActions( | ||||
|   dropDownActions: ActionItem[], | ||||
|   divider: boolean, | ||||
|  | @ -108,7 +109,7 @@ function processDropdownActions( | |||
|     }); | ||||
| } | ||||
| 
 | ||||
| /** 监听actions变化并更新缓存 */ | ||||
| /** 监听 actions 变化并更新缓存 */ | ||||
| watchEffect(() => { | ||||
|   const rawActions = toRaw(props.actions) || []; | ||||
|   const currentStringified = JSON.stringify( | ||||
|  | @ -127,7 +128,7 @@ watchEffect(() => { | |||
|   } | ||||
| }); | ||||
| 
 | ||||
| /** 监听dropDownActions变化并更新缓存 */ | ||||
| /** 监听 dropDownActions 变化并更新缓存 */ | ||||
| watchEffect(() => { | ||||
|   const rawDropDownActions = toRaw(props.dropDownActions) || []; | ||||
|   const currentStringified = JSON.stringify({ | ||||
|  | @ -154,14 +155,14 @@ const getActions = computed(() => processedActions.value); | |||
| 
 | ||||
| const getDropdownList = computed(() => processedDropdownActions.value); | ||||
| 
 | ||||
| /** 缓存Space组件的size计算结果 */ | ||||
| /** 缓存 Space 组件的 size 计算结果 */ | ||||
| const spaceSize = computed(() => { | ||||
|   return unref(getActions)?.some((item: ActionItem) => item.type === 'link') | ||||
|     ? 0 | ||||
|     : 8; | ||||
| }); | ||||
| 
 | ||||
| /** 缓存PopConfirm属性 */ | ||||
| /** 缓存 PopConfirm 属性 */ | ||||
| const popConfirmPropsMap = new Map<string, any>(); | ||||
| 
 | ||||
| function getPopConfirmProps(attrs: PopConfirm) { | ||||
|  | @ -191,7 +192,7 @@ function getPopConfirmProps(attrs: PopConfirm) { | |||
|   return originAttrs; | ||||
| } | ||||
| 
 | ||||
| /** 缓存Button属性 */ | ||||
| /** 缓存 Button 属性 */ | ||||
| const buttonPropsMap = new Map<string, any>(); | ||||
| 
 | ||||
| function getButtonProps(action: ActionItem) { | ||||
|  | @ -217,7 +218,7 @@ function getButtonProps(action: ActionItem) { | |||
|   return res; | ||||
| } | ||||
| 
 | ||||
| /** 缓存Tooltip属性 */ | ||||
| /** 缓存 Tooltip 属性 */ | ||||
| const tooltipPropsMap = new Map<string, any>(); | ||||
| 
 | ||||
| function getTooltipProps(tooltip: any | string) { | ||||
|  | @ -243,7 +244,7 @@ function handleMenuClick(e: any) { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 生成稳定的key */ | ||||
| /** 生成稳定的 key */ | ||||
| function getActionKey(action: ActionItem, index: number) { | ||||
|   return `${action.label || ''}-${action.type || ''}-${index}`; | ||||
| } | ||||
|  |  | |||
|  | @ -346,7 +346,12 @@ onMounted(async () => { | |||
|               </Row> | ||||
|             </TabPane> | ||||
| 
 | ||||
|             <TabPane tab="流程图" key="diagram" class="tab-pane-content"> | ||||
|             <TabPane | ||||
|               tab="流程图" | ||||
|               key="diagram" | ||||
|               class="tab-pane-content" | ||||
|               :force-render="true" | ||||
|             > | ||||
|               <div class="h-full"> | ||||
|                 <ProcessInstanceSimpleViewer | ||||
|                   v-show=" | ||||
|  |  | |||
|  | @ -1,9 +1,180 @@ | |||
| <script setup lang="ts"> | ||||
| defineOptions({ name: 'ProcessInstanceSimpleViewer' }); | ||||
| </script> | ||||
| <script lang="ts" setup> | ||||
| import type { SimpleFlowNode } from '#/components/simple-process-design'; | ||||
| 
 | ||||
| import { ref, watch } from 'vue'; | ||||
| 
 | ||||
| import { SimpleProcessViewer } from '#/components/simple-process-design'; | ||||
| import { BpmNodeTypeEnum, BpmTaskStatusEnum } from '#/utils'; | ||||
| 
 | ||||
| defineOptions({ name: 'BpmProcessInstanceSimpleViewer' }); | ||||
| 
 | ||||
| const props = withDefaults( | ||||
|   defineProps<{ | ||||
|     loading?: boolean; // 是否加载中 | ||||
|     modelView?: any; | ||||
|     simpleJson?: string; // Simple 模型结构数据 (json 格式) | ||||
|   }>(), | ||||
|   { | ||||
|     loading: false, | ||||
|     modelView: () => ({}), | ||||
|     simpleJson: '', | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| const simpleModel = ref<any>({}); | ||||
| // 用户任务 | ||||
| const tasks = ref([]); | ||||
| // 流程实例 | ||||
| const processInstance = ref(); | ||||
| 
 | ||||
| /** 监控模型视图 包括任务列表、进行中的活动节点编号等 */ | ||||
| watch( | ||||
|   () => props.modelView, | ||||
|   async (newModelView) => { | ||||
|     if (newModelView) { | ||||
|       tasks.value = newModelView.tasks; | ||||
|       processInstance.value = newModelView.processInstance; | ||||
|       // 已经拒绝的活动节点编号集合,只包括 UserTask | ||||
|       const rejectedTaskActivityIds: string[] = | ||||
|         newModelView.rejectedTaskActivityIds; | ||||
|       // 进行中的活动节点编号集合, 只包括 UserTask | ||||
|       const unfinishedTaskActivityIds: string[] = | ||||
|         newModelView.unfinishedTaskActivityIds; | ||||
|       // 已经完成的活动节点编号集合, 包括 UserTask、Gateway 等 | ||||
|       const finishedActivityIds: string[] = | ||||
|         newModelView.finishedTaskActivityIds; | ||||
|       // 已经完成的连线节点编号集合,只包括 SequenceFlow | ||||
|       const finishedSequenceFlowActivityIds: string[] = | ||||
|         newModelView.finishedSequenceFlowActivityIds; | ||||
|       setSimpleModelNodeTaskStatus( | ||||
|         newModelView.simpleModel, | ||||
|         newModelView.processInstance?.status, | ||||
|         rejectedTaskActivityIds, | ||||
|         unfinishedTaskActivityIds, | ||||
|         finishedActivityIds, | ||||
|         finishedSequenceFlowActivityIds, | ||||
|       ); | ||||
|       simpleModel.value = newModelView.simpleModel || {}; | ||||
|     } | ||||
|   }, | ||||
| ); | ||||
| 
 | ||||
| /** 监控模型结构数据 */ | ||||
| watch( | ||||
|   () => props.simpleJson, | ||||
|   async (value) => { | ||||
|     if (value) { | ||||
|       simpleModel.value = JSON.parse(value); | ||||
|     } | ||||
|   }, | ||||
| ); | ||||
| const setSimpleModelNodeTaskStatus = ( | ||||
|   simpleModel: SimpleFlowNode | undefined, | ||||
|   processStatus: number, | ||||
|   rejectedTaskActivityIds: string[], | ||||
|   unfinishedTaskActivityIds: string[], | ||||
|   finishedActivityIds: string[], | ||||
|   finishedSequenceFlowActivityIds: string[], | ||||
| ) => { | ||||
|   if (!simpleModel) { | ||||
|     return; | ||||
|   } | ||||
|   // 结束节点 | ||||
|   if (simpleModel.type === BpmNodeTypeEnum.END_EVENT_NODE) { | ||||
|     simpleModel.activityStatus = finishedActivityIds.includes(simpleModel.id) | ||||
|       ? processStatus | ||||
|       : BpmTaskStatusEnum.NOT_START; | ||||
|     return; | ||||
|   } | ||||
|   // 审批节点 | ||||
|   if ( | ||||
|     simpleModel.type === BpmNodeTypeEnum.START_USER_NODE || | ||||
|     simpleModel.type === BpmNodeTypeEnum.USER_TASK_NODE || | ||||
|     simpleModel.type === BpmNodeTypeEnum.TRANSACTOR_NODE || | ||||
|     simpleModel.type === BpmNodeTypeEnum.CHILD_PROCESS_NODE | ||||
|   ) { | ||||
|     simpleModel.activityStatus = BpmTaskStatusEnum.NOT_START; | ||||
|     if (rejectedTaskActivityIds.includes(simpleModel.id)) { | ||||
|       simpleModel.activityStatus = BpmTaskStatusEnum.REJECT; | ||||
|     } else if (unfinishedTaskActivityIds.includes(simpleModel.id)) { | ||||
|       simpleModel.activityStatus = BpmTaskStatusEnum.RUNNING; | ||||
|     } else if (finishedActivityIds.includes(simpleModel.id)) { | ||||
|       simpleModel.activityStatus = BpmTaskStatusEnum.APPROVE; | ||||
|     } | ||||
|     // TODO 是不是还缺一个 cancel 的状态 | ||||
|   } | ||||
|   // 抄送节点 | ||||
|   if (simpleModel.type === BpmNodeTypeEnum.COPY_TASK_NODE) { | ||||
|     // 抄送节点,只有通过和未执行状态 | ||||
|     simpleModel.activityStatus = finishedActivityIds.includes(simpleModel.id) | ||||
|       ? BpmTaskStatusEnum.APPROVE | ||||
|       : BpmTaskStatusEnum.NOT_START; | ||||
|   } | ||||
|   // 延迟器节点 | ||||
|   if (simpleModel.type === BpmNodeTypeEnum.DELAY_TIMER_NODE) { | ||||
|     // 延迟器节点,只有通过和未执行状态 | ||||
|     simpleModel.activityStatus = finishedActivityIds.includes(simpleModel.id) | ||||
|       ? BpmTaskStatusEnum.APPROVE | ||||
|       : BpmTaskStatusEnum.NOT_START; | ||||
|   } | ||||
|   // 触发器节点 | ||||
|   if (simpleModel.type === BpmNodeTypeEnum.TRIGGER_NODE) { | ||||
|     // 触发器节点,只有通过和未执行状态 | ||||
|     simpleModel.activityStatus = finishedActivityIds.includes(simpleModel.id) | ||||
|       ? BpmTaskStatusEnum.APPROVE | ||||
|       : BpmTaskStatusEnum.NOT_START; | ||||
|   } | ||||
| 
 | ||||
|   // 条件节点对应 SequenceFlow | ||||
|   if (simpleModel.type === BpmNodeTypeEnum.CONDITION_NODE) { | ||||
|     // 条件节点,只有通过和未执行状态 | ||||
|     simpleModel.activityStatus = finishedSequenceFlowActivityIds.includes( | ||||
|       simpleModel.id, | ||||
|     ) | ||||
|       ? BpmTaskStatusEnum.APPROVE | ||||
|       : BpmTaskStatusEnum.NOT_START; | ||||
|   } | ||||
|   // 网关节点 | ||||
|   if ( | ||||
|     simpleModel.type === BpmNodeTypeEnum.CONDITION_BRANCH_NODE || | ||||
|     simpleModel.type === BpmNodeTypeEnum.PARALLEL_BRANCH_NODE || | ||||
|     simpleModel.type === BpmNodeTypeEnum.INCLUSIVE_BRANCH_NODE || | ||||
|     simpleModel.type === BpmNodeTypeEnum.ROUTER_BRANCH_NODE | ||||
|   ) { | ||||
|     // 网关节点。只有通过和未执行状态 | ||||
|     simpleModel.activityStatus = finishedActivityIds.includes(simpleModel.id) | ||||
|       ? BpmTaskStatusEnum.APPROVE | ||||
|       : BpmTaskStatusEnum.NOT_START; | ||||
|     simpleModel.conditionNodes?.forEach((node) => { | ||||
|       setSimpleModelNodeTaskStatus( | ||||
|         node, | ||||
|         processStatus, | ||||
|         rejectedTaskActivityIds, | ||||
|         unfinishedTaskActivityIds, | ||||
|         finishedActivityIds, | ||||
|         finishedSequenceFlowActivityIds, | ||||
|       ); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   setSimpleModelNodeTaskStatus( | ||||
|     simpleModel.childNode, | ||||
|     processStatus, | ||||
|     rejectedTaskActivityIds, | ||||
|     unfinishedTaskActivityIds, | ||||
|     finishedActivityIds, | ||||
|     finishedSequenceFlowActivityIds, | ||||
|   ); | ||||
| }; | ||||
| </script> | ||||
| <template> | ||||
|   <div> | ||||
|     <h1>Simple BPM Viewer</h1> | ||||
|   <div v-loading="loading"> | ||||
|     <SimpleProcessViewer | ||||
|       :flow-node="simpleModel" | ||||
|       :tasks="tasks" | ||||
|       :process-instance="processInstance" | ||||
|     /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <style lang="scss" scoped></style> | ||||
|  |  | |||
|  | @ -2,10 +2,10 @@ | |||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; | ||||
| 
 | ||||
| import { h, nextTick, onMounted, ref } from 'vue'; | ||||
| import { nextTick, onMounted, ref } from 'vue'; | ||||
| import { useRoute, useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { confirm, Page } from '@vben/common-ui'; | ||||
| import { Page, prompt } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { Input, message } from 'ant-design-vue'; | ||||
| 
 | ||||
|  | @ -29,7 +29,6 @@ const processDefinitionId = query.processDefinitionId as string; | |||
| const formFields = ref<any[]>([]); | ||||
| const userList = ref<any[]>([]); // 用户列表 | ||||
| const gridReady = ref(false); // 表格是否准备好 | ||||
| const cancelReason = ref(''); // 取消原因 | ||||
| 
 | ||||
| // 表格的列需要解析表单字段,这里定义成变量,解析表单字段后再渲染 | ||||
| let Grid: any = null; | ||||
|  | @ -81,26 +80,19 @@ const handleDetail = (row: BpmProcessInstanceApi.ProcessInstance) => { | |||
| 
 | ||||
| /** 取消按钮操作 */ | ||||
| const handleCancel = async (row: BpmProcessInstanceApi.ProcessInstance) => { | ||||
|   cancelReason.value = ''; // 重置取消原因 | ||||
|   confirm({ | ||||
|   prompt({ | ||||
|     content: '请输入取消原因:', | ||||
|     title: '取消流程', | ||||
|     content: h('div', [ | ||||
|       h('p', '请输入取消原因:'), | ||||
|       h(Input, { | ||||
|         value: cancelReason.value, | ||||
|         'onUpdate:value': (val: string) => { | ||||
|           cancelReason.value = val; | ||||
|         }, | ||||
|         placeholder: '请输入取消原因', | ||||
|       }), | ||||
|     ]), | ||||
|     beforeClose: async ({ isConfirm }) => { | ||||
|       if (!isConfirm) return; | ||||
|       if (!cancelReason.value.trim()) { | ||||
|     icon: 'question', | ||||
|     component: Input, | ||||
|     modelPropName: 'value', | ||||
|     async beforeClose(scope) { | ||||
|       if (!scope.isConfirm) return; | ||||
|       if (!scope.value) { | ||||
|         message.warning('请输入取消原因'); | ||||
|         return false; | ||||
|       } | ||||
|       await cancelProcessInstanceByAdmin(row.id, cancelReason.value); | ||||
|       await cancelProcessInstanceByAdmin(row.id, scope.value); | ||||
|       return true; | ||||
|     }, | ||||
|   }).then(() => { | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ const [Modal, modalApi] = useVbenModal({ | |||
|   <Modal class="w-2/5" :title="$t('ui.widgets.qa')"> | ||||
|     <div class="mt-2 flex flex-col"> | ||||
|       <div class="mt-2 flex flex-row"> | ||||
|         <!-- TODO @xingyu:要不要垂直?1. 项目地址;2. 问题反馈;3. 开发文档 --> | ||||
|         <VbenButtonGroup class="basis-1/3" :gap="2" border size="large"> | ||||
|           <p class="p-2">项目地址:</p> | ||||
|           <VbenButton | ||||
|  |  | |||
|  | @ -46,6 +46,7 @@ async function handleChange(id: number | undefined) { | |||
| } | ||||
| </script> | ||||
| <template> | ||||
|   <!-- TODO @xingyu:1)未选择的时候,空着一块,有点怪。是不是有个 placeholder 会好看点哈(之前有 page.tenant.placeholder)?2)是不是要支持个 clear 选择 --> | ||||
|   <DropdownMenu> | ||||
|     <DropdownMenuTrigger as-child> | ||||
|       <Button | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 xingyu4j
						xingyu4j