跳到主要内容

API 参考

@vef-framework-react/approval-flow-editor 的干货签名参考。关于叙述性的用法说明,见 概览宿主集成

组件

ApprovalFlowEditor

FC<ApprovalFlowEditorProps>

属性类型说明
valueFlowDefinition流程定义(后端格式)。传入一个新的引用(不是编辑器自己发出的那个)会重新加载画布。空值 / 缺省会填充一个 start→end 的最小流程。
onChange(definition: FlowDefinition) => void每次图变化时调用,携带一份脱离的深拷贝。
onPublish(definition: FlowDefinition) => void发布钩子,在校验通过后携带当前定义调用。省略此项则不渲染发布按钮。
publishTextstring发布按钮文案。默认 "发布"
publishLoadingboolean在发布按钮上显示加载指示并阻止重复点击。默认 false
readonlyboolean禁用编辑并隐藏工具栏 / 发布按钮。默认 false
pluginsEditorPlugins宿主集成点(选择器、表单字段、全局变量)。
classNamestring编辑器外壳上的自定义 class。
styleCSSProperties编辑器外壳上的自定义样式。

插件

导出种类说明
EditorPluginstype{ pickers?, formFields?, globalSubjects? }——见 宿主集成
PickerPropstype{ value: string[]; onChange: (ids: string[]) => void; disabled?: boolean }
PrincipalKindtype"user" | "role" | "department"
PRINCIPAL_KINDSconstreadonly ["user", "role", "department"]
isPrincipalKindfn(kind: string) => kind is PrincipalKind

流程定义(wire 格式)

导出结构
FlowDefinition{ nodes: NodeDefinition[]; edges: EdgeDefinition[] }
NodeDefinition{ id: string; kind: NodeKind; position: XYPosition; data?: NodeDataMap[kind] }(基于 kind 的可辨识联合类型)
EdgeDefinition{ id?: string; source: string; target: string; sourceHandle?: string; data?: Record<string, unknown> }——sourceHandle 把一个 condition 节点的外出边绑定到某条分支 id
NodeKind"start" | "approval" | "handle" | "condition" | "cc" | "end"
FlowNodeFlowEdge实时(内存中)的画布形态,由本包自行定义: FlowNode 是各按类型定型的 @xyflow/react 节点(见下一行)的联合类型;FlowEdge 是 xyflow 的 Edge。通过 toFlowDefinition / fromFlowDefinitionFlowDefinition 互转。
StartNodeApprovalNodeHandleNodeCcNode按类型定型的实时节点——Node<StartNodeData, "start">Node<ApprovalNodeData, "approval">Node<HandleNodeData, "handle">Node<CcNodeData, "cc">。在实时节点上,由 React Flow 的 type 承载节点类型(它驱动节点渲染);只有 wire 层的 NodeDefinition 才把它提升为 kind,而节点的业务字段直接位于 data 上——正是 wire 所承载的那个 NodeDataMap[kind] 形态。end / condition 的别名在内部存在,但未导出。
版本提示: nodeloom 时代(v2.7.0 – v2.12.0)

从 v2.7.0 到 v2.12.0,编辑器的状态引擎是 @coldsmirk/nodeloom-core,实时形态采用的是 nodeloom 的统一形状: 每个节点都携带 type: "flowNode",节点类型判别字段位于 data.kind,业务字段则位于 data.config 之下。FlowNode / FlowEdge / EditorState 曾是 nodeloom 的重导出,且按类型划分的节点别名没有导出。v2.12.0 之后一次尚未发布的变更(81e3ade)移除了这一依赖,恢复了本页所描述的手写状态引擎——按类型定型的节点,业务字段直接位于 data 上。两次变动都没有触及 wire 格式(FlowDefinition / NodeDefinition / EdgeDefinition)和 toFlowDefinition / fromFlowDefinition 的签名,因此已持久化的定义不受影响。迁移历史见版本说明

按类型划分的节点数据

NodeDataMap 把每种 NodeKind 映射到它的数据形态;AnyNodeData 是全部六种的联合类型(定义为 FlowNode["data"])。每种类型的数据都包含 name?: string(显示名称)和 description?: string。这些形态身兼二职: 它们既是 wire 上的 NodeDefinition.data 载荷,是实时节点的 data——序列化器只翻译类型判别字段(kind ↔ React Flow 的 type),从不触碰数据。

种类类型字段(除 name / description 外)
startStartNodeData
endEndNodeData
approvalApprovalNodeData继承 TaskNodeData,加上 approvalMethodpassRulepassRatiosameApplicantActionconsecutiveApproverActionrollbackTyperollbackDataStrategyrollbackTargetKeysisRollbackAllowedisAddAssigneeAllowedaddAssigneeTypesisRemoveAssigneeAllowedisManualCcAllowed
handleHandleNodeDataTaskNodeData 的别名(没有审批专属字段)
conditionConditionNodeDatabranches?: ConditionBranchDefinition[]
ccCcNodeDataccs?: CcDefinition[]isReadConfirmRequired?: booleanfieldPermissions?: Record<string, CcFieldPermission>

