当前位置:   article > 正文

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

若依审批流

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

gitee源代码地址

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

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

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

说明几点:

 1.1 增加界面

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

  1. <template>
  2. <div>
  3. <el-row>
  4. <h4><b>审批人设置</b></h4>
  5. <el-radio-group v-model="dataType" @change="changeDataType">
  6. <el-radio label="USERS">指定用户</el-radio>
  7. <el-radio label="ROLES">角色</el-radio>
  8. <el-radio label="DEPTS">部门</el-radio>
  9. <el-radio label="INITIATOR">发起人</el-radio>
  10. <el-radio label="MANAGER">部门经理</el-radio>
  11. </el-radio-group>
  12. </el-row>
  13. <el-row>
  14. <div v-if="dataType === 'USERS'">
  15. <el-tag v-for="userText in selectedUser.text" :key="userText" effect="plain">
  16. {{userText}}
  17. </el-tag>
  18. <div class="element-drawer__button">
  19. <el-button size="mini" type="primary" icon="el-icon-plus" @click="onSelectUsers()">添加用户</el-button>
  20. </div>
  21. </div>
  22. <div v-if="dataType === 'ROLES'">
  23. <el-select v-model="roleIds" multiple size="mini" placeholder="请选择 角色" @change="changeSelectRoles">
  24. <el-option
  25. v-for="item in roleOptions"
  26. :key="item.roleId"
  27. :label="item.roleName"
  28. :value="`ROLE${item.roleId}`"
  29. :disabled="item.status === 1">
  30. </el-option>
  31. </el-select>
  32. </div>
  33. <div v-if="dataType === 'DEPTS'">
  34. <tree-select
  35. :width="320"
  36. :height="400"
  37. size="mini"
  38. :data="deptTreeData"
  39. :defaultProps="deptProps"
  40. multiple
  41. clearable
  42. checkStrictly
  43. nodeKey="id"
  44. :checkedKeys="deptIds"
  45. @change="checkedDeptChange">
  46. </tree-select>
  47. </div>
  48. </el-row>
  49. <el-row>
  50. <div v-show="showMultiFlog">
  51. <el-divider />
  52. <h4><b>多实例审批方式</b></h4>
  53. <el-row>
  54. <el-radio-group v-model="multiLoopType" @change="changeMultiLoopType()">
  55. <el-row><el-radio label="Null"></el-radio></el-row>
  56. <el-row><el-radio label="SequentialMultiInstance">会签(需所有审批人同意)</el-radio></el-row>
  57. <el-row><el-radio label="ParallelMultiInstance">或签(一名审批人同意即可)</el-radio></el-row>
  58. <el-row><el-radio label="CustomMultiInstance">自定义会签条件</el-radio></el-row>
  59. </el-radio-group>
  60. </el-row>
  61. <el-row v-if="multiLoopType === 'CustomMultiInstance'">
  62. <el-input v-model="CustomCompletionCondition" clearable @change="updateLoopCondition" />
  63. </el-row>
  64. <el-row v-if="multiLoopType !== 'Null'">
  65. <el-tooltip content="开启后,实例需按顺序轮流审批" placement="top-start" @click.stop.prevent>
  66. <i class="header-icon el-icon-info"></i>
  67. </el-tooltip>
  68. <span class="custom-label">顺序审批:</span>
  69. <el-switch v-model="isSequential" @change="changeMultiLoopType()" />
  70. </el-row>
  71. </div>
  72. </el-row>
  73. <!-- 候选用户弹窗 -->
  74. <el-dialog title="候选用户" :visible.sync="userOpen" width="60%" append-to-body>
  75. <el-row type="flex" :gutter="20">
  76. <!--部门数据-->
  77. <el-col :span="7">
  78. <el-card shadow="never" style="height: 100%">
  79. <div slot="header">
  80. <span>部门列表</span>
  81. </div>
  82. <div class="head-container">
  83. <el-input
  84. v-model="deptName"
  85. placeholder="请输入部门名称"
  86. clearable
  87. size="small"
  88. prefix-icon="el-icon-search"
  89. style="margin-bottom: 20px"
  90. />
  91. <el-tree
  92. :data="deptOptions"
  93. :props="deptProps"
  94. :expand-on-click-node="false"
  95. :filter-node-method="filterNode"
  96. ref="tree"
  97. default-expand-all
  98. @node-click="handleNodeClick"
  99. />
  100. </div>
  101. </el-card>
  102. </el-col>
  103. <el-col :span="17">
  104. <el-table ref="multipleTable" height="600" :data="userTableList" border @selection-change="handleSelectionChange">
  105. <el-table-column type="selection" width="50" align="center" />
  106. <el-table-column label="用户名" align="center" prop="nickName" />
  107. <el-table-column label="部门" align="center" prop="dept.deptName" />
  108. </el-table>
  109. <pagination
  110. :total="userTotal"
  111. :page.sync="queryParams.pageNum"
  112. :limit.sync="queryParams.pageSize"
  113. @pagination="getUserList"
  114. />
  115. </el-col>
  116. </el-row>
  117. <div slot="footer" class="dialog-footer">
  118. <el-button type="primary" @click="handleTaskUserComplete">确 定</el-button>
  119. <el-button @click="userOpen = false">取 消</el-button>
  120. </div>
  121. </el-dialog>
  122. </div>
  123. </template>
  124. <script>
  125. import { listUser, deptTreeSelect } from "@/api/system/user";
  126. import { listRole } from "@/api/system/role";
  127. import TreeSelect from "@/components/TreeSelect";
  128. const userTaskForm = {
  129. dataType: '',
  130. assignee: '',
  131. candidateUsers: '',
  132. candidateGroups: '',
  133. text: '',
  134. // dueDate: '',
  135. // followUpDate: '',
  136. // priority: ''
  137. }
  138. export default {
  139. name: "UserTask",
  140. props: {
  141. id: String,
  142. type: String
  143. },
  144. components: { TreeSelect },
  145. data() {
  146. return {
  147. loading: false,
  148. dataType: 'USERS',
  149. selectedUser: {
  150. ids: [],
  151. text: []
  152. },
  153. userOpen: false,
  154. deptName: undefined,
  155. deptOptions: [],
  156. deptProps: {
  157. children: "children",
  158. label: "label"
  159. },
  160. deptTempOptions: [],
  161. userTableList: [],
  162. userTotal: 0,
  163. selectedUserDate: [],
  164. roleOptions: [],
  165. roleIds: [],
  166. deptTreeData: [],
  167. deptIds: [],
  168. // 查询参数
  169. queryParams: {
  170. deptId: undefined
  171. },
  172. showMultiFlog: false,
  173. isSequential: false,
  174. multiLoopType: 'Null',
  175. CustomCompletionCondition: '${nrOfCompletedInstances/nrOfInstances>=1}',
  176. };
  177. },
  178. watch: {
  179. id: {
  180. immediate: true,
  181. handler() {
  182. this.bpmnElement = window.bpmnInstances.bpmnElement;
  183. this.$nextTick(() => this.resetTaskForm());
  184. }
  185. },
  186. // 根据名称筛选部门树
  187. deptName(val) {
  188. this.$refs.tree.filter(val);
  189. }
  190. },
  191. beforeDestroy() {
  192. this.bpmnElement = null;
  193. },
  194. methods: {
  195. resetTaskForm() {
  196. const bpmnElementObj = this.bpmnElement?.businessObject;
  197. if (!bpmnElementObj) {
  198. return;
  199. }
  200. this.clearOptionsData()
  201. this.dataType = bpmnElementObj['dataType'];
  202. if (this.dataType === 'USERS') {
  203. let userIdData = bpmnElementObj['candidateUsers'] || bpmnElementObj['assignee'];
  204. let userText = bpmnElementObj['text'] || [];
  205. if (userIdData && userIdData.toString().length > 0 && userText && userText.length > 0) {
  206. this.selectedUser.ids = userIdData?.toString().split(',');
  207. this.selectedUser.text = userText?.split(',');
  208. }
  209. if (this.selectedUser.ids.length > 1) {
  210. this.showMultiFlog = true;
  211. }
  212. } else if (this.dataType === 'ROLES') {
  213. this.getRoleOptions();
  214. let roleIdData = bpmnElementObj['candidateGroups'] || [];
  215. if (roleIdData && roleIdData.length > 0) {
  216. this.roleIds = roleIdData.split(',')
  217. }
  218. this.showMultiFlog = true;
  219. } else if (this.dataType === 'DEPTS') {
  220. this.getDeptTreeData();
  221. let deptIdData = bpmnElementObj['candidateGroups'] || [];
  222. if (deptIdData && deptIdData.length > 0) {
  223. this.deptIds = deptIdData.split(',');
  224. }
  225. this.showMultiFlog = true;
  226. }
  227. this.getElementLoop(bpmnElementObj);
  228. },
  229. /**
  230. * 清空选项数据
  231. */
  232. clearOptionsData() {
  233. this.selectedUser.ids = [];
  234. this.selectedUser.text = [];
  235. this.roleIds = [];
  236. this.deptIds = [];
  237. },
  238. // 完成条件
  239. updateLoopCondition(condition) {
  240. },
  241. /**
  242. * 更新节点数据
  243. */
  244. updateElementTask() {
  245. const taskAttr = Object.create(null);
  246. for (let key in userTaskForm) {
  247. taskAttr[key] = userTaskForm[key];
  248. }
  249. window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);
  250. },
  251. /**
  252. * 查询部门下拉树结构
  253. */
  254. getDeptOptions() {
  255. return new Promise((resolve, reject) => {
  256. if (!this.deptOptions || this.deptOptions.length <= 0) {
  257. deptTreeSelect().then(response => {
  258. this.deptTempOptions = response.data;
  259. this.deptOptions = response.data;
  260. resolve()
  261. })
  262. } else {
  263. reject()
  264. }
  265. });
  266. },
  267. /**
  268. * 查询部门下拉树结构(含部门前缀)
  269. */
  270. getDeptTreeData() {
  271. function refactorTree(data) {
  272. return data.map(node => {
  273. let treeData = { id: `DEPT${node.id}`, label: node.label, parentId: node.parentId, weight: node.weight };
  274. if (node.children && node.children.length > 0) {
  275. treeData.children = refactorTree(node.children);
  276. }
  277. return treeData;
  278. });
  279. }
  280. return new Promise((resolve, reject) => {
  281. if (!this.deptTreeData || this.deptTreeData.length <= 0) {
  282. this.getDeptOptions().then(() => {
  283. this.deptTreeData = refactorTree(this.deptOptions);
  284. resolve()
  285. }).catch(() => {
  286. reject()
  287. })
  288. } else {
  289. resolve()
  290. }
  291. })
  292. },
  293. /**
  294. * 查询部门下拉树结构
  295. */
  296. getRoleOptions() {
  297. if (!this.roleOptions || this.roleOptions.length <= 0) {
  298. listRole().then(response => this.roleOptions = response.rows);
  299. }
  300. },
  301. /** 查询用户列表 */
  302. getUserList() {
  303. listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
  304. this.userTableList = response.rows;
  305. this.userTotal = response.total;
  306. });
  307. },
  308. // 筛选节点
  309. filterNode(value, data) {
  310. if (!value) return true;
  311. return data.label.indexOf(value) !== -1;
  312. },
  313. // 节点单击事件
  314. handleNodeClick(data) {
  315. this.queryParams.deptId = data.id;
  316. this.getUserList();
  317. },
  318. // 关闭标签
  319. handleClose(tag) {
  320. this.selectedUserDate.splice(this.selectedUserDate.indexOf(tag), 1);
  321. this.$refs.multipleTable.toggleRowSelection(tag);
  322. },
  323. // 多选框选中数据
  324. handleSelectionChange(selection) {
  325. this.selectedUserDate = selection;
  326. },
  327. onSelectUsers() {
  328. this.selectedUserDate = []
  329. this.$refs.multipleTable?.clearSelection();
  330. this.getDeptOptions();
  331. this.userOpen = true;
  332. },
  333. handleTaskUserComplete() {
  334. if (!this.selectedUserDate || this.selectedUserDate.length <= 0) {
  335. this.$modal.msgError('请选择用户');
  336. return;
  337. }
  338. userTaskForm.dataType = 'USERS';
  339. this.selectedUser.text = this.selectedUserDate.map(k => k.nickName) || [];
  340. if (this.selectedUserDate.length === 1) {
  341. let data = this.selectedUserDate[0];
  342. userTaskForm.assignee = data.userName;
  343. userTaskForm.text = data.nickName;
  344. userTaskForm.candidateUsers = null;
  345. this.showMultiFlog = false;
  346. this.multiLoopType = 'Null';
  347. this.changeMultiLoopType();
  348. } else {
  349. userTaskForm.candidateUsers = this.selectedUserDate.map(k => k.userName).join() || null;
  350. userTaskForm.text = this.selectedUserDate.map(k => k.nickName).join() || null;
  351. userTaskForm.assignee = null;
  352. this.showMultiFlog = true;
  353. }
  354. this.updateElementTask()
  355. this.userOpen = false;
  356. },
  357. changeSelectRoles(val) {
  358. let groups = null;
  359. let text = null;
  360. if (val && val.length > 0) {
  361. userTaskForm.dataType = 'ROLES';
  362. groups = val.join() || null;
  363. let textArr = this.roleOptions.filter(k => val.indexOf(`ROLE${k.roleId}`) >= 0);
  364. text = textArr?.map(k => k.roleName).join() || null;
  365. } else {
  366. userTaskForm.dataType = null;
  367. this.multiLoopType = 'Null';
  368. }
  369. userTaskForm.candidateGroups = groups;
  370. userTaskForm.text = text;
  371. this.updateElementTask();
  372. this.changeMultiLoopType();
  373. },
  374. checkedDeptChange(checkedIds) {
  375. let groups = null;
  376. let text = null;
  377. this.deptIds = checkedIds;
  378. if (checkedIds && checkedIds.length > 0) {
  379. userTaskForm.dataType = 'DEPTS';
  380. groups = checkedIds.join() || null;
  381. let textArr = []
  382. let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
  383. checkedIds.forEach(id => {
  384. let stark = []
  385. stark = stark.concat(treeStarkData);
  386. while(stark.length) {
  387. let temp = stark.shift();
  388. if(temp.children) {
  389. stark = temp.children.concat(stark);
  390. }
  391. if(id === temp.id) {
  392. textArr.push(temp);
  393. }
  394. }
  395. })
  396. text = textArr?.map(k => k.label).join() || null;
  397. } else {
  398. userTaskForm.dataType = null;
  399. this.multiLoopType = 'Null';
  400. }
  401. userTaskForm.candidateGroups = groups;
  402. userTaskForm.text = text;
  403. this.updateElementTask();
  404. this.changeMultiLoopType();
  405. },
  406. changeDataType(val) {
  407. if (val === 'ROLES' || val === 'DEPTS' || (val === 'USERS' && this.selectedUser.ids.length > 1)) {
  408. this.showMultiFlog = true;
  409. } else {
  410. this.showMultiFlog = false;
  411. }
  412. this.multiLoopType = 'Null';
  413. this.changeMultiLoopType();
  414. // 清空 userTaskForm 所有属性值
  415. Object.keys(userTaskForm).forEach(key => userTaskForm[key] = null);
  416. userTaskForm.dataType = val;
  417. console.log("changeDataType this.selectedUser",this.selectedUser);
  418. if (val === 'USERS') {
  419. if (this.selectedUser && this.selectedUser.ids && this.selectedUser.ids.length > 0) {
  420. if (this.selectedUser.ids.length === 1) {
  421. userTaskForm.assignee = this.selectedUser.ids[0];
  422. } else {
  423. userTaskForm.candidateUsers = this.selectedUser.ids.join()
  424. }
  425. userTaskForm.text = this.selectedUser.text?.join() || null
  426. }
  427. } else if (val === 'ROLES') {
  428. this.getRoleOptions();
  429. if (this.roleIds && this.roleIds.length > 0) {
  430. userTaskForm.candidateGroups = this.roleIds.join() || null;
  431. let textArr = this.roleOptions.filter(k => this.roleIds.indexOf(`ROLE${k.roleId}`) >= 0);
  432. userTaskForm.text = textArr?.map(k => k.roleName).join() || null;
  433. }
  434. } else if (val === 'DEPTS') {
  435. this.getDeptTreeData();
  436. if (this.deptIds && this.deptIds.length > 0) {
  437. userTaskForm.candidateGroups = this.deptIds.join() || null;
  438. let textArr = []
  439. let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));
  440. this.deptIds.forEach(id => {
  441. let stark = []
  442. stark = stark.concat(treeStarkData);
  443. while(stark.length) {
  444. let temp = stark.shift();
  445. if(temp.children) {
  446. stark = temp.children.concat(stark);
  447. }
  448. if(id === temp.id) {
  449. textArr.push(temp);
  450. }
  451. }
  452. })
  453. userTaskForm.text = textArr?.map(k => k.label).join() || null;
  454. }
  455. } else if (val === 'MANAGER') {
  456. userTaskForm.assignee = "${DepManagerHandler.getUser(execution)}";
  457. userTaskForm.text = "部门经理";
  458. } else if (val === 'INITIATOR') {
  459. userTaskForm.assignee = "${initiator}";
  460. userTaskForm.text = "流程发起人";
  461. }
  462. this.updateElementTask();
  463. },
  464. getElementLoop(businessObject) {
  465. if (!businessObject.loopCharacteristics) {
  466. this.multiLoopType = "Null";
  467. return;
  468. }
  469. this.isSequential = businessObject.loopCharacteristics.isSequential;
  470. if (businessObject.loopCharacteristics.completionCondition) {
  471. if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances >= nrOfInstances}") {
  472. this.multiLoopType = "SequentialMultiInstance";
  473. } else if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances > 0}") {
  474. this.multiLoopType = "ParallelMultiInstance";
  475. } else {
  476. this.multiLoopType = "CustomMultiInstance";
  477. }
  478. }
  479. },
  480. changeMultiLoopType() {
  481. // 取消多实例配置
  482. if (this.multiLoopType === "Null") {
  483. window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { loopCharacteristics: null, assignee: null });
  484. return;
  485. }
  486. this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: this.isSequential });
  487. // 更新多实例配置
  488. window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {
  489. loopCharacteristics: this.multiLoopInstance,
  490. assignee: '${assignee}'
  491. });
  492. // 完成条件
  493. let completionCondition = null;
  494. // 会签
  495. if (this.multiLoopType === "SequentialMultiInstance") {
  496. completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances >= nrOfInstances}" });
  497. }
  498. // 或签
  499. if (this.multiLoopType === "ParallelMultiInstance") {
  500. completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances > 0}" });
  501. }
  502. // 自定义会签
  503. if (this.multiLoopType === "CustomMultiInstance") {
  504. completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: this.CustomCompletionCondition });
  505. }
  506. // 更新模块属性信息
  507. window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, {
  508. collection: '${multiInstanceHandler.getUserNames(execution)}',
  509. elementVariable: 'assignee',
  510. completionCondition
  511. });
  512. },
  513. }
  514. };
  515. </script>
  516. <style scoped lang="scss">
  517. .el-row .el-radio-group {
  518. margin-bottom: 15px;
  519. .el-radio {
  520. line-height: 28px;
  521. }
  522. }
  523. .el-tag {
  524. margin-bottom: 10px;
  525. + .el-tag {
  526. margin-left: 10px;
  527. }
  528. }
  529. .custom-label {
  530. padding-left: 5px;
  531. font-weight: 500;
  532. font-size: 14px;
  533. color: #606266;
  534. }
  535. </style>

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

  1. @Override
  2. public List<String> selectUserNames(List<Long> userIds) {
  3. List<SysUser> listuser = baseMapper.selectBatchIds(userIds);
  4. List<String> 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<String> getUserNames(DelegateExecution execution) {
  10. Set<String> 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<Long> groups = userTask.getCandidateGroups().stream()
  21. .map(item -> Long.parseLong(item.substring(4)))
  22. .collect(Collectors.toList());
  23. List<Long> userIds = new ArrayList<>();
  24. List<String> userNames = new ArrayList<>();
  25. if ("ROLES".equals(dataType)) {
  26. // 通过角色id,获取所有用户id集合
  27. LambdaQueryWrapper<SysUserRole> 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<SysUser> 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、效果图如下:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/888570
推荐阅读
相关标签
  

闽ICP备14008679号