# 协同新建修改和详情页面前后端分离动态接入组件

# 适用版本

V9.0及以上

# 背景

自V9.0开始,PC协同新建、查看页面进行了前后端分离改造,页面已经基于VUE实现了html化,过去基于JSP的扩展模式已经不适用。

项目上期望在前后端分离的协同页面做无侵入的前端动态注入,需要重新按照新规范进行扩展实现,本文档介绍相关扩展方法。

# 适用范围

适用于V9.0以后版本的协同前后端分离页面,请求地址格式如下:

1719381862755.png

# 动态注入js/css资源

原有JSP的extend/js动态脚本链路机制仅仅支持js代码且一旦引入,所有的业务流程都会加载此js代码,若某个客开代码出现报错或者修改了共享的资源,会影响到全部协同页面的正常运行。

前后端分离页面引入了动态加载Javascript、css静态资源,按需可以保证后续持续扩展健康发展,避免出现资源冲突、保护非相干性业务的稳定运行。

1719382123274.png

首先创建资源负载类并继承抽象接口AbstractCollaborationPlugin,通过实现方法loadPluginResource(PluginResourceLocation locationParam);完成js/css的按需加载。

public interface CollaborationPlugin {
    /**
     * 加载资源文件
     * @param locationParam 对应页面参数{@link com.seeyon.ctp.plugins.resources.PluginResourceScope}
     */
    public List<PluginResource> loadPluginResource(PluginResourceLocation locationParam);
}

PluginResourceLocation对象属性介绍:

属性 类型 解释
location PluginResourceScopes 页面标记:COLL_PC_SEND - 协同发送页面;COLL_PC_DEAL - 协同处理页面
isM3 boolean 布尔类型,是否是移动端
affair CtpAffair 待办对象
template CtpTemplate 模版对象
extParams Map<String,Object> 扩展字段,当key为:summary - ColSummary协同对象

PluginResource返回值对象,有两个实现类介绍:

  • JavascriptResource - js资源类型
  • CssResource - css资源类型
属性 类型 解释
path String 资源的类型要求字符串开始固定格式为{serverRoot}例如:{serverRoot}/extend/action/example.js,前端会统一将路径替换为真实服务器路径
getType PluginResourceType 若是js资源则用PluginResourceType,css资源用CssResource

# 编写扩展js接收事件

如何编写js代码,保证上面新增的节点动作按钮被用户点击后,将事件传递到扩展js代码中呢?

按照约束,我们会把点击事件接收到后,通过事件分发出去,事件的code为扩展节点动作的wrapContent#customerAction的值。为了防止变量污染,页面逻辑请统一在立即执行函数表达式中执行。例如example.js

(function () {

 	$.ctp.bind('fileDiff',function(params){
		//do something
    })

  })();

为了兼容原有的老功能,我们将一些重要的参数挂载到window上面:

    const {affair, template} = businessData;
    //协同id
    window.summaryId = affair.objectId;
    //事项的id
    window.affairId = affair.id;
    //节点权限,系统内置的就是标识代码,比如newCol\collaboration,自定义的为名称
    window.nodePolicy = affair.nodePolicy;
    //流程id
    window.processId = affair.processId;
    //cap4有流程表单的数据记录id
    window.fromRecordId = affair.formRecordid;
    //工作流的示例id
    window.wfCaseId = affair.caseId;
    //协同标题
    window.subject = affair.subject;
    //cap4有流程表单的id
    window.formAppId = affair.formAppId;
    //工作流节点id
    window.wfActivityId = affair.activityId
    //工作流流程图模版id
    window.templateWorkflowId = template.workflowId;
    // 0 : 没有做修改, 1:修改了正文   2:修改了附件 3 : 修改了正文和附件
    window.isEditAttOrContent = 0;
    //协同所使用的模版id
    window.templateId = template.id;

# 自定义节点动作按钮

# 目的

帮助客开完成协同页面的二次开发以及标准产品侧对页面实现按钮功能定制的需求。提供三方扩展节点动作的能力,节点动作与标准产品支持的动作能力相当,支持在流程基础设置>节点权限中用户根据个人需求完成动作到任意节点权限的配置;提供能力实现对不同流程动态加载、卸载节点动作的能力。

# 名词解释

节点动作:协同页面上的用户可点击的按钮,比如:查看附件、关联文档、提交、发送等

节点权限:在协同流程中不同节点打开协同页面展示的所使用的权限,比如:发起人新建页面节点权限为新建,处理人根据节点不同可以是:协同、审批、知会等权限

# 开发扩展主要步骤

  • 实现抽象接口AbstractCustomerClassOperationLoader完成节点动作注入到系统,实现节点动作的扩展。
  • 实现抽象接口AbstractCustomizePermissionOperationForPage实现对不同节点权限下对扩展节点动作动态加载、卸载扩展,实现节点动作的运行态控制。
  • 实现接口CollaborationPlugin完成协同页面加载js/css资源加载,实现用户节点动作点击触发后的响应。