TaskNodeDataapprovalhandle 共用):

字段类型
assignees?AssigneeDefinition[]
executionType?ExecutionType
emptyAssigneeAction?EmptyAssigneeAction
fallbackUserIds?string[]
adminUserIds?string[]
isTransferAllowed?boolean
isOpinionRequired?boolean
timeoutHours?number
timeoutAction?TimeoutAction
timeoutNotifyBeforeHours?number
urgeCooldownMinutes?number
ccs?CcDefinition[]
fieldPermissions?Record<string, FieldPermission>

审批人、抄送与条件相关类型

导出结构
AssigneeDefinition{ kind: AssigneeKind; ids?: string[]; formField?: string; sortOrder: number }
AssigneeKindPrincipalKind | "self" | "superior" | "department_leader" | "form_field"
CcDefinition{ kind: CcKind; ids?: string[]; formField?: string; timing?: CcTiming }
CcKindPrincipalKind | "form_field"
CcTiming"always" | "on_approve" | "on_reject"
FieldPermission"visible" | "editable" | "hidden" | "required"
CcFieldPermission"visible" | "hidden"——CC 节点只能观察表单,因此不提供 editable / required
ConditionDefinition{ kind: ConditionKind; subject: string; aggregate?: "sum" | "count" | "avg"; column?: string; operator: ConditionOperator | ""; value: unknown; expression: string }
ConditionKind"field" | "expression"
ConditionOperatorCONDITION_OPERATORS 派生——见 宿主集成
ConditionGroup{ conditions: ConditionDefinition[] }——组内条件为 AND 关系
ConditionBranchDefinition{ id: string; label: string; conditionGroups?: ConditionGroup[]; isDefault?: boolean; priority: number }
FormFieldDefinition{ key: string; kind: FieldKind; label: string; options?: FieldOptionDefinition[]; columns?: FormFieldDefinition[]; hasConditionalVisibility?: boolean }
FieldKind"input" | "textarea" | "select" | "number" | "date" | "upload" | "table"
FieldOptionDefinition{ label: string; value: unknown }

ConditionDefinition.aggregate 为某个明细表 subject 圈定一次聚合折叠(sum / count / avg,对应后端的 AggregateKind);column 指定要折叠的数值列(count 不需要,因为它折叠的是行)。

FormFieldDefinition.hasConditionalVisibility(v2.10.0)是一个设计器侧提示: 当该字段的可见性可能被表单联动关掉时为 true——它自身带有具备隐藏能力的联动、默认隐藏,或某个祖先容器具备隐藏能力。当某个节点在这样的字段上授予 required 权限时,字段权限表会显示一条非阻塞警告,因为后端的必填检查并不感知联动: 如果字段在审批时被隐藏,它的空值会让通过操作永远被拒绝。该标注由 approval-form-bridgeprojectFormSchema 填充;当字段清单来自其他途径时则不存在。

枚举

导出取值
ApprovalMethod"sequential" | "parallel"
PassRule"all" | "any" | "ratio"——"all" 意味着有一票否决权
EmptyAssigneeAction"auto_pass" | "transfer_admin" | "transfer_superior" | "transfer_applicant" | "transfer_specified"
ExecutionType"manual" | "auto_pass" | "auto_reject"
SameApplicantAction"auto_pass" | "self_approve" | "transfer_superior"
RollbackType"none" | "previous" | "start" | "any" | "specified"
RollbackDataStrategy"clear" | "keep"
ConsecutiveApproverAction"none" | "auto_pass"
TimeoutAction"none" | "auto_pass" | "auto_reject" | "notify" | "transfer_admin"
AddAssigneeType"before" | "after" | "parallel"

校验

function validateFlowDefinition(
definition: FlowDefinition,
formFields?: FormFieldDefinition[]
): FlowValidationError[];

FlowValidationError: { code: FlowValidationCode; message: string; nodeId?: string; edgeId?: string; branchId?: string }message 是与编辑器 UI 语言一致的人类可读中文产品文案;请针对 code 和各个 id 字段编程。

formFields 是三态的(v2.10.0): undefined 表示字段清单不可用(宿主没有任何表单集成)——只会跳过 key 存在性检查(field_permission_key_unknown);显式传入 [] 表示已确认表单有零个字段,此时每一条 fieldPermissions 条目都是悬空引用。见 宿主集成 → 校验

FlowValidationCode 是一个封闭集合,对应后端部署时的各项检查:

图结构

