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

21 KiB
Raw Blame History

构件详情右键菜单功能

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

// 调用方式
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

// 点击时触发
handleMouseClick(event) {
  const hit = event.catch;
  const model = hit.object;
  // model.url  → 模型URL
  // model.name → 构件ID
  engine.events.trigger(EventType.Click, hit);
}

EventType.Click 数据结构:

{
  point: Vector3,      // 点击位置
  object: {
    url: string,       // 模型URL
    name: string       // 构件ID
  }
}

3. 右键菜单 API 已验证

文件: engine/src/managers/right-key-manager.ts

// 注册处理器
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. 新增私有属性存储选中构件信息:
    private selectedComponent: { url: string; id: string } | null = null;
    
    1. init() 方法中监听底层 Click 事件:
    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] 取消选中');
      }
    });
    
    1. 新增公共方法:
    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. 新增代理方法:
    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);
    }
    
    1. 修改 initialize() 中的 registerHandler,根据选中状态返回不同菜单:
    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

    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:

    • 新建 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,添加类型声明:
    import type { ComponentDetailManager } from '../managers/component-detail-manager';
    
    // 在 ManagerRegistry 类中添加
    public componentDetail: ComponentDetailManager | null = null;
    
    1. 修改 engine/src/bim-engine.ts,添加初始化方法:
    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,添加:
    menu: {
      // 现有...
      componentDetail: '构件详情',
      showAll: '显示全部',
    },
    panel: {
      // 现有...
      componentDetail: {
        title: '构件详情',
      },
    },
    
    1. 修改 engine/src/locales/en-US.ts,添加:
    menu: {
      // 现有...
      componentDetail: 'Component Detail',
      showAll: 'Show All',
    },
    panel: {
      // 现有...
      componentDetail: {
        title: 'Component Detail',
      },
    },
    
    1. 运行构建验证:
    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 调用链文档:

    # 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

bun run build  # Expected: BUILD SUCCESS

Final Checklist

  • 点击构件后,控制台输出选中信息
  • 有选中构件时,右键显示"构件详情"+"显示全部"
  • 无选中构件时,右键只显示"显示全部"
  • 点击"构件详情"弹出属性弹窗
  • 弹窗正确展示底层 API 返回的属性数据
  • 点击"显示全部"控制台输出提示
  • 构建成功
  • 调用链文档已重构为全局文档
  • 新增右键菜单章节
  • 新增构件交互章节