# 实现方案介绍

1719382646602.png

# 具体执行步骤

# 扩展节点动作

在xml中注入节点动作扩展的bean,继承抽象类为AbstractCustomerClassOperationLoader,完成抽象方法getClassOperations()的实现逻辑。

class定义 补充内容
AbstractCustomerClassOperationLoader 完成List<CustomerClassOperation> getClassOperations()方法的实现,此方法为扩展的动作集合
CustomerClassOperation 自定义的节点动作对象,需要指定id\name\icon信息,和wrapContent

示例代码:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
	<bean id="fileDiffCustomerClassOperation" class="com.seeyon.ctp.common.permission.bo.CustomerClassOperation">
		<!--唯一标识id-->
		<constructor-arg value="-3939844178343801046"/>
		<!--国际化名称-->
		<constructor-arg value="permission.operation.intelliContract.docComparison"/>
		<!--显示图标样式-->
		<constructor-arg value="syIcon sy-meeting-summary|#1F85EC"/>
		<property name="wrapContent" >
			<value>
		<!--customerAction为节点动作的唯一标识,用于前端监听触发的点击事件,supportPermission为该动作支持绑定的节点权限,多个用逗号分隔-->		{"attitudeAction":"","processAction":"","submitAction":"","attitudeShowName":"","category":"4","customerAction":"fileDiff","supportPermission":"inform"}
			</value>
		</property>
		<property name="operationName" value="permission.operation.intelliContract.docComparison"/>
	</bean>

	<bean id="fileDiffHistoryCustomerClassOperation" class="com.seeyon.ctp.common.permission.bo.CustomerClassOperation">
		<!--唯一标识id-->
		<constructor-arg value="5757082696558594222"/>
		<!--国际化名称-->
		<constructor-arg value="permission.operation.intelliContract.docComparisonHistory"/>
		<!--显示图标样式-->
		<constructor-arg value="syIcon sy-view_log|#1F85EC"/>
		<property name="wrapContent" >
			<value>
				<!--customerAction为节点动作的唯一标识,用于前端监听触发的点击事件,supportPermission为该动作支持绑定的节点权限,多个用逗号分隔-->{"attitudeAction":"","processAction":"","submitAction":"","attitudeShowName":"","category":"4","customerAction":"fileDiffHistory","supportPermission":"newCol,inform"}
			</value>
		</property>
		<property name="operationName" value="permission.operation.intelliContract.docComparisonHistory"/>
	</bean>

	<bean id="colCustomerClassOperationLoader" class="com.seeyon.ctp.common.permission.FileDiffCustomerClassOperationLoaderImpl">
		<property name="customerClassOperations" >
			<list>
				<ref bean="fileDiffCustomerClassOperation"/>
				<ref bean="fileDiffHistoryCustomerClassOperation"/>
			</list>
		</property>
	</bean>
</beans>

FileDiffCustomerClassOperationLoaderImpl

public class FileDiffCustomerClassOperationLoaderImpl extends AbstractCustomerClassOperationLoader {

    private List<CustomerClassOperation>  customerClassOperations;

    @Override
    public List<CustomerClassOperation> getClassOperations() {
        return customerClassOperations;
    }

    public void setCustomerClassOperations(List<CustomerClassOperation> customerClassOperations) {
        this.customerClassOperations = customerClassOperations;
    }
}

解释:

1、创建自定义节点动作扩展类FileDiffCustomerClassOperationLoaderImpl继承抽象接口AbstractCustomerClassOperationLoader,并通过实现方法getClassOperations()暴露需要注入的扩展动作信息,同时将扩展的节点动作放入集合customerClassOperations,添加setter方法用xml方式扩展节点动作具体内容。

2、创建xml的bean定义文件,通过<property name="customerClassOperations" >指定了节点动作数据来源,此处有两个fileDiffCustomerClassOperationfileDiffHistoryCustomerClassOperation

3、创建节点动作的具体定义信息,比如在fileDiffCustomerClassOperation<constructor-arg>构造器方式创建CustomerClassOperation对象,并设置了wrapContent内容,配置key说明见下表格:

标签 作用 解释
<constructor-arg> 构造器 参数依次为id\name\icon样式
<property name="wrapContent" > 扩展属性对象
wrapContent#category category分类 0- 流程调整类
1- 应用类
2- 提交类
3- 意见回复类
4- 其他 (三方业务扩展分类为其他)
wrapContent#customerAction customerAction 改动作的标识code,此code会作为事件名称,用户点击按钮后前端事件触发
wrapContent#supportPermission supportPermission 支持的在那些特殊的节点权限上显示,默认支持处理的节点权限,比如协同、审批等。
特殊的节点权限指:
newCol- 新建页面节点权限
inform- 知会页面节点权限

