# 1、前言

本文前端部分仅提供React示例且使用TypeScript做类型定义。在开始之前,推荐先学习 React (opens new window)TypeScript (opens new window),并正确安装和配置了 Node.js (opens new window) v18 或以上。官方指南假设你已了解关于 HTML、CSS 和 JavaScript 的中级知识,并且已经完全掌握了 React 全家桶的正确开发方式。

客开接入扩展指南:https://www.yuque.com/teamdocs/v8-client-kb/zngrgup351qls4gv#%20

# 1.1、前端客开扩展前置条件:

为了保证页签之间的事件互不干扰,使用bpmEventId做事件隔离。某些扩展支持postMessage通信,某些扩展支持eventBus通信。

# 1.1.1、通过事件获取bpmEventId

//通过该事件可以得到bpmEventId
window.postMessage({ type: 'SUB_PAGE_MESSAGE', data: msgData }, '*');

通过事件获取bpmEventId

# 1.1.2、eventBus 事件

发起、监听、销毁方法从@seeyon/global中获取,window上已挂载该组件(window.SeeyonGlobal)。

使用方式可以从组件包引入:

import { eventEmit, eventOn, eventOff } from '@seeyon/global';

也可以使用全局方法:

const fn = (params: any) => {
	console.log(params);
};
SeeyonGlobal.eventEmit(`xxx_${bpmEventId}`, fn);
SeeyonGlobal.eventOn(`xxx_${bpmEventId}`, fn);
SeeyonGlobal.eventOff(`xxx_${bpmEventId}`, fn);

# 2、可扩展组件

# 2.1、意见列表

事件需要保证多页签下不同的URL页面互相不干扰,事件类型带上bpmEventId,BPM初始化会抛出事件,参考前端客开扩展前置条件

# 2.1.1、列表节点栏点击事件扩展

增加点击事件(postMessage通信)

# 2.1.1.1、IOpinionInfo:意见信息,部分流程信息

export interface IOpinionNodeInfo {
  activityStateEnum?: string; //节点状态
  batchId?: string; //批号
  dynamicChildNode?: string[]; //动态节点内部子节点拆分出来的节点
  hasMore?: boolean; //弃用字段-忽略
  hasNotice?: boolean; //弃用字段-忽略
  nodeId: string; //节点id
  nodeName: string; //节点名称
  nodeType?: string; //节点类型
  opinionDtoList: IOpinionDto[]; //意见列表
  sourceNodeId?: string; //模板源节点id
  noticeUsers: INoticeUser[];
  processingUsers: IProcessingUser[];
  noticeOpinionDtoList?: IOpinionDto[]; //知会意见
  activityFirstTime: number;
  hasOpinion: boolean;
  hasProcessing: boolean;
  isDynamic: boolean;
  reRendered?: boolean; //三方系统自定义节点内意见内容和排序规则
}

# 2.1.1.2、调用示例

postMessage事件定义: BPM_OPINION_NODE_CLICK_${bpmEventId}:意见列表节点栏点击时,标品发起该事件并传递意见信息及部分流程信息;

REWRITE_BPM_OPINION_NODE_CLICK_${bpmEventId}:标品监听该事件,标品需要重写意见列表节点栏点击事件时主动发起该事件。

//处理message事件
const handleMessageEvent = (event: Record<string, any>) => {
	//重写意见列表节点名称点击事件
	if (event.data?.type === `BPM_OPINION_NODE_CLICK_${bpmEventId}`) {
		//发起事件告知标品需要复写意见列表节点栏点击事件
		window.postMessage({type: `REWRITE_BPM_OPINION_NODE_CLICK_${bpmEventId}`}, '*');
		//获取意见信息及部分流程信息
		console.log(event.data?.data)
	}
};

useEffect(() => {
	//消息监听
	window.addEventListener('message', handleMessageEvent);
	return () => {
		window.removeEventListener('message', handleMessageEvent);
	};
}, []);

# 2.1.2、PC端支持自定义更新意见内容

eventBus 事件定义: OPINION_CUSTOM_OPERATION_CONTROL_ASK_${bpmEventId}:询问是否要自定义控制编辑、删除按钮;

OPINION_CUSTOM_OPERATION_CONTROL_${bpmEventId}:告知标品要展示编辑、删除按钮、以及编辑删除调用的服务;

# 2.1.2.1 标品代码示例

