使用场景概览
流程Meta节点
项目名称模板
- 运行环境:后端,创建项目时
- 一些流程中的项目不需要填写项目名称,则需要配置项目名称模板,以便自动生成项目名称。
任务标题模板
- 运行环境:前端,渲染任务列表时
- 默认任务会使用项目名称作为任务标题,有的时候在我的任务列表中不太好区分,需要在标题中增加更多的信息,可以配置任务标题模板,以便自动生成任务标题。
计算属性
- 运行环境:后端,相关项目或环节发生变化时
- 项目的某些属性是由其他属性计算得到的,例如项目的总金额是由项目的单价和数量计算得到的,这种属性称为计算属性,可以配置计算属性,以便自动计算。
分配后续环节负责人
- 运行环境:前端,渲染人员分配组件时
- 用于控制是否显示当前分配组件,以及动态加载默认分配人员。
环节可见性配置
- 运行环境:前端,渲染流程日志列表时
- 用户控制流程日志列表中的环节是否可见。
自定义跳转逻辑
- 运行环境:后端,项目创建后执行
- 控制项目创建后动态跳转的第一个节点,一般会依赖创建项目时的表单数据。
插件相关的配置
- 见各插件的文档
环节节点
分配后续环节负责人
- 运行环境:前端,渲染人员分配组件时
- 用于控制是否显示当前分配组件,以及动态加载默认分配人员。
分配当前环节负责人
脚本
将当前环节负责人分配脚本与指定表单绑定(ctx)
类型
interface OwnerItem {
/** 负责人用户id */
owner_id: number,
/** 处理状态 0 未处理, 1 通过, 2 拒绝 */
status?: number,
[p: string]: any;
}
interface FlowStepId2Owners {
/** 为0时为设置项目负责人 */
[flow_step_id: string]: OwnerItem[];
};
interface ResetProjectStepOwnerOption {
/** 是否全匹配,
* 矩阵和脚本分配环节负责人时默认为true: 删除掉老负责人数据中在新配置中不存在的人
* 为false时只会向环节中添加参数中不存在的负责人, 不会去除系统中多余的负责人
*/
full_match?: boolean;
/**
* 变更原因
*/
reason?: string;
/**
* 操作批次,修改多个项目多个环节负责人可以共享一个批次
* 用于交接动作一次性重新分配多个项目多个环节负责人,记录同一次操作造成的多次影响
*/
batch_id?: string;
/** 操作人id */
user_id?: number;
/**
* 触发有效环节负责人变更后是否发送变更消息给相关用户(新旧负责人)
* 矩阵和脚本分配环节负责人时默认为true
*/
send_message?: boolean;
/** 需要删除的负责人
* full_match为false时仍要删除的负责人,用于环节转办以及交接时在不影响其余环节负责人的情况下移除要转办的负责人
* 即当当前环节负责人为 A B C 时 要将A转办给D, 需要在保留B C 的情况下删除 A
*/
flow_step_id_to_owners_del?: FlowStepId2Owners;
/**
* 更新环节负责人时是否触发es刷新
* 矩阵和脚本分配环节负责人时默认为true
*/
trigger_es_refresh?: boolean;
/**
* 工作区, 决定变更负责人时要将变更消息发送到哪个工作区
* 矩阵和脚本分配环节负责人时默认发送到项目所在的工作区
*/
workspace?: string;
}
interface Context {
$subScribeForm(form_id: number, option: Partial<ResetProjectStepOwnerOption>): Promise<void>;
$subscribe(table: string, id: any, option: Partial<ResetProjectStepOwnerOption>): Promise<void>;
}示例
// 当form_id = 1的表单中控件被修改时将会重新执行当前脚本附加新的环节负责人
const form_id = 1;
await ctx.$subScribeForm(form_id, {
reason: '测试原因'
});
// 返回当前负责人列表
return [ 1 ];矩阵
将当前环节负责人分配矩阵与指定矩阵绑定(ctx)
- 无法订阅一个不存在于系统中的记录,对于运行完次脚本之后新增加的可以匹配到的记录不会订阅(订阅所有省份符合河北的总监作为环节负责人,则后续新增一条河北省份记录不会导致重新触发负责人分配)
类型
type MatrixSelectOptions = {
/** 流程标识 `${flow_type}:${flow_key}` */
flow?: string;
/** 是否保持同步,为true时 对应搜索记录发生变更时触发环节负责人同步 */
auto_subscribe?: boolean;
/** 订阅选项 */
subscribe_option?: Partial<ResetProjectStepOwnerOption>;
}
interface Context {
$matrixService {
/**
* 原生es搜索矩阵
* @param query any es查询语句body.query
* @param flow 流程标识 `${flow_type}:${flow_key}`
* @returns
*/
select(query: any, option?: MatrixSelectOptions): Promise<any>;
/**
* matrix简易请求
*/
selectByObject(queryObject: Record<string, any>, option?: MatrixSelectOptions): Promise<any>;
/**
* 订阅矩阵
* @param pid 矩阵所绑定的项目id
* @param option 订阅选项
*/
subscribeMatrix(pid: number, option?: Partial<ResetProjectStepOwnerOption>): Promise<void>;
}
}示例
// 方案1, 传入自动订阅参数
const result = await ctx.$matrixService.selectByObject({
province: "河北",
}, {
// 自动订阅命中的矩阵记录,对应矩阵表单修改时重新分配环节负责人
auto_subscribe: true,
// 订阅配置, 详见
subscribe_option: {
full_match: true,
}
});
// 方案2, 手动调用订阅api
result.forEach(i => {
ctx.$matrixService.subscribeMatrix(i.__id);
});
// 河北省的du_dao作为环节负责人
return result.map((i) => i.du_dao);条件节点的定义
- 运行环境:后端,环节流转时执行
- 用于控制环节依据项目的某些属性值来决定流转到多个后续环节中的哪一个。
可用参数
| 参数名 | 类型 | 描述 |
|---|---|---|
| step | object | 当前环节 |
| steps | object | 所有项目中已存在的环节,使用环节模板ID来获取 |
| project | object | 项目数据 |
| options | Array | 目标跳转环节的集合 |
返回值
目标跳转环节的下标
示例代码
const lastStep = steps['1681717443449'];
const targetSteps = options;
return Math.max(targetSteps.indexOf(lastStep.from_flow_step_id), 0);说明:如果lastStep所代表的环节的来源环节与备选环节某环节一致,则返回该环节的下标,否则返回0。
环节流转前处理
- 运行环境:后端,环节流转时执行
- 在环节流转前运行生命周期钩子脚本,用于修改提交数据、请求其他接口、校验等操作。
- 配置位置:环节节点配置 → 启用"流转前处理" → 编写"流转前处理脚本"
适用场景
- 在环节流转前进行额外的数据校验
- 根据业务逻辑修改提交的表单数据
- 调用外部接口或服务
- 记录审计日志
- 触发其他系统的通知
配置说明
enableBeforeNextStepScript: 是否启用流转前处理(布尔值)beforeNextStepScript: 流转前处理脚本内容(字符串)all_complete_required: 是否需要所有负责人完成才执行脚本(布尔值)
当 all_complete_required 为 true 时,只有当所有负责人都完成了当前环节,脚本才会执行。
上下文对象 (ctx)
类型定义
interface BeforeNextStepContext {
// 基础信息
action: string; // 当前操作类型:'approve'(通过) | 'refuse'(拒绝) | 'accept'(接受) 等
step_id: number; // 当前环节ID
payload: Record<string, any>; // 用户提交的数据
userInfo: UserEntity; // 当前操作用户信息
// 流程相关
flow: FlowEntity; // 完整的流程配置信息
project: ProjectEntity; // 项目数据
projectForm: FormEntity; // 项目主表单数据
currentStep: StepEntity; // 当前环节数据
currentStepForm: FormEntity; // 当前环节表单数据
curStepConfig: FlowNode; // 当前环节在流程中的配置
// 工具方法
$moment: typeof moment; // moment 日期处理库
$service(name: string): Service; // 获取服务实例
$model(name: string): Model; // 获取模型实例
/**
* 设置环节表单数据
* 注意:必须通过此方法修改表单,不能直接修改 payload.step_form
* @param obj 表单数据对象,支持 key_to_value、field_to_value、title_to_value 等映射方式
*/
$setForm(obj: {
key_to_value?: Record<string, any>; // 通过字段 key 设置值
field_to_value?: Record<string, any>; // 通过字段 field 设置值
title_to_value?: Record<string, any>; // 通过字段 title 设置值
}): void;
/**
* 当前流转逻辑触发后刷新数据集的模式,默认为异步刷新(流转完成第一时间查询数据集数据可能是旧的,改为wait_for可以等待数据集刷新完毕后动作才结束)
* @param refreshType 刷新类型,'wait_for' 等待刷新完成 | boolean 是否刷新, 默认 false
*/
$setRefreshType(refreshType: 'wait_for' | boolean): void;
// API 代理(由 ApiProxyService 注入)
// 可以通过 ctx.$api 调用配置的 API 代理
}实体类型说明
相关实体类型定义可参考:
FlowEntity: 流程实体,参见 实体文档 - FlowEntityProjectEntity: 项目实体,参见 实体文档 - ProjectEntityStepEntity: 环节实体,参见 实体文档 - StepEntityUserEntity: 用户实体,参见 实体文档 - UserEntityFormEntity: 表单实体,参见 实体文档 - FormEntity
使用示例
示例 1:修改环节表单数据
// 在环节流转前,根据项目表单中的某个字段自动填充环节表单
const projectAmount = ctx.project.metaForm?.key_to_value?.amount || 0;
// 使用 $setForm 方法修改环节表单
ctx.$setForm({
key_to_value: {
calculated_amount: projectAmount * 1.1, // 自动计算金额
operator: ctx.userInfo.name, // 记录操作人
operate_time: ctx.$moment().format('YYYY-MM-DD HH:mm:ss') // 记录操作时间
}
});示例 2:数据校验
// 在审批通过前,校验必填字段
if (ctx.action === 'approve') {
const stepForm = ctx.payload.step_form;
const approval_reason = stepForm?.key_to_value?.approval_reason;
if (!approval_reason || approval_reason.trim() === '') {
throw new Error('审批意见不能为空,请填写审批理由');
}
if (approval_reason.length < 10) {
throw new Error('审批意见不能少于10个字符');
}
}示例 3:调用外部服务
// contactV3ScopesGet
const result = await ctx.$proxy('feishu').contactV3ScopesGet(param1, param2, param3, param4);示例 4:根据条件动态设置后续环节
// 根据审批金额动态设置下一个环节
if (ctx.action === 'approve') {
const amount = ctx.currentStep.formList?.[0]?.key_to_value?.amount || 0;
// 金额大于 100万,需要额外的高层审批
if (amount > 1000000) {
ctx.payload.target_step = '1681717443450'; // 高层审批环节ID
} else {
ctx.payload.target_step = '1681717443451'; // 普通审批环节ID
}
}示例 5:记录审计日志
// 记录详细的操作日志到自定义表
const auditModel = ctx.$model('audit_log');
await auditModel.add({
project_id: ctx.project.id,
step_id: ctx.step_id,
user_id: ctx.userInfo.id,
action: ctx.action,
action_label: ctx.payload.action_label || ctx.action,
form_data: JSON.stringify(ctx.payload.step_form),
operate_time: ctx.$moment().format('YYYY-MM-DD HH:mm:ss'),
ip_address: ctx.userInfo.ip || '',
organization_id: ctx.userInfo.organization_id
});示例 6:多条件综合处理
// 综合多个条件进行复杂的业务逻辑处理
const { action, payload, currentStep, project, userInfo } = ctx;
// 只在审批通过时执行
if (action === 'approve') {
const stepForm = payload.step_form?.key_to_value || {};
const projectForm = project.metaForm?.key_to_value || {};
// 1. 数据校验
if (!stepForm.approval_result) {
throw new Error('请选择审批结果');
}
// 2. 根据项目类型和审批结果设置不同的数据
if (projectForm.project_type === 'urgent') {
ctx.$setForm({
key_to_value: {
priority_level: 'high',
expect_finish_time: ctx.$moment().add(3, 'days').format('YYYY-MM-DD'),
notice_users: projectForm.urgent_contact_users
}
});
} else {
ctx.$setForm({
key_to_value: {
priority_level: 'normal',
expect_finish_time: ctx.$moment().add(7, 'days').format('YYYY-MM-DD')
}
});
}
// 3. 调用外部服务通知相关人员
const messageService = ctx.$service('message');
await messageService.sendMessage(
userInfo.id,
projectForm.owner_users || [],
{
title: '审批通过通知',
content: `项目"${project.name}"已通过审批`,
actions: [{
type: 'link',
text: '查看详情',
url: `/project/${project.id}`
}]
}
);
}注意事项
- 表单修改:必须通过
ctx.$setForm()方法修改表单数据,直接修改payload.step_form不会生效 - 异常处理:脚本中抛出的异常会阻止环节流转,错误信息会直接显示给用户
- 异步操作:脚本支持 async/await,可以执行异步操作
- 执行时机:脚本在环节负责人状态更新之前执行,此时环节尚未流转
- 权限控制:脚本中可以访问的模型和服务受系统权限控制
- 性能考虑:避免在脚本中执行耗时过长的操作,建议将复杂逻辑移到后台任务中
错误处理
当脚本执行出错时,系统会:
- 阻止环节流转
- 将错误信息显示给用户
- 记录错误日志到系统日志中
- 触发
save_logical_node_error插件钩子(如果有配置)
示例错误处理:
try {
// 业务逻辑
const result = await someAsyncOperation();
if (!result.success) {
throw new Error('操作失败:' + result.message);
}
} catch (error) {
// 记录详细错误日志
ctx.$service('logger').error('环节流转前处理失败', {
project_id: ctx.project.id,
step_id: ctx.step_id,
error: error.message,
stack: error.stack
});
// 抛出用户友好的错误信息
throw new Error('处理失败,请联系管理员或稍后重试');
}自动创建流程

