• 基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持多实例自定义条件的流程流转


    更多ruoyi-nbcio功能请看演示系统

    gitee源代码地址

    前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio

    演示地址:RuoYi-Nbcio后台管理系统

    1、前端代码,主要修改下面这个文件,如下:

    说明几点:

     1.1 增加界面

      1.2 增加自定义条件的逻辑与保存

    1. <script>
    2. import { listUser, deptTreeSelect } from "@/api/system/user";
    3. import { listRole } from "@/api/system/role";
    4. import TreeSelect from "@/components/TreeSelect";
    5. const userTaskForm = {
    6. dataType: '',
    7. assignee: '',
    8. candidateUsers: '',
    9. candidateGroups: '',
    10. text: '',
    11. // dueDate: '',
    12. // followUpDate: '',
    13. // priority: ''
    14. }
    15. export default {
    16. name: "UserTask",
    17. props: {
    18. id: String,
    19. type: String
    20. },
    21. components: { TreeSelect },
    22. data() {
    23. return {
    24. loading: false,
    25. dataType: 'USERS',
    26. selectedUser: {
    27. ids: [],
    28. text: []
    29. },
    30. userOpen: false,
    31. deptName: undefined,
    32. deptOptions: [],
    33. deptProps: {
    34. children: "children",
    35. label: "label"
    36. },
    37. deptTempOptions: [],
    38. userTableList: [],
    39. userTotal: 0,
    40. selectedUserDate: [],
    41. roleOptions: [],
    42. roleIds: [],
    43. deptTreeData: [],
    44. deptIds: [],
    45. // 查询参数
    46. queryParams: {
    47. deptId: undefined
    48. },
    49. showMultiFlog: false,
    50. isSequential: false,
    51. multiLoopType: 'Null',
    52. CustomCompletionCondition: '${nrOfCompletedInstances/nrOfInstances>=1}',
    53. };
    54. },
    55. watch: {
    56. id: {
    57. immediate: true,
    58. handler() {
    59. this.bpmnElement = window.bpmnInstances.bpmnElement;
    60. this.$nextTick(() => this.resetTaskForm());
    61. }
    62. },
    63. // 根据名称筛选部门树
    64. deptName(val) {
    65. this.$refs.tree.filter(val);
    66. }
    67. },
    68. beforeDestroy() {
    69. this.bpmnElement = null;
    70. },
    71. methods: {
    72. resetTaskForm() {
    73. const bpmnElementObj = this.bpmnElement?.businessObject;
    74. if (!bpmnElementObj) {
    75. return;
    76. }
    77. this.clearOptionsData()
    78. this.dataType = bpmnElementObj['dataType'];
    79. if (this.dataType === 'USERS') {
    80. let userIdData = bpmnElementObj['candidateUsers'] || bpmnElementObj['assignee'];
    81. let userText = bpmnElementObj['text'] || [];
    82. if (userIdData && userIdData.toString().length > 0 && userText && userText.length > 0) {
    83. this.selectedUser.ids = userIdData?.toString().split(',');
    84. this.selectedUser.text = userText?.split(',');
    85. }
    86. if (this.selectedUser.ids.length > 1) {
    87. this.showMultiFlog = true;
    88. }
    89. } else if (this.dataType === 'ROLES') {
    90. this.getRoleOptions();
    91. let roleIdData = bpmnElementObj['candidateGroups'] || [];
    92. if (roleIdData && roleIdData.length > 0) {
    93. this.roleIds = roleIdData.split(',')
    94. }
    95. this.showMultiFlog = true;
    96. } else if (this.dataType === 'DEPTS') {
    97. this.getDeptTreeData();
    98. let deptIdData = bpmnElementObj['candidateGroups'] || [];
    99. if (deptIdData && deptIdData.length > 0) {
    100. this.deptIds = deptIdData.split(',');
    101. }
    102. this.showMultiFlog = true;
    103. }
    104. this.getElementLoop(bpmnElementObj);
    105. },
    106. /**
    107. * 清空选项数据
    108. */
    109. clearOptionsData() {
    110. this.selectedUser.ids = [];
    111. this.selectedUser.text = [];
    112. this.roleIds = [];
    113. this.deptIds = [];
    114. },
    115. // 完成条件
    116. updateLoopCondition(condition) {
    117. },
    118. /**
    119. * 更新节点数据
    120. */
    121. updateElementTask() {
    122. const taskAttr = Object.create(null);
    123. for (let key in userTaskForm) {
    124. taskAttr[key] = userTaskForm[key];
    125. }
    126. window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);
    127. },
    128. /**
    129. * 查询部门下拉树结构
    130. */
    131. getDeptOptions() {
    132. return new Promise((resolve, reject) => {
    133. if (!this.deptOptions || this.deptOptions.length <= 0) {
    134. deptTreeSelect().then(response => {
    135. this.deptTempOptions = response.data;
    136. this.deptOptions = response.data;
    137. resolve()
    138. })
    139. } else {
    140. reject()
    141. }
    142. });
    143. },
    144. /**
    145. * 查询部门下拉树结构(含部门前缀)
    146. */
    147. getDeptTreeData() {
    148. function refactorTree(data) {
    149. return data.map(node => {
    150. let treeData = { id: `DEPT${node.id}`, label: node.label, parentId: node.parentId, weight: node.weight };
    151. if (node.children && node.children.length > 0) {
    152. treeData.children = refactorTree(node.children);
    153. }
    154. return treeData;
    155. });
    156. }
    157. return new Promise((resolve, reject) => {
    158. if (!this.deptTreeData || this.deptTreeData.length <= 0) {
    159. this.getDeptOptions().then(() => {
    160. this.deptTreeData = refactorTree(this.deptOptions);
    161. resolve()
    162. }).catch(() => {
    163. reject()
    164. })
    165. } else {
    166. resolve()
    167. }
    168. })
    169. },
    170. /**
    171. * 查询部门下拉树结构
    172. */
    173. getRoleOptions() {
    174. if (!this.roleOptions || this.roleOptions.length <= 0) {
    175. listRole().then(response => this.roleOptions = response.rows);
    176. }
    177. },
    178. /** 查询用户列表 */
    179. getUserList() {
    180. listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
    181. this.userTableList = response.rows;
    182. this.userTotal = response.total;
    183. });
    184. },
    185. // 筛选节点
    186. filterNode(value, data) {
    187. if (!value) return true;
    188. return data.label.indexOf(value) !== -1;
    189. },
    190. // 节点单击事件
    191. handleNodeClick(data) {
    192. this.queryParams.deptId = data.id;
    193. this.getUserList();
    194. },
    195. // 关闭标签
    196. handleClose(tag) {
    197. this.selectedUserDate.splice(this.selectedUserDate.indexOf(tag), 1);
    198. this.$refs.multipleTable.toggleRowSelection(tag);
    199. },
    200. // 多选框选中数据
    201. handleSelectionChange(selection) {
    202. this.selectedUserDate = selection;
    203. },
    204. onSelectUsers() {
    205. this.selectedUserDate = []
    206. this.$refs.multipleTable?.clearSelection();
    207. this.getDeptOptions();
    208. this.userOpen = true;
    209. },
    210. handleTaskUserComplete() {
    211. if (!this.selectedUserDate || this.selectedUserDate.length <= 0) {
    212. this.$modal.msgError('请选择用户');
    213. return;
    214. }
    215. userTaskForm.dataType = 'USERS';
    216. this.selectedUser.text = this.selectedUserDate.map(k => k.nickName) || [];
    217. if (this.selectedUserDate.length === 1) {
    218. let data = this.selectedUserDate[0];
    219. userTaskForm.assignee = data.userName;
    220. userTaskForm.text = data.nickName;
    221. userTaskForm.candidateUsers = null;
    222. this.showMultiFlog = false;
    223. this.multiLoopType = 'Null';
    224. this.changeMultiLoopType();
    225. } else {
    226. userTaskForm.candidateUsers = this.selectedUserDate.map(k => k.userName).join() || null;
    227. userTaskForm.text = this.selectedUserDate.map(k => k.nickName).join() || null;
    228. userTaskForm.assignee = null;
    229. this.showMultiFlog = true;
    230. }
    231. this.updateElementTask()
    232. this.userOpen = false;
    233. },
    234. changeSelectRoles(val) {
    235. let groups = null;
    236. let text = null;
    237. if (val && val.length > 0) {
    238. userTaskForm.dataType = 'ROLES';
    239. groups = val.join() || null;
    240. let textArr = this.roleOptions.filter(k => val.indexOf(`ROLE${k.roleId}`) >= 0);
    241. text = textArr?.map(k => k.roleName).join() || null;
    242. } else {
    243. userTaskForm.dataType = null;
    244. this.multiLoopType = 'Null';
    245. }
    246. userTaskForm.candidateGroups = groups;
    247. userTaskForm.text = text;
    248. this.updateElementTask();
    249. this.changeMultiLoopType();
    250. },
    251. checkedDeptChange(checkedIds) {
    252. let groups = null;
    253. let text = null;
    254. this.deptIds = checkedIds;
    255. if (checkedIds && checkedIds.length > 0) {
    256. userTaskForm.dataType = 'DEPTS';
    257. groups = checkedIds.join() || null;
    258. let textArr = []
    259. let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
    260. checkedIds.forEach(id => {
    261. let stark = []
    262. stark = stark.concat(treeStarkData);
    263. while(stark.length) {
    264. let temp = stark.shift();
    265. if(temp.children) {
    266. stark = temp.children.concat(stark);
    267. }
    268. if(id === temp.id) {
    269. textArr.push(temp);
    270. }
    271. }
    272. })
    273. text = textArr?.map(k => k.label).join() || null;
    274. } else {
    275. userTaskForm.dataType = null;
    276. this.multiLoopType = 'Null';
    277. }
    278. userTaskForm.candidateGroups = groups;
    279. userTaskForm.text = text;
    280. this.updateElementTask();
    281. this.changeMultiLoopType();
    282. },
    283. changeDataType(val) {
    284. if (val === 'ROLES' || val === 'DEPTS' || (val === 'USERS' && this.selectedUser.ids.length > 1)) {
    285. this.showMultiFlog = true;
    286. } else {
    287. this.showMultiFlog = false;
    288. }
    289. this.multiLoopType = 'Null';
    290. this.changeMultiLoopType();
    291. // 清空 userTaskForm 所有属性值
    292. Object.keys(userTaskForm).forEach(key => userTaskForm[key] = null);
    293. userTaskForm.dataType = val;
    294. console.log("changeDataType this.selectedUser",this.selectedUser);
    295. if (val === 'USERS') {
    296. if (this.selectedUser && this.selectedUser.ids && this.selectedUser.ids.length > 0) {
    297. if (this.selectedUser.ids.length === 1) {
    298. userTaskForm.assignee = this.selectedUser.ids[0];
    299. } else {
    300. userTaskForm.candidateUsers = this.selectedUser.ids.join()
    301. }
    302. userTaskForm.text = this.selectedUser.text?.join() || null
    303. }
    304. } else if (val === 'ROLES') {
    305. this.getRoleOptions();
    306. if (this.roleIds && this.roleIds.length > 0) {
    307. userTaskForm.candidateGroups = this.roleIds.join() || null;
    308. let textArr = this.roleOptions.filter(k => this.roleIds.indexOf(`ROLE${k.roleId}`) >= 0);
    309. userTaskForm.text = textArr?.map(k => k.roleName).join() || null;
    310. }
    311. } else if (val === 'DEPTS') {
    312. this.getDeptTreeData();
    313. if (this.deptIds && this.deptIds.length > 0) {
    314. userTaskForm.candidateGroups = this.deptIds.join() || null;
    315. let textArr = []
    316. let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
    317. this.deptIds.forEach(id => {
    318. let stark = []
    319. stark = stark.concat(treeStarkData);
    320. while(stark.length) {
    321. let temp = stark.shift();
    322. if(temp.children) {
    323. stark = temp.children.concat(stark);
    324. }
    325. if(id === temp.id) {
    326. textArr.push(temp);
    327. }
    328. }
    329. })
    330. userTaskForm.text = textArr?.map(k => k.label).join() || null;
    331. }
    332. } else if (val === 'MANAGER') {
    333. userTaskForm.assignee = "${DepManagerHandler.getUser(execution)}";
    334. userTaskForm.text = "部门经理";
    335. } else if (val === 'INITIATOR') {
    336. userTaskForm.assignee = "${initiator}";
    337. userTaskForm.text = "流程发起人";
    338. }
    339. this.updateElementTask();
    340. },
    341. getElementLoop(businessObject) {
    342. if (!businessObject.loopCharacteristics) {
    343. this.multiLoopType = "Null";
    344. return;
    345. }
    346. this.isSequential = businessObject.loopCharacteristics.isSequential;
    347. if (businessObject.loopCharacteristics.completionCondition) {
    348. if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances >= nrOfInstances}") {
    349. this.multiLoopType = "SequentialMultiInstance";
    350. } else if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances > 0}") {
    351. this.multiLoopType = "ParallelMultiInstance";
    352. } else {
    353. this.multiLoopType = "CustomMultiInstance";
    354. }
    355. }
    356. },
    357. changeMultiLoopType() {
    358. // 取消多实例配置
    359. if (this.multiLoopType === "Null") {
    360. window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { loopCharacteristics: null, assignee: null });
    361. return;
    362. }
    363. this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: this.isSequential });
    364. // 更新多实例配置
    365. window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
    366. loopCharacteristics: this.multiLoopInstance,
    367. assignee: '${assignee}'
    368. });
    369. // 完成条件
    370. let completionCondition = null;
    371. // 会签
    372. if (this.multiLoopType === "SequentialMultiInstance") {
    373. completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances >= nrOfInstances}" });
    374. }
    375. // 或签
    376. if (this.multiLoopType === "ParallelMultiInstance") {
    377. completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances > 0}" });
    378. }
    379. // 自定义会签
    380. if (this.multiLoopType === "CustomMultiInstance") {
    381. completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: this.CustomCompletionCondition });
    382. }
    383. // 更新模块属性信息
    384. window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, {
    385. collection: '${multiInstanceHandler.getUserNames(execution)}',
    386. elementVariable: 'assignee',
    387. completionCondition
    388. });
    389. },
    390. }
    391. };

    2、增加一个方法通过多ids来获取userNames,因为现在流程都是通过userName来执行流转

    1. @Override
    2. public List selectUserNames(List userIds) {
    3. List listuser = baseMapper.selectBatchIds(userIds);
    4. List userNames = new ArrayList<>();
    5. for(SysUser sysuser: listuser) {
    6. userNames.add(sysuser.getUserName());
    7. }
    8. return userNames;
    9. }
    10. }

    3、多实例处理类修改如下

    1. /**
    2. * 多实例处理类
    3. *
    4. * @author nbacheng
    5. */
    6. @AllArgsConstructor
    7. @Component("multiInstanceHandler")
    8. public class MultiInstanceHandler {
    9. public Set getUserNames(DelegateExecution execution) {
    10. Set candidateUserNames = new LinkedHashSet<>();
    11. FlowElement flowElement = execution.getCurrentFlowElement();
    12. if (ObjectUtil.isNotEmpty(flowElement) && flowElement instanceof UserTask) {
    13. UserTask userTask = (UserTask) flowElement;
    14. String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE);
    15. if ("USERS".equals(dataType) && CollUtil.isNotEmpty(userTask.getCandidateUsers())) {
    16. // 添加候选用户
    17. candidateUserNames.addAll(userTask.getCandidateUsers());
    18. } else if (CollUtil.isNotEmpty(userTask.getCandidateGroups())) {
    19. // 获取组的ID,角色ID集合或部门ID集合
    20. List groups = userTask.getCandidateGroups().stream()
    21. .map(item -> Long.parseLong(item.substring(4)))
    22. .collect(Collectors.toList());
    23. List userIds = new ArrayList<>();
    24. List userNames = new ArrayList<>();
    25. if ("ROLES".equals(dataType)) {
    26. // 通过角色id,获取所有用户id集合
    27. LambdaQueryWrapper lqw = Wrappers.lambdaQuery(SysUserRole.class).select(SysUserRole::getUserId).in(SysUserRole::getRoleId, groups);
    28. userIds = SimpleQuery.list(lqw, SysUserRole::getUserId);
    29. } else if ("DEPTS".equals(dataType)) {
    30. // 通过部门id,获取所有用户id集合
    31. LambdaQueryWrapper lqw = Wrappers.lambdaQuery(SysUser.class).select(SysUser::getUserId).in(SysUser::getDeptId, groups);
    32. userIds = SimpleQuery.list(lqw, SysUser::getUserId);
    33. }
    34. // 添加候选用户
    35. ISysUserService sysUserService = SpringContextUtils.getBean(ISysUserService.class);
    36. userNames = sysUserService.selectUserNames(userIds);
    37. userNames.forEach(userName -> candidateUserNames.add(userName));
    38. }
    39. }
    40. return candidateUserNames;
    41. }
    42. }

    4、效果图如下:

  • 相关阅读:
    Vue 卸载Better Scroll插件
    2023年系统规划与设计管理师-第二章信息技术知识
    485自收发专用芯片
    并查集应用
    51单片机项目(33)——基于51单片机的GSM家庭防火防盗系统
    音视频开发15 FFmpeg FLV封装格式分析
    Java学习笔记(二十)
    四、python的import和路径
    166 个最常用的 Linux 命令汇总,总有你需要用到的...
    如何阅读一篇论文
  • 原文地址:https://blog.csdn.net/qq_40032778/article/details/133947678