diff --git a/README.md b/README.md index 099692fb860e6335d842810ff694a26a4da52e98..a9760ec45c0c351d2cbf21fcdad2549fe51ffe5b 100644 --- a/README.md +++ b/README.md @@ -117,54 +117,77 @@ ### 工作流程 -| | 功能 | 描述 | -|----|-------|-----------------------------------------| -| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 | -| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 | -| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 | -| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 | -| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 | -| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 | -| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 | - ![功能图](/.image/common/bpm-feature.png) +基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作: + | BPMN 设计器 | 钉钉/飞书设计器 | |------------------------------|--------------------------------| | ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) | +> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!! +> +> 前者支持轻量配置简单流程,后者实现复杂场景深度编排 + +| 功能列表 | 功能描述 | 是否完成 | +|------------|-------------------------------------------------------------------------------------|------| +| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ | +| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ | +| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ | +| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ | +| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ | +| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ | +| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ | +| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ | +| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ | +| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ | +| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ | +| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ | +| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ | +| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ | +| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ | +| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ | +| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ | +| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ | +| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ | +| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ | +| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ | +| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ | +| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ | +| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ | + ### 支付系统 | | 功能 | 描述 | |-----|------|---------------------------| -| 🚀 | 商户信息 | 管理商户信息,支持 Saas 场景下的多商户功能 | | 🚀 | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 | | 🚀 | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单 | | 🚀 | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单 | - -ps:核心功能已经实现,正在对接微信小程序中... +| 🚀 | 回调通知 | 查看支付回调业务的【支付】【退款】的通知结果 | +| 🚀 | 接入示例 | 提供接入支付系统的【支付】【退款】的功能实战 | ### 基础设施 -| | 功能 | 描述 | -|----|----------|----------------------------------------------| -| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 | -| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 | -| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 | -| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 | -| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 | -| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 | -| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 | -| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 | -| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 | -| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 | -| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 | -| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 | -| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 | -| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 | -| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 | -| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 | -| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 | +| | 功能 | 描述 | +|-----|-----------|----------------------------------------------| +| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 | +| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 | +| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 | +| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 | +| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 | +| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 | +| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 | +| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 | +| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 | +| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 | +| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 | +| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 | +| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 | +| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 | +| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 | +| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 | +| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 | +| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 | ![功能图](/.image/common/infra-feature.png) diff --git a/build/vite/optimize.ts b/build/vite/optimize.ts index aa7e68c9cce9548c01e007e75da7e1867472682f..9de496a30ab69d5b7a13b1def823ad786f6a272b 100644 --- a/build/vite/optimize.ts +++ b/build/vite/optimize.ts @@ -114,7 +114,8 @@ const include = [ 'element-plus/es/components/segmented/style/css', '@element-plus/icons-vue', 'element-plus/es/components/footer/style/css', - 'element-plus/es/components/empty/style/css' + 'element-plus/es/components/empty/style/css', + 'element-plus/es/components/mention/style/css' ] const exclude = ['@iconify/json'] diff --git a/src/api/bpm/definition/index.ts b/src/api/bpm/definition/index.ts index caedba14e4e486f135f6ca891e94df7b2a2bb4ad..c917787fa95f3279004cc1987f3be5676c4f62ab 100644 --- a/src/api/bpm/definition/index.ts +++ b/src/api/bpm/definition/index.ts @@ -20,3 +20,9 @@ export const getProcessDefinitionList = async (params) => { params }) } + +export const getSimpleProcessDefinitionList = async () => { + return await request.get({ + url: '/bpm/process-definition/simple-list' + }) +} diff --git a/src/api/bpm/processInstance/index.ts b/src/api/bpm/processInstance/index.ts index 9a99a91e252751d288f1e2c496acfc027c570dec..06392bc435f41752c62cd6f2c6e9bad5febebdac 100644 --- a/src/api/bpm/processInstance/index.ts +++ b/src/api/bpm/processInstance/index.ts @@ -90,7 +90,12 @@ export const getProcessInstanceCopyPage = async (params: any) => { // 获取审批详情 export const getApprovalDetail = async (params: any) => { - return await request.get({ url: 'bpm/process-instance/get-approval-detail', params }) + return await request.get({ url: '/bpm/process-instance/get-approval-detail', params }) +} + +// 获取下一个执行的流程节点 +export const getNextApprovalNodes = async (params: any) => { + return await request.get({ url: '/bpm/process-instance/get-next-approval-nodes', params }) } // 获取表单字段权限 diff --git a/src/assets/svgs/bpm/child-process.svg b/src/assets/svgs/bpm/child-process.svg new file mode 100644 index 0000000000000000000000000000000000000000..249723f741aa797913f38c8f53fad5130c138b8e --- /dev/null +++ b/src/assets/svgs/bpm/child-process.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svgs/bpm/transactor.svg b/src/assets/svgs/bpm/transactor.svg new file mode 100644 index 0000000000000000000000000000000000000000..a9547a7dda353a47166eb476b25ae85a44b77e04 --- /dev/null +++ b/src/assets/svgs/bpm/transactor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue index b3f62340a2e7595da3898d2b5abe43cfb3e8bbb1..25e4ed74f912f7f64a5478589516f327cbdb5e02 100644 --- a/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue +++ b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue @@ -15,6 +15,12 @@
审批人
+
+
+ +
+
办理人
+
@@ -57,7 +63,13 @@
触发器
- +
+
+ +
+
子流程
+
+ @@ -78,7 +90,7 @@ import { SimpleFlowNode, DEFAULT_CONDITION_GROUP_VALUE } from './consts' -import {generateUUID} from '@/utils' +import { generateUUID } from '@/utils' defineOptions({ name: 'NodeHandler' @@ -114,13 +126,13 @@ const addNode = (type: number) => { } popoverShow.value = false - if (type === NodeType.USER_TASK_NODE) { + if (type === NodeType.USER_TASK_NODE || type === NodeType.TRANSACTOR_NODE) { const id = 'Activity_' + generateUUID() const data: SimpleFlowNode = { id: id, - name: NODE_DEFAULT_NAME.get(NodeType.USER_TASK_NODE) as string, + name: NODE_DEFAULT_NAME.get(type) as string, showText: '', - type: NodeType.USER_TASK_NODE, + type: type, approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, // 超时处理 rejectHandler: { @@ -277,6 +289,31 @@ const addNode = (type: number) => { } emits('update:childNode', data) } + if (type === NodeType.CHILD_PROCESS_NODE) { + const data: SimpleFlowNode = { + id: 'Activity_' + generateUUID(), + name: NODE_DEFAULT_NAME.get(NodeType.CHILD_PROCESS_NODE) as string, + showText: '', + type: NodeType.CHILD_PROCESS_NODE, + childNode: props.childNode, + childProcessSetting: { + calledProcessDefinitionKey: '', + calledProcessDefinitionName: '', + async: false, + skipStartUserNode: false, + startUserSetting: { + type: 1 + }, + timeoutSetting: { + enable: false + }, + multiInstanceSetting: { + enable: false + } + } + } + emits('update:childNode', data) + } } diff --git a/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue index 048764c1ab03aab5c5d284b07c24aa9c54f0a2a3..dddeda690bd534eac0540ec954e3d00d4a96afaa 100644 --- a/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue +++ b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue @@ -6,7 +6,11 @@ /> - - + + +
-
+
@@ -23,10 +23,19 @@ {{ scaleValue }}% + 重置
-
+
@@ -76,11 +85,51 @@ const emits = defineEmits<{ const processNodeTree = useWatchNode(props) provide('readonly', props.readonly) + +// TODO 可优化:拖拽有点卡顿 +/** 拖拽、放大缩小等操作 */ let scaleValue = ref(100) const MAX_SCALE_VALUE = 200 const MIN_SCALE_VALUE = 50 +const isDragging = ref(false) +const startX = ref(0) +const startY = ref(0) +const currentX = ref(0) +const currentY = ref(0) +const initialX = ref(0) +const initialY = ref(0) + +const setGrabCursor = () => { + document.body.style.cursor = 'grab' +} + +const resetCursor = () => { + document.body.style.cursor = 'default' +} + +const startDrag = (e: MouseEvent) => { + isDragging.value = true + startX.value = e.clientX - currentX.value + startY.value = e.clientY - currentY.value + setGrabCursor() // 设置小手光标 +} + +const onDrag = (e: MouseEvent) => { + if (!isDragging.value) return + e.preventDefault() // 禁用文本选择 + + // 使用 requestAnimationFrame 优化性能 + requestAnimationFrame(() => { + currentX.value = e.clientX - startX.value + currentY.value = e.clientY - startY.value + }) +} + +const stopDrag = () => { + isDragging.value = false + resetCursor() // 重置光标 +} -// 放大 const zoomIn = () => { if (scaleValue.value == MAX_SCALE_VALUE) { return @@ -88,7 +137,6 @@ const zoomIn = () => { scaleValue.value += 10 } -// 缩小 const zoomOut = () => { if (scaleValue.value == MIN_SCALE_VALUE) { return @@ -100,20 +148,15 @@ const processReZoom = () => { scaleValue.value = 100 } +const resetPosition = () => { + currentX.value = initialX.value + currentY.value = initialY.value +} + +/** 校验节点设置 */ const errorDialogVisible = ref(false) let errorNodes: SimpleFlowNode[] = [] -const saveSimpleFlowModel = async () => { - errorNodes = [] - validateNode(processNodeTree.value, errorNodes) - if (errorNodes.length > 0) { - errorDialogVisible.value = true - return - } - emits('save', processNodeTree.value) -} - -// 校验节点设置。 暂时以 showText 为空 未节点错误配置 const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => { if (node) { const { type, showText, conditionNodes } = node @@ -193,6 +236,30 @@ const importLocalFile = () => { } } } + +// 在组件初始化时记录初始位置 +onMounted(() => { + initialX.value = currentX.value + initialY.value = currentY.value +}) - + diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue index abf73b481e110e7e55e65f82c13455235270ae1f..26cd43fd3866eeec7d7b78583f1eb2e497548354 100644 --- a/src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue +++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessViewer.vue @@ -45,4 +45,3 @@ watch( provide('tasks', approveTasks) provide('processInstance', currentProcessInstance) -p diff --git a/src/components/SimpleProcessDesignerV2/src/consts.ts b/src/components/SimpleProcessDesignerV2/src/consts.ts index f0614eefa9a7ca5495da35c339b7301da2bd5a22..f564c3f06b4e2cf505b4207b2ff7da48413f94fb 100644 --- a/src/components/SimpleProcessDesignerV2/src/consts.ts +++ b/src/components/SimpleProcessDesignerV2/src/consts.ts @@ -23,6 +23,11 @@ export enum NodeType { */ COPY_TASK_NODE = 12, + /** + * 办理人节点 + */ + TRANSACTOR_NODE = 13, + /** * 延迟器节点 */ @@ -33,6 +38,11 @@ export enum NodeType { */ TRIGGER_NODE = 15, + /** + * 子流程节点 + */ + CHILD_PROCESS_NODE = 20, + /** * 条件节点 */ @@ -123,6 +133,8 @@ export interface SimpleFlowNode { reasonRequire?: boolean // 触发器设置 triggerSetting?: TriggerSetting + // 子流程 + childProcessSetting?: ChildProcessSetting } // 候选人策略枚举 ( 用于审批节点。抄送节点 ) export enum CandidateStrategy { @@ -150,6 +162,10 @@ export enum CandidateStrategy { * 指定用户 */ USER = 30, + /** + * 审批人自选 + */ + APPROVE_USER_SELECT = 34, /** * 发起人自选 */ @@ -506,6 +522,8 @@ NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人') NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器') NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点') NODE_DEFAULT_TEXT.set(NodeType.TRIGGER_NODE, '请设置触发器') +NODE_DEFAULT_TEXT.set(NodeType.TRANSACTOR_NODE, '请设置办理人') +NODE_DEFAULT_TEXT.set(NodeType.CHILD_PROCESS_NODE, '请设置子流程') export const NODE_DEFAULT_NAME = new Map() NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人') @@ -515,15 +533,20 @@ NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人') NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器') NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支') NODE_DEFAULT_NAME.set(NodeType.TRIGGER_NODE, '触发器') +NODE_DEFAULT_NAME.set(NodeType.TRANSACTOR_NODE, '办理人') +NODE_DEFAULT_NAME.set(NodeType.CHILD_PROCESS_NODE, '子流程') // 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序 export const CANDIDATE_STRATEGY: DictDataVO[] = [ { label: '指定成员', value: CandidateStrategy.USER }, { label: '指定角色', value: CandidateStrategy.ROLE }, + { label: '指定岗位', value: CandidateStrategy.POST }, { label: '部门成员', value: CandidateStrategy.DEPT_MEMBER }, { label: '部门负责人', value: CandidateStrategy.DEPT_LEADER }, { label: '连续多级部门负责人', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER }, + { label: '指定岗位', value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER }, { label: '发起人自选', value: CandidateStrategy.START_USER_SELECT }, + { label: '审批人自选', value: CandidateStrategy.APPROVE_USER_SELECT }, { label: '发起人本人', value: CandidateStrategy.START_USER }, { label: '发起人部门负责人', value: CandidateStrategy.START_USER_DEPT_LEADER }, { label: '发起人连续部门负责人', value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER }, @@ -627,6 +650,16 @@ export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ { id: OperationButtonType.RETURN, displayName: '退回', enable: true } ] +// 办理人默认的按钮权限设置 +export const TRANSACTOR_DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '办理', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: false }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false }, + { id: OperationButtonType.RETURN, displayName: '退回', enable: false } +] + // 发起人的按钮权限。暂时定死,不可以编辑 export const START_USER_BUTTON_SETTING: ButtonSetting[] = [ { id: OperationButtonType.APPROVE, displayName: '提交', enable: true }, @@ -717,7 +750,7 @@ export type RouterSetting = { export type TriggerSetting = { type: TriggerTypeEnum httpRequestSetting?: HttpRequestTriggerSetting - normalFormSetting?: NormalFormTriggerSetting + formSettings?: FormTriggerSetting[] } /** @@ -729,9 +762,17 @@ export enum TriggerTypeEnum { */ HTTP_REQUEST = 1, /** - * 更新流程表单触发器 + * 接收 HTTP 回调请求触发器 */ - UPDATE_NORMAL_FORM = 2 // TODO @jason:FORM_UPDATE? + HTTP_CALLBACK = 2, + /** + * 表单数据更新触发器 + */ + FORM_UPDATE = 10, + /** + * 表单数据删除触发器 + */ + FORM_DELETE = 11 } /** @@ -751,12 +792,110 @@ export type HttpRequestTriggerSetting = { /** * 流程表单触发器配置结构定义 */ -export type NormalFormTriggerSetting = { - // 更新表单字段 +export type FormTriggerSetting = { + // 条件类型 + conditionType?: ConditionType + // 条件表达式 + conditionExpression?: string + // 条件组 + conditionGroups?: ConditionGroup + // 更新表单字段配置 updateFormFields?: Record + // 删除表单字段配置 + deleteFields?: string[] } export const TRIGGER_TYPES: DictDataVO[] = [ - { label: 'HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST }, - { label: '修改表单数据', value: TriggerTypeEnum.UPDATE_NORMAL_FORM } + { label: '发送 HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST }, + { label: '接收 HTTP 回调', value: TriggerTypeEnum.HTTP_CALLBACK }, + { label: '修改表单数据', value: TriggerTypeEnum.FORM_UPDATE }, + { label: '删除表单数据', value: TriggerTypeEnum.FORM_DELETE } +] + +/** + * 子流程节点结构定义 + */ +export type ChildProcessSetting = { + calledProcessDefinitionKey: string + calledProcessDefinitionName: string + async: boolean + inVariables?: IOParameter[] + outVariables?: IOParameter[] + skipStartUserNode: boolean + startUserSetting: StartUserSetting + timeoutSetting: TimeoutSetting + multiInstanceSetting: MultiInstanceSetting +} +export type IOParameter = { + source: string + target: string +} +export type StartUserSetting = { + type: ChildProcessStartUserTypeEnum + formField?: string + emptyType?: ChildProcessStartUserEmptyTypeEnum +} +export type TimeoutSetting = { + enable: boolean + type?: DelayTypeEnum + timeExpression?: string +} +export type MultiInstanceSetting = { + enable: boolean + sequential?: boolean + approveRatio?: number + sourceType?: ChildProcessMultiInstanceSourceTypeEnum + source?: string +} +export enum ChildProcessStartUserTypeEnum { + /** + * 同主流程发起人 + */ + MAIN_PROCESS_START_USER = 1, + /** + * 表单 + */ + FROM_FORM = 2 +} +export const CHILD_PROCESS_START_USER_TYPE = [ + { label: '同主流程发起人', value: ChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER }, + { label: '表单', value: ChildProcessStartUserTypeEnum.FROM_FORM } +] +export enum ChildProcessStartUserEmptyTypeEnum { + /** + * 同主流程发起人 + */ + MAIN_PROCESS_START_USER = 1, + /** + * 子流程管理员 + */ + CHILD_PROCESS_ADMIN = 2, + /** + * 主流程管理员 + */ + MAIN_PROCESS_ADMIN = 3 +} +export const CHILD_PROCESS_START_USER_EMPTY_TYPE = [ + { label: '同主流程发起人', value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER }, + { label: '子流程管理员', value: ChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN }, + { label: '主流程管理员', value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN } +] +export enum ChildProcessMultiInstanceSourceTypeEnum { + /** + * 固定数量 + */ + FIXED_QUANTITY = 1, + /** + * 数字表单 + */ + NUMBER_FORM = 2, + /** + * 多选表单 + */ + MULTIPLE_FORM = 3 +} +export const CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = [ + { label: '固定数量', value: ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY }, + { label: '数字表单', value: ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM }, + { label: '多选表单', value: ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM } ] diff --git a/src/components/SimpleProcessDesignerV2/src/node.ts b/src/components/SimpleProcessDesignerV2/src/node.ts index 407fd4837933520d8a3cbc907a37616ee107c138..5b754bfac57564a34094640b9b8095e2ae4c151a 100644 --- a/src/components/SimpleProcessDesignerV2/src/node.ts +++ b/src/components/SimpleProcessDesignerV2/src/node.ts @@ -15,7 +15,10 @@ import { AssignEmptyHandlerType, FieldPermissionType, HttpRequestParam, - ProcessVariableEnum + ProcessVariableEnum, + ConditionType, + ConditionGroup, + COMPARISON_OPERATORS } from './consts' import { parseFormFields } from '@/components/FormCreate/src/utils' @@ -201,7 +204,7 @@ export function useNodeForm(nodeType: NodeType) { const deptTreeOptions = inject('deptTree', ref()) // 部门树 const formFields = inject>('formFields', ref([])) // 流程表单字段 const configForm = ref() - if (nodeType === NodeType.USER_TASK_NODE) { + if (nodeType === NodeType.USER_TASK_NODE || nodeType === NodeType.TRANSACTOR_NODE) { configForm.value = { candidateStrategy: CandidateStrategy.USER, approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, @@ -307,6 +310,11 @@ export function useNodeForm(nodeType: NodeType) { showText = `表单内部门负责人` } + // 审批人自选 + if (configForm.value?.candidateStrategy === CandidateStrategy.APPROVE_USER_SELECT) { + showText = `审批人自选` + } + // 发起人自选 if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER_SELECT) { showText = `发起人自选` @@ -543,6 +551,66 @@ export function useTaskStatusClass(taskStatus: TaskStatusEnum | undefined): stri if (taskStatus === TaskStatusEnum.CANCEL) { return 'status-cancel' } - return '' } + +/** 条件组件文字展示 */ +export function getConditionShowText( + conditionType: ConditionType | undefined, + conditionExpression: string | undefined, + conditionGroups: ConditionGroup | undefined, + fieldOptions: Array> +) { + let showText = '' + if (conditionType === ConditionType.EXPRESSION) { + if (conditionExpression) { + showText = `表达式:${conditionExpression}` + } + } + if (conditionType === ConditionType.RULE) { + // 条件组是否为与关系 + const groupAnd = conditionGroups?.and + let warningMessage: undefined | string = undefined + const conditionGroup = conditionGroups?.conditions.map((item) => { + return ( + '(' + + item.rules + .map((rule) => { + if (rule.leftSide && rule.rightSide) { + return ( + getFormFieldTitle(fieldOptions, rule.leftSide) + + ' ' + + getOpName(rule.opCode) + + ' ' + + rule.rightSide + ) + } else { + // 有一条规则不完善。提示错误 + warningMessage = '请完善条件规则' + return '' + } + }) + .join(item.and ? ' 且 ' : ' 或 ') + + ' ) ' + ) + }) + if (warningMessage) { + showText = '' + } else { + showText = conditionGroup!.join(groupAnd ? ' 且 ' : ' 或 ') + } + } + return showText +} + +/** 获取表单字段名称*/ +const getFormFieldTitle = (fieldOptions: Array>, field: string) => { + const item = fieldOptions.find((item) => item.field === field) + return item?.title +} + +/** 获取操作符名称 */ +const getOpName = (opCode: string): string => { + const opName = COMPARISON_OPERATORS.find((item: any) => item.value === opCode) + return opName?.label +} diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/ChildProcessNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/ChildProcessNodeConfig.vue new file mode 100644 index 0000000000000000000000000000000000000000..7ec382fca0dacf10ce8457e006d51f667bc1731c --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/ChildProcessNodeConfig.vue @@ -0,0 +1,610 @@ + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue index e9b387a7ff5bdb3116d66cd6a876b519da901d56..9020d655e0f57ea7bde7cd2aba2e0116b55603c7 100644 --- a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue +++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue @@ -43,15 +43,12 @@ diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue index 039e4c80dcbf2908f397622d32dfc6efa5f9640e..9a0a9feffe349a9fc7a8ecdd460b282321ab20c8 100644 --- a/src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue +++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue @@ -1,5 +1,5 @@ + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/ChildProcessNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/ChildProcessNode.vue new file mode 100644 index 0000000000000000000000000000000000000000..0b362446daced6e9293ccef95b96f25b8c0af83c --- /dev/null +++ b/src/components/SimpleProcessDesignerV2/src/nodes/ChildProcessNode.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue index 47ef540cc9e1229db7d2986ff42eff60fb6db84e..ae1af6c287957627fcdc5b0febe729ad618593be 100644 --- a/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue +++ b/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue @@ -9,7 +9,14 @@ ]" >
-
+
+ + +
- {{ NODE_DEFAULT_TEXT.get(NodeType.USER_TASK_NODE) }} + {{ NODE_DEFAULT_TEXT.get(currentNode.type) }}
diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf b/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf index 58f31c36179719bbeed7b42238b838ff9c3d4d49..06f4e31c4b4ce9868cd39b25f6beb1fd5884ff9c 100644 Binary files a/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf and b/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf differ diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff index f4b4f3de95d8fb8c727b52d9a06b18e22a566d85..0724e75054dc761f7cec794944cc140f3056bebf 100644 Binary files a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff and b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff differ diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 index d66f968531f29868912969439cc3634ae976b9c1..c904bb67db38c218b1f3f4292803cbc7fe3a80c6 100644 Binary files a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 and b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 differ diff --git a/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss index f3d8b4437335aa206dce04c919d343001252a4ad..d0adbdbb9c4622c53c7ad1ba3c78b30ae201a723 100644 --- a/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss +++ b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss @@ -177,6 +177,18 @@ color: #ca3a31 } + .transactor { + color: #330099; + } + + .child-process { + color: #996633; + } + + .async-child-process { + color: #006666; + } + .handler-item-text { margin-top: 4px; width: 80px; @@ -290,10 +302,22 @@ &.trigger-node { color: #3373d2; } - + &.router-node { color: #ca3a31 } + + &.transactor-task { + color: #330099; + } + + &.child-process { + color: #996633; + } + + &.async-child-process { + color: #006666; + } } .node-title { @@ -777,7 +801,7 @@ content: "\e7eb"; } -.icon-handle:before { +.icon-transactor:before { content: "\e61c"; } @@ -792,3 +816,11 @@ .icon-parallel:before { content: "\e688"; } + +.icon-async-child-process:before { + content: "\e6f2"; +} + +.icon-child-process:before { + content: "\e6c1"; +} diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue index 83d40fbfcf430a6b5f16aecc1b688ccf62b99135..00e887cb519bceb5ae48ef90219396aa0c5bb2bd 100644 --- a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue +++ b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue @@ -188,12 +188,8 @@ :scroll="true" max-height="600px" > - -
- - - {{ previewResult }} - +
+
@@ -237,6 +233,8 @@ import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml' // const eventName = reactive({ // name: '' // }) +import hljs from 'highlight.js' // 导入代码高亮文件 +import 'highlight.js/styles/github.css' // 导入代码高亮样式 defineOptions({ name: 'MyProcessDesigner' }) @@ -308,6 +306,18 @@ const props = defineProps({ } }) +/** + * 代码高亮 + */ +const highlightedCode = (code: string) => { + // 高亮 + if (previewType.value === 'json') { + code = JSON.stringify(code, null, 2) + } + const result = hljs.highlight(code, { language: previewType.value, ignoreIllegals: true }) + return result.value || ' ' +} + provide('configGlobal', props) let bpmnModeler: any = null const defaultZoom = ref(1) diff --git a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue index aab130d087aa243b2e89c8c89fef76b50b21bb9b..3c748ff6f8cc81b7e772b1cb1951e9a2ab816e1e 100644 --- a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue +++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue @@ -123,13 +123,19 @@
字段权限 -
+
字段名称
- 只读 - 可编辑 - 隐藏 + 只读 + 可编辑 + 隐藏
@@ -140,24 +146,30 @@ :value="FieldPermissionType.READ" size="large" :label="FieldPermissionType.READ" - > + @change="updateElementExtensions" + > + +
+ @change="updateElementExtensions" + > + +
+ @change="updateElementExtensions" + > + +
@@ -165,12 +177,22 @@ 是否需要签名 - + 审批意见 - +
@@ -191,6 +213,7 @@ import { } from '@/components/SimpleProcessDesignerV2/src/consts' import * as UserApi from '@/api/system/user' import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node' +import { BpmModelFormType } from '@/utils/constants' defineOptions({ name: 'ElementCustomConfig4UserTask' }) const props = defineProps({ @@ -248,7 +271,6 @@ const resetCustomConfigList = () => { bpmnElement.value.id, bpmnInstances().modeler ) - // 获取元素扩展属性 或者 创建扩展属性 elExtensionElements.value = bpmnElement.value.businessObject?.extensionElements ?? @@ -311,14 +333,13 @@ const resetCustomConfigList = () => { } // 字段权限 - if (formType.value === 10) { + if (formType.value === BpmModelFormType.NORMAL) { const fieldsPermissionList = elExtensionElements.value.values?.filter( (ex) => ex.$type === `${prefix}:FieldsPermission` ) fieldsPermissionEl.value = [] getNodeConfigFormFields() - // 由于默认添加了发起人元素,这里需要删掉 - fieldsPermissionConfig.value = fieldsPermissionConfig.value.slice(1) + fieldsPermissionConfig.value = fieldsPermissionConfig.value fieldsPermissionConfig.value.forEach((element) => { element.permission = fieldsPermissionList?.find((obj) => obj.field === element.field)?.permission ?? '1' @@ -487,6 +508,19 @@ function useButtonsSetting() { } } +/** 批量更新权限 */ +// TODO @lesan:这个页面,有一些 idea 红色报错,咱要不要 fix 下! +const updatePermission = (type: string) => { + fieldsPermissionEl.value.forEach((field) => { + field.permission = + type === 'READ' + ? FieldPermissionType.READ + : type === 'WRITE' + ? FieldPermissionType.WRITE + : FieldPermissionType.NONE + }) +} + const userOptions = ref([]) // 用户列表 onMounted(async () => { // 获得用户列表 @@ -497,9 +531,9 @@ onMounted(async () => { diff --git a/src/views/bpm/model/form/ExtraSettings.vue b/src/views/bpm/model/form/ExtraSettings.vue index d9eb1c611c966b8b14420b1f6751905d7d3f8824..1d685c75ef4b5c2c195c22aed2cad62467581f7c 100644 --- a/src/views/bpm/model/form/ExtraSettings.vue +++ b/src/views/bpm/model/form/ExtraSettings.vue @@ -140,6 +140,46 @@
+ + +
+
+ +
流程启动后通知
+
+ +
+
+ + +
+
+ +
流程启动后通知
+
+ +
+
@@ -149,6 +189,7 @@ import { BpmAutoApproveType, BpmModelFormType } from '@/utils/constants' import * as FormApi from '@/api/bpm/form' import { parseFormFields } from '@/components/FormCreate/src/utils' import { ProcessVariableEnum } from '@/components/SimpleProcessDesignerV2/src/consts' +import HttpRequestSetting from '@/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestSetting.vue' const modelData = defineModel() @@ -205,6 +246,36 @@ const numberExample = computed(() => { } }) +/** 是否开启流程前置通知 */ +const preProcessNotifyEnable = ref(false) +const handlePreProcessNotifyEnableChange = (val: boolean | string | number) => { + if (val) { + modelData.value.preProcessNotifySetting = { + url: '', + header: [], + body: [], + response: [] + } + } else { + modelData.value.preProcessNotifySetting = null + } +} + +/** 是否开启流程后置通知 */ +const postProcessNotifyEnable = ref(false) +const handlePostProcessNotifyEnableChange = (val: boolean | string | number) => { + if (val) { + modelData.value.postProcessNotifySetting = { + url: '', + header: [], + body: [], + response: [] + } + } else { + modelData.value.postProcessNotifySetting = null + } +} + /** 表单选项 */ const formField = ref>([]) const formFieldOptions4Title = computed(() => { @@ -264,6 +335,12 @@ const initData = () => { summary: [] } } + if (modelData.value.preProcessNotifySetting) { + preProcessNotifyEnable.value = true + } + if (modelData.value.postProcessNotifySetting) { + postProcessNotifyEnable.value = true + } } defineExpose({ initData }) diff --git a/src/views/bpm/model/form/FormDesign.vue b/src/views/bpm/model/form/FormDesign.vue index 74742f2e3ec0a1bd6bdc49637e5e20a984f23b04..e1ca27f209ceb2dd883bcd22f583e76053af1d49 100644 --- a/src/views/bpm/model/form/FormDesign.vue +++ b/src/views/bpm/model/form/FormDesign.vue @@ -11,12 +11,12 @@ - + - + - +
@@ -68,6 +68,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as FormApi from '@/api/bpm/form' import { setConfAndFields2 } from '@/utils/formCreate' +import { BpmModelFormType } from '@/utils/constants' const props = defineProps({ formList: { @@ -96,7 +97,7 @@ const formPreview = ref({ watch( () => modelData.value.formId, async (newFormId) => { - if (newFormId && modelData.value.formType === 10) { + if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) { const data = await FormApi.getForm(newFormId) setConfAndFields2(formPreview.value, data.conf, data.fields) // 设置只读 diff --git a/src/views/bpm/model/form/ProcessDesign.vue b/src/views/bpm/model/form/ProcessDesign.vue index e85076a6db20f219081fa86b6a0187a82bb5b094..bb3a04b0d8b115be9d12b32a709c89105671be2b 100644 --- a/src/views/bpm/model/form/ProcessDesign.vue +++ b/src/views/bpm/model/form/ProcessDesign.vue @@ -25,7 +25,7 @@ diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue index 849338784deaa22e590d50eb3635ff0d0d677ad4..f69035f9a9095159a4f9ce6edb8649f9ed416d1b 100644 --- a/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue +++ b/src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue @@ -36,14 +36,27 @@ :rule="approveForm.rule" /> - + + +
+ +
+
{{ getButtonDisplayName(OperationButtonType.APPROVE) }} - 取消 + 取消
@@ -111,7 +124,7 @@ > {{ getButtonDisplayName(OperationButtonType.REJECT) }} - 取消 + 取消
@@ -169,7 +182,7 @@ {{ getButtonDisplayName(OperationButtonType.COPY) }} - 取消 + 取消
@@ -221,7 +234,7 @@ {{ getButtonDisplayName(OperationButtonType.TRANSFER) }} - 取消 + 取消
@@ -273,7 +286,7 @@ {{ getButtonDisplayName(OperationButtonType.DELEGATE) }} - 取消 + 取消
@@ -328,7 +341,7 @@ 向后{{ getButtonDisplayName(OperationButtonType.ADD_SIGN) }} - 取消 + 取消
@@ -379,7 +392,7 @@ 减签 - 取消 + 取消 @@ -431,7 +444,7 @@ {{ getButtonDisplayName(OperationButtonType.RETURN) }} - 取消 + 取消 @@ -475,7 +488,7 @@ 确认 - 取消 + 取消 @@ -504,12 +517,16 @@ import * as TaskApi from '@/api/bpm/task' import * as ProcessInstanceApi from '@/api/bpm/processInstance' import * as UserApi from '@/api/system/user' import { + NodeType, OPERATION_BUTTON_NAME, - OperationButtonType + OperationButtonType, + CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts' import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants' import type { FormInstance, FormRules } from 'element-plus' import SignDialog from './SignDialog.vue' +import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue' +import { isEmpty } from '@/utils/is' defineOptions({ name: 'ProcessInstanceBtnContainer' }) @@ -546,22 +563,29 @@ const returnList = ref([] as any) // 退回节点 const runningTask = ref() // 运行中的任务 const approveForm = ref({}) // 审批通过时,额外的补充信息 const approveFormFApi = ref({}) // approveForms 的 fAPi +const nodeTypeName = ref('审批') // 节点类型名称 // 审批通过意见表单 const reasonRequire = ref() const approveFormRef = ref() const signRef = ref() const approveSignFormRef = ref() +const nextAssigneesActivityNode = ref([]) // 下一个审批节点信息 const approveReasonForm = reactive({ reason: '', - signPicUrl: '' + signPicUrl: '', + nextAssignees: {} }) const approveReasonRule = computed(() => { return { - reason: [{ required: reasonRequire.value, message: '审批意见不能为空', trigger: 'blur' }], - signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }] + reason: [ + { required: reasonRequire.value, message: nodeTypeName + '意见不能为空', trigger: 'blur' } + ], + signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }], + nextAssignees: [{ required: true, message: '审批人不能为空', trigger: 'blur' }] } }) + // 拒绝表单 const rejectFormRef = ref() const rejectReasonForm = reactive({ @@ -668,6 +692,7 @@ const openPopover = async (type: string) => { message.warning('表单校验不通过,请先完善表单!!') return } + initNextAssigneesFormField() } if (type === 'return') { // 获取退回节点 @@ -685,11 +710,58 @@ const openPopover = async (type: string) => { } /** 关闭气泡卡 */ -const closePropover = (type: string, formRef: FormInstance | undefined) => { +const closePopover = (type: string, formRef: FormInstance | undefined) => { if (formRef) { formRef.resetFields() } popOverVisible.value[type] = false + nextAssigneesActivityNode.value = [] +} + +/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */ +const initNextAssigneesFormField = async () => { + // 获取修改的流程变量, 暂时只支持流程表单 + const variables = getUpdatedProcessInstanceVariables() + const data = await ProcessInstanceApi.getNextApprovalNodes({ + processInstanceId: props.processInstance.id, + taskId: runningTask.value.id, + processVariablesStr: JSON.stringify(variables) + }) + if (data && data.length > 0) { + data.forEach((node: any) => { + if ( + // 情况一:当前节点没有审批人,并且是发起人自选 + (isEmpty(node.tasks) && + isEmpty(node.candidateUsers) && + CandidateStrategy.START_USER_SELECT === node.candidateStrategy) || + // 情况二:当前节点是审批人自选 + CandidateStrategy.APPROVE_USER_SELECT === node.candidateStrategy + ) { + nextAssigneesActivityNode.value.push(node) + } + }) + } +} + +/** 选择下一个节点的审批人 */ +const selectNextAssigneesConfirm = (id: string, userList: any[]) => { + approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id) +} +/** 审批通过时,校验每个自选审批人的节点是否都已配置了审批人 */ +const validateNextAssignees = () => { + // TODO @小北:可以考虑 Object.keys(nextAssigneesActivityNode.value).length === 0) return true;减少括号层级 + // 如果需要自选审批人,则校验自选审批人 + if (Object.keys(nextAssigneesActivityNode.value).length > 0) { + // 校验每个节点是否都已配置审批人 + for (const item of nextAssigneesActivityNode.value) { + if (isEmpty(approveReasonForm.nextAssignees[item.id])) { + // TODO @小北:可以打印下节点名,嘿嘿。 + message.warning('下一个节点的审批人不能为空!') + return false + } + } + } + return true } /** 处理审批通过和不通过的操作 */ @@ -699,15 +771,24 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => // 校验表单 if (!formRef) return await formRef.validate() + // 校验流程表单必填字段 + const valid = await validateNormalForm() + if (!valid) { + message.warning('表单校验不通过,请先完善表单!!') + return + } + if (pass) { - // 获取修改的流程变量, 暂时只支持流程表单 + const nextAssigneesValid = validateNextAssignees() + if (!nextAssigneesValid) return const variables = getUpdatedProcessInstanceVariables() // 审批通过数据 const data = { id: runningTask.value.id, reason: approveReasonForm.reason, - variables // 审批通过, 把修改的字段值赋于流程实例变量 - } + variables, // 审批通过, 把修改的字段值赋于流程实例变量 + nextAssignees: approveReasonForm.nextAssignees // 下个自选节点选择的审批人信息 + } as any // 签名 if (runningTask.value.signEnable) { data.signPicUrl = approveReasonForm.signPicUrl @@ -722,6 +803,7 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) => } await TaskApi.approveTask(data) popOverVisible.value.approve = false + nextAssigneesActivityNode.value = [] message.success('审批通过成功') } else { // 审批不通过数据 @@ -969,9 +1051,10 @@ const getButtonDisplayName = (btnType: OperationButtonType) => { const loadTodoTask = (task: any) => { approveForm.value = {} - approveFormFApi.value = {} runningTask.value = task + approveFormFApi.value = {} reasonRequire.value = task?.reasonRequire ?? false + nodeTypeName.value = task?.nodeType === NodeType.TRANSACTOR_NODE ? '办理' : '审批' // 处理 approve 表单. if (task && task.formId && task.formConf) { const tempApproveForm = {} @@ -997,6 +1080,11 @@ const validateNormalForm = async () => { } } +/** + * TODO @小北 TO @芋道 + * 问题:这里存在一种场景会出现问题,流程发起后,A节点审批完成,B节点没有可编辑的流程字段且B节点为自选审批人节点,会导致流程审批人为空, + * 原因:因为没有可编辑的流程字段时props.writableFields为空,参数variables传递时也为空 + */ /** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */ const getUpdatedProcessInstanceVariables = () => { const variables = {} diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue b/src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue index 69fd70141fbe2526fcb60a562da28a74c56f9bd5..87f8119df476b7b33b505e42ca60661f64b27b6b 100644 --- a/src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue +++ b/src/views/bpm/processInstance/detail/ProcessInstanceSimpleViewer.vue @@ -42,13 +42,13 @@ watch( const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds setSimpleModelNodeTaskStatus( newModelView.simpleModel, - newModelView.processInstance.status, + newModelView.processInstance?.status, rejectedTaskActivityIds, unfinishedTaskActivityIds, finishedActivityIds, finishedSequenceFlowActivityIds ) - simpleModel.value = newModelView.simpleModel + simpleModel.value = newModelView.simpleModel ? newModelView.simpleModel : {} } } ) @@ -84,7 +84,9 @@ const setSimpleModelNodeTaskStatus = ( // 审批节点 if ( simpleModel.type === NodeType.START_USER_NODE || - simpleModel.type === NodeType.USER_TASK_NODE + simpleModel.type === NodeType.USER_TASK_NODE || + simpleModel.type === NodeType.TRANSACTOR_NODE || + simpleModel.type === NodeType.CHILD_PROCESS_NODE ) { simpleModel.activityStatus = TaskStatusEnum.NOT_START if (rejectedTaskActivityIds.includes(simpleModel.id)) { @@ -169,5 +171,4 @@ const setSimpleModelNodeTaskStatus = ( } - + diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue index fcd5ec899fac59a7912fb83530d8e7c6f4b8b0bd..110b8bd6cf933a01b87ba1273ddb28cc2d00789f 100644 --- a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue +++ b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue @@ -43,7 +43,8 @@ v-if=" isEmpty(activity.tasks) && isEmpty(activity.candidateUsers) && - CandidateStrategy.START_USER_SELECT === activity.candidateStrategy + (CandidateStrategy.START_USER_SELECT === activity.candidateStrategy || + CandidateStrategy.APPROVE_USER_SELECT === activity.candidateStrategy) " > @@ -121,6 +122,7 @@ " class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md" > + 审批意见:{{ task.reason }}
{ if ( nodeType === NodeType.START_USER_NODE || nodeType === NodeType.USER_TASK_NODE || + nodeType === NodeType.TRANSACTOR_NODE || + nodeType === NodeType.CHILD_PROCESS_NODE || nodeType === NodeType.END_EVENT_NODE ) { return statusIconMap[taskStatus]?.icon diff --git a/src/views/bpm/processInstance/detail/index.vue b/src/views/bpm/processInstance/detail/index.vue index 9809f7a8d0236ad18ef091a1cf2d28253dbd4b17..c3f83cf06947ec276f54a5da0af8bf64b4d8ad67 100644 --- a/src/views/bpm/processInstance/detail/index.vue +++ b/src/views/bpm/processInstance/detail/index.vue @@ -178,8 +178,9 @@ const writableFields: Array = [] // 表单可以编辑的字段 /** 获得详情 */ const getDetail = () => { + // 获得审批详情 getApprovalDetail() - + // 获得流程模型视图 getProcessModelView() } diff --git a/src/views/bpm/processInstance/index.vue b/src/views/bpm/processInstance/index.vue index a9d7cdd4ccccd119b0459edd3a1d2723deeb1e7c..d6fc83d38607868bd4c1d54683ae072f3ceef343 100644 --- a/src/views/bpm/processInstance/index.vue +++ b/src/views/bpm/processInstance/index.vue @@ -24,9 +24,7 @@ 搜索 - - - + - - + - - + 高级筛选 - - + @change="handleQuery" + > + + - + - - - 确认 - 取消 - 清空 + +
+ 清空 + 取消 + 确认 +
@@ -130,7 +119,7 @@ - +