feat(tree,menu,docs): 对接目录树三类数据并中文化文档
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"active_plan": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/.sisyphus/plans/component-detail-rightclick.md",
|
"active_plan": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/.sisyphus/plans/component-detail-bugfix.md",
|
||||||
"started_at": "2026-01-28T03:53:31.469Z",
|
"started_at": "2026-01-28T07:42:20.635Z",
|
||||||
"session_ids": [
|
"session_ids": [
|
||||||
"ses_3fd75ccc4ffe13KZZk467OXNg6"
|
"ses_3fd75ccc4ffe13KZZk467OXNg6"
|
||||||
],
|
],
|
||||||
"plan_name": "component-detail-rightclick"
|
"plan_name": "component-detail-bugfix"
|
||||||
}
|
}
|
||||||
@@ -102,12 +102,12 @@ interface MenuItemConfig {
|
|||||||
- `.sisyphus/drafts/API_CALLCHAIN.md` - 重构调用链文档(从 Toolbar 专用扩展为全局文档)
|
- `.sisyphus/drafts/API_CALLCHAIN.md` - 重构调用链文档(从 Toolbar 专用扩展为全局文档)
|
||||||
|
|
||||||
### Definition of Done
|
### Definition of Done
|
||||||
- [ ] `bun run build` 构建成功
|
- [x] `bun run build` 构建成功
|
||||||
- [ ] 点击构件后,右键显示"构件详情"和"显示全部"
|
- [x] 点击构件后,右键显示"构件详情"和"显示全部"
|
||||||
- [ ] 未选中构件时,右键只显示"显示全部"
|
- [x] 未选中构件时,右键只显示"显示全部"
|
||||||
- [ ] 点击"构件详情"弹出属性弹窗,展示底层 API 返回的数据
|
- [x] 点击"构件详情"弹出属性弹窗,展示底层 API 返回的数据
|
||||||
- [ ] 点击"显示全部"控制台输出提示
|
- [x] 点击"显示全部"控制台输出提示
|
||||||
- [ ] 调用链文档已重构并更新
|
- [x] 调用链文档已重构并更新
|
||||||
|
|
||||||
### Must Have
|
### Must Have
|
||||||
- 监听底层 Click 事件获取选中构件
|
- 监听底层 Click 事件获取选中构件
|
||||||
@@ -155,7 +155,7 @@ Task 6 (重构调用链文档)
|
|||||||
|
|
||||||
## TODOs
|
## TODOs
|
||||||
|
|
||||||
- [ ] 1. Engine 组件 - 监听构件点击,记录选中状态
|
- [x] 1. Engine 组件 - 监听构件点击,记录选中状态
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
在 `engine/src/components/engine/index.ts` 中:
|
在 `engine/src/components/engine/index.ts` 中:
|
||||||
@@ -211,11 +211,11 @@ Task 6 (重构调用链文档)
|
|||||||
- `engine/src/components/engine/index.ts:108-131` - init() 方法位置
|
- `engine/src/components/engine/index.ts:108-131` - init() 方法位置
|
||||||
|
|
||||||
**Acceptance Criteria**:
|
**Acceptance Criteria**:
|
||||||
- [ ] 新增 `selectedComponent` 私有属性
|
- [x] 新增 `selectedComponent` 私有属性
|
||||||
- [ ] 在 init() 中监听 click 事件
|
- [x] 在 init() 中监听 click 事件
|
||||||
- [ ] 新增 `getSelectedComponent()` 方法
|
- [x] 新增 `getSelectedComponent()` 方法
|
||||||
- [ ] 新增 `getComponentProperties()` 方法
|
- [x] 新增 `getComponentProperties()` 方法
|
||||||
- [ ] 点击构件时控制台输出选中信息
|
- [x] 点击构件时控制台输出选中信息
|
||||||
|
|
||||||
**Commit**: YES
|
**Commit**: YES
|
||||||
- Message: `feat(engine): 监听构件点击事件并记录选中状态`
|
- Message: `feat(engine): 监听构件点击事件并记录选中状态`
|
||||||
@@ -223,7 +223,7 @@ Task 6 (重构调用链文档)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 2. EngineManager - 暴露方法并修改右键处理器
|
- [x] 2. EngineManager - 暴露方法并修改右键处理器
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
在 `engine/src/managers/engine-manager.ts` 中:
|
在 `engine/src/managers/engine-manager.ts` 中:
|
||||||
@@ -289,11 +289,11 @@ Task 6 (重构调用链文档)
|
|||||||
- `engine/src/components/menu/item.ts` - MenuItemConfig 定义
|
- `engine/src/components/menu/item.ts` - MenuItemConfig 定义
|
||||||
|
|
||||||
**Acceptance Criteria**:
|
**Acceptance Criteria**:
|
||||||
- [ ] 新增 `getSelectedComponent()` 代理方法
|
- [x] 新增 `getSelectedComponent()` 代理方法
|
||||||
- [ ] 新增 `getComponentProperties()` 代理方法
|
- [x] 新增 `getComponentProperties()` 代理方法
|
||||||
- [ ] 修改 registerHandler 动态返回菜单项
|
- [x] 修改 registerHandler 动态返回菜单项
|
||||||
- [ ] 有选中时显示"构件详情"+"显示全部"
|
- [x] 有选中时显示"构件详情"+"显示全部"
|
||||||
- [ ] 无选中时只显示"显示全部"
|
- [x] 无选中时只显示"显示全部"
|
||||||
|
|
||||||
**Commit**: YES
|
**Commit**: YES
|
||||||
- Message: `feat(engine-manager): 添加构件选中方法和动态右键菜单`
|
- Message: `feat(engine-manager): 添加构件选中方法和动态右键菜单`
|
||||||
@@ -301,7 +301,7 @@ Task 6 (重构调用链文档)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 3. 创建 ComponentDetailManager - 构件详情弹窗
|
- [x] 3. 创建 ComponentDetailManager - 构件详情弹窗
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
创建 `engine/src/managers/component-detail-manager.ts`:
|
创建 `engine/src/managers/component-detail-manager.ts`:
|
||||||
@@ -443,11 +443,11 @@ Task 6 (重构调用链文档)
|
|||||||
- `engine/src/components/description/index.ts` - Description 组件
|
- `engine/src/components/description/index.ts` - Description 组件
|
||||||
|
|
||||||
**Acceptance Criteria**:
|
**Acceptance Criteria**:
|
||||||
- [ ] 新建 `component-detail-manager.ts` 文件
|
- [x] 新建 `component-detail-manager.ts` 文件
|
||||||
- [ ] 实现 `show(modelUrl, componentId)` 方法
|
- [x] 实现 `show(modelUrl, componentId)` 方法
|
||||||
- [ ] 调用底层 API 获取属性数据
|
- [x] 调用底层 API 获取属性数据
|
||||||
- [ ] 使用 Collapse + Description 展示属性
|
- [x] 使用 Collapse + Description 展示属性
|
||||||
- [ ] 实现 `hide()` 和 `destroy()` 方法
|
- [x] 实现 `hide()` 和 `destroy()` 方法
|
||||||
|
|
||||||
**Commit**: YES
|
**Commit**: YES
|
||||||
- Message: `feat: 新增构件详情弹窗管理器`
|
- Message: `feat: 新增构件详情弹窗管理器`
|
||||||
@@ -455,7 +455,7 @@ Task 6 (重构调用链文档)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 4. BimEngine 和 Registry 注册管理器
|
- [x] 4. BimEngine 和 Registry 注册管理器
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
|
|
||||||
@@ -491,9 +491,9 @@ Task 6 (重构调用链文档)
|
|||||||
- `engine/src/core/manager-registry.ts` - Registry 结构
|
- `engine/src/core/manager-registry.ts` - Registry 结构
|
||||||
|
|
||||||
**Acceptance Criteria**:
|
**Acceptance Criteria**:
|
||||||
- [ ] ManagerRegistry 添加 componentDetail 类型声明
|
- [x] ManagerRegistry 添加 componentDetail 类型声明
|
||||||
- [ ] BimEngine 添加 componentDetail 属性
|
- [x] BimEngine 添加 componentDetail 属性
|
||||||
- [ ] BimEngine 添加 initComponentDetail() 方法
|
- [x] BimEngine 添加 initComponentDetail() 方法
|
||||||
|
|
||||||
**Commit**: YES
|
**Commit**: YES
|
||||||
- Message: `feat(bim-engine): 注册构件详情管理器`
|
- Message: `feat(bim-engine): 注册构件详情管理器`
|
||||||
@@ -501,7 +501,7 @@ Task 6 (重构调用链文档)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 5. 国际化文本 + 构建验证
|
- [x] 5. 国际化文本 + 构建验证
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
|
|
||||||
@@ -550,9 +550,9 @@ Task 6 (重构调用链文档)
|
|||||||
- `engine/src/locales/en-US.ts` - 英文国际化
|
- `engine/src/locales/en-US.ts` - 英文国际化
|
||||||
|
|
||||||
**Acceptance Criteria**:
|
**Acceptance Criteria**:
|
||||||
- [ ] zh-CN 添加 menu.componentDetail, menu.showAll, panel.componentDetail.title
|
- [x] zh-CN 添加 menu.componentDetail, menu.showAll, panel.componentDetail.title
|
||||||
- [ ] en-US 添加对应英文文本
|
- [x] en-US 添加对应英文文本
|
||||||
- [ ] `bun run build` 执行成功,无报错
|
- [x] `bun run build` 执行成功,无报错
|
||||||
|
|
||||||
**Commit**: YES
|
**Commit**: YES
|
||||||
- Message: `feat(i18n): 添加构件详情相关国际化文本`
|
- Message: `feat(i18n): 添加构件详情相关国际化文本`
|
||||||
@@ -560,7 +560,7 @@ Task 6 (重构调用链文档)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [ ] 6. 重构调用链文档
|
- [x] 6. 重构调用链文档
|
||||||
|
|
||||||
**What to do**:
|
**What to do**:
|
||||||
|
|
||||||
@@ -728,11 +728,11 @@ Task 6 (重构调用链文档)
|
|||||||
- `engine/src/managers/engine-manager.ts` - 右键处理器注册
|
- `engine/src/managers/engine-manager.ts` - 右键处理器注册
|
||||||
|
|
||||||
**Acceptance Criteria**:
|
**Acceptance Criteria**:
|
||||||
- [ ] 文档重命名为 `API_CALLCHAIN.md`
|
- [x] 文档重命名为 `API_CALLCHAIN.md`
|
||||||
- [ ] 文档标题改为"SDK API 调用链文档"
|
- [x] 文档标题改为"SDK API 调用链文档"
|
||||||
- [ ] 新增"右键菜单"章节,包含构件详情、显示全部、信息、首页
|
- [x] 新增"右键菜单"章节,包含构件详情、显示全部、信息、首页
|
||||||
- [ ] 新增"构件交互"章节,包含选中/取消选中调用链
|
- [x] 新增"构件交互"章节,包含选中/取消选中调用链
|
||||||
- [ ] 原 Toolbar 内容保持不变,作为第一章
|
- [x] 原 Toolbar 内容保持不变,作为第一章
|
||||||
|
|
||||||
**Commit**: YES
|
**Commit**: YES
|
||||||
- Message: `docs: 重构调用链文档,新增右键菜单和构件交互章节`
|
- Message: `docs: 重构调用链文档,新增右键菜单和构件交互章节`
|
||||||
@@ -761,13 +761,13 @@ bun run build # Expected: BUILD SUCCESS
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Final Checklist
|
### Final Checklist
|
||||||
- [ ] 点击构件后,控制台输出选中信息
|
- [x] 点击构件后,控制台输出选中信息
|
||||||
- [ ] 有选中构件时,右键显示"构件详情"+"显示全部"
|
- [x] 有选中构件时,右键显示"构件详情"+"显示全部"
|
||||||
- [ ] 无选中构件时,右键只显示"显示全部"
|
- [x] 无选中构件时,右键只显示"显示全部"
|
||||||
- [ ] 点击"构件详情"弹出属性弹窗
|
- [x] 点击"构件详情"弹出属性弹窗
|
||||||
- [ ] 弹窗正确展示底层 API 返回的属性数据
|
- [x] 弹窗正确展示底层 API 返回的属性数据
|
||||||
- [ ] 点击"显示全部"控制台输出提示
|
- [x] 点击"显示全部"控制台输出提示
|
||||||
- [ ] 构建成功
|
- [x] 构建成功
|
||||||
- [ ] 调用链文档已重构为全局文档
|
- [x] 调用链文档已重构为全局文档
|
||||||
- [ ] 新增右键菜单章节
|
- [x] 新增右键菜单章节
|
||||||
- [ ] 新增构件交互章节
|
- [x] 新增构件交互章节
|
||||||
|
|||||||
@@ -527,9 +527,16 @@ const dialog = engine.dialog.create({
|
|||||||
| `ButtonGroupManager` | `src/managers/button-group-manager.ts` | 管理通用按钮组 | `BimComponent` |
|
| `ButtonGroupManager` | `src/managers/button-group-manager.ts` | 管理通用按钮组 | `BimComponent` |
|
||||||
| `EngineManager` | `src/managers/engine-manager.ts` | 管理 3D 引擎 | `BimComponent` |
|
| `EngineManager` | `src/managers/engine-manager.ts` | 管理 3D 引擎 | `BimComponent` |
|
||||||
| `RightKeyManager` | `src/managers/right-key-manager.ts` | 管理右键菜单 (Context Menu) | `BimComponent` |
|
| `RightKeyManager` | `src/managers/right-key-manager.ts` | 管理右键菜单 (Context Menu) | `BimComponent` |
|
||||||
| `ModelTreeManager` | `src/managers/model-tree-manager.ts` | 模型树业务管理器 | `BimComponent` |
|
|
||||||
| `PropertyPanelManager` | `src/managers/property-panel-manager.ts` | 属性面板业务管理器 (演示 Collapse) | `BimComponent` |
|
|
||||||
| `MeasureDialogManager` | `src/managers/measure-dialog-manager.ts` | 测量弹窗管理器 | `BimComponent` |
|
| `MeasureDialogManager` | `src/managers/measure-dialog-manager.ts` | 测量弹窗管理器 | `BimComponent` |
|
||||||
|
| `SectionPlaneDialogManager` | `src/managers/section-plane-dialog-manager.ts` | 拾取面剖切弹窗管理器 | `BimComponent` |
|
||||||
|
| `SectionAxisDialogManager` | `src/managers/section-axis-dialog-manager.ts` | 轴向剖切弹窗管理器 | `BimComponent` |
|
||||||
|
| `SectionBoxDialogManager` | `src/managers/section-box-dialog-manager.ts` | 剖切盒弹窗管理器 | `BimComponent` |
|
||||||
|
| `WalkControlManager` | `src/managers/walk-control-manager.ts` | 漫游控制管理器 | `BimComponent` |
|
||||||
|
| `WalkPathDialogManager` | `src/managers/walk-path-dialog-manager.ts` | 路径漫游弹窗管理器 | `BimComponent` |
|
||||||
|
| `WalkPlanViewDialogManager` | `src/managers/walk-plan-view-dialog-manager.ts` | 漫游平面图弹窗管理器 | `BimComponent` |
|
||||||
|
| `MapDialogManager` | `src/managers/map-dialog-manager.ts` | 地图弹窗管理器 | `BimComponent` |
|
||||||
|
| `ConstructTreeManagerBtn` | `src/managers/construct-tree-manager-btn.ts` | 目录树按钮/弹窗管理器 | `BimComponent` |
|
||||||
|
| `ComponentDetailManager` | `src/managers/component-detail-manager.ts` | 构件详情弹窗管理器 | `BimComponent` |
|
||||||
|
|
||||||
### 4.2 组件类清单
|
### 4.2 组件类清单
|
||||||
|
|
||||||
@@ -1665,4 +1672,3 @@ function example(param1: string, param2: number): boolean {
|
|||||||
---
|
---
|
||||||
|
|
||||||
**重要提醒**: 本文档是 AI 协作的重要参考,请保持文档与代码同步更新!
|
**重要提醒**: 本文档是 AI 协作的重要参考,请保持文档与代码同步更新!
|
||||||
|
|
||||||
|
|||||||
@@ -396,7 +396,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 加载模型文件(从 model 目录)
|
// 加载模型文件(从 model 目录)
|
||||||
const modelUrl = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/8634e556-a94e-4ba7-be3e-2ea1507cced5/';
|
const modelUrl = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e/';
|
||||||
|
|
||||||
engine.engine.loadModel(modelUrl, {
|
engine.engine.loadModel(modelUrl, {
|
||||||
position: [0, 0, 0], // 初始位置
|
position: [0, 0, 0], // 初始位置
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,6 +1,42 @@
|
|||||||
# Toolbar 按钮调用链文档
|
# BIM Engine SDK - API 调用链文档
|
||||||
|
|
||||||
本文档详细记录 Toolbar 中每个按钮的完整调用链,从用户点击到底层 3D 引擎 API。
|
本文档详细记录 BIM Engine SDK 中各功能模块的完整调用链,从用户交互到底层 3D 引擎 API。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
### 第一章:工具栏 (Toolbar)
|
||||||
|
|
||||||
|
1. [首页 (Home)](#1-首页-home)
|
||||||
|
2. [框选放大 (Zoom Box)](#2-框选放大-zoom-box)
|
||||||
|
3. [测量 (Measure)](#3-测量-measure)
|
||||||
|
4. [剖切菜单 (Section)](#4-剖切菜单-section)
|
||||||
|
- [拾取面剖切 (Section Plane)](#41-拾取面剖切-section-plane)
|
||||||
|
- [轴向剖切 (Section Axis)](#42-轴向剖切-section-axis)
|
||||||
|
- [剖切盒 (Section Box)](#43-剖切盒-section-box)
|
||||||
|
5. [漫游 (Walk)](#5-漫游-walk)
|
||||||
|
6. [地图 (Map)](#6-地图-map)
|
||||||
|
7. [构件详情 (Property)](#7-构件详情-property)
|
||||||
|
8. [设置 (Setting)](#8-设置-setting)
|
||||||
|
9. [信息 (Info)](#9-信息-info)
|
||||||
|
10. [全屏 (Fullscreen)](#10-全屏-fullscreen)
|
||||||
|
|
||||||
|
### 第二章:右键菜单 (Context Menu)
|
||||||
|
|
||||||
|
1. [构件详情 (Component Detail)](#21-构件详情-component-detail)
|
||||||
|
2. [显示全部 (Show All)](#22-显示全部-show-all)
|
||||||
|
3. [信息 (Info)](#23-信息-info-1)
|
||||||
|
4. [首页 (Home)](#24-首页-home-1)
|
||||||
|
|
||||||
|
### 第三章:构件交互 (Component Interaction)
|
||||||
|
|
||||||
|
1. [构件选中 (Component Selection)](#31-构件选中-component-selection)
|
||||||
|
2. [取消选中 (Deselection)](#32-取消选中-deselection)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 第一章:工具栏 (Toolbar)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -585,9 +621,9 @@ registry.on('map:closed', () => {
|
|||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
Button onClick (buttons/property/index.ts)
|
Button onClick (buttons/property/index.ts)
|
||||||
│ registry.propertyPanel?.show()
|
│ registry.componentDetail?.show()
|
||||||
▼
|
▼
|
||||||
PropertyPanelManager.show()
|
ComponentDetailManager.show()
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
属性面板显示
|
属性面板显示
|
||||||
@@ -729,5 +765,468 @@ registry.sectionAxis // SectionAxisDialogManager - 轴向剖切
|
|||||||
registry.sectionBox // SectionBoxDialogManager - 剖切盒
|
registry.sectionBox // SectionBoxDialogManager - 剖切盒
|
||||||
registry.walkControl // WalkControlManager - 漫游控制
|
registry.walkControl // WalkControlManager - 漫游控制
|
||||||
registry.map // MapDialogManager - 地图
|
registry.map // MapDialogManager - 地图
|
||||||
registry.propertyPanel // PropertyPanelManager - 属性面板
|
registry.componentDetail // ComponentDetailManager - 构件详情弹窗
|
||||||
|
registry.componentDetail // ComponentDetailManager - 构件详情弹窗
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 第二章:右键菜单 (Context Menu)
|
||||||
|
|
||||||
|
右键菜单系统提供上下文相关的操作选项,根据当前状态动态生成菜单项。
|
||||||
|
|
||||||
|
## 2.1 构件详情 (Component Detail)
|
||||||
|
|
||||||
|
**触发条件**: 用户右键点击,且有构件被选中
|
||||||
|
**功能**: 打开构件详情弹窗,展示构件属性信息
|
||||||
|
|
||||||
|
### 调用链
|
||||||
|
|
||||||
|
```
|
||||||
|
用户右键点击 3D 场景(有构件选中)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
RightKeyManager.handleContextMenu() (managers/right-key-manager.ts)
|
||||||
|
│ 获取当前选中构件: registry.engine3d.getSelectedComponent()
|
||||||
|
│ 动态生成菜单项(包含"构件详情")
|
||||||
|
▼
|
||||||
|
用户点击 [构件详情] 菜单项
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
MenuItem onClick (managers/engine-manager.ts:63-68)
|
||||||
|
│ registry.componentDetail?.show(selected.url, selected.id)
|
||||||
|
▼
|
||||||
|
ComponentDetailManager.show(url, id) (managers/component-detail-manager.ts)
|
||||||
|
│ 1. createDialog() - 创建右侧对话框(400px 宽)
|
||||||
|
│ 2. showLoading() - 显示"加载中..."
|
||||||
|
│ 3. registry.engine3d.getComponentProperties(url, id, callback)
|
||||||
|
▼
|
||||||
|
EngineManager.getComponentProperties(url, id, callback)
|
||||||
|
│ this.engineInstance.getComponentProperties(url, id, callback)
|
||||||
|
▼
|
||||||
|
Engine.getComponentProperties(url, id, callback)
|
||||||
|
│ this.engine.modelProperties.getModelProperties(url, id, callback)
|
||||||
|
▼
|
||||||
|
底层引擎 API: engine.modelProperties.getModelProperties(url, id, callback)
|
||||||
|
│ 异步回调返回属性数据
|
||||||
|
▼
|
||||||
|
ComponentDetailManager.renderProperties(data)
|
||||||
|
│ 使用 BimCollapse + BimDescription 渲染属性
|
||||||
|
▼
|
||||||
|
对话框显示构件属性(分类折叠面板 + 键值对列表)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据流
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 1. 选中信息 (从 Engine)
|
||||||
|
{
|
||||||
|
url: string, // 模型 URL
|
||||||
|
id: string // 构件 ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 属性数据 (从底层引擎回调)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
categoryName: "基本属性",
|
||||||
|
items: [
|
||||||
|
{ key: "名称", value: "墙-001" },
|
||||||
|
{ key: "类型", value: "承重墙" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
categoryName: "尺寸",
|
||||||
|
items: [
|
||||||
|
{ key: "长度", value: "5000mm" },
|
||||||
|
{ key: "高度", value: "3000mm" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 3. UI 渲染 (BimCollapse)
|
||||||
|
- 每个 category → 一个折叠面板
|
||||||
|
- items[] → BimDescription 键值对列表
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码摘要
|
||||||
|
|
||||||
|
**动态菜单生成** (`managers/engine-manager.ts`):
|
||||||
|
```typescript
|
||||||
|
const selected = this.getSelectedComponent();
|
||||||
|
if (selected) {
|
||||||
|
menuItems.push({
|
||||||
|
id: 'component-detail',
|
||||||
|
label: 'menu.componentDetail',
|
||||||
|
group: 'component',
|
||||||
|
onClick: () => {
|
||||||
|
const registry = ManagerRegistry.getInstance();
|
||||||
|
registry.componentDetail?.show(selected.url, selected.id);
|
||||||
|
this.rightKey?.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**ComponentDetailManager** (`managers/component-detail-manager.ts`):
|
||||||
|
```typescript
|
||||||
|
public show(modelUrl: string, componentId: string): void {
|
||||||
|
this.createDialog();
|
||||||
|
this.showLoading();
|
||||||
|
|
||||||
|
const registry = ManagerRegistry.getInstance();
|
||||||
|
registry.engine3d?.getComponentProperties(
|
||||||
|
modelUrl,
|
||||||
|
componentId,
|
||||||
|
(data) => {
|
||||||
|
this.renderProperties(data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.2 显示全部 (Show All)
|
||||||
|
|
||||||
|
**触发条件**: 用户右键点击(任何情况)
|
||||||
|
**功能**: 显示所有已加载的模型构件
|
||||||
|
|
||||||
|
### 调用链
|
||||||
|
|
||||||
|
```
|
||||||
|
用户右键点击 3D 场景
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
RightKeyManager.handleContextMenu()
|
||||||
|
│ 动态生成菜单项(始终包含"显示全部")
|
||||||
|
▼
|
||||||
|
用户点击 [显示全部] 菜单项
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
MenuItem onClick (managers/engine-manager.ts:71-77)
|
||||||
|
│ registry.engine3d?.showAllComponents()
|
||||||
|
▼
|
||||||
|
EngineManager.showAllComponents()
|
||||||
|
│ ⚠️ 方法尚未实现,当前为空方法
|
||||||
|
▼
|
||||||
|
⚠️ 待实现:调用底层引擎 API 显示所有构件
|
||||||
|
```
|
||||||
|
|
||||||
|
> **注意**: "显示全部"功能的底层 API 对接尚未完成,当前仅显示菜单项。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.3 信息 (Info)
|
||||||
|
|
||||||
|
**触发条件**: 用户右键点击(任何情况)
|
||||||
|
**功能**: 打开信息对话框
|
||||||
|
|
||||||
|
### 调用链
|
||||||
|
|
||||||
|
```
|
||||||
|
用户右键点击 3D 场景
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
RightKeyManager.handleContextMenu()
|
||||||
|
│ 动态生成菜单项(始终包含"信息")
|
||||||
|
▼
|
||||||
|
用户点击 [信息] 菜单项
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
MenuItem onClick (managers/engine-manager.ts)
|
||||||
|
│ registry.emit('ui:open-dialog', { id: 'info' })
|
||||||
|
│ this.rightKey?.hide()
|
||||||
|
▼
|
||||||
|
事件系统广播 'ui:open-dialog' 事件
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
监听该事件的组件响应并打开信息对话框
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2.4 首页 (Home)
|
||||||
|
|
||||||
|
**触发条件**: 用户右键点击(任何情况)
|
||||||
|
**功能**: 相机回到初始位置
|
||||||
|
|
||||||
|
### 调用链
|
||||||
|
|
||||||
|
```
|
||||||
|
用户右键点击 3D 场景
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
RightKeyManager.handleContextMenu()
|
||||||
|
│ 动态生成菜单项(始终包含"首页")
|
||||||
|
▼
|
||||||
|
用户点击 [首页] 菜单项
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
MenuItem onClick (managers/engine-manager.ts)
|
||||||
|
│ registry.engine3d?.CameraGoHome()
|
||||||
|
│ this.rightKey?.hide()
|
||||||
|
▼
|
||||||
|
(调用链同 Toolbar 首页按钮)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 右键菜单系统架构
|
||||||
|
|
||||||
|
### 动态菜单机制
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 1. 注册处理器 (EngineManager.initialize)
|
||||||
|
const handler: RightKeyHandler = () => {
|
||||||
|
const menuItems: MenuItemConfig[] = [];
|
||||||
|
|
||||||
|
// 根据选中状态动态生成菜单
|
||||||
|
const selected = this.getSelectedComponent();
|
||||||
|
if (selected) {
|
||||||
|
menuItems.push({ /* 构件详情 */ });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用菜单项
|
||||||
|
menuItems.push({ /* 显示全部 */ });
|
||||||
|
menuItems.push({ /* 信息 */ });
|
||||||
|
menuItems.push({ /* 首页 */ });
|
||||||
|
|
||||||
|
return menuItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.rightKey?.registerHandler('engine', handler);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 菜单项配置结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface MenuItemConfig {
|
||||||
|
id: string; // 唯一标识
|
||||||
|
label: string; // 国际化 key
|
||||||
|
group?: string; // 分组(可选)
|
||||||
|
onClick: () => void; // 点击回调
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 第三章:构件交互 (Component Interaction)
|
||||||
|
|
||||||
|
构件交互系统处理用户与 3D 场景中构件的交互事件,包括选中和取消选中。
|
||||||
|
|
||||||
|
## 3.1 构件选中 (Component Selection)
|
||||||
|
|
||||||
|
**触发**: 用户点击 3D 场景中的构件
|
||||||
|
**效果**: 记录选中状态,可用于后续操作(如右键菜单、属性面板等)
|
||||||
|
|
||||||
|
### 调用链
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击 3D 场景中的构件
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[底层] interactionModule.handleMouseClick() (bim_engine_base)
|
||||||
|
│ 检测鼠标点击并进行射线拾取
|
||||||
|
│ 获取点击到的物体信息 (hit)
|
||||||
|
▼
|
||||||
|
[底层] engine.events.trigger('click', hit)
|
||||||
|
│ hit = { object: { url, name }, point, distance, ... }
|
||||||
|
▼
|
||||||
|
[SDK] Engine.init() 中的 'click' 事件监听器 (components/engine/index.ts:127-136)
|
||||||
|
│ if (hit && hit.object) {
|
||||||
|
│ this.selectedComponent = {
|
||||||
|
│ url: hit.object.url,
|
||||||
|
│ id: hit.object.name
|
||||||
|
│ };
|
||||||
|
│ console.log('[Engine] 构件选中:', this.selectedComponent);
|
||||||
|
│ }
|
||||||
|
▼
|
||||||
|
选中状态已记录在 Engine.selectedComponent
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态存储
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Engine 组件 (components/engine/index.ts)
|
||||||
|
private selectedComponent: { url: string; id: string } | null = null;
|
||||||
|
|
||||||
|
// 点击事件监听器
|
||||||
|
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] 取消选中');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取选中状态
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Engine 方法
|
||||||
|
public getSelectedComponent(): { url: string; id: string } | null {
|
||||||
|
return this.selectedComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EngineManager 代理
|
||||||
|
public getSelectedComponent(): { url: string; id: string } | null {
|
||||||
|
if (!this.engineInstance) {
|
||||||
|
console.warn('[EngineManager] Engine 尚未初始化');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.engineInstance.getSelectedComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
const registry = ManagerRegistry.getInstance();
|
||||||
|
const selected = registry.engine3d?.getSelectedComponent();
|
||||||
|
if (selected) {
|
||||||
|
console.log(`选中构件: ${selected.url}#${selected.id}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3.2 取消选中 (Deselection)
|
||||||
|
|
||||||
|
**触发**: 用户点击 3D 场景的空白区域
|
||||||
|
**效果**: 清除选中状态
|
||||||
|
|
||||||
|
### 调用链
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击 3D 场景空白区域
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[底层] interactionModule.handleMouseClick()
|
||||||
|
│ 射线拾取未命中任何物体
|
||||||
|
▼
|
||||||
|
[底层] engine.events.trigger('click', null)
|
||||||
|
│ hit = null(表示未点击到任何物体)
|
||||||
|
▼
|
||||||
|
[SDK] Engine.init() 中的 'click' 事件监听器
|
||||||
|
│ if (hit && hit.object) { ... }
|
||||||
|
│ else {
|
||||||
|
│ this.selectedComponent = null;
|
||||||
|
│ console.log('[Engine] 取消选中');
|
||||||
|
│ }
|
||||||
|
▼
|
||||||
|
选中状态已清除
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 构件交互数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ [底层] 3D 引擎 - 事件系统 │
|
||||||
|
│ engine.events.trigger('click', hit) │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼ hit: { object: {url, name}, ... } | null
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ [SDK] Engine 组件 - 事件监听 + 状态管理 │
|
||||||
|
│ - 监听 'click' 事件 │
|
||||||
|
│ - 更新 selectedComponent 状态 │
|
||||||
|
│ - 提供 getSelectedComponent() 方法 │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ [SDK] EngineManager - 状态代理 │
|
||||||
|
│ - 代理 getSelectedComponent() 方法 │
|
||||||
|
│ - 供其他 Manager 调用 │
|
||||||
|
└────────────────────────────┬────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ [SDK] 消费者 - 使用选中状态 │
|
||||||
|
│ - RightKeyManager: 动态生成右键菜单 │
|
||||||
|
│ - ComponentDetailManager: 显示构件详情 │
|
||||||
|
│ - ComponentDetailManager: 显示构件详情弹窗 │
|
||||||
|
│ - ... 其他需要选中状态的功能 │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用场景示例
|
||||||
|
|
||||||
|
### 场景 1: 右键菜单根据选中状态显示不同菜单项
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// RightKeyManager.handleContextMenu()
|
||||||
|
const handler: RightKeyHandler = () => {
|
||||||
|
const menuItems: MenuItemConfig[] = [];
|
||||||
|
|
||||||
|
// 检查是否有选中的构件
|
||||||
|
const selected = registry.engine3d?.getSelectedComponent();
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
// 有选中构件:显示"构件详情"菜单
|
||||||
|
menuItems.push({
|
||||||
|
id: 'component-detail',
|
||||||
|
label: 'menu.componentDetail',
|
||||||
|
onClick: () => {
|
||||||
|
registry.componentDetail?.show(selected.url, selected.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用菜单项(无论是否选中都显示)
|
||||||
|
menuItems.push({
|
||||||
|
id: 'show-all',
|
||||||
|
label: 'menu.showAll',
|
||||||
|
onClick: () => { /* ... */ }
|
||||||
|
});
|
||||||
|
|
||||||
|
return menuItems;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 2: 构件详情面板获取当前选中构件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ComponentDetailManager
|
||||||
|
public showCurrentSelection(): void {
|
||||||
|
const registry = ManagerRegistry.getInstance();
|
||||||
|
const selected = registry.engine3d?.getSelectedComponent();
|
||||||
|
|
||||||
|
if (!selected) {
|
||||||
|
console.warn('未选中任何构件');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用选中信息加载构件详情
|
||||||
|
this.show(selected.url, selected.id);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 附录:完整 ManagerRegistry 访问方式(更新)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 在任意位置获取 Registry
|
||||||
|
const registry = ManagerRegistry.getInstance();
|
||||||
|
|
||||||
|
// 访问各个 Manager
|
||||||
|
registry.engine3d // EngineManager - 3D 引擎
|
||||||
|
registry.toolbar // ToolbarManager - 工具栏
|
||||||
|
registry.dialog // DialogManager - 对话框
|
||||||
|
registry.rightKey // RightKeyManager - 右键菜单
|
||||||
|
registry.measure // MeasureDialogManager - 测量
|
||||||
|
registry.sectionPlane // SectionPlaneDialogManager - 拾取面剖切
|
||||||
|
registry.sectionAxis // SectionAxisDialogManager - 轴向剖切
|
||||||
|
registry.sectionBox // SectionBoxDialogManager - 剖切盒
|
||||||
|
registry.walkControl // WalkControlManager - 漫游控制
|
||||||
|
registry.map // MapDialogManager - 地图
|
||||||
|
registry.componentDetail // ComponentDetailManager - 构件详情弹窗
|
||||||
|
registry.componentDetail // ComponentDetailManager - 构件详情弹窗(新增)
|
||||||
```
|
```
|
||||||
@@ -122,7 +122,7 @@ class ManagerRegistry {
|
|||||||
buttonGroup: ButtonGroupManager | null;
|
buttonGroup: ButtonGroupManager | null;
|
||||||
rightKey: RightKeyManager | null;
|
rightKey: RightKeyManager | null;
|
||||||
constructTree: ConstructTreeManagerBtn | null;
|
constructTree: ConstructTreeManagerBtn | null;
|
||||||
propertyPanel: PropertyPanelManager | null;
|
componentDetail: ComponentDetailManager | null;
|
||||||
measure: MeasureDialogManager | null;
|
measure: MeasureDialogManager | null;
|
||||||
walkControl: WalkControlManager | null;
|
walkControl: WalkControlManager | null;
|
||||||
map: MapDialogManager | null;
|
map: MapDialogManager | null;
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
| 模块 | 路径 | 职责 | 文档 |
|
| 模块 | 路径 | 职责 | 文档 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| **core** | `src/core/` | 核心基础设施:事件系统、管理器基类、注册表 | [core.md](core.md) |
|
| **core** | `src/core/` | 核心基础设施:事件系统、管理器基类、注册表 | [核心模块.md](核心模块.md) |
|
||||||
| **managers** | `src/managers/` | 15 个管理器,处理业务逻辑和组件协调 | [managers.md](managers.md) |
|
| **managers** | `src/managers/` | 15 个管理器,处理业务逻辑和组件协调 | [管理器模块.md](管理器模块.md) |
|
||||||
| **components** | `src/components/` | 20+ 个 UI 组件 | [components.md](components.md) |
|
| **components** | `src/components/` | 20+ 个 UI 组件 | [组件模块.md](组件模块.md) |
|
||||||
| **services** | `src/services/` | 全局服务:国际化、主题管理 | [services.md](services.md) |
|
| **services** | `src/services/` | 全局服务:国际化、主题管理 | [服务模块.md](服务模块.md) |
|
||||||
|
|
||||||
## Core 模块
|
## Core 模块
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
| BaseManager | `base-manager.ts` | Manager 抽象基类 |
|
| BaseManager | `base-manager.ts` | Manager 抽象基类 |
|
||||||
| BaseDialogManager | `base-dialog-manager.ts` | 对话框 Manager 基类 |
|
| BaseDialogManager | `base-dialog-manager.ts` | 对话框 Manager 基类 |
|
||||||
|
|
||||||
[查看详情 →](core.md)
|
[查看详情 →](核心模块.md)
|
||||||
|
|
||||||
## Managers 模块
|
## Managers 模块
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
| 管理器 | 职责 |
|
| 管理器 | 职责 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| PropertyPanelManager | 属性面板管理 |
|
| ComponentDetailManager | 构件详情弹窗管理 |
|
||||||
| ConstructTreeManagerBtn | 构件树管理 |
|
| ConstructTreeManagerBtn | 构件树管理 |
|
||||||
| MeasureDialogManager | 测量对话框管理 |
|
| MeasureDialogManager | 测量对话框管理 |
|
||||||
| SectionPlaneDialogManager | 平面剖切管理 |
|
| SectionPlaneDialogManager | 平面剖切管理 |
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
| WalkPlanViewDialogManager | 漫游平面图管理 |
|
| WalkPlanViewDialogManager | 漫游平面图管理 |
|
||||||
| MapDialogManager | 地图管理 |
|
| MapDialogManager | 地图管理 |
|
||||||
|
|
||||||
[查看详情 →](managers.md)
|
[查看详情 →](管理器模块.md)
|
||||||
|
|
||||||
## Components 模块
|
## Components 模块
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
| BimTab | 标签页 |
|
| BimTab | 标签页 |
|
||||||
| BimDescription | 描述列表 |
|
| BimDescription | 描述列表 |
|
||||||
|
|
||||||
[查看详情 →](components.md)
|
[查看详情 →](组件模块.md)
|
||||||
|
|
||||||
## Services 模块
|
## Services 模块
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
| LocaleManager | 国际化服务,多语言支持 |
|
| LocaleManager | 国际化服务,多语言支持 |
|
||||||
| ThemeManager | 主题管理服务,明暗切换 |
|
| ThemeManager | 主题管理服务,明暗切换 |
|
||||||
|
|
||||||
[查看详情 →](services.md)
|
[查看详情 →](服务模块.md)
|
||||||
|
|
||||||
## 类型定义
|
## 类型定义
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ src/managers/
|
|||||||
├── dialog-manager.ts # 对话框管理器
|
├── dialog-manager.ts # 对话框管理器
|
||||||
├── button-group-manager.ts # 按钮组管理器
|
├── button-group-manager.ts # 按钮组管理器
|
||||||
├── right-key-manager.ts # 右键菜单管理器
|
├── right-key-manager.ts # 右键菜单管理器
|
||||||
├── property-panel-manager.ts # 属性面板管理器
|
├── component-detail-manager.ts # 构件详情弹窗管理器
|
||||||
├── construct-tree-manager-btn.ts # 构件树管理器
|
├── construct-tree-manager-btn.ts # 构件树管理器
|
||||||
├── walk-control-manager.ts # 漫游控制管理器
|
├── walk-control-manager.ts # 漫游控制管理器
|
||||||
├── measure-dialog-manager.ts # 测量对话框管理器
|
├── measure-dialog-manager.ts # 测量对话框管理器
|
||||||
@@ -41,7 +41,7 @@ BaseManager
|
|||||||
├── DialogManager
|
├── DialogManager
|
||||||
├── ButtonGroupManager
|
├── ButtonGroupManager
|
||||||
├── RightKeyManager
|
├── RightKeyManager
|
||||||
├── PropertyPanelManager
|
├── ComponentDetailManager
|
||||||
├── ConstructTreeManagerBtn
|
├── ConstructTreeManagerBtn
|
||||||
└── WalkControlManager
|
└── WalkControlManager
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ BaseDialogManager (继承自 BaseManager)
|
|||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| **核心** | EngineManager | 3D 引擎管理 |
|
| **核心** | EngineManager | 3D 引擎管理 |
|
||||||
| **UI 容器** | ToolbarManager, DialogManager, ButtonGroupManager | 容器组件管理 |
|
| **UI 容器** | ToolbarManager, DialogManager, ButtonGroupManager | 容器组件管理 |
|
||||||
| **交互** | RightKeyManager, PropertyPanelManager, ConstructTreeManagerBtn | 用户交互 |
|
| **交互** | RightKeyManager, ComponentDetailManager, ConstructTreeManagerBtn | 用户交互 |
|
||||||
| **3D 工具** | MeasureDialogManager, SectionPlaneDialogManager, SectionAxisDialogManager, SectionBoxDialogManager | 3D 操作工具 |
|
| **3D 工具** | MeasureDialogManager, SectionPlaneDialogManager, SectionAxisDialogManager, SectionBoxDialogManager | 3D 操作工具 |
|
||||||
| **漫游** | WalkControlManager, WalkPathDialogManager, WalkPlanViewDialogManager, MapDialogManager | 漫游功能 |
|
| **漫游** | WalkControlManager, WalkPathDialogManager, WalkPlanViewDialogManager, MapDialogManager | 漫游功能 |
|
||||||
|
|
||||||
@@ -350,20 +350,20 @@ class WalkControlManager extends BaseManager {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## PropertyPanelManager
|
## ComponentDetailManager
|
||||||
|
|
||||||
### 概述
|
### 概述
|
||||||
|
|
||||||
管理构件属性面板的显示。
|
管理构件详情弹窗(属性展示)的显示。
|
||||||
|
|
||||||
### API
|
### API
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class PropertyPanelManager extends BaseManager {
|
class ComponentDetailManager extends BaseManager {
|
||||||
init(): void;
|
init(): void;
|
||||||
show(): void;
|
show(): void;
|
||||||
hide(): void;
|
|
||||||
isOpen(): boolean;
|
isOpen(): boolean;
|
||||||
|
hide(): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -378,8 +378,8 @@ import type {
|
|||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
- 查看 [架构文档](ARCHITECTURE.md) 了解设计原理
|
- 查看 [架构文档](架构设计.md) 了解设计原理
|
||||||
- 查看 [模块文档](MODULES/_INDEX.md) 了解详细 API
|
- 查看 [模块文档](MODULES/模块索引.md) 了解详细 API
|
||||||
- 查看 [demo](../demo/) 获取更多示例
|
- 查看 [demo](../demo/) 获取更多示例
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -18,9 +18,12 @@
|
|||||||
|
|
||||||
| 文档 | 说明 | 适合人群 |
|
| 文档 | 说明 | 适合人群 |
|
||||||
|------|------|---------|
|
|------|------|---------|
|
||||||
| [ARCHITECTURE.md](ARCHITECTURE.md) | 架构设计、设计模式、模块依赖 | 架构师、核心开发 |
|
| [架构设计.md](架构设计.md) | 架构设计、设计模式、模块依赖 | 架构师、核心开发 |
|
||||||
| [GETTING_STARTED.md](GETTING_STARTED.md) | 快速开始、安装、基础用法 | 新用户、集成开发 |
|
| [快速开始.md](快速开始.md) | 快速开始、安装、基础用法 | 新用户、集成开发 |
|
||||||
| [MODULES/_INDEX.md](MODULES/_INDEX.md) | 所有模块的详细文档索引 | 所有开发者 |
|
| [MODULES/模块索引.md](MODULES/模块索引.md) | 所有模块的详细文档索引 | 所有开发者 |
|
||||||
|
| [贡献指南.md](贡献指南.md) | 开发协作、脚本说明、最小验证集 | 项目贡献者 |
|
||||||
|
| [运维手册.md](运维手册.md) | 构建/发布/回滚流程与常见问题 | 维护者 |
|
||||||
|
| [API调用链.md](API调用链.md) | API 调用链(用户交互 → Manager → Engine → 底层引擎) | SDK 维护者 |
|
||||||
|
|
||||||
## 模块概览
|
## 模块概览
|
||||||
|
|
||||||
@@ -42,11 +45,11 @@ src/
|
|||||||
|
|
||||||
| 模块 | 路径 | 职责 | 文档 |
|
| 模块 | 路径 | 职责 | 文档 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| **BimEngine** | `src/bim-engine.ts` | 主入口类,整合所有功能 | [详情](MODULES/bim-engine.md) |
|
| **BimEngine** | `src/bim-engine.ts` | 主入口类,整合所有功能 | 暂无独立文档 |
|
||||||
| **core** | `src/core/` | 事件系统、管理器基类、注册表 | [详情](MODULES/core.md) |
|
| **core** | `src/core/` | 事件系统、管理器基类、注册表 | [详情](MODULES/核心模块.md) |
|
||||||
| **managers** | `src/managers/` | 15 个管理器,处理业务逻辑 | [详情](MODULES/managers.md) |
|
| **managers** | `src/managers/` | 15 个管理器,处理业务逻辑 | [详情](MODULES/管理器模块.md) |
|
||||||
| **components** | `src/components/` | 20+ 个 UI 组件 | [详情](MODULES/components.md) |
|
| **components** | `src/components/` | 20+ 个 UI 组件 | [详情](MODULES/组件模块.md) |
|
||||||
| **services** | `src/services/` | 主题管理、国际化服务 | [详情](MODULES/services.md) |
|
| **services** | `src/services/` | 主题管理、国际化服务 | [详情](MODULES/服务模块.md) |
|
||||||
|
|
||||||
## 架构分层
|
## 架构分层
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ registry.emit('engine:model-loaded', { url: 'model.gltf' });
|
|||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| 核心 | EngineManager | 3D 引擎管理 |
|
| 核心 | EngineManager | 3D 引擎管理 |
|
||||||
| UI 容器 | ToolbarManager, DialogManager, ButtonGroupManager | 容器组件管理 |
|
| UI 容器 | ToolbarManager, DialogManager, ButtonGroupManager | 容器组件管理 |
|
||||||
| 交互 | RightKeyManager, PropertyPanelManager, ConstructTreeManagerBtn | 用户交互 |
|
| 交互 | RightKeyManager, ComponentDetailManager, ConstructTreeManagerBtn | 用户交互 |
|
||||||
| 工具 | MeasureDialogManager, SectionPlaneDialogManager, SectionAxisDialogManager, SectionBoxDialogManager | 3D 操作工具 |
|
| 工具 | MeasureDialogManager, SectionPlaneDialogManager, SectionAxisDialogManager, SectionBoxDialogManager | 3D 操作工具 |
|
||||||
| 漫游 | WalkControlManager, WalkPathDialogManager, WalkPlanViewDialogManager, MapDialogManager | 漫游功能 |
|
| 漫游 | WalkControlManager, WalkPathDialogManager, WalkPlanViewDialogManager, MapDialogManager | 漫游功能 |
|
||||||
|
|
||||||
50
docs/贡献指南.md
Normal file
50
docs/贡献指南.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 贡献指南(CONTRIB)
|
||||||
|
|
||||||
|
本文档面向 SDK 开发/维护者,内容以 `package.json` 的 scripts 为单一事实来源。
|
||||||
|
|
||||||
|
## 开发环境
|
||||||
|
|
||||||
|
- Node.js: 建议使用当前 LTS
|
||||||
|
- 包管理器: npm(项目脚本默认使用 `npm run ...`)
|
||||||
|
|
||||||
|
备注:本仓库当前没有提供 `.env.example`,因此默认不需要额外环境变量。
|
||||||
|
|
||||||
|
## 常用命令(Scripts)
|
||||||
|
|
||||||
|
| Script | 命令 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `dev` | `vite` | 启动开发环境(通常用于 playground) |
|
||||||
|
| `build` | `tsc && vite build` | 构建 SDK(产物输出到 `dist/`) |
|
||||||
|
| `copy:demo` | `mkdir -p demo/lib && cp dist/iflow-engine.es.js dist/iflow-engine.umd.js dist/iflow-engine.umd.js.map demo/lib/` | 复制构建产物到 HTML demo 目录 |
|
||||||
|
| `copy:demo-draco` | `mkdir -p demo/static/js/draco && cp node_modules/iflow-engine-base/dist/draco/*.js node_modules/iflow-engine-base/dist/draco/*.wasm demo/static/js/draco/` | 复制 draco 解码器到 HTML demo 目录 |
|
||||||
|
| `copy:demo-all` | `npm run copy:demo && npm run copy:demo-draco` | 一次性准备 HTML demo 需要的全部静态资源 |
|
||||||
|
| `copy:demo-vue` | `mkdir -p demo-vue/public/lib && cp dist/iflow-engine.es.js dist/iflow-engine.umd.js dist/iflow-engine.umd.js.map demo-vue/public/lib/` | 复制构建产物到 Vue demo 目录 |
|
||||||
|
| `dev:demo` | `npm run build && npm run copy:demo-all && cd demo && npm run dev` | 构建并启动 HTML demo |
|
||||||
|
| `dev:demo-vue` | `npm run build && npm run copy:demo-vue && cd demo-vue && npm run dev` | 构建并启动 Vue demo |
|
||||||
|
| `dev:all` | `npm run dev:demo & npm run dev:demo-vue` | 同时启动两个 demo(并行) |
|
||||||
|
| `prepublishOnly` | `npm run build` | npm 发布前自动构建 |
|
||||||
|
|
||||||
|
## 开发流程(建议)
|
||||||
|
|
||||||
|
1. 本地开发:`npm run dev`
|
||||||
|
2. 修改代码后做一次构建验证:`npm run build`
|
||||||
|
3. 需要人工验证交互时:
|
||||||
|
- HTML demo:`npm run dev:demo`
|
||||||
|
- Vue demo:`npm run dev:demo-vue`
|
||||||
|
|
||||||
|
## 测试与验证
|
||||||
|
|
||||||
|
当前仓库未配置独立的单元测试脚本。
|
||||||
|
|
||||||
|
推荐的最小验证集:
|
||||||
|
- `npm run build`(保证 TypeScript 编译与 Vite 打包通过)
|
||||||
|
- 在 `demo/` 或 `demo-vue/` 中进行手动回归(与本次修改相关的功能点)
|
||||||
|
|
||||||
|
## 文档维护
|
||||||
|
|
||||||
|
- 代码变更后,务必同步更新 `AI_COLLABORATION.md` 的相关部分。
|
||||||
|
- 如果新增/修改了组件或管理器,建议同步更新 `docs/` 下对应模块文档。
|
||||||
|
|
||||||
|
## 过期文档检查(90 天)
|
||||||
|
|
||||||
|
按最近一次扫描结果:`docs/` 下没有发现 90 天未更新的文档。
|
||||||
60
docs/运维手册.md
Normal file
60
docs/运维手册.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# 运维手册(RUNBOOK)
|
||||||
|
|
||||||
|
本文档面向维护者,描述 SDK 的构建、发布与回滚流程。
|
||||||
|
|
||||||
|
## 发布(Deployment)
|
||||||
|
|
||||||
|
本项目是 SDK(library)形态,发布通常指 `npm publish`(或内部制品库发布)。
|
||||||
|
|
||||||
|
发布前检查:
|
||||||
|
1. 本地构建通过:`npm run build`
|
||||||
|
2. `dist/` 目录产物完整:
|
||||||
|
- `dist/iflow-engine.es.js`
|
||||||
|
- `dist/iflow-engine.umd.js`
|
||||||
|
- `dist/index.d.ts`
|
||||||
|
3. demo 自测(建议):
|
||||||
|
- `npm run dev:demo` 或 `npm run dev:demo-vue`
|
||||||
|
|
||||||
|
说明:`prepublishOnly` 已配置为 `npm run build`,因此 `npm publish` 前会自动触发构建。
|
||||||
|
|
||||||
|
## 监控与告警(Monitoring & Alerts)
|
||||||
|
|
||||||
|
SDK 仓库本身不包含线上服务,因此没有传统意义的运行时监控。
|
||||||
|
|
||||||
|
建议的“质量监控”方式:
|
||||||
|
- 发布后在一个最小集成项目中验证:
|
||||||
|
- ESM/UMD 能否被正确加载
|
||||||
|
- 类型定义是否可用(TypeScript 项目可正常编译)
|
||||||
|
- 对关键交互(目录树、右键菜单、构件详情、剖切/测量/漫游)做冒烟回归
|
||||||
|
|
||||||
|
## 常见问题与处理(Common Issues)
|
||||||
|
|
||||||
|
### 1) build 失败
|
||||||
|
|
||||||
|
现象:`npm run build` 报 TypeScript 或 Vite 打包错误。
|
||||||
|
|
||||||
|
处理:
|
||||||
|
- 优先修复类型错误/导入路径错误
|
||||||
|
- 确认 `dist/` 未被误引用为源码依赖
|
||||||
|
|
||||||
|
### 2) demo 无法正常显示 3D 或缺少 draco
|
||||||
|
|
||||||
|
现象:HTML demo 中模型无法加载,控制台提示 draco 相关资源缺失。
|
||||||
|
|
||||||
|
处理:
|
||||||
|
- 运行 `npm run copy:demo-draco`
|
||||||
|
- 或直接运行 `npm run dev:demo`(会自动执行 build + copy)
|
||||||
|
|
||||||
|
### 3) 右键菜单/弹窗交互异常(事件被 3D 场景吞掉)
|
||||||
|
|
||||||
|
处理:
|
||||||
|
- 检查 UI 容器是否对 mouse 事件做了 stopPropagation
|
||||||
|
- 检查 RightKey/Menu 子菜单容器是否阻止 mousedown 冒泡
|
||||||
|
|
||||||
|
## 回滚(Rollback)
|
||||||
|
|
||||||
|
SDK 发布回滚常见方式:
|
||||||
|
1. 版本回退:在上游业务工程将依赖版本 pin 回稳定版本
|
||||||
|
2. 重发版本:发布一个修复版本(推荐)
|
||||||
|
|
||||||
|
注意:npm registry 通常不建议覆盖已发布版本(避免破坏依赖缓存)。
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
background-color: var(--bim-component-bg);
|
background-color: var(--bim-component-bg);
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
border-bottom: 1px solid var(--bim-border-strong);
|
border-bottom: 1px solid var(--bim-border-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-collapse.is-ghost .bim-collapse-header:hover {
|
.bim-collapse.is-ghost .bim-collapse-header:hover {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { themeManager } from '../../services/theme';
|
|||||||
import type { EngineOptions, ModelLoadOptions } from './types';
|
import type { EngineOptions, ModelLoadOptions } from './types';
|
||||||
import type { MeasureMode } from '../../types/measure';
|
import type { MeasureMode } from '../../types/measure';
|
||||||
import type { SectionBoxRange } from '../section-box-panel/types';
|
import type { SectionBoxRange } from '../section-box-panel/types';
|
||||||
|
import { ManagerRegistry } from '../../core/manager-registry';
|
||||||
// 导入第三方 SDK 的 createEngine 函数(从 npm 包引入)
|
// 导入第三方 SDK 的 createEngine 函数(从 npm 包引入)
|
||||||
// import { createEngine as createEngineSDK } from 'iflow-engine-base';
|
// import { createEngine as createEngineSDK } from 'iflow-engine-base';
|
||||||
import { createEngine as createEngineSDK } from '../../../../bim_engine_base/dist/bim-engine-sdk.es';
|
import { createEngine as createEngineSDK } from '../../../../bim_engine_base/dist/bim-engine-sdk.es';
|
||||||
@@ -128,15 +129,18 @@ export class Engine implements IBimComponent {
|
|||||||
|
|
||||||
// 监听构件点击事件
|
// 监听构件点击事件
|
||||||
this.engine.events.on('click', (hit: any) => {
|
this.engine.events.on('click', (hit: any) => {
|
||||||
|
const registry = ManagerRegistry.getInstance();
|
||||||
if (hit && hit.object) {
|
if (hit && hit.object) {
|
||||||
this.selectedComponent = {
|
this.selectedComponent = {
|
||||||
url: hit.object.url,
|
url: hit.object.url,
|
||||||
id: hit.object.name
|
id: hit.object.name
|
||||||
};
|
};
|
||||||
console.log('[Engine] 构件选中:', this.selectedComponent);
|
console.log('[Engine] 构件选中:', this.selectedComponent);
|
||||||
|
registry.emit('component:selected', this.selectedComponent);
|
||||||
} else {
|
} else {
|
||||||
this.selectedComponent = null;
|
this.selectedComponent = null;
|
||||||
console.log('[Engine] 取消选中');
|
console.log('[Engine] 取消选中');
|
||||||
|
registry.emit('component:deselected', {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -735,6 +739,31 @@ export class Engine implements IBimComponent {
|
|||||||
|
|
||||||
// ==================== 结束:构件选中 ====================
|
// ==================== 结束:构件选中 ====================
|
||||||
|
|
||||||
|
// ==================== 模型树 ====================
|
||||||
|
|
||||||
|
public getTypeTreeData(): any[] {
|
||||||
|
if (!this._isInitialized || !this.engine?.modelTree) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.engine.modelTree.getTypeTreeData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLevelTreeData(): any[] {
|
||||||
|
if (!this._isInitialized || !this.engine?.modelTree) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.engine.modelTree.getLevelTreeData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMajorTreeData(): any[] {
|
||||||
|
if (!this._isInitialized || !this.engine?.modelTree) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.engine.modelTree.getMajorTreeData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 结束:模型树 ====================
|
||||||
|
|
||||||
/** 激活框选放大功能 */
|
/** 激活框选放大功能 */
|
||||||
public activateZoomBox(): void {
|
public activateZoomBox(): void {
|
||||||
if (!this._isInitialized || !this.engine) {
|
if (!this._isInitialized || !this.engine) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
padding: 6px;
|
padding: 6px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
min-width: 160px;
|
|
||||||
box-shadow: var(--bim-shadow-lg);
|
box-shadow: var(--bim-shadow-lg);
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -38,8 +37,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bim-menu-item:hover {
|
.bim-menu-item:hover {
|
||||||
background-color: var(--bim-floating-btn-bg);
|
background-color: var(--bim-component-bg-hover);
|
||||||
transform: translateX(2px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-menu-item.disabled {
|
.bim-menu-item.disabled {
|
||||||
@@ -49,6 +47,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bim-menu-item-icon {
|
.bim-menu-item-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-menu.has-icons .bim-menu-item-icon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|||||||
@@ -93,14 +93,23 @@ export class BimMenu implements IBimComponent {
|
|||||||
return this.element;
|
return this.element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private hasAnyIcon(items: MenuItemConfig[]): boolean {
|
||||||
* 核心渲染逻辑
|
return items.some(item => {
|
||||||
* 处理分组、排序和 DOM 生成
|
if (item.icon) return true;
|
||||||
*/
|
if (item.children) return this.hasAnyIcon(item.children);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private render(): void {
|
private render(): void {
|
||||||
const { items, groupOrder } = this.options;
|
const { items, groupOrder } = this.options;
|
||||||
|
|
||||||
// 1. 数据分桶:按 group 字段将菜单项分组
|
if (this.hasAnyIcon(items)) {
|
||||||
|
this.element.classList.add('has-icons');
|
||||||
|
} else {
|
||||||
|
this.element.classList.remove('has-icons');
|
||||||
|
}
|
||||||
|
|
||||||
const groups = new Map<string, MenuItemConfig[]>();
|
const groups = new Map<string, MenuItemConfig[]>();
|
||||||
const defaultGroup = 'default';
|
const defaultGroup = 'default';
|
||||||
|
|
||||||
@@ -112,25 +121,19 @@ export class BimMenu implements IBimComponent {
|
|||||||
groups.get(groupName)!.push(item);
|
groups.get(groupName)!.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. 确定分组顺序
|
|
||||||
let sortedGroupKeys: string[] = [];
|
let sortedGroupKeys: string[] = [];
|
||||||
if (groupOrder) {
|
if (groupOrder) {
|
||||||
// 优先按照 groupOrder 指定的顺序排序
|
|
||||||
sortedGroupKeys = groupOrder.filter(g => groups.has(g));
|
sortedGroupKeys = groupOrder.filter(g => groups.has(g));
|
||||||
// 将未在 groupOrder 中定义的组追加到最后
|
|
||||||
for (const key of groups.keys()) {
|
for (const key of groups.keys()) {
|
||||||
if (!sortedGroupKeys.includes(key)) {
|
if (!sortedGroupKeys.includes(key)) {
|
||||||
sortedGroupKeys.push(key);
|
sortedGroupKeys.push(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果未指定顺序,则按默认遍历顺序
|
|
||||||
sortedGroupKeys = Array.from(groups.keys());
|
sortedGroupKeys = Array.from(groups.keys());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 渲染分组和组内项
|
|
||||||
sortedGroupKeys.forEach((groupName, index) => {
|
sortedGroupKeys.forEach((groupName, index) => {
|
||||||
// 除了第一组外,每组之前插入分割线
|
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
const divider = document.createElement('li');
|
const divider = document.createElement('li');
|
||||||
divider.className = 'bim-menu-divider';
|
divider.className = 'bim-menu-divider';
|
||||||
@@ -138,13 +141,16 @@ export class BimMenu implements IBimComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const groupItems = groups.get(groupName)!;
|
const groupItems = groups.get(groupName)!;
|
||||||
// 组内排序:根据 item.order 升序排列
|
|
||||||
groupItems.sort((a, b) => (a.order || 0) - (b.order || 0));
|
groupItems.sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||||
|
|
||||||
groupItems.forEach(item => {
|
groupItems.forEach(item => {
|
||||||
// 仅渲染可见的项
|
|
||||||
if (item.visible !== false) {
|
if (item.visible !== false) {
|
||||||
this.element.appendChild(this.createItemElement(item));
|
this.element.appendChild(this.createItemElement(item));
|
||||||
|
if (item.divider) {
|
||||||
|
const divider = document.createElement('li');
|
||||||
|
divider.className = 'bim-menu-divider';
|
||||||
|
this.element.appendChild(divider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
/**
|
|
||||||
* 菜单项配置接口 (用于简化的对象配置)
|
|
||||||
*/
|
|
||||||
export interface MenuItemConfig {
|
export interface MenuItemConfig {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -11,4 +8,5 @@ export interface MenuItemConfig {
|
|||||||
children?: MenuItemConfig[];
|
children?: MenuItemConfig[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
|
divider?: boolean;
|
||||||
}
|
}
|
||||||
@@ -35,6 +35,12 @@ export const enUS: TranslationDictionary = {
|
|||||||
info: 'Info',
|
info: 'Info',
|
||||||
home: 'Home',
|
home: 'Home',
|
||||||
componentDetail: 'Component Detail',
|
componentDetail: 'Component Detail',
|
||||||
|
hideSelected: 'Hide Selected',
|
||||||
|
transparentSelected: 'Transparent Selected',
|
||||||
|
isolateSelected: 'Isolate Selected',
|
||||||
|
hideOthers: 'Hide Others',
|
||||||
|
transparentOthers: 'Transparent Others',
|
||||||
|
fitSectionBox: 'Fit Section Box',
|
||||||
showAll: 'Show All'
|
showAll: 'Show All'
|
||||||
},
|
},
|
||||||
tree: {
|
tree: {
|
||||||
@@ -47,6 +53,8 @@ export const enUS: TranslationDictionary = {
|
|||||||
component: 'Component',
|
component: 'Component',
|
||||||
system: 'System',
|
system: 'System',
|
||||||
space: 'Space',
|
space: 'Space',
|
||||||
|
type: 'Type',
|
||||||
|
major: 'Major',
|
||||||
},
|
},
|
||||||
panel: {
|
panel: {
|
||||||
property: {
|
property: {
|
||||||
@@ -60,7 +68,8 @@ export const enUS: TranslationDictionary = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentDetail: {
|
componentDetail: {
|
||||||
title: 'Component Detail'
|
title: 'Component Detail',
|
||||||
|
noSelection: 'Please select a component first'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
measure: {
|
measure: {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface TranslationDictionary {
|
|||||||
};
|
};
|
||||||
componentDetail: {
|
componentDetail: {
|
||||||
title: string;
|
title: string;
|
||||||
|
noSelection: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
dialog: {
|
dialog: {
|
||||||
@@ -53,6 +54,12 @@ export interface TranslationDictionary {
|
|||||||
info: string;
|
info: string;
|
||||||
home: string;
|
home: string;
|
||||||
componentDetail: string;
|
componentDetail: string;
|
||||||
|
hideSelected: string;
|
||||||
|
transparentSelected: string;
|
||||||
|
isolateSelected: string;
|
||||||
|
hideOthers: string;
|
||||||
|
transparentOthers: string;
|
||||||
|
fitSectionBox: string;
|
||||||
showAll: string;
|
showAll: string;
|
||||||
};
|
};
|
||||||
tree: {
|
tree: {
|
||||||
@@ -65,6 +72,8 @@ export interface TranslationDictionary {
|
|||||||
component: string;
|
component: string;
|
||||||
system: string;
|
system: string;
|
||||||
space: string;
|
space: string;
|
||||||
|
type: string;
|
||||||
|
major: string;
|
||||||
};
|
};
|
||||||
measure: {
|
measure: {
|
||||||
btnName: string;
|
btnName: string;
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ export const zhCN: TranslationDictionary = {
|
|||||||
info: '信息',
|
info: '信息',
|
||||||
home: '首页',
|
home: '首页',
|
||||||
componentDetail: '构件详情',
|
componentDetail: '构件详情',
|
||||||
|
hideSelected: '隐藏选中构件',
|
||||||
|
transparentSelected: '半透明选中构件',
|
||||||
|
isolateSelected: '隔离选中构件',
|
||||||
|
hideOthers: '其他构件隐藏',
|
||||||
|
transparentOthers: '其他构件半透明',
|
||||||
|
fitSectionBox: '剖切盒适应',
|
||||||
showAll: '显示全部'
|
showAll: '显示全部'
|
||||||
},
|
},
|
||||||
tree: {
|
tree: {
|
||||||
@@ -47,6 +53,8 @@ export const zhCN: TranslationDictionary = {
|
|||||||
component: '构件',
|
component: '构件',
|
||||||
system: '系统',
|
system: '系统',
|
||||||
space: '空间',
|
space: '空间',
|
||||||
|
type: '类型',
|
||||||
|
major: '专业',
|
||||||
},
|
},
|
||||||
panel: {
|
panel: {
|
||||||
property: {
|
property: {
|
||||||
@@ -60,7 +68,8 @@ export const zhCN: TranslationDictionary = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentDetail: {
|
componentDetail: {
|
||||||
title: '构件详情'
|
title: '构件详情',
|
||||||
|
noSelection: '请先选中构件'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
measure: {
|
measure: {
|
||||||
|
|||||||
@@ -1,35 +1,56 @@
|
|||||||
import { BaseManager } from '../core/base-manager';
|
import { BaseManager } from '../core/base-manager';
|
||||||
import { BimCollapse } from '../components/collapse/index';
|
import { BimCollapse } from '../components/collapse/index';
|
||||||
import { BimDescription } from '../components/description/index';
|
import { BimTab } from '../components/tab';
|
||||||
|
import { t } from '../services/locale';
|
||||||
|
|
||||||
export class ComponentDetailManager extends BaseManager {
|
export class ComponentDetailManager extends BaseManager {
|
||||||
private dialogId = 'component-detail-dialog';
|
private dialogId = 'component-detail-dialog';
|
||||||
private dialog: any = null;
|
private dialog: any = null;
|
||||||
|
private currentSelection: { url: string; id: string } | null = null;
|
||||||
|
private unsubscribeSelected: (() => void) | null = null;
|
||||||
|
private unsubscribeDeselected: (() => void) | null = null;
|
||||||
|
private tabInstance: BimTab | null = null;
|
||||||
|
private propertiesData: any = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public show(modelUrl: string, componentId: string): void {
|
public init(): void {
|
||||||
|
this.unsubscribeSelected = this.registry.on('component:selected', (payload) => {
|
||||||
|
this.currentSelection = payload;
|
||||||
|
if (this.isOpen()) {
|
||||||
|
this.loadAndRenderContent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.unsubscribeDeselected = this.registry.on('component:deselected', () => {
|
||||||
|
this.currentSelection = null;
|
||||||
|
if (this.isOpen()) {
|
||||||
|
this.renderNoSelection();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public show(): void {
|
||||||
if (!this.registry.dialog) {
|
if (!this.registry.dialog) {
|
||||||
console.warn('[ComponentDetailManager] Dialog manager not initialized');
|
console.warn('[ComponentDetailManager] Dialog manager not initialized');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isOpen()) {
|
if (!this.isOpen()) {
|
||||||
this.hide();
|
this.createDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.createDialog();
|
if (this.currentSelection) {
|
||||||
this.showLoading();
|
this.loadAndRenderContent();
|
||||||
|
} else {
|
||||||
this.registry.engine3d?.getComponentProperties(modelUrl, componentId, (data) => {
|
this.renderNoSelection();
|
||||||
this.renderProperties(data, componentId);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createDialog(): void {
|
private createDialog(): void {
|
||||||
const width = 400;
|
const width = 300;
|
||||||
const x = document.body.clientWidth - width - 40;
|
const x = document.body.clientWidth - width - 40;
|
||||||
|
|
||||||
this.dialog = this.registry.dialog?.create({
|
this.dialog = this.registry.dialog?.create({
|
||||||
@@ -44,27 +65,81 @@ export class ComponentDetailManager extends BaseManager {
|
|||||||
} as any);
|
} as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadAndRenderContent(): void {
|
||||||
|
if (!this.dialog || !this.currentSelection) return;
|
||||||
|
|
||||||
|
this.showLoading();
|
||||||
|
|
||||||
|
this.registry.engine3d?.getComponentProperties(
|
||||||
|
this.currentSelection.url,
|
||||||
|
this.currentSelection.id,
|
||||||
|
(data) => {
|
||||||
|
this.propertiesData = data;
|
||||||
|
this.renderTabbedContent();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private showLoading(): void {
|
private showLoading(): void {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.style.padding = '20px';
|
container.style.cssText = 'height:100%;display:flex;align-items:center;justify-content:center;';
|
||||||
container.style.textAlign = 'center';
|
|
||||||
container.textContent = '加载中...';
|
container.textContent = '加载中...';
|
||||||
this.dialog?.setContent(container);
|
this.dialog?.setContent(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderProperties(data: any, _componentId: string): void {
|
private renderNoSelection(): void {
|
||||||
if (!this.dialog) return;
|
if (!this.dialog) return;
|
||||||
|
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.style.height = '100%';
|
container.style.cssText = 'height:100%;display:flex;align-items:center;justify-content:center;color:var(--bim-text-secondary,#999);';
|
||||||
container.style.overflowY = 'auto';
|
container.textContent = t('panel.componentDetail.noSelection') || '请先选中构件';
|
||||||
|
this.dialog.setContent(container);
|
||||||
|
}
|
||||||
|
|
||||||
const properties = data?.properties || [];
|
private renderTabbedContent(): void {
|
||||||
|
if (!this.dialog) return;
|
||||||
|
|
||||||
|
if (this.tabInstance) {
|
||||||
|
this.tabInstance.destroy();
|
||||||
|
this.tabInstance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.cssText = 'height:100%;display:flex;flex-direction:column;';
|
||||||
|
|
||||||
|
const propertiesPanel = this.createPropertiesPanel();
|
||||||
|
const materialsPanel = this.createMaterialsPanel();
|
||||||
|
|
||||||
|
this.tabInstance = new BimTab({
|
||||||
|
container,
|
||||||
|
activeId: 'properties',
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: 'properties',
|
||||||
|
title: 'panel.property.tab.props',
|
||||||
|
content: propertiesPanel
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'materials',
|
||||||
|
title: 'panel.property.tab.material',
|
||||||
|
content: materialsPanel
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
this.tabInstance.init();
|
||||||
|
|
||||||
|
this.dialog.setContent(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPropertiesPanel(): HTMLElement {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.cssText = 'height:100%;overflow-y:auto;';
|
||||||
|
|
||||||
|
const properties = this.propertiesData?.properties || [];
|
||||||
|
|
||||||
if (properties.length === 0) {
|
if (properties.length === 0) {
|
||||||
container.innerHTML = '<div style="padding:20px;text-align:center;">无属性数据</div>';
|
container.innerHTML = '<div style="padding:20px;text-align:center;color:var(--bim-text-secondary,#999);">无属性数据</div>';
|
||||||
this.dialog.setContent(container);
|
return container;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const collapseItems = properties.map((category: any, index: number) => ({
|
const collapseItems = properties.map((category: any, index: number) => ({
|
||||||
@@ -76,26 +151,102 @@ export class ComponentDetailManager extends BaseManager {
|
|||||||
new BimCollapse({
|
new BimCollapse({
|
||||||
container,
|
container,
|
||||||
accordion: false,
|
accordion: false,
|
||||||
|
ghost: true,
|
||||||
activeIds: collapseItems.length > 0 ? [collapseItems[0].id] : [],
|
activeIds: collapseItems.length > 0 ? [collapseItems[0].id] : [],
|
||||||
items: collapseItems
|
items: collapseItems
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dialog.setContent(container);
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
#${this.dialogId} .bim-collapse-header {
|
||||||
|
background-color: var(--bim-component-bg-hover) !important;
|
||||||
|
}
|
||||||
|
#${this.dialogId} .bim-collapse-header:hover {
|
||||||
|
background-color: var(--bim-component-bg-active) !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
container.appendChild(style);
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createMaterialsPanel(): HTMLElement {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.style.cssText = 'height:100%;overflow-y:auto;';
|
||||||
|
|
||||||
|
const materials = this.propertiesData?.materials || [];
|
||||||
|
|
||||||
|
if (materials.length === 0) {
|
||||||
|
container.innerHTML = '<div style="padding:20px;text-align:center;color:var(--bim-text-secondary,#999);">无材质数据</div>';
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collapseItems = materials.map((material: any, index: number) => ({
|
||||||
|
id: `material-${index}`,
|
||||||
|
title: material.name || `材质 ${index + 1}`,
|
||||||
|
content: this.createCategoryContent(material.children || material.properties || [])
|
||||||
|
}));
|
||||||
|
|
||||||
|
new BimCollapse({
|
||||||
|
container,
|
||||||
|
accordion: false,
|
||||||
|
ghost: true,
|
||||||
|
activeIds: collapseItems.length > 0 ? [collapseItems[0].id] : [],
|
||||||
|
items: collapseItems
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
#${this.dialogId} .bim-collapse-header {
|
||||||
|
background-color: var(--bim-component-bg-hover) !important;
|
||||||
|
}
|
||||||
|
#${this.dialogId} .bim-collapse-header:hover {
|
||||||
|
background-color: var(--bim-component-bg-active) !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
container.appendChild(style);
|
||||||
|
|
||||||
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createCategoryContent(items: any[]): HTMLElement {
|
private createCategoryContent(items: any[]): HTMLElement {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
|
|
||||||
const descItems = items.map((item: any) => ({
|
items.forEach((item: any, index: number) => {
|
||||||
label: item.name || '-',
|
const row = document.createElement('div');
|
||||||
value: String(item.value ?? '-')
|
row.style.cssText = `
|
||||||
}));
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--bim-border-default, rgba(255,255,255,0.15));
|
||||||
|
`;
|
||||||
|
|
||||||
new BimDescription({
|
if (index === items.length - 1) {
|
||||||
container,
|
row.style.borderBottom = 'none';
|
||||||
labelWidth: '120px',
|
}
|
||||||
bordered: true,
|
|
||||||
items: descItems
|
const label = document.createElement('div');
|
||||||
|
label.style.cssText = `
|
||||||
|
width: 120px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--bim-text-secondary, #999);
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-right: 1px solid var(--bim-border-default, rgba(255,255,255,0.15));
|
||||||
|
`;
|
||||||
|
label.textContent = item.name || '-';
|
||||||
|
|
||||||
|
const value = document.createElement('div');
|
||||||
|
value.style.cssText = `
|
||||||
|
flex: 1;
|
||||||
|
color: var(--bim-text-primary, #fff);
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
word-break: break-all;
|
||||||
|
`;
|
||||||
|
value.textContent = String(item.value ?? '-');
|
||||||
|
|
||||||
|
row.appendChild(label);
|
||||||
|
row.appendChild(value);
|
||||||
|
container.appendChild(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
@@ -106,6 +257,10 @@ export class ComponentDetailManager extends BaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public hide(): void {
|
public hide(): void {
|
||||||
|
if (this.tabInstance) {
|
||||||
|
this.tabInstance.destroy();
|
||||||
|
this.tabInstance = null;
|
||||||
|
}
|
||||||
if (this.dialog) {
|
if (this.dialog) {
|
||||||
this.dialog.destroy();
|
this.dialog.destroy();
|
||||||
this.dialog = null;
|
this.dialog = null;
|
||||||
@@ -113,6 +268,14 @@ export class ComponentDetailManager extends BaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
|
if (this.unsubscribeSelected) {
|
||||||
|
this.unsubscribeSelected();
|
||||||
|
this.unsubscribeSelected = null;
|
||||||
|
}
|
||||||
|
if (this.unsubscribeDeselected) {
|
||||||
|
this.unsubscribeDeselected();
|
||||||
|
this.unsubscribeDeselected = null;
|
||||||
|
}
|
||||||
this.hide();
|
this.hide();
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
/**
|
|
||||||
* 构件树管理器
|
|
||||||
* 负责管理构件树按钮和构件树对话框
|
|
||||||
*/
|
|
||||||
import type { ButtonGroupColors, ButtonConfig } from '../components/button-group/index.type';
|
import type { ButtonGroupColors, ButtonConfig } from '../components/button-group/index.type';
|
||||||
import { BaseManager } from '../core/base-manager';
|
import { BaseManager } from '../core/base-manager';
|
||||||
import { BimButtonGroup } from '../components/button-group';
|
import { BimButtonGroup } from '../components/button-group';
|
||||||
@@ -11,76 +7,37 @@ import { BimDialog } from '../components/dialog';
|
|||||||
import { BimTab } from '../components/tab';
|
import { BimTab } from '../components/tab';
|
||||||
import { getIcon } from '../utils/icon-manager';
|
import { getIcon } from '../utils/icon-manager';
|
||||||
|
|
||||||
/** 模拟的构件树数据 */
|
function transformTreeData(apiData: any[]): TreeNodeConfig[] {
|
||||||
const MOCK_STRUCT_DATA: TreeNodeConfig[] = [
|
if (!apiData || apiData.length === 0) return [];
|
||||||
{
|
|
||||||
id: 'root',
|
return apiData.map((model, modelIndex) => {
|
||||||
label: '全部构件',
|
const transformNode = (node: any, index: number): TreeNodeConfig => {
|
||||||
expanded: true,
|
const hasChildren = node.children && node.children.length > 0;
|
||||||
clickAction: 'expand',
|
return {
|
||||||
children: [
|
id: node.id || `node-${modelIndex}-${index}`,
|
||||||
{
|
label: node.name || node.label || '未命名',
|
||||||
id: 'level-1',
|
expanded: false,
|
||||||
label: '一层',
|
clickAction: hasChildren ? 'expand' : 'select',
|
||||||
expanded: false,
|
children: hasChildren
|
||||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M20.73 16.52V7.59a.7.7 0 0 0-.08-.33a.74.74 0 0 0-.36-.36l-8-3.58a.75.75 0 0 0-.62 0l-8 3.58a.8.8 0 0 0-.44.69v8.82a.83.83 0 0 0 .44.69l8 3.58a.72.72 0 0 0 .62 0l8-3.58a.77.77 0 0 0 .44-.58m-16-7.78l6.5 2.92v7.18l-6.5-2.91Zm8 2.92l6.5-2.92v7.19l-6.5 2.91ZM12 4.82l6.17 2.77L12 10.35L5.83 7.59Z"/></svg>',
|
? node.children.map((child: any, childIndex: number) => transformNode(child, childIndex))
|
||||||
clickAction: 'expand',
|
: undefined,
|
||||||
children: [
|
data: node
|
||||||
{ id: 'l1-wall', label: '墙体(128)' },
|
};
|
||||||
{ id: 'l1-column', label: '柱(46)' },
|
};
|
||||||
{ id: 'l1-beam', label: '梁(82)' },
|
|
||||||
{ id: 'l1-slab', label: '楼板(12)' },
|
const hasChildren = model.children && model.children.length > 0;
|
||||||
{ id: 'l1-door', label: '门(24)' },
|
return {
|
||||||
{ id: 'l1-window', label: '窗(36)' }
|
id: `model-${modelIndex}`,
|
||||||
]
|
label: model.name || '模型',
|
||||||
},
|
expanded: true,
|
||||||
{
|
clickAction: 'expand',
|
||||||
id: 'level-2',
|
children: hasChildren
|
||||||
label: '二层',
|
? model.children.map((child: any, childIndex: number) => transformNode(child, childIndex))
|
||||||
expanded: false,
|
: undefined
|
||||||
clickAction: 'expand',
|
};
|
||||||
children: [
|
});
|
||||||
{ id: 'l2-wall', label: '墙体(141)' },
|
}
|
||||||
{ id: 'l2-column', label: '柱(52)' },
|
|
||||||
{ id: 'l2-beam', label: '梁(90)' },
|
|
||||||
{ id: 'l2-slab', label: '楼板(12)' },
|
|
||||||
{ id: 'l2-door', label: '门(18)' },
|
|
||||||
{ id: 'l2-window', label: '窗(40)' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'level-3',
|
|
||||||
label: '三层',
|
|
||||||
expanded: false,
|
|
||||||
clickAction: 'expand',
|
|
||||||
children: [
|
|
||||||
{ id: 'l3-wall', label: '墙体(136)' },
|
|
||||||
{ id: 'l3-column', label: '柱(48)' },
|
|
||||||
{ id: 'l3-beam', label: '梁(88)' },
|
|
||||||
{ id: 'l3-slab', label: '楼板(12)' },
|
|
||||||
{ id: 'l3-door', label: '门(16)' },
|
|
||||||
{ id: 'l3-window', label: '窗(38)' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'level-roof',
|
|
||||||
label: '屋面层',
|
|
||||||
expanded: false,
|
|
||||||
clickAction: 'expand',
|
|
||||||
children: [
|
|
||||||
{ id: 'rf-slab', label: '屋面板(6)' },
|
|
||||||
{ id: 'rf-beam', label: '屋面梁(24)' },
|
|
||||||
{ id: 'rf-parapet', label: '女儿墙(18)' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构件树管理器
|
|
||||||
* 管理左上角的构件树按钮和对话框
|
|
||||||
*/
|
|
||||||
export class ConstructTreeManagerBtn extends BaseManager {
|
export class ConstructTreeManagerBtn extends BaseManager {
|
||||||
/** 按钮组实例 */
|
/** 按钮组实例 */
|
||||||
private toolbar: BimButtonGroup | null = null;
|
private toolbar: BimButtonGroup | null = null;
|
||||||
@@ -125,41 +82,54 @@ export class ConstructTreeManagerBtn extends BaseManager {
|
|||||||
this.toolbar.render();
|
this.toolbar.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开构件树对话框 */
|
|
||||||
public openConstructTreeDialog() {
|
public openConstructTreeDialog() {
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
|
|
||||||
const tree = new BimTree({
|
const levelTreeData = this.registry.engine3d?.getLevelTreeData() ?? [];
|
||||||
data: MOCK_STRUCT_DATA,
|
const typeTreeData = this.registry.engine3d?.getTypeTreeData() ?? [];
|
||||||
checkable: true,
|
const majorTreeData = this.registry.engine3d?.getMajorTreeData() ?? [];
|
||||||
indent: 0,
|
|
||||||
enableSearch: true,
|
|
||||||
checkStrictly: true,
|
|
||||||
defaultExpandAll: true,
|
|
||||||
renderActions: (_node) => {
|
|
||||||
return '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="1.5" d="m14.828 6.343l2.829 2.829m1.414-1.415L8.464 18.364l-3.535.707l.707-3.536L16.243 4.93a2 2 0 0 1 2.828 2.828Z"/></svg>';
|
|
||||||
},
|
|
||||||
onNodeCheck: (node) => {
|
|
||||||
console.log('onNodeCheck', node);
|
|
||||||
},
|
|
||||||
onNodeSelect: (node) => {
|
|
||||||
console.log('onNodeSelect', node);
|
|
||||||
},
|
|
||||||
onNodeExpand: (node) => {
|
|
||||||
console.log('onNodeExpand', node);
|
|
||||||
this.dialog?.fitWidth();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
tree.init();
|
|
||||||
|
|
||||||
const systemPlaceholder = document.createElement('div');
|
console.log('[ConstructTree] 构件树数据 (Level):', levelTreeData);
|
||||||
systemPlaceholder.className = 'construct-tab__panel-content';
|
console.log('[ConstructTree] 类型树数据 (Type):', typeTreeData);
|
||||||
const spacePlaceholder = document.createElement('div');
|
console.log('[ConstructTree] 专业树数据 (Major):', majorTreeData);
|
||||||
spacePlaceholder.className = 'construct-tab__panel-content';
|
|
||||||
|
const createTree = (data: any[]) => {
|
||||||
|
const tree = new BimTree({
|
||||||
|
data: transformTreeData(data),
|
||||||
|
checkable: true,
|
||||||
|
indent: 0,
|
||||||
|
enableSearch: true,
|
||||||
|
checkStrictly: true,
|
||||||
|
defaultExpandAll: true,
|
||||||
|
onNodeCheck: (node) => {
|
||||||
|
console.log('onNodeCheck', node);
|
||||||
|
},
|
||||||
|
onNodeSelect: (node) => {
|
||||||
|
console.log('onNodeSelect', node);
|
||||||
|
},
|
||||||
|
onNodeExpand: () => {
|
||||||
|
this.dialog?.fitWidth();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
tree.init();
|
||||||
|
return tree;
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentTree = createTree(levelTreeData);
|
||||||
|
const typeTree = createTree(typeTreeData);
|
||||||
|
const majorTree = createTree(majorTreeData);
|
||||||
|
|
||||||
const componentPanel = document.createElement('div');
|
const componentPanel = document.createElement('div');
|
||||||
componentPanel.className = 'construct-tab__panel-content';
|
componentPanel.className = 'construct-tab__panel-content';
|
||||||
componentPanel.appendChild(tree.element);
|
componentPanel.appendChild(componentTree.element);
|
||||||
|
|
||||||
|
const typePanel = document.createElement('div');
|
||||||
|
typePanel.className = 'construct-tab__panel-content';
|
||||||
|
typePanel.appendChild(typeTree.element);
|
||||||
|
|
||||||
|
const majorPanel = document.createElement('div');
|
||||||
|
majorPanel.className = 'construct-tab__panel-content';
|
||||||
|
majorPanel.appendChild(majorTree.element);
|
||||||
|
|
||||||
const tabMount = document.createElement('div');
|
const tabMount = document.createElement('div');
|
||||||
tabMount.className = 'construct-tab__container';
|
tabMount.className = 'construct-tab__container';
|
||||||
@@ -169,8 +139,8 @@ export class ConstructTreeManagerBtn extends BaseManager {
|
|||||||
container: tabMount,
|
container: tabMount,
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 'component', title: 'tab.component', content: componentPanel },
|
{ id: 'component', title: 'tab.component', content: componentPanel },
|
||||||
{ id: 'system', title: 'tab.system', content: systemPlaceholder },
|
{ id: 'type', title: 'tab.type', content: typePanel },
|
||||||
{ id: 'space', title: 'tab.space', content: spacePlaceholder },
|
{ id: 'major', title: 'tab.major', content: majorPanel },
|
||||||
],
|
],
|
||||||
activeId: 'component',
|
activeId: 'component',
|
||||||
onChange: () => {
|
onChange: () => {
|
||||||
@@ -188,7 +158,9 @@ export class ConstructTreeManagerBtn extends BaseManager {
|
|||||||
resizable: false,
|
resizable: false,
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
tab.destroy();
|
tab.destroy();
|
||||||
tree.destroy();
|
componentTree.destroy();
|
||||||
|
typeTree.destroy();
|
||||||
|
majorTree.destroy();
|
||||||
this.setVisible(true);
|
this.setVisible(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
import { Engine, type EngineOptions, type ModelLoadOptions } from '../components/engine';
|
import { Engine, type EngineOptions, type ModelLoadOptions } from '../components/engine';
|
||||||
import { BaseManager } from '../core/base-manager';
|
import { BaseManager } from '../core/base-manager';
|
||||||
import { RightKeyManager } from './right-key-manager';
|
import { RightKeyManager } from './right-key-manager';
|
||||||
import { infoMenuButton } from '../components/menu/buttons/info';
|
|
||||||
import { homeMenuButton } from '../components/menu/buttons/home';
|
|
||||||
import type { MeasureMode } from '../types/measure';
|
import type { MeasureMode } from '../types/measure';
|
||||||
import type { SectionBoxRange } from '../components/section-box-panel/types';
|
import type { SectionBoxRange } from '../components/section-box-panel/types';
|
||||||
import type { MenuItemConfig } from '../components/menu/item';
|
import type { MenuItemConfig } from '../components/menu/item';
|
||||||
@@ -56,31 +54,96 @@ export class EngineManager extends BaseManager {
|
|||||||
const items: MenuItemConfig[] = [];
|
const items: MenuItemConfig[] = [];
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
|
// 1. 构件详情
|
||||||
items.push({
|
items.push({
|
||||||
id: 'componentDetail',
|
id: 'componentDetail',
|
||||||
label: 'menu.componentDetail',
|
label: 'menu.componentDetail',
|
||||||
group: 'component',
|
group: 'component',
|
||||||
|
order: 1,
|
||||||
|
divider: true,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const registry = ManagerRegistry.getInstance();
|
const registry = ManagerRegistry.getInstance();
|
||||||
registry.componentDetail?.show(selected.url, selected.id);
|
registry.componentDetail?.show();
|
||||||
|
this.rightKey?.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 隐藏选中构件
|
||||||
|
items.push({
|
||||||
|
id: 'hideSelected',
|
||||||
|
label: 'menu.hideSelected',
|
||||||
|
group: 'component',
|
||||||
|
order: 2,
|
||||||
|
onClick: () => {
|
||||||
|
console.log('[Menu] 隐藏选中构件 - 暂未实现');
|
||||||
|
this.rightKey?.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 半透明选中构件
|
||||||
|
items.push({
|
||||||
|
id: 'transparentSelected',
|
||||||
|
label: 'menu.transparentSelected',
|
||||||
|
group: 'component',
|
||||||
|
order: 3,
|
||||||
|
onClick: () => {
|
||||||
|
console.log('[Menu] 半透明选中构件 - 暂未实现');
|
||||||
|
this.rightKey?.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 隔离选中构件(带子菜单)
|
||||||
|
items.push({
|
||||||
|
id: 'isolateSelected',
|
||||||
|
label: 'menu.isolateSelected',
|
||||||
|
group: 'component',
|
||||||
|
order: 4,
|
||||||
|
divider: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 'hideOthers',
|
||||||
|
label: 'menu.hideOthers',
|
||||||
|
onClick: () => {
|
||||||
|
console.log('[Menu] 隔离 - 其他构件隐藏 - 暂未实现');
|
||||||
|
this.rightKey?.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'transparentOthers',
|
||||||
|
label: 'menu.transparentOthers',
|
||||||
|
onClick: () => {
|
||||||
|
console.log('[Menu] 隔离 - 其他构件半透明 - 暂未实现');
|
||||||
|
this.rightKey?.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. 剖切盒适应
|
||||||
|
items.push({
|
||||||
|
id: 'fitSectionBox',
|
||||||
|
label: 'menu.fitSectionBox',
|
||||||
|
group: 'component',
|
||||||
|
order: 5,
|
||||||
|
onClick: () => {
|
||||||
|
console.log('[Menu] 剖切盒适应 - 暂未实现');
|
||||||
this.rightKey?.hide();
|
this.rightKey?.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. 显示全部(始终显示)
|
||||||
items.push({
|
items.push({
|
||||||
id: 'showAll',
|
id: 'showAll',
|
||||||
label: 'menu.showAll',
|
label: 'menu.showAll',
|
||||||
group: 'component',
|
group: 'component',
|
||||||
|
order: 6,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
console.log('[Menu] 显示全部 - 功能开发中');
|
console.log('[Menu] 显示全部 - 暂未实现');
|
||||||
this.rightKey?.hide();
|
this.rightKey?.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
items.push(infoMenuButton());
|
|
||||||
items.push(homeMenuButton());
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -370,6 +433,22 @@ export class EngineManager extends BaseManager {
|
|||||||
|
|
||||||
// ==================== 结束:构件选中 ====================
|
// ==================== 结束:构件选中 ====================
|
||||||
|
|
||||||
|
// ==================== 模型树 ====================
|
||||||
|
|
||||||
|
public getTypeTreeData(): any[] {
|
||||||
|
return this.engineInstance?.getTypeTreeData() ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLevelTreeData(): any[] {
|
||||||
|
return this.engineInstance?.getLevelTreeData() ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMajorTreeData(): any[] {
|
||||||
|
return this.engineInstance?.getMajorTreeData() ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 结束:模型树 ====================
|
||||||
|
|
||||||
/** 销毁引擎管理器 */
|
/** 销毁引擎管理器 */
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
if (this.engineInstance) {
|
if (this.engineInstance) {
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
/**
|
|
||||||
* 属性面板管理器
|
|
||||||
* 负责管理构件属性面板的显示和内容
|
|
||||||
*/
|
|
||||||
import { BaseManager } from '../core/base-manager';
|
|
||||||
import { BimCollapse } from '../components/collapse/index';
|
|
||||||
import { BimDescription } from '../components/description/index';
|
|
||||||
import { BimTab } from '../components/tab/index';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 属性面板管理器
|
|
||||||
* 显示选中构件的属性信息和材质信息
|
|
||||||
*/
|
|
||||||
export class PropertyPanelManager extends BaseManager {
|
|
||||||
/** 对话框 ID */
|
|
||||||
private dialogId = 'property-panel-dialog';
|
|
||||||
/** 对话框实例 */
|
|
||||||
private dialog: any = null;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 初始化,监听打开事件 */
|
|
||||||
public init(): void {
|
|
||||||
document.addEventListener('bim-demo:open-property-panel', () => {
|
|
||||||
this.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 显示属性面板 */
|
|
||||||
public show() {
|
|
||||||
if (!this.registry.dialog) {
|
|
||||||
console.warn('Dialog manager is not initialized');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const width = 360;
|
|
||||||
const x = document.body.clientWidth - width - 40;
|
|
||||||
|
|
||||||
this.dialog = this.registry.dialog.create({
|
|
||||||
id: this.dialogId,
|
|
||||||
title: 'panel.property.title',
|
|
||||||
content: '',
|
|
||||||
width: `${width}px`,
|
|
||||||
height: '500px',
|
|
||||||
position: { x, y: 20 },
|
|
||||||
showMask: false,
|
|
||||||
resizable: true,
|
|
||||||
onClose: () => {
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
const contentContainer = document.createElement('div');
|
|
||||||
contentContainer.style.height = '100%';
|
|
||||||
contentContainer.style.display = 'flex';
|
|
||||||
contentContainer.style.flexDirection = 'column';
|
|
||||||
|
|
||||||
this.dialog.setContent(contentContainer);
|
|
||||||
|
|
||||||
const tab = new BimTab({
|
|
||||||
container: contentContainer,
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
id: 'props',
|
|
||||||
title: 'panel.property.tab.props',
|
|
||||||
content: this.createPropsTabContent()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'material',
|
|
||||||
title: 'panel.property.tab.material',
|
|
||||||
content: this.createMaterialTabContent()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
tab.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 创建属性标签页内容 */
|
|
||||||
private createPropsTabContent(): HTMLElement {
|
|
||||||
const container = document.createElement('div');
|
|
||||||
container.style.height = '100%';
|
|
||||||
container.style.overflowY = 'auto';
|
|
||||||
|
|
||||||
new BimCollapse({
|
|
||||||
container: container,
|
|
||||||
accordion: true,
|
|
||||||
activeIds: ['base', 'location'],
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'base',
|
|
||||||
title: 'panel.property.base',
|
|
||||||
content: this.createBaseInfoContent(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'advanced',
|
|
||||||
title: 'panel.property.advanced',
|
|
||||||
content: this.createAdvancedInfoContent(),
|
|
||||||
disabled: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 创建材质标签页内容 */
|
|
||||||
private createMaterialTabContent(): HTMLElement {
|
|
||||||
const container = document.createElement('div');
|
|
||||||
container.style.height = '100%';
|
|
||||||
container.style.overflowY = 'auto';
|
|
||||||
|
|
||||||
new BimCollapse({
|
|
||||||
container: container,
|
|
||||||
accordion: true,
|
|
||||||
activeIds: ['material'],
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: 'material',
|
|
||||||
title: 'panel.property.material',
|
|
||||||
content: this.createMaterialContent(),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 创建基本信息内容 */
|
|
||||||
private createBaseInfoContent(): HTMLElement {
|
|
||||||
const container = document.createElement('div');
|
|
||||||
|
|
||||||
new BimDescription({
|
|
||||||
container: container,
|
|
||||||
labelWidth: '80px',
|
|
||||||
bordered: true,
|
|
||||||
items: [
|
|
||||||
{ label: 'Guid', value: '<span style="color:#666">1f8d-4a2e-9c</span>' },
|
|
||||||
{ label: 'Name', value: '<b>Basic Wall: Generic - 200mm</b>' },
|
|
||||||
{ label: 'Type', value: 'Basic Wall' },
|
|
||||||
{ label: 'Level', value: 'Trane - Centrifugal Water Chiller - CVHF 2 Stage direct drive TAG(BP-RHS-1100RT) 0202104531 1' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 创建高级信息内容 */
|
|
||||||
private createAdvancedInfoContent(): HTMLElement {
|
|
||||||
const container = document.createElement('div');
|
|
||||||
|
|
||||||
new BimDescription({
|
|
||||||
container: container,
|
|
||||||
labelWidth: '100px',
|
|
||||||
bordered: true,
|
|
||||||
items: [
|
|
||||||
{ label: 'Area', value: '32.5 m²' },
|
|
||||||
{ label: 'Volume', value: '6.5 m³' },
|
|
||||||
{ label: 'Length', value: '5000 mm' },
|
|
||||||
{ label: 'Phase', value: 'New Construction' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 创建材质内容 */
|
|
||||||
private createMaterialContent(): HTMLElement {
|
|
||||||
const container = document.createElement('div');
|
|
||||||
|
|
||||||
const preview = document.createElement('div');
|
|
||||||
preview.style.display = 'flex';
|
|
||||||
preview.style.alignItems = 'center';
|
|
||||||
preview.style.marginBottom = '4px';
|
|
||||||
preview.innerHTML = `
|
|
||||||
<div style="width:24px;height:24px;background:#9ca3af;margin-right:8px;border:1px solid #6b7280;border-radius:2px;"></div>
|
|
||||||
<span>Concrete - Cast-in-Place Gray</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const descContainer = document.createElement('div');
|
|
||||||
|
|
||||||
new BimDescription({
|
|
||||||
container: descContainer,
|
|
||||||
items: [
|
|
||||||
{ label: 'Preview', value: preview },
|
|
||||||
{ label: 'Class', value: 'Concrete' },
|
|
||||||
{ label: 'Density', value: '2400 kg/m³' },
|
|
||||||
{ label: 'Thermal', value: '0.6 W/(m·K)' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
container.appendChild(descContainer);
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查面板是否打开
|
|
||||||
* @returns 是否打开
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,4 +30,8 @@ export interface EngineEvents {
|
|||||||
// 地图事件
|
// 地图事件
|
||||||
'map:opened': {};
|
'map:opened': {};
|
||||||
'map:closed': {};
|
'map:closed': {};
|
||||||
|
|
||||||
|
// 构件选中事件
|
||||||
|
'component:selected': { url: string; id: string };
|
||||||
|
'component:deselected': {};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user