# 一、场景描述

随着业务的增长某些客户需要向表单应用、流程基本信息、流程图注入一些个性化的参数。BPM针对表单应用详情页面、流程设计页面、流程图节点信息组件做了页签扩展能力。

# 1.1 用户查看页面或者组件页签时序图

# 1.2 页签接入

本文前端部分仅提供React示例。在开始之前,推荐先学习 React (opens new window),并正确安装和配置了 Node.js (opens new window) v16 或以上。官方指南假设你已了解关于 HTML、CSS 和 JavaScript 的中级知识,并且已经完全掌握了 React 全家桶的正确开发方式。当然并不代表只能用React扩展页签,你依然可以使用原生代码或者其他框架,你只需关注示例中的参数,并根据实际需要开发即可。

# 1.2.1 创建自定义页面

所有自定义页面需要发布到服务器上。针对不同场景的自定义页签,BPM在加载自定义页面时会根据当前场景传递一些必要的参数,这些参数将拼接在url上。

# 场景1、表单应用详情

加载页面时URL上会传递应用guid(guid)、应用id(appId)、应用来源标识(artifactSource)。

表单应用详情自定义页签

前端示例代码:

import React, { useRef } from 'react';
/**
 * 获取query参数
 * @param variable -string
 * @returns -string | undefined
 */
const getQueryParam = (variable: string) => {
  const query = window.location.search.substring(1);
  const vars = query.split('&');
  for (let i = 0; i < vars.length; i++) {
    const pair = vars[i].split('=');
    if (pair[0] === variable) {
      return pair[1];
    }
  }
  return undefined;
};

const AppDeatilCustomTab = () => {
  //应用guid
  const guid = getQueryParam('guid');
  //应用id
  const appId = getQueryParam('appId');
  //应用来源标识。DEFAULT:标识应用是当前环境设计的;MAVEN或者OSS:表示跨环境推送到本环境的
  const artifactSource = getQueryParam('artifactSource');

  const inputRef = useRef<HTMLInputElement | null>(null);

  //提交
  const onSubmit = () => {
    const value = inputRef.current?.value;
    console.log(guid, appId, artifactSource, value);
  };

  //渲染页面组件
  return (
    <div style={{ margin: 20 }}>
      <div style={{ display: 'flex' }}>
        <div>自定义参数:</div>
        <div>
          <input ref={inputRef} style={{ border: '1px solid #ccc', borderRadius: '4px' }} />
        </div>
      </div>
      <div style={{ marginTop: 10 }}>
        <button
          style={{
            background: '#1890ff',
            padding: '4px 8px',
            border: 'none',
            color: '#fff',
            borderRadius: '4px',
          }}
          onClick={onSubmit}>
          提交
        </button>
      </div>
    </div>
  );
};

export default AppDeatilCustomTab;

# 场景2、流程设计

加载页面时URL上会传递应用guid(guid)、应用id(appId)、模板id(templateId)

流程设计自定义页签

示例代码:

import React, { useRef } from 'react';

/**
 * 获取query参数
 * @param variable -string
 * @returns -string | undefined
 */
const getQueryParam = (variable: string) => {
  const query = window.location.search.substring(1);
  const vars = query.split('&');
  for (let i = 0; i < vars.length; i++) {
    const pair = vars[i].split('=');
    if (pair[0] === variable) {
      return pair[1];
    }
  }
  return undefined;
};

const ProcessDesignCustomTab = () => {
  //应用guid
  const guid = getQueryParam('guid');
  //应用id
  const appId = getQueryParam('appId');
  //模板id
  const templateId = getQueryParam('templateId');

  const inputRef = useRef<HTMLInputElement | null>(null);

  //提交
  const onSubmit = () => {
    const value = inputRef.current?.value;
    console.log(guid, appId, templateId, value);
  };

  return (
    <div style={{ margin: 20 }}>
      <div style={{ display: 'flex' }}>
        <div>自定义参数:</div>
        <div>
          <input ref={inputRef} style={{ border: '1px solid #ccc', borderRadius: '4px' }} />
        </div>
      </div>
      <div style={{ marginTop: 10 }}>
        <button
          style={{
            background: '#1890ff',
            padding: '4px 8px',
            border: 'none',
            color: '#fff',
            borderRadius: '4px',
          }}
          onClick={onSubmit}>
          提交
        </button>
      </div>
    </div>
  );
};

