更多ruoyi-nbcio功能请看演示系统
gitee源代码地址
前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio
演示地址:RuoYi-Nbcio后台管理系统
1、前端代码,主要修改下面这个文件,如下:
说明几点:
1.1 增加界面
1.2 增加自定义条件的逻辑与保存
<template><div><el-row><h4><b>审批人设置</b></h4><el-radio-group v-model="dataType" @change="changeDataType"><el-radio label="USERS">指定用户</el-radio><el-radio label="ROLES">角色</el-radio><el-radio label="DEPTS">部门</el-radio><el-radio label="INITIATOR">发起人</el-radio><el-radio label="MANAGER">部门经理</el-radio></el-radio-group></el-row><el-row><div v-if="dataType === 'USERS'"><el-tag v-for="userText in selectedUser.text" :key="userText" effect="plain">{{userText}}</el-tag><div class="element-drawer__button"><el-button size="mini" type="primary" icon="el-icon-plus" @click="onSelectUsers()">添加用户</el-button></div></div><div v-if="dataType === 'ROLES'"><el-select v-model="roleIds" multiple size="mini" placeholder="请选择 角色" @change="changeSelectRoles"><el-optionv-for="item in roleOptions":key="item.roleId":label="item.roleName":value="`ROLE${item.roleId}`":disabled="item.status === 1"></el-option></el-select></div><div v-if="dataType === 'DEPTS'"><tree-select:width="320":height="400"size="mini":data="deptTreeData":defaultProps="deptProps"multipleclearablecheckStrictlynodeKey="id":checkedKeys="deptIds"@change="checkedDeptChange"></tree-select></div></el-row><el-row><div v-show="showMultiFlog"><el-divider /><h4><b>多实例审批方式</b></h4><el-row><el-radio-group v-model="multiLoopType" @change="changeMultiLoopType()"><el-row><el-radio label="Null">无</el-radio></el-row><el-row><el-radio label="SequentialMultiInstance">会签(需所有审批人同意)</el-radio></el-row><el-row><el-radio label="ParallelMultiInstance">或签(一名审批人同意即可)</el-radio></el-row><el-row><el-radio label="CustomMultiInstance">自定义会签条件</el-radio></el-row></el-radio-group></el-row><el-row v-if="multiLoopType === 'CustomMultiInstance'"><el-input v-model="CustomCompletionCondition" clearable @change="updateLoopCondition" /></el-row><el-row v-if="multiLoopType !== 'Null'"><el-tooltip content="开启后,实例需按顺序轮流审批" placement="top-start" @click.stop.prevent><i class="header-icon el-icon-info"></i></el-tooltip><span class="custom-label">顺序审批:</span><el-switch v-model="isSequential" @change="changeMultiLoopType()" /></el-row></div></el-row><!-- 候选用户弹窗 --><el-dialog title="候选用户" :visible.sync="userOpen" width="60%" append-to-body><el-row type="flex" :gutter="20"><!--部门数据--><el-col :span="7"><el-card shadow="never" style="height: 100%"><div slot="header"><span>部门列表</span></div><div class="head-container"><el-inputv-model="deptName"placeholder="请输入部门名称"clearablesize="small"prefix-icon="el-icon-search"style="margin-bottom: 20px"/><el-tree:data="deptOptions":props="deptProps":expand-on-click-node="false":filter-node-method="filterNode"ref="tree"default-expand-all@node-click="handleNodeClick"/></div></el-card></el-col><el-col :span="17"><el-table ref="multipleTable" height="600" :data="userTableList" border @selection-change="handleSelectionChange"><el-table-column type="selection" width="50" align="center" /><el-table-column label="用户名" align="center" prop="nickName" /><el-table-column label="部门" align="center" prop="dept.deptName" /></el-table><pagination:total="userTotal":page.sync="queryParams.pageNum":limit.sync="queryParams.pageSize"@pagination="getUserList"/></el-col></el-row><div slot="footer" class="dialog-footer"><el-button type="primary" @click="handleTaskUserComplete">确 定</el-button><el-button @click="userOpen = false">取 消</el-button></div></el-dialog></div></template><script>
import { listUser, deptTreeSelect } from "@/api/system/user";
import { listRole } from "@/api/system/role";
import TreeSelect from "@/components/TreeSelect";const userTaskForm = {dataType: '',assignee: '',candidateUsers: '',candidateGroups: '',text: '',// dueDate: '',// followUpDate: '',// priority: ''
}export default {name: "UserTask",props: {id: String,type: String},components: { TreeSelect },data() {return {loading: false,dataType: 'USERS',selectedUser: {ids: [],text: []},userOpen: false,deptName: undefined,deptOptions: [],deptProps: {children: "children",label: "label"},deptTempOptions: [],userTableList: [],userTotal: 0,selectedUserDate: [],roleOptions: [],roleIds: [],deptTreeData: [],deptIds: [],// 查询参数queryParams: {deptId: undefined},showMultiFlog: false,isSequential: false,multiLoopType: 'Null',CustomCompletionCondition: '${nrOfCompletedInstances/nrOfInstances>=1}',};},watch: {id: {immediate: true,handler() {this.bpmnElement = window.bpmnInstances.bpmnElement;this.$nextTick(() => this.resetTaskForm());}},// 根据名称筛选部门树deptName(val) {this.$refs.tree.filter(val);}},beforeDestroy() {this.bpmnElement = null;},methods: {resetTaskForm() {const bpmnElementObj = this.bpmnElement?.businessObject;if (!bpmnElementObj) {return;}this.clearOptionsData()this.dataType = bpmnElementObj['dataType'];if (this.dataType === 'USERS') {let userIdData = bpmnElementObj['candidateUsers'] || bpmnElementObj['assignee'];let userText = bpmnElementObj['text'] || [];if (userIdData && userIdData.toString().length > 0 && userText && userText.length > 0) {this.selectedUser.ids = userIdData?.toString().split(',');this.selectedUser.text = userText?.split(',');}if (this.selectedUser.ids.length > 1) {this.showMultiFlog = true;}} else if (this.dataType === 'ROLES') {this.getRoleOptions();let roleIdData = bpmnElementObj['candidateGroups'] || [];if (roleIdData && roleIdData.length > 0) {this.roleIds = roleIdData.split(',')}this.showMultiFlog = true;} else if (this.dataType === 'DEPTS') {this.getDeptTreeData();let deptIdData = bpmnElementObj['candidateGroups'] || [];if (deptIdData && deptIdData.length > 0) {this.deptIds = deptIdData.split(',');}this.showMultiFlog = true;}this.getElementLoop(bpmnElementObj);},/*** 清空选项数据*/clearOptionsData() {this.selectedUser.ids = [];this.selectedUser.text = [];this.roleIds = [];this.deptIds = [];},// 完成条件updateLoopCondition(condition) {},/*** 更新节点数据*/updateElementTask() {const taskAttr = Object.create(null);for (let key in userTaskForm) {taskAttr[key] = userTaskForm[key];}window.bpmnInstances.modeling.updateProperties(this.bpmnElement, taskAttr);},/*** 查询部门下拉树结构*/getDeptOptions() {return new Promise((resolve, reject) => {if (!this.deptOptions || this.deptOptions.length <= 0) {deptTreeSelect().then(response => {this.deptTempOptions = response.data;this.deptOptions = response.data;resolve()})} else {reject()}});},/*** 查询部门下拉树结构(含部门前缀)*/getDeptTreeData() {function refactorTree(data) {return data.map(node => {let treeData = { id: `DEPT${node.id}`, label: node.label, parentId: node.parentId, weight: node.weight };if (node.children && node.children.length > 0) {treeData.children = refactorTree(node.children);}return treeData;});}return new Promise((resolve, reject) => {if (!this.deptTreeData || this.deptTreeData.length <= 0) {this.getDeptOptions().then(() => {this.deptTreeData = refactorTree(this.deptOptions);resolve()}).catch(() => {reject()})} else {resolve()}})},/*** 查询部门下拉树结构*/getRoleOptions() {if (!this.roleOptions || this.roleOptions.length <= 0) {listRole().then(response => this.roleOptions = response.rows);}},/** 查询用户列表 */getUserList() {listUser(this.addDateRange(this.queryParams, this.dateRange)).then(response => {this.userTableList = response.rows;this.userTotal = response.total;});},// 筛选节点filterNode(value, data) {if (!value) return true;return data.label.indexOf(value) !== -1;},// 节点单击事件handleNodeClick(data) {this.queryParams.deptId = data.id;this.getUserList();},// 关闭标签handleClose(tag) {this.selectedUserDate.splice(this.selectedUserDate.indexOf(tag), 1);this.$refs.multipleTable.toggleRowSelection(tag);},// 多选框选中数据handleSelectionChange(selection) {this.selectedUserDate = selection;},onSelectUsers() {this.selectedUserDate = []this.$refs.multipleTable?.clearSelection();this.getDeptOptions();this.userOpen = true;},handleTaskUserComplete() {if (!this.selectedUserDate || this.selectedUserDate.length <= 0) {this.$modal.msgError('请选择用户');return;}userTaskForm.dataType = 'USERS';this.selectedUser.text = this.selectedUserDate.map(k => k.nickName) || [];if (this.selectedUserDate.length === 1) {let data = this.selectedUserDate[0];userTaskForm.assignee = data.userName;userTaskForm.text = data.nickName;userTaskForm.candidateUsers = null;this.showMultiFlog = false;this.multiLoopType = 'Null';this.changeMultiLoopType();} else {userTaskForm.candidateUsers = this.selectedUserDate.map(k => k.userName).join() || null;userTaskForm.text = this.selectedUserDate.map(k => k.nickName).join() || null;userTaskForm.assignee = null;this.showMultiFlog = true;}this.updateElementTask()this.userOpen = false;},changeSelectRoles(val) {let groups = null;let text = null;if (val && val.length > 0) {userTaskForm.dataType = 'ROLES';groups = val.join() || null;let textArr = this.roleOptions.filter(k => val.indexOf(`ROLE${k.roleId}`) >= 0);text = textArr?.map(k => k.roleName).join() || null;} else {userTaskForm.dataType = null;this.multiLoopType = 'Null';}userTaskForm.candidateGroups = groups;userTaskForm.text = text;this.updateElementTask();this.changeMultiLoopType();},checkedDeptChange(checkedIds) {let groups = null;let text = null;this.deptIds = checkedIds;if (checkedIds && checkedIds.length > 0) {userTaskForm.dataType = 'DEPTS';groups = checkedIds.join() || null;let textArr = []let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));checkedIds.forEach(id => {let stark = []stark = stark.concat(treeStarkData);while(stark.length) {let temp = stark.shift();if(temp.children) {stark = temp.children.concat(stark);}if(id === temp.id) {textArr.push(temp);}}})text = textArr?.map(k => k.label).join() || null;} else {userTaskForm.dataType = null;this.multiLoopType = 'Null';}userTaskForm.candidateGroups = groups;userTaskForm.text = text;this.updateElementTask();this.changeMultiLoopType();},changeDataType(val) {if (val === 'ROLES' || val === 'DEPTS' || (val === 'USERS' && this.selectedUser.ids.length > 1)) {this.showMultiFlog = true;} else {this.showMultiFlog = false;}this.multiLoopType = 'Null';this.changeMultiLoopType();// 清空 userTaskForm 所有属性值Object.keys(userTaskForm).forEach(key => userTaskForm[key] = null);userTaskForm.dataType = val;console.log("changeDataType this.selectedUser",this.selectedUser);if (val === 'USERS') {if (this.selectedUser && this.selectedUser.ids && this.selectedUser.ids.length > 0) {if (this.selectedUser.ids.length === 1) {userTaskForm.assignee = this.selectedUser.ids[0];} else {userTaskForm.candidateUsers = this.selectedUser.ids.join()}userTaskForm.text = this.selectedUser.text?.join() || null}} else if (val === 'ROLES') {this.getRoleOptions();if (this.roleIds && this.roleIds.length > 0) {userTaskForm.candidateGroups = this.roleIds.join() || null;let textArr = this.roleOptions.filter(k => this.roleIds.indexOf(`ROLE${k.roleId}`) >= 0);userTaskForm.text = textArr?.map(k => k.roleName).join() || null;}} else if (val === 'DEPTS') {this.getDeptTreeData();if (this.deptIds && this.deptIds.length > 0) {userTaskForm.candidateGroups = this.deptIds.join() || null;let textArr = []let treeStarkData = JSON.parse(JSON.stringify(this.deptTreeData));this.deptIds.forEach(id => {let stark = []stark = stark.concat(treeStarkData);while(stark.length) {let temp = stark.shift();if(temp.children) {stark = temp.children.concat(stark);}if(id === temp.id) {textArr.push(temp);}}})userTaskForm.text = textArr?.map(k => k.label).join() || null;}} else if (val === 'MANAGER') {userTaskForm.assignee = "${DepManagerHandler.getUser(execution)}";userTaskForm.text = "部门经理";} else if (val === 'INITIATOR') {userTaskForm.assignee = "${initiator}";userTaskForm.text = "流程发起人";}this.updateElementTask();},getElementLoop(businessObject) {if (!businessObject.loopCharacteristics) {this.multiLoopType = "Null";return;}this.isSequential = businessObject.loopCharacteristics.isSequential;if (businessObject.loopCharacteristics.completionCondition) {if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances >= nrOfInstances}") {this.multiLoopType = "SequentialMultiInstance";} else if (businessObject.loopCharacteristics.completionCondition.body === "${nrOfCompletedInstances > 0}") {this.multiLoopType = "ParallelMultiInstance";} else {this.multiLoopType = "CustomMultiInstance";}}},changeMultiLoopType() {// 取消多实例配置if (this.multiLoopType === "Null") {window.bpmnInstances.modeling.updateProperties(this.bpmnElement, { loopCharacteristics: null, assignee: null });return;}this.multiLoopInstance = window.bpmnInstances.moddle.create("bpmn:MultiInstanceLoopCharacteristics", { isSequential: this.isSequential });// 更新多实例配置window.bpmnInstances.modeling.updateProperties(this.bpmnElement, {loopCharacteristics: this.multiLoopInstance,assignee: '${assignee}'});// 完成条件let completionCondition = null;// 会签if (this.multiLoopType === "SequentialMultiInstance") {completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances >= nrOfInstances}" });}// 或签if (this.multiLoopType === "ParallelMultiInstance") {completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: "${nrOfCompletedInstances > 0}" });}// 自定义会签if (this.multiLoopType === "CustomMultiInstance") {completionCondition = window.bpmnInstances.moddle.create("bpmn:FormalExpression", { body: this.CustomCompletionCondition });}// 更新模块属性信息window.bpmnInstances.modeling.updateModdleProperties(this.bpmnElement, this.multiLoopInstance, {collection: '${multiInstanceHandler.getUserNames(execution)}',elementVariable: 'assignee',completionCondition});},}
};
</script><style scoped lang="scss">
.el-row .el-radio-group {margin-bottom: 15px;.el-radio {line-height: 28px;}
}
.el-tag {margin-bottom: 10px;+ .el-tag {margin-left: 10px;}
}.custom-label {padding-left: 5px;font-weight: 500;font-size: 14px;color: #606266;
}</style>
2、增加一个方法通过多ids来获取userNames,因为现在流程都是通过userName来执行流转
@Overridepublic List<String> selectUserNames(List<Long> userIds) {List<SysUser> listuser = baseMapper.selectBatchIds(userIds);List<String> userNames = new ArrayList<>();for(SysUser sysuser: listuser) {userNames.add(sysuser.getUserName());}return userNames;}
}
3、多实例处理类修改如下
/*** 多实例处理类** @author nbacheng*/
@AllArgsConstructor
@Component("multiInstanceHandler")
public class MultiInstanceHandler {public Set<String> getUserNames(DelegateExecution execution) {Set<String> candidateUserNames = new LinkedHashSet<>();FlowElement flowElement = execution.getCurrentFlowElement();if (ObjectUtil.isNotEmpty(flowElement) && flowElement instanceof UserTask) {UserTask userTask = (UserTask) flowElement;String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, ProcessConstants.PROCESS_CUSTOM_DATA_TYPE);if ("USERS".equals(dataType) && CollUtil.isNotEmpty(userTask.getCandidateUsers())) {// 添加候选用户candidateUserNames.addAll(userTask.getCandidateUsers());} else if (CollUtil.isNotEmpty(userTask.getCandidateGroups())) {// 获取组的ID,角色ID集合或部门ID集合List<Long> groups = userTask.getCandidateGroups().stream().map(item -> Long.parseLong(item.substring(4))).collect(Collectors.toList());List<Long> userIds = new ArrayList<>();List<String> userNames = new ArrayList<>();if ("ROLES".equals(dataType)) {// 通过角色id,获取所有用户id集合LambdaQueryWrapper<SysUserRole> lqw = Wrappers.lambdaQuery(SysUserRole.class).select(SysUserRole::getUserId).in(SysUserRole::getRoleId, groups);userIds = SimpleQuery.list(lqw, SysUserRole::getUserId);} else if ("DEPTS".equals(dataType)) {// 通过部门id,获取所有用户id集合LambdaQueryWrapper<SysUser> lqw = Wrappers.lambdaQuery(SysUser.class).select(SysUser::getUserId).in(SysUser::getDeptId, groups);userIds = SimpleQuery.list(lqw, SysUser::getUserId);}// 添加候选用户ISysUserService sysUserService = SpringContextUtils.getBean(ISysUserService.class);userNames = sysUserService.selectUserNames(userIds);userNames.forEach(userName -> candidateUserNames.add(userName));}}return candidateUserNames;}
}
4、效果图如下: