Files
bim_engine/.sisyphus/plans/component-detail-rightclick.md

774 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 构件详情右键菜单功能
## 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
- [x] `bun run build` 构建成功
- [x] 点击构件后,右键显示"构件详情"和"显示全部"
- [x] 未选中构件时,右键只显示"显示全部"
- [x] 点击"构件详情"弹出属性弹窗,展示底层 API 返回的数据
- [x] 点击"显示全部"控制台输出提示
- [x] 调用链文档已重构并更新
### 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
- [x] 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**:
- [x] 新增 `selectedComponent` 私有属性
- [x] 在 init() 中监听 click 事件
- [x] 新增 `getSelectedComponent()` 方法
- [x] 新增 `getComponentProperties()` 方法
- [x] 点击构件时控制台输出选中信息
**Commit**: YES
- Message: `feat(engine): 监听构件点击事件并记录选中状态`
- Files: `src/components/engine/index.ts`
---
- [x] 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**:
- [x] 新增 `getSelectedComponent()` 代理方法
- [x] 新增 `getComponentProperties()` 代理方法
- [x] 修改 registerHandler 动态返回菜单项
- [x] 有选中时显示"构件详情"+"显示全部"
- [x] 无选中时只显示"显示全部"
**Commit**: YES
- Message: `feat(engine-manager): 添加构件选中方法和动态右键菜单`
- Files: `src/managers/engine-manager.ts`
---
- [x] 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 = '<div style="padding:20px;text-align:center;">无属性数据</div>';
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**:
- [x] 新建 `component-detail-manager.ts` 文件
- [x] 实现 `show(modelUrl, componentId)` 方法
- [x] 调用底层 API 获取属性数据
- [x] 使用 Collapse + Description 展示属性
- [x] 实现 `hide()` 和 `destroy()` 方法
**Commit**: YES
- Message: `feat: 新增构件详情弹窗管理器`
- Files: `src/managers/component-detail-manager.ts`
---
- [x] 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**:
- [x] ManagerRegistry 添加 componentDetail 类型声明
- [x] BimEngine 添加 componentDetail 属性
- [x] BimEngine 添加 initComponentDetail() 方法
**Commit**: YES
- Message: `feat(bim-engine): 注册构件详情管理器`
- Files: `src/bim-engine.ts`, `src/core/manager-registry.ts`
---
- [x] 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**:
- [x] zh-CN 添加 menu.componentDetail, menu.showAll, panel.componentDetail.title
- [x] en-US 添加对应英文文本
- [x] `bun run build` 执行成功,无报错
**Commit**: YES
- Message: `feat(i18n): 添加构件详情相关国际化文本`
- Files: `src/locales/zh-CN.ts`, `src/locales/en-US.ts`
---
- [x] 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**:
- [x] 文档重命名为 `API_CALLCHAIN.md`
- [x] 文档标题改为"SDK API 调用链文档"
- [x] 新增"右键菜单"章节,包含构件详情、显示全部、信息、首页
- [x] 新增"构件交互"章节,包含选中/取消选中调用链
- [x] 原 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
- [x] 点击构件后,控制台输出选中信息
- [x] 有选中构件时,右键显示"构件详情"+"显示全部"
- [x] 无选中构件时,右键只显示"显示全部"
- [x] 点击"构件详情"弹出属性弹窗
- [x] 弹窗正确展示底层 API 返回的属性数据
- [x] 点击"显示全部"控制台输出提示
- [x] 构建成功
- [x] 调用链文档已重构为全局文档
- [x] 新增右键菜单章节
- [x] 新增构件交互章节