export default ProcessDesignCustomTab;

# 场景3、节点属性

加载页面时URL会传递应用id(appId),模板id(templateId),节点id(activityId)。自定义页签保存的值只能是字符串,保存的值将赋给节点的extProp属性。

节点属性自定义页签

事件消息体data部分:通过type区分事件类型,value传递属性值。

type类型: GET_CUSTOM_VALUE_${appId}_${templateId}_${activityId}:向父窗口询问节点的扩展属性(extProp)值;

SEND_CUSTOM_VALUE_${appId}_${templateId}_${activityId}:父窗口发送扩展属性(extProp)值;

CHANGE_VALUE_${appId}_${templateId}_${activityId}:向父窗口提交扩展属性(extProp)值;

1、如果需要获取当前节点扩展属性(extProp)值,需要先通过postMessage主动发起 GET_CUSTOM_VALUE_${appId}_${templateId}_${activityId}事件,并且监听SEND_CUSTOM_VALUE_${appId}_${templateId}_${activityId}事件,该事件传递的value即为扩展属性值。

2、当自定义属性需要保存到当前节点时通过postMessage发起CHANGE_VALUE_${appId}_${templateId}_${activityId}事件,且通过value传递扩展属性值。

示例代码:

import React, { useEffect } from 'react';
import { Button, Form, Input } from '@seeyon/ui';

/**
 * 获取query参数
 * @param variable -string
 * @returns -string | undefined
 */
const getQueryParam = (variable: string) => {
  const query = window.location.search.substring(1);
  const vars = query.split('&');
  for (let i = 0; i < vars.length; i++) {
    const pair = vars[i].split('=');
    if (pair[0] === variable) {
      return pair[1];
    }
  }
  return undefined;
};
 
const ThirdNodeProps = () => {
  const [form] = Form.useForm();
  //iframe的scr- url的query包含应用id,模板id,节点id
  const appId = getQueryParam('appId');
  const templateId = getQueryParam('templateId');
  const activityId = getQueryParam('activityId');
 
 
  //监听上层传递的值-字符串
  useEffect(() => {
    //主动发消息获取当前页面的值:`GET_CUSTOM_VALUE_${appId}_${templateId}_${activityId}`
    window.parent.postMessage({ type: `GET_CUSTOM_VALUE_${appId}_${templateId}_${activityId}` }, '*');
    //接收父窗口传递下来的值:`SEND_CUSTOM_VALUE_${appId}_${templateId}_${activityId}`
    window.parent.addEventListener(`message`, (data: any) => {
      const { type, value } = data.data || {};
      if (type === `SEND_CUSTOM_VALUE_${appId}_${templateId}_${activityId}`) {
        try {
          const values = JSON.parse(value);
          form.setFieldsValue(values);
        } catch (error) {
          console.log(error);
        }
      }
    });
  }, []);
 
  //调用postMessage,主动提交数据:`CHANGE_VALUE_${appId}_${templateId}_${activityId}`
  const onFinish = (values: any) => {
    window.parent.postMessage(
      {
        type: `CHANGE_VALUE_${appId}_${templateId}_${activityId}`,
        value: JSON.stringify(values),
      },
      '*',
    );
  };
 
  return (
    <div>
      <div>三方注册的节点属性页签</div>
      <Form labelCol={{ span: 5 }} wrapperCol={{ span: 18 }} form={form} onFinish={onFinish}>
        <Form.Item key={'customProperty'} name={'customProperty'} label="自定义属性">
          <Input maxLenth={50}></Input>
        </Form.Item>
      </Form>
      <div>
        <Button type="primary" onClick={form.submit}>
          iframe提交
        </Button>
      </div>
    </div>
  );
};
 
export default ThirdNodeProps;

** 注: 1、前端准备好页面后需要发布到服务器。确保页面能够在浏览器中正常访问。 2、场景1、场景2需要通过客开自行调用后端接口方式保存数据,详情见:1.2.4.1 **

# 1.2.2 注册前端页签

每个页签都需要唯一的key(tabKey),标品提供的页签我们称之为默认页签。表单应用详情和流程设计默认页签对应的key如下,新增页签时需要避免与默认页签重复。其中流程图节点属性的页签顺序不会根据sort更改,流程图节点属性的默认页签只会是最后一个页签。tabSubGroup是默认页签的二级分类,流程设计和流程图节点属性都有二级分类。

