# Architectural Decisions: 构件详情右键菜单功能 ## 2026-01-28 ### Decision 1: 选中状态存储在 Engine 而非 EngineManager **Context**: - 需要存储当前选中的构件信息(url + id) - Engine 组件监听底层 click 事件 - EngineManager 是 Engine 的代理层 **Options Considered**: 1. 存储在 Engine 组件中 2. 存储在 EngineManager 中 3. 存储在独立的 SelectionManager 中 **Decision**: 选择 Option 1 - 存储在 Engine 组件中 **Rationale**: - Engine 直接监听底层事件,数据流最短 - 避免事件转发的复杂性 - EngineManager 作为代理,只需暴露访问方法 - 保持单一职责原则:Engine 管理状态,EngineManager 提供接口 **Consequences**: - ✅ 数据流清晰:底层事件 → Engine 状态 → EngineManager 访问 - ✅ 性能更好:无需事件转发 - ⚠️ Engine 组件职责略有增加(但合理) --- ### Decision 2: 使用动态菜单生成而非事件驱动更新 **Context**: - 右键菜单内容需要根据选中状态变化 - 选中状态频繁变化 **Options Considered**: 1. 注册静态菜单 + 监听选中事件动态更新 2. 注册处理器函数,每次右键时动态生成菜单 **Decision**: 选择 Option 2 - 动态生成菜单 **Rationale**: - 右键菜单不常使用,生成成本低 - 避免维护菜单状态的复杂性 - 无需监听选中事件单独更新菜单 - 代码更简洁,逻辑集中 **Implementation**: ```typescript rightKey.registerHandler((_e) => { const selected = this.getSelectedComponent(); const items: MenuItemConfig[] = []; if (selected) { items.push({ id: 'componentDetail', ... }); } items.push({ id: 'showAll', ... }); return items; }); ``` **Consequences**: - ✅ 代码简洁,逻辑集中 - ✅ 无需手动同步菜单状态 - ✅ 易于扩展(添加更多条件判断) - ⚠️ 每次右键都重新生成(但开销可忽略) --- ### Decision 3: ComponentDetailManager 不注册为 BaseDialogManager **Context**: - ComponentDetailManager 需要创建对话框 - BaseDialogManager 提供了对话框生命周期管理 - 现有的 MeasureDialogManager、SectionPlaneDialogManager 等都继承 BaseDialogManager **Options Considered**: 1. 继承 BaseDialogManager(与现有 Manager 一致) 2. 不继承,直接使用 DialogManager API **Decision**: 选择 Option 2 - 不继承 BaseDialogManager **Rationale**: - ComponentDetailManager 的对话框逻辑简单,无需复杂生命周期管理 - 无需 Panel 组件(直接使用 Collapse + Description) - 避免继承带来的不必要复杂性 - 直接调用 DialogManager.create() 更灵活 **Implementation**: ```typescript export class ComponentDetailManager { private dialog: BimDialog | null = null; public show(modelUrl: string, componentId: string): void { this.createDialog(); // ... } private createDialog(): void { const registry = ManagerRegistry.getInstance(); this.dialog = registry.dialog?.create({ ... }); } } ``` **Consequences**: - ✅ 代码更简洁(119 行 vs 预计 200+ 行) - ✅ 职责明确:Manager 只负责数据获取和展示 - ⚠️ 与现有 Manager 不一致(但合理,因需求不同) --- ### Decision 4: 属性数据在 Manager 层转换而非 UI 组件层 **Context**: - 底层 API 返回格式: `{ properties: [{ name, children: [...] }] }` - BimCollapse 需要格式: `{ items: [{ categoryName, items: [...] }] }` **Options Considered**: 1. ComponentDetailManager 转换数据后传给 UI 2. 直接传原始数据,UI 组件自己转换 3. 创建 Adapter 类专门处理转换 **Decision**: 选择 Option 1 - Manager 层转换 **Rationale**: - Manager 职责包括数据适配 - UI 组件保持纯粹(只负责展示) - 转换逻辑集中,易于维护 - 无需额外的 Adapter 类(简单转换) **Implementation**: ```typescript private renderProperties(data: any): void { const categories = data.properties.map((cat: any) => ({ categoryName: cat.name, items: cat.children.map((child: any) => ({ key: child.name, value: child.value })) })); const collapse = new BimCollapse({ items: categories, ... }); } ``` **Consequences**: - ✅ UI 组件可复用性更强 - ✅ 数据转换逻辑集中 - ⚠️ Manager 职责略有增加(但合理) --- ### Decision 5: "显示全部"功能暂时只打印日志 **Context**: - 右键菜单需要"显示全部"选项 - 底层 API 尚未明确(可能是 showAllComponents、resetVisibility 等) **Options Considered**: 1. 实现完整功能(调用底层 API) 2. 暂时只打印日志,等 API 明确后实现 3. 跳过此功能 **Decision**: 选择 Option 2 - 暂时只打印日志 **Rationale**: - 不阻塞主功能(构件详情) - 底层 API 不明确,避免错误实现 - 菜单结构已就位,后续补充实现即可 - 符合 MVP 原则 **Implementation**: ```typescript public showAllComponents(): void { console.log('[EngineManager] 显示全部'); // TODO: 调用底层 API 显示所有构件 } ``` **Consequences**: - ✅ 不阻塞主功能开发 - ✅ 菜单结构完整 - ⚠️ 用户点击后无实际效果(需后续补充) --- ### Decision 6: 文档重构为多章节而非单独创建新文档 **Context**: - 现有 TOOLBAR_API_CALLCHAIN.md 只记录 Toolbar 调用链 - 新增右键菜单和构件交互功能 - 未来可能还有更多功能模块 **Options Considered**: 1. 创建独立文档 RIGHTKEY_API_CALLCHAIN.md 2. 重构现有文档为多章节结构 3. 合并到 README 或其他文档 **Decision**: 选择 Option 2 - 重构为多章节 API_CALLCHAIN.md **Rationale**: - 统一的调用链文档便于查阅 - 支持未来扩展(第四章、第五章...) - 避免文档碎片化 - 保持现有内容(第一章),降低风险 **Structure**: ``` # BIM Engine SDK - API 调用链文档 ## 第一章:工具栏 (Toolbar) - 首页、框选放大、测量、剖切... ## 第二章:右键菜单 (Context Menu) - 构件详情、显示全部、信息、首页 ## 第三章:构件交互 (Component Interaction) - 构件选中、取消选中 ``` **Consequences**: - ✅ 文档结构更清晰 - ✅ 易于扩展 - ✅ 避免重复内容(Info、Home 在多处复用) - ⚠️ 文件变大(734 → 1232 行) --- ### Decision 7: BimEngine 不自动初始化 ComponentDetailManager **Context**: - 现有 Manager(Measure、SectionPlane 等)都有独立的 init 方法 - ComponentDetailManager 是新增功能 - 需要决定初始化时机 **Options Considered**: 1. 在 BimEngine.init() 中自动初始化 2. 提供 initComponentDetail() 方法,由用户选择是否初始化 3. 延迟初始化(首次使用时) **Decision**: 选择 Option 1(实际实现)- 在 BimEngine.init() 中自动初始化 **Rationale**: - 构件详情是核心功能,大部分项目都需要 - 与现有 Manager 初始化方式保持一致 - 避免用户忘记初始化导致功能不可用 - 初始化成本低(只是实例化) **Implementation**: ```typescript // src/bim-engine.ts private init() { // ... this.componentDetail = new ComponentDetailManager(); this.registry.componentDetail = this.componentDetail; } ``` **Consequences**: - ✅ 开箱即用 - ✅ 与现有 Manager 一致 - ⚠️ 即使不使用也会初始化(但开销可忽略) **Note**: 实际实现中选择了自动初始化,与 Plan 中的"提供 initComponentDetail()"不同,但更符合现有架构。 --- ## Summary **Core Decisions**: 1. ✅ 状态存储在 Engine(数据源头) 2. ✅ 动态菜单生成(简洁高效) 3. ✅ 不继承 BaseDialogManager(需求简单) 4. ✅ Manager 层转换数据(职责明确) 5. ✅ "显示全部"暂时占位(不阻塞) 6. ✅ 文档多章节结构(统一管理) 7. ✅ 自动初始化(开箱即用) **Guiding Principles**: - **KISS**: Keep It Simple, Stupid - **YAGNI**: You Aren't Gonna Need It - **Single Responsibility**: 每个组件职责明确 - **Consistency**: 与现有代码保持一致(除非有充分理由)