- 点击
项目创建规则和数据映射时可以输入代码 - 运行环境:后端,环节流转时执行
- 用于在流程环节中自动创建子流程,支持动态生成子流程数据,并可与父流程表单建立订阅关系
- 支持子项目基本信息的同步,如项目名称、状态、扩展配置等字段的自动更新
函数定义
// 返回要创建的子项目列表
const autoCreateSubProject = (ctx: AutoCreateSubProjectContext) : Promise<CreateProjectTaskDto[]>// 返回要创建的子项目列表
interface AutoCreateSubProjectContext extends ServerProjectContext {
// 此标识用于识别是否由订阅触发, 脚本中可以借用此标识规避一些副作用(比如运行创建子流程脚本时需要额外创建一些数据)
trigger_by_subscription: boolean;
}
type CreateProjectTaskDto = {
flow_type: string;
key: string;
/**
* 若为true, 会先把对应的用户创建的项目删除再走创建操作
* @see packages/huanpingtong-server/src/model/project.ts#createProjectByTask
*/
delete: boolean;
data: CreateProjectTaskDataDto;
}
type CreateProjectTaskDataDto = {
form: SimpleFormValue;
contract_id?:string;
subscribe?: {
// 是否跟随新的task结果自动删除
auto_delete?: boolean
// 是否跟随新的task结果自动创建新的子流程项目
auto_add?: boolean;
subscriptions: SubscriptionItem[]
};
} & ProjectCreateDto;
export interface BasicSubscriptionItem {
// 处理类型和设置
handle: string;
setting?: Record<string, any>;
// 被订阅者表名
publisher_table: string;
// 被订阅者表单id
publisher_id: number;
// 是否激活
enable: boolean;
}
// 处理类型声明
export interface FormSubscriptionItem extends BasicSubscriptionItem {
handle: 'form';
setting?: Record<string, any>;
}
export interface ProjectSubscriptionItem extends BasicSubscriptionItem {
handle: 'project';
setting?: {
// 要同步的子项目基础字段
// 特别的,当存在ext_config 字段时, 会和当前数据库中的project.ext_config 进行Object.assign 合并操作
fields?: (keyof ProjectCreateDto)[];
};
}
export type SubscriptionItem = FormSubscriptionItem | ProjectSubscriptionItem;示例代码
示例1:创建固定流程类型的多个子项目
// 根据主项目的表单数据创建多个子流程
const itemList = ctx.$META_FORM_KEY.sub_form_list || [];
// 为每个列表项创建一个子流程
return itemList.map((item, index) => {
return {
// 使用唯一ID作为contract_id,用于订阅时后续更新或删除
contract_id: `item_${item.id}`,
// 子项目名称
name: `${ctx.project.name}-子项目${index + 1}`,
// 子项目表单数据
form: {
key_to_value: {
name: item.name,
price: item.price,
}
},
// 订阅父项目的表单变化
subscribe: {
auto_delete: true, // 当父项目中对应项被删除时,自动删除子项目
auto_add: true, // 当父项目中新增项时,自动创建新的子项目
subscriptions: [
{
handle: 'form',
publisher_table: 'form',
publisher_id: mainFormId,
enable: true
},
{
handle: 'project',
publisher_table: 'project',
publisher_id: ctx.project.id,
enable: true,
setting: {
fields: ['name', 'status', 'ext_config'] // 同步项目基本信息
}
}
]
}
};
});示例2:使用项目基本信息订阅
// 创建子项目并订阅父项目的基本信息变更
return [
{
contract_id: "main_sub_project",
flow_type: "flow_type_B",
key: "key_B",
delete: false,
data: {
name: `${ctx.project.name}-辅助项目`,
form: {
key_to_value: {
reference_id: ctx.project.id
}
},
subscribe: {
auto_delete: true,
subscriptions: [
// 订阅父项目的基本信息变更
{
handle: 'project',
publisher_table: 'project',
publisher_id: ctx.project.id,
enable: true,
setting: {
// 指定需要同步的字段
fields: ['name', 'status', 'ext_config']
}
}
]
}
}
}
];动态修改环节配置
- 运行环境:前端,渲染环节处理页时
- 用于在进入环节处理面的时候动态控制环节的配置,例如动态可用的操作
插件相关的配置
- 见各插件的文档