# 1、默认页签

默认页签间隔10个单位,与插入的页签共同排序(sort),业务方需自行控制插入页签的顺序。

前端定义如下:

export const defaultTabs: { [key: string]: IBpmCustomTab[] } = {
  //表单应用详情默认页签
  APP_APPROVAL_DETAIL: [
    { tabKey: 'base_info', sort: 10 }, //基本信息
    { tabKey: 'design_publish', sort: 20 }, //设计发布
    { tabKey: 'process_empower', sort: 30 }, //流程权限
    { tabKey: 'base_data', sort: 40 }, //基础数据
    { tabKey: 'query_data', sort: 50 }, //数据查询
    { tabKey: 'process_authority', sort: 60 }, //节点权限
    { tabKey: 'app_log', sort: 70 }, //应用日志
  ],
  //流程设计默认页签(tabSubGroup:TEMPLATE 表示流程模板信息,tabSubGroup:CONFIGURATION 表示流程配置信息)
  PROCESS_DESIGN: [
    { tabKey: 'basic-info', sort: 10, tabSubGroup: 'TEMPLATE' }, //基本信息
    { tabKey: 'design', sort: 20, tabSubGroup: 'TEMPLATE' }, //流程设计
    { tabKey: 'form-permission', sort: 30, tabSubGroup: 'TEMPLATE' }, //操作权限
    { tabKey: 'third-form-param', sort: 40, tabSubGroup: 'TEMPLATE' }, //流程参数
    { tabKey: 'path', sort: 50, tabSubGroup: 'TEMPLATE' }, //流程路径表
    { tabKey: 'advanced-setting', sort: 60, tabSubGroup: 'TEMPLATE' }, //高级设置
    //流程配置
    {
      tabKey: 'MENU_flowPathTable',
      sort: 10,
      tabSubGroup: 'CONFIGURATION',
      tabTitle: '流程路径表',
    }, //流程路径表
    { tabKey: 'MENU_FormAuth', sort: 20, tabSubGroup: 'CONFIGURATION', tabTitle: '表单权限' }, //表单权限
    {
      tabKey: 'MENU_CustomOperation',
      sort: 30,
      tabSubGroup: 'CONFIGURATION',
      tabTitle: '自定义操作',
    }, //自定义操作
  ],
};

# 2、 默认页签子分类(tabSubGroup)

1、流程设计二级分类:

节点类型 备注
TEMPLATE 流程模板信息
CONFIGURATION 流程配置信息

2、流程图节点属性按照节点类型做二级分类:

节点类型 备注
开始节点(BPM_START)
自动开始(BPM_AUTO_START)
定时开始(BPM_TIMER_START)
开始节点
定时节点(BPM_TIMER)
监听节点(BPM_MONITOR)
事件节点(BPM_EVENT)
结束节点(BPM_END)
终止节点(BPM_STOP)
-
条件网关节点(BPM_SPLIT)
汇聚网关节点(BPM_JOIN)
条件网关节点
子流程(BPM_SUBPROCESS)
送转子流程(BPM_SUBPROCESS)
子流程节点
自动节点(BPM_AUTO_ACTIVITY)
消息节点(BPM_MESSAGE_ACTIVITY)
-
普通人工节点(bpmHumanActivity)
规则节点(humanRuleActivity)
脚本找人节点(scriptHumanActivity)
知会节点(bpmInfoActivity)
流程路径表(pathTableActivity)
逐级审批(levelapproval)
动态节点虚线框(bpmDynamicActivity)
动态节点(首节点)(bpmDynamicFirstActivity)
专人送转(transferPersonal)
共享服务节点(FSSCActivity)
人工类型节点

# 3、自定义页签内容描述(tabContent)

前端定义如下:

 {
    type: 'iframe' | 'remoteComponent'; //客开系统仅支持iframe
    url?: string; //iframe对应的源地址, 在原始URL上会拼接特定参数,如应用id,模板id等;
    iframeStyle?: CSSProperties; //iframe样式
    iframeClassName?: string; //iframe类名
    remoteComponent?: any; // 页签内容支持模块联邦(入参请参考模块联邦组件的入参);
  };

# 1.2.3 创建后端接入数据

# 1.2.3.1 后端数据定义

不同注册业务场景tabGroup字段值不一样。

