Files

288 lines
8.0 KiB
Markdown
Raw Permalink Normal View History

# 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**:
- 现有 ManagerMeasure、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**: 与现有代码保持一致(除非有充分理由)