代码含义
no_nodes流程没有任何节点。
empty_node_id / duplicate_node_id某个节点 id 为空 / 重复。
invalid_node_kind某个节点的 kind 不属于 NODE_KINDS
start_node_count流程必须恰好有一个 start 节点。
end_node_count流程必须至少有一个 end 节点。
empty_edge_id / duplicate_edge_id某条边 id 为空 / 重复。
unknown_source_node / unknown_target_node某条边引用了一个不存在的节点 id。
start_incomingstart 节点有一条进入边。
start_outgoingstart 节点的外出边不是恰好一条。
end_outgoing某个 end 节点有外出边。
end_incoming某个 end 节点没有任何进入边。
node_outgoing_count某个非 condition 节点的外出边不是恰好一条。
node_source_handle某个非 condition 节点的外出边带有分支句柄。
graph_cycle图中存在环。
node_unreachable某个节点无法从 start 节点到达。
node_cannot_reach_end某个节点无法到达任何 end 节点。

Condition 节点

代码含义
condition_min_branches分支数少于 2 条。
condition_empty_branch_id / condition_duplicate_branch_id某个分支 id 为空 / 重复。
condition_default_count默认分支不是恰好一条。
condition_missing_handle某条外出边没有分支句柄。
condition_unknown_handle某条外出边的句柄不匹配任何分支 id。
condition_duplicate_handle两条外出边绑定了同一个分支。
condition_branch_no_edge某条分支没有外出边。
duplicate_branch_priority两条非默认分支共用同一个优先级。
condition_subject_required某个字段条件没有 subject。
invalid_condition_operator运算符不在 CONDITION_OPERATORS 之内。
condition_expression_required某个表达式条件的 expression 为空。
invalid_condition_kindkind 既不是 "field" 也不是 "expression"
branch_conditions_required某条非默认分支没有条件组。
condition_group_empty某个条件组里没有任何条件。
invalid_aggregateaggregate 不属于 "sum" | "count" | "avg"
aggregate_operator某个聚合条件使用了非数值比较类的运算符。
aggregate_column_requiredsum / avg 聚合没有指定列。
aggregate_column_forbiddencount 聚合选择了某一列。
aggregate_on_expression某个表达式条件携带了 aggregate。

审批人、抄送与字段权限(approval / handle / cc 节点)——字段权限相关代码于 v2.10.0 加入,对应后端部署时的 ValidateFieldPermissions,因此权限配置错误会在设计器中暴露,而不是等到部署时才发现

代码含义
invalid_assignee_kind某个审批人的 kind 不在 AssigneeKind 之内。
assignee_form_field_required某个 form_field 类型的审批人没有选择字段。
invalid_cc_kind / invalid_cc_timing某个抄送的 kind / timing 不在其枚举范围内。
cc_form_field_required某个 form_field 类型的抄送收件人没有选择字段。
invalid_execution_typeexecutionType 不在 ExecutionType 之内。
invalid_empty_assignee_actionemptyAssigneeAction 不在其枚举范围内。
invalid_timeout_actiontimeoutAction 不在 TimeoutAction 之内。
fallback_users_requiredemptyAssigneeAction: "transfer_specified" 但没有 fallbackUserIds
admin_users_requiredemptyAssigneeAction: "transfer_admin" 但没有 adminUserIds
invalid_field_permission某个 fieldPermissions 值不在 FieldPermission 之内。
field_permission_key_unknown某个 fieldPermissions key 不存在于传入的 formFields 中。当 formFields 省略(undefined)时跳过——没有可供比对的清单。
cc_field_permission_not_allowed某个 CC 节点的 fieldPermissions 值不在 visible / hidden 之内。
field_permission_required_auto_pass某个任务节点把 "required" 字段权限与 timeoutAction: "auto_pass" 配在一起——超时路径会在不经过人工审批必填检查的情况下结束该节点,因此必填字段可能带着空值通过。无论 formFields 处于三态中的哪一态都会运行(它只检查节点自身的数据)。

Approval 专属

代码含义
invalid_approval_method / invalid_pass_rule不在各自的枚举范围内。
pass_ratio_rangepassRule: "ratio"passRatio 不在 (0, 100] 范围内。
invalid_same_applicant_action / invalid_consecutive_approver_action不在各自的枚举范围内。
invalid_rollback_type / invalid_rollback_data_strategy不在各自的枚举范围内。
invalid_add_assignee_type某个 addAssigneeTypes 项不在 AddAssigneeType 之内。
rollback_targets_requiredrollbackType: "specified" 但没有 rollbackTargetKeys
rollback_target_unknown某个回退目标不是一个已存在的 approval / handle 节点。
rollback_target_self某个回退目标引用了节点自身。
sequential_parallel_add_assigneeapprovalMethod: "sequential"addAssigneeTypes 里包含了 "parallel"

Handle 专属

