From 81fb8179c2edc0e80af0da01d0c479e22cd23076 Mon Sep 17 00:00:00 2001 From: xcs Date: Sat, 29 Aug 2026 06:03:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(interaction):=20data-options=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E4=B8=8B=E6=8B=89=E7=9A=84=E9=80=89=E9=A1=B9?= =?UTF-8?q?=E4=BB=8E=E5=8F=B0=E8=B4=A6=E7=8E=B0=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 芯片类型是人在件上现造的,写死在模版里等于「只能选出包那天就有的那几种」。 与 data-rows 同一层机制:只取值不判断,选中哪一个仍由 selectCurrent 按行内的值定。 空选项一律留着 ——「还没选」得说得出来。 brief 给的实现有两处会让测试落空,已按 TDD 修正并留痕在代码注释里: ①产出的 2)—— fillOptions 需要把 data-slot 一并摘掉; ②selectCurrent 只在 renderRows 的逐行流程里被调用,不在任何 data-rows 里的下拉 永远等不到人给它标 selected —— renderView 收尾再兜底调一遍(用外层作用域), 已经被行内定过的整块原样放过(靠判断块内是否已有 selected,不靠字符串层面 分不清的「是不是在行里」)。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet --- .../api/src/modules/interaction/index.d.ts | 2 +- .../interaction/__tests__/view-render.test.ts | 43 +++++++++++++ apps/api/src/modules/interaction/index.ts | 2 +- .../internal/domain/view-render.ts | 63 ++++++++++++++++++- docs/HTML报告生成规范.md | 1 + 5 files changed, 107 insertions(+), 4 deletions(-) diff --git a/api-report/apps/api/src/modules/interaction/index.d.ts b/api-report/apps/api/src/modules/interaction/index.d.ts index 9caf8c3..21a234a 100644 --- a/api-report/apps/api/src/modules/interaction/index.d.ts +++ b/api-report/apps/api/src/modules/interaction/index.d.ts @@ -7,6 +7,6 @@ export type { Audience, Clock, HumanEventPublisher, IdGenerator, InteractionStor export type { InteractionServiceDeps } from './internal/usecase/interaction-service.ts'; export { createInteractionService, visibleTo } from './internal/usecase/interaction-service.ts'; export { CONTROL_HOLDERS, checkTemplate, handoverHappens, inputControl, INTERACTION_PHASES, outputControl, templateIsSafe, } from './internal/domain/control-handover.ts'; -export { renderView, renderTemplate, renderRows, escapeHtml, displayValue } from './internal/domain/view-render.ts'; +export { renderView, renderTemplate, renderRows, fillOptions, escapeHtml, displayValue } from './internal/domain/view-render.ts'; export { createInteractionStore } from './internal/adapter/interaction-store.ts'; export { chatMessageTable, taskViewTable } from './internal/adapter/interaction-schema.ts'; diff --git a/apps/api/src/modules/interaction/__tests__/view-render.test.ts b/apps/api/src/modules/interaction/__tests__/view-render.test.ts index 1656502..cdf1888 100644 --- a/apps/api/src/modules/interaction/__tests__/view-render.test.ts +++ b/apps/api/src/modules/interaction/__tests__/view-render.test.ts @@ -435,3 +435,46 @@ describe('嵌套行模版与 data-by —— 一个候选一张表', () => { expect(all.split('使能逻辑').length - 1).toBe(2); }); }); + +describe('data-options:下拉的选项从台账现读', () => { + const scope = { + 'parts.chip_type': [ + { type_id: 1, type_name: 'DC-DC 降压稳压器' }, + { type_id: 2, type_name: 'LDO 线性稳压器' }, + ], + chip_type_id: 2, + }; + + it('把选项铺进去,并留一个空选项(「还没选」要说得出来)', () => { + const html = ''; + const out = renderView(html, scope); + expect(out).toContain(''); + expect(out).toContain(''); + expect(out).toContain(''); + }); + + it('选项文字要转义 —— 台账里的值是人填的', () => { + const out = renderView( + '', + { 't.t': [{ v: 'x', l: '坏' }], c: '' }, + ); + expect(out).toContain('<b>坏</b>'); + expect(out).not.toContain('坏'); + }); + + it('地址取不到 → 只留那个空选项,不塞假数据(界面不撒谎)', () => { + const out = renderView('', {}); + expect(out).toContain(''); + expect(out).not.toContain('undefined'); + }); + + it('行内的下拉:每一行都铺同一份选项,选中的按各行自己的值', () => { + const html = + '' + + '' + + ''; + const out = renderView(html, { ...scope, 'mine:a.b': [{ chip_type_id: 1 }, { chip_type_id: 2 }] }); + expect(out.match(/