fix(menu): refactor menu system to use pure config objects and fix submenu click events

This commit is contained in:
yuding
2025-12-09 18:34:43 +08:00
parent c112c87dad
commit 9ae1d9d809
38 changed files with 6756 additions and 2575 deletions

File diff suppressed because it is too large Load Diff

882
docs/components/dialog.md Normal file
View File

@@ -0,0 +1,882 @@
# 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 元素)
#### `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 'bim-engine-sdk';
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 |
---
**重要提醒**: 本文档必须与组件代码保持同步。任何组件修改都必须更新本文档!

601
docs/components/engine.md Normal file
View File

@@ -0,0 +1,601 @@
# 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 依赖
- 依赖 `src/bim-engine-sdk.es.js` 中的 `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 'bim-engine-sdk';
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 依赖**:
- 依赖 `bim-engine-sdk.es.js` 文件
- 通过动态导入或全局变量访问
- 需要确保 SDK 文件已加载
---
## 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` - 管理器类
- `src/bim-engine-sdk.es.js` - 第三方 SDK依赖
### 12.2 依赖文件
- `src/types/component.ts` - IBimComponent 接口
- `src/themes/types.ts` - ThemeConfig 类型
- `src/services/theme.ts` - ThemeManager
---
## 13. 更新记录
| 日期 | 修改内容 | 修改人 |
|------|---------|--------|
| 2024-XX-XX | 初始创建 | AI Assistant |
---
**重要提醒**: 本文档必须与组件代码保持同步。任何组件修改都必须更新本文档!

66
docs/components/menu.md Normal file
View File

@@ -0,0 +1,66 @@
# Menu 组件文档
## 1. 组件概述
`BimMenu` 是一个通用的菜单列表组件。
- **位置**: `src/components/menu/index.ts`
- **功能**: 渲染一组 `BimMenuItem`,支持分组、排序、图标、快捷键提示和多级子菜单。
- **特点**: 数据驱动(通过 Item 类实例),支持国际化。
## 2. 组件类 API
### `BimMenu`
#### 构造函数
`new BimMenu(options: MenuOptions)`
#### 方法
| 方法名 | 参数 | 返回值 | 描述 |
|:----|:---|:---|:---|
| `init` | `()` | `void` | 初始化,渲染 DOM |
| `destroy` | `()` | `void` | 销毁,清理事件和子菜单 |
| `getElement` | `()` | `HTMLElement` | 获取根 DOM 元素 (实现 IRightKeyContent) |
### `BimMenuItem` (基类)
所有菜单项必须继承此抽象类。
| 方法/属性 | 类型 | 描述 |
|:----|:---|:---|
| `id` | `string` | 唯一标识 |
| `group` | `string` | 分组 ID (默认 'default') |
| `order` | `number` | 排序权重 |
| `getLabel()` | `string` | 获取显示文本 |
| `onClick()` | `void` | 点击回调 |
| `getIcon()` | `string?` | 获取图标 HTML |
| `getChildren()` | `BimMenuItem[]?` | 获取子菜单项 |
## 3. UI 详细描述
- **根元素**: `<ul class="bim-menu">`
- **分组分割线**: `<li class="bim-menu-divider">`
- **菜单项**: `<li class="bim-menu-item">`
- 图标槽: `.bim-menu-item-icon`
- 文本槽: `.bim-menu-item-label`
- 箭头槽: `.bim-menu-item-arrow` (仅当有子菜单时)
## 4. 逻辑流程
1. **分组与排序**:
- 根据 `item.group` 将项归类。
- 根据 `options.groupOrder` 对组进行排序。
- 组内根据 `item.order` 排序。
2. **多级菜单**:
- 鼠标进入 (`mouseenter`) 带子菜单的项时,创建新的 `BimMenu` 实例。
- 将新实例挂载到临时的 fixed 容器中。
- 自动计算位置(通常在父项右侧,空间不足时翻转)。
## 5. 类型定义
```typescript
export interface MenuOptions {
items: BimMenuItem[];
groupOrder?: string[];
}
```

View File

@@ -0,0 +1,58 @@
# RightKey 组件文档
## 1. 组件概述
`BimRightKey` 是一个通用的右键浮层容器组件。
- **位置**: `src/components/right-key/index.ts`
- **功能**: 负责在指定坐标 (x, y) 显示内容,处理边界检测防止溢出屏幕,以及点击外部自动关闭。
- **特点**: 它不关心内容是什么,通过 `mount()` 方法挂载任意实现 `IRightKeyContent` 接口的内容组件。
## 2. 组件类 API
### `BimRightKey`
#### 方法
| 方法名 | 参数 | 返回值 | 描述 |
|:----|:---|:---|:---|
| `init` | `()` | `void` | 初始化,绑定全局点击事件 |
| `mount` | `(content: IRightKeyContent)` | `void` | 挂载内容组件 |
| `show` | `(x: number, y: number)` | `void` | <20><><EFBFBD>指定位置显示会自动调整位置防止溢出 |
| `hide` | `()` | `void` | 隐藏容器 |
| `destroy` | `()` | `void` | 销毁容器,解绑事件 |
| `setOnClose` | `(callback: () => void)` | `void` | 设置关闭时的回调 |
## 3. Manager API
### `RightKeyManager`
- **位置**: `src/managers/right-key-manager.ts`
- **功能**: 监听容器的 `contextmenu` 事件,决定显示什么内容。
#### 方法
| 方法名 | 参数 | 返回值 | 描述 |
|:----|:---|:---|:---|
| `registerHandler` | `(handler: (e) => BimMenuItem[])` | `void` | 注册上下文处理器 |
| `showMenu` | `(x, y, items, groupOrder?)` | `void` | 手动显示菜单 |
| `hide` | `()` | `void` | 隐藏 |
## 4. UI 详细描述
- **容器**: `<div class="bim-right-key"></div>`
- **定位**: `position: fixed`
- **层级**: `z-index: 10000` (默认)
## 5. 实现细节
- **边界检测**: 在 `show(x, y)` 时,会计算容器宽高和视口宽高。如果 `x + width > viewportWidth`,则 `x = x - width`(向左展开)。同理处理垂直方向。
- **点击外部关闭**: 监听 `document``mousedown` 事件,如果点击目标不在容器内,则调用 `hide()`
## 6. 类型定义
```typescript
export interface IRightKeyContent {
getElement(): HTMLElement;
destroy(): void;
}
```