字段名称 字段类型 备注
applicationGuid BIGINT 应用guid
applicationName VARCHAR(128) 应用的name
tabKey VARCHAR(50) 页签的唯一标识(customType是insert时,tabKey一定不能重复)
tabTitle VARCHAR(50) 如果没有传入该字段,如果key是默认页签,展示默认标题,否则展示tabKey
tabContent LONGTEXT 自定义页签渲染需要的数据(参照前端数据类型定义)
customType VARCHAR(50) 自定义类型:insert(追加插入)
tabGroup SMALLINT(6) 页签分组 APP_APPROVAL_DETAIL:审批应用详情页面;PROCESS_DESIGN:流程设计页面;PROCESS_DIAGRAM_NODE:流程图节点属性
tabSubGroup VARCHAR(2000) 页签分组子分类
sort INT 排序
enable TINYINT 是否启用

# 1.2.3.2 注册机制

1、如基于V8平台开发的客开应用,则注册使用平台提供的datainit机制 在 bpm@com.seeyon.bpm.domain.entity.BpmCustomTab.json中注册 需要预置json. 例:

[
  {
    applicationGuid: "-6319207780276483080",
    applicationName: "dynamic4560675929256379644",
    tabGroup: "PROCESS_DIAGRAM_NODE",
    enable: true,
    tabKey: "customTabIframe",
    tabTitle: "自定义节点属性",
    customType: "insert",
    sort: 10,
    tabSubGroup: "bpmHumanActivity,humanRuleActivity,scriptHumanActivity,bpmInfoActivity,pathTableActivity,levelapproval,bpmDynamicActivity,bpmDynamicFirstActivity,transferPersonal,FSSCActivity",
    tabContent: "{\"type\":\"iframe\",\"iframeStyle\":{\"height\":500,\"width\":\"100%\",\"border\":\"none\"},\"url\":\"https://dev.seeyonv8.com/main/child-frame/bpm/node/thirdCustomTab\"}",
  },
]

2、非V8平台的开发,联系技术提供注册方式。

# 1.2.4 运行态使用

# 1.2.4.1 更新模板的扩展信息

接口名称:更新模板的扩展信息 1、请求地址:/bpm/template/update-template-extend 2、参数名称

字段名称 字段类型 备注
id BIGINT id
appBusinessData string 应用业务扩展数据

# 1.2.4.2 节点属性数据获取方式

接口名称:根据流程模板查询节点信息 1、请求地址:bpm/template-node/list 2、参数名称

字段名称 字段类型 备注
templateGuid BIGINT 模板guid和模板id必须填写一个
designState enum 和templateGuid一起使用,默认查运行态启用的模板。枚举项可选值列表:NONE(默认值:空),DESIGN(设计态),RUNTIME(运行态)
templateId BIGINT 有值优先根据模板id查
activityType enum 节点类型。枚举项可选值列表:NONE(默认值:空),BPM_AND_ROUTER(网关节点),BPM_END(结束节点),BPM_AUTO_ACTIVITY(自动节点),BPM_HUMEN_ACTIVITY(人工节点),BPM_START(开始节点),BPM_STOP(终止节点),BPM_SUBPROCESS(子流程节点),BPM_AUTO_START(自动开始节点),BPM_TIMER_START(定时开始节点),BPM_EVENT_ACTIVITY(事件节点),BPM_TIMER_ACTIVITY(定时器节点),BPM_MESSAGE_ACTIVITY(消息节点),BPM_MONITOR_ACTIVITY(监听节点),BPM_DYNAMIC_ACTIVITY(动态节点),BPM_DYNAMIC_FIRST_ACTIVITY(动态首节点),BPM_NESTED_SUB_PROCESS_ACTIVITY(嵌套子流程节点),BPM_INFORM_ACTIVITY(知会节点),BPM_RULE_ACTIVITY(规则节点),BPM_PATH_TABLE(路径表节点),BPM_LEVEL_APPROVAL(逐级审批节点),BPM_SCRIPT_HUMAN(脚本找人节点),BPM_FSSC_HUMAN(共享节点)

3、返回体

字段名称 字段类型 备注
extProp string 节点中三方扩展信息

# 二、业务扩展

# 1、 机构级页签注册

注册数据中applicationGuid、applicationName为null时,则会注册成为机构下全局应用的注册数据

编撰人:wensl、wxju、cgjun