Update all three section dialogs to support hide/show toggle: SectionAxisDialogManager: - onHideToggle now calls hideSection()/recoverSection() SectionBoxDialogManager: - onHideToggle now calls hideSection()/recoverSection() SectionPlanePanel: - Add isHidden state tracking - Change onHide to onHideToggle(isHidden) - Add setHiddenState/getHiddenState methods - Update button to toggle active state SectionPlaneDialogManager: - Switch to onHideToggle callback - Call hideSection()/recoverSection() based on toggle state Behavior: Click hide button to hide section, click again to recover.
12 KiB
Learnings: 构件详情右键菜单功能
2026-01-28 - Implementation Complete
Architecture Patterns
1. Event-Driven Component Selection
Pattern: 底层引擎触发事件 → SDK 监听并维护状态
// SDK 层监听底层事件
this.engine.events.on('click', (hit: any) => {
if (hit && hit.object) {
this.selectedComponent = { url: hit.object.url, id: hit.object.name };
} else {
this.selectedComponent = null;
}
});
Key Insight: SDK 不需要主动查询状态,而是被动监听底层事件,降低耦合度。
2. Dynamic Menu Generation
Pattern: 注册处理器返回函数,运行时动态生成菜单
// 注册时传入函数,而非静态配置
rightKey.registerHandler((_e) => {
const selected = this.getSelectedComponent();
const items: MenuItemConfig[] = [];
// 根据当前状态动态构建菜单
if (selected) {
items.push({ /* 构件详情 */ });
}
items.push({ /* 显示全部 */ });
return items;
});
Key Insight: 右键菜单内容不是静态的,而是根据运行时状态(选中/未选中)动态生成,提供上下文感知的用户体验。
3. Manager Registry Pattern
Pattern: 单例注册表 + 代理方法
// Registry 存储所有 Manager 实例
registry.engine3d = this.engine;
registry.componentDetail = this.componentDetail;
// 其他 Manager 通过 Registry 访问
const selected = registry.engine3d?.getSelectedComponent();
registry.componentDetail?.show(selected.url, selected.id);
Key Insight:
- 避免 Manager 之间直接引用,降低耦合
- 统一访问入口,便于管理和调试
- 支持延迟初始化(nullable 类型)
4. Data Transformation Layer
Pattern: 底层数据 → SDK 转换 → UI 展示
// 底层返回格式
{
properties: [{
name: "分类名",
children: [{ name: "属性名", value: "值" }]
}]
}
// SDK 转换为 UI 组件格式
const categories = data.properties.map(cat => ({
categoryName: cat.name,
items: cat.children.map(child => ({
key: child.name,
value: child.value
}))
}));
// UI 组件渲染
BimCollapse({ items: categories })
→ BimDescription({ items: category.items })
Key Insight: SDK 作为中间层,负责数据格式转换,使 UI 组件专注于展示逻辑。
Code Conventions
1. Console Logging
// 中文日志 + Manager 前缀
console.log('[Engine] 构件选中:', this.selectedComponent);
console.log('[ComponentDetailManager] 显示构件详情');
console.error('[EngineManager] Engine 尚未初始化');
Reason:
- 中文便于业务人员理解
- 前缀便于定位代码位置
- 区分 log/warn/error 级别
2. Section Markers
// ==================== 选中状态管理 ====================
private selectedComponent: { url: string; id: string } | null = null;
public getSelectedComponent(): { url: string; id: string } | null {
return this.selectedComponent;
}
Reason: 大文件中用分隔符标记功能区域,提升可读性。
3. JSDoc Comments
/**
* 获取当前选中的构件信息
* @returns 选中的构件信息,包含 url 和 id;未选中时返回 null
*/
public getSelectedComponent(): { url: string; id: string } | null {
return this.selectedComponent;
}
Reason:
- 只对 public 方法添加 JSDoc
- 描述功能、参数、返回值
- 符合现有代码风格
TypeScript Patterns
1. Optional Chaining + Nullish Coalescing
// 安全访问 + 默认值
return this.engineInstance?.getSelectedComponent() ?? null;
// 条件调用
registry.componentDetail?.show(url, id);
Reason:
- Manager 可能未初始化
- 优雅处理 null/undefined
2. Type-Safe Event Callbacks
// 明确回调参数类型
public getComponentProperties(
url: string,
id: string,
callback: (data: any) => void // 底层未定义类型,先用 any
): void {
this.engine.modelProperties.getModelProperties(url, id, callback);
}
Trade-off: 底层 API 未提供 TypeScript 类型定义,暂用 any,后续可补充。
i18n Patterns
1. Structured Translation Keys
// 按功能分组
menu: {
componentDetail: '构件详情',
showAll: '显示全部'
}
panel: {
componentDetail: {
title: '构件详情'
}
}
Reason:
- 层级结构便于管理
- 避免命名冲突
- 易于扩展
2. Type-Safe Translation
// 先定义类型
interface TranslationDictionary {
menu: {
componentDetail: string;
showAll: string;
};
}
// 再实现翻译
export const zhCN: TranslationDictionary = { ... };
export const enUS: TranslationDictionary = { ... };
Reason: TypeScript 编译时检查,防止遗漏翻译。
Documentation Patterns
1. Multi-Chapter Structure
第一章:工具栏 (Toolbar)
第二章:右键菜单 (Context Menu)
第三章:构件交互 (Component Interaction)
Reason:
- 从功能维度到文档维度的升级
- 支持未来扩展(第四章、第五章...)
- 保持现有内容不变,降低风险
2. Call Chain Visualization
用户点击构件
↓
[底层] interactionModule.handleMouseClick()
↓
[SDK] Engine: click event listener
↓
[SDK] EngineManager: getSelectedComponent()
↓
[UI] RightKeyManager: dynamic menu
Reason:
- 清晰展示调用层级
- 区分底层/SDK/UI 边界
- 便于新人理解架构
Challenges & Solutions
Challenge 1: 右键菜单如何知道是否有选中构件?
Solution:
- Engine 组件监听底层 click 事件,维护
selectedComponent状态 - EngineManager 暴露
getSelectedComponent()方法 - RightKeyManager 的处理器函数每次运行时调用此方法
Key Decision: 状态存储在 Engine,而非 EngineManager,因为 Engine 直接监听底层事件。
Challenge 2: 如何避免循环依赖?
Problem:
- ComponentDetailManager 需要调用 EngineManager
- EngineManager 的菜单需要调用 ComponentDetailManager
Solution:
- 使用 ManagerRegistry 单例作为中介
- 两者都不直接引用对方,而是通过 Registry 访问
// ComponentDetailManager
const registry = ManagerRegistry.getInstance();
registry.engine3d?.getComponentProperties(...);
// EngineManager
const registry = ManagerRegistry.getInstance();
registry.componentDetail?.show(...);
Challenge 3: 底层 API 数据格式与 UI 组件格式不一致
Problem:
- 底层返回:
properties: [{ name, children: [{ name, value }] }] - UI 需要:
items: [{ categoryName, items: [{ key, value }] }]
Solution:
- ComponentDetailManager 中进行数据转换
- 职责明确:Manager 负责数据适配,Component 负责展示
const categories = data.properties.map(cat => ({
categoryName: cat.name,
items: cat.children.map(child => ({
key: child.name,
value: child.value
}))
}));
Build & Verification
Build Command
bun run build
Output:
✓ 87 modules transformed
dist/iflow-engine.es.js 2,025.42 kB
dist/iflow-engine.umd.js 1,329.90 kB
✓ built in 4.98s
Verification Checklist:
- TypeScript 编译无错误
- 项目级 LSP diagnostics 无错误
- 模块打包成功(ESM + UMD)
- 文件大小在合理范围
Git Workflow
Commit Strategy: 每个任务独立提交
cf20389- Engine 监听点击事件e75886d- EngineManager 动态菜单33f1c72- ComponentDetailManager 实现89789e0- Registry 注册a61c7f4- i18n 国际化
Benefits:
- 每个 commit 职责单一
- 便于 code review
- 支持 cherry-pick / revert
- 清晰的历史记录
Reusable Patterns for Future Work
Pattern: Dynamic UI Based on Runtime State
When to use:
- Toolbar button visibility
- Menu item enable/disable
- Panel content switching
Template:
// 1. State storage
private currentState: State | null = null;
// 2. State getter
public getState(): State | null {
return this.currentState;
}
// 3. Dynamic generator
const handler = () => {
const state = this.getState();
const items = [];
if (state?.condition) {
items.push({ /* conditional item */ });
}
items.push({ /* always visible item */ });
return items;
};
Pattern: Manager Communication via Registry
When to use:
- Manager A needs to call Manager B
- Avoid circular dependencies
Template:
// In Manager A
const registry = ManagerRegistry.getInstance();
registry.managerB?.doSomething();
// In Manager B
const registry = ManagerRegistry.getInstance();
registry.managerA?.getSomeData();
Checklist:
- Update ManagerRegistry type definition
- Register manager instance in BimEngine.init()
- Use optional chaining (
?.) for all registry accesses
Pattern: Bottom-Up Data Flow (Event-Driven)
When to use:
- 底层引擎有原生事件系统
- SDK 需要响应底层变化
Template:
// 1. SDK 监听底层事件
this.engine.events.on('eventName', (payload) => {
// 2. 更新 SDK 状态
this.updateState(payload);
// 3. 可选:触发 SDK 层事件
registry.emit('sdk:eventName', transformedPayload);
});
Gotchas
Gotcha 1: Registry 访问时机
Problem: 在 Manager constructor 中访问 registry 可能为空
Solution: 在 init() 方法或延迟访问点使用 registry
// ❌ Bad
constructor() {
const registry = ManagerRegistry.getInstance();
this.engine = registry.engine3d; // 可能为 null
}
// ✅ Good
public show() {
const registry = ManagerRegistry.getInstance();
registry.engine3d?.doSomething(); // 使用时访问
}
Gotcha 2: 国际化类型定义先行
Problem: 直接在 zh-CN.ts 添加翻译会导致 TypeScript 错误 Solution: 先更新 types.ts,再更新 zh-CN.ts 和 en-US.ts
Order:
src/locales/types.ts- 添加类型定义src/locales/zh-CN.ts- 添加中文翻译src/locales/en-US.ts- 添加英文翻译
Gotcha 3: 文档 Read-Before-Write
Problem: Edit_tool 要求先 Read 文件才能编辑 Solution: 即使之前读过,每次 Edit 前也要 Read 一次
// ✅ Correct workflow
1. Read_tool(file)
2. Edit_tool(file, oldString, newString)
3. Read_tool(file) // Next edit
4. Edit_tool(file, ...)
Metrics
- Files Modified: 9
- New File Created: 1 (component-detail-manager.ts)
- Lines Added: ~500
- Commits: 5
- Build Time: ~5s
- Implementation Time: ~2 hours
- Documentation Lines: +498 (API_CALLCHAIN.md)
Future Improvements
1. Type Definitions for Bottom-Layer API
Current: callback: (data: any) => void
Future:
interface ComponentProperty {
name: string;
children: Array<{ name: string; value: string | number }>;
}
interface PropertyData {
properties: ComponentProperty[];
materials: any[];
}
callback: (data: PropertyData) => void
2. Loading State Optimization
Current: Simple "加载中..." text Future:
- Skeleton loading UI
- Progress indicator
- Error handling with retry
3. "显示全部" Implementation
Current: console.log('[EngineManager] 显示全部')
Future: 调用底层 API 显示所有隐藏构件
4. Property Panel Cache
Current: 每次打开都重新请求 Future:
- Cache recently viewed properties
- Invalidate on model change
- Reduce API calls
Success Criteria - All Met ✅
- 点击构件后,控制台输出选中信息
- 有选中构件时,右键显示"构件详情"+"显示全部"
- 无选中构件时,右键只显示"显示全部"
- 点击"构件详情"弹出属性弹窗
- 弹窗正确展示底层 API 返回的属性数据
- 点击"显示全部"控制台输出提示
- 构建成功
- 调用链文档已重构为全局文档
- 新增右键菜单章节
- 新增构件交互章节