级联下拉永远「暂无选项」——parent_field_key 指向了 alias 而不是实际存储 key
我们做了一个元数据驱动的低代码表单平台,支持 preset_enum 级联下拉(类似省市区那种多级联动)。某天有用户反馈,某张单据的二级下拉展开后始终显示「暂无选项」,换了浏览器也没用,清缓存也没用。更诡异的是:用户明确说父字段已经选了值,而其他页面的省市区级联工作完全正常。
前端同学捋了半天 Context Provider 和 useCascadeField hook,逻辑一切看起来都对。后来数据库一查,真相让人哭笑不得。
现象
- 级联子字段打开后显示「暂无选项」或 placeholder 停在「请先选择上级字段」
- 用户确认父字段已有值(在前序审批节点或编辑态已保存)
- 查
form_dataJSON,父字段的业务值确实存在 - 其他同类型级联(比如省市区)工作正常
- UI 架构层(Context / hook)看不出任何异常
根因
问题不在前端,在 L1 元数据配置层。
同一个业务概念(比如「评审系列」)在字段配置表里有两条记录:一条 key 叫 titleSeries,另一条叫 reviewSeries。它们指向完全相同的 preset_enum_id 和 linked_enum_level,本质上是同一个东西的两个名字。
问题在于:
- 子字段的
parent_field_key配置指向了titleSeries - 但实际写入
form_data的父值,历史上走的是另一条入口,存的是reviewSeries - 级联引擎拿
titleSeries去form_data里找,什么都没有,于是子字段得不到父值,永远显示「暂无选项」
这是 SSOT(单一真源)在 DB 层的分裂:同一业务概念有多个 key,不同历史代码路径各走各的,cascade 链就这么断了。
字段配置表里同一业务概念可能存在多条记录(alias key),不同写入路径历史上各自用了不同的 key。parent_field_key 只能指向一个,一旦配错,子字段永远读不到父值,但前端和 hook 层毫无报错,极难定位。
排查三步法
Step 1:查子字段的 parent_field_key 指向谁
SELECT `key`, title, parent_field_key, linked_enum_level, preset_enum_id
FROM field_config
WHERE `key` = '<子字段 key>' AND is_deleted = 0;记下 parent_field_key 的值,假设是 titleSeries。
Step 2:查 form_data 里实际存的 key
SELECT
JSON_EXTRACT(form_data, '$.titleSeries') AS via_config_key,
JSON_KEYS(form_data) AS all_keys
FROM submissions WHERE id = '<记录 id>';如果 via_config_key 是 NULL,但 all_keys 里能看到另一个同义 key(比如 reviewSeries)——确诊。
Step 3:确认 SSOT 分裂
SELECT `key`, title, preset_enum_id, linked_enum_level
FROM field_config
WHERE preset_enum_id = '<相同 enum id>'
AND linked_enum_level = '<相同 level>'
AND is_deleted = 0;返回 > 1 行,说明同一业务概念被定义了多个 key,这是根因。
正解
救火(1 分钟):先统计哪个 key 在 form_data 里覆盖率更高,把 parent_field_key 改成实际存储的那个 key。
-- 先统计两个 key 的覆盖情况
SELECT
SUM(JSON_EXTRACT(form_data, '$.titleSeries') IS NOT NULL) AS title_count,
SUM(JSON_EXTRACT(form_data, '$.reviewSeries') IS NOT NULL) AS review_count
FROM submissions;
-- 确认哪个多,再改配置
UPDATE field_config
SET parent_field_key = 'reviewSeries'
WHERE `key` = '<子字段 key>' AND is_deleted = 0;根治(小半天):选定 canonical key,迁移历史数据,软删 alias 定义,并在全仓库 grep 替换所有旧 key 引用。
-- 把 alias key 的历史值迁移到 canonical key
UPDATE submissions
SET form_data = JSON_SET(
JSON_REMOVE(form_data, '$.titleSeries'),
'$.reviewSeries',
JSON_EXTRACT(form_data, '$.titleSeries')
)
WHERE JSON_EXTRACT(form_data, '$.titleSeries') IS NOT NULL
AND JSON_EXTRACT(form_data, '$.reviewSeries') IS NULL;
-- 软删 alias 字段定义
UPDATE field_config SET is_deleted = 1 WHERE `key` = 'titleSeries';不要直接改配置——若生产数据里两个 key 都有历史存量,改配置只会让另一半失效。先 COUNT 统计各 key 的覆盖量,确定 canonical key 后再做数据迁移 + 配置收敛。
每次新增字段配置前,先 SELECT * FROM field_config WHERE title LIKE '%业务概念%' 扫一遍,确认没有同义重复定义。有条件的话,给 preset_enum_id + linked_enum_level 组合加 CI 唯一性校验,从源头杜绝 SSOT 分裂。
一句话外卖
级联子字段读不到父值,八成不是前端 hook 的问题——先查
parent_field_key指向的 key 和form_data里实际存的 key 是不是同一个。