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

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

更多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、效果图如下:

相关文章
|
6月前
|
前端开发
基于若依ruoyi-nbcio支持flowable流程增加自定义业务表单(二)
基于若依ruoyi-nbcio支持flowable流程增加自定义业务表单(二)
276 1
|
6月前
|
前端开发
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(二)
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(二)
402 2
|
6月前
|
SQL JavaScript 前端开发
基于若依ruoyi-nbcio支持flowable流程增加自定义业务表单(一)
基于若依ruoyi-nbcio支持flowable流程增加自定义业务表单(一)
485 2
|
6月前
基于若依ruoyi-nbcio支持flowable流程分类里增加流程应用类型
基于若依ruoyi-nbcio支持flowable流程分类里增加流程应用类型
158 1
|
6月前
|
前端开发
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(一)
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(一)
162 1
|
6月前
基于若依ruoyi-nbcio支持flowable流程增加自定义业务表单(三)
基于若依ruoyi-nbcio支持flowable流程增加自定义业务表单(三)
304 1
|
6月前
|
资源调度 前端开发 NoSQL
ruoyi-nbcio版本从RuoYi-Flowable-Plus迁移过程记录
ruoyi-nbcio版本从RuoYi-Flowable-Plus迁移过程记录
73 1
|
6月前
|
SQL 前端开发 数据库
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程的集成方法与步骤(一)
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程的集成方法与步骤(一)
289 2
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程的集成方法与步骤(一)
|
6月前
|
前端开发
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(四)
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(四)
86 1
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(四)
|
6月前
|
SQL 前端开发
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(五)
基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(五)
139 2
下一篇
无影云桌面