---

25. 尺码下拉「同名项重影」:归一 id→codeValue 修复身份错配 —— §24 分页修复揭出的潜伏 bug(2026-06-09)

25.1 现象

编辑某商品(款号 2606080001),尺码已选 M、L。打开尺码下拉滚到底,发现:

  • 列表里原本那条 L 没有勾选
  • 末尾额外补了一条 L 并打勾
  • 于是下拉里出现两个 L(一个不勾、一个勾)。M 同理。

25.2 第一反应 vs 真相

第一反应会怀疑「dedup 没写对」或「manualOptions 重复 push」。但真相更微妙:这是 §24 的分页修复(下拉滚动续页)把一个一直潜伏的 bug 揭出来了

根因是身份错配:这个商品的 sizeIds 落库存的是字典主键 id(8 位),不是 codeValue。于是三处身份不统一:

Select 的 value(回填值) = [id_M, id_L]        ← 8 位 id
下拉真实选项 dictData.value = codeValue          ← 1~2 位
manualOptions(回填选项).value = 原始存的 id     ← 8 位 id
  • Select 高亮的是「value 命中 form value」的选项 → 只有 manual 那条(id)被勾,真实选项(codeValue)勾不上。
  • dedup 按 value 去重,但两条 L 的 value(id vs codeValue)不相等 → 去重失效,两个 L 并存

为什么 §24 之前看不到? 改分页前下拉只加载 20 条,codeValue 那条 L 在后面的页、根本没加载,列表里只有 manual 那条 → 不重复、还正好勾上,看着「正常」。一旦支持滚动加载全部页,codeValue 那条 L 被拉进来,重影才显形。修复 A(分页)揭出了潜伏 bug B(身份错配)——这是非常典型的「一个改动打开了另一个 bug 的可见窗口」。

25.3 修复:在源头把引用归一成 codeValue

身份系统该统一到哪个?——codeValue。因为下拉选项、保存接口(handleModalOk 回传的就是 i.codeValue)都以 codeValue 为身份,只有「回填 value」这一路混进了 id。所以在加载商品详情时把存的引用统一归一成 codeValue:

  1. services/colorSizeDict.ts 新增 colorValueMap / sizeValueMapref(id|codeValue) → 规范 codeValue
    • 主键 id(8 位)→ 该行 codeValue(归一旧数据)
    • codeValue → 自身(恒等透传新数据)
    • 两套键空间不重叠,同一个 Map 塞两种键不撞车(沿用本文件既有「双键」思路)。
  2. ProductFormModal.tsx:回填 value、manualOptions 的 value 全过一遍 normalizeRefs / valueMap,统一成 codeValue。字典没加载到(map 空)则回退原值,等价旧行为、不崩。

归一后:form value、manualOptions、dictData 选项全说 codeValue → dedup 生效(两个 L 合一)→ 真实选项命中 form value(正常勾上)。§24 在 useTableSelection 加的 id 兜底匹配,降级为「字典没加载成功时」的防御网,仍保留。

25.4 取舍:为什么在源头归一,不在 FilterDict 里 dedup

❌ 在 FilterDict 按 label 去重:同名不同义有风险,且勾选态仍错(value 没对齐,勾不上)
⚠️ 在 FilterDict 把 id 翻成 codeValue:FilterDict 手里只有已加载的几页 + 手动选项,
    字典没全量,翻不可靠
✅ 在加载详情处归一(有全量字典 map):一次翻译,下游 value/选项/保存全部对齐,
    dedup 和勾选态都自然正确——身份统一是因,重影/勾不上是果,治因不治果

核心不变量:Select 的 value 必须和它 optionsvalue 用同一套身份。一旦两者可能来自不同编码(id / codeValue),就必须在进 Select 之前归一到同一种,否则高亮、去重、回显全部会以隐蔽的方式错乱。

25.5 举一反三

  • 「修了 A 冒出 B」≠ 改错了:很多潜伏 bug 靠「另一个限制」遮着(这里是「只加载 20 条」遮住了「身份错配」)。一旦放开限制,潜伏 bug 显形。遇到「我改完这里,那边冒出个怪现象」,先判断是不是揭出了旧坑,而不是急着回滚自己的改动。
  • 重复项 / 去重失效,先查「去重键」的身份一致性:dedup 按 value,但 value 来自两个编码空间 → 失效。重复类 bug 的根因常常不在「忘了去重」,而在「去重键不是同一个身份」。
  • 同一逻辑字段存了两种编码 = 系统性事实,要在「有完整翻译表的那一层」一次归一,别在下游每个消费点各自 if 特判。

25.6 面试角度

面试话术:「我修下拉无限滚动后,冒出一个『同名选项重影、原项不勾、末尾补一条勾』的怪现象。排查发现不是去重写错,而是分页放开后揭出了潜伏的身份错配——这个商品的尺码引用存的是字典主键 id,而下拉选项和保存接口都以 codeValue 为身份,回填 value 这一路混进了 id,导致按 value 去重失效、按 value 高亮也勾不上。我没有在下游打补丁,而是在加载详情、手握完整字典的那一层,用 id|codeValue → codeValue 的归一表把回填值和回填选项统一成 codeValue,让『value 与 options 同一身份』这个不变量重新成立——去重和勾选态就都自然正确了。这也提醒我:一个改动冒出的新现象,先判断是不是揭出了旧坑。」