// 处理自定义操作
const handleCustomOp = (
	data: {
		//是否展示编辑按钮
		customEdit?: boolean;
		//是否展示删除按钮
		customDelete?: boolean;
		//编辑、删除按钮提交到哪个服务
		serviceName?: string;
	}
) => {
	console.log(data);
};

  useEffect(() => {
    if (!bpmEventId) {
      return;
    }
    // 监听自定义操作
    eventOn(`OPINION_CUSTOM_OPERATION_CONTROL_${bpmEventId}`, handleCustomOp);
    // 询问是否要自定义编辑、删除
    eventEmit(`OPINION_CUSTOM_OPERATION_CONTROL_ASK_${bpmEventId}`);
    return () => eventOff(`OPINION_CUSTOM_OPERATION_CONTROL_${bpmEventId}`, handleCustomOp);
  }, [bpmEventId]);

# 2.1.2.1 客开调用示例

// 自定义操作
const controlCustomOp = () => {
	//发送自定义操作事件
	eventEmit(
		`OPINION_CUSTOM_OPERATION_CONTROL_${bpmEventId}`,
		{ customEdit: true, customDelete: true, serviceName: 'appName' }
	);
};

  useEffect(() => {
    if (!bpmEventId) {
      return;
    }
    // 监听意见列表自定义操作询问事件
    eventOn(`OPINION_CUSTOM_OPERATION_CONTROL_ASK_${bpmEventId}`, controlCustomOp);
    return () => eventOff(`OPINION_CUSTOM_OPERATION_CONTROL_ASK_${bpmEventId}`, controlCustomOp);
  }, [bpmEventId]);

# 2.1.3 阻止弹出知会人员、处理中人员已读/未读弹框

postMessage事件定义: BPM_OPINION_LIST_USERS_LABEL_INITIALIZED_${bpmEventId}:意见列表处理中人员或者知会人展示组件初始化完成事件;

BPM_OPINION_CANCEL_SHOW_ALL_USERS_${bpmEventId}:处理中人员、知会是按节点合并展示时,点击人员名称文案是否需要弹出完整人员列表弹窗

# 2.1.3.1 标品代码示例

  //处理message事件 -支持客开能力扩展
  const handleMessageEvent = (event: Record<string, any>) => {
    // 当处理中人员、知会是按节点合并展示时,点击人员名称文案是否需要弹出完整人员列表弹窗
    if (get(event, 'data.type') === `BPM_OPINION_CANCEL_SHOW_ALL_USERS_${bpmEventId}`) {
		console.log(data.data);
	}
  };

  useEffect(() => {
    window.addEventListener('message', handleMessageEvent);
    return () => {
      window.removeEventListener('message', handleMessageEvent);
    };
  }, [bpmEventId]);
  
  useEffect(() => {
    if (!bpmEventId) {
      return;
    }
    // 广播意见列表处理中人员或者知会人初始化完成事件
    window.postMessage(
      {
        type: `BPM_OPINION_LIST_USERS_LABEL_INITIALIZED_${bpmEventId}`,
        data: {
          opinionInfo, //当前意见信息
          serviceName, //服务名称
          userType, //'noticeUser':知会人; 'processingUser':处理中人员
          caseId,
        },
      },
      '*',
    );
  }, [bpmEventId]);

# 2.1.3.2 客开调用示例

  //处理message事件
  const handleMessageEvent = (event: Record<string, any>) => {
    // 取消知会人、处理中人员已读/未读弹框
    if (get(event, 'data.type') === `BPM_OPINION_LIST_USERS_LABEL_INITIALIZED_${bpmEventId}`) {
			console.log(data.data);
			window.postMessage({
				type: `BPM_OPINION_CANCEL_SHOW_ALL_USERS_${bpmEventId}`,
				data: {noticeUsers: true, processingUser: true}
			})
	 }
  };

  useEffect(() => {
    window.addEventListener('message', handleMessageEvent);
    return () => {
      window.removeEventListener('message', handleMessageEvent);
    };
  }, [bpmEventId]);

# 2.2、意见输入框

# 2.2.1、@功能扩展(包含意见列表回复):

1、点击意见输入框的@按钮的时候,触发postMessage事件,BPM侧向客户方询问是否要使用自定义的@功能。 2、如果需要自定义@功能则通知BPM侧,需要自定义并且返回相应的参数。 3、BPM将客开的回调参数传给意见输入框组件方。

事件定义:

BPM_OPINION_CUSTOM_@_${bpmEventId}:标品询问是否需要重写@人员;

REWRITE_BPM_OPINION_CUSTOM_@_${bpmEventId}:客开回答:重写@人员;

BASE_OPINION_CUSTOM_@_CLICK_${bpmEventId}:@图标点击发起该事件;

BASE_OPINION_CUSTOM_@_SUBMIT_${bpmEventId}:@客开弹窗人员提交事件。