# 控制节点动作

扩展节点动作是为了1.满足同节点权限下,节点动作在部分人处理时加载显示,部分人又不显示加载。2.用户免配置,根据业务情况去动态的加载节点动作。

首先创建自定义节点权限控制类集成抽象类AbstractCustomizePermissionOperationForPage,注入到spring-bean中,需要实现是三个方法:

/**
 * 用户扩展协同页面的节点动作相关
 * 支持1:扩展协同页面的节点动作动态按需动态添加(前提是:节点动作都存在于系统中)
 * 支持2:对协同页面的节点动作进行按需过滤功能 (前提是:节点动作都存在于系统中)
 */
public abstract class AbstractCustomizePermissionOperationForPage implements ICustomizePermissionOperationCheck {

	/**
     * 控制的节点动作id集合
     *
     * @return
    */
    public abstract List<Long> getPermissionActionIds();

	/**
	 * 按需过滤节点权限上绑定的节点动作
	 *
	 * @param permissionOperationIds 控制的节点动作id集合
	 * @param customizeContext       上下文对象
	 * @param type                   0-基础区域 1 -高级区域 2- 常用区域
	 * @return true 是可以显示, false为不能显示
	 */
	public abstract Map<Long, Boolean> permissionOperationFilter(List<Long> permissionOperationIds, CustomizeContext customizeContext, int type);


	/**
	 * 把节点动作扩展到协同节点权限上去
	 *
	 * @param customizeContext 上下文对象
	 * @param type             0-基础区域 1 -高级区域 2- 常用区域
	 * @return
	 */
	public abstract List<Long> extendPermissionOperation(CustomizeContext customizeContext, int type);


	@Data
	@Builder
	@AllArgsConstructor
	public static class CustomizeContext {
		private CtpAffair affair;
		private ColSummary summary;
		private CtpTemplate template;
		private Boolean isTemplate;

		/**
		 * 节点权限code
		 * <pre>
		 * newCol 新建
		 * collaboration 协同
		 * inform 知会
		 * </pre>
		 */
		private String permissionCode;

		/**
		 * 节点权限的id
		 */
		private Long permissionId;

		/**
		 * 来pc还是移动端
		 *
		 * @see Constants.login_sign
		 */
		private String userAgent;
	}
}

示例代码:

public class ExampleForCustomizeAction extends AbstractCustomizePermissionOperationForPage {
	
	@Inject(beanName = "colCustomerClassOperationLoader")
	private CustomerOperationLoader customerOperationLoader;
    
    /**
	 * 按需过滤节点权限上绑定的节点动作
	 *
	 * @param permissionOperationIds 控制的节点动作id集合
	 * @param customizeContext       上下文对象
	 * @param type                   0-基础区域 1 -高级区域 2- 常用区域
	 * @return true 是可以显示, false为不能显示
	 */
    @Override
	public Map<Long, Boolean> permissionOperationFilter(List<Long> permissionOperationIds, CustomizeContext customizeContext, int type) {
		//特定表单的,基础按钮才实现自己的节点动作
		Long formAppId = customizeContext.getAffair().getFormAppId();
		boolean support = type == 0 && Strings.equals(123456789L, formAppId);
		Map<Long, Boolean> collect = permissionOperationIds.stream().collect(Collectors.toMap(Function.identity(), id -> support, (o1, o2) -> o2));
		return collect;
	}
	
    /**
	 * 把节点动作扩展到协同节点权限上去
	 *
	 * @param customizeContext 上下文对象
	 * @param type             0-基础区域 1 -高级区域 2- 常用区域
	 * @return
	 */
	@Override
	public List<Long> extendPermissionOperation(CustomizeContext customizeContext, int type) {
		//如果是新建节点并且用户是pc端,则把节点动作加载新建页面上
		if (Strings.equals(customizeContext.getPermissionCode(), PermissionCodeEnum.newCol.getKey())
				&& Strings.equals(customizeContext.getUserAgent(), "pc")) {
			return getPermissionActionIds();
		}
		return null;
	}
	
      /**
     * 控制的节点动作id集合,只有自己的节点动作才能控制
     *
     * @return
     */
	@Override
	public List<Long> getPermissionActionIds() {
		List<AbstractCustomerOperation> operations = customerOperationLoader.getOperations();
		if(CollectionUtils.isEmpty(operations)) {
			return Collections.emptyList();
		}
		return operations.stream().map(AbstractCustomerOp=eration::getId).collect(Collectors.toList());
	}
}

以上逻辑即实现了:新建页面新增扩展节点动作,同时按照指定的表单控制是否显示节点动作。

创建人:het
修改人:admin、het