本文面向集成方,介绍 Flow Frontend 提供的全部扩展点与插件机制,指导如何替换框架默认的 UI 组件、行为逻辑和数据流。
Flow Frontend 框架通过 8 类插件机制 实现了高度的可定制性。集成方可以在不修改框架源码的情况下,完成以下定制:
| 定制目标 | 使用机制 | 粒度 |
|---|---|---|
| 替换审批页面的 Header/Body/Footer/Sider | ViewBindPlugin | 页面级布局 |
| 替换审批动作按钮(通过/驳回/保存/委派...) | ViewBindPlugin | 按钮级 |
| 替换人员选择/签名/节点选择等子视图 | ViewBindPlugin | 弹窗/子视图级 |
| 替换设计器的脚本编辑器、节点标题等视图 | ViewBindPlugin | 编辑器视图级 |
| 替换后端指定的动态表单视图 | ViewBindPlugin | 表单级 |
| 监听和触发前端事件 | EventBus | 事件级 |
| 自定义审批动作的执行逻辑 | EventBus + CustomStyleButton | 动作级 |
| 向审批框架注入自定义表单数据 | FormActionContext | 数据级 |
| 替换后端 API 调用 | FlowApprovalApi 接口 | 接口级 |
| 替换 HTTP 层的消息提示实现 | HttpClient.setMessageBox | 通知级 |
| 注册自定义表单字段类型 | registerFormItems | 字段级 |
| 自定义审批动作按钮样式 | FlowActionDisplay(后端配置) | 样式级 |
所有机制遵循 "注册即覆盖,不注册用默认" 的原则,对现有行为零影响。
ViewBindPlugin 是框架最核心的定制入口,几乎所有 UI 组件都可以通过它被替换。
// 注册自定义组件
ViewBindPlugin.getInstance().register(key: string, component: React.ComponentType<any>): void
// 获取已注册的组件
ViewBindPlugin.getInstance().get(key: string): React.ComponentType<any> | undefined框架在每个关键组件渲染前,都会先通过 ViewBindPlugin.get(key) 查找是否有已注册的自定义组件。若找到则直接渲染自定义组件,否则渲染框架默认实现。
// 框架内部逻辑示例(Header 组件)
const HeaderView = ViewBindPlugin.getInstance().get(APPROVAL_HEADER_VIEW_KEY);
if (HeaderView) {
return <HeaderView />; // 渲染自定义组件
}
// 渲染默认 Header| Key 常量 | Key 值 | 替换对象 |
|---|---|---|
APPROVAL_HEADER_VIEW_KEY |
"APPROVAL_HEADER_VIEW_KEY" |
审批页顶部标题栏 + 操作按钮区 |
APPROVAL_BODY_VIEW_KEY |
"APPROVAL_BODY_VIEW_KEY" |
审批页主内容区(表单 + 审批历史) |
APPROVAL_FOOTER_VIEW_KEY |
"APPROVAL_FOOTER_VIEW_KEY" |
审批页底部操作区 |
APPROVAL_BODY_SIDER_VIEW_KEY |
"APPROVAL_BODY_SIDER_VIEW_KEY" |
审批页侧边栏(仅 PC 端,流程记录面板) |
// 替换审批页 Header
import { ViewBindPlugin } from "@coding-flow/flow-core";
import { APPROVAL_HEADER_VIEW_KEY } from "@coding-flow/flow-pc-approval";
ViewBindPlugin.getInstance().register(APPROVAL_HEADER_VIEW_KEY, MyCustomHeader);| Key 常量 | Key 值 | 对应的默认动作 |
|---|---|---|
APPROVAL_ACTION_PASS_KEY |
"APPROVAL_ACTION_PASS_KEY" |
审批通过 |
APPROVAL_ACTION_REJECT_KEY |
"APPROVAL_ACTION_REJECT_KEY" |
审批驳回 |
APPROVAL_ACTION_SAVE_KEY |
"APPROVAL_ACTION_SAVE_KEY" |
保存 |
APPROVAL_ACTION_DELEGATE_KEY |
"APPROVAL_ACTION_DELEGATE_KEY" |
委托 |
APPROVAL_ACTION_ADD_AUDIT_KEY |
"APPROVAL_ACTION_ADD_AUDIT_KEY" |
加签 |
APPROVAL_ACTION_TRANSFER_KEY |
"APPROVAL_ACTION_TRANSFER_KEY" |
转交 |
APPROVAL_ACTION_RETURN_KEY |
"APPROVAL_ACTION_RETURN_KEY" |
退回 |
APPROVAL_ACTION_REVOKE_KEY |
"APPROVAL_ACTION_REVOKE_KEY" |
撤回 |
APPROVAL_ACTION_URGE_KEY |
"APPROVAL_ACTION_URGE_KEY" |
催办 |
APPROVAL_ACTION_CUSTOM_KEY |
"APPROVAL_ACTION_CUSTOM_KEY" |
自定义动作 |
APPROVAL_ACTION_CLOSE_KEY |
"APPROVAL_ACTION_CLOSE_KEY" |
关闭 |
重要提示:当替换审批动作按钮时,自定义组件会完全接管该按钮的渲染和交互逻辑。建议参考默认实现中的 action 执行流程(调用
actionPresenter.action()后处理res.success)。
| Key 常量 | Key 值 | 用途 |
|---|---|---|
AddAuditViewPluginKey |
"AddAuditViewPlugin" |
加签人员选择 |
DelegateViewPluginKey |
"DelegateViewPlugin" |
委托人员选择 |
ReturnViewPluginKey |
"ReturnViewPlugin" |
退回节点选择 |
TransferViewPluginKey |
"TransferViewPlugin" |
转交人员选择 |
SignKeyViewPluginKey |
"SignKeyViewPlugin" |
审批签名输入 |
OperatorSelectViewPluginKey |
"OperatorSelectViewPlugin" |
操作人选择 |
ManualViewPluginKey |
"ManualViewPlugin" |
手动节点走向选择 |
这些子视图组件有标准的 Props 接口契约,见下文"组件 Props 接口契约"章节。
| Key 常量 | Key 值 | 用途 |
|---|---|---|
ConditionViewPluginKey |
"ConditionViewPlugin" |
条件节点的脚本编辑器 |
RouterViewPluginKey |
"RouterViewPlugin" |
路由节点的脚本编辑器 |
SubProcessViewPluginKey |
"SubProcessViewPlugin" |
子流程节点的脚本编辑器 |
TriggerViewPluginKey |
"TriggerViewPlugin" |
触发器脚本编辑器 |
ErrorTriggerViewPluginKey |
"ErrorTriggerViewPlugin" |
异常触发器视图 |
NodeTitleViewPluginKey |
"NodeTitleViewPlugin" |
节点标题视图 |
OperatorCreateViewPluginKey |
"OperatorCreateViewPlugin" |
操作人创建视图 |
OperatorLoadViewPluginKey |
"OperatorLoadViewPlugin" |
操作人加载视图 |
ActionCustomViewPluginKey |
"ActionCustomViewPlugin" |
自定义动作脚本编辑 |
ActionRejectViewPluginKey |
"ActionRejectViewPlugin" |
拒绝动作脚本编辑 |
IMPORT_FORM_VIEW_KEY |
"ImportFormViewPlugin" |
导入流程选择视图 |
后端 FlowContent.view 字段指定了一个视图 key,框架通过这个 key 从 ViewBindPlugin 中获取对应的表单组件。这使得不同流程可以使用完全不同的表单 UI。
// 为特定流程注册自定义表单
ViewBindPlugin.getInstance().register('my-custom-form', MyCustomFormView);
// 如果后端返回的 FlowContent.view === 'my-custom-form'
// 框架将自动渲染 MyCustomFormView替换 ViewBindPlugin 中的组件时,需遵循对应的 Props 接口约定:
// 审批子视图通用 Props
interface AddAuditViewPlugin {
onChange?: (value: string | string[]) => void;
value?: string | string[];
action?: React.Ref<ApprovalViewPluginAction>;
}
// ApprovalViewPluginAction — 暴露验证方法给框架调用
interface ApprovalViewPluginAction {
onValidate: () => Promise<boolean>;
}
// 设计器视图通用 Props
interface ConditionViewPlugin {
type: ScriptType;
script: string;
variables: GroovyVariableMapping[];
onChange: (script: string) => void;
action?: React.Ref<DesignViewPluginAction>;
}
// DesignViewPluginAction — 暴露验证方法给框架调用
interface DesignViewPluginAction {
onValidate: (script: string) => Promise<boolean>;
}
// 导入表单视图 Props
interface ImportFormViewPlugin {
open: boolean;
onSelect: (form: FlowForm) => void;
onCancel: () => void;
}实现自定义组件时请注意:如果你的组件需要在点击"保存"或"提交"前由框架做校验,请将验证逻辑通过
actionRef 暴露出去。
EventBus 提供发布-订阅模式的事件通信,允许集成方监听框架内部事件,也可用于自定义动作的前端事件触发。
// 注册事件监听
EventBus.getInstance().on(eventName: string, callback: (...args: any[]) => void): void
// 触发事件
EventBus.getInstance().emit(eventName: string, ...args: any[]): void
// 取消监听:移除特定回调;不传 callback 则移除事件的所有监听
EventBus.getInstance().off(eventName: string, callback?: Function): void场景一:自定义动作的前端事件触发
后端可通过 FlowAction 对象的 triggerFrontEvent 字段配置一个事件名。当该动作按钮被点击时,框架会自动 emit 该事件,无需编写任何前端代码。
// App 层监听该事件
EventBus.getInstance().on('compile', () => {
alert('触发了编译事件');
});场景二:在自定义 ViewBindPlugin 组件中与其他模块通信
// 自定义 Header 组件中发送事件
const MyHeader = () => {
const handleClick = () => {
EventBus.getInstance().emit('header:refresh');
};
return <button onClick={handleClick}>刷新</button>;
};注意:事件回调中的异常会被框架捕获并 console.error,不会中断后续回调的执行。
当你在审批页面中使用了自定义的表单视图(通过 ViewBindPlugin 注册),需要将自己表单的数据注入到框架的提交流程中时,使用 FormActionContext。
interface IFormAction {
save(): any; // 返回表单数据
key(): string; // 唯一标识,用于去重
validate(): Promise<any>; // 表单校验
}
class FormActionContext {
addAction(submit: IFormAction): void;
removeAction(key: string): void;
save(): any; // 合并所有已注册表单的数据
validate(): Promise<any>; // 串行校验所有已注册表单
}在自定义 ViewBindPlugin 组件中获取 FormActionContext 并注册:
import { useApprovalContext } from "@coding-flow/flow-approval-presenter";
const MyCustomFormView = () => {
const context = useApprovalContext();
const formActionContext = context.getPresenter().getFormActionContext();
useEffect(() => {
const formActionKey = 'my-custom-form';
formActionContext.addAction({
key: () => formActionKey,
save: () => myFormRef.current?.getFieldsValue(),
validate: () => myFormRef.current?.validateFields(),
});
return () => formActionContext.removeAction(formActionKey);
}, []);
return <MyForm ref={myFormRef} />;
};关键点:框架在提交审批时会调用
FormActionContext.save()和FormActionContext.validate(),确保所有已注册表单的数据被合并提交。组件卸载时务必调用removeAction()清理。
通过实现 FlowApprovalApi 接口,可以完全替换框架的 API 调用逻辑,适配自有后端。
interface FlowApprovalApi {
create(body: Record<string, any>, mockKey: string): Promise<number>;
processNodes(body: Record<string, any>, mockKey: string): Promise<ProcessNode[]>;
action(body: Record<string, any>, mockKey: string): Promise<any>;
revoke(id: any, mockKey: string): Promise<any>;
urge(id: any, mockKey: string): Promise<any>;
}在创建审批上下文时注入自定义 API 实现:
import { createApprovalContext } from "@coding-flow/flow-approval-presenter";
const myApi: FlowApprovalApi = {
create: async (body, mockKey) => { /* 自定义实现 */ },
processNodes: async (body, mockKey) => { /* 自定义实现 */ },
action: async (body, mockKey) => { /* 自定义实现 */ },
revoke: async (id, mockKey) => { /* 自定义实现 */ },
urge: async (id, mockKey) => { /* 自定义实现 */ },
};
const context = createApprovalContext(props, myApi);框架的 HTTP 拦截器在 token 失效、无权限等场景会自动触发提示。可通过替换 MessageBox 实现自定义通知方式。
interface MessageBox {
success: (msg: string) => void;
error: (msg: string) => void;
}import { HttpClient } from "@coding-flow/flow-core";
const myMessageBox: MessageBox = {
success: (msg) => Notify.success(msg),
error: (msg) => Notify.error(msg),
};
const httpClient = new HttpClient(30000, myMessageBox, '/api');
// 或动态替换
httpClient.setMessageBox(myMessageBox);提示:message 文案本身可以通过 FlowMessageRegistry 消息提示定制 进一步自定义。
审批动作按钮的样式(颜色、圆角、图标等)由 FlowActionDisplay 对象驱动,该对象由后端在 FlowAction.display 字段中返回。集成员通常无需前端代码修改,通过后端配置即可改变按钮外观。
| 字段 | 示例值 | 效果 |
|---|---|---|
backgroundColor |
"1890ff" |
按钮背景色(自动添加 # 前缀) |
borderColor |
"ff4d4f" |
边框颜色 |
borderRadius |
"8" |
圆角大小(px) |
borderSize |
"2" |
边框宽度(px) |
icon |
图标名称 | 按钮图标(通过 @coding-flow/flow-icons 渲染) |
title |
"自定义文本" |
按钮标题 |
后端 FlowAction.display.style (JSON string)
→ CustomStyleButton 组件解析为 DisplayStyle 对象
→ 渲染带样式的 antd Button
所有审批动作按钮(通过/拒绝/保存/委派/加签/转交/退回/自定义)内部均使用 CustomStyleButton 渲染外观。
Flow Frontend 使用 @coding-form/form-engine 作为表单引擎,通过 registerFormItems 可以注册自定义的表单字段渲染组件。
registerFormItems(Form: any, items: FormItemType[]): void;
interface FormItemType {
type: string; // 字段类型标识(如 'string', 'integer', 'date')
componentType: React.ComponentType<any>; // 对应的渲染组件
}import { registerFormItems } from "@coding-form/form-engine";
import { Form } from "antd";
// 注册自定义字段类型
registerFormItems(Form, [
{ type: 'string', componentType: MyStringInput },
{ type: 'integer', componentType: MyIntegerInput },
{ type: 'date', componentType: MyDatePicker },
{ type: 'boolean', componentType: MySwitch },
{ type: 'custom-selector', componentType: MyCustomSelector },
]);表单字段类型由流程设计器创建字段时选择,具体类型在
@coding-flow/flow-types的FormTypeContext中定义。
集成方通过审批入口组件将审批能力嵌入到自己的页面中。框架提供了两套入口:
import { ApprovalPanelDrawer } from "@coding-flow/flow-pc-approval";
<ApprovalPanelDrawer
workflowCode={workflowCode} // 流程设计编码
open={visible} // 是否显示
recordId={recordId} // 流程记录 ID(有值=审批,无值=发起)
review={isReview} // 是否预览模式(只看不操作)
onClose={() => setVisible(false)}
/>import { ApprovalPanel } from "@coding-flow/flow-mobile-approval";
<ApprovalPanel
workflowCode={workflowCode} // 流程设计编码
recordId={recordId} // 流程记录 ID(有值=审批,无值=发起)
review={isReview} // 是否预览模式
onClose={() => navigate(-1)}
initData={initialFormData} // 可选的初始化表单数据
/>以下是一个 App 初始化阶段的完整定制示例:
// main.tsx — App 入口
import { ViewBindPlugin, EventBus, FlowMessageRegistry, FlowMessageKey } from "@coding-flow/flow-core";
import { registerFormItems } from "@coding-form/form-engine";
import {
APPROVAL_HEADER_VIEW_KEY,
APPROVAL_BODY_VIEW_KEY,
APPROVAL_ACTION_PASS_KEY,
APPROVAL_ACTION_REJECT_KEY,
APPROVAL_ACTION_SAVE_KEY,
} from "@coding-flow/flow-pc-approval";
import { IMPORT_FORM_VIEW_KEY } from "@coding-flow/flow-design";
const plugin = ViewBindPlugin.getInstance();
const msgRegistry = FlowMessageRegistry.getInstance();
// ── 1. 替换布局组件 ──
plugin.register(APPROVAL_HEADER_VIEW_KEY, MyCustomHeader);
plugin.register(APPROVAL_BODY_VIEW_KEY, MyCustomBody);
// ── 2. 替换动作按钮 ──
plugin.register(APPROVAL_ACTION_PASS_KEY, MyPassButton);
plugin.register(APPROVAL_ACTION_REJECT_KEY, MyRejectButton);
// ── 3. 替换子视图 ──
plugin.register('SignKeyViewPlugin', MySignatureView);
plugin.register('AddAuditViewPlugin', MyUserSelector);
// ── 4. 替换设计器视图 ──
plugin.register(IMPORT_FORM_VIEW_KEY, MyImportFormDialog);
plugin.register('ConditionViewPlugin', MyCodeEditor);
// ── 5. 替换动态表单视图 ──
plugin.register('my-custom-form', MyCustomForm);
plugin.register('default', MyDefaultForm);
// ── 6. 定制消息提示 ──
msgRegistry.registerAll({
[FlowMessageKey.APPROVAL_PASS]: '审批通过',
[FlowMessageKey.APPROVAL_SAVE]: (data) =>
data.isStartNode ? '流程已发起' : '已保存',
});
// ── 7. 监听事件 ──
EventBus.getInstance().on('compile', () => { /* ... */ });
EventBus.getInstance().on('notify:refresh', () => { /* ... */ });
// ── 8. 注册表单字段 ──
registerFormItems(Form, [
{ type: 'string', componentType: MyStringInput },
{ type: 'custom-field', componentType: MyCustomField },
]);当同一能力存在多个定制机制时,优先级规则如下:
审批动作按钮渲染优先级:
ViewBindPlugin(完全替换)
→ 无注册 → ActionFactory 默认组件
→ 默认组件内部 → CustomStyleButton(根据 display.style 渲染样式)
自定义动作分发优先级(CustomAction 内部):
ViewBindPlugin(完全替换)
→ triggerFrontEvent(EventBus 事件触发)
→ triggerType(转发为其他动作类型)
→ 默认
审批布局渲染优先级:
ViewBindPlugin(完全替换)
→ 无注册 → 默认 Header/Body/Footer/Sider
审批表单视图优先级:
ViewBindPlugin.get(state.flow?.view)(后端指定的 key)
→ 无注册 → FlowFormView(@coding-flow/flow-pc-form / flow-mobile-form)
消息提示优先级:
FlowMessageRegistry.register()(完全覆盖)
→ 未覆盖 → 内置默认值
- 注册时机 — ViewBindPlugin 和 EventBus 的注册应在组件首次渲染前完成,建议在
main.tsx或 App 组件顶层useEffect中执行 - 组件卸载清理 — 通过 FormActionContext 注册的表单数据,务必在组件卸载时调用
removeAction()清理;EventBus 的on()也应在useEffect的 cleanup 中调用off() - 完全替换 — ViewBindPlugin 注册的组件会完全接管渲染,不会叠加默认组件的内容。如需在默认内容基础上增加 UI,需自行引用默认组件
- Props 兼容性 — 替换 ViewBindPlugin 中的组件时,请确保你的自定义组件能接收对应的 Props 类型。推荐使用 TypeScript 并在开发时引用框架的 Props 接口
- 不要循环依赖 — 自定义组件内部调用框架 API(如
actionPresenter.action())是推荐做法;但如果在自定义组件内部调用框架的组件并注册回同一个 key,会导致死循环