- 重构测量激活逻辑,在 Engine 组件中添加统一的 activateMeasure(mode) 方法 - 简化 MeasureDialogManager,移除冗余的 handleMeasureTypeChange 方法 - 添加 EngineManager.activateMeasure 转发方法 - 修复 loadModel 错误,正确调用 Engine 组件方法 - 为 Engine 组件设置固定背景渐变色 - MeasurePanel 初始化时触发 onModeChange 回调 - 添加 MeasureMode 共享类型定义 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1199 lines
32 KiB
Markdown
1199 lines
32 KiB
Markdown
# ButtonGroup 组件详细文档
|
||
|
||
> 本文档详细描述 ButtonGroup 组件的实现细节,包括 API、UI 结构、逻辑流程等,供 AI 根据文档重现组件。
|
||
|
||
---
|
||
|
||
## 1. 组件概述
|
||
|
||
### 1.1 基本信息
|
||
- **组件名称**: `BimButtonGroup`
|
||
- **文件路径**: `src/components/button-group/index.ts`
|
||
- **类型定义**: `src/components/button-group/index.type.ts`
|
||
- **样式文件**: `src/components/button-group/index.css`
|
||
- **实现接口**: `IBimComponent`
|
||
- **用途**: 提供通用的按钮组组件,支持按钮、菜单、分组、嵌套菜单等功能
|
||
|
||
### 1.2 在 SDK 中的位置
|
||
- ButtonGroup 组件是独立的 UI 组件
|
||
- 必须通过 `ButtonGroupManager` 或 `ToolbarManager` 使用,不允许直接使用
|
||
- `ButtonGroupManager` 位于 `src/managers/button-group-manager.ts`
|
||
- `ToolbarManager` 位于 `src/managers/toolbar-manager.ts`
|
||
|
||
---
|
||
|
||
## 2. 组件类 API 文档
|
||
|
||
### 2.1 构造函数
|
||
|
||
```typescript
|
||
constructor(options: ButtonGroupOptions)
|
||
```
|
||
|
||
**参数**:
|
||
- `options`: `ButtonGroupOptions` - 按钮组配置选项(详见类型定义)
|
||
|
||
**默认配置**:
|
||
```typescript
|
||
{
|
||
showLabel: true,
|
||
visibility: {},
|
||
direction: 'row', // 默认横向排列
|
||
position: 'static', // 默认静态定位
|
||
align: 'vertical', // 默认图标在上
|
||
expand: 'down' // 默认向下展开
|
||
}
|
||
```
|
||
|
||
**行为**:
|
||
- 解析容器元素(支持字符串 ID 或 HTMLElement)
|
||
- 合并用户配置和默认配置
|
||
- 记录用户自定义的颜色属性
|
||
- 初始化容器
|
||
- 应用样式
|
||
|
||
### 2.2 公共方法
|
||
|
||
#### `setEngine(engine: BimEngine): void`
|
||
设置引擎实例
|
||
|
||
**参数**:
|
||
- `engine`: `BimEngine` - 引擎实例
|
||
|
||
**功能**:
|
||
- 保存引擎引用,用于发送事件
|
||
|
||
#### `init(): Promise<void>`
|
||
初始化组件功能(实现 `IBimComponent` 接口)
|
||
|
||
**功能**:
|
||
- 调用 `render()` 渲染按钮组
|
||
- 订阅语言变更:`localeManager.subscribe()`
|
||
- 订阅主题变更:`themeManager.subscribe()`
|
||
|
||
**返回**: Promise(异步方法,但当前实现是同步的)
|
||
|
||
#### `setTheme(theme: ThemeConfig): void`
|
||
设置主题(实现 `IBimComponent` 接口)
|
||
|
||
**参数**:
|
||
- `theme`: `ThemeConfig` - 全局主题配置
|
||
|
||
**功能**:
|
||
- 将主题颜色映射到按钮组颜色
|
||
- **只应用没有被用户自定义的颜色属性**
|
||
- 如果用户已自定义颜色,保持用户自定义
|
||
|
||
**主题颜色映射**:
|
||
- `theme.panelBackground` → `backgroundColor`
|
||
- `theme.componentBackground` → `btnBackgroundColor`
|
||
- `theme.componentHover` → `btnHoverColor`
|
||
- `theme.componentActive` → `btnActiveColor`
|
||
- `theme.icon` → `iconColor`
|
||
- `theme.iconActive` → `iconActiveColor`
|
||
- `theme.textSecondary` → `textColor`
|
||
- `theme.textPrimary` → `textActiveColor`
|
||
|
||
#### `setLocales(): void`
|
||
设置语言(实现 `IBimComponent` 接口)
|
||
|
||
**功能**:
|
||
- 调用 `render()` 重新渲染,更新所有按钮的标签文本
|
||
|
||
#### `setColors(colors: ButtonGroupColors): void`
|
||
直接设置颜色(强制覆盖)
|
||
|
||
**参数**:
|
||
- `colors`: `ButtonGroupColors` - 颜色配置对象
|
||
|
||
**功能**:
|
||
- 更新配置选项
|
||
- **标记这些颜色为自定义**,后续的 `setTheme()` 不会覆盖它们
|
||
- 应用样式
|
||
|
||
#### `setBackgroundColor(color: string): void`
|
||
设置背景颜色
|
||
|
||
**参数**:
|
||
- `color`: `string` - 背景颜色值
|
||
|
||
**功能**:
|
||
- 调用 `setColors({ backgroundColor: color })`
|
||
|
||
#### `addGroup(groupId: string, beforeGroupId?: string): void`
|
||
添加按钮组
|
||
|
||
**参数**:
|
||
- `groupId`: `string` - 组 ID(必须唯一)
|
||
- `beforeGroupId`: `string` (可选) - 在此组之前插入
|
||
|
||
**功能**:
|
||
- 创建新的按钮组
|
||
- 如果组已存在,忽略
|
||
- 如果指定了 `beforeGroupId`,在该组之前插入;否则追加到末尾
|
||
|
||
#### `addButton(config: ButtonConfig): void`
|
||
添加按钮
|
||
|
||
**参数**:
|
||
- `config`: `ButtonConfig` - 按钮配置
|
||
|
||
**功能**:
|
||
- 将按钮添加到指定组
|
||
- 如果指定了 `parentId`,将按钮添加为父按钮的子项(嵌套菜单)
|
||
- 如果未指定 `parentId`,将按钮添加到组的根级别
|
||
|
||
**按钮类型**:
|
||
- `type: 'button'` - 普通按钮
|
||
- `type: 'menu'` - 菜单按钮(有子项)
|
||
|
||
#### `render(): void`
|
||
渲染按钮组
|
||
|
||
**功能**:
|
||
- 清空容器
|
||
- 清空按钮引用映射
|
||
- 遍历所有组,渲染每个组
|
||
- 每个组渲染为一个 `.bim-btn-group-section` 元素
|
||
|
||
#### `updateButtonVisibility(id: string, visible: boolean): void`
|
||
更新按钮可见性
|
||
|
||
**参数**:
|
||
- `id`: `string` - 按钮 ID
|
||
- `visible`: `boolean` - 是否可见
|
||
|
||
**功能**:
|
||
- 更新 `options.visibility` 对象
|
||
- 调用 `render()` 重新渲染
|
||
|
||
#### `setShowLabel(show: boolean): void`
|
||
设置是否显示标签
|
||
|
||
**参数**:
|
||
- `show`: `boolean` - 是否显示标签
|
||
|
||
**功能**:
|
||
- 更新 `options.showLabel`
|
||
- 调用 `updateLabelsVisibility()` 更新所有按钮的标签显示状态
|
||
|
||
#### `destroy(): void`
|
||
销毁组件(实现 `IBimComponent` 接口)
|
||
|
||
**功能**:
|
||
- 取消语言和主题订阅
|
||
- 关闭下拉菜单(如果打开)
|
||
- 清空容器
|
||
- 清空按钮引用映射
|
||
|
||
### 2.3 受保护方法
|
||
|
||
#### `emit<K extends keyof EngineEvents>(event: K, payload: EngineEvents[K]): void`
|
||
发送事件
|
||
|
||
**功能**:
|
||
- 如果引擎已设置,通过引擎发送事件
|
||
- 如果引擎未设置,输出警告
|
||
|
||
### 2.4 私有方法(供理解实现细节)
|
||
|
||
#### `initContainer(): void`
|
||
初始化容器
|
||
|
||
**功能**:
|
||
- 清空容器内容
|
||
- 添加 `bim-btn-group-root` 类
|
||
- 根据 `direction` 添加 `dir-row` 或 `dir-column` 类
|
||
- 如果提供了 `className`,添加自定义类
|
||
- 调用 `updatePosition()` 更新位置
|
||
|
||
#### `updatePosition(): void`
|
||
更新容器位置
|
||
|
||
**功能**:
|
||
- 根据 `position` 选项设置容器位置
|
||
- 支持预设位置(如 'center', 'top-left' 等)或坐标对象 `{ x, y }`
|
||
- 如果 `position` 是 'static',使用相对定位
|
||
|
||
**位置计算逻辑**:
|
||
- `static`: 使用相对定位,不设置绝对定位属性
|
||
- `{ x, y }`: 直接使用坐标值
|
||
- 预设位置: 使用 `margin: 20px` 和 `transform` 实现定位
|
||
|
||
#### `applyStyles(): void`
|
||
应用样式到容器
|
||
|
||
**功能**:
|
||
- 将配置的颜色值设置到 CSS 变量
|
||
- CSS 变量映射:
|
||
- `backgroundColor` → `--bim-btn-group-section-bg`
|
||
- `btnBackgroundColor` → `--bim-btn-bg`
|
||
- `btnHoverColor` → `--bim-btn-hover-bg`
|
||
- `btnActiveColor` → `--bim-btn-active-bg`
|
||
- `iconColor` → `--bim-icon-color`
|
||
- `iconActiveColor` → `--bim-icon-active-color`
|
||
- `textColor` → `--bim-btn-text-color`
|
||
- `textActiveColor` → `--bim-btn-text-active-color`
|
||
|
||
#### `renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement`
|
||
渲染按钮组
|
||
|
||
**参数**:
|
||
- `group`: `ButtonGroup` - 按钮组对象
|
||
- `index`: `number` - 组索引
|
||
- `total`: `number` - 总组数
|
||
|
||
**返回**: 组元素
|
||
|
||
**功能**:
|
||
- 创建 `.bim-btn-group-section` 元素
|
||
- 如果不是最后一组,添加 `has-divider` 类(用于显示分隔线)
|
||
- 遍历组内的按钮,渲染每个可见的按钮
|
||
|
||
#### `renderButton(button: OptButton): HTMLElement`
|
||
渲染单个按钮
|
||
|
||
**参数**:
|
||
- `button`: `OptButton` - 按钮配置
|
||
|
||
**返回**: 按钮包装元素
|
||
|
||
**功能**:
|
||
- 创建 `.opt-btn-wrapper` 和 `.opt-btn` 元素
|
||
- 根据按钮的 `align` 或全局 `align` 设置布局方向
|
||
- 如果按钮是激活状态,添加 `active` 类
|
||
- 如果按钮是禁用状态,添加 `disabled` 类
|
||
- 如果按钮没有标签,添加 `no-label` 类
|
||
- 创建图标元素(`.opt-btn-icon`)
|
||
- 创建文字包装器(`.opt-btn-text-wrapper`),包含标签和箭头(如果有子项)
|
||
- 绑定点击、鼠标进入、鼠标离开事件
|
||
- 保存按钮引用到 `btnRefs` Map
|
||
|
||
#### `renderDropdownItem(button: OptButton): HTMLElement`
|
||
渲染下拉菜单项
|
||
|
||
**参数**:
|
||
- `button`: `OptButton` - 按钮配置
|
||
|
||
**返回**: 菜单项元素
|
||
|
||
**功能**:
|
||
- 创建 `.opt-btn-dropdown-item` 元素
|
||
- 根据按钮的 `align` 设置布局方向(默认 'horizontal')
|
||
- 创建图标和标签
|
||
- 绑定点击事件
|
||
|
||
#### `handleClick(button: OptButton): void`
|
||
处理按钮点击
|
||
|
||
**功能**:
|
||
- 如果按钮是禁用状态,忽略
|
||
- 如果按钮没有子项:
|
||
- 如果 `keepActive` 为 true,切换激活状态
|
||
- 关闭下拉菜单
|
||
- 调用 `onClick` 回调(如果存在)
|
||
- 如果按钮有子项,不处理(由鼠标悬停处理)
|
||
|
||
#### `handleMouseEnter(button: OptButton, btnEl: HTMLElement): void`
|
||
处理鼠标进入
|
||
|
||
**功能**:
|
||
- 清除悬停超时
|
||
- 如果按钮有子项,显示下拉菜单
|
||
- 如果按钮没有子项,关闭下拉菜单
|
||
|
||
#### `handleMouseLeave(): void`
|
||
处理鼠标离开
|
||
|
||
**功能**:
|
||
- 设置 200ms 延迟后关闭下拉菜单(防止鼠标移动到菜单时关闭)
|
||
|
||
#### `showDropdown(button: OptButton, btnEl: HTMLElement): void`
|
||
显示下拉菜单
|
||
|
||
**功能**:
|
||
- 关闭当前打开的下拉菜单
|
||
- 创建 `.opt-btn-dropdown` 元素
|
||
- 根据主按钮组的方向设置菜单的布局方向:
|
||
- 如果主组是横向(`direction: 'row'`),菜单纵向排列
|
||
- 如果主组是纵向(`direction: 'column'`),菜单横向排列
|
||
- 渲染所有可见的子按钮
|
||
- 计算菜单位置(根据 `expand` 选项):
|
||
- `up`: 向上展开,与按钮水平居中对齐
|
||
- `down`: 向下展开,与按钮水平居中对齐
|
||
- `right`: 向右展开,与按钮垂直居中对齐
|
||
- `left`: 向左展开,与按钮垂直居中对齐
|
||
- 将菜单添加到 `document.body`(绝对定位)
|
||
- 绑定鼠标进入和离开事件(保持菜单打开)
|
||
|
||
#### `closeDropdown(): void`
|
||
关闭下拉菜单
|
||
|
||
**功能**:
|
||
- 移除下拉菜单元素
|
||
- 移除所有按钮箭头元素的 `rotated` 类
|
||
|
||
#### `updateButtonState(buttonId: string): void`
|
||
更新按钮状态
|
||
|
||
**功能**:
|
||
- 根据 `activeBtnIds` Set 更新按钮的 `active` 类
|
||
|
||
#### `findButton(buttons: OptButton[], id: string): OptButton | undefined`
|
||
查找按钮(递归)
|
||
|
||
**功能**:
|
||
- 在按钮数组中查找指定 ID 的按钮
|
||
- 递归查找子按钮
|
||
|
||
#### `findButtonById(id: string): OptButton | undefined`
|
||
根据 ID 查找按钮
|
||
|
||
**功能**:
|
||
- 在所有组中查找指定 ID 的按钮
|
||
|
||
#### `isVisible(id: string): boolean`
|
||
检查按钮是否可见
|
||
|
||
**功能**:
|
||
- 检查 `options.visibility` 对象
|
||
- 如果未设置或为 true,返回 true
|
||
- 如果设置为 false,返回 false
|
||
|
||
#### `getIcon(icon?: string): string`
|
||
获取图标 HTML
|
||
|
||
**功能**:
|
||
- 如果提供了图标,返回图标 HTML
|
||
- 否则返回默认图标
|
||
|
||
#### `updateLabelsVisibility(): void`
|
||
更新标签可见性
|
||
|
||
**功能**:
|
||
- 遍历所有按钮引用
|
||
- 根据 `showLabel` 选项和按钮是否有标签,更新 `no-label` 类
|
||
|
||
---
|
||
|
||
## 3. 分化组件说明
|
||
|
||
### 3.1 Toolbar
|
||
|
||
**文件路径**: `src/components/button-group/toolbar/index.ts`
|
||
|
||
**继承关系**: `Toolbar extends BimButtonGroup`
|
||
|
||
**特殊功能**:
|
||
- 预定义了底部工具栏的配置
|
||
- 自动加载默认按钮(首页、漫游、定位、设置、信息等)
|
||
- 使用固定的配置(底部居中、横向排列、图标在上、向上展开)
|
||
|
||
**实现方式**:
|
||
- 重写 `init()` 方法
|
||
- 先调用 `super.init()`
|
||
- 动态导入所有默认按钮配置
|
||
- 创建两个组('group-1' 和 'group-2')
|
||
- 添加所有默认按钮
|
||
- 调用 `render()` 渲染
|
||
|
||
**默认按钮列表**:
|
||
- `group-1`:
|
||
- `home` - 首页按钮(通过工厂函数创建,注入 engine)
|
||
- `walk-menu` - 漫游菜单
|
||
- `walk-person` - 人视
|
||
- `walk-bird` - 鸟瞰
|
||
- `location` - 定位
|
||
- `group-2`:
|
||
- `setting` - 设置
|
||
- `info` - 信息
|
||
|
||
**与父类的差异**:
|
||
- 固定了配置选项(`position: 'bottom-center'`, `direction: 'row'`, `align: 'vertical'`, `expand: 'up'`)
|
||
- 自动加载默认按钮
|
||
- 其他功能完全继承自 `BimButtonGroup`
|
||
|
||
---
|
||
|
||
## 4. Manager API 文档
|
||
|
||
### 4.1 ButtonGroupManager 类
|
||
|
||
**文件路径**: `src/managers/button-group-manager.ts`
|
||
|
||
**继承关系**: `ButtonGroupManager extends BimComponent`
|
||
|
||
#### 构造函数
|
||
|
||
```typescript
|
||
constructor(engine: BimEngine, container: HTMLElement)
|
||
```
|
||
|
||
**参数**:
|
||
- `engine`: `BimEngine` - 引擎实例
|
||
- `container`: `HTMLElement` - 按钮组挂载的目标容器
|
||
|
||
#### 公共方法
|
||
|
||
##### `create(id: string, options: Omit<ButtonGroupOptions, 'container'>): BimButtonGroup`
|
||
创建按钮组
|
||
|
||
**参数**:
|
||
- `id`: `string` - 按钮组唯一标识
|
||
- `options`: `Omit<ButtonGroupOptions, 'container'>` - 配置选项(不需要传 container)
|
||
|
||
**返回**: `BimButtonGroup` 实例
|
||
|
||
**功能**:
|
||
- 自动使用 Manager 绑定的容器
|
||
- 创建 `BimButtonGroup` 实例
|
||
- 注入引擎实例
|
||
- 调用 `init()` 初始化
|
||
- 将按钮组保存到 Map 中
|
||
|
||
##### `get(id: string): BimButtonGroup | undefined`
|
||
获取按钮组
|
||
|
||
**参数**:
|
||
- `id`: `string` - 按钮组 ID
|
||
|
||
**返回**: `BimButtonGroup` 实例或 undefined
|
||
|
||
##### `updateTheme(theme: ThemeConfig): void`
|
||
更新所有按钮组的主题
|
||
|
||
**参数**:
|
||
- `theme`: `ThemeConfig` - 主题配置
|
||
|
||
##### `destroy(): void`
|
||
销毁管理器
|
||
|
||
**功能**:
|
||
- 销毁所有按钮组
|
||
- 清空 Map
|
||
|
||
### 4.2 ToolbarManager 类
|
||
|
||
**文件路径**: `src/managers/toolbar-manager.ts`
|
||
|
||
**继承关系**: `ToolbarManager extends BimComponent`
|
||
|
||
#### 构造函数
|
||
|
||
```typescript
|
||
constructor(engine: BimEngine, container: HTMLElement)
|
||
```
|
||
|
||
**参数**:
|
||
- `engine`: `BimEngine` - 引擎实例
|
||
- `container`: `HTMLElement` - 工具栏挂载的目标容器
|
||
|
||
**行为**:
|
||
- 自动调用 `init()` 创建工具栏
|
||
|
||
#### 公共方法
|
||
|
||
##### `updateTheme(theme: ThemeConfig): void`
|
||
更新工具栏主题
|
||
|
||
##### `refresh(): void`
|
||
刷新工具栏
|
||
|
||
**功能**:
|
||
- 调用工具栏的 `render()` 方法重新渲染
|
||
|
||
##### `addGroup(groupId: string, beforeGroupId?: string): void`
|
||
添加按钮组
|
||
|
||
**功能**:
|
||
- 转发到工具栏的 `addGroup()` 方法
|
||
- 自动调用 `render()` 重新渲染
|
||
|
||
##### `addButton(config: ButtonConfig): void`
|
||
添加按钮
|
||
|
||
**功能**:
|
||
- 转发到工具栏的 `addButton()` 方法
|
||
- 自动调用 `render()` 重新渲染
|
||
|
||
##### `setButtonVisibility(id: string, v: boolean): void`
|
||
设置按钮可见性
|
||
|
||
**功能**:
|
||
- 转发到工具栏的 `updateButtonVisibility()` 方法
|
||
|
||
##### `setShowLabel(show: boolean): void`
|
||
设置是否显示标签
|
||
|
||
**功能**:
|
||
- 转发到工具栏的 `setShowLabel()` 方法
|
||
|
||
##### `setVisible(visible: boolean): void`
|
||
设置工具栏可见性
|
||
|
||
**功能**:
|
||
- 通过设置容器的 `visibility` CSS 属性控制显示/隐藏
|
||
|
||
##### `setBackgroundColor(color: string): void`
|
||
设置背景颜色
|
||
|
||
**功能**:
|
||
- 转发到工具栏的 `setBackgroundColor()` 方法
|
||
|
||
##### `setColors(colors: ButtonGroupColors): void`
|
||
设置颜色
|
||
|
||
**功能**:
|
||
- 转发到工具栏的 `setColors()` 方法
|
||
|
||
##### `destroy(): void`
|
||
销毁管理器
|
||
|
||
**功能**:
|
||
- 销毁工具栏实例
|
||
|
||
---
|
||
|
||
## 5. UI 详细描述
|
||
|
||
### 5.1 DOM 结构
|
||
|
||
**完整 HTML 结构**:
|
||
```html
|
||
<div class="bim-btn-group-root [dir-row|dir-column] [static] [className]">
|
||
<!-- 按钮组 1 -->
|
||
<div class="bim-btn-group-section [has-divider]">
|
||
<!-- 按钮包装器 -->
|
||
<div class="opt-btn-wrapper">
|
||
<div class="opt-btn [align-vertical|align-horizontal] [active] [disabled] [no-label]">
|
||
<div class="opt-btn-icon">[图标 SVG]</div>
|
||
<div class="opt-btn-text-wrapper">
|
||
<span class="opt-btn-label">标签文本</span>
|
||
[<span class="opt-btn-arrow [rotated]">▼</span>] <!-- 如果有子项 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 更多按钮... -->
|
||
</div>
|
||
|
||
<!-- 按钮组 2 -->
|
||
<div class="bim-btn-group-section">
|
||
<!-- 更多按钮... -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 下拉菜单(动态创建,添加到 document.body) -->
|
||
<div class="opt-btn-dropdown" style="[位置样式]">
|
||
<div class="opt-btn-dropdown-item [align-horizontal|align-vertical]">
|
||
<div class="opt-btn-icon">[图标 SVG]</div>
|
||
<span class="opt-btn-dropdown-label">标签文本</span>
|
||
</div>
|
||
<!-- 更多菜单项... -->
|
||
</div>
|
||
```
|
||
|
||
### 5.2 CSS 类名和样式
|
||
|
||
#### `.bim-btn-group-root` (根容器)
|
||
- `display: flex` - Flex 布局
|
||
- `gap: 8px` - 组之间的间距
|
||
- `z-index: 1000` - 层级
|
||
- `position: absolute` - 绝对定位(除非是 static)
|
||
- `pointer-events: auto` - 确保可接收事件
|
||
|
||
#### `.bim-btn-group-root.static` (静态定位)
|
||
- `position: relative` - 相对定位
|
||
- 清除所有绝对定位属性
|
||
|
||
#### `.bim-btn-group-root.dir-row` (横向排列)
|
||
- `flex-direction: row` - 横向排列
|
||
- `align-items: center` - 垂直居中
|
||
|
||
#### `.bim-btn-group-root.dir-column` (纵向排列)
|
||
- `flex-direction: column` - 纵向排列
|
||
- `align-items: stretch` - 拉伸对齐
|
||
|
||
#### `.bim-btn-group-section` (按钮组)
|
||
- `display: flex` - Flex 布局
|
||
- `gap: 4px` - 按钮之间的间距
|
||
- `background-color: var(--bim-btn-group-section-bg)` - 背景色
|
||
- `border-radius: 6px` - 圆角
|
||
- `padding: 4px` - 内边距
|
||
- `box-shadow` - 阴影效果
|
||
|
||
#### `.opt-btn-wrapper` (按钮包装器)
|
||
- `position: relative` - 相对定位(为下拉菜单提供定位参考)
|
||
|
||
#### `.opt-btn` (按钮本体)
|
||
- `display: flex` - Flex 布局
|
||
- `cursor: pointer` - 指针光标
|
||
- `border-radius: 4px` - 圆角
|
||
- `transition: background-color 0.2s, color 0.2s` - 过渡动画
|
||
- `padding: 6px` - 内边距
|
||
- `align-items: center` - 垂直居中
|
||
- `justify-content: center` - 水平居中
|
||
|
||
#### `.opt-btn:hover` (按钮悬停)
|
||
- `background-color: var(--bim-btn-hover-bg)` - 悬停背景色
|
||
|
||
#### `.opt-btn.active` (按钮激活)
|
||
- `background-color: var(--bim-btn-active-bg)` - 激活背景色
|
||
- `color: var(--bim-btn-text-active-color)` - 激活文字颜色
|
||
|
||
#### `.opt-btn.disabled` (按钮禁用)
|
||
- `opacity: 0.5` - 半透明
|
||
- `cursor: not-allowed` - 禁止光标
|
||
|
||
#### `.opt-btn-icon` (图标)
|
||
- `width/height: var(--bim-icon-size)` - 图标尺寸
|
||
- `display: flex` - Flex 布局
|
||
- `align-items: center` - 垂直居中
|
||
- `justify-content: center` - 水平居中
|
||
- `color: var(--bim-icon-color)` - 图标颜色
|
||
- `flex-shrink: 0` - 不收缩
|
||
|
||
#### `.opt-btn-text-wrapper` (文字包装器)
|
||
- `display: flex` - Flex 布局
|
||
- `align-items: center` - 垂直居中
|
||
- `justify-content: center` - 水平居中
|
||
- `pointer-events: none` - 不接收鼠标事件
|
||
|
||
#### `.opt-btn-label` (标签)
|
||
- `display: inline` - 内联显示
|
||
|
||
#### `.opt-btn.no-label .opt-btn-label` (无标签时隐藏)
|
||
- `display: none` - 隐藏
|
||
|
||
#### `.opt-btn.align-vertical` (垂直布局 - 图标在上)
|
||
- `flex-direction: column` - 纵向排列
|
||
- `text-align: center` - 文字居中
|
||
|
||
#### `.opt-btn.align-horizontal` (水平布局 - 图标在左)
|
||
- `flex-direction: row` - 横向排列
|
||
|
||
#### `.opt-btn-arrow` (箭头)
|
||
- `font-size: 10px` - 字体大小
|
||
- `opacity: 0.6` - 半透明
|
||
- `transition: transform 0.2s` - 旋转过渡
|
||
- `margin-left: 4px` - 左边距
|
||
|
||
#### `.opt-btn-arrow.rotated` (箭头旋转)
|
||
- `transform: rotate(180deg)` - 旋转 180 度
|
||
|
||
#### `.opt-btn-dropdown` (下拉菜单)
|
||
- `position: absolute` - 绝对定位
|
||
- `background-color: var(--bim-toolbar-bg)` - 背景色
|
||
- `border-radius: 4px` - 圆角
|
||
- `padding: 4px` - 内边距
|
||
- `box-shadow` - 阴影效果
|
||
- `z-index: 1001` - 层级
|
||
- `display: flex` - Flex 布局
|
||
- `flex-direction: column` - 纵向排列(默认)
|
||
- `border: 1px solid rgba(255, 255, 255, 0.1)` - 边框
|
||
- `animation: dropdown-fade-in 0.2s` - 淡入动画
|
||
|
||
#### `.opt-btn-dropdown-item` (菜单项)
|
||
- `display: flex` - Flex 布局
|
||
- `align-items: center` - 垂直居中
|
||
- `padding: 8px 12px` - 内边距
|
||
- `cursor: pointer` - 指针光标
|
||
- `border-radius: 4px` - 圆角
|
||
- `color: var(--bim-btn-text-color)` - 文字颜色
|
||
- `transition: background 0.2s` - 背景过渡
|
||
|
||
#### `.opt-btn-dropdown-item:hover` (菜单项悬停)
|
||
- `background-color: var(--bim-btn-hover-bg)` - 悬停背景色
|
||
- `color: #fff` - 白色文字
|
||
|
||
### 5.3 CSS 变量
|
||
|
||
**定义位置**: 容器元素的内联样式
|
||
|
||
**变量列表**:
|
||
- `--bim-btn-group-section-bg`: 按钮组背景颜色
|
||
- `--bim-btn-bg`: 按钮背景颜色
|
||
- `--bim-btn-hover-bg`: 按钮悬停背景颜色
|
||
- `--bim-btn-active-bg`: 按钮激活背景颜色
|
||
- `--bim-icon-color`: 图标颜色
|
||
- `--bim-icon-active-color`: 图标激活颜色
|
||
- `--bim-btn-text-color`: 文字颜色
|
||
- `--bim-btn-text-active-color`: 文字激活颜色
|
||
- `--bim-toolbar-bg`: 工具栏背景颜色(用于下拉菜单)
|
||
|
||
### 5.4 交互行为
|
||
|
||
#### 按钮点击
|
||
- **触发条件**: 点击按钮元素
|
||
- **行为**:
|
||
- 如果按钮是禁用状态,忽略
|
||
- 如果按钮没有子项:
|
||
- 如果 `keepActive` 为 true,切换激活状态
|
||
- 关闭下拉菜单
|
||
- 调用 `onClick` 回调
|
||
- 如果按钮有子项,不处理(由鼠标悬停处理)
|
||
|
||
#### 鼠标悬停
|
||
- **触发条件**: 鼠标进入按钮
|
||
- **行为**:
|
||
- 如果按钮有子项,显示下拉菜单
|
||
- 如果按钮没有子项,关闭下拉菜单
|
||
|
||
#### 鼠标离开
|
||
- **触发条件**: 鼠标离开按钮或下拉菜单
|
||
- **行为**:
|
||
- 延迟 200ms 后关闭下拉菜单(防止鼠标移动到菜单时关闭)
|
||
|
||
#### 下拉菜单显示
|
||
- **触发条件**: 鼠标悬停在有子项的按钮上
|
||
- **行为**:
|
||
- 创建下拉菜单元素
|
||
- 计算位置(根据 `expand` 选项)
|
||
- 添加到 `document.body`
|
||
- 显示淡入动画
|
||
|
||
#### 下拉菜单关闭
|
||
- **触发条件**:
|
||
- 鼠标离开按钮和菜单区域
|
||
- 点击按钮(非菜单按钮)
|
||
- **行为**:
|
||
- 移除下拉菜单元素
|
||
- 移除箭头旋转状态
|
||
|
||
### 5.5 响应式行为
|
||
|
||
- 按钮组位置会根据容器尺寸自动调整(如果使用绝对定位)
|
||
- 下拉菜单位置会根据按钮位置和窗口尺寸自动计算
|
||
- 标签文本支持多语言切换
|
||
- 按钮可见性可以动态控制
|
||
|
||
---
|
||
|
||
## 6. 逻辑流程详细描述
|
||
|
||
### 6.1 初始化流程
|
||
|
||
1. **构造函数调用**:
|
||
- 解析容器元素
|
||
- 合并配置选项
|
||
- 记录用户自定义的颜色属性
|
||
- 调用 `initContainer()` 初始化容器
|
||
- 调用 `applyStyles()` 应用样式
|
||
|
||
2. **init() 方法执行**:
|
||
- 调用 `render()` 渲染按钮组
|
||
- 订阅语言变更:`localeManager.subscribe()`
|
||
- 订阅主题变更:`themeManager.subscribe()`
|
||
|
||
### 6.2 渲染流程
|
||
|
||
1. **render() 方法**:
|
||
- 清空容器
|
||
- 清空按钮引用映射
|
||
- 遍历所有组,调用 `renderGroup()` 渲染每个组
|
||
|
||
2. **renderGroup() 方法**:
|
||
- 创建组元素
|
||
- 如果不是最后一组,添加分隔线类
|
||
- 遍历组内按钮,调用 `renderButton()` 渲染每个可见按钮
|
||
|
||
3. **renderButton() 方法**:
|
||
- 创建按钮包装器和按钮元素
|
||
- 设置布局方向(根据按钮或全局配置)
|
||
- 设置激活/禁用状态
|
||
- 创建图标元素
|
||
- 创建文字包装器(包含标签和箭头)
|
||
- 绑定事件监听器
|
||
- 保存按钮引用
|
||
|
||
### 6.3 事件处理流程
|
||
|
||
#### 按钮点击流程
|
||
1. 用户点击按钮 → `click` 事件
|
||
2. `handleClick()` 被调用
|
||
3. 检查按钮是否禁用
|
||
4. 如果按钮没有子项:
|
||
- 如果 `keepActive` 为 true,切换激活状态
|
||
- 更新按钮的 `active` 类
|
||
- 关闭下拉菜单
|
||
- 调用 `onClick` 回调
|
||
|
||
#### 下拉菜单显示流程
|
||
1. 鼠标进入按钮 → `mouseenter` 事件
|
||
2. `handleMouseEnter()` 被调用
|
||
3. 清除悬停超时
|
||
4. 如果按钮有子项,调用 `showDropdown()`
|
||
5. `showDropdown()` 执行:
|
||
- 关闭当前打开的下拉菜单
|
||
- 创建下拉菜单元素
|
||
- 设置菜单布局方向(根据主组方向)
|
||
- 渲染所有可见的子按钮
|
||
- 计算菜单位置(根据 `expand` 选项)
|
||
- 添加到 `document.body`
|
||
- 绑定鼠标事件(保持菜单打开)
|
||
|
||
#### 下拉菜单关闭流程
|
||
1. 鼠标离开按钮或菜单 → `mouseleave` 事件
|
||
2. `handleMouseLeave()` 被调用
|
||
3. 设置 200ms 延迟
|
||
4. 延迟后调用 `closeDropdown()`
|
||
5. `closeDropdown()` 执行:
|
||
- 移除下拉菜单元素
|
||
- 移除箭头旋转状态
|
||
|
||
### 6.4 状态管理
|
||
|
||
#### 内部状态
|
||
- `groups`: 按钮组数组
|
||
- `activeBtnIds`: 激活按钮 ID 的 Set
|
||
- `btnRefs`: 按钮元素引用的 Map
|
||
- `dropdownElement`: 当前打开的下拉菜单元素
|
||
- `hoverTimeout`: 悬停超时的 ID
|
||
- `customColors`: 用户自定义颜色属性的 Set
|
||
|
||
#### 订阅管理
|
||
- `unsubscribeLocale`: 语言订阅取消函数
|
||
- `unsubscribeTheme`: 主题订阅取消函数
|
||
|
||
### 6.5 与其他组件的交互
|
||
|
||
- **与 ButtonGroupManager/ToolbarManager**: 通过 Manager 创建和管理
|
||
- **与 ThemeManager**: 订阅主题变更
|
||
- **与 LocaleManager**: 订阅语言变更
|
||
- **与 BimEngine**: 通过引擎发送事件(如果设置了引擎)
|
||
|
||
---
|
||
|
||
## 7. 国际化支持
|
||
|
||
### 7.1 使用的翻译键
|
||
|
||
- `button.label`: 按钮标签(每个按钮的 `label` 属性)
|
||
|
||
### 7.2 语言变更处理
|
||
|
||
- 组件订阅 `localeManager.subscribe()`
|
||
- 语言变更时,`setLocales()` 方法被调用
|
||
- 调用 `render()` 重新渲染,更新所有按钮的标签文本
|
||
|
||
### 7.3 实现细节
|
||
|
||
```typescript
|
||
public setLocales(): void {
|
||
this.render(); // 重新渲染,标签会通过 t() 函数获取最新翻译
|
||
}
|
||
|
||
// 在 renderButton() 中
|
||
label.textContent = t(button.label);
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 主题支持
|
||
|
||
### 8.1 使用的主题变量
|
||
|
||
- `theme.panelBackground` → `backgroundColor`
|
||
- `theme.componentBackground` → `btnBackgroundColor`
|
||
- `theme.componentHover` → `btnHoverColor`
|
||
- `theme.componentActive` → `btnActiveColor`
|
||
- `theme.icon` → `iconColor`
|
||
- `theme.iconActive` → `iconActiveColor`
|
||
- `theme.textSecondary` → `textColor`
|
||
- `theme.textPrimary` → `textActiveColor`
|
||
|
||
### 8.2 主题变更处理
|
||
|
||
- 组件订阅 `themeManager.subscribe()`
|
||
- 主题变更时,`setTheme()` 方法被调用
|
||
- **只应用没有被用户自定义的颜色属性**
|
||
- 如果用户已自定义颜色,保持用户自定义
|
||
|
||
### 8.3 自定义颜色保护机制
|
||
|
||
- 构造函数中记录用户自定义的颜色属性
|
||
- `setTheme()` 时只更新未自定义的颜色
|
||
- `setColors()` 时标记颜色为自定义
|
||
|
||
---
|
||
|
||
## 9. 使用示例
|
||
|
||
### 9.1 基本使用(通过 ButtonGroupManager)
|
||
|
||
```typescript
|
||
import { BimEngine } from 'bim-engine-sdk';
|
||
|
||
const engine = new BimEngine('container');
|
||
|
||
// 创建按钮组
|
||
const buttonGroup = engine.buttonGroup.create('my-group', {
|
||
position: 'top-right',
|
||
direction: 'row',
|
||
showLabel: true
|
||
});
|
||
|
||
// 添加组
|
||
buttonGroup.addGroup('group-1');
|
||
|
||
// 添加按钮
|
||
buttonGroup.addButton({
|
||
id: 'btn-1',
|
||
groupId: 'group-1',
|
||
type: 'button',
|
||
label: 'toolbar.home',
|
||
icon: '<svg>...</svg>',
|
||
onClick: (button) => {
|
||
console.log('按钮被点击:', button.id);
|
||
}
|
||
});
|
||
```
|
||
|
||
### 9.2 使用工具栏(通过 ToolbarManager)
|
||
|
||
```typescript
|
||
// 工具栏已自动创建,直接使用
|
||
engine.toolbar.addButton({
|
||
id: 'custom-btn',
|
||
groupId: 'group-1',
|
||
type: 'button',
|
||
label: 'toolbar.custom',
|
||
icon: '<svg>...</svg>',
|
||
onClick: (button) => {
|
||
console.log('自定义按钮被点击');
|
||
}
|
||
});
|
||
|
||
// 控制按钮可见性
|
||
engine.toolbar.setButtonVisibility('location', false);
|
||
|
||
// 控制标签显示
|
||
engine.toolbar.setShowLabel(false);
|
||
```
|
||
|
||
### 9.3 使用嵌套菜单
|
||
|
||
```typescript
|
||
// 添加菜单按钮
|
||
buttonGroup.addButton({
|
||
id: 'menu-btn',
|
||
groupId: 'group-1',
|
||
type: 'menu',
|
||
label: 'toolbar.menu',
|
||
icon: '<svg>...</svg>',
|
||
children: [
|
||
{
|
||
id: 'sub-1',
|
||
type: 'button',
|
||
label: 'toolbar.sub1',
|
||
onClick: () => console.log('子项 1')
|
||
},
|
||
{
|
||
id: 'sub-2',
|
||
type: 'button',
|
||
label: 'toolbar.sub2',
|
||
onClick: () => console.log('子项 2')
|
||
}
|
||
]
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 实现细节(供 AI 重现)
|
||
|
||
### 10.1 关键算法
|
||
|
||
#### 位置计算算法
|
||
```typescript
|
||
function calculatePosition(position, containerWidth, containerHeight) {
|
||
const margin = 20;
|
||
switch (position) {
|
||
case 'top-left': return { top: margin, left: margin };
|
||
case 'top-center': return { top: margin, left: '50%', transform: 'translateX(-50%)' };
|
||
case 'top-right': return { top: margin, right: margin };
|
||
// ... 其他位置
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 下拉菜单位置计算算法
|
||
```typescript
|
||
function calculateDropdownPosition(expand, buttonRect, dropdownRect) {
|
||
switch (expand) {
|
||
case 'up':
|
||
return {
|
||
bottom: window.innerHeight - buttonRect.top + 8,
|
||
left: buttonRect.left + (buttonRect.width - dropdownRect.width) / 2
|
||
};
|
||
case 'down':
|
||
return {
|
||
top: buttonRect.bottom + 8,
|
||
left: buttonRect.left + (buttonRect.width - dropdownRect.width) / 2
|
||
};
|
||
// ... 其他方向
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 激活状态切换算法
|
||
```typescript
|
||
function toggleActiveState(buttonId, activeBtnIds) {
|
||
if (activeBtnIds.has(buttonId)) {
|
||
activeBtnIds.delete(buttonId);
|
||
} else {
|
||
activeBtnIds.add(buttonId);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 10.2 性能优化点
|
||
|
||
1. **按钮引用缓存**:
|
||
- 使用 `btnRefs` Map 缓存按钮元素引用
|
||
- 避免重复查询 DOM
|
||
|
||
2. **延迟关闭下拉菜单**:
|
||
- 使用 200ms 延迟,防止鼠标移动到菜单时关闭
|
||
- 使用 `setTimeout` 和 `clearTimeout` 管理
|
||
|
||
3. **条件渲染**:
|
||
- 只渲染可见的按钮(根据 `visibility` 选项)
|
||
- 减少 DOM 操作
|
||
|
||
4. **CSS 变量**:
|
||
- 使用 CSS 变量应用主题和颜色
|
||
- 主题变更时无需重新渲染 DOM
|
||
|
||
### 10.3 注意事项和边界情况
|
||
|
||
1. **按钮 ID 唯一性**:
|
||
- 按钮 ID 必须唯一
|
||
- 如果重复,可能导致状态管理错误
|
||
|
||
2. **组 ID 唯一性**:
|
||
- 组 ID 必须唯一
|
||
- `addGroup()` 会检查重复
|
||
|
||
3. **嵌套菜单深度**:
|
||
- 当前实现只支持一级嵌套(按钮 → 子项)
|
||
- 不支持多级嵌套
|
||
|
||
4. **下拉菜单定位**:
|
||
- 下拉菜单添加到 `document.body`,使用绝对定位
|
||
- 需要根据按钮位置和窗口尺寸计算位置
|
||
- 可能需要处理边界情况(菜单超出窗口)
|
||
|
||
5. **事件清理**:
|
||
- 组件销毁时必须清理所有事件监听
|
||
- 必须取消主题和语言订阅
|
||
- 必须关闭下拉菜单
|
||
|
||
6. **翻译键处理**:
|
||
- 标签可能是翻译键或普通文本
|
||
- 只有翻译键才需要通过 `t()` 函数处理
|
||
|
||
7. **自定义颜色保护**:
|
||
- 用户自定义的颜色不会被主题覆盖
|
||
- 需要正确记录和管理自定义颜色
|
||
|
||
---
|
||
|
||
## 11. 类型定义
|
||
|
||
### 11.1 ButtonGroupOptions
|
||
|
||
```typescript
|
||
interface ButtonGroupOptions extends ButtonGroupColors {
|
||
container: HTMLElement | string; // 容器(必需)
|
||
position?: GroupPosition; // 位置(可选)
|
||
direction?: GroupDirection; // 排列方向(可选,默认 'row')
|
||
align?: ButtonAlign; // 按钮内部布局(可选,默认 'vertical')
|
||
expand?: ExpandDirection; // 菜单展开方向(可选,默认 'down')
|
||
showLabel?: boolean; // 是否显示标签(可选,默认 true)
|
||
visibility?: Record<string, boolean>; // 按钮可见性(可选)
|
||
className?: string; // 自定义类名(可选)
|
||
}
|
||
```
|
||
|
||
### 11.2 ButtonConfig
|
||
|
||
```typescript
|
||
interface ButtonConfig {
|
||
id: string; // 按钮 ID(必需,必须唯一)
|
||
type: ButtonType; // 按钮类型(必需,'button' | 'menu')
|
||
label: string; // 标签(必需,支持翻译键)
|
||
icon?: string; // 图标 SVG(可选)
|
||
keepActive?: boolean; // 是否保持激活状态(可选)
|
||
disabled?: boolean; // 是否禁用(可选)
|
||
onClick?: (button: OptButton) => void; // 点击回调(可选)
|
||
children?: ButtonConfig[]; // 子项(可选,用于菜单)
|
||
groupId?: string; // 所属组 ID(可选)
|
||
parentId?: string; // 父按钮 ID(可选,用于嵌套)
|
||
align?: ButtonAlign; // 按钮内部布局(可选)
|
||
iconSize?: number; // 图标大小(可选,默认 32)
|
||
minWidth?: number; // 最小宽度(可选,默认 50)
|
||
}
|
||
```
|
||
|
||
### 11.3 其他类型
|
||
|
||
```typescript
|
||
type ButtonType = 'button' | 'menu';
|
||
type GroupPosition = 'center' | 'top-left' | 'top-center' | 'top-right'
|
||
| 'left-center' | 'right-center'
|
||
| 'bottom-left' | 'bottom-center' | 'bottom-right'
|
||
| { x: number; y: number } | 'static';
|
||
type GroupDirection = 'row' | 'column';
|
||
type ButtonAlign = 'vertical' | 'horizontal';
|
||
type ExpandDirection = 'up' | 'down' | 'left' | 'right';
|
||
```
|
||
|
||
---
|
||
|
||
## 12. 文件清单
|
||
|
||
### 12.1 相关文件
|
||
|
||
- `src/components/button-group/index.ts` - 主组件类
|
||
- `src/components/button-group/index.type.ts` - 类型定义
|
||
- `src/components/button-group/index.css` - 样式文件
|
||
- `src/components/button-group/toolbar/index.ts` - 工具栏(分化组件)
|
||
- `src/managers/button-group-manager.ts` - 通用按钮组管理器
|
||
- `src/managers/toolbar-manager.ts` - 工具栏管理器
|
||
|
||
### 12.2 依赖文件
|
||
|
||
- `src/types/component.ts` - IBimComponent 接口
|
||
- `src/themes/types.ts` - ThemeConfig 类型
|
||
- `src/services/theme.ts` - ThemeManager
|
||
- `src/services/locale.ts` - LocaleManager
|
||
- `src/types/events.ts` - EngineEvents 类型
|
||
|
||
---
|
||
|
||
## 13. 更新记录
|
||
|
||
| 日期 | 修改内容 | 修改人 |
|
||
|------|---------|--------|
|
||
| 2024-XX-XX | 初始创建 | AI Assistant |
|
||
|
||
---
|
||
|
||
**重要提醒**: 本文档必须与组件代码保持同步。任何组件修改都必须更新本文档!
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|