审批表单字段集来源读错节点,四轮修复全部打在症状上
在一个内部审批平台里,我们的工单详情页支持多节点工作流——发起 → 主管分配 → 评审 → 审批 → 结束。每个节点有自己独立的必填/选填/自动字段配置。理论上,审批人打开表单时,只该看到「当前节点」对应的字段集。
但是用户反馈说:在主管分配节点,页面却展示了 20 多个「数据录入员」才需要填的字段,评审节点同理。每轮补丁修复后,新 bug 照常出现,连续四轮都没彻底解决。
现象
- 主管分配节点(node-2)页面上渲染出 24 个必填 + 18 个选填字段
- 其中大量字段是 node-1(发起节点)才该显示的内容
- 级联联动(cascade)、脏字段 diff、锁定 icon 的逻辑后来各改了一轮,症状仍然部分存在
- 四次修复均无法根治,只是让部分字段的交互行为"看上去稍好一点"
根因
审批详情页在计算「渲染哪些字段」时,代码里写了这样一段:
// 反模式:硬读发起节点
const startNode = workflow.nodes.find((n) => n.type === 'start')
const requiredFields = startNode?.requiredFields ?? []
const optionalFields = startNode?.optionalFields ?? []approval.current_node_id 随工作流推进会不断移动(node-1 → node-2 → node-3 …),但字段集来源始终钉在 start。后续的所有逻辑——级联 Provider、脏 diff 计算、锁定状态——都以「这就是当前节点的字段」为前提在运行。它们都做对了,只是对着一份错误的数据在操作。
把根因分层来看:
Layer 0(字段集来源) ← 读错节点 → 以下所有层都在操作错误数据
↓
Layer 1(级联联动) ← 读 "columns" 来自 Layer 0
↓
Layer 2(脏字段 diff) ← 读 formValues 的 key 来自 Layer 0
↓
Layer 3(锁定 icon) ← 读 fieldStatus 来自 Layer 0
↓
Layer 4(渲染) ← FieldValueEditor 按字段列表渲染
Layer 0 错了,Layer 1-4 行为都「正确地执行了错误任务」。在 Layer 1/2/3 各打一轮补丁,本质上是在移动甲板上的椅子。
还有一种「安全兜底」写法更危险:
// 更隐蔽的反模式:fallback 到 start
const node =
workflow.nodes.find((n) => n.id === approval.currentNodeId) ??
workflow.nodes.find((n) => n.type === 'start')这段代码对「恰好在发起节点的用户(通常是 QA 或管理员)」完全正常,却对所有处于下游节点的真实用户静默返回错误字段集。测试通过,线上炸。
正解
从 approval.current_node_id 出发,精准匹配当前节点,匹配不到就返回空配置,绝不 fallback 到 start:
export function resolveCurrentNodeFieldConfig(
nodes: WorkflowNodeFieldConfig[] | undefined | null,
currentNodeId: string | undefined | null
): ResolvedNodeFieldConfig {
if (!nodes || nodes.length === 0) return EMPTY_CONFIG
if (!currentNodeId) return EMPTY_CONFIG
const nodeIdx = nodes.findIndex((n) => n.id === currentNodeId)
if (nodeIdx < 0) return EMPTY_CONFIG // 不 fallback,直接返回空
const node = nodes[nodeIdx]
return {
requiredFields: node.requiredFields ?? [],
optionalFields: node.optionalFields ?? [],
autoFields: node.autoFields ?? [],
}
}调用时:
const currentNodeCfg = resolveCurrentNodeFieldConfig(
wf.nodes as WorkflowNodeFieldConfig[],
found.currentNodeId // ← 来自 approval 记录,随流程实时更新
)
requiredFields = currentNodeCfg.requiredFields
optionalFields = currentNodeCfg.optionalFields
autoFields = currentNodeCfg.autoFields修完字段集来源后,级联联动、脏 diff、锁定 icon 一个补丁都没有再动,行为全部自动正确。这印证了:四轮症状修复积累的代码复杂度,本可以用一次根因定位来替代。
验收清单:
grep -n "nodes.find\|nodes\[0\]\|type === 'start'"在审批详情页无结果- 存在测试:「node-2 返回 node-2 配置,而非 node-1 配置」
currentNodeId匹配失败时函数返回EMPTY_CONFIG,而非 start 节点字段
一句话外卖
在多层数据流中,一旦最上游的「数据来源」是错的,下游所有层的修复都是在对着错误数据做正确操作——先定位 Layer 0,再动任何 Layer N。