springboot+activiti7+react实现模仿钉钉功能的审批流(五、实现类似钉钉的流程设计器)

springboot+activiti7+react实现模仿钉钉功能的审批流(五、实现类似钉钉的流程设计器)_第1张图片

开发自定义流程设计器的原因:
软件领域,一直都是没有最好的方案,只有最适合的方案,单说方案,没有任何意义,要结合应用场景,不然就不会有这么多的开发语言、技术框架,早就被最好的一统江湖了。
目前比较流行的设计器:
1.activiti-modeler:
    优点:灵活,可以实现各种bpmn流程设计操作;
    缺点:学习成本高,容易配置出错,配置流程只能由熟悉这套逻辑的人,甚至是只有开发人员才能配置,人工成本高;
    适用场景:流程灵活复杂的场景;
2.bpmn.js:
    优点:灵活度适中,可定制开发;
    缺点:若扩展功能,需额外开发成本;学习成本稍低,但也需要熟悉bpmn规范,配置流程只能由熟悉这套逻辑的人配置,人工成本稍高;
    适用场景:流程不复杂,只需要简易的bpmn流程功能的场景;
3.类似钉钉的流程设计器:
    优点:流程配置简单,学习成本低,普通用户容易上手;
    缺点:开发成本高,只能配置简易的流程;
    适用场景:流程简单,需C端用户自己配置流程的场景;

这块是整个系统最复杂的部分,需要熟悉bpmn规范,算法,后端部署操作;

实现的核心是前端操作和生成bpmn的xml字符串给后端直接使用,后端不用做什么,只需要1.存xml 2.查询返回xml给前端操作 3.部署xml;所以工作量基本都在前端;

具体步骤:
新建流程:
1.前端直接从定义好的xml模板中初始化;
    这里的模板其实就是提前在保存好的xml文件,钉钉也有类似的功能,进去之后左侧可以选择一些它设置好的流程。我这里预习放了一个模板:  '发起人' -> '审批人' -> '抄送' -> '结束'。这里也是一个技巧,bpmn-moddle不需要创建对象在操作属性,可以直接从模板中创建对象再操作属性,会方便很多,类似写文章填空(审批人、分支条件、抄送人)总比重头写省不少事;

springboot+activiti7+react实现模仿钉钉功能的审批流(五、实现类似钉钉的流程设计器)_第2张图片

上几个模板的xml代码吧:

新建流程进去之后的模板: '发起人' -> '审批人' -> '抄送' -> '结束'



  
    
      sid-DE4A60B5-3607-4501-BC30-C83C86223148
    
    
    
      
        
          
            0
          
          
            
          
          
            
          
          
            
          
          
            
          
          
            
          
          
            
          
          
            
          
          
            SAIUWVDZ
          
        
        
      
      
        ${nrOfCompletedInstances == nrOfInstances}
      
    
    
    
      
        
          
        
        
          
        
        
          
        
      
    
    
    
      sid-46FA3418-A51D-45EB-9337-FB7A1D8CEDDC
    
  

解读:

  • taskSequentialListenerCreate,实现了ExecutionListener接口的类,在节点创建时动态指定审批人
  • activiti:field,bpmn-moddle可以塞的各种值比如用户ids、角色ids,然后在taskSequentialListenerCreate里面获取并做操作
  • copyService,抄送bean

taskSequentialListenerCreate:


