# 构件详情右键菜单功能 ## TL;DR > **Quick Summary**: 实现右键菜单显示构件详情功能。用户选择构件后右键显示"构件详情"和"显示全部"菜单,点击构件详情弹出属性弹窗。 > > **Deliverables**: > - Engine 组件监听构件点击事件,记录选中构件信息 > - 修改右键菜单根据选中状态动态显示菜单项 > - 创建构件详情弹窗,调用底层 API 展示属性数据 > > **Estimated Effort**: Medium > **Parallel Execution**: NO - 顺序依赖 > **Critical Path**: Task 1 → Task 2 → Task 3 → Task 4 → Task 5 → Task 6 --- ## Context ### Original Request 用户选择构件后,右键显示"构件详情"按钮(有选中时)或"显示全部"按钮(无选中时)。点击构件详情查询数据并弹窗展示。 ### 底层 API 验证结果 #### 1. 构件属性查询 API ✅ 已验证 **文件**: `bim_engine_base/src/core/v2/managers/modelProperties/index.ts` **实例化**: `EngineKernelV2.modelProperties` ```typescript // 调用方式 engine.modelProperties.getModelProperties(url: string, id: string, callback: (data) => void) // 返回格式 { properties: [{ name: string, // 分类名称 children: [{ name: string, // 属性名 value: any // 属性值 }] }], materials: [] } ``` #### 2. 构件点击事件 ✅ 已验证 **文件**: `bim_engine_base/src/core/v2/modules/interactionModule.ts` ```typescript // 点击时触发 handleMouseClick(event) { const hit = event.catch; const model = hit.object; // model.url → 模型URL // model.name → 构件ID engine.events.trigger(EventType.Click, hit); } ``` **EventType.Click** 数据结构: ```typescript { point: Vector3, // 点击位置 object: { url: string, // 模型URL name: string // 构件ID } } ``` #### 3. 右键菜单 API ✅ 已验证 **文件**: `engine/src/managers/right-key-manager.ts` ```typescript // 注册处理器 rightKey.registerHandler((e: MouseEvent) => MenuItemConfig[] | null) // MenuItemConfig interface MenuItemConfig { id: string; label: string; onClick?: () => void; icon?: string; group?: string; disabled?: boolean; } ``` --- ## Work Objectives ### Core Objective 实现构件详情右键菜单功能,包括:选中状态追踪、动态菜单、属性弹窗展示。 ### Concrete Deliverables - `engine/src/components/engine/index.ts` - 新增选中状态管理和事件监听 - `engine/src/managers/engine-manager.ts` - 暴露选中状态和属性查询方法,修改右键处理器 - `engine/src/managers/component-detail-manager.ts` - 新建构件详情弹窗管理器 - `engine/src/bim-engine.ts` - 注册新管理器 - `engine/src/locales/*.ts` - 新增国际化文本 - `.sisyphus/drafts/API_CALLCHAIN.md` - 重构调用链文档(从 Toolbar 专用扩展为全局文档) ### Definition of Done - [ ] `bun run build` 构建成功 - [ ] 点击构件后,右键显示"构件详情"和"显示全部" - [ ] 未选中构件时,右键只显示"显示全部" - [ ] 点击"构件详情"弹出属性弹窗,展示底层 API 返回的数据 - [ ] 点击"显示全部"控制台输出提示 - [ ] 调用链文档已重构并更新 ### Must Have - 监听底层 Click 事件获取选中构件 - 根据选中状态动态返回菜单项 - 调用 `modelProperties.getModelProperties()` 获取属性 - 使用现有 Dialog + Collapse + Description 组件展示 ### Must NOT Have (Guardrails) - 不修改底层引擎代码(bim_engine_base) - 不实现"显示全部"的实际逻辑(只 console.log) - 不添加新的 npm 依赖 --- ## Verification Strategy ### Test Decision - **Infrastructure exists**: NO - **User wants tests**: Manual-only - **Framework**: none ### Manual QA 1. `bun run build` 无报错 2. 在 playground 中测试交互流程 --- ## Task Flow ``` Task 1 (Engine 监听点击,记录选中) ↓ Task 2 (EngineManager 暴露方法,修改右键处理器) ↓ Task 3 (创建 ComponentDetailManager) ↓ Task 4 (BimEngine 注册管理器) ↓ Task 5 (国际化 + 构建验证) ↓ Task 6 (重构调用链文档) ``` --- ## TODOs - [ ] 1. Engine 组件 - 监听构件点击,记录选中状态 **What to do**: 在 `engine/src/components/engine/index.ts` 中: 1. 新增私有属性存储选中构件信息: ```typescript private selectedComponent: { url: string; id: string } | null = null; ``` 2. 在 `init()` 方法中监听底层 Click 事件: ```typescript this.engine.events.on('click', (hit: any) => { if (hit && hit.object) { this.selectedComponent = { url: hit.object.url, id: hit.object.name }; console.log('[Engine] 构件选中:', this.selectedComponent); } else { this.selectedComponent = null; console.log('[Engine] 取消选中'); } }); ``` 3. 新增公共方法: ```typescript public getSelectedComponent(): { url: string; id: string } | null { return this.selectedComponent; } public getComponentProperties( url: string, id: string, callback: (data: any) => void ): void { if (!this.engine?.modelProperties) { console.error('[Engine] modelProperties not available'); return; } this.engine.modelProperties.getModelProperties(url, id, callback); } ``` **Must NOT do**: - 不要修改现有的其他功能 **Parallelizable**: NO (后续任务依赖) **References**: - `bim_engine_base/src/core/v2/modules/interactionModule.ts:36-53` - Click 事件触发逻辑 - `bim_engine_base/src/core/v2/managers/modelProperties/index.ts:11-52` - getModelProperties 实现 - `engine/src/components/engine/index.ts:108-131` - init() 方法位置 **Acceptance Criteria**: - [ ] 新增 `selectedComponent` 私有属性 - [ ] 在 init() 中监听 click 事件 - [ ] 新增 `getSelectedComponent()` 方法 - [ ] 新增 `getComponentProperties()` 方法 - [ ] 点击构件时控制台输出选中信息 **Commit**: YES - Message: `feat(engine): 监听构件点击事件并记录选中状态` - Files: `src/components/engine/index.ts` --- - [ ] 2. EngineManager - 暴露方法并修改右键处理器 **What to do**: 在 `engine/src/managers/engine-manager.ts` 中: 1. 新增代理方法: ```typescript public getSelectedComponent(): { url: string; id: string } | null { return this.engineInstance?.getSelectedComponent() ?? null; } public getComponentProperties( url: string, id: string, callback: (data: any) => void ): void { this.engineInstance?.getComponentProperties(url, id, callback); } ``` 2. 修改 `initialize()` 中的 `registerHandler`,根据选中状态返回不同菜单: ```typescript this.rightKey.registerHandler((_e) => { const selected = this.getSelectedComponent(); const items: MenuItemConfig[] = []; if (selected) { items.push({ id: 'componentDetail', label: 'menu.componentDetail', group: 'component', onClick: () => { const registry = ManagerRegistry.getInstance(); registry.componentDetail?.show(selected.url, selected.id); this.rightKey?.hide(); } }); } items.push({ id: 'showAll', label: 'menu.showAll', group: 'component', onClick: () => { console.log('[Menu] 显示全部 - 功能开发中'); this.rightKey?.hide(); } }); items.push(infoMenuButton()); items.push(homeMenuButton()); return items; }); ``` **Must NOT do**: - 不要删除现有的 infoMenuButton 和 homeMenuButton **Parallelizable**: NO (依赖 Task 1) **References**: - `engine/src/managers/engine-manager.ts:50-57` - 现有 registerHandler 位置 - `engine/src/components/menu/item.ts` - MenuItemConfig 定义 **Acceptance Criteria**: - [ ] 新增 `getSelectedComponent()` 代理方法 - [ ] 新增 `getComponentProperties()` 代理方法 - [ ] 修改 registerHandler 动态返回菜单项 - [ ] 有选中时显示"构件详情"+"显示全部" - [ ] 无选中时只显示"显示全部" **Commit**: YES - Message: `feat(engine-manager): 添加构件选中方法和动态右键菜单` - Files: `src/managers/engine-manager.ts` --- - [ ] 3. 创建 ComponentDetailManager - 构件详情弹窗 **What to do**: 创建 `engine/src/managers/component-detail-manager.ts`: ```typescript import { BaseManager } from '../core/base-manager'; import { BimCollapse } from '../components/collapse/index'; import { BimDescription } from '../components/description/index'; export class ComponentDetailManager extends BaseManager { private dialogId = 'component-detail-dialog'; private dialog: any = null; constructor() { super(); } public show(modelUrl: string, componentId: string): void { if (!this.registry.dialog) { console.warn('[ComponentDetailManager] Dialog manager not initialized'); return; } if (this.isOpen()) { this.hide(); } // 先创建弹窗显示加载中 this.createDialog(); this.showLoading(); // 调用底层 API 获取属性 this.registry.engine3d?.getComponentProperties(modelUrl, componentId, (data) => { this.renderProperties(data, componentId); }); } private createDialog(): void { const width = 400; const x = document.body.clientWidth - width - 40; this.dialog = this.registry.dialog.create({ id: this.dialogId, title: 'panel.componentDetail.title', content: '', width: `${width}px`, height: '500px', position: { x, y: 20 }, showMask: false, resizable: true, onClose: () => this.hide() }); } private showLoading(): void { const container = document.createElement('div'); container.style.padding = '20px'; container.style.textAlign = 'center'; container.textContent = '加载中...'; this.dialog?.setContent(container); } private renderProperties(data: any, componentId: string): void { if (!this.dialog) return; const container = document.createElement('div'); container.style.height = '100%'; container.style.overflowY = 'auto'; const properties = data?.properties || []; if (properties.length === 0) { container.innerHTML = '
无属性数据
'; this.dialog.setContent(container); return; } // 转换为 Collapse 需要的格式 const collapseItems = properties.map((category: any, index: number) => ({ id: `category-${index}`, title: category.name || `分类 ${index + 1}`, content: this.createCategoryContent(category.children || []) })); new BimCollapse({ container, accordion: false, activeIds: collapseItems.length > 0 ? [collapseItems[0].id] : [], items: collapseItems }); this.dialog.setContent(container); } private createCategoryContent(items: any[]): HTMLElement { const container = document.createElement('div'); const descItems = items.map((item: any) => ({ label: item.name || '-', value: String(item.value ?? '-') })); new BimDescription({ container, labelWidth: '120px', bordered: true, items: descItems }); return container; } public isOpen(): boolean { return this.dialog !== null; } public hide(): void { if (this.dialog) { this.dialog.destroy(); this.dialog = null; } } public destroy(): void { this.hide(); super.destroy(); } } ``` **Must NOT do**: - 不要创建新的 UI 组件,复用现有的 Collapse/Description **Parallelizable**: NO (依赖 Task 2) **References**: - `engine/src/managers/property-panel-manager.ts` - 参考现有属性面板实现 - `engine/src/components/collapse/index.ts` - Collapse 组件 - `engine/src/components/description/index.ts` - Description 组件 **Acceptance Criteria**: - [ ] 新建 `component-detail-manager.ts` 文件 - [ ] 实现 `show(modelUrl, componentId)` 方法 - [ ] 调用底层 API 获取属性数据 - [ ] 使用 Collapse + Description 展示属性 - [ ] 实现 `hide()` 和 `destroy()` 方法 **Commit**: YES - Message: `feat: 新增构件详情弹窗管理器` - Files: `src/managers/component-detail-manager.ts` --- - [ ] 4. BimEngine 和 Registry 注册管理器 **What to do**: 1. 修改 `engine/src/core/manager-registry.ts`,添加类型声明: ```typescript import type { ComponentDetailManager } from '../managers/component-detail-manager'; // 在 ManagerRegistry 类中添加 public componentDetail: ComponentDetailManager | null = null; ``` 2. 修改 `engine/src/bim-engine.ts`,添加初始化方法: ```typescript import { ComponentDetailManager } from './managers/component-detail-manager'; // 添加属性 public componentDetail: ComponentDetailManager | null = null; // 添加初始化方法 public initComponentDetail(): void { this.componentDetail = new ComponentDetailManager(); this.registry.componentDetail = this.componentDetail; } ``` **Must NOT do**: - 不要修改其他管理器的初始化逻辑 **Parallelizable**: NO (依赖 Task 3) **References**: - `engine/src/bim-engine.ts:106-108` - PropertyPanelManager 注册方式 - `engine/src/core/manager-registry.ts` - Registry 结构 **Acceptance Criteria**: - [ ] ManagerRegistry 添加 componentDetail 类型声明 - [ ] BimEngine 添加 componentDetail 属性 - [ ] BimEngine 添加 initComponentDetail() 方法 **Commit**: YES - Message: `feat(bim-engine): 注册构件详情管理器` - Files: `src/bim-engine.ts`, `src/core/manager-registry.ts` --- - [ ] 5. 国际化文本 + 构建验证 **What to do**: 1. 修改 `engine/src/locales/zh-CN.ts`,添加: ```typescript menu: { // 现有... componentDetail: '构件详情', showAll: '显示全部', }, panel: { // 现有... componentDetail: { title: '构件详情', }, }, ``` 2. 修改 `engine/src/locales/en-US.ts`,添加: ```typescript menu: { // 现有... componentDetail: 'Component Detail', showAll: 'Show All', }, panel: { // 现有... componentDetail: { title: 'Component Detail', }, }, ``` 3. 运行构建验证: ```bash bun run build ``` **Must NOT do**: - 不要修改现有的国际化文本 **Parallelizable**: NO (依赖前面所有任务) **References**: - `engine/src/locales/zh-CN.ts` - 中文国际化 - `engine/src/locales/en-US.ts` - 英文国际化 **Acceptance Criteria**: - [ ] zh-CN 添加 menu.componentDetail, menu.showAll, panel.componentDetail.title - [ ] en-US 添加对应英文文本 - [ ] `bun run build` 执行成功,无报错 **Commit**: YES - Message: `feat(i18n): 添加构件详情相关国际化文本` - Files: `src/locales/zh-CN.ts`, `src/locales/en-US.ts` --- - [ ] 6. 重构调用链文档 **What to do**: 1. 将 `.sisyphus/drafts/TOOLBAR_API_CALLCHAIN.md` 重命名为 `.sisyphus/drafts/API_CALLCHAIN.md` 2. 重构文档结构,从 Toolbar 专用扩展为全局 API 调用链文档: ```markdown # SDK API 调用链文档 本文档记录 SDK 中所有功能的完整调用链,从用户交互到底层 3D 引擎 API。 --- ## 目录 1. [Toolbar 工具栏](#1-toolbar-工具栏) 2. [右键菜单 (Context Menu)](#2-右键菜单-context-menu) 3. [构件交互 (Component Interaction)](#3-构件交互-component-interaction) --- ## 1. Toolbar 工具栏 ### 1.1 首页 (Home) [原有内容...] ### 1.2 框选放大 (Zoom Box) [原有内容...] ...(其他 toolbar 按钮) --- ## 2. 右键菜单 (Context Menu) ### 2.1 构件详情 (Component Detail) **触发条件**: 选中构件后右键 **功能**: 查询并展示构件属性 #### 调用链 ``` 用户点击构件 │ ▼ 底层 interactionModule.handleMouseClick() │ engine.events.trigger('click', hit) ▼ [SDK] Engine 监听 'click' 事件 │ 记录 selectedComponent = { url, id } ▼ 用户右键点击 │ ▼ [SDK] RightKeyManager.handleContextMenu() │ 调用所有 contextHandlers ▼ [SDK] EngineManager 的 handler │ 检查 getSelectedComponent() │ 返回 MenuItemConfig[] (含 "构件详情") ▼ 用户点击 "构件详情" │ ▼ [SDK] ComponentDetailManager.show(url, id) │ 调用 getComponentProperties(url, id, callback) ▼ [SDK] Engine.getComponentProperties() │ this.engine.modelProperties.getModelProperties(url, id, callback) ▼ [底层] ModelProperties.getModelProperties() │ 加载/解析属性数据 ▼ [SDK] ComponentDetailManager.renderProperties() │ 展示属性弹窗 ``` ### 2.2 显示全部 (Show All) **触发条件**: 右键(无论是否选中) **功能**: 显示全部构件(暂未实现) #### 调用链 ``` 用户右键点击 │ ▼ [SDK] RightKeyManager.handleContextMenu() │ ▼ [SDK] EngineManager 的 handler │ 返回 MenuItemConfig[] (含 "显示全部") ▼ 用户点击 "显示全部" │ ▼ console.log('显示全部 - 功能开发中') ``` ### 2.3 信息 (Info) [现有 infoMenuButton 的调用链] ### 2.4 首页 (Home) [现有 homeMenuButton 的调用链] --- ## 3. 构件交互 (Component Interaction) ### 3.1 构件选中 **触发条件**: 点击 3D 场景中的构件 **功能**: 高亮构件并记录选中状态 #### 调用链 ``` 用户点击 3D 场景 │ ▼ [底层] handelBehaved 监听 mouseup │ 射线检测 (raycaster) ▼ [底层] interactionModule.handleMouseClick(event) │ event.catch = 射线检测结果 │ engine.events.trigger('click', hit) │ engine.modelToolModule.highlightModel([{url, ids}]) ▼ [SDK] Engine 监听 'click' 事件 │ 记录 this.selectedComponent = { url: hit.object.url, id: hit.object.name } ``` ### 3.2 取消选中 **触发条件**: 点击空白区域 **功能**: 取消高亮并清除选中状态 #### 调用链 ``` 用户点击空白区域 │ ▼ [底层] interactionModule.handleMouseClick(event) │ event.catch = null │ engine.modelToolModule.unhighlightAllModels() ▼ [SDK] Engine 监听 'click' 事件 │ this.selectedComponent = null ``` ``` **Must NOT do**: - 不要删除现有 Toolbar 部分的内容 - 不要修改现有调用链的准确性 **Parallelizable**: NO (依赖 Task 5) **References**: - `.sisyphus/drafts/TOOLBAR_API_CALLCHAIN.md` - 现有文档 - `engine/src/managers/right-key-manager.ts` - 右键菜单实现 - `engine/src/managers/engine-manager.ts` - 右键处理器注册 **Acceptance Criteria**: - [ ] 文档重命名为 `API_CALLCHAIN.md` - [ ] 文档标题改为"SDK API 调用链文档" - [ ] 新增"右键菜单"章节,包含构件详情、显示全部、信息、首页 - [ ] 新增"构件交互"章节,包含选中/取消选中调用链 - [ ] 原 Toolbar 内容保持不变,作为第一章 **Commit**: YES - Message: `docs: 重构调用链文档,新增右键菜单和构件交互章节` - Files: `.sisyphus/drafts/API_CALLCHAIN.md` --- ## Commit Strategy | After Task | Message | Files | |------------|---------|-------| | 1 | `feat(engine): 监听构件点击事件并记录选中状态` | src/components/engine/index.ts | | 2 | `feat(engine-manager): 添加构件选中方法和动态右键菜单` | src/managers/engine-manager.ts | | 3 | `feat: 新增构件详情弹窗管理器` | src/managers/component-detail-manager.ts | | 4 | `feat(bim-engine): 注册构件详情管理器` | src/bim-engine.ts, src/core/manager-registry.ts | | 5 | `feat(i18n): 添加构件详情相关国际化文本` | src/locales/*.ts | | 6 | `docs: 重构调用链文档,新增右键菜单和构件交互章节` | .sisyphus/drafts/API_CALLCHAIN.md | --- ## Success Criteria ### Verification Commands ```bash bun run build # Expected: BUILD SUCCESS ``` ### Final Checklist - [ ] 点击构件后,控制台输出选中信息 - [ ] 有选中构件时,右键显示"构件详情"+"显示全部" - [ ] 无选中构件时,右键只显示"显示全部" - [ ] 点击"构件详情"弹出属性弹窗 - [ ] 弹窗正确展示底层 API 返回的属性数据 - [ ] 点击"显示全部"控制台输出提示 - [ ] 构建成功 - [ ] 调用链文档已重构为全局文档 - [ ] 新增右键菜单章节 - [ ] 新增构件交互章节