# 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` 初始化组件功能(实现 `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(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): BimButtonGroup` 创建按钮组 **参数**: - `id`: `string` - 按钮组唯一标识 - `options`: `Omit` - 配置选项(不需要传 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
[图标 SVG]
标签文本 []
[图标 SVG]
标签文本
``` ### 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: '...', 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: '...', 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: '...', 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; // 按钮可见性(可选) 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 | --- **重要提醒**: 本文档必须与组件代码保持同步。任何组件修改都必须更新本文档!