/**
 * 会签任务监听-创建
 * 指定collection参数,会签人
 * 

* * * * * * * * * * * * * * * * * */ @Data @Slf4j @Service public class TaskSequentialListenerCreate implements ExecutionListener { /** * 会签类型(0:指定人员,1:角色,2:主管,3:多级主管,4:发起人自己) */ private Expression type; /** * 集合key,为了区分多个会签任务节点(集合(多实例)设置的字符串) */ private Expression collectionKey; /** * 用户ls */ private Expression users; /** * 角色ls(财务、人事、研发、行政...,钉钉这里只能是一个角色,此处做了升级优化,可以设置多个角色) */ private Expression roles; /** * 主管ls(发起人的直接主管、从下往上第0....19级主管) */ private Expression grade; /** * 通讯录中的第xx级主管,从上往下(第0....19级主管) */ private Expression heightGrade; /** * 多级主管类型 0:指定角色 1:通讯录中的heightGrade */ private Expression moreGradeType; /** * 找不到主管时,由上级主管代审批标记(默认true) true:找不到往上找,false:指点主管层级 */ private Expression gradeNext; /** * 审批人为空时,指定的默认审批用户 */ private Expression defaultUserId; @Autowired private TaskService taskService; @Autowired private RuntimeService runtimeService; @Autowired private CopyService copyService; @Autowired private ISysUserService sysUserService; @Override public void notify(DelegateExecution execution) { String key = String.valueOf(collectionKey.getValue(execution)); LinkedHashSet us = execution.getVariable(key, LinkedHashSet.class); //不空的话就不用重复赋值 if (ObjectUtil.isNotNull(us)) { return; } // log.info("type: {}, collectionKey: {}, users: {}, roles: {}, grade: {}, heightGrade: {}, moreGradeType: {}, gradeNext: {}, defaultUserId: {}, ", // type.getValue(execution), collectionKey.getValue(execution), users.getValue(execution), // roles.getValue(execution), grade.getValue(execution), heightGrade.getValue(execution), // moreGradeType.getValue(execution), gradeNext.getValue(execution), defaultUserId.getValue(execution)); String typeStr = String.valueOf(type.getValue(execution)); //用户id ls,要保证有序 List userList = new ArrayList<>(); List userLs = new ArrayList<>(); // //处理用户 switch (typeStr) { //指定成员 case "0": { Object usersObj = ObjectUtil.isNotEmpty(users) ? users.getValue(execution) : null; if (ObjectUtil.isNotEmpty(usersObj)) { //用户id ls userList = copyService.getList(String.valueOf(usersObj)); } break; } //角色 case "1": { Object rolesObj = ObjectUtil.isNotEmpty(roles) ? roles.getValue(execution) : null; if (ObjectUtil.isNotEmpty(rolesObj)) { List roleList = copyService.getList(String.valueOf(rolesObj)); if (ObjectUtil.isNotEmpty(roleList)) { //角色获取用户 userLs = sysUserService.getUserListByActRoleList(roleList); for (SysUser user : userLs) { userList.add(user.getId()); } } } break; } //主管 case "2": { //默认0 直接主管 Integer gradeNum = 0; Object gradeObj = ObjectUtil.isNotEmpty(grade) ? grade.getValue(execution) : null; if (ObjectUtil.isNotEmpty(gradeObj)) { gradeNum = Integer.parseInt(String.valueOf(gradeObj)); } //默认true 找不到则往上找 Boolean gradeNextFlag = true; Object gradeNextObj = ObjectUtil.isNotEmpty(gradeNext) ? gradeNext.getValue(execution) : null; if (ObjectUtil.isNotEmpty(gradeNextObj)) { gradeNextFlag = Boolean.parseBoolean(String.valueOf(gradeNextObj)); } //获取主管 //发起人所在的部门。 Integer deptId = execution.getVariable("startUserDeptId", Integer.class); if (ObjectUtil.isEmpty(deptId)) { //部门id空的时候,使用启动用户所在部门id //这里不能这样查,第一个节点的pi查出来是null的! // ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult(); // Integer startUserId = Integer.parseInt(pi.getStartUserId()); //发起人用户id Integer startUserId = execution.getVariable("startUserId", Integer.class); deptId = sysUserService.getById(startUserId).getDeptId(); } if (ObjectUtil.isNotEmpty(deptId)) { userLs = sysUserService.getUsersByDeptId(deptId, gradeNum, gradeNextFlag); for (SysUser user : userLs) { userList.add(user.getId()); } } break; } //多级主管,实现上有个问题就是一个部门上有多个主管的,目前做法的随机的顺序签 case "3": { //多级主管类型 0:指定角色 1:通讯录中的heightGrade String moreGradeTypeStr = "0"; Object moreGradeTypeObj = ObjectUtil.isNotEmpty(moreGradeType) ? moreGradeType.getValue(execution) : null; if (ObjectUtil.isNotEmpty(moreGradeTypeObj)) { moreGradeTypeStr = String.valueOf(moreGradeTypeObj); } //角色获取用户 Set userRoleList = new HashSet<>(); Object rolesObj = ObjectUtil.isNotEmpty(roles) ? roles.getValue(execution) : null; if (ObjectUtil.isNotEmpty(rolesObj)) { List roleList = copyService.getList(String.valueOf(rolesObj)); if (ObjectUtil.isNotEmpty(roleList)) { for (SysUser user : sysUserService.getUserListByActRoleList(roleList)) { userRoleList.add(user.getId()); } } } //向上层级,默认0 Integer gradeNum = 0; Object gradeObj = ObjectUtil.isNotEmpty(grade) ? grade.getValue(execution) : null; if (ObjectUtil.isNotEmpty(gradeObj)) { gradeNum = Integer.parseInt(String.valueOf(gradeObj)); } //向下层级,默认0 Integer heightGradeNum = 0; Object heightGradeObj = ObjectUtil.isNotEmpty(heightGrade) ? heightGrade.getValue(execution) : null; if (ObjectUtil.isNotEmpty(heightGradeObj)) { heightGradeNum = Integer.parseInt(String.valueOf(heightGradeObj)); } //启动用户所在部门 Integer deptId = execution.getVariable("startUserDeptId", Integer.class); if (ObjectUtil.isEmpty(deptId)) { //部门id空的时候,使用启动用户所在部门id //这里不能这样查,第一个节点的pi查出来是null的! // ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(execution.getProcessInstanceId()).singleResult(); // Integer startUserId = Integer.parseInt(pi.getStartUserId()); //发起人用户id Integer startUserId = execution.getVariable("startUserId", Integer.class); deptId = sysUserService.getById(startUserId).getDeptId(); } if (ObjectUtil.isNotEmpty(deptId)) { userLs = sysUserService.getUsersByDeptId(deptId, moreGradeTypeStr, userRoleList, gradeNum, heightGradeNum); for (SysUser user : userLs) { userList.add(user.getId()); } } break; } //发起人自己 case "4": { //发起人用户id Integer startUserId = execution.getVariable("startUserId", Integer.class); userList.add(startUserId); } default: } if (ObjectUtil.isEmpty(userList)) { //审批人为空时指定默认用户,defaultUserId默认为0,兜底处理,userList为空数组的话,流程里面这个节点就跳过了!!! Integer defaultUser = 0; Object defaultUserObj = ObjectUtil.isNotEmpty(defaultUserId) ? defaultUserId.getValue(execution) : null; if (ObjectUtil.isNotEmpty(defaultUserObj)) { defaultUser = Integer.parseInt(String.valueOf(defaultUserObj)); } log.info("defaultUserId: {}", defaultUser); userList.add(defaultUser); } //去重(避免同一个节点同一个人多次审批) LinkedHashSet userSet = new LinkedHashSet<>(); userSet.addAll(userList); log.info("userSet: {}", userSet); //离职人员不分配任务,需要指派 Set ids = sysUserService.listOffJob(userSet); log.info("ids: {}", ids); // //获取审批人列表 // List> arr = execution.getVariable("assigneeListHis", List.class); // if(ObjectUtil.isEmpty(arr)) { // arr = new ArrayList<>(); // } // if(ObjectUtil.isNotEmpty(userSet)) { // arr.add(userSet); // //塞审批人 // execution.setVariable("assigneeListHis", arr); // } //塞一下审批人类型, 给自动通过审批使用 execution.setVariable("assigneeType", typeStr); //设置会签用户 runtimeService.setVariable(execution.getId(), key, ids); } }

copyService:

/**
 * 抄送任务-服务任务
 * 设计器上
 * 
 * 
 * 
 * 
 * 
 * 
 * 
 *
 * @Author: ltx
 * @Date: 2020/5/15 12:39
 * @Description:
 */
@Service
@Slf4j
public class CopyService implements JavaDelegate {
    @Autowired
    private IActCopyTaskService actCopyTaskService;

    @Autowired
    private ISysUserService sysUserService;

    @Autowired
    @Lazy
    private IActService actService;

    @Autowired
    private DingTalk dingTalk;

    /**
     * 用户ls
     */
    private Expression users;

    /**
     * 角色ls
     */
    private Expression roles;

    /**
     * 主管ls
     */
    private Expression grades;

    @Override
    public void execute(DelegateExecution execution) {
        log.info("抄送服务....start");
        //使用LinkedHashSet,避免同一用户抄送两次
        LinkedHashSet userList = new LinkedHashSet<>();
        //1.处理用户
        Object usersObj = ObjectUtil.isNotEmpty(users) ? users.getValue(execution) : null;
        if (ObjectUtil.isNotEmpty(usersObj)) {
            List us = getList(usersObj);
            log.info("抄送用户us: {}", us);
            userList.addAll(us);
        }
        //2.处理角色
        Object rolesObj = ObjectUtil.isNotEmpty(roles) ? roles.getValue(execution) : null;
        if (ObjectUtil.isNotEmpty(rolesObj)) {
            List rs = getList(rolesObj);
            log.info("抄送角色rs: {}", rs);
            if (ObjectUtil.isNotEmpty(rs)) {
                List users = sysUserService.getUserListByActRoleList(rs);
                for (SysUser user : users) {
                    userList.add(user.getId());
                }
            }
        }
        //处理主管
        //发起人用户id
        Integer startUserId = execution.getVariable("startUserId", Integer.class);
        Object gradesObj = ObjectUtil.isNotEmpty(grades) ? grades.getValue(execution) : null;
        if (ObjectUtil.isNotEmpty(gradesObj)) {
            List gs = getList(gradesObj);
            log.info("抄送主管层级gs: {}", gs);
            if (ObjectUtil.isNotEmpty(gs)) {
                List users = sysUserService.getUsersByGrades(startUserId, gs);
                for (SysUser user : users) {
                    userList.add(user.getId());
                }
            }
        }
        log.info("抄送用户userList: {}", userList);
        //执行抄送
        if (!userList.isEmpty()) {
            //离职人员ls
            Set ls = sysUserService.listOffJobIds(userList);
            if (ls.isEmpty()) {
                copyToUser(userList, execution);
            } else {
                //抄送人里面排除掉离职人员
                Set res = new HashSet<>();
                for (Integer id : userList) {
                    if (!ls.contains(id)) {
                        res.add(id);
                    }
                }
                copyToUser(res, execution);
            }
        }
        log.info("抄送服务....end");
    }

    /**
     * 返回list值
     *
     * @return
     */
    public List getList(Object obj) {
        //用set去重
        List ls = new ArrayList<>();
        if (ObjectUtil.isNotNull(obj)) {
            String objStr = String.valueOf(obj).trim();
            String[] strs = objStr.split(",");
            for (String s : strs) {
                if (StrUtil.isNotBlank(s)) {
                    Integer id = Integer.parseInt(s);
                    if (!id.equals(0)) {
                        ls.add(Integer.parseInt(s));
                    }
                }
            }
        }
        return ls;
    }

    /**
     * 数据保存在抄送表
     *
     * @param us 抄送用户ids
     * @return
     */
    public void copyToUser(Collection us, DelegateExecution execution) {
        if (us.isEmpty()) {
            return;
        }
        List ls = new ArrayList<>();
        for (Integer u : us) {
            ActCopyTask act = new ActCopyTask();
            act.setUserId(u);
            act.setExecutionId(execution.getId());
            act.setProcessInstanceId(execution.getProcessInstanceId());
            act.setProcessDefinitionId(execution.getProcessDefinitionId());
            act.setCreateTime(new Date());
            ls.add(act);
            //推送数据到websocket
            actService.sendTaskByUserId(String.valueOf(u));
        }
        //批量保存
        actCopyTaskService.saveBatch(ls);
    }
}

2.使用bpmn-moddle对节点进行操作;
    比如修改节点属性值,添加自定义属性等操作,这里就可以把审批人设置到节点上,比如配置审批人为'指定成员'、'主管'、'角色'、'连续多级主管'、'发起人自己',多人会签方式:'依次审批'、'会签(须所有审批人同意)'、'或签(一名审批人同意或拒绝即可)',抄送;
    审批人节点用的是bpmn2:userTask节点,抄送用的是bpmn2:serviceTask节点;
3.bpmn-moddle将操作的对象转xml,发给后端存起来,或直接部署;

bpmn-moddle核心方法:

将xml转换成对象,这个对象类似一个json结构,主要由有点和线组成,前端跑一下它README的demo就清楚了;

用有向图的算法操作这个对象(节点新增和删除,节点可以是bpmn规范里面的任意节点),对象类似json结构,上面的节点也有很多function可以任意set设置节点属性(审批人、会签属性、分支条件....),最后把最终的data转成xml给后端即可;

springboot+activiti7+react实现模仿钉钉功能的审批流(五、实现类似钉钉的流程设计器)_第3张图片

springboot+activiti7+react实现模仿钉钉功能的审批流(五、实现类似钉钉的流程设计器)_第4张图片

编辑流程:
1.前端请求后端拿到xml字符串,转bpmn-moddle对象之后操作,完了之后就上上面的第2、3步;

样式和布局:
直接拿了钉钉的前端布局和样式,基本和它一致;当然需要拆解封装各种自己需要的组件,比如发起人节点组件、条件组件、审批人节点组件、抄送节点组件,代码复用组件式开发,数据驱动dom;

前端算法逻辑:
bpmn是一个有向图,只不过它的表现形式是点和线,得用邻接表数据结构表示和操作即可;就是xml->读取节点和线->邻接表->完成图节点的增删->还原点和线->xml。
没写条件分支之前我以为是一个链表,对链表进行节点的增删,觉得还是简单的,写到条件分支(网关)时候才改成有向图算法。

前端js有向图算法部分代码:

import {generateMixed} from '@/utils/tools';
import userTaskTemp from "@/pages/bpmn/edit/template/userTask.xml";
import copyTaskTemp from "@/pages/bpmn/edit/template/copyTask.xml";
import ExclusiveGateway from "@/pages/bpmn/edit/template/ExclusiveGateway.xml";
import sequenceFlow from "@/pages/bpmn/edit/template/sequenceFlow.xml";
import BpmnModdle from 'bpmn-moddle';
import {v4 as uuidv4} from 'uuid';

//所有的顶点, se为出度, te为入度
export let nodes = [
  {id: '1', name: 'S', se: 0, te: 0, adj: [], type: 'start'},
  {id: '6', name: 'G1', se: 0, te: 0, adj: [], type: 'exclusiveGateway'},
  {id: '2', name: 'A', se: 0, te: 0, adj: [], type: 'userTask'},
  {id: '3', name: 'B', se: 0, te: 0, adj: [], type: 'userTask'},
  {id: '4', name: 'C', se: 0, te: 0, adj: [], type: 'userTask'},
  {id: '5', name: 'E', se: 0, te: 0, adj: [], type: 'end'},
  {id: '7', name: 'D', se: 0, te: 0, adj: [], type: 'exclusiveGateway'},
  {id: '8', name: 'F', se: 0, te: 0, adj: [], type: 'userTask'},
  {id: '9', name: 'G', se: 0, te: 0, adj: [], type: 'userTask'},
  {id: '10', name: 'H', se: 0, te: 0, adj: [], type: 'userTask'},
];
//所有的边
export let edges = [
  {sid: '1', tid: '6'},
  {sid: '2', tid: '3'},
  {sid: '3', tid: '4'},
  {sid: '4', tid: '5'},
  {sid: '6', tid: '2'},
  {sid: '6', tid: '7'},
  {sid: '7', tid: '8'},
  {sid: '7', tid: '9'},
  {sid: '8', tid: '10'},
  {sid: '9', tid: '10'},
  {sid: '10', tid: '5'},
];
export let jsonData = [
  {id: '1', name: 'S', se: 0, te: 0, adj: ['6'], type: 'start'},
  {
    id: '6', name: 'G1', se: 0, te: 0, adj: ['2', '5'], type: 'exclusiveGateway',
    conditions: [{
      id: `sid-${uuidv4().toUpperCase()}`,
      name: '条件1',
      extension: {
        des: '',
        jsonStr: '',
      },
      el: '',
      //对象
      children: [
        {id: '2', name: 'A', se: 0, te: 0, adj: ['3'], type: 'userTask'},
        {id: '3', name: 'B', se: 0, te: 0, adj: ['4'], type: 'userTask'},
        {id: '4', name: 'C', se: 0, te: 0, adj: ['5'], type: 'userTask'},
      ]
    }, {
      id: `sid-${uuidv4().toUpperCase()}`,
      name: '条件2',
      extension: {
        des: '',
        jsonStr: '',
      },
      el: '',
      children: []
    }]
  },
  {id: '5', name: 'E', se: 0, te: 0, adj: [], type: 'end'},
];
//使用模版生成bpmn对象,可以省很多事情,简直666
export const getBpmnFromXml = async (type) => {
  let bpmnModdle = new BpmnModdle();
  switch (type) {
    case 'userTask': {
      //设置下集合的key
      let userTaskTempStr = userTaskTemp.replace(
        /collectionKeyValue/g,
        generateMixed(8),
      );
      const {rootElement: definitions} = await bpmnModdle.fromXML(
        userTaskTempStr,
      );
      let userTask = definitions.rootElements[0].flowElements[0];
      //设置下随机id
      userTask.id = `sid-${uuidv4().toUpperCase()}`;
      return userTask;
    }
    case 'copyTask': {
      const {rootElement: definitions} = await bpmnModdle.fromXML(copyTaskTemp);
      let copyTask = definitions.rootElements[0].flowElements[0];
      //设置下随机id
      copyTask.id = `sid-${uuidv4().toUpperCase()}`;
      return copyTask;
    }
    case 'condition': {
      const {rootElement: definitions} = await bpmnModdle.fromXML(ExclusiveGateway);
      let condition = definitions.rootElements[0].flowElements[0];
      //设置下随机id
      condition.id = `sid-${uuidv4().toUpperCase()}`;
      return condition;
    }
    case 'sequenceFlow': {
      const {rootElement: definitions} = await bpmnModdle.fromXML(sequenceFlow);
      let condition = definitions.rootElements[0].flowElements[0];
      //设置下随机id
      condition.id = `sid-${uuidv4().toUpperCase()}`;
      return condition;
    }
  }
  //打印bpmn结果
  // const {rootElement: definitions} = await bpmnModdle.fromXML(userTaskTemp);
  // console.log(definitions.rootElements[0].flowElements[0]);
};
//id查找节点
export const findNodeById = (id) => {
  for (let node of nodes) {
    if (id == node.id) {
      return node;
    }
  }
};
//id查找出度节点
export const findTNodesById = (id) => {
  let tNodes = [];
  for (let node of nodes) {
    if (id == node.id) {
      node.adj.map((a) => {
        tNodes.push(findNodeById(a));
      });
    }
  }
  return tNodes;
};
//id查找入度节点
export const findSNodesById = (id) => {
  let sNodes = [];
  for (let node of nodes) {
    for (let a of node.adj) {
      if (a == id) {
        sNodes.push(node);
        break;
      }
    }
  }
  return sNodes;
};
//查找起始节点(入度为0的则是起始节点)
export const findStartNode = () => {
  for (let node of nodes) {
    if (node.te == 0) {
      return node;
    }
  }
};
//打印顺序的串
export const randerNode = (nodeId, ls) => {
  // console.log(nodeId);
  let node = findNodeById(nodeId);
  ls.push(node);
  if (node.adj.length) {
    return node.adj.map((a) => {
      return randerNode(a, ls);
    });
  }
};
//添加任务节点
export const addNode = async (index, node, parents, type) => {
  // console.log(index, node, parents, type);
  let newNode;
  switch (type) {
    case 'approver': {
      //新节点
      newNode = await getBpmnFromXml('userTask');
      //激活
      newNode.active = true;
      newNode.adj = [];
      newNode.se = 1;
      newNode.te = 1;
      break;
    }
    case 'notifier': {
      //抄送
      newNode = await getBpmnFromXml('copyTask');
      //激活
      newNode.active = true;
      newNode.adj = [];
      newNode.se = 1;
      newNode.te = 1;
      break;
    }
    case 'condition': {
      let ls = [];
      let i = index + 1;
      let newp = [...parents];
      newp.map((item, indexI) => {
        if (indexI > index && item.$type != 'bpmn:EndEvent') {
          ls.push(item);
          parents.splice(i, 1);
        }
      });
      newNode = await getBpmnFromXml('condition');
      newNode.conditions = [{
        id: `sid-${uuidv4().toUpperCase()}`,
        name: '条件1',
        extension: {
          des: '',
          jsonStr: '',
        },
        el: '',
        children: ls
      }, {
        id: `sid-${uuidv4().toUpperCase()}`,
        name: '条件2',
        extension: {
          des: '',
          jsonStr: '',
        },
        el: '',
        children: []
      }];
      break;
    }
  }
  parents.splice(index + 1, 0, newNode);
  // console.log(parents);
  // console.log(jsonData);
  jsonData = [...jsonData];
  return newNode;
};
//删除元素
export const delNode = (index, node, parents) => {
  parents.splice(index, 1);
  // console.log(jsonData);
  jsonData = [...jsonData];
};
//添加条件
export const addNodeCondition = (node) => {
  let newNode = {
    id: `sid-${uuidv4().toUpperCase()}`,
    name: `条件${node.conditions.length + 1}`,
    extension: {
      des: '',
      jsonStr: '',
    },
    el: '',
    children: []
  };
  node.conditions.push(newNode);
  // console.log(jsonData);
  jsonData = [...jsonData];
};
//调整条件节点位置
export const sortNodeCondition = (conditions, index, type) => {
  let temp = conditions[index];
  if (type == 'left') {
    conditions[index] = conditions[index - 1];
    conditions[index - 1] = temp;
  } else if (type == 'right') {
    conditions[index] = conditions[index + 1];
    conditions[index + 1] = temp;
  }
  // console.log(jsonData);
  jsonData = [...jsonData];
};
//删除条件
export const delNodeCondition = (parents, index, gIndex, gParent) => {
  if (parents.length > 2) {
    parents.splice(index, 1);
  } else if (parents.length == 2) {
    //剩余的元素全部放外层
    // console.log(parents[1 - index].children, gIndex, gParent);
    let i = gIndex;
    parents[1 - index].children.map((item) => {
      gParent.splice(i, 0, item);
      i++;
    });
    gParent.splice(i, 1);
  }
  // parents.splice(index, 1);
  // console.log(jsonData);
  jsonData = [...jsonData];
};
//初始化边
export const addEdge = (edge) => {
  //源节点
  let sNode = findNodeById(edge.sourceRef.id);
  //目标节点
  let tNode = findNodeById(edge.targetRef.id);
  sNode.adj.push(tNode.id);
  sNode.lines.push(edge);
  sNode.se++;
  tNode.te++;
};
/**
 * 邻接表图数据结构转json
 * @param item
 * @param rs
 */
export const handerGtoJ = (item, rs) => {
  rs.push(item);
  if (item.adj.length) {
    if (item.adj.length > 1) {
      //有分支
      item.conditions = [];
      let pls = [];
      // let lastNode;
      item.adj.map((a, index) => {
        let node = findNodeById(a);
        let ls = [];
        handerGtoJ(node, ls);
        pls.push([...ls]);
        // console.log(item);
        //处理条件
        let condition = {};
        if (item?.lines?.length) {
          if(item.lines[index]) {
            let line = item.lines[index];
            if (line?.extensionElements?.values?.length) {
              let eles = line.extensionElements.values;
              eles.map(ele => {
                if (ele.$children?.length) {
                  ele.$children.map(v => {
                    if ('des' == ele.name) {
                      if(v.$body) {
                        condition.des = unescape(v.$body);
                      }
                    }
                    if ('jsonStr' == ele.name) {
                      //需要转义一下
                      if(v.$body) {
                        condition.jsonStr = unescape(v.$body);
                      }
                    }
                  });
                }
              });
            }
          }
        }
        item.conditions.push({
          id: `sid-${uuidv4().toUpperCase()}`,
          name: item.lines[index].name,
          extension: condition,
          el: item.lines[index]?.conditionExpression?.body ? item.lines[index].conditionExpression.body : '',
          children: ls
        });
      });
      //这里的算法:结尾相同的,合并了,然后放上一级
      let f = [];
      for (let pl of pls) {
        pl.reverse();
      }
      let pln = pls[pls.length - 1];
      for (let j = 0; j < pln.length; j++) {
        f.push(true);
        pls.map((ps) => {
          if (ps[j] != pln[j]) {
            f[j] = false;
          }
        });
      }
      f.reverse();
      pln.reverse();
      f.map((fe, index) => {
        if (fe) {
          rs.push(pln[index]);
          item.conditions.map((ele) => {
            ele.children.pop();
          });
        }
      });
    } else {
      //无分支
      item.adj.map((a) => {
        let node = findNodeById(a);
        handerGtoJ(node, rs);
      });
    }
  }
};
/**
 * json结构转邻接表
 */
export const handerJtoG = (node, rs) => {
  rs.push(node);
  if (node?.conditions?.length) {
    node.conditions.map((condition) => {
      if (condition?.children?.length) {
        condition.children.map((item, index) => {
          handerJtoG(item, rs);
        });
      }
    });
  }
};
export const handleChangeNodeData = (node, newData) => {
  if (newData.id == node.id) {
    node = newData;
  }else {
    node.active = false;
  }
  if (node?.conditions?.length) {
    node.conditions.map((condition) => {
      if (condition?.children?.length) {
        condition.children.map((item, index) => {
          handleChangeNodeData(item, newData);
        });
      }
    });
  }
};
/**
 * 修改节点数据
 * @param newData
 */
export const changeNodeData = (newData) => {
  jsonData.map((node, index) => {
    handleChangeNodeData(node, newData);
  });
  jsonData = [...jsonData];
  // console.log(jsonData);
};

export const handleChangeConditionData = (node, newConditionData) => {
  if (node?.conditions?.length) {
    node.conditions.map((item) => {
      if (newConditionData.id == item.id) {
        item = newConditionData;
      }
    });
  }
};
/**
 * 修改条件数据
 * @param newData
 */
export const changeConditionData = (newConditionData) => {
  jsonData.map((node, index) => {
    handleChangeConditionData(node, newConditionData);
  });
  jsonData = [...jsonData];
  // console.log(jsonData);
};
/**
 *json转邻接表
 */
export const JtoG = () => {
  let rs = [];
  jsonData.map((node, index) => {
    handerJtoG(node, rs);
  });
  // console.log(rs);
  return rs;
};
export const handleAdj = (node, nodeN, rs) => {
  // rs.push(node.id);
  // console.log(node, nodeN, rs);
  if (node?.conditions?.length) {
    // console.log(node);
    node.conditions.map((condition) => {
      if (condition?.children?.length) {
        rs.push(condition.children[0].id);
        condition.children.map((item, index) => {
          if (index < condition.children.length - 1) {
            handleAdj(item, condition.children[index + 1], []);
          } else if (index == condition.children.length - 1) {
            //只有一个子节点(网关特殊处理)
            item.adj = [];
            if(item.$type == 'bpmn:ExclusiveGateway') {
              item.conditions.map(()=>{
                item.adj.push(nodeN.id);
              });
            }else {
              item.adj.push(nodeN.id);
            }
          }
        });
      } else {
        rs.push(nodeN.id);
      }
    });
  } else {
    rs.push(nodeN.id);
  }
  node.adj = rs;
};
/**
 * 各个节点修改新的adj
 */
export const resetAdj = () => {
  // console.log(jsonData);
  jsonData.map((item, index) => {
    if (index < jsonData.length - 1) {
      handleAdj(item, jsonData[index + 1], []);
    }
  });
  // console.log(jsonData);
};
/**
 * 点和线转邻接表图数据结构
 * @param edges
 */
export const NtoG = () => {
  //初始化邻接表
  edges.map((edge) => {
    addEdge(edge);
  });
  // console.log(nodes);
};
/**
 * 邻接表转json
 * @param edges
 * @constructor
 */
export const GtoJ = () => {
  let startNode = findStartNode();
  let rs = [];
  handerGtoJ(startNode, rs);
  // console.log(rs);
  jsonData = [...rs];
};
/**
 * 获取邻接表结构
 */
export const handleG = async () => {
  resetAdj();
  nodes = JtoG();

  //获取邻接表结构
  let eles = [...nodes];

  //连线
  await Promise.all(nodes.map((node, index) => {
    // console.log(node);
    node.incoming = [];
    node.outgoing = [];
    //
    // if (node?.conditions?.length > 1) {
    //
    // }
    //创建连线
    node.adj.map(async (a, index) => {
      let newFlow = await getBpmnFromXml('sequenceFlow');
      newFlow.sourceRef = node;
      newFlow.targetRef = findNodeById(a);
      if (node?.conditions?.length > 1) {
        // console.log(node);
        newFlow.conditionExpression.body = node.conditions[index].el;
        newFlow.name = node.conditions[index].name;
        //网关要设置出口连线
        node.outgoing.push(newFlow);
        if (node?.conditions[index]?.extension) {

          if (newFlow?.extensionElements?.values?.length) {
            let eles = newFlow.extensionElements.values;
            eles.map(ele => {
              if (ele.$children?.length) {
                ele.$children.map(v => {
                  if ('des' == ele.name) {
                    if(node?.conditions[index]?.extension?.des) {
                      v.$body = escape(node.conditions[index].extension.des);
                    }
                  }
                  if ('jsonStr' == ele.name) {
                    if(node?.conditions[index]?.extension?.jsonStr) {
                      //需要转义一下
                      v.$body = escape(node.conditions[index].extension.jsonStr);
                    }
                  }
                });
              }
            });
          }
        }
      }
      eles.push(newFlow);
      // return;
    });
    // console.log(eles);
  }));
  // console.log(eles);
  return eles;
};

export const getG = async () => {
  resetAdj();

  //获取邻接表结构
  return JtoG();
};
/**
 * 处理点和线
 * @param ls
 */
export const getNodeLine = (ls) => {
  let dots = [];
  let lines = [];
  ls.map((item) => {
    if ('bpmn:SequenceFlow' == item.$type) {
      lines.push(item);
    } else {
      item.adj = [];
      item.lines = [];
      item.se = 0;
      item.te = 0;
      dots.push(item);
    }
  });
  nodes = dots;
  edges = lines;
  // console.log(nodes);
  // console.log(edges);
};
//初始化
export const init = (data) => {
  getNodeLine(data);
  //点和线转邻接表图数据结构
  NtoG();
  //邻接表转json
  GtoJ();
  //json转邻接表
  // JtoG();
};

细节:
发起节点:什么都不用管,就一个bpmn2:startEvent,钉钉上还做了权限控制,我项目还没实现,我的实现思路是建立部署id和用户id的关联关系,有权限的用户就可以"看到"这个流程,可以发起,没权限的就"看不到"这个流程,不能发起;
审批节点:上面也说了,它是一个xml节点,bpmn2:userTask节点,可以在上面放任何自定义属性和内部标签,后端可以拿到这些属性就可以操作了;
抄送节点:同审批节点,只不过它用的是bpmn2:serviceTask节点;
条件节点:bpmn2:exclusiveGateway 网关节点,可以在上面配置分支条件,bpmn规范里面网关多分支情况,从左侧开始判断,例如 条件分支1,条件分支2,条件分支3,判断顺序1->2->3,必须有条件出口,不然会报异常,条件分支3可以不配置任何条件,就是1和2都不符合的其他情况都会走分支3出口;
结束节点:什么都不用管,就一个bpmn2:endEvent;

动态分配审批人:
所有审批人节点,监听创建事件,实现ExecutionListener接口,动态指定assignee,这样就可以实现各种情况的审批人,'指定成员'、'主管'、'角色'、'连续多级主管'、'发起人自己'。

动态抄送:
serviceTask,bean实现JavaDelegate,在execute方法里面获取配置项,往抄送表(自己添加的表)里面写用户id和流程实例id;

条件分支:
前端直接操作节点,写条件属性;
 

流程发布:

发布之后把部署之后的部署id(deployment_id)存到表单类型表里面,这样每次发起流程都是最新的版本发布;此处结合表单类型管理查阅。

你可能感兴趣的:(activiti,bpmn)