代码含义
handle_execution_auto_reject某个 handle 节点的 executionType"auto_reject"(不支持)。
handle_timeout_auto_reject某个 handle 节点的 timeoutAction"auto_reject"(不支持)。

序列化

导出签名
toFlowDefinition(nodes: FlowNode[], edges: FlowEdge[]) => FlowDefinition
fromFlowDefinition(definition: FlowDefinition) => { nodes: FlowNode[]; edges: FlowEdge[] }

规格定义

导出签名 / 结构
NodeSpecification{ type: NodeKind; label: string; color: string; icon: ComponentType<LucideProps>; badgeVariant?: "soft" | "solid"; configPanel?: ComponentType<{ nodeId: string }> }
getSpecification(kind: NodeKind) => NodeSpecification——遇到未知类型会抛出异常
getAllSpecifications() => NodeSpecification[]
getAddableSpecifications() => NodeSpecification[]——工具栏可以添加的类型对应的规格(NODE_RULES[kind].addable

常量与结构规则

导出结构
NODE_KINDSreadonly NodeKind[]——全部六种类型
isNodeKind(kind: string) => kind is NodeKind
NODE_KIND_LABELSRecord<NodeKind, string>——每种类型对应的显示标签
NodeRule{ maxCount?: number; deletable: boolean; addable: boolean }
NODE_RULESRecord<NodeKind, NodeRule>——见 概览 中的表格
DEFAULT_NODE_DATARecord<NodeKind, Record<string, unknown>>——每种类型新增节点时的深度冻结默认数据;使用前请先克隆
CONDITION_OPERATORSreadonly ConditionOperator[]——ConditionOperator 派生自的运行时白名单;见 宿主集成

Store hooks

用于高级状态检查——例如测试,或以编程方式读取实时图:

导出签名
useEditorStore<T>(selector: (state: EditorState) => T) => T
useEditorStoreApi() => UnboundStore<EditorState>——一个 zustand store api(带 subscribeWithSelector / immer 中间件),来自 core 的 createComponentStore
EditorState完整的 store 状态 + 动作——自手写状态引擎回归后,这是本包自己的类型(见上方版本提示;v2.7.0 到 v2.12.0 期间它是 @coldsmirk/nodeloom-core 的重导出)

EditorState 是五个切片(slice)的交集(切片类型本身是内部实现;只有 EditorState 被导出):

切片状态动作
Graphnodes: FlowNode[]edges: FlowEdge[]onNodesChange / onEdgesChange / onConnect(xyflow 变更处理器);addNode(type: NodeKind, position: XYPosition, data?: Partial<AnyNodeData>) => string | null——返回新节点的 id,被拒绝时返回 null(只读,或该类型已达数量上限);removeNode(nodeId)——只读或不可删除的类型会被拒绝,会一并清理相连的边和回退目标引用;removeEdge(edgeId)updateNodeData(nodeId, data: Partial<AnyNodeData>)updateNodePositions(positions: Map<string, XYPosition>)——批量提交位置(自动布局用);updateConditionBranch(nodeId, branchId, patch)addConditionBranch(nodeId)——插入到默认分支之前;removeConditionBranch(nodeId, branchId)——级联移除该分支的边
InteractionselectedNodeId: string | nullhoveredEdgeId: string | nullreadonly: booleanselectNode(nodeId | null)setHoveredEdgeId(edgeId | null)setReadonly(readonly)
FlowisDirty: booleanloadDefinition(definition)——完成水合(空定义会填充 start→end),并重置选中状态、脏标记与撤销时间线;toDefinition() => FlowDefinition——一份脱离的深拷贝;reset()
ValidationvalidationIssues: FlowValidationError[]nodeIssueCounts: Record<string, number>——每个节点 id 的问题计数,这样节点外观只需订阅自己的那一项setValidationIssues(issues)
Historypast: HistoryEntry[]future: HistoryEntry[]——HistoryEntry = { nodes: FlowNode[]; edges: FlowEdge[] }(整图快照;视图状态从不进入检查点)undo()redo()

没有 canUndo / canRedo 成员(nodeloom 的 store 曾有过): 请用 past.length > 0 / future.length > 0 自行推导。少数成员属于内部管线(changeVersioncheckpointbreakCoalescing)——在类型上可见,但不面向宿主。

两个 hook 都解析自 <ApprovalFlowEditor> 内部挂载的 store——没有面向宿主的插槽可以在那个 provider 树内部渲染额外组件(provider 本身就是刻意不导出的),所以这两个 hook 主要用于读取状态(例如通过 useEditorStoreApi().getState()),而不是用于在画布内部挂载自定义 UI。

视觉 tokens

导出结构
NODE_KIND_COLORSRecord<NodeKind, string>——每种类型对应的强调色(主题自适应的 CSS 变量引用)
ApprovalIconCcIconConditionIconEndIconHandleIconStartIcon与六种节点类型一一对应的 lucide-react 图标组件