// 是否需要重写@人员 事件key
const rewriteCustomAtKey = `BPM_OPINION_CUSTOM_@_${bpmEventId}`
// 回答:重写@人员 事件key
const isCustomAtKey = `REWRITE_BPM_OPINION_CUSTOM_@_${bpmEventId}`
// @图标点击 事件key
const atCustomClickKey = `BASE_OPINION_CUSTOM_@_CLICK_${bpmEventId}`;
// @客开弹窗人员提交 事件key
const atCustomSubmitKey = `BASE_OPINION_CUSTOM_@_SUBMIT_${bpmEventId}`;

# 2.2.1.1 代码示例

标品BPM示例代码(BPM向客开询问是否重写@功能,BPM接收客开返回的参数):


  // 重写@需要的回调
  const [isCustomAt, setIsCustomAt] = useState<boolean>();
  
  // 处理message事件
  const handleMessageEvent = (event: Record<string, any>) => {
    // 重写@功能
    if (get(event, 'data.type') === isCustomAtKey) {
      setIsCustomAt(true);
    }
  };
 
  useEffect(() => {
    window.addEventListener('message', handleMessageEvent);
    return () => {
      window.removeEventListener('message', handleMessageEvent);
    };
  }, []);
 
// Bpm调用Opinion组件
<Opinion
  id={bpmEventId} // 输入框唯一标识ID,bpm可用bpmEventId
  extraConfig={{
    mentionConfig: {
      custom: isCustomAt, // 是否是自定义
    }
  }}
/>
 
// bpm主动询问是否需要客开
window.postMessage({
  type: rewriteCustomAtKey,
  data: { bpmEventId },
});

客开@人员组件示例(例如@流程外的人员张三):

// 流程内部@人员列表接口的入参
interface Params {
  appName: string; // 应用服务名
  extendImplFlag: string; // 应用服务的扩展常量
  objectId: string; // 详情数据id,bpm即caseId
  userName: string; // @人员查询值(可模糊搜索)
}
 
// @人员的数据格式
interface Atuser {
  id: string; // 人员id
  name: string; // 人员名称
}
 
----------------------------------------  客开@人员列表 ----------------------------------------
// atCustomClickKey的data入参
const [submitFnInfo, setSubmitFnInfo] = useState<{atCustomSubmitKey: string, params
:Params}>({});

// 客开发送通知给bpm
window.postMessage({
  type: rewriteCustomAtKey,
  data: {
    atCustomClickKey: atCustomClickKey
  },
});
 
// 监听postMessage事件
useEffect(() => {
    const handleMessageEvent = (event: Record<string, any>) => {
        // 接受是否需要重写@
        if (get(event, 'data.type') === rewriteCustomAtKey) {
            window.postMessage({
                type: isCustomAtKey,
                data: true,
            });
         }
        // 监听@按钮的点击事件
        if (get(event, 'data.type') === atCustomClickKey) {
            // 打开客开弹窗事件
            setSubmitFnInfo(event.data.data);
        }
    };
    window.addEventListener('message', handleMessageEvent);
    return () => {
      window.removeEventListener('message', handleMessageEvent);
    };
}, []);
 
// 客开@弹窗选择数据的确定提交
const handleSubmitByCustom =() => {
    window.postMessage({
        type: submitFnInfo.atCustomSubmitKey,
        data: [{ id:123, name:"张三" }],
    });
}
 
<Modal>
    <Button onClick={handleSubmitByCustom}>提交选择的人员</Button>
</Modal>

标品平台输入框组件代码示例:

// 监听客开@弹窗选择数据的确定提交
  const handleAtCustomSubmitEvent = (event: Record<string, any>) => {
    if (get(event, 'data.type') === atCustomSubmitKey) {
        const data = get(event, 'data.data');
        // 输入框组件拿到@人员列表信息
        doSomething(data);
    }
  };

  useEffect(() => {
    window.addEventListener('message', handleAtCustomSubmitEvent);
    return () => {
      window.removeEventListener('message', handleAtCustomSubmitEvent);
    };
  }, []);

// atClick触发postMessage,其中data入参两个(第一个是click的type,第二个atCustomSubmitKey作为客开@弹窗选择数据的确定提交所触发的postMessage的type key)
function atClick() {
    window.postMessage({
        type: atCustomClickKey,
        data: {
            params: {
                appName: "",
                extendImplFlag: "",
                objectId:  "",
                userName:  "",
            },
            atCustomSubmitKey: atCustomSubmitKey,
        },
    });
}

<div onClick={atClick}>@图标</div>

# 2.2.1.2、注意点:

前端: 1、handleSubmit参数的人员数据格式必须是 Atuser[] 格式 2、Atuser里面id为userId,name为username 3、@所有人时,含义是流程内的所有人,并且,@所有人对应的数据是 {id: 'all', name: '所有人'}(name可自定义,id不能改)

编撰人:wensl、wxju、cgjun