feat(registry): 注册 ComponentDetailManager 到全局 Registry 和 BimEngine

This commit is contained in:
yuding
2026-01-28 12:00:55 +08:00
parent 33f1c72791
commit 89789e003b
87 changed files with 37063 additions and 24064 deletions

View File

@@ -0,0 +1,616 @@
# Engine 组件详细文档
> 本文档详细描述 Engine 组件的实现细节,包括 API、UI 结构、逻辑流程等,供 AI 根据文档重现组件。
---
## 1. 组件概述
### 1.1 基本信息
- **组件名称**: `Engine`
- **文件路径**: `src/components/engine/index.ts`
- **类型定义**: `src/components/engine/types.ts`
- **实现接口**: `IBimComponent`
- **用途**: 封装第三方 3D 引擎 SDK提供统一的 3D 引擎管理接口
### 1.2 在 SDK 中的位置
- Engine 组件是独立的 3D 引擎组件
- 必须通过 `EngineManager` 使用,不允许直接使用
- `EngineManager` 位于 `src/managers/engine-manager.ts`
- 采用延迟初始化模式,需要用户主动调用 `init()` 方法
### 1.3 第三方 SDK 依赖
- 依赖 `iflow-engine-base` npm 包中的 `createEngine` 函数
- 通过依赖注入方式使用,不直接导入
---
## 2. 组件类 API 文档
### 2.1 构造函数
```typescript
constructor(options: EngineOptions)
```
**参数**:
- `options`: `EngineOptions` - 3D 引擎配置选项
**默认配置**:
```typescript
{
backgroundColor: 0x1a1a1a, // 默认深色背景
version: 'v1', // 默认使用 v1 版本
showStats: false, // 默认不显示统计
showViewCube: true // 默认显示视图立方体
}
```
**行为**:
- 解析容器元素
- 如果容器没有 id生成一个唯一的 id
- 保存配置选项(设置默认值)
### 2.2 公共方法
#### `init(): void`
初始化组件(实现 `IBimComponent` 接口)
**功能**:
- 检查是否已初始化或已销毁
- 创建引擎配置对象
- 调用 `createEngineSDK()` 创建引擎实例
- 标记为已初始化
- 订阅主题变化
- 应用当前主题
**错误处理**:
- 如果创建失败,抛出错误并标记为未初始化
#### `setTheme(theme: ThemeConfig): void`
设置主题(实现 `IBimComponent` 接口)
**参数**:
- `theme`: `ThemeConfig` - 全局主题配置
**功能**:
- 根据主题名称选择背景色:
- `dark`: 使用深色背景 `0x1a1a1a`
- `light`: 使用浅色背景 `0xf5f5f5`
- `custom`: 使用配置中的 `backgroundColor` 或默认值
- 尝试更新引擎背景色:
- 如果引擎有 `setBackgroundColor()` 方法,调用它
- 否则,如果引擎有 `scene.background`,设置其颜色
#### `setLocales(): void`
设置语言(实现 `IBimComponent` 接口)
**功能**:
- 3D 引擎组件暂时不需要本地化
- 方法为空实现
#### `isInitialized(): boolean`
检查是否已初始化
**返回**: `boolean` - 是否已初始化
#### `loadModel(url: string, options?: ModelLoadOptions): void`
加载 3D 模型
**参数**:
- `url`: `string` - 模型文件 URL必需
- `options`: `ModelLoadOptions` (可选) - 加载选项
**功能**:
- 检查引擎是否已初始化
- 检查 URL 是否提供
- 调用引擎的 `loader.loadModel()` 方法加载模型
**加载选项**:
- `position`: `[number, number, number]` - 模型位置 [x, y, z]
- `rotation`: `[number, number, number]` - 模型旋转(弧度)[x, y, z]
- `scale`: `[number, number, number]` - 模型缩放 [x, y, z]
- `id`: `string` - 模型 ID可选
#### `getEngine(): any`
获取原始 3D 引擎实例
**返回**: 第三方 3D 引擎实例或 `null`
**功能**:
- 返回底层 3D 引擎实例
- 用于直接调用第三方引擎的其他 API
#### `destroy(): void`
销毁组件(实现 `IBimComponent` 接口)
**功能**:
- 取消主题订阅
- 清空容器内容
- 标记为已销毁和未初始化
**注意**: 不销毁引擎实例本身(由第三方 SDK 管理)
---
## 3. 分化组件说明
**Engine 组件没有分化组件**
---
## 4. Manager API 文档
### 4.1 EngineManager 类
**文件路径**: `src/managers/engine-manager.ts`
**继承关系**: `EngineManager extends BimComponent`
### 4.2 构造函数
```typescript
constructor(engine: BimEngine, container: HTMLElement)
```
**参数**:
- `engine`: `BimEngine` - 引擎实例
- `container`: `HTMLElement` - 3D 引擎挂载的目标容器
**行为**:
- 保存容器引用
- **不自动初始化引擎**(延迟初始化模式)
### 4.3 公共方法
#### `initialize(options?: Omit<EngineOptions, 'container'>): boolean`
初始化 3D 引擎
**参数**:
- `options`: `Omit<EngineOptions, 'container'>` (可选) - 引擎配置选项
**返回**: `boolean` - 是否初始化成功
**功能**:
- 如果已经初始化,先销毁旧的实例
- 创建 `Engine` 组件实例
- 调用组件的 `init()` 方法初始化引擎
- 返回初始化结果
**错误处理**:
- 如果初始化失败,返回 `false` 并输出错误信息
#### `isInitialized(): boolean`
检查 3D 引擎是否已初始化
**返回**: `boolean` - 是否已初始化
#### `loadModel(url: string, options?: ModelLoadOptions): void`
加载 3D 模型
**参数**:
- `url`: `string` - 模型文件 URL
- `options`: `ModelLoadOptions` (可选) - 加载选项
**功能**:
- 检查引擎是否已初始化
- 如果未初始化,输出错误信息
- 调用引擎组件的 `loadModel()` 方法
#### `getEngine(): any`
获取原始 3D 引擎实例
**返回**: 第三方 3D 引擎实例或 `null`
**功能**:
- 用于直接调用第三方引擎的其他 API
- 如果引擎未初始化,返回 `null` 并输出警告
#### `destroy(): void`
销毁 3D 引擎实例
**功能**:
- 调用引擎组件的 `destroy()` 方法
- 清空引擎实例引用
---
## 5. UI 详细描述
### 5.1 DOM 结构
**Engine 组件不直接创建 UI 元素**,而是:
1. **使用容器元素**:
- 容器元素由外部提供(通过 `EngineOptions.container`
- 如果容器没有 id组件会生成一个唯一的 id
2. **第三方 SDK 创建 UI**:
- 第三方 SDK 的 `createEngine()` 函数会在容器内创建 3D 场景
- 可能包括:
- Canvas 元素WebGL 渲染)
- 统计面板(如果 `showStats: true`
- 视图立方体(如果 `showViewCube: true`
### 5.2 容器 ID 生成
**算法**:
```typescript
containerId = `engine-container-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
```
**格式**: `engine-container-[时间戳]-[随机字符串]`
**示例**: `engine-container-1704067200000-k3j9x2m1p`
### 5.3 第三方 SDK 配置
**传递给 `createEngine()` 的配置对象**:
```typescript
{
containerId: string, // 容器 ID
backgroundColor: number, // 背景色(十六进制数字)
version: 'v1' | 'v2', // WebGL 版本
showStats: boolean, // 是否显示统计
showViewCube: boolean // 是否显示视图立方体
}
```
---
## 6. 逻辑流程详细描述
### 6.1 初始化流程
1. **构造函数调用**:
- 解析容器元素
- 生成或使用容器 ID
- 保存配置选项(设置默认值)
2. **init() 方法执行**(用户主动调用):
- 检查是否已初始化,如果是则返回
- 检查是否已销毁,如果是则报错
- 创建引擎配置对象
- 调用 `createEngineSDK()` 创建引擎实例
- 检查引擎实例是否创建成功
- 标记为已初始化
- 订阅主题变化:`themeManager.subscribe()`
- 应用当前主题
### 6.2 生命周期
#### 创建阶段
- 构造函数 → 保存配置
#### 初始化阶段(延迟)
- 用户调用 `init()` → 创建引擎实例 → 订阅主题
#### 运行阶段
- 响应主题变更:`setTheme()` 被调用
- 加载模型:`loadModel()` 被调用
- 用户交互3D 场景交互(由第三方 SDK 处理)
#### 销毁阶段
- `destroy()` 被调用
- 取消主题订阅
- 清空容器
- 更新状态
### 6.3 主题变更流程
1. 主题变更 → `themeManager` 通知订阅者
2. `setTheme()` 方法被调用
3. 根据主题名称选择背景色:
- `dark``0x1a1a1a`
- `light``0xf5f5f5`
- `custom` → 使用配置值或默认值
4. 尝试更新引擎背景色:
- 方法 1: 调用 `engine.setBackgroundColor(backgroundColor)`
- 方法 2: 设置 `engine.scene.background.setHex(backgroundColor)`
### 6.4 模型加载流程
1. 用户调用 `loadModel(url, options)`
2. 检查引擎是否已初始化
3. 检查 URL 是否提供
4. 调用 `engine.loader.loadModel(url, options)`
5. 第三方 SDK 处理模型加载
### 6.5 状态管理
#### 内部状态
- `_isInitialized`: 是否已初始化
- `_isDestroyed`: 是否已销毁
- `engine`: 第三方 3D 引擎实例
- `containerId`: 容器 ID
- `options`: 引擎配置选项
#### 订阅管理
- `unsubscribeTheme`: 主题订阅取消函数
### 6.6 与其他组件的交互
- **与 EngineManager**: 通过 Manager 创建和管理
- **与 ThemeManager**: 订阅主题变更
- **与第三方 SDK**: 通过 `createEngine()` 函数创建引擎实例
---
## 7. 国际化支持
### 7.1 使用的翻译键
**Engine 组件不使用翻译键**3D 引擎不需要国际化)。
### 7.2 语言变更处理
- `setLocales()` 方法为空实现
- 不订阅语言变更
---
## 8. 主题支持
### 8.1 使用的主题变量
- 根据主题名称选择背景色:
- `dark` 主题 → `0x1a1a1a`(深色背景)
- `light` 主题 → `0xf5f5f5`(浅色背景)
- `custom` 主题 → 使用配置值或默认值
### 8.2 主题变更处理
- 组件订阅 `themeManager.subscribe()`
- 主题变更时,`setTheme()` 方法被调用
- 根据主题名称选择背景色
- 尝试更新引擎背景色(通过引擎 API
### 8.3 实现细节
```typescript
public setTheme(theme: ThemeConfig): void {
let backgroundColor: number;
if (theme.name === 'dark') {
backgroundColor = 0x1a1a1a;
} else if (theme.name === 'light') {
backgroundColor = 0xf5f5f5;
} else {
backgroundColor = this.options.backgroundColor ?? 0x1a1a1a;
}
// 尝试更新引擎背景色
if (this.engine && typeof this.engine.setBackgroundColor === 'function') {
this.engine.setBackgroundColor(backgroundColor);
} else if (this.engine && this.engine.scene) {
if (this.engine.scene.background) {
this.engine.scene.background.setHex(backgroundColor);
}
}
}
```
---
## 9. 使用示例
### 9.1 基本使用(通过 EngineManager
```typescript
import { BimEngine } from 'iflow-engine';
const engine = new BimEngine('container');
// 初始化 3D 引擎(延迟初始化)
const success = engine.initEngine({
backgroundColor: 0x333333,
version: 'v1',
showStats: true,
showViewCube: true
});
if (success) {
console.log('3D 引擎初始化成功');
}
// 加载模型
engine.engine.loadModel('/model/building.glb', {
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1]
});
```
### 9.2 高级使用
```typescript
// 检查引擎是否已初始化
if (engine.engine.isInitialized()) {
// 加载模型
engine.engine.loadModel('/model/model.glb');
}
// 获取原始引擎实例,调用第三方 API
const rawEngine = engine.engine.getEngine();
if (rawEngine) {
// 调用第三方 SDK 的其他方法
rawEngine.someOtherMethod();
}
```
### 9.3 主题切换
```typescript
// 切换主题会自动更新 3D 场景背景色
engine.setTheme('dark'); // 深色背景
engine.setTheme('light'); // 浅色背景
```
---
## 10. 实现细节(供 AI 重现)
### 10.1 关键算法
#### 容器 ID 生成算法
```typescript
function generateContainerId(): string {
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 9);
return `engine-container-${timestamp}-${random}`;
}
```
#### 主题背景色选择算法
```typescript
function selectBackgroundColor(theme: ThemeConfig, defaultColor: number): number {
if (theme.name === 'dark') {
return 0x1a1a1a; // 深色背景
} else if (theme.name === 'light') {
return 0xf5f5f5; // 浅色背景
} else {
return defaultColor; // 自定义主题,使用配置值
}
}
```
#### 引擎背景色更新算法
```typescript
function updateEngineBackground(engine: any, color: number): void {
// 方法 1: 如果引擎有 setBackgroundColor 方法
if (engine && typeof engine.setBackgroundColor === 'function') {
engine.setBackgroundColor(color);
return;
}
// 方法 2: 如果引擎有 scene.background
if (engine && engine.scene && engine.scene.background) {
engine.scene.background.setHex(color);
return;
}
// 方法 3: 无法更新(引擎可能不支持)
}
```
### 10.2 性能优化点
1. **延迟初始化**:
- 引擎不自动初始化,由用户主动调用
- 减少不必要的资源消耗
2. **状态检查**:
- 初始化前检查状态,避免重复初始化
- 销毁后禁止初始化,避免错误
3. **错误处理**:
- 所有操作都有错误检查
- 提供有意义的错误信息
### 10.3 注意事项和边界情况
1. **容器 ID 唯一性**:
- 容器 ID 必须唯一
- 如果容器已有 id使用现有 id
- 如果容器没有 id生成唯一 id
2. **延迟初始化**:
- 引擎不会自动初始化
- 用户必须主动调用 `init()``initialize()`
- 初始化前调用其他方法会报错
3. **引擎实例管理**:
- 引擎实例由第三方 SDK 创建
- 组件不负责销毁引擎实例本身
- 只负责清理订阅和容器
4. **主题更新兼容性**:
- 不同第三方 SDK 的 API 可能不同
- 需要尝试多种方式更新背景色
- 如果都不支持,静默失败
5. **模型加载**:
- 模型 URL 必须是可访问的
- 模型格式由第三方 SDK 支持
- 加载选项由第三方 SDK 处理
6. **错误处理**:
- 初始化失败时,标记为未初始化
- 提供有意义的错误信息
- 不抛出未捕获的异常
7. **第三方 SDK 依赖**:
- 依赖 `iflow-engine-base` npm 包
- 通过 npm 包导入
- 需要确保依赖已安装
---
## 11. 类型定义
### 11.1 EngineOptions
```typescript
interface EngineOptions {
container: HTMLElement; // 容器元素(必需)
backgroundColor?: number; // 背景颜色(可选,默认 0x1a1a1a
version?: 'v1' | 'v2'; // WebGL 版本(可选,默认 'v1'
showStats?: boolean; // 是否显示性能统计(可选,默认 false
showViewCube?: boolean; // 是否显示视图立方体(可选,默认 true
}
```
### 11.2 ModelLoadOptions
```typescript
interface ModelLoadOptions {
position?: [number, number, number]; // 模型位置 [x, y, z](可选)
rotation?: [number, number, number]; // 模型旋转(弧度)[x, y, z](可选)
scale?: [number, number, number]; // 模型缩放 [x, y, z](可选)
id?: string; // 模型 ID可选
}
```
---
## 12. 文件清单
### 12.1 相关文件
- `src/components/engine/index.ts` - 主组件类
- `src/components/engine/types.ts` - 类型定义
- `src/managers/engine-manager.ts` - 管理器类
- `iflow-engine-base` - 第三方 SDKnpm 依赖)
### 12.2 依赖文件
- `src/types/component.ts` - IBimComponent 接口
- `src/themes/types.ts` - ThemeConfig 类型
- `src/services/theme.ts` - ThemeManager
---
## 13. 更新记录
| 日期 | 修改内容 | 修改人 |
|------|---------|--------|
| 2024-XX-XX | 初始创建 | AI Assistant |
---
**重要提醒**: 本文档必须与组件代码保持同步。任何组件修改都必须更新本文档!

View File

@@ -0,0 +1,211 @@
# RightKey 组件详细文档
> 本文档详细描述 RightKey 组件的实现细节,包括 API、UI 结构、逻辑流程等,供 AI 根据文档重现组件。
---
## 1. 组件概述
### 1.1 基本信息
- **组件名称**: `BimRightKey`
- **文件路径**: `src/components/right-key/index.ts`
- **类型定义**: `src/components/right-key/types.ts`
- **样式文件**: `src/components/right-key/index.css`
- **实现接口**: `IBimComponent`
- **用途**: 提供一个全屏感知的定位容器,用于显示右键菜单等浮层内容。
- **特点**: 自动边界检测(防止溢出屏幕)、点击外部自动关闭、内容无关性(支持挂载任意内容)。
### 1.2 在 SDK 中的位置
- RightKey 是右键菜单系统的容器部分。
- 必须通过 `RightKeyManager` 使用,不允许直接使用。
- `RightKeyManager` 位于 `src/managers/right-key-manager.ts`
---
## 2. 组件类 API 文档
### 2.1 构造函数
```typescript
constructor(options?: RightKeyOptions)
```
**<EFBFBD><EFBFBD>数**:
- `options`:
- `zIndex`: number (默认 10000)
- `className`: string
### 2.2 公共方法
#### `init(): void`
初始化组件(实现 `IBimComponent` 接口)
**功能**:
- 创建 DOM 结构
- 将容器挂载到 `document.body`(或其他指定容器,当前实现是 body
- 绑定全局 `mousedown` 事件用于检测“点击外部”
#### `mount(content: IRightKeyContent): void`
挂载内容组件
**参数**:
- `content`: 实现了 `IRightKeyContent` 接口的对象(如 `BimMenu`
**功能**:
- 清空当前容器内容
- 调用 `content.getElement()` 并将结果 append 到容器中
- 保存 content 引用
#### `unmountContent(): void`
卸载内容
**功能**:
- 调用 `content.destroy()`
- 清空容器 DOM
#### `show(x: number, y: number): void`
显示容器
**参数**:
- `x`, `y`: 屏幕坐标(通常是鼠标事件的 `clientX/Y`
**功能**:
- 计算容器尺寸
- 执行边界检测算法,调整坐标防止溢出
- 设置 `top/left` 样式
- 设置 `display: block`
#### `hide(): void`
隐藏容器
**功能**:
- 设置 `display: none`
- 触发 `onClose` 回调(如果设置了)
#### `setOnClose(callback: () => void): void`
设置关闭回调
**功能**:
- 注册一个回调函数,在容器被隐藏(如点击外部)时触发。通常用于通知 Manager 清理状态。
#### `destroy(): void`
销毁组件
**功能**:
- 解绑全局事件
- 卸载内容
- 移除自身 DOM
---
## 3. Manager API 文档
### 3.1 RightKeyManager 类
**文件路径**: `src/managers/right-key-manager.ts`
#### `registerHandler(handler: (e) => MenuItemConfig[] | null): void`
注册上下文菜单处理器
**参数**:
- `handler`: 函数,接收 MouseEvent返回菜单配置数组或 null。
**功能**:
- 将 handler 加入内部列表。
- 右键点击时,遍历列表。只要有一个 handler 返回了非空数组,就显示这些菜单项。
#### `showMenu(x, y, items, groupOrder?): void`
手动显示菜单
**功能**:
- 创建 `BimMenu` 实例
- 调用 `rightKeyPanel.mount(menu)`
- 调用 `rightKeyPanel.show(x, y)`
---
## 4. UI 详细描述
### 4.1 DOM 结构
```html
<!-- 容器 -->
<div class="bim-right-key" style="z-index: 9000; display: none; left: ...; top: ...">
<!-- 挂载的内容 (例如 BimMenu 的 ul) -->
<ul class="bim-menu">...</ul>
</div>
```
### 4.2 CSS 类名
#### `.bim-right-key`
- `position: fixed`: 固定定位,相对于视口。
- `display: none`: 默认隐藏。
- `box-shadow`: 即使内容组件没有阴影容器也可以提供当前<E5BD93><E5898D>要由内容组件自己提供
---
## 5. 逻辑流程详细描述
### 5.1 全局点击检测 (`handleGlobalClick`)
组件初始化时,会在 `document` 上绑定 `mousedown` (捕获阶段或冒泡阶段,通常冒泡即可)。
1.`mousedown` 发生时,检查 `event.target`
2. 如果 `target` 位于 `.bim-right-key` 容器内部,**不做处理**(认为是有效的内部交互)。
3. 如果 `target` 在容器外部,调用 `hide()` 关闭菜单。
**注意**: 子菜单SubMenu通常挂载在 `body` 上,物理上位于 `.bim-right-key` 外部。为了防止点击子菜单时误关主菜单,子菜单容器必须阻止 `mousedown` 冒泡(见 Menu 组件文档)。
### 5.2 边界检测算法
`show(x, y)` 时执行:
1. 获取容器尺寸: `rect = element.getBoundingClientRect()`
2. 获取视口尺寸: `winW = window.innerWidth`, `winH = window.innerHeight`
3. **水平调整**:
- 如果 `x + rect.width > winW`: `x = x - rect.width`(向左展开)
- 否则: `x` 保持不变(向右展开)
4. **垂直调整**:
- 如果 `y + rect.height > winH`: `y = y - rect.height`(向上展开)
- 否则: `y` 保持不变(向下展开)
5. 应用调整后的 `x, y``style.left``style.top`
---
## 6. 类型定义
### 6.1 IRightKeyContent
```typescript
interface IRightKeyContent {
getElement(): HTMLElement;
destroy(): void;
}
```
### 6.2 RightKeyOptions
```typescript
interface RightKeyOptions {
className?: string;
zIndex?: number;
}
```
---
## 7. 文件清单
- `src/components/right-key/index.ts`: 组件核心逻辑
- `src/components/right-key/index.css`: 样式
- `src/components/right-key/types.ts`: 类型定义
- `src/managers/right-key-manager.ts`: 管理器
---
## 8. 更新记录
| 日期 | 修改内容 | 修改人 |
|------|---------|--------|
| 2024-XX-XX | 更新 Manager API 以支持 MenuItemConfig | AI Assistant |

View File

@@ -0,0 +1,907 @@
# Dialog 组件详细文档
> 本文档详细描述 Dialog 组件的实现细节,包括 API、UI 结构、逻辑流程等,供 AI 根据文档重现组件。
---
## 1. 组件概述
### 1.1 基本信息
- **组件名称**: `BimDialog`
- **文件路径**: `src/components/dialog/index.ts`
- **类型定义**: `src/components/dialog/index.type.ts`
- **样式文件**: `src/components/dialog/index.css`
- **实现接口**: `IBimComponent`
- **用途**: 提供可拖拽、可缩放的通用弹窗组件,支持自定义内容和样式
### 1.2 在 SDK 中的位置
- Dialog 组件是独立的 UI 组件
- 必须通过 `DialogManager` 使用,不允许直接使用
- `DialogManager` 位于 `src/managers/dialog-manager.ts`
---
## 2. 组件类 API 文档
### 2.1 构造函数
```typescript
constructor(options: DialogOptions)
```
**参数**:
- `options`: `DialogOptions` - 弹窗配置选项(详见类型定义)
**默认配置**:
```typescript
{
title: 'Dialog',
width: 300,
height: 'auto',
position: 'center',
draggable: true,
resizable: false,
minWidth: 200,
minHeight: 100
}
```
**行为**:
- 合并用户配置和默认配置
- 创建 DOM 结构
- 自动调用 `init()` 方法
### 2.2 公共方法
#### `init(): void`
初始化组件功能(实现 `IBimComponent` 接口)
**功能**:
- 将弹窗元素添加到容器
- 初始化位置
- 如果 `draggable` 为 true初始化拖拽功能
- 如果 `resizable` 为 true初始化缩放功能
- 订阅主题和语言变更
- 调用 `onOpen` 回调
**调用时机**: 构造函数中自动调用,也可手动调用
#### `setTheme(theme: ThemeConfig): void`
设置主题(实现 `IBimComponent` 接口)
**参数**:
- `theme`: `ThemeConfig` - 全局主题配置
**功能**:
- 将主题颜色映射到 CSS 变量
- 如果用户未自定义颜色,使用主题颜色
- 如果用户已自定义颜色,保持用户自定义
**CSS 变量映射**:
- `--bim-dialog-bg``theme.panelBackground` (如果未自定义 `backgroundColor`)
- `--bim-dialog-header-bg``theme.componentHover` (如果未自定义 `headerBackgroundColor`)
- `--bim-dialog-title-color``theme.textPrimary` (如果未自定义 `titleColor`)
- `--bim-dialog-text-color``theme.textPrimary` (如果未自定义 `textColor`)
- `--bim-dialog-border-color``theme.border` (如果未自定义 `borderColor`)
#### `setLocales(): void`
设置语言(实现 `IBimComponent` 接口)
**功能**:
- 更新标题文本(如果标题是翻译键)
- 使用 `t()` 函数获取翻译后的文本
**行为**:
- 查找 `.bim-dialog-title` 元素
- 如果 `options.title` 存在,使用 `t(options.title)` 更新文本
#### `setContent(content: HTMLElement | string): void`
动态设置弹窗内容
**参数**:
- `content`: `HTMLElement | string` - 内容元素或 HTML 字符串
**功能**:
- 清空当前内容
- 设置新内容(支持 HTML 字符串或 DOM 元素)
#### `fitHeight(recenter: boolean = false): void`
根据内容自动调整弹窗高度
**使用场景**
- 弹窗内容存在“展开/收起”等高度变化的交互(例如测量面板展开后需要增高弹窗,避免遮挡底部操作按钮)
**参数**
- `recenter`: 是否根据 `options.position` 重新计算定位(默认 `false`
**行为**
- 先将高度设置为 `auto`,获取自然高度
- 再将高度夹紧到 `[minHeight, containerHeight]` 范围内
- 默认不重置用户拖拽后的 `left/top`,仅做边界夹紧;若 `recenter=true` 则按 `position` 重新定位
#### `close(): void`
关闭弹窗并销毁
**功能**:
- 清理 `requestAnimationFrame`(如果存在)
- 取消主题和语言订阅
- 从 DOM 中移除元素
- 标记为已销毁
- 调用 `onClose` 回调
#### `destroy(): void`
销毁组件(实现 `IBimComponent` 接口)
**功能**:
- 调用 `close()` 方法
### 2.3 私有方法(供理解实现细节)
#### `createDom(): HTMLElement`
创建弹窗的 DOM 结构
**返回**: 创建的弹窗根元素
**DOM 结构**:
```html
<div class="bim-dialog" [id="..."]>
<div class="bim-dialog-header" [class="draggable"]>
<span class="bim-dialog-title">标题文本</span>
<span class="bim-dialog-close">&times;</span>
</div>
<div class="bim-dialog-content">
<!-- 用户内容 -->
</div>
[<div class="bim-dialog-resize-handle"></div>] <!-- 如果 resizable -->
</div>
```
**关键实现**:
1. 创建根元素,应用 CSS 变量和尺寸
2. 创建标题栏,包含标题和关闭按钮
3. 创建内容区域,支持 HTML 字符串或 DOM 元素
4. 如果 `resizable` 为 true创建缩放手柄
5. **事件拦截**: 绑定所有鼠标/触摸事件,使用 `stopPropagation()` 阻止冒泡,防止传递给 3D 引擎
**事件拦截列表**:
```typescript
['click', 'dblclick', 'contextmenu', 'wheel',
'mousedown', 'mouseup', 'mousemove',
'touchstart', 'touchend', 'touchmove',
'pointerdown', 'pointerup', 'pointermove',
'pointerenter', 'pointerleave', 'pointerover', 'pointerout']
```
#### `initPosition(): void`
初始化弹窗位置
**功能**:
- 根据 `position` 选项计算弹窗位置
- 支持预设位置(如 'center', 'top-left' 等)或坐标对象 `{ x, y }`
- 确保弹窗不超出容器边界
**位置计算逻辑**:
- `center`: `left = (containerWidth - dialogWidth) / 2`, `top = (containerHeight - dialogHeight) / 2`
- `top-left`: `left = 0`, `top = 0`
- `top-center`: `left = (containerWidth - dialogWidth) / 2`, `top = 0`
- `top-right`: `left = containerWidth - dialogWidth`, `top = 0`
- `left-center`: `left = 0`, `top = (containerHeight - dialogHeight) / 2`
- `right-center`: `left = containerWidth - dialogWidth`, `top = (containerHeight - dialogHeight) / 2`
- `bottom-left`: `left = 0`, `top = containerHeight - dialogHeight`
- `bottom-center`: `left = (containerWidth - dialogWidth) / 2`, `top = containerHeight - dialogHeight`
- `bottom-right`: `left = containerWidth - dialogWidth`, `top = containerHeight - dialogHeight`
- `{ x, y }`: 直接使用坐标值
**边界限制**:
```typescript
left = Math.max(0, Math.min(left, containerWidth - dialogWidth));
top = Math.max(0, Math.min(top, containerHeight - dialogHeight));
```
#### `initDrag(): void`
初始化拖拽功能
**功能**:
- 在标题栏上绑定 `mousedown` 事件
- 实现拖拽移动功能
- 使用 `requestAnimationFrame` 优化性能
- 使用 `capture: true` 确保事件在捕获阶段处理
**拖拽流程**:
1. `mousedown`: 记录起始位置和弹窗当前位置,缓存容器和弹窗尺寸
2. `mousemove`: 计算偏移量,更新弹窗位置,限制在容器边界内
3. `mouseup`: 清理事件监听和动画帧
**性能优化**:
- 使用 `requestAnimationFrame` 节流更新
- 缓存容器和弹窗尺寸,减少 reflow
- 使用 `capture: true` 确保事件处理
#### `initResize(): void`
初始化缩放功能
**功能**:
- 在缩放手柄上绑定 `mousedown` 事件
- 实现右下角拖拽缩放功能
- 使用 `requestAnimationFrame` 优化性能
- 限制最小尺寸
**缩放流程**:
1. `mousedown`: 记录起始位置和弹窗当前尺寸
2. `mousemove`: 计算偏移量,更新弹窗尺寸,限制最小尺寸
3. `mouseup`: 清理事件监听和动画帧
**尺寸限制**:
```typescript
newWidth = Math.max(minWidth || 100, startWidth + deltaX);
newHeight = Math.max(minHeight || 50, startHeight + deltaY);
```
---
## 3. 分化组件说明
### 3.1 BimInfoDialog
**文件路径**: `src/components/dialog/bimInfoDialog/index.ts`
**继承关系**: `BimInfoDialog extends BimDialog`
**特殊功能**:
- 预定义了信息展示的内容结构
- 包含标题、信息列表和操作按钮
- 使用固定的配置(宽度 320px可缩放可拖拽
**实现方式**:
- 在构造函数中创建内容 DOM
- 调用父类构造函数,传入预定义的配置
- 不需要重写其他方法,直接继承父类功能
**内容结构**:
```html
<div class="bim-info-dialog-content">
<h3>Model Information</h3>
<ul>
<li><strong>Name:</strong> Sample Project</li>
<li><strong>Version:</strong> 1.0.0</li>
<li><strong>Date:</strong> 当前日期</li>
<li><strong>Status:</strong> <span style="color: green;">Active</span></li>
</ul>
<button>Update Status</button>
</div>
```
**与父类的差异**:
- 固定了内容结构
- 固定了部分配置选项
- 其他功能完全继承自 `BimDialog`
---
## 4. Manager API 文档
### 4.1 DialogManager 类
**文件路径**: `src/managers/dialog-manager.ts`
**继承关系**: `DialogManager extends BimComponent`
### 4.2 构造函数
```typescript
constructor(engine: BimEngine, container: HTMLElement)
```
**参数**:
- `engine`: `BimEngine` - 引擎实例
- `container`: `HTMLElement` - 弹窗挂载的目标容器
**行为**:
- 保存容器引用
- 监听 `ui:open-dialog` 事件
- 如果事件 payload.id 是 'info',自动打开信息弹窗
### 4.3 公共方法
#### `create(options: Omit<DialogOptions, 'container'>): BimDialog`
创建一个通用弹窗
**参数**:
- `options`: `Omit<DialogOptions, 'container'>` - 弹窗配置(不需要传 container
**返回**: `BimDialog` 实例
**功能**:
- 自动使用 Manager 绑定的容器
- 创建 `BimDialog` 实例
- 应用当前主题
- 将弹窗添加到活跃列表
-`onClose` 回调中自动从列表移除
**使用示例**:
```typescript
const dialog = engine.dialog.create({
title: '测试弹窗',
content: '这是内容',
width: 400,
height: 300
});
```
#### `showInfoDialog(): void`
显示二次封装的模型信息弹窗
**功能**:
- 创建 `BimInfoDialog` 实例
- 直接显示信息弹窗
**注意**: 此方法创建的弹窗不会自动加入管理列表(遗留逻辑)
#### `updateTheme(theme: ThemeConfig): void`
响应全局主题变更
**参数**:
- `theme`: `ThemeConfig` - 全局主题配置
**功能**:
- 遍历所有活跃弹窗
- 调用每个弹窗的 `setTheme()` 方法
#### `destroy(): void`
销毁管理器
**功能**:
- 销毁所有活跃弹窗
- 清空活跃列表
---
## 5. UI 详细描述
### 5.1 DOM 结构
**完整 HTML 结构**:
```html
<div class="bim-dialog" id="[可选ID]" style="[CSS变量和尺寸]">
<!-- 标题栏 -->
<div class="bim-dialog-header [draggable]">
<span class="bim-dialog-title">标题文本</span>
<span class="bim-dialog-close">&times;</span>
</div>
<!-- 内容区域 -->
<div class="bim-dialog-content">
<!-- 用户内容HTML字符串或DOM元素 -->
</div>
<!-- 缩放手柄(如果 resizable -->
<div class="bim-dialog-resize-handle"></div>
</div>
```
### 5.2 CSS 类名和样式
#### `.bim-dialog` (根元素)
- `position: absolute` - 绝对定位
- `background-color: var(--bim-dialog-bg)` - 背景色CSS 变量)
- `border: 1px solid var(--bim-dialog-border-color)` - 边框
- `border-radius: 6px` - 圆角
- `box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3)` - 阴影
- `display: flex` + `flex-direction: column` - 垂直布局
- `z-index: 10001` - 层级(确保在 3D 引擎之上)
- `min-width: 200px` - 最小宽度
- `min-height: 100px` - 最小高度
- `pointer-events: auto` - 确保可接收事件
#### `.bim-dialog-header` (标题栏)
- `height: 32px` - 固定高度
- `background-color: var(--bim-dialog-header-bg)` - 背景色
- `display: flex` + `align-items: center` + `justify-content: space-between` - 水平布局
- `padding: 0 10px` - 内边距
- `cursor: default` - 默认光标
- `user-select: none` - 禁止选中
- `border-bottom: 1px solid var(--bim-dialog-border-color)` - 底边框
#### `.bim-dialog-header.draggable` (可拖拽标题栏)
- `cursor: move` - 移动光标
#### `.bim-dialog-title` (标题文本)
- `font-size: 14px` - 字体大小
- `font-weight: 500` - 字体粗细
- `white-space: nowrap` - 不换行
- `overflow: hidden` - 隐藏溢出
- `text-overflow: ellipsis` - 文本省略
- `color: var(--bim-dialog-title-color)` - 文字颜色
#### `.bim-dialog-close` (关闭按钮)
- `cursor: pointer` - 指针光标
- `font-size: 18px` - 字体大小
- `color: #999` - 默认颜色
- `line-height: 1` - 行高
- `margin-left: 8px` - 左边距
#### `.bim-dialog-close:hover` (关闭按钮悬停)
- `color: #fff` - 悬停颜色
#### `.bim-dialog-content` (内容区域)
- `flex: 1` - 占据剩余空间
- `padding: 10px` - 内边距
- `overflow: auto` - 内容溢出时滚动
- `font-size: 14px` - 字体大小
- `color: var(--bim-dialog-text-color)` - 文字颜色
#### `.bim-dialog-resize-handle` (缩放手柄)
- `position: absolute` - 绝对定位
- `width: 10px` + `height: 10px` - 尺寸
- `bottom: 0` + `right: 0` - 右下角位置
- `cursor: se-resize` - 缩放光标
- `z-index: 10` - 层级
#### `.bim-dialog-resize-handle::after` (缩放装饰)
- 使用伪元素创建右下角斜线装饰
- `border-right` + `border-bottom` - 创建斜线效果
### 5.3 CSS 变量
**定义位置**: `:root` 或元素内联样式
**变量列表**:
- `--bim-dialog-bg`: 窗体背景颜色(默认 `rgba(17, 17, 17, 0.95)`
- `--bim-dialog-header-bg`: 标题栏背景颜色(默认 `#2a2a2a`
- `--bim-dialog-title-color`: 标题文字颜色(默认 `#fff`
- `--bim-dialog-text-color`: 内容文字颜色(默认 `#ccc`
- `--bim-dialog-border-color`: 边框颜色(默认 `#444`
### 5.4 交互行为
#### 拖拽
- **触发区域**: 标题栏(`.bim-dialog-header`
- **触发条件**: `draggable: true`
- **行为**:
- 鼠标按下标题栏时开始拖拽
- 鼠标移动时弹窗跟随移动
- 鼠标释放时结束拖拽
- 弹窗位置限制在容器边界内
#### 缩放
- **触发区域**: 缩放手柄(`.bim-dialog-resize-handle`
- **触发条件**: `resizable: true`
- **行为**:
- 鼠标按下缩放手柄时开始缩放
- 鼠标移动时弹窗尺寸跟随变化
- 鼠标释放时结束缩放
- 尺寸限制在最小尺寸以上
#### 关闭
- **触发方式**:
- 点击关闭按钮(`.bim-dialog-close`
- 调用 `close()` 方法
- **行为**: 弹窗从 DOM 移除,调用 `onClose` 回调
### 5.5 响应式行为
- 弹窗位置会根据容器尺寸自动调整,确保不超出边界
- 内容区域支持滚动(`overflow: auto`
- 标题文本过长时显示省略号(`text-overflow: ellipsis`
---
## 6. 逻辑流程详细描述
### 6.1 初始化流程
1. **构造函数调用**:
- 合并配置选项(用户配置 + 默认配置)
- 调用 `createDom()` 创建 DOM 结构
- 保存 header 和 contentArea 引用
- 自动调用 `init()`
2. **init() 方法执行**:
- 检查是否已初始化,如果是则返回
- 将弹窗元素添加到容器
- 调用 `initPosition()` 计算并设置位置
- 如果 `draggable` 为 true调用 `initDrag()`
- 如果 `resizable` 为 true调用 `initResize()`
- 标记为已初始化
- 调用 `onOpen` 回调(如果存在)
- 订阅主题变更:`themeManager.subscribe()`
- 订阅语言变更:`localeManager.subscribe()`
### 6.2 生命周期
#### 创建阶段
- 构造函数 → `createDom()``init()`
#### 运行阶段
- 响应主题变更:`setTheme()` 被调用
- 响应语言变更:`setLocales()` 被调用
- 用户交互:拖拽、缩放、关闭
#### 销毁阶段
- `close()``destroy()` 被调用
- 清理 `requestAnimationFrame`
- 取消主题和语言订阅
- 从 DOM 移除元素
- 调用 `onClose` 回调
### 6.3 事件处理流程
#### 拖拽事件流程
1. 用户在标题栏按下鼠标 → `mousedown` 事件
2. 记录起始位置和弹窗当前位置
3. 缓存容器和弹窗尺寸
4. 在 document 上绑定 `mousemove``mouseup`(捕获阶段)
5. 鼠标移动时 → `mousemove` 事件
- 计算偏移量
- 使用 `requestAnimationFrame` 更新位置
- 限制在容器边界内
6. 鼠标释放时 → `mouseup` 事件
- 清理事件监听
- 清理动画帧
#### 缩放事件流程
1. 用户在缩放手柄按下鼠标 → `mousedown` 事件
2. 记录起始位置和弹窗当前尺寸
3. 在 document 上绑定 `mousemove``mouseup`(捕获阶段)
4. 鼠标移动时 → `mousemove` 事件
- 计算偏移量
- 使用 `requestAnimationFrame` 更新尺寸
- 限制在最小尺寸以上
5. 鼠标释放时 → `mouseup` 事件
- 清理事件监听
- 清理动画帧
#### 事件拦截流程
- 弹窗根元素上绑定所有鼠标/触摸事件
- 使用 `stopPropagation()` 阻止事件冒泡
- 防止事件传递给 3D 引擎
- 使用 `passive: false` 允许阻止默认行为
### 6.4 状态管理
#### 内部状态
- `_isInitialized`: 是否已初始化
- `_isDestroyed`: 是否已销毁
- `rafId`: `requestAnimationFrame` 的 ID用于节流
#### 订阅管理
- `unsubscribeTheme`: 主题订阅取消函数
- `unsubscribeLocale`: 语言订阅取消函数
### 6.5 与其他组件的交互
- **与 DialogManager**: 通过 Manager 创建和管理
- **与 ThemeManager**: 订阅主题变更
- **与 LocaleManager**: 订阅语言变更
- **与 3D 引擎**: 通过事件拦截防止交互冲突
---
## 7. 国际化支持
### 7.1 使用的翻译键
- `options.title`: 弹窗标题(如果提供,会通过 `t()` 函数翻译)
### 7.2 语言变更处理
- 组件订阅 `localeManager.subscribe()`
- 语言变更时,`setLocales()` 方法被调用
- 更新标题文本:`titleEl.textContent = t(this.options.title)`
### 7.3 实现细节
```typescript
public setLocales(): void {
if (this.options.title) {
const titleEl = this.header.querySelector('.bim-dialog-title');
if (titleEl) {
titleEl.textContent = t(this.options.title);
}
}
}
```
---
## 8. 主题支持
### 8.1 使用的主题变量
- `theme.panelBackground``--bim-dialog-bg`
- `theme.componentHover``--bim-dialog-header-bg`
- `theme.textPrimary``--bim-dialog-title-color``--bim-dialog-text-color`
- `theme.border``--bim-dialog-border-color`
### 8.2 主题变更处理
- 组件订阅 `themeManager.subscribe()`
- 主题变更时,`setTheme()` 方法被调用
- 如果用户未自定义颜色,使用主题颜色
- 如果用户已自定义颜色,保持用户自定义
### 8.3 实现细节
```typescript
public setTheme(theme: ThemeConfig) {
const style = this.element.style;
if (!this.options.backgroundColor)
style.setProperty('--bim-dialog-bg', theme.panelBackground);
if (!this.options.headerBackgroundColor)
style.setProperty('--bim-dialog-header-bg', theme.componentHover);
// ... 其他颜色映射
}
```
---
## 9. 使用示例
### 9.1 基本使用(通过 Manager
```typescript
import { BimEngine } from 'iflow-engine';
const engine = new BimEngine('container');
// 创建简单弹窗
const dialog = engine.dialog.create({
title: 'dialog.testTitle', // 使用翻译键
content: '这是内容',
width: 400,
height: 300
});
// 创建可缩放弹窗
const resizableDialog = engine.dialog.create({
title: '可缩放弹窗',
content: '<div>内容</div>',
width: 500,
height: 400,
resizable: true,
draggable: true
});
// 创建自定义位置弹窗
const positionedDialog = engine.dialog.create({
title: '自定义位置',
content: '内容',
position: { x: 100, y: 100 },
width: 300,
height: 200
});
```
### 9.2 高级使用
```typescript
// 创建带自定义样式的弹窗
const styledDialog = engine.dialog.create({
title: '自定义样式',
content: '<div>内容</div>',
backgroundColor: 'rgba(100, 0, 0, 0.95)',
headerBackgroundColor: '#cc0000',
titleColor: '#ffffff',
textColor: '#ffcccc',
borderColor: '#ff6666',
width: 300,
height: 200
});
// 创建带回调的弹窗
const callbackDialog = engine.dialog.create({
title: '带回调',
content: '内容',
onOpen: () => {
console.log('弹窗已打开');
},
onClose: () => {
console.log('弹窗已关闭');
}
});
// 动态更新内容
const dynamicDialog = engine.dialog.create({
title: '动态内容',
content: '初始内容'
});
// 稍后更新内容
dynamicDialog.setContent('<div>新内容</div>');
```
### 9.3 使用信息弹窗
```typescript
// 通过 Manager 显示信息弹窗
engine.dialog.showInfoDialog();
// 通过事件总线打开(如果配置了监听)
engine.emit('ui:open-dialog', { id: 'info' });
```
---
## 10. 实现细节(供 AI 重现)
### 10.1 关键算法
#### 位置计算算法
```typescript
// 计算居中位置
function calculateCenterPosition(containerWidth, containerHeight, dialogWidth, dialogHeight) {
return {
left: (containerWidth - dialogWidth) / 2,
top: (containerHeight - dialogHeight) / 2
};
}
// 边界限制算法
function clampPosition(left, top, containerWidth, containerHeight, dialogWidth, dialogHeight) {
return {
left: Math.max(0, Math.min(left, containerWidth - dialogWidth)),
top: Math.max(0, Math.min(top, containerHeight - dialogHeight))
};
}
```
#### 拖拽算法
```typescript
// 拖拽位置更新
function updateDragPosition(startX, startY, currentX, currentY, startLeft, startTop) {
const deltaX = currentX - startX;
const deltaY = currentY - startY;
return {
left: startLeft + deltaX,
top: startTop + deltaY
};
}
```
#### 缩放算法
```typescript
// 缩放尺寸更新
function updateResizeSize(startX, startY, currentX, currentY, startWidth, startHeight, minWidth, minHeight) {
const deltaX = currentX - startX;
const deltaY = currentY - startY;
return {
width: Math.max(minWidth, startWidth + deltaX),
height: Math.max(minHeight, startHeight + deltaY)
};
}
```
### 10.2 性能优化点
1. **requestAnimationFrame 节流**:
- 拖拽和缩放使用 `requestAnimationFrame` 节流更新
- 避免频繁的 DOM 操作
2. **尺寸缓存**:
- 拖拽开始时缓存容器和弹窗尺寸
- 减少 `getBoundingClientRect()` 调用
3. **事件捕获**:
- 使用 `capture: true` 确保事件在捕获阶段处理
- 即使内部元素阻止冒泡,也能捕获事件
4. **CSS 变量**:
- 使用 CSS 变量应用主题
- 主题变更时无需重新渲染 DOM
### 10.3 注意事项和边界情况
1. **容器尺寸为 0**:
- 位置计算时可能出现除零或负数
- 使用 `Math.max()` 确保位置不为负
2. **弹窗尺寸大于容器**:
- 位置计算时确保弹窗不超出容器
- 使用边界限制算法
3. **快速连续操作**:
- 使用 `rafId` 防止多个动画帧同时执行
- 确保动画帧正确清理
4. **事件清理**:
- 组件销毁时必须清理所有事件监听
- 必须取消主题和语言订阅
5. **事件拦截**:
- 必须拦截所有鼠标/触摸事件
- 防止事件传递给 3D 引擎
6. **翻译键处理**:
- 标题可能是翻译键或普通文本
- 只有翻译键才需要通过 `t()` 函数处理
---
## 11. 类型定义
### 11.1 DialogOptions
```typescript
interface DialogOptions extends DialogColors {
container: HTMLElement; // 弹窗挂载的父容器(必需)
title?: string; // 弹窗标题(可选,支持翻译键)
content?: HTMLElement | string; // 弹窗内容(可选)
width?: number | string; // 宽度(可选,默认 300
height?: number | string; // 高度(可选,默认 'auto'
position?: DialogPosition; // 位置(可选,默认 'center'
draggable?: boolean; // 是否可拖拽(可选,默认 true
resizable?: boolean; // 是否可缩放(可选,默认 false
minWidth?: number; // 最小宽度(可选,默认 200
minHeight?: number; // 最小高度(可选,默认 100
onClose?: () => void; // 关闭回调(可选)
onOpen?: () => void; // 打开回调(可选)
id?: string; // 弹窗 ID可选
}
```
### 11.2 DialogPosition
```typescript
type DialogPosition =
| 'center'
| 'top-left' | 'top-center' | 'top-right'
| 'left-center' | 'right-center'
| 'bottom-left' | 'bottom-center' | 'bottom-right'
| { x: number; y: number };
```
### 11.3 DialogColors
```typescript
interface DialogColors {
backgroundColor?: string; // 窗体背景颜色
headerBackgroundColor?: string; // 标题栏背景颜色
titleColor?: string; // 标题文字颜色
textColor?: string; // 内容文字颜色
borderColor?: string; // 边框颜色
}
```
---
## 12. 文件清单
### 12.1 相关文件
- `src/components/dialog/index.ts` - 主组件类
- `src/components/dialog/index.type.ts` - 类型定义
- `src/components/dialog/index.css` - 样式文件
- `src/components/dialog/bimInfoDialog/index.ts` - 信息弹窗(分化组件)
- `src/components/dialog/bimInfoDialog/index.css` - 信息弹窗样式
- `src/managers/dialog-manager.ts` - 管理器类
### 12.2 依赖文件
- `src/types/component.ts` - IBimComponent 接口
- `src/themes/types.ts` - ThemeConfig 类型
- `src/services/theme.ts` - ThemeManager
- `src/services/locale.ts` - LocaleManager
---
## 13. 更新记录
| 日期 | 修改内容 | 修改人 |
|------|---------|--------|
| 2024-XX-XX | 初始创建 | AI Assistant |
---
**重要提醒**: 本文档必须与组件代码保持同步。任何组件修改都必须更新本文档!

View File

@@ -0,0 +1,97 @@
# Collapse 折叠面板组件
`BimCollapse` 是一个通用的折叠面板组件,支持手风琴模式、自定义内容和标题。常用于属性面板、设置菜单等场景。
## 1. 组件概述
- **类名**: `BimCollapse`
- **文件路径**: `src/components/collapse/index.ts`
- **样式文件**: `src/components/collapse/index.css`
- **类型定义**: `src/components/collapse/types.ts`
该组件实现了 `IBimComponent` 接口,具备完整的生命周期管理、主题响应和国际化支持能力。
## 2. API 文档
### 2.1 构造函数
```typescript
const collapse = new BimCollapse(options: CollapseOptions);
```
### 2.2 CollapseOptions 配置项
| 属性 | 类型 | 默认值 | 说明 |
|------|------|-------|------|
| `container` | `HTMLElement \| string` | - | **(必填)** 挂载容器或其 ID |
| `items` | `CollapseItemConfig[]` | - | **(必填)** 面板项列表 |
| `accordion` | `boolean` | `false` | 是否开启手风琴模式(一次只能展开一项) |
| `activeIds` | `string[]` | `[]` | 初始展开的面板 ID 列表 |
| `bordered` | `boolean` | `true` | 是否显示外边框 |
| `ghost` | `boolean` | `false` | 是否开启幽灵模式(无背景、无边框) |
| `className` | `string` | - | 自定义类名 |
| `onChange` | `(activeIds: string[]) => void` | - | 切换面板时的回调函数 |
### 2.3 CollapseItemConfig 面板项配置
| 属性 | 类型 | 说明 |
|------|------|------|
| `id` | `string` | **(必填)** 唯一标识符 |
| `title` | `string` | **(必填)** 标题文本的**翻译键** (例如 `'panel.property.base'`) |
| `content` | `string \| HTMLElement` | **(必填)** 面板内容 |
| `icon` | `string` | 标题左侧图标 (SVG 字符串) |
| `extra` | `string \| HTMLElement` | 标题栏右侧额外内容 |
| `disabled` | `boolean` | 是否禁用该面板 |
| `className` | `string` | 自定义面板类名 |
### 2.4 实例方法
- **`toggleItem(id: string)`**: 切换指定面板的展开/折叠状态。
- **`setTheme(theme: ThemeConfig)`**: 设置组件主题 (CSS 变量映射)。
- **`setLocales()`**: 更新组件文本 (标题翻译)。
- **`destroy()`**: 销毁组件,清理资源。
## 3. 使用示例
```typescript
import { BimCollapse } from '../components/collapse/index';
const collapse = new BimCollapse({
container: document.getElementById('panel'),
accordion: true,
activeIds: ['base'],
items: [
{
id: 'base',
title: 'panel.property.base', // 必须是翻译键
content: document.createElement('div'), // 或 HTML 字符串
icon: '<svg>...</svg>'
},
{
id: 'advanced',
title: 'panel.property.advanced',
content: 'Advanced Content',
disabled: true
}
],
onChange: (ids) => {
console.log('Current active panels:', ids);
}
});
```
## 4. 主题支持
组件自动订阅主题变更,并通过 CSS 变量控制样式:
- **面板背景**: `theme.panelBackground`
- **边框颜色**: `theme.border`
- **文本颜色**: `theme.textPrimary`
- **标题栏背景**: `theme.componentHover` (默认) / `theme.componentBackground`
- **禁用态颜色**: `theme.textSecondary`
## 5. 国际化支持
组件自动订阅语言变更:
- **标题 (`title`)**: 会自动使用 `t(config.title)` 进行翻译。确保传入的是有效的翻译键。
- **内容 (`content`)**: 如果内容包含文本,请确保内容生成时已翻译,或内容本身具有响应国际化的能力(如使用 `BimDescription`)。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,127 @@
# Description 描述列表组件
`BimDescription` 组件用于展示一组键值对Key-Value数据常用于详情页、属性面板等场景。
## 1. 组件概述
- **类名**: `BimDescription`
- **文件路径**: `src/components/description/index.ts`
- **样式文件**: `src/components/description/index.css`
- **类型定义**: `src/components/description/types.ts`
该组件是一个**纯展示组件**,其特点是:
- **不内置国际化**: 为了最大灵活性Label 和 Value 均直接显示传入的字符串/元素,不调用翻译函数。调用者应负责传入已翻译的文本。
- **高度定制化**: 支持全局或行级的颜色、Padding、字体大小定制。
- **布局灵活**: 支持普通列表模式和带边框的表格模式Key-Value 间有纵向分割线)。
## 2. API 文档
### 2.1 构造函数
```typescript
const description = new BimDescription(options: DescriptionOptions);
```
### 2.2 DescriptionOptions 配置项
| 属性 | 类型 | 默认值 | 说明 |
|------|------|-------|------|
| `container` | `HTMLElement \| string` | - | **(必填)** 挂载容器或其 ID |
| `items` | `DescriptionItem[]` | - | **(必填)** 数据项列表 |
| `bordered` | `boolean` | `false` | 是否显示边框。开启后会显示行间分割线以及 Key-Value 之间的纵向分割线 |
| `labelWidth` | `string` | - | 标签列的固定宽度 (如 `'80px'`),不设置则自适应 |
| `fontSize` | `string` | `'14px'` | 全局字体大小 (如 `'12px'`) |
| `labelColor` | `string` | `theme.textSecondary` | 全局标签颜色 |
| `valueColor` | `string` | `theme.textPrimary` | 全局内容颜色 |
| `labelPadding` | `string` | `'0 4px'` | 标签单元格内边距 (CSS padding 语法) |
| `valuePadding` | `string` | `'0 4px'` | 内容单元格内边距 (CSS padding 语法) |
| `className` | `string` | - | 自定义类名 |
### 2.3 DescriptionItem 数据项
| 属性 | 类型 | 说明 |
|------|------|------|
| `label` | `string` | 标签文本 (直接显示,在非 bordered 模式下会自动添加冒号) |
| `value` | `string \| HTMLElement` | 内容文本或 DOM 元素 |
| `labelColor` | `string` | 行级自定义标签颜色 (优先级高于全局) |
| `valueColor` | `string` | 行级自定义内容颜色 (优先级高于全局) |
| `className` | `string` | 自定义行类名 |
### 2.4 实例方法
- **`setItems(items: DescriptionItem[])`**: 动态更新数据列表。
- **`setTheme(theme: ThemeConfig)`**: <20><>置组件主题 (通常自动调用)。
- **`destroy()`**: 销毁组件,清理 DOM 和事件订阅。
## 3. 使用示例
### 3.1 基础使用
```typescript
new BimDescription({
container: document.getElementById('container'),
labelWidth: '80px',
items: [
{ label: 'Name', value: 'Wall-01' },
{ label: 'ID', value: 'E-1001' },
{ label: 'Level', value: 'Level 1' }
]
});
```
### 3.2 带边框的表格模式 (Bordered)
```typescript
new BimDescription({
container: document.getElementById('container'),
bordered: true,
fontSize: '12px',
labelPadding: '4px 8px',
valuePadding: '4px 8px',
items: [
{ label: 'Material', value: 'Concrete' },
{ label: 'Density', value: '2400 kg/m³' }
]
});
```
### 3.3 自定义样式
```typescript
new BimDescription({
container: 'container',
labelColor: '#999',
valueColor: '#333',
items: [
{
label: 'Status',
value: '<span style="color: green">Active</span>',
labelColor: 'blue' // 单独覆盖此行的标签颜色
}
]
});
```
## 4. 主题支持
组件会自动响应系统主题变更。默认映射关系如下:
- **文本颜色**: `theme.textPrimary`
- **标签颜色**: `theme.textSecondary`
- **边框颜色**: `theme.border`
- **标签背景 (仅 Bordered 模式)**: `theme.background` (用于轻微区分 Key 和 Value)
## 5. 国际化支持
**组件内部不进行翻译**。调用者应在传入 `label` 之前使用 `t()` 函数进行翻译。
```typescript
import { t } from 'iflow-engine/services/locale';
new BimDescription({
// ...
items: [
{ label: t('panel.property.id'), value: '123' }
]
});
```

View File

@@ -0,0 +1,92 @@
## 1. 组件概述
- **名称**BimTab
- **位置**`src/components/tab/`
- **功能**:渲染固定标签页(不支持运行期增删),支持点击切换、禁用、可选内容托管。主题由全局 `ThemeManager` 提供,文案通过 `t()` 翻译。
- **使用方式**:直接通过构造函数 + `init()` 创建;本项目内目前仅在 `ConstructTreeManagerBtn` 弹窗中使用(无专门 Manager
## 2. 组件类 APIBimTab
- `constructor(options: TabOptions)`:创建实例并挂载到 `options.container`
- `init(): void`:渲染头部与内容,订阅主题/语言。
- `activateTab(tabId: string): void`:切换激活标签,触发 `onChange`
- `setTheme(theme: ThemeConfig): void`:应用主题变量。
- `setLocales(): void`:刷新标题文案。
- `destroy(): void`:解绑事件、取消订阅、移除 DOM。
## 3. 分化组件
- 当前无子类或变体,后续可扩展滚动、溢出折叠等能力。
## 4. 使用场景说明
- **ConstructTreeManagerBtn 弹窗**:顶部三段标签(构件/系统/空间),构件标签承载原有 Tree系统/空间暂为空容器。
- 适用于固定分区切换,内容简单或由外部回调控制。
## 5. UI 结构
```
div.bim-tab
div.bim-tab__nav [role=tablist]
button.bim-tab__item[role=tab][aria-selected][aria-disabled?]
span.bim-tab__icon? (可选)
span.bim-tab__title
div.bim-tab__content
div.bim-tab__panel[role=tabpanel][aria-labelledby=tab-xxx]
...
```
- 状态类:`.is-active``.is-disabled`
- 内容区:仅切换显示,不强制填充(可为空)。
## 6. 逻辑流程
1) `constructor`:创建根节点、头部、内容容器;缓存 tab 数据;挂载到传入容器。
2) `init`:渲染头部按钮与内容面板,设置初始激活;应用当前主题/语言;订阅主题、语言变更。
3) 交互:点击非禁用按钮 → `activateTab` 更新头部/面板状态 → 回调 `onChange`
4) 销毁:解绑点击监听、取消订阅、清空映射并移除 DOM。
## 7. 国际化
- 标题:`t(tab.title)`,若翻译键不存在则回退原文。
- 新增翻译键:`tab.component` / `tab.system` / `tab.space`(已在 `zh-CN.ts``en-US.ts` 注册)。
- `setLocales()`:遍历头部按钮刷新标题文案;订阅 `localeManager` 自动响应语言切换。
## 8. 主题支持
- 来自 `themeManager`,不从配置传入。
- 使用的变量(设置在根节点上):
- `--bim-tab-bg``--bim-tab-nav-bg`
- `--bim-tab-text``--bim-tab-text-secondary``--bim-tab-text-active`
- `--bim-tab-border``--bim-tab-hover-bg``--bim-tab-active-bg`
- `--bim-tab-icon`
- `setTheme(theme)`:根据 `ThemeConfig` 写入上述 CSS 变量。
## 9. 使用示例
```ts
const tabMount = document.createElement('div');
const tab = new BimTab({
container: tabMount,
tabs: [
{ id: 'component', title: 'tab.component', content: componentEl },
{ id: 'system', title: 'tab.system', content: systemEl },
{ id: 'space', title: 'tab.space', content: spaceEl },
],
activeId: 'component',
onChange: (id) => {
// 根据 id 做额外逻辑(如埋点、动态加载)
},
});
tab.init();
```
## 10. 实现细节
- 仅支持固定 tabs`TabOptions.tabs` 为初始化列表,不提供新增/删除接口。
- 内容托管两种方式:`content: HTMLElement`append`content: string`innerHTML。未提供内容时面板为空。
- 事件绑定:头部使用事件委托绑定 click销毁时移除。
- 访问性:头部 `role=tab``aria-selected``aria-disabled`;面板 `role=tabpanel``aria-labelledby` 对应头部 id。
- 主题/语言订阅:`localeManager.subscribe``themeManager.subscribe`,销毁时必须取消。
## 11. 类型定义
- 位置:`src/components/tab/index.type.ts`
- 主要类型:
- `TabItem`: `{ id; title; disabled?; icon?; content?; meta? }`
- `TabOptions`: `{ container; tabs; activeId?; onChange? }`
## 12. 文件清单
- `src/components/tab/index.ts`:组件实现
- `src/components/tab/index.type.ts`:类型定义
- `src/components/tab/index.css`:样式
- (无 Manager当前仅在 `ConstructTreeManagerBtn` 中直接使用

View File

@@ -0,0 +1,108 @@
# Tree 组件文档
## 1. 组件概述
**BimTree** 是一个通用的树形组件,支持多级嵌套、复选框、图标和自定义内容。它通常用于展示模型结构、文件目录或层级列表。
该组件设计为被包裹在容器(如 Dialog并通过 `ModelTreeManager` 进行创建和管理。
## 2. 组件类 API (BimTree)
`src/components/tree/index.ts`
### 2.1 核心方法
* `checkNode(id: string, checked: boolean)`: 勾选/取消勾选指定节点。支持父子联动。
* `checkAll(checked: boolean)`: 全选或全不选。
* `expandNode(id: string, expanded: boolean)`: 展开/折叠指定节点。
* `expandAll(expanded: boolean)`: 展开/折叠所有层级。
* `getCheckedNodes(includeHalfChecked?: boolean)`: 获取当前所有被勾选的节点配置列表。
* `getNode(id: string)`: 获取指定 ID 的节点实例。
## 3. Manager API (ModelTreeManager)
`ModelTreeManager` 负责创建和管理 `BimTree` 实例。
### 方法
#### `createTree(options: TreeOptions): BimTree`
创建一个新的树组件实例。
- **参数**: `options` (TreeOptions) - 树组件配置
- **返回**: `BimTree` - 树组件实例
- **功能**:
- 实例化组件
- 自动绑定组件事件到全局事件总线 (`ui:tree-node-check`, `ui:tree-node-select` 等)
- 初始化组件
#### `showStructTree(data: TreeNodeConfig[], title: string): { tree: BimTree, dialog: BimDialog }`
显示带复选框的模型结构树弹窗。
#### `showSimpleTree(data: TreeNodeConfig[], title: string): { tree: BimTree, dialog: BimDialog }`
显示简单的树形弹窗 (无复选框)。
---
## 4. 类型定义
`src/components/tree/types.ts`
```typescript
interface TreeNodeConfig {
id: string;
label: string; // 翻译键
children?: TreeNodeConfig[];
checked?: boolean;
expanded?: boolean;
disabled?: boolean;
icon?: string;
data?: any; // 业务数据
}
interface TreeOptions {
data: TreeNodeConfig[];
checkable?: boolean; // 是否显示复选框
checkStrictly?: boolean; // 是否父子联动 (默认 true)
defaultExpandAll?: boolean;
indent?: number;
// 事件回调
onNodeCheck?: (node: BimTreeNode) => void;
onNodeSelect?: (node: BimTreeNode) => void;
onNodeExpand?: (node: BimTreeNode) => void;
}
```
## 5. UI 结构与样式
* **容器**: `.bim-tree` (Flex column)
* **节点**: `.bim-tree-node`
* **内容行**: `.bim-tree-node-content` (Flex row, align-center)
* **箭头**: `.bim-tree-switcher` (SVG, rotate transform)
* **复选框**: `.bim-tree-checkbox` (自定义样式, support indeterminate)
* **图标**: `.bim-tree-icon`
* **文本**: `.bim-tree-title`
* **子容器**: `.bim-tree-children` (padding-left 缩进)
## 6. 逻辑流程
1. **初始化**:
* 根据 `data` 递归创建 `BimTreeNode` 实例。
* 建立 `id -> Node` 的 Map 索引。
* 订阅主题和语言变更。
2. **联动逻辑 (Check Cascade)**:
* **向下**: 父节点状态变更 -> 递归强制设置所有子节点。
* **向上**: 子节点状态变更 -> 冒泡检查兄弟节点状态 -> 更新父节点 (Checked/Unchecked/Indeterminate)。
3. **事件**:
* 点击复选框 -> 更新状态 -> 触发联动 -> 发送 `ui:tree-node-check`
* 点击内容 -> 发送 `ui:tree-node-select`
## 7. 国际化支持
* 节点 `label` 必须是翻译键。
* 组件订阅 `localeManager`,语言变更时自动刷新文本。
## 8. 使用示例
```typescript
// 通过 ModelTreeManager 创建
const tree = engine.modelTree.createTree({
data: [
{ id: 'root', label: 'tree.root', children: [...] }
],
checkable: true
});
// 挂载到 DOM
document.body.appendChild(tree.element);
```

View File

@@ -0,0 +1,305 @@
# MeasurePanel 组件详细文档
> 本文档详细描述 `MeasurePanel`(测量面板)组件的实现细节,包括 API、UI 结构、逻辑流程等,供后续维护/AI 重现。
>
> 重要说明:**本组件仅实现 UI不实现真实测量算法**(不做拾取、画线、计算等)。测量结果通过对外方法注入,仅用于展示。
---
## 1. 组件概述
### 1.1 基本信息
- **组件名称**`MeasurePanel`
- **文件路径**`src/components/measure-panel/index.ts`
- **类型定义**`src/components/measure-panel/types.ts`
- **样式文件**`src/components/measure-panel/index.css`
- **实现接口**`IBimComponent`
### 1.2 在 SDK 中的位置
- `MeasurePanel` 是内部 UI 组件
-`MeasureDialogManager` 创建并挂载到 `BimDialog`
- 外部业务SDK 使用者)不直接 import 组件类,统一通过 `engine.measure`Manager调用
### 1.3 配置项(单位/精度)与缓存策略(新增)
- **创建 `MeasurePanel` 不传入单位/精度**
- 默认配置由组件内部维护:
- `unit`: `'mm'`
- `precision`: `2`(即 `0.00`
- 组件初始化时会读取缓存(`localStorage`
- key`bim-engine:measure:config`
- 若缓存存在且合法,则使用缓存值覆盖默认配置
- 若缓存不存在/解析失败,则使用默认配置
- 用户在设置面板点击“保存设置”后,组件会写入缓存
---
## 2. 组件类 API 文档
### 2.1 构造函数
```typescript
constructor(options?: MeasurePanelOptions)
```
**参数**
- `options.defaultMode?`: 默认测量方式(不传默认 `distance`
- `options.defaultExpanded?`: 是否默认展开(不传默认 `false`,即只显示前 4 个)
- `options.onModeChange?`: 用户切换测量方式时回调
- `options.onClearAll?`: 用户点击“删除全部”时回调
- `options.onSettings?`: 用户点击“设置”时回调
### 2.2 公共方法
#### `init(): void`
- 初始化订阅(主题/语言),并刷新 UI 状态(展开/选中态/结果区)。
#### `setTheme(theme: ThemeConfig): void`
- 将主题色映射到 CSS 变量按钮背景、hover、active、文字、分割线等
#### `setLocales(): void`
- 更新所有用户可见文本:
- 8 个按钮的 tooltip图标占位时 tooltip 是主要可读文本)
- 展开/收起按钮 tooltip
- “删除全部”文本
- “设置”tooltip
- “当前测量方式”显示文本
- 主值 label随模式变化
- X/Y/Z 标签
#### `destroy(): void`
- 取消主题/语言订阅并移除 DOM。
#### `getActiveMode(): MeasureMode`
- 获取当前选中的测量方式。
#### `switchMode(mode: MeasureMode): void`
- **切换类型的方法**:切换当前测量方式(等价于 `setActiveMode`)。
#### `setActiveMode(mode: MeasureMode): void`
- 设置当前测量方式,并触发 `onModeChange`(如果提供)。
#### `setResult(result: MeasureResult | null): void`
- 外部注入测量结果:
- `null`:清空显示(主值与 xyz 均显示 `--`
- 非 null根据当前 mode 显示对应字段的值
#### `clearAll(): void`
- 清空结果展示并触发 `onClearAll`(如果提供)。
#### `openSettings(): void`
- 进入组件内部“设置面板”(单位/精度选择)。
- 同时触发 `onSettings`(如果提供,作为外部监听)。
#### `getConfig(): MeasureConfig`
- 获取当前测量配置(单位/精度)。
#### `setConfig(partial: Partial<MeasureConfig>, persist = false): void`
- 更新配置:
- `persist=false`:仅更新内存,不写缓存
- `persist=true`:更新并写入 `localStorage`
#### `setExpanded(expanded: boolean): void`
- 展开/收起按钮区(收起时只显示前 4 个)。
#### `getExpanded(): boolean`
- 获取当前是否展开。
---
## 3. 分化组件说明
-
---
## 4. Manager API 文档(关联)
### 4.1 MeasureDialogManager关联文件
- `src/managers/measure-dialog-manager.ts`
### 4.2 相关对外方法(本次新增/补齐)
- `getActiveMode(): MeasureMode | null`
- `switchMode(mode: MeasureMode): void`
- `setResult(result: MeasureResult | null): void`
- `clearAll(): void`
- `openSettings(): void`
---
## 5. UI 详细描述
### 5.1 DOM 结构(核心)
```html
<div class="bim-measure-panel">
<!-- 主视图 -->
<div class="bim-measure-main">
<div class="bim-measure-tools">
<div class="bim-measure-tool-grid">
<!-- 8 个按钮:收起时隐藏后 4 个 -->
<button class="bim-measure-tool-btn is-active" data-mode="distance">
<span class="bim-measure-tool-icon">(圆形占位 svg</span>
</button>
<!-- ... -->
</div>
<div class="bim-measure-toggle">
<button class="bim-measure-toggle-btn">
<!-- 箭头 svg展开时旋转 180deg -->
</button>
</div>
</div>
<div class="bim-measure-result">
<div class="bim-measure-row">
<span class="label">当前测量方式:</span>
<span class="value">距离</span>
</div>
<div class="bim-measure-row">
<span class="label">距离:</span>
<span class="value">--</span>
</div>
<div class="bim-measure-xyz">
<div class="bim-measure-row"><span class="label">X</span><span class="value">--</span></div>
<div class="bim-measure-row"><span class="label">Y</span><span class="value">--</span></div>
<div class="bim-measure-row"><span class="label">Z</span><span class="value">--</span></div>
</div>
</div>
<div class="bim-measure-footer">
<button class="bim-measure-clear-btn">删除全部</button>
<button class="bim-measure-settings-btn">(齿轮 svg</button>
</div>
</div>
<!-- 设置视图(点击设置按钮进入) -->
<div class="bim-measure-settings">
<div class="bim-measure-settings-title">设置</div>
<div class="bim-measure-settings-row">
<div class="label">单位:</div>
<select class="bim-measure-settings-select">
<option value="m">m</option>
<option value="cm">cm</option>
<option value="mm">mm</option>
<option value="km">km</option>
</select>
</div>
<div class="bim-measure-settings-row">
<div class="label">精度:</div>
<select class="bim-measure-settings-select">
<option value="0">0</option>
<option value="1">0.0</option>
<option value="2">0.00</option>
<option value="3">0.000</option>
</select>
</div>
<div class="bim-measure-settings-actions">
<button class="bim-measure-settings-save">保存设置</button>
<button class="bim-measure-settings-cancel">取消</button>
</div>
</div>
</div>
```
### 5.2 CSS 类名说明
- `.bim-measure-tool-grid`: 4 列网格布局
- `.bim-measure-tool-btn.is-active`: 当前选中态
- `.bim-measure-toggle-btn.is-expanded`: 展开态(箭头旋转)
- `.bim-measure-result`: 结果区,顶部与底部用分割线隔开
---
## 6. 逻辑流程详细描述
### 6.1 初始化
- 构造函数创建 DOM但不订阅
- `init()`
- 订阅 `localeManager``themeManager`
- 调用 `setLocales()``setTheme()`
- 应用展开状态(隐藏/显示后 4 个按钮)
- 应用选中态
- 渲染结果(初始为 `--`
### 6.2 切换测量方式
- 点击按钮或调用 `switchMode/setActiveMode`
- 更新内部 `activeMode`
- 刷新按钮选中态
- 更新“当前测量方式”文本
- 更新主值 label
- 触发 `onModeChange`(若提供)
- 重新渲染结果
### 6.3 注入结果
- 调用 `setResult(result)`
- 保存 result 并刷新结果区:
- 主值:根据 mode 显示对应字段
- xyz无则显示 `--`
---
## 7. 国际化支持
### 7.1 使用的翻译键(核心)
- `measure.modes.*`8 种模式名
- `measure.actions.*`:展开/收起/删除全部/设置
- `measure.labels.*`当前方式、X/Y/Z、主值 label
- `measure.units.*`mm/°/m³/% 等单位
---
## 8. 主题支持
### 8.1 CSS 变量(核心)
- `--bim-measure-border`
- `--bim-measure-divider`
- `--bim-measure-btn-bg` / `--bim-measure-btn-hover-bg` / `--bim-measure-btn-active-bg`
- `--bim-measure-label-color` / `--bim-measure-value-color`
- `--bim-measure-icon-color`
---
## 9. 使用示例(通过 Manager
```typescript
// 通过工具栏点击“测量”按钮打开弹窗后:外部可以这样注入展示数据
engine.measure.switchMode('angle');
engine.measure.setResult({ angleDeg: 12.34, xyz: { x: 1, y: 2, z: 3 } });
// 清空
engine.measure.clearAll();
```
---
## 10. 实现细节(供 AI 重现)
### 10.1 关键点
- 模式列表严格按需求顺序渲染
- 收起时隐藏后 4 个按钮(通过 `style.display = 'none'`
- 图标占位统一用圆形 SVG
- 主值显示使用 `formatWithUnit`(单位走国际化)
---
## 11. 类型定义
`src/components/measure-panel/types.ts`
- `MeasureMode`
- `MeasureResult`
- `MeasurePanelOptions`
---
## 12. 文件清单
- `src/components/measure-panel/index.ts`
- `src/components/measure-panel/index.css`
- `src/components/measure-panel/types.ts`
- 关联:`src/managers/measure-dialog-manager.ts`

View File

@@ -0,0 +1,594 @@
# WalkControlPanel 组件详细文档
> 本文档详细描述 WalkControlPanel 组件的实现细节,包括 API、UI 结构、逻辑流程等,供 AI 根据文档重现组件。
---
## 1. 组件概述
### 1.1 基本信息
- **组件名称**: `WalkControlPanel`
- **文件路径**: `src/components/walk-control-panel/index.ts`
- **类型定义**: `src/components/walk-control-panel/types.ts`
- **样式文件**: `src/components/walk-control-panel/index.css`
- **实现接口**: `IBimComponent`
- **用途**: 渲染漫游控制面板,提供平面图切换、路径漫游、人物漫游三种模式,以及速度、重力、碰撞、角色模型、行走模式等设置。
- **特点**: 纯 UI 组件,通过回调函数通知外部状态变化,支持主题和国际化。
### 1.2 在 SDK 中的位置
- WalkControlPanel 组件通常由 `WalkControlManager` 管理(如果存在)
- 也可以单独实例化并挂载到任何容器中
- 用于 3D 场景的漫游控制界面
---
## 2. 组件类 API 文档
### 2.1 构造函数
```typescript
constructor(options: WalkControlPanelOptions = {})
```
**参数**:
- `options`: `WalkControlPanelOptions` - 漫游控制面板配置选项(可选)
**WalkControlPanelOptions 定义**:
```typescript
interface WalkControlPanelOptions {
/** 平面图切换回调 */
onPlanViewToggle?: (isActive: boolean) => void;
/** 路径漫游模式切换回调 */
onPathModeToggle?: (isActive: boolean) => void;
/** 漫游模式切换回调 */
onWalkModeToggle?: (isActive: boolean) => void;
/** 速度变化回调 */
onSpeedChange?: (speed: number) => void;
/** 重力切换回调 */
onGravityToggle?: (enabled: boolean) => void;
/** 碰撞切换回调 */
onCollisionToggle?: (enabled: boolean) => void;
/** 角色模型变化回调 */
onCharacterModelChange?: (model: CharacterModel) => void;
/** 行走模式变化回调 */
onWalkModeChange?: (mode: WalkMode) => void;
/** 退出回调 */
onExit?: () => void;
/** 默认速度 (1-10) */
defaultSpeed?: number;
/** 默认重力状态 */
defaultGravity?: boolean;
/** 默认碰撞状态 */
defaultCollision?: boolean;
/** 默认角色模型 */
defaultCharacterModel?: CharacterModel;
/** 默认行走模式 */
defaultWalkMode?: WalkMode;
}
```
**行为**:
- 创建面板 DOM 结构
- 保存配置选项
- 初始化状态
### 2.2 公共方法
#### `init(): void`
初始化组件(实现 `IBimComponent` 接口)
**功能**:
- 创建并渲染 DOM 结构
- 订阅语言变更:`localeManager.subscribe()`
- 订阅主题变更:`themeManager.subscribe()`
- 更新初始视图状态
#### `setTheme(theme: ThemeConfig): void`
设置主题(实现 `IBimComponent` 接口)
**参数**:
- `theme`: `ThemeConfig`
**功能**:
- 将主题颜色映射到 CSS 变量:
- `--bim-walk-control-bg``theme.panelBackground`
- `--bim-walk-btn-hover``theme.componentHover`
- `--bim-walk-btn-active``theme.componentActive`
- `--bim-primary-color``theme.primary`
- `--bim-primary-hover``theme.primaryHover`
- `--bim-icon-color``theme.icon`
- `--bim-text-color``theme.textPrimary`
- `--bim-divider-color``theme.border`
- 以及其他速度控制、下拉框相关的主题变量
#### `setLocales(): void`
设置语言(实现 `IBimComponent` 接口)
**功能**:
- 更新速度标签文本:`t('walkControl.speed')`
- 更新复选框标签:`t('walkControl.gravity')`, `t('walkControl.collision')`
- 更新角色模型下拉框选项和标签
- 更新行走模式下拉框选项和标签
- 更新退出按钮文本:`t('walkControl.exit')`
#### `destroy(): void`
销毁组件(实现 `IBimComponent` 接口)
**功能**:
- 取消语言和主题订阅
- 从 DOM 中移除自身元素
#### `setPlanViewActive(active: boolean): void`
设置平面图按钮激活状态
**参数**:
- `active`: boolean - 是否激活
**功能**:
- 更新内部状态
- 更新按钮视觉状态
#### `setPathModeActive(active: boolean): void`
设置路径模式按钮激活状态
**参数**:
- `active`: boolean - 是否激活
**功能**:
- 更新模式状态
- 更新按钮视觉状态
- 路径模式激活时禁用重力和碰撞选项
#### `getState(): WalkControlState`
获取当前状态
**返回**: `WalkControlState` - 当前控制面板状态的副本
---
## 3. 分化组件说明
**WalkControlPanel 没有子类或分化组件**
---
## 4. Manager API 文档
目前 WalkControlPanel 没有专门的 Manager,通常由使用方直接创建和管理。
未来可以创建 `WalkControlManager` 来统一管理该组件的实例。
---
## 5. UI 详细描述
### 5.1 DOM 结构
```html
<div class="walk-control-panel">
<!-- 左侧按钮区 -->
<div class="walk-control-left">
<button class="walk-icon-btn walk-icon-btn-plan-view [active]">
[平面图 SVG图标]
</button>
<button class="walk-icon-btn walk-icon-btn-path [active]">
[路径 SVG图标]
</button>
<button class="walk-icon-btn walk-icon-btn-walk [active]">
[漫游 SVG图标]
</button>
</div>
<!-- 分割线 -->
<div class="walk-divider"></div>
<!-- 中间设置区 -->
<div class="walk-control-settings">
<!-- 速度控件(路径模式显示) -->
<div class="walk-speed-control">
<label class="walk-speed-label">速度</label>
<div class="walk-speed-group">
<button class="walk-speed-btn">-</button>
<div class="walk-speed-display">1X</div>
<button class="walk-speed-btn">+</button>
</div>
</div>
<!-- 角色模型选择(漫游模式显示) -->
<div class="walk-select-wrapper walk-select-wrapper-character-model">
<label class="walk-select-label">角色模型</label>
<select class="walk-select walk-select-character-model">
<option value="construction-worker">建筑工人</option>
<option value="office-male">办公室男性</option>
</select>
</div>
<!-- 行走模式选择(漫游模式显示) -->
<div class="walk-select-wrapper walk-select-wrapper-walk-mode">
<label class="walk-select-label">行走模式</label>
<select class="walk-select walk-select-walk-mode">
<option value="walk">行走</option>
<option value="run">奔跑</option>
</select>
</div>
<!-- 重力复选框 -->
<label class="walk-checkbox-wrapper walk-checkbox-gravity">
<input type="checkbox" class="walk-checkbox" [disabled]>
<span class="walk-checkbox-label">重力</span>
</label>
<!-- 碰撞复选框 -->
<label class="walk-checkbox-wrapper walk-checkbox-collision">
<input type="checkbox" class="walk-checkbox" [disabled]>
<span class="walk-checkbox-label">碰撞</span>
</label>
</div>
<!-- 分割线 -->
<div class="walk-divider"></div>
<!-- 右侧退出按钮 -->
<button class="walk-exit-btn">退出</button>
</div>
```
### 5.2 CSS 类名和样式
#### `.walk-control-panel` (根容器)
- `display: flex`, `align-items: center`
- `gap: 20px`, `padding: 8px 16px`
- `background`: `var(--bim-walk-control-bg)`
- `border-radius: 8px`
- `user-select: none`
#### `.walk-icon-btn` (左侧图标按钮)
- `width: 48px`, `height: 48px`
- `padding: 8px` (实际图标 32x32)
- `display: flex`, `align-items: center`, `justify-content: center`
- `background: transparent`
- `border: 2px solid transparent`, `border-radius: 6px`
- `cursor: pointer`, `transition: all 0.2s`
- `color`: `var(--bim-icon-color)`
#### `.walk-icon-btn:hover`
- `background`: `var(--bim-walk-btn-hover)`
#### `.walk-icon-btn.active`
- `background`: `var(--bim-walk-btn-active)`
#### `.walk-icon-btn svg`
- `width: 32px`, `height: 32px`
#### `.walk-divider` (分割线)
- `width: 1px`, `height: 40px`
- `background`: `var(--bim-divider-color)`
- `flex-shrink: 0`
#### `.walk-speed-control` (速度控件)
- `display: flex`, `align-items: center`, `gap: 12px`
#### `.walk-speed-btn` (速度按钮)
- `width: 32px`, `height: 32px`
- `background`: `var(--bim-speed-btn-bg)`
- `border: none`, `border-radius: 4px`
- `cursor: pointer`
- `:disabled``opacity: 0.5`, `cursor: not-allowed`
#### `.walk-checkbox-wrapper` (复选框包装)
- `display: flex`, `align-items: center`, `gap: 8px`
- `cursor: pointer`
#### `.walk-select-wrapper` (下拉框包装)
- `display: flex`, `align-items: center`, `gap: 8px`
#### `.walk-exit-btn` (退出按钮)
- `padding: 10px 24px`
- `background`: `var(--bim-primary-color)`
- `border: none`, `border-radius: 6px`
- `color: #fff`, `font-size: 14px`, `font-weight: 600`
- `cursor: pointer`, `transition: all 0.2s`
---
## 6. 逻辑流程详细描述
### 6.1 初始化流程 (`init`)
1. **创建面板**(`createPanel`):
- 创建根容器 `.walk-control-panel`
- 创建左侧按钮区(`createLeftButtons`)
- 创建中间设置区(`createSettingsContainer`)
- 创建右侧退出按钮(`createExitButton`)
- 组装各部分并添加分割线
2. **订阅变更**:
- 订阅语言变更:`localeManager.subscribe(() => this.setLocales())`
- 订阅主题变更:`themeManager.subscribe((theme) => this.setTheme(theme))`
3. **初始设置**:
- 调用 `setLocales()` 设置初始文本
- 调用 `setTheme()` 应用当前主题
- 调用 `updateSettingsView()` 根据初始模式显示/隐藏控件
### 6.2 模式切换流程
#### 模式类型
- `'none'`: 无模式
- `'path'`: 路径漫游模式
- `'walk'`: 人物漫游模式
#### 模式切换逻辑 (`setMode`)
1. **保存旧模式**:用于触发关闭事件
2. **触发旧模式关闭事件**:
- 如果从 `'walk'` 切换出去,触发 `onWalkModeToggle(false)`
- 如果从 `'path'` 切换出去,触发 `onPathModeToggle(false)`
3. **更新模式状态**:`this.state.mode = newMode`
4. **特殊处理**:
- 路径模式时:禁用重力和碰撞复选框,并设置为 false
- 其他模式:启用重力和碰撞复选框
5. **更新视图**:
- `updateButtonStates()`: 更新按钮激活状态
- `updateSettingsView()`: 显示/隐藏相应的设置控件
- `updateSpeedButtonStates()`: 更新速度按钮禁用状态
### 6.3 设置视图更新 (`updateSettingsView`)
根据当前模式动态显示/隐藏控件:
**漫游模式 (`'walk'`)**:
- 隐藏速度控件
- 显示角色模型选择
- 显示行走模式选择
- 显示重力复选框
- 显示碰撞复选框
**路径模式 (`'path'`)****无模式 (`'none'`)**:
- 显示速度控件
- 隐藏角色模型选择
- 隐藏行走模式选择
- 显示重力复选框
- 显示碰撞复选框
### 6.4 图标管理
使用统一的图标管理器 (`icon-manager.ts`) 获取 SVG 图标:
```typescript
private getIconSVG(type: string): string {
const icons: Record<string, string> = {
'plan-view': getIcon('地图'), // 平面图/鸟瞰
'path': getIcon('地图'), // 路径漫游
'walk': getIcon('漫游') // 人物漫游
};
return icons[type] || '';
}
```
**重要**: 所有按钮图标尺寸统一为 32x32,与 toolbar 底部工具栏保持一致。
---
## 7. 国际化支持
### 7.1 使用的翻译键
```typescript
// 翻译键路径
'walkControl.speed' // 速度标签
'walkControl.gravity' // 重力标签
'walkControl.collision' // 碰撞标签
'walkControl.characterModel.label' // 角色模型标签
'walkControl.characterModel.constructionWorker' // 建筑工人
'walkControl.characterModel.officeMale' // 办公室男性
'walkControl.walkMode.label' // 行走模式标签
'walkControl.walkMode.walk' // 行走
'walkControl.walkMode.run' // 奔跑
'walkControl.exit' // 退出按钮
```
### 7.2 实现方式
- 使用 `src/services/locale.ts` 中的 `t()` 函数
- 组件初始化时订阅语言变更,变更发生时更新所有文本 (`setLocales`)
- 动态创建的下拉框选项也使用 `t()` 函数获取翻译文本
---
## 8. 主题支持
### 8.1 变量映射
`setTheme` 中设置内联样式变量:
- `--bim-walk-control-bg`: 面板背景色
- `--bim-walk-btn-hover`: 按钮悬停背景
- `--bim-walk-btn-active`: 按钮激活背景
- `--bim-primary-color`: 主色(退出按钮)
- `--bim-primary-hover`: 主色悬停
- `--bim-icon-color`: 图标颜色
- `--bim-text-color`: 文本颜色
- `--bim-divider-color`: 分割线颜色
- `--bim-speed-group-bg`: 速度组背景
- `--bim-speed-btn-bg`: 速度按钮背景
- `--bim-speed-btn-hover`: 速度按钮悬停
- `--bim-select-bg`: 下拉框背景
- `--bim-select-border`: 下拉框边框
- `--bim-select-option-bg`: 下拉框选项背景
---
## 9. 使用示例
### 9.1 基本使用
```typescript
import { WalkControlPanel } from './components/walk-control-panel';
// 创建实例
const walkPanel = new WalkControlPanel({
defaultSpeed: 1,
defaultGravity: false,
defaultCollision: false,
onPlanViewToggle: (isActive) => {
console.log('平面图模式:', isActive);
},
onPathModeToggle: (isActive) => {
console.log('路径模式:', isActive);
// 启用/禁用路径漫游功能
},
onWalkModeToggle: (isActive) => {
console.log('漫游模式:', isActive);
// 启用/禁用人物漫游功能
},
onSpeedChange: (speed) => {
console.log('速度:', speed);
// 更新漫游速度
},
onGravityToggle: (enabled) => {
console.log('重力:', enabled);
},
onCollisionToggle: (enabled) => {
console.log('碰撞:', enabled);
},
onCharacterModelChange: (model) => {
console.log('角色模型:', model);
},
onWalkModeChange: (mode) => {
console.log('行走模式:', mode);
},
onExit: () => {
console.log('退出漫游');
// 销毁面板,退出漫游模式
}
});
// 初始化
walkPanel.init();
// 挂载到 DOM
document.body.appendChild(walkPanel.element);
```
### 9.2 外部控制状态
```typescript
// 外部设置平面图激活状态
walkPanel.setPlanViewActive(true);
// 外部设置路径模式激活状态
walkPanel.setPathModeActive(true);
// 获取当前状态
const state = walkPanel.getState();
console.log('当前模式:', state.mode);
console.log('当前速度:', state.speed);
```
---
## 10. 实现细节(供 AI 重现)
### 10.1 状态管理
组件内部维护一个状态对象 `WalkControlState`:
```typescript
private state: WalkControlState = {
mode: 'none', // 当前模式
isPlanViewActive: false, // 平面图是否激活
speed: 1, // 移动速度 1-10
gravity: false, // 重力是否启用
collision: false, // 碰撞是否启用
characterModel: 'construction-worker', // 角色模型
walkMode: 'walk' // 行走模式
};
```
状态变更时:
1. 更新内部状态
2. 更新 UI 视觉效果
3. 触发对应的回调函数
### 10.2 模式互斥逻辑
三个按钮(平面图、路径、漫游)中:
- **平面图**是独立的开关,不影响模式
- **路径**和**漫游**是互斥的,激活一个会关闭另一个
- 点击已激活的模式按钮会关闭该模式(回到 `'none'`)
### 10.3 路径模式的特殊处理
当进入路径模式时:
1. 自动禁用并取消勾选重力和碰撞
2. 禁用重力和碰撞复选框(设置 `disabled` 属性)
3. 退出路径模式时重新启用这两个复选框
### 10.4 速度控制
- 速度范围: 1-10
- 减速按钮在速度为 1 时禁用
- 加速按钮在速度为 10 时禁用
- 速度显示格式: `${speed}X`
### 10.5 下拉框动态创建
角色模型和行走模式的下拉框选项在 `setLocales()` 中动态创建:
1. 清空现有选项:`select.innerHTML = ''`
2. 创建新选项,使用 `t()` 获取翻译文本
3. 根据当前状态设置选中项
这样可以确保语言切换时下拉框文本也会更新。
---
## 11. 类型定义
### 11.1 WalkControlMode
```typescript
export type WalkControlMode = 'none' | 'path' | 'walk';
```
### 11.2 CharacterModel
```typescript
export type CharacterModel = 'office-male' | 'construction-worker';
```
### 11.3 WalkMode
```typescript
export type WalkMode = 'walk' | 'run';
```
### 11.4 WalkControlState
```typescript
export interface WalkControlState {
mode: WalkControlMode;
isPlanViewActive: boolean;
speed: number;
gravity: boolean;
collision: boolean;
characterModel: CharacterModel;
walkMode: WalkMode;
}
```
### 11.5 WalkControlPanelOptions
见 2.1 节
---
## 12. 文件清单
- `src/components/walk-control-panel/index.ts`: 组件核心逻辑
- `src/components/walk-control-panel/index.css`: 组件样式
- `src/components/walk-control-panel/types.ts`: 类型定义
---
## 13. 更新记录
| 日期 | 修改内容 | 修改人 |
| ---------- | ----------------------------------------- | ------------ |
| 2025-12-25 | 创建漫游控制面板组件文档,集成图标管理器 | AI Assistant |

View File

@@ -0,0 +1,318 @@
# Menu 组件详细文档
> 本文档详细描述 Menu 组件的实现细节,包括 API、UI 结构、逻辑流程等,供 AI 根据文档重现组件。
---
## 1. 组件概述
### 1.1 基本信息
- **组件名称**: `BimMenu`
- **文件路径**: `src/components/menu/index.ts`
- **类型定义**: `src/components/menu/item.ts` (配置), `src/components/menu/types.ts` (选项)
- **样式文件**: `src/components/menu/index.css`
- **实现接口**: `IBimComponent`, `IRightKeyContent`
- **用途**: 渲染一组菜单项,支持分组、排序、图标、快捷键提示和无限级递归子菜单。
- **特点**: 纯数据驱动(基于 `MenuItemConfig`),内置国际化支持。
### 1.2 在 SDK 中的位置
- Menu 组件主要由 `RightKeyManager` 使用,作为右键菜单的内容载体。
- 也可以单独实例化并挂载到任何容器中。
- `RightKeyManager` 位于 `src/managers/right-key-manager.ts`
---
## 2. 组件类 API 文档
### 2.1 构造函数
```typescript
constructor(options: MenuOptions)
```
**参数**:
- `options`: `MenuOptions` - 菜单配置选项
**MenuOptions 定义**:
```typescript
interface MenuOptions {
items: MenuItemConfig[]; // 菜单项配置列表
groupOrder?: string[]; // 分组显示顺序
}
```
**行为**:
- 创建根元素 `<ul>`
- 保存配置选项
### 2.2 公共方法
#### `init(): void`
初始化组件(实现 `IBimComponent` 接口)
**功能**:
- 调用 `render()` 渲染 DOM 结构
- 订阅语言变更:`localeManager.subscribe()`
- 订阅主题变更:`themeManager.subscribe()`(虽然目前主要通过 CSS 变量,但保留订阅以备扩展)
#### `setTheme(theme: ThemeConfig): void`
设置主题(实现 `IBimComponent` 接口)
**参数**:
- `theme`: `ThemeConfig`
**功能**:
- 将主题颜色映射到 CSS 变量
- `--bim-ui_bg_color``theme.panelBackground`
- `--bim-ui_text_primary``theme.textPrimary`
- `--bim-ui_border_color``theme.border`
- `--bim-ui_bg_hover``theme.componentHover`
#### `setLocales(): void`
设置语言(实现 `IBimComponent` 接口)
**功能**:
- 清空当前 DOM
- 重新调用 `render()`,使 `t()` 函数获取最新的翻译文本
#### `destroy(): void`
销毁组件(实现 `IBimComponent`, `IRightKeyContent` 接口)
**功能**:
- 取消语言和主题订阅
- 关闭并销毁所有打开的子菜<E5AD90><E88F9C>
- 从 DOM 中移除自身元素
#### `getElement(): HTMLElement`
获取组件根元素(实现 `IRightKeyContent` 接口)
**返回**: `HTMLElement` (根 `<ul>` 元素)
**功能**:
- 允许父容器(如 `BimRightKey`)获取并挂载此菜单
---
## 3. 分化组件说明
**BimMenu 没有子类或分化组件**。它通过递归自身来实现多级菜单。
---
## 4. Manager API 文档
参见 [RightKey 组件文档](./right-key.md) 中的 `RightKeyManager``BimMenu` 本身是一个纯 UI 组件,通常不直接通过 Manager 管理,而是被 `RightKeyManager` 动态创建和销毁。
---
## 5. UI 详细描述
### 5.1 DOM 结构
```html
<ul class="bim-menu">
<!-- 分组 1 -->
<li class="bim-menu-item [disabled]">
<div class="bim-menu-item-icon">[SVG图标]</div>
<div class="bim-menu-item-label">菜单文本</div>
<!-- 如果有子菜单 -->
<div class="bim-menu-item-arrow">
<svg>...</svg> <!-- 右箭头 -->
</div>
</li>
<!-- 分组分割线 -->
<li class="bim-menu-divider"></li>
<!-- 分组 2 -->
<li class="bim-menu-item">...</li>
</ul>
```
### 5.2 CSS 类名和样式
#### `.bim-menu` (根容器)
- `display: flex`, `flex-direction: column`
- `background`: `var(--bim-ui_bg_color)`
- `border-radius: 4px`
- `padding: 4px 0`, `margin: 0`, `list-style: none` (关键:移除列表默认样式)
- `min-width: 160px`
- `box-shadow`: `0 4px 12px rgba(0,0,0,0.2)`
- `user-select: none`
#### `.bim-menu-item` (菜单项)
- `display: flex`, `align-items: center`
- `padding: 6px 12px`
- `cursor: pointer`
- `position: relative`
- `color`: `var(--bim-ui_text_primary)`
#### `.bim-menu-item:hover`
- `background-color`: `var(--bim-ui_bg_hover)`
#### `.bim-menu-item.disabled`
- `opacity: 0.5`
- `cursor: not-allowed`
- `pointer-events: none`
#### `.bim-menu-divider` (分割线)
- `height: 1px`
- `background-color`: `var(--bim-ui_border_color)`
- `margin: 4px 0`
---
## 6. 逻辑流程详细描述
### 6.1 渲染流程 (`render`)
1. **数据分桶**: 遍历 `items`,根据 `item.group` (默认为 'default') 将菜单项放入不同的数组中。
2. **确定顺序**:
- 如果提供了 `groupOrder`,优先按其顺序排列组。
- 未指定的组按遍历顺序追加。
3. **渲染分组**:
- 遍历排序后的组键。
- 如果不是第一组,先插入一个 `.bim-menu-divider`
- 获取组内项,根据 `item.order` 升序排序。
- 遍历组内项,如果 `item.visible !== false`,调用 `createItemElement` 创建 DOM。
### 6.2 交互流程
#### 点击事件
- **绑定**: 在 `.bim-menu-item` 上绑定 `click`
- **逻辑**:
- `e.stopPropagation()`: 阻止冒泡。
- 检查 `!hasChildren`: 只有叶子节点才触发点击。
- 调用 `item.onClick?.()`
- **注意**: 点击后,通常期望菜单关闭。这依赖于 `RightKeyManager` 或外部逻辑(通过 `onClick` 内部调用关闭,或者点击触发全局关闭)。
#### 子菜单展开 (`mouseenter`)
- **触发**: 鼠标移入带有子项的菜单项。
- **逻辑**:
- 调用 `closeSubMenu()` 关闭当前可能已打开的其他子菜单。
- 计算位置:通常位于父项右侧 (`rect.right`),顶部对齐 (`rect.top`)。
- 创建子菜单容器 `div` (fixed 定位, z-index 10001)。
- **关键修复**: 在容器上绑定 `mousedown``stopPropagation`,防止触发 `BimRightKey` 的全局关闭逻辑。
- 实例化新的 `BimMenu` (递归) 并 `init()`
- 将新菜单挂载到容器,容器挂载到 `document.body`
- 保存引用到 `this.activeSubMenu`
- **边界检测**: 如果右侧超出屏幕,改为向左展开。
#### 子菜单关闭
- **触发**: 鼠标移入**不带**子项的菜单项。
- **逻辑**: 调用 `closeSubMenu()`,销毁实例并移除 DOM。
---
## 7. 国际化支持
### 7.1 实现方式
- 使用 `src/services/locale.ts` 中的 `t()` 函数。
-`createItemElement` 中,直接使用 `t(item.label)` 设置文本。
- 组件初始化时订阅语言变更,变更发生时清空并重绘 (`setLocales` -> `render`)。
---
## 8. 主题支持
### 8.1 变量映射
-`setTheme` 中设置内联样式变量:
- `--bim-ui_bg_color`: 面板背景
- `--bim-ui_text_primary`: 主要文字
- `--bim-ui_border_color`: 边框/分割线
- `--bim-ui_bg_hover`: 悬停背景
---
## 9. 使用示例
### 9.1 数据结构示例
```typescript
const menuItems: MenuItemConfig[] = [
{
id: 'view',
label: 'menu.view', // 翻译键
group: 'view',
children: [
{
id: 'home',
label: 'menu.home',
onClick: () => console.log('Go Home')
}
]
},
{
id: 'delete',
label: 'menu.delete',
group: 'edit',
icon: '<svg>...</svg>',
onClick: () => console.log('Delete')
}
];
```
### 9.2 手动创建(通常不直接使用,而是通过 RightKeyManager
```typescript
const menu = new BimMenu({ items: menuItems });
menu.init();
document.body.appendChild(menu.getElement());
```
---
## 10. 实现细节(供 AI 重现)
### 10.1 递归设计
组件自身不包含递归渲染 DOM 的逻辑,而是通过“交互触发”来递归实例化。即:子菜单**不是**一开始就渲染在 DOM 树中的,而是当用户鼠标悬停时,动态创建一个新的 `BimMenu` 实例并挂载到 `body` 上。这种设计避免了深层嵌套 DOM 带来的样式问题(如 `overflow: hidden` 截断子菜单)。
### 10.2 事件冲突处理
由于子菜单挂载在 `body` 上,点击子菜单在 DOM 树看来是“点击了主菜单外部”。这会触发 `BimRightKey` 的“点击外部关闭”逻辑。
**解决方案**: 子菜单容器监听 `mousedown``stopPropagation()`,欺骗 `BimRightKey` 认为点击没有发生(或者发生在内部)。
---
## 11. 类型定义
### 11.1 MenuItemConfig
```typescript
interface MenuItemConfig {
id: string;
label: string;
onClick?: () => void;
icon?: string; // SVG 字符串
group?: string; // 分组标识
order?: number; // 排序权重
children?: MenuItemConfig[];
disabled?: boolean;
visible?: boolean;
}
```
### 11.2 MenuOptions
```typescript
interface MenuOptions {
items: MenuItemConfig[];
groupOrder?: string[];
}
```
---
## 12. 文件清单
- `src/components/menu/index.ts`: 组件核心逻辑
- `src/components/menu/index.css`: 组件样式
- `src/components/menu/item.ts`: 仅包含类型定义 (`MenuItemConfig`)
- `src/components/menu/types.ts`: 组件选项接口
---
## 13. 更新记录
| 日期 | 修改内容 | 修改人 |
| ---------- | ----------------------------------------- | ------------ |
| 2024-XX-XX | 重构为纯配置对象驱动,移除 BimMenuItem 类 | AI Assistant |