更多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-option v-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" multiple clearable checkStrictly nodeKey="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-input v-model="deptName" placeholder="请输入部门名称" clearable size="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来执行流转
@Override public 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、效果图如下: