Files
bim_engine/docs/components/button-group.md
2025-12-16 11:57:44 +08:00

1186 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 |
---
**重要提醒**: 本文档必须与组件代码保持同步。任何组件修改都必须更新本文档!