feat: Refactor engine structure and add UI customization support

- Refactor  to delegate logic to  and
- Add  for manager classes
- Implement dynamic styling for Toolbar (color config, CSS vars)
- Implement dynamic styling for Dialog (options, CSS vars)
- Add  example
- Add documentation for Toolbar and Dialog
- Update demo to showcase new styling features
This commit is contained in:
yuding
2025-12-03 18:35:05 +08:00
parent 14ac91aa6e
commit 4dd923f19e
21 changed files with 2328 additions and 243 deletions

View File

@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BIM Engine SDK Demo</title>
<script src="./dist/bim-engine-sdk.umd.js"></script>
</head>
<body>
<div id="app" style="width: 1000px; height: 500px; border: 1px dashed #ccc;"></div>
<script>
// 等待 SDK 加载
window.onload = () => {
if (window.LyzBimEngineSDK) {
const Engine = window.LyzBimEngineSDK.BimEngine;
try {
const instance = new Engine(document.getElementById('app'));
console.log('Engine initialized:', instance);
} catch (err) {
console.error('Init failed:', err);
}
} else {
console.error('SDK not found');
}
};
</script>
</body>
</html>

171
demo/index.html Normal file
View File

@@ -0,0 +1,171 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BIM Engine SDK Demo</title>
<script src="../dist/bim-engine-sdk.umd.js"></script>
</head>
<body>
<div id="app" style="width: 100%; height: 500px; border: 1px dashed #ccc;"></div>
<div style="margin-top: 20px; display: flex; gap: 10px; flex-wrap: wrap;">
<button onclick="toggleToolbar()">切换工具栏显隐</button>
<button onclick="toggleLabel()">切换文字标签</button>
<button onclick="addCustomGroup()">添加自定义组(最前)</button>
<button onclick="addCustomButton()">添加按钮到自定义组</button>
<button onclick="toggleLocationBtn()">切换"定位"按钮显隐</button>
<button onclick="changeToolbarColor()">修改工具栏颜色 (蓝色)</button>
<button onclick="changeToolbarStyleFull()">修改工具栏样式 (浅色主题)</button>
<button onclick="resetToolbarStyle()">重置工具栏样式</button>
<button onclick="openRedDialog()">打开红色弹窗 (样式定制)</button>
<button onclick="openCustomHeaderDialog()">打开自定义标题弹窗</button>
</div>
<script>
let engine = null;
let isToolbarVisible = true;
let isLabelVisible = true;
let isLocationVisible = true;
let customGroupAdded = false;
// 等 SDK <20><>
window.onload = () => {
if (window.LyzBimEngineSDK) {
const Engine = window.LyzBimEngineSDK.BimEngine;
try {
engine = new Engine(document.getElementById('app'));
console.log('Engine initialized:', engine);
} catch (err) {
console.error('Init failed:', err);
}
} else {
console.error('SDK not found');
}
};
function toggleToolbar() {
if (!engine || !engine.toolbar) return;
isToolbarVisible = !isToolbarVisible;
engine.toolbar.setVisible(isToolbarVisible);
}
function toggleLabel() {
if (!engine || !engine.toolbar) return;
isLabelVisible = !isLabelVisible;
engine.toolbar.setShowLabel(isLabelVisible);
}
function addCustomGroup() {
if (!engine || !engine.toolbar) return;
if (customGroupAdded) {
alert('已添加过');
return;
}
// 添加到 group-1 之前
engine.toolbar.addGroup('custom-group', 'group-1');
customGroupAdded = true;
console.log('Added custom group');
}
function addCustomButton() {
if (!engine || !engine.toolbar) return;
if (!customGroupAdded) {
alert('请先添加自定义组');
return;
}
const btnId = 'custom-btn-' + Date.now();
engine.toolbar.addButton({
id: btnId,
groupId: 'custom-group',
type: 'button',
label: '新按钮',
// 一个简单的笑脸 SVG
icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>',
onClick: (btn) => {
alert('你点击了动态添加的按钮: ' + btn.label);
}
});
}
function toggleLocationBtn() {
if (!engine || !engine.toolbar) return;
isLocationVisible = !isLocationVisible;
// location 按钮的 ID 是 'location'
engine.toolbar.setButtonVisibility('location', isLocationVisible);
}
function changeToolbarColor() {
if (!engine || !engine.toolbar) return;
// 仅修改背景色
engine.toolbar.setBackgroundColor('rgba(0, 100, 200, 0.9)');
}
function changeToolbarStyleFull() {
if (!engine || !engine.toolbar) return;
// 完整的浅色主题配置
engine.toolbar.setColors({
backgroundColor: '#f0f0f0',
btnBackgroundColor: '#ffffff',
btnHoverColor: '#e0e0e0',
btnActiveColor: '#d0d0d0',
iconColor: '#333333',
iconActiveColor: '#0078d4',
textColor: '#666666',
textActiveColor: '#000000'
});
}
function resetToolbarStyle() {
if (!engine || !engine.toolbar) return;
// 重置为默认深色主题
engine.toolbar.setColors({
backgroundColor: 'rgba(17, 17, 17, 0.88)',
btnBackgroundColor: 'transparent',
btnHoverColor: '#444',
btnActiveColor: 'rgba(255, 255, 255, 0.15)',
iconColor: '#ccc',
iconActiveColor: '#fff',
textColor: '#ccc',
textActiveColor: '#fff'
});
}
function openRedDialog() {
if (!engine || !engine.dialog) return;
engine.dialog.create({
title: '警报',
content: '<div style="color: #ffcccc;">这是一个高度定制的警告弹窗。<br>注意查看标题栏和边框颜色。</div>',
width: 300,
height: 150,
backgroundColor: 'rgba(100, 0, 0, 0.95)',
headerBackgroundColor: '#cc0000',
titleColor: '#ffffff',
borderColor: '#ff6666',
position: { x: 50, y: 50 } // 自定义坐标
});
}
function openCustomHeaderDialog() {
if (!engine || !engine.dialog) return;
engine.dialog.create({
title: '自定义样式弹窗',
content: '观察标题栏背景和文字颜色。',
width: 300,
headerBackgroundColor: '#0078d4', // 蓝色标题栏
titleColor: '#ffffff',
backgroundColor: '#ffffff', // 白色内容背景
textColor: '#333333', // 深色文字
borderColor: '#0078d4', // 蓝色边框
position: 'center'
});
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

363
dist/index.d.ts vendored
View File

@@ -1,9 +1,81 @@
export declare class BimEngine { /**
* 通用弹窗组件类
* 支持拖拽、缩放、自定义内容和位置。
*/
declare class BimDialog {
private element;
private options;
private container; private container;
private optBtnGroups; private header;
constructor(container: HTMLElement | string); private contentArea;
private _isDestroyed;
/**
* 构造函数
* @param options 弹窗配置选项
*/
constructor(options: DialogOptions);
/**
* 创建弹窗的 DOM 结构
*/
private createDom;
/**
* 设置元素尺寸
*/
private setSize;
/**
* 初始化组件功能
*/
private init; private init;
private initOptBtnGroups; /**
* 初始化弹窗位置
*/
private initPosition;
/**
* 初始化拖拽功能
*/
private initDrag;
/**
* 初始化缩放功能
*/
private initResize;
/**
* 动态设置内容
* @param content 内容元素或 HTML 字符串
*/
setContent(content: HTMLElement | string): void;
/**
* 关闭弹窗并销毁
*/
close(): void;
}
/**
* BimEngine 主类
* 负责初始化整个应用界面,协调各个子模块(如工具栏、弹窗等)。
*/
export declare class BimEngine {
/** 主容器元素 */
private container;
/** 内部包装器元素,用于承载所有 UI 组件 */
private wrapper;
/** 工具栏管理器实例 */
toolbar: ToolbarManager | null;
/** 弹窗管理器实例 */
dialog: DialogManager | null;
/**
* 构造函数
* @param container 容器元素或容器 ID
*/
constructor(container: HTMLElement | string);
/**
* 初始化方法
* 创建 DOM 结构并初始化各子模块
*/
private init;
/**
* 销毁实例
* 清理所有资源和 DOM 元素
*/
destroy(): void; destroy(): void;
} }
@@ -11,15 +83,25 @@ export declare class BimEngine {
* 按钮配置接口(用于外部定义按钮) * 按钮配置接口(用于外部定义按钮)
*/ */
declare interface ButtonConfig { declare interface ButtonConfig {
/** 唯一标识 */
id: string; id: string;
/** 按钮类型:普通按钮或菜单按钮 */
type: ButtonType; type: ButtonType;
/** 按钮显示文字 */
label: string; label: string;
/** SVG 图标(内联 SVG 字符串) */
icon?: string; icon?: string;
/** 是否保持激活状态(默认 false */
keepActive?: boolean; keepActive?: boolean;
/** 是否禁用 */
disabled?: boolean; disabled?: boolean;
/** 点击回调函数 */
onClick?: (button: OptButton) => void; onClick?: (button: OptButton) => void;
/** 子按钮配置(可选,用于菜单按钮) */
children?: ButtonConfig[]; children?: ButtonConfig[];
/** 所属组ID */
groupId?: string; groupId?: string;
/** 父按钮ID如果是子按钮则必填 */
parentId?: string; parentId?: string;
} }
@@ -27,7 +109,9 @@ declare interface ButtonConfig {
* 按钮组接口 * 按钮组接口
*/ */
export declare interface ButtonGroup { export declare interface ButtonGroup {
/** 组 ID */
id: string; id: string;
/** 组内按钮列表 */
buttons: OptButton[]; buttons: OptButton[];
} }
@@ -37,57 +121,236 @@ declare type ButtonType = 'button' | 'menu';
* 点击事件载荷 * 点击事件载荷
*/ */
export declare interface ClickPayload { export declare interface ClickPayload {
/** 被点击的按钮对象 */
button: OptButton; button: OptButton;
/** 触发的动作类型 */
action: 'activate' | 'deactivate' | 'trigger'; action: 'activate' | 'deactivate' | 'trigger';
/** 当前激活状态 */
isActive?: boolean; isActive?: boolean;
} }
export declare class OptBtnGroups { /**
* 弹窗颜色配置
*/
declare interface DialogColors {
/** 窗体背景颜色,默认 rgba(17, 17, 17, 0.95) */
backgroundColor?: string;
/** 标题栏背景颜色,默认 #2a2a2a */
headerBackgroundColor?: string;
/** 标题文字颜色,默认 #fff */
titleColor?: string;
/** 内容文字颜色,默认 #ccc */
textColor?: string;
/** 边框颜色,默认 #444 */
borderColor?: string;
}
/**
* 弹窗管理器
* 负责创建和管理应用中的各类弹窗。
*/
declare class DialogManager {
/** 弹窗挂载的父容器 */
private container; private container;
/**
* 构造函数
* @param container 弹窗挂载的目标容器
*/
constructor(container: HTMLElement);
/**
* 创建一个通用弹窗
* @param options 弹窗配置选项(不需要传 container自动使用管理器绑定的容器
* @returns BimDialog 实例
*/
create(options: Omit<DialogOptions, 'container'>): BimDialog;
/**
* 显示二次封装的模型信息弹窗
* 演示如何调用特定的业务弹窗组件
*/
showInfoDialog(): void;
}
/**
* 弹窗配置选项接口
*/
declare interface DialogOptions extends DialogColors {
/** 弹窗挂载的父容器 */
container: HTMLElement;
/** 弹窗标题 */
title?: string;
/** 弹窗内容,支持 HTML 字符串或 HTMLElement */
content?: HTMLElement | string;
/** 弹窗宽度,数字(像素)或字符串(如 '50%' */
width?: number | string;
/** 弹窗高度 */
height?: number | string;
/** 弹窗位置 */
position?: DialogPosition;
/** 是否可拖拽 */
draggable?: boolean;
/** 是否可调整大小 */
resizable?: boolean;
/** 最小宽度限制 */
minWidth?: number;
/** 最小高度限制 */
minHeight?: number;
/** 关闭时的回调函数 */
onClose?: () => void;
/** 弹窗唯一标识 ID (可选) */
id?: string;
}
/**
* 弹窗位置类型定义
* 可以是预设的字符串位置(如 'center', 'top-left' 等),
* 也可以是具体的坐标对象 { x, y }
*/
declare type DialogPosition = 'center' | 'top-left' | 'top-center' | 'top-right' | 'left-center' | 'right-center' | 'bottom-left' | 'bottom-center' | 'bottom-right' | {
x: number;
y: number;
};
/**
* 底部操作按钮组组件
* 负责渲染和管理底部工具栏的按钮、下拉菜单及相关交互。
*/
export declare class OptBtnGroups {
/** 挂载容器 */
private container;
/** 组件配置选项 */
private options; private options;
/** 按钮组列表,按顺序存储 */
private groups; private groups;
/** 当前处于激活状态的按钮 ID 集合 */
private activeBtnIds; private activeBtnIds;
/** 按钮 DOM 元素的引用映射,方便快速查找 */
private btnRefs; private btnRefs;
/** 当<><E5BD93>显示的下拉菜单元素 */
private dropdownElement; private dropdownElement;
/** 鼠标悬停计时器,用于处理菜单显示的防抖 */
private hoverTimeout; private hoverTimeout;
/** 默认图标 SVG */
private readonly DEFAULT_ICON; private readonly DEFAULT_ICON;
/**
* 构造函数
* @param options 配置选项
*/
constructor(options: OptBtnGroupsOptions); constructor(options: OptBtnGroupsOptions);
/**
* 初始化容器
*/
private initContainer; private initContainer;
/**
* 应用样式配置到 CSS 变量
*/
private applyStyles;
/**
* 更新颜色配置
* @param colors 颜色配置对象
*/
setColors(colors: ToolbarColors): void;
/** /**
* 添加按钮组 * 添加按钮组
* @param groupId 组ID * @param groupId 组ID
* @param beforeGroupId 在哪个组之前插入(可选,不传则插入到最后 * @param beforeGroupId 在哪个组之前插入(可选<EFBFBD><EFBFBD><EFBFBD>,不传则插入到最后
*/ */
addGroup(groupId: string, beforeGroupId?: string): void; addGroup(groupId: string, beforeGroupId?: string): void;
/** /**
* 添加按钮 * 添加按钮到指定组
* @param config 按钮配置(必须包含 groupId可选包含 parentId * @param config 按钮配置(必须包含 groupId可选包含 parentId
*/ */
addButton(config: ButtonConfig): void; addButton(config: ButtonConfig): void;
/**
* 递归查找按钮
*/
private findButton; private findButton;
/**
* 初始化组件,加载默认按钮配置
*/
init(): Promise<void>; init(): Promise<void>;
/**
* 渲染整个工具栏
*/
render(): void; render(): void;
/**
* 渲染单个按钮组
*/
private renderGroup; private renderGroup;
/**
* 渲染单个按钮
*/
private renderButton; private renderButton;
/**
* 处理按钮点击事件
*/
private handleClick; private handleClick;
/**
* 处理子菜单项点击事件
*/
private handleSubClick; private handleSubClick;
/**
* 处理鼠标移入事件(显示菜单)
*/
private handleMouseEnter; private handleMouseEnter;
/**
* 处理鼠标移出事件(隐藏菜单)
*/
private handleMouseLeave; private handleMouseLeave;
/**
* 显示下拉菜单
*/
private showDropdown; private showDropdown;
/**
* 渲染下拉菜单项
*/
private renderDropdownItem; private renderDropdownItem;
/**
* 关闭所有下拉菜单
*/
private closeDropdown; private closeDropdown;
/**
* 更新按钮的激活状态样式
*/
private updateButtonState; private updateButtonState;
/**
* 获取图标 SVG 字符串
*/
private getIcon; private getIcon;
/**
* 更新按钮可见性
* @param buttonId 按钮ID
* @param visible 是否可见
*/
updateButtonVisibility(buttonId: string, visible: boolean): void;
/**
* 设置是否显示标签
* @param show 是否显示
*/
setShowLabel(show: boolean): void;
/**
* 设置背景颜色 (兼容旧接口)
* @param color CSS 颜色值
*/
setBackgroundColor(color: string): void;
/**
* 检查按钮是否可见
*/
private isVisible; private isVisible;
/**
* 销毁组件,清理资源
*/
destroy(): void; destroy(): void;
} }
/** /**
* OptBtnGroups 配置选项 * OptBtnGroups 配置选项
*/ */
export declare interface OptBtnGroupsOptions { export declare interface OptBtnGroupsOptions extends ToolbarColors {
/** 容器元素或 ID */
container: HTMLElement | string; container: HTMLElement | string;
/** 是否显示标签 */
showLabel?: boolean; showLabel?: boolean;
/** 按钮可见性配置 Map */
visibility?: Record<string, boolean>; visibility?: Record<string, boolean>;
} }
@@ -95,7 +358,91 @@ export declare interface OptBtnGroupsOptions {
* 操作按钮接口(内部使用,继承配置) * 操作按钮接口(内部使用,继承配置)
*/ */
export declare interface OptButton extends ButtonConfig { export declare interface OptButton extends ButtonConfig {
/** 内部使用的子按钮列表 */
children?: OptButton[]; children?: OptButton[];
} }
/**
* 工具栏颜色配置接口
*/
declare interface ToolbarColors {
/** 工具栏背景颜色 */
backgroundColor?: string;
/** 按钮默认背景颜色 */
btnBackgroundColor?: string;
/** 按钮 Hover 背景颜色 */
btnHoverColor?: string;
/** 按钮激活状态背景颜色 */
btnActiveColor?: string;
/** 图标默认颜色 */
iconColor?: string;
/** 图标激活/Hover 颜色 */
iconActiveColor?: string;
/** 文字默认颜色 */
textColor?: string;
/** 文字激活/Hover 颜色 */
textActiveColor?: string;
}
/**
* 工具栏管理器
* 负责管理底部操作栏的按钮组、按钮及其可见性等状态。
*/
declare class ToolbarManager {
/** 内部工具栏组件实例 */
private optBtnGroups;
/** 工具栏挂载的容器 */
private container;
/**
* 构造函数
* @param container 工具栏挂载的容器元素
*/
constructor(container: HTMLElement);
/**
* 初始化工具栏
*/
private init;
/**
* 添加一个工具栏按钮组
* @param groupId 新组的 ID
* @param beforeGroupId (可选) 插入到哪个组之前,不传则追加到最后
*/
addGroup(groupId: string, beforeGroupId?: string): void;
/**
* 添加一个工具栏按钮
* @param config 按钮配置对象
*/
addButton(config: ButtonConfig): void;
/**
* 设置按钮的可见性
* @param buttonId 按钮 ID
* @param visible 是否可见
*/
setButtonVisibility(buttonId: string, visible: boolean): void;
/**
* 设置是否显示按钮下方的文字标签
* @param show 是否显示
*/
setShowLabel(show: boolean): void;
/**
* 设置整个工具栏的可见性
* @param visible 是否可见
*/
setVisible(visible: boolean): void;
/**
* 设置工具栏背景颜色
* @param color CSS 颜色值
*/
setBackgroundColor(color: string): void;
/**
* 设置工具栏详细颜色配置
* @param colors 颜色配置对象
*/
setColors(colors: ToolbarColors): void;
/**
* 销毁工具栏管理器
*/
destroy(): void;
}
export { } export { }

95
docs/Dialog.md Normal file
View File

@@ -0,0 +1,95 @@
# 弹窗组件 (Dialog)
BimEngine SDK 提供了可拖拽、可缩放的通用弹窗组件 `DialogManager` (底层基于 `BimDialog`),支持自定义内容和高度定制的样式。
## 1. 组件作用
* 提供浮动的交互窗口。
* 支持任意 HTML 内容挂载。
* 内置拖拽移动和拖拽缩放功能。
* 支持丰富的样式定制接口。
## 2. 初始化与使用
通过 `BimEngine` 实例访问:
```typescript
const engine = new BimEngine('container-id');
// engine.dialog 即为 DialogManager 实例
```
## 3. 配置项 (DialogOptions)
`engine.dialog.create(options)` 方法接受以下配置:
| 属性 | 类型 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| `title` | `string` | `'Dialog'` | 弹窗标题 |
| `content` | `string \| HTMLElement` | - | 弹窗内容 |
| `width` | `number \| string` | `300` | 宽度 |
| `height` | `number \| string` | `'auto'` | 高度 |
| `position` | `DialogPosition` | `'center'` | 初始位置 (支持 'center', 'top-left' 等或坐标对象) |
| `draggable` | `boolean` | `true` | 是否可拖拽 |
| `resizable` | `boolean` | `false` | 是否可调整大小 |
| `minWidth` | `number` | `200` | 最小宽度 |
| `minHeight` | `number` | `100` | 最小高度 |
| `onClose` | `Function` | - | 关闭时的回调 |
| `backgroundColor` | `string` | `rgba(17, 17, 17, 0.95)` | 窗体背景色 |
| `headerBackgroundColor` | `string` | `#2a2a2a` | 标题栏背景色 |
| `titleColor` | `string` | `#fff` | 标题文字颜色 |
| `textColor` | `string` | `#ccc` | 内容默认文字颜色 |
| `borderColor` | `string` | `#444` | 边框颜色 |
## 4. 使用案例
### 案例 1: 创建基本弹窗
```typescript
engine.dialog.create({
title: '欢迎',
content: 'Hello World!',
width: 400
});
```
### 案例 2: 创建包含复杂 DOM 的弹窗
```typescript
const div = document.createElement('div');
div.innerHTML = '<button>Click Me</button>';
div.querySelector('button').onclick = () => alert('Clicked');
engine.dialog.create({
title: '交互组件',
content: div,
resizable: true
});
```
### 案例 3: 定制样式 (红色主题)
```typescript
engine.dialog.create({
title: '警告',
content: '这是一个红色主题的警告弹窗。',
backgroundColor: 'rgba(100, 0, 0, 0.9)', // 深红背景
headerBackgroundColor: '#ff0000', // 鲜红标题栏
titleColor: '#ffffff',
borderColor: '#ff4444'
});
```
### 案例 4: 监听关闭事件
```typescript
const dlg = engine.dialog.create({
title: '任务',
content: '处理中...',
onClose: () => {
console.log('弹窗已关闭');
}
});
// 手动关闭
// dlg.close();
```

130
docs/Toolbar.md Normal file
View File

@@ -0,0 +1,130 @@
# 工具栏组件 (Toolbar)
BimEngine SDK 提供了功能强大的工具栏组件 `ToolbarManager` (底层基于 `OptBtnGroups`),支持多级菜单、按钮分组、动态显隐和样式高度定制。
## 1. 组件作用
* 提供一个统一的底部操作栏。
* 支持按钮分组管理。
* 支持层级下拉菜单。
* 提供标准的交互反馈Hover、Active、Disabled
* 支持完全的样式定制(背景色、图标色、文字色等)。
## 2. 初始化与使用
通常通过 `BimEngine` 实例访问:
```typescript
const engine = new BimEngine('container-id');
// engine.toolbar 即为 ToolbarManager 实例
```
或者单独使用(不推荐,除非只需工具栏):
```typescript
import { ToolbarManager } from 'bim-engine-sdk';
const toolbar = new ToolbarManager(document.getElementById('toolbar-container'));
```
## 3. 配置项
`ToolbarManager` 没有直接的配置项,但它管理着底层的 `OptBtnGroups`。主要配置在于添加按钮时的 `ButtonConfig` 和样式设置。
### ButtonConfig (按钮配置)
| 属性 | 类型 | 说明 |
| :--- | :--- | :--- |
| `id` | `string` | 按钮唯一标识 |
| `type` | `'button' \| 'menu'` | 按钮类型 |
| `label` | `string` | 按钮显示文字 |
| `icon` | `string` | SVG 图标字符串 |
| `groupId` | `string` | 所属组 ID (必需) |
| `parentId` | `string` | 父按钮 ID (可选,用于子菜单) |
| `keepActive` | `boolean` | 是否保持激活状态 (Toggle 模式) |
| `disabled` | `boolean` | 是否禁用 |
| `onClick` | `Function` | 点击回调 |
| `children` | `ButtonConfig[]` | 子按钮数组 |
### ToolbarColors (颜色配置)
用于 `setColors` 方法。
| 属性 | 说明 | 默认值 |
| :--- | :--- | :--- |
| `backgroundColor` | 工具栏背景色 | `rgba(17, 17, 17, 0.88)` |
| `btnBackgroundColor` | 按钮默认背景 | `transparent` |
| `btnHoverColor` | 按钮 Hover 背景 | `#444` |
| `btnActiveColor` | 按钮激活背景 | `rgba(255, 255, 255, 0.15)` |
| `iconColor` | 图标默认颜色 | `#ccc` |
| `iconActiveColor` | 图标激活颜色 | `#fff` |
| `textColor` | 文字默认颜色 | `#ccc` |
| `textActiveColor` | 文字激活颜色 | `#fff` |
## 4. 使用案例
### 案例 1: 添加自定义按钮组和按钮
```typescript
// 1. 添加一个新组
engine.toolbar.addGroup('my-group');
// 2. 在该组添加按钮
engine.toolbar.addButton({
id: 'my-btn',
groupId: 'my-group',
type: 'button',
label: '自定义功能',
icon: '<svg>...</svg>', // 填入 SVG 内容
onClick: (btn) => {
console.log('Clicked!', btn);
}
});
```
### 案例 2: 添加带下拉菜单的按钮
```typescript
engine.toolbar.addButton({
id: 'menu-btn',
groupId: 'my-group',
type: 'menu',
label: '更多选项',
children: [
{
id: 'sub-1',
type: 'button',
label: '选项 A',
onClick: () => console.log('A')
},
{
id: 'sub-2',
type: 'button',
label: '选项 B',
onClick: () => console.log('B')
}
]
});
```
### 案例 3: 修改工具栏样式
```typescript
engine.toolbar.setColors({
backgroundColor: 'rgba(0, 122, 255, 0.9)', // 蓝色背景
iconColor: '#ffffff', // 白色图标
btnHoverColor: 'rgba(255, 255, 255, 0.2)'
});
```
### 案例 4: 控制显隐
```typescript
// 隐藏整个工具栏
engine.toolbar.setVisible(false);
// 隐藏特定按钮
engine.toolbar.setButtonVisibility('my-btn', false);
// 隐藏文字标签
engine.toolbar.setShowLabel(false);
```

View File

@@ -1,10 +1,26 @@
import './bim-engine.css'; import './bim-engine.css';
import { OptBtnGroups } from './toolbar'; import { ToolbarManager } from './modules/toolbar-manager';
import { DialogManager } from './modules/dialog-manager';
/**
* BimEngine 主类
* 负责初始化整个应用界面,协调各个子模块(如工具栏、弹窗等)。
*/
export class BimEngine { export class BimEngine {
/** 主容器元素 */
private container: HTMLElement; private container: HTMLElement;
private optBtnGroups: OptBtnGroups | null = null; /** 内部包装器元素,用于承载所有 UI 组件 */
private wrapper: HTMLElement | null = null;
/** 工具栏管理器实例 */
public toolbar: ToolbarManager | null = null;
/** 弹窗管理器实例 */
public dialog: DialogManager | null = null;
/**
* 构造函数
* @param container 容器元素或容器 ID
*/
constructor(container: HTMLElement | string) { constructor(container: HTMLElement | string) {
const el = typeof container === 'string' ? document.getElementById(container) : container; const el = typeof container === 'string' ? document.getElementById(container) : container;
if (!el) throw new Error('Container not found'); if (!el) throw new Error('Container not found');
@@ -12,20 +28,24 @@ export class BimEngine {
this.init(); this.init();
} }
/**
* 初始化方法
* 创建 DOM 结构并初始化各子模块
*/
private init() { private init() {
// 1. 清空容器可能存在的旧内容 // 1. 清空容器可能存在的旧内容
this.container.innerHTML = ''; this.container.innerHTML = '';
// 2. 创建外层容器 div // 2. 创建外层容器 div
const wrapper = document.createElement('div'); this.wrapper = document.createElement('div');
wrapper.className = 'bim-engine-wrapper'; this.wrapper.className = 'bim-engine-wrapper';
// 3. 创建标题 h1 // 3. 创建标题 h1
const title = document.createElement('h1'); const title = document.createElement('h1');
title.textContent = 'BimEngine'; title.textContent = 'BimEngine';
title.className = 'bim-engine-title'; title.className = 'bim-engine-title';
// 4. 创建段落 p // 4. 创建描述段落 p
const desc = document.createElement('p'); const desc = document.createElement('p');
desc.textContent = '这是一个使用BIM-ENGINE。'; desc.textContent = '这是一个使用BIM-ENGINE。';
desc.className = 'bim-engine-desc'; desc.className = 'bim-engine-desc';
@@ -36,34 +56,58 @@ export class BimEngine {
btnGroupContainer.className = 'bim-engine-opt-btn-container'; btnGroupContainer.className = 'bim-engine-opt-btn-container';
// 7. 组装元素 // 7. 组装元素
wrapper.appendChild(title); this.wrapper.appendChild(title);
wrapper.appendChild(desc); this.wrapper.appendChild(desc);
wrapper.appendChild(btnGroupContainer); // 将按钮组放入 wrapper 中
// 初始化管理器
this.dialog = new DialogManager(this.wrapper);
this.toolbar = new ToolbarManager(btnGroupContainer);
// 8. 挂载到主容器 // 5. 测试按钮(更新为使用管理器)
this.container.appendChild(wrapper); // 5.1 创建普通测试弹窗按钮
const dialogBtn = document.createElement('button');
dialogBtn.textContent = '打开测试弹窗';
dialogBtn.className = 'bim-engine-btn';
dialogBtn.onclick = () => {
this.dialog?.create({
title: '测试弹窗',
content: '<div style="padding: 10px;">这是一个 <b>可拖拽</b> 且 <b>可缩放</b> 的弹窗。<br><br>你可以尝试拖动标题栏,或者拖动右下角改变大小。</div>',
width: 300,
height: 400,
position: 'top-left',
draggable: true,
resizable: true
});
};
// 9. 初始化操作按钮 // 5.2 创建二次封装信息弹窗按钮
this.initOptBtnGroups(btnGroupContainer); const infoDialogBtn = document.createElement('button');
} infoDialogBtn.textContent = '打开信息弹窗 (封装版)';
infoDialogBtn.className = 'bim-engine-btn';
private initOptBtnGroups(container: HTMLElement) { infoDialogBtn.style.marginLeft = '10px';
this.optBtnGroups = new OptBtnGroups({ infoDialogBtn.onclick = () => {
container, this.dialog?.showInfoDialog();
showLabel: true };
});
// 将按钮和工具栏容器添加到包装器中
// 初始化并加载默认按钮 this.wrapper.appendChild(dialogBtn);
this.optBtnGroups.init().catch(err => { this.wrapper.appendChild(infoDialogBtn);
console.error('Failed to initialize OptBtnGroups:', err); this.wrapper.appendChild(btnGroupContainer);
});
// 8. 将包装器挂载到主容器
this.container.appendChild(this.wrapper);
} }
/**
* 销毁实例
* 清理所有资源和 DOM 元素
*/
public destroy() { public destroy() {
if (this.optBtnGroups) { if (this.toolbar) {
this.optBtnGroups.destroy(); this.toolbar.destroy();
this.optBtnGroups = null; this.toolbar = null;
} }
this.dialog = null;
this.container.innerHTML = ''; this.container.innerHTML = '';
} }
} }

View File

@@ -0,0 +1,30 @@
.bim-info-dialog-content {
padding: 16px;
font-family: sans-serif;
color: #333;
}
.bim-info-dialog-content h3 {
margin-top: 0;
margin-bottom: 12px;
border-bottom: 1px solid #eee;
padding-bottom: 8px;
color: #0078d4;
}
.bim-info-dialog-content ul {
list-style: none;
padding: 0;
margin: 0;
}
.bim-info-dialog-content li {
margin-bottom: 8px;
font-size: 14px;
display: flex;
}
.bim-info-dialog-content li strong {
width: 80px;
color: #555;
}

View File

@@ -0,0 +1,61 @@
import './index.css';
import { BimDialog } from '../index';
/**
* BimInfoDialog (二次封装示例)
* 这是一个展示项目信息的业务弹窗组件,内部封装了 BimDialog。
*/
export class BimInfoDialog {
private dialog: BimDialog;
/**
* 构造函数
* @param container 父容器
*/
constructor(container: HTMLElement) {
// 创建自定义的 DOM 内容
const contentEl = document.createElement('div');
contentEl.className = 'bim-info-dialog-content';
const infoTitle = document.createElement('h3');
infoTitle.textContent = 'Model Information';
const infoList = document.createElement('ul');
infoList.innerHTML = `
<li><strong>Name:</strong> Sample Project</li>
<li><strong>Version:</strong> 1.0.0</li>
<li><strong>Date:</strong> ${new Date().toLocaleDateString()}</li>
<li><strong>Status:</strong> <span style="color: green;">Active</span></li>
`;
const actionBtn = document.createElement('button');
actionBtn.textContent = 'Update Status';
actionBtn.style.marginTop = '10px';
actionBtn.onclick = () => {
alert('Status updated!');
};
contentEl.appendChild(infoTitle);
contentEl.appendChild(infoList);
contentEl.appendChild(actionBtn);
// 初始化 BimDialog直接传入构建好的 HTMLElement
this.dialog = new BimDialog({
container: container,
title: 'Project Info (Wrapped)',
content: contentEl,
width: 320,
height: 'auto',
position: 'center',
resizable: true,
draggable: true
});
}
/**
* 关闭弹窗
*/
public close() {
this.dialog.close();
}
}

95
src/dialog/index.css Normal file
View File

@@ -0,0 +1,95 @@
: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;
}
.bim-dialog {
position: absolute;
background-color: var(--bim-dialog-bg);
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: 1000;
color: var(--bim-dialog-title-color);
overflow: hidden;
min-width: 200px;
min-height: 100px;
}
.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);
flex-shrink: 0;
}
.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 {
content: '';
position: absolute;
bottom: 3px;
right: 3px;
width: 6px;
height: 6px;
border-right: 2px solid #666;
border-bottom: 2px solid #666;
}
.bim-dialog-resize-handle:hover::after {
border-color: #fff;
}

294
src/dialog/index.ts Normal file
View File

@@ -0,0 +1,294 @@
import './index.css';
import type { DialogOptions } from './index.type';
/**
* 通用弹窗组件类
* 支持拖拽、缩放、自定义内容和位置。
*/
export class BimDialog {
private element: HTMLElement;
private options: DialogOptions;
private container: HTMLElement;
private header: HTMLElement;
private contentArea: HTMLElement;
private _isDestroyed = false;
/**
* 构造函数
* @param options 弹窗配置选项
*/
constructor(options: DialogOptions) {
// 合并默认配置
this.options = {
title: 'Dialog',
width: 300,
height: 'auto',
position: 'center',
draggable: true,
resizable: false,
minWidth: 200,
minHeight: 100,
...options
};
this.container = options.container;
// 创建 DOM 结构
this.element = this.createDom();
this.header = this.element.querySelector('.bim-dialog-header') as HTMLElement;
this.contentArea = this.element.querySelector('.bim-dialog-content') as HTMLElement;
// 初始化
this.init();
}
/**
* 创建弹窗的 DOM 结构
*/
private createDom(): HTMLElement {
const el = document.createElement('div');
el.className = 'bim-dialog';
if (this.options.id) el.id = this.options.id;
// 应用颜色配置到 CSS 变量 (局部作用域)
const style = el.style;
if (this.options.backgroundColor) style.setProperty('--bim-dialog-bg', this.options.backgroundColor);
if (this.options.headerBackgroundColor) style.setProperty('--bim-dialog-header-bg', this.options.headerBackgroundColor);
if (this.options.titleColor) style.setProperty('--bim-dialog-title-color', this.options.titleColor);
if (this.options.textColor) style.setProperty('--bim-dialog-text-color', this.options.textColor);
if (this.options.borderColor) style.setProperty('--bim-dialog-border-color', this.options.borderColor);
// 设置初始尺寸
this.setSize(el, this.options.width, this.options.height);
// 创建标题栏 (Header)
const header = document.createElement('div');
header.className = 'bim-dialog-header';
if (this.options.draggable) header.classList.add('draggable');
const title = document.createElement('span');
title.className = 'bim-dialog-title';
title.textContent = this.options.title || '';
const closeBtn = document.createElement('span');
closeBtn.className = 'bim-dialog-close';
closeBtn.innerHTML = '&times;';
closeBtn.onclick = () => this.close();
header.appendChild(title);
header.appendChild(closeBtn);
// 创建内容区域 (Content)
const content = document.createElement('div');
content.className = 'bim-dialog-content';
if (typeof this.options.content === 'string') {
content.innerHTML = this.options.content;
} else if (this.options.content instanceof HTMLElement) {
content.appendChild(this.options.content);
}
el.appendChild(header);
el.appendChild(content);
// 如果允许缩放,创建缩放手柄
if (this.options.resizable) {
const resizeHandle = document.createElement('div');
resizeHandle.className = 'bim-dialog-resize-handle';
el.appendChild(resizeHandle);
}
return el;
}
/**
* 设置元素尺寸
*/
private setSize(el: HTMLElement, width?: number | string, height?: number | string) {
if (width !== undefined) {
el.style.width = typeof width === 'number' ? `${width}px` : width;
}
if (height !== undefined) {
el.style.height = typeof height === 'number' ? `${height}px` : height;
}
}
/**
* 初始化组件功能
*/
private init() {
this.container.appendChild(this.element);
// 必须先挂载才能计算尺寸进行定位
this.initPosition();
if (this.options.draggable) {
this.initDrag();
}
if (this.options.resizable) {
this.initResize();
}
}
/**
* 初始化弹窗位置
*/
private initPosition() {
const pos = this.options.position;
const elRect = this.element.getBoundingClientRect();
// 计算相对父容器的定位
let left = 0;
let top = 0;
const pW = this.container.clientWidth;
const pH = this.container.clientHeight;
const elW = elRect.width;
const elH = elRect.height;
if (typeof pos === 'object' && 'x' in pos) {
left = pos.x;
top = pos.y;
} else {
switch (pos) {
case 'center':
left = (pW - elW) / 2;
top = (pH - elH) / 2;
break;
case 'top-left': left = 0; top = 0; break;
case 'top-center': left = (pW - elW) / 2; top = 0; break;
case 'top-right': left = pW - elW; top = 0; break;
case 'left-center': left = 0; top = (pH - elH) / 2; break;
case 'right-center': left = pW - elW; top = (pH - elH) / 2; break;
case 'bottom-left': left = 0; top = pH - elH; break;
case 'bottom-center': left = (pW - elW) / 2; top = pH - elH; break;
case 'bottom-right': left = pW - elW; top = pH - elH; break;
default:
left = (pW - elW) / 2;
top = (pH - elH) / 2;
}
}
// 简单的边界检查,防止初始位置溢出
left = Math.max(0, Math.min(left, pW - elW));
top = Math.max(0, Math.min(top, pH - elH));
this.element.style.left = `${left}px`;
this.element.style.top = `${top}px`;
}
/**
* 初始化拖拽功能
*/
private initDrag() {
let startX = 0;
let startY = 0;
let startLeft = 0;
let startTop = 0;
const onMouseDown = (e: MouseEvent) => {
e.preventDefault();
startX = e.clientX;
startY = e.clientY;
startLeft = this.element.offsetLeft;
startTop = this.element.offsetTop;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
const onMouseMove = (e: MouseEvent) => {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
let newLeft = startLeft + dx;
let newTop = startTop + dy;
// 边界限制,防止拖出容器
const maxLeft = this.container.clientWidth - this.element.offsetWidth;
const maxTop = this.container.clientHeight - this.element.offsetHeight;
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
newTop = Math.max(0, Math.min(newTop, maxTop));
this.element.style.left = `${newLeft}px`;
this.element.style.top = `${newTop}px`;
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
this.header.addEventListener('mousedown', onMouseDown);
}
/**
* 初始化缩放功能
*/
private initResize() {
const handle = this.element.querySelector('.bim-dialog-resize-handle') as HTMLElement;
if (!handle) return;
let startX = 0;
let startY = 0;
let startW = 0;
let startH = 0;
const onMouseDown = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
startX = e.clientX;
startY = e.clientY;
startW = this.element.offsetWidth;
startH = this.element.offsetHeight;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
const onMouseMove = (e: MouseEvent) => {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newW = Math.max(this.options.minWidth || 100, startW + dx);
const newH = Math.max(this.options.minHeight || 50, startH + dy);
this.element.style.width = `${newW}px`;
this.element.style.height = `${newH}px`;
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
handle.addEventListener('mousedown', onMouseDown);
}
/**
* 动态设置内容
* @param content 内容元素或 HTML 字符串
*/
public setContent(content: HTMLElement | string) {
this.contentArea.innerHTML = '';
if (typeof content === 'string') {
this.contentArea.innerHTML = content;
} else {
this.contentArea.appendChild(content);
}
}
/**
* 关闭弹窗并销毁
*/
public close() {
if (this._isDestroyed) return;
this.element.remove();
this._isDestroyed = true;
if (this.options.onClose) {
this.options.onClose();
}
}
}

57
src/dialog/index.type.ts Normal file
View File

@@ -0,0 +1,57 @@
/**
* 弹窗位置类型定义
* 可以是预设的字符串位置(如 'center', 'top-left' 等),
* 也可以是具体的坐标对象 { x, y }
*/
export type DialogPosition =
| 'center'
| 'top-left' | 'top-center' | 'top-right'
| 'left-center' | 'right-center'
| 'bottom-left' | 'bottom-center' | 'bottom-right'
| { x: number; y: number };
/**
* 弹窗颜色配置
*/
export interface DialogColors {
/** 窗体背景颜色,默认 rgba(17, 17, 17, 0.95) */
backgroundColor?: string;
/** 标题栏背景颜色,默认 #2a2a2a */
headerBackgroundColor?: string;
/** 标题文字颜色,默认 #fff */
titleColor?: string;
/** 内容文字颜色,默认 #ccc */
textColor?: string;
/** 边框颜色,默认 #444 */
borderColor?: string;
}
/**
* 弹窗配置选项接口
*/
export interface DialogOptions extends DialogColors {
/** 弹窗挂载的父容器 */
container: HTMLElement;
/** 弹窗标题 */
title?: string;
/** 弹窗内容,支持 HTML 字符串或 HTMLElement */
content?: HTMLElement | string;
/** 弹窗宽度,数字(像素)或字符串(如 '50%' */
width?: number | string;
/** 弹窗高度 */
height?: number | string;
/** 弹窗位置 */
position?: DialogPosition;
/** 是否可拖拽 */
draggable?: boolean;
/** 是否可调整大小 */
resizable?: boolean;
/** 最小宽度限制 */
minWidth?: number;
/** 最小高度限制 */
minHeight?: number;
/** 关闭时的回调函数 */
onClose?: () => void;
/** 弹窗唯一标识 ID (可选) */
id?: string;
}

View File

@@ -1,4 +1,10 @@
import { BimEngine } from './bim-engine'; import { BimEngine } from './bim-engine';
// 导出 OptBtnGroups 组件,用于工具栏操作
export { OptBtnGroups } from './toolbar'; export { OptBtnGroups } from './toolbar';
// 导出相关类型定义
export type { OptButton, ButtonGroup, OptBtnGroupsOptions, ClickPayload } from './toolbar/index.type'; export type { OptButton, ButtonGroup, OptBtnGroupsOptions, ClickPayload } from './toolbar/index.type';
export { BimEngine };
// 导出主引擎类
export { BimEngine };

View File

@@ -0,0 +1,40 @@
import { BimDialog } from '../dialog';
import { BimInfoDialog } from '../dialog/bimInfoDialog';
import type { DialogOptions } from '../dialog/index.type';
/**
* 弹窗管理器
* 负责创建和管理应用中的各类弹窗。
*/
export class DialogManager {
/** 弹窗挂载的父容器 */
private container: HTMLElement;
/**
* 构造函数
* @param container 弹窗挂载的目标容器
*/
constructor(container: HTMLElement) {
this.container = container;
}
/**
* 创建一个通用弹窗
* @param options 弹窗配置选项(不需要传 container自动使用管理器绑定的容器
* @returns BimDialog 实例
*/
public create(options: Omit<DialogOptions, 'container'>): BimDialog {
return new BimDialog({
container: this.container,
...options
});
}
/**
* 显示二次封装的模型信息弹窗
* 演示如何调用特定的业务弹窗组件
*/
public showInfoDialog() {
new BimInfoDialog(this.container);
}
}

View File

@@ -0,0 +1,132 @@
import type { ToolbarColors } from '../toolbar/index.type';
import { OptBtnGroups } from '../toolbar';
import type { ButtonConfig } from '../toolbar/index.type';
/**
* 工具栏管理器
* 负责管理底部操作栏的按钮组、按钮及其可见性等状态。
*/
export class ToolbarManager {
/** 内部工具栏组件实例 */
private optBtnGroups: OptBtnGroups | null = null;
/** 工具栏挂载的容器 */
private container: HTMLElement;
/**
* 构造函数
* @param container 工具栏挂载的容器元素
*/
constructor(container: HTMLElement) {
this.container = container;
this.init();
}
/**
* 初始化工具栏
*/
private init() {
this.optBtnGroups = new OptBtnGroups({
container: this.container,
showLabel: true
});
// 初始化并加载默认按钮配置
this.optBtnGroups.init().catch(err => {
console.error('Failed to initialize OptBtnGroups:', err);
});
}
/**
* 添加一个工具栏按钮组
* @param groupId 新组的 ID
* @param beforeGroupId (可选) 插入到哪个组之前,不传则追加到最后
*/
public addGroup(groupId: string, beforeGroupId?: string) {
if (this.optBtnGroups) {
this.optBtnGroups.addGroup(groupId, beforeGroupId);
this.optBtnGroups.render(); // 重新渲染以更新 UI
} else {
console.warn('Toolbar not initialized yet.');
}
}
/**
* 添加一个工具栏按钮
* @param config 按钮配置对象
*/
public addButton(config: ButtonConfig) {
if (this.optBtnGroups) {
this.optBtnGroups.addButton(config);
this.optBtnGroups.render(); // 重新渲染以更新 UI
} else {
console.warn('Toolbar not initialized yet.');
}
}
/**
* 设置按钮的可见性
* @param buttonId 按钮 ID
* @param visible 是否可见
*/
public setButtonVisibility(buttonId: string, visible: boolean) {
if (this.optBtnGroups) {
this.optBtnGroups.updateButtonVisibility(buttonId, visible);
} else {
console.warn('Toolbar not initialized yet.');
}
}
/**
* 设置是否显示按钮下方的文字标签
* @param show 是否显示
*/
public setShowLabel(show: boolean) {
if (this.optBtnGroups) {
this.optBtnGroups.setShowLabel(show);
} else {
console.warn('Toolbar not initialized yet.');
}
}
/**
* 设置整个工具栏的可见性
* @param visible 是否可见
*/
public setVisible(visible: boolean) {
this.container.style.display = visible ? 'block' : 'none';
}
/**
* 设置工具栏背景颜色
* @param color CSS 颜色值
*/
public setBackgroundColor(color: string) {
if (this.optBtnGroups) {
this.optBtnGroups.setBackgroundColor(color);
} else {
console.warn('Toolbar not initialized yet.');
}
}
/**
* 设置工具栏详细颜色配置
* @param colors 颜色配置对象
*/
public setColors(colors: ToolbarColors) {
if (this.optBtnGroups) {
this.optBtnGroups.setColors(colors);
} else {
console.warn('Toolbar not initialized yet.');
}
}
/**
* 销毁工具栏管理器
*/
public destroy() {
if (this.optBtnGroups) {
this.optBtnGroups.destroy();
this.optBtnGroups = null;
}
}
}

View File

@@ -1,19 +1,26 @@
:root {
--bim-toolbar-bg: rgba(17, 17, 17, 0.88);
--bim-btn-bg: transparent;
--bim-btn-hover-bg: #444;
--bim-btn-active-bg: rgba(255, 255, 255, 0.15);
--bim-icon-color: #ccc;
--bim-icon-active-color: #fff;
--bim-btn-text-color: #ccc;
--bim-btn-text-active-color: #fff;
}
/* 容器样式 */ /* 容器样式 */
.toolbar-container { .toolbar-container {
display: flex; display: flex;
align-items: center; align-items: center;
/* 容器本身不需要背景 */
max-width: 100%; max-width: 100%;
overflow-x: auto; overflow-x: auto;
scrollbar-width: none; scrollbar-width: none;
/* Firefox 隐藏滚动条 */
-ms-overflow-style: none; -ms-overflow-style: none;
/* IE 10+ 隐藏滚动条 */
} }
.toolbar-container::-webkit-scrollbar { .toolbar-container::-webkit-scrollbar {
display: none; display: none;
/* Chrome/Safari 隐藏滚动条 */
} }
/* 按钮组样式 */ /* 按钮组样式 */
@@ -23,15 +30,13 @@
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
background-color: rgba(17, 17, 17, 0.88); background-color: var(--bim-toolbar-bg);
/* 每个组独立的背景 */
border-radius: 4px; border-radius: 4px;
padding: 4px 8px; padding: 4px 8px;
} }
.has-divider { .has-divider {
margin-right: 16px; margin-right: 16px;
/* 增加右边距来分隔组 */
} }
/* 按钮包装器 */ /* 按钮包装器 */
@@ -49,23 +54,21 @@
min-height: 50px; min-height: 50px;
padding: 4px; padding: 4px;
cursor: pointer; cursor: pointer;
color: #ccc; background-color: var(--bim-btn-bg);
color: var(--bim-icon-color);
transition: all 0.2s; transition: all 0.2s;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
/* 默认透明底边 */
} }
.opt-btn:hover { .opt-btn:hover {
background-color: #444; background-color: var(--bim-btn-hover-bg);
color: #fff; color: var(--bim-icon-active-color);
} }
.opt-btn.active { .opt-btn.active {
background-color: rgba(255, 255, 255, 0.15); background-color: var(--bim-btn-active-bg);
/* 白色半透明背景 */ color: var(--bim-icon-active-color);
color: #fff;
border-bottom: 2px solid #fff; border-bottom: 2px solid #fff;
/* 纯白色底部横条 */
} }
.opt-btn.disabled { .opt-btn.disabled {
@@ -81,7 +84,6 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
/* 防止图标被压缩 */
} }
.opt-btn-icon svg { .opt-btn-icon svg {
@@ -93,7 +95,13 @@
.opt-btn-label { .opt-btn-label {
font-size: 10px; font-size: 10px;
margin-top: 2px; margin-top: 2px;
color: var(--bim-btn-text-color);
} }
.opt-btn:hover .opt-btn-label,
.opt-btn.active .opt-btn-label {
color: var(--bim-btn-text-active-color);
}
/* 下拉箭头样式 */ /* 下拉箭头样式 */
.opt-btn-arrow { .opt-btn-arrow {
@@ -105,12 +113,10 @@
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
/* 箭头旋转 (菜单展开时) */
.opt-btn-arrow.rotated { .opt-btn-arrow.rotated {
transform: rotate(180deg); transform: rotate(180deg);
} }
/* 无标签模式调整 */
.opt-btn.no-label .opt-btn-arrow { .opt-btn.no-label .opt-btn-arrow {
top: 2px; top: 2px;
right: 2px; right: 2px;
@@ -119,16 +125,13 @@
/* 下拉菜单样式 */ /* 下拉菜单样式 */
.opt-btn-dropdown { .opt-btn-dropdown {
position: fixed; position: fixed;
/* 固定定位 */
transform: translate(-50%, -100%); transform: translate(-50%, -100%);
/* 水平居中并向上移动 */ background-color: var(--bim-toolbar-bg);
background-color: rgba(17, 17, 17, 0.88);
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
min-width: 50px; min-width: 50px;
z-index: 9999; z-index: 9999;
/* 高层级 */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -137,45 +140,40 @@
.opt-btn-dropdown-item { .opt-btn-dropdown-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* 垂直布局 */
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: #b3b4b4; color: var(--bim-icon-color);
cursor: pointer; cursor: pointer;
transition: background 0.2s; transition: background 0.2s;
white-space: nowrap; white-space: nowrap;
min-width: 50px; min-width: 50px;
min-height: 50px; min-height: 50px;
padding: 4px; padding: 4px;
} background-color: var(--bim-btn-bg);
.opt-btn-dropdown-item:last-child {
border-bottom: none;
/* 移除最后一项的分隔线 */
} }
.opt-btn-dropdown-item:hover { .opt-btn-dropdown-item:hover {
background-color: #444; background-color: var(--bim-btn-hover-bg);
color: #fff; color: var(--bim-icon-active-color);
} }
.opt-btn-dropdown-item .opt-btn-icon.small { .opt-btn-dropdown-item .opt-btn-icon.small {
width: 30px; width: 30px;
/* 与主按钮图标大小一致 */
height: 30px; height: 30px;
margin-right: 0; margin-right: 0;
/* 移除右边距 */
margin-bottom: 4px; margin-bottom: 4px;
/* 添加下边距 */
} }
.opt-btn-dropdown-item span { .opt-btn-dropdown-item span {
font-size: 10px; font-size: 10px;
/* 较小的文字 */ color: var(--bim-btn-text-color);
}
.opt-btn-dropdown-item:hover span {
color: var(--bim-btn-text-active-color);
} }
/* 无标签模式调整 */
.opt-btn.no-label .opt-btn-icon { .opt-btn.no-label .opt-btn-icon {
width: 32px; width: 32px;
height: 32px; height: 32px;
} }

View File

@@ -3,21 +3,37 @@ import type {
OptButton, OptButton,
ButtonGroup, ButtonGroup,
OptBtnGroupsOptions, OptBtnGroupsOptions,
ButtonConfig ButtonConfig,
ToolbarColors
} from './index.type'; } from './index.type';
/**
* 底部操作按钮组组件
* 负责渲染和管理底部工具栏的按钮、下拉菜单及相关交互。
*/
export class OptBtnGroups { export class OptBtnGroups {
/** 挂载容器 */
private container: HTMLElement; private container: HTMLElement;
/** 组件配置选项 */
private options: OptBtnGroupsOptions; private options: OptBtnGroupsOptions;
// 改用 Array 存储 Group方便控制顺序 /** 按钮组列表,按顺序存储 */
private groups: ButtonGroup[] = []; private groups: ButtonGroup[] = [];
/** 当前处于激活状态的按钮 ID 集合 */
private activeBtnIds: Set<string> = new Set(); private activeBtnIds: Set<string> = new Set();
/** 按钮 DOM 元素的引用映射,方便快速查找 */
private btnRefs: Map<string, HTMLElement> = new Map(); private btnRefs: Map<string, HTMLElement> = new Map();
/** 当<><E5BD93>显示的下拉菜单元素 */
private dropdownElement: HTMLElement | null = null; private dropdownElement: HTMLElement | null = null;
/** 鼠标悬停计时器,用于处理菜单显示的防抖 */
private hoverTimeout: number | null = null; private hoverTimeout: number | null = null;
/** 默认图标 SVG */
private readonly DEFAULT_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>'; private readonly DEFAULT_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>';
/**
* 构造函数
* @param options 配置选项
*/
constructor(options: OptBtnGroupsOptions) { constructor(options: OptBtnGroupsOptions) {
const el = typeof options.container === 'string' const el = typeof options.container === 'string'
? document.getElementById(options.container) ? document.getElementById(options.container)
@@ -33,16 +49,47 @@ export class OptBtnGroups {
}; };
this.initContainer(); this.initContainer();
this.applyStyles(); // 应用初始样式配置
} }
/**
* 初始化容器
*/
private initContainer(): void { private initContainer(): void {
this.container.innerHTML = ''; this.container.innerHTML = '';
this.container.classList.add('toolbar-root'); // 添加一个根类方便定位样式
}
/**
* 应用样式配置到 CSS 变量
*/
private applyStyles(): void {
const style = this.container.style;
if (this.options.backgroundColor) style.setProperty('--bim-toolbar-bg', this.options.backgroundColor);
if (this.options.btnBackgroundColor) style.setProperty('--bim-btn-bg', this.options.btnBackgroundColor);
if (this.options.btnHoverColor) style.setProperty('--bim-btn-hover-bg', this.options.btnHoverColor);
if (this.options.btnActiveColor) style.setProperty('--bim-btn-active-bg', this.options.btnActiveColor);
if (this.options.iconColor) style.setProperty('--bim-icon-color', this.options.iconColor);
if (this.options.iconActiveColor) style.setProperty('--bim-icon-active-color', this.options.iconActiveColor);
if (this.options.textColor) style.setProperty('--bim-btn-text-color', this.options.textColor);
if (this.options.textActiveColor) style.setProperty('--bim-btn-text-active-color', this.options.textActiveColor);
}
/**
* 更新颜色配置
* @param colors 颜色配置对象
*/
public setColors(colors: ToolbarColors): void {
// 更新 options
this.options = { ...this.options, ...colors };
// 应用到 CSS 变量
this.applyStyles();
} }
/** /**
* 添加按钮组 * 添加按钮组
* @param groupId 组ID * @param groupId 组ID
* @param beforeGroupId 在哪个组之前插入(可选,不传则插入到最后 * @param beforeGroupId 在哪个组之前插入(可选<EFBFBD><EFBFBD><EFBFBD>,不传则插入到最后
*/ */
public addGroup(groupId: string, beforeGroupId?: string): void { public addGroup(groupId: string, beforeGroupId?: string): void {
if (this.groups.some(g => g.id === groupId)) { if (this.groups.some(g => g.id === groupId)) {
@@ -66,7 +113,7 @@ export class OptBtnGroups {
} }
/** /**
* 添加按钮 * 添加按钮到指定组
* @param config 按钮配置(必须包含 groupId可选包含 parentId * @param config 按钮配置(必须包含 groupId可选包含 parentId
*/ */
public addButton(config: ButtonConfig): void { public addButton(config: ButtonConfig): void {
@@ -87,7 +134,7 @@ export class OptBtnGroups {
}; };
if (parentId) { if (parentId) {
// Add as sub-button // 添加为子按钮(菜单项)
const parentBtn = this.findButton(group.buttons, parentId); const parentBtn = this.findButton(group.buttons, parentId);
if (!parentBtn) { if (!parentBtn) {
throw new Error(`Parent button ${parentId} not found in group ${groupId}`); throw new Error(`Parent button ${parentId} not found in group ${groupId}`);
@@ -97,11 +144,14 @@ export class OptBtnGroups {
} }
parentBtn.children.push(button); parentBtn.children.push(button);
} else { } else {
// Add as main button // 添加为主按钮
group.buttons.push(button); group.buttons.push(button);
} }
} }
/**
* 递归查找按钮
*/
private findButton(buttons: OptButton[], id: string): OptButton | undefined { private findButton(buttons: OptButton[], id: string): OptButton | undefined {
for (const btn of buttons) { for (const btn of buttons) {
if (btn.id === id) return btn; if (btn.id === id) return btn;
@@ -113,7 +163,11 @@ export class OptBtnGroups {
return undefined; return undefined;
} }
/**
* 初始化组件,加载默认按钮配置
*/
public async init(): Promise<void> { public async init(): Promise<void> {
// 动态导入默认按钮配置
const { homeButton } = await import('./buttons/home'); const { homeButton } = await import('./buttons/home');
const { locationButton } = await import('./buttons/location'); const { locationButton } = await import('./buttons/location');
const { walkMenuButton } = await import('./buttons/walk/walk-menu'); const { walkMenuButton } = await import('./buttons/walk/walk-menu');
@@ -122,7 +176,7 @@ export class OptBtnGroups {
const { settingButton } = await import('./buttons/setting'); const { settingButton } = await import('./buttons/setting');
const { infoButton } = await import('./buttons/info'); const { infoButton } = await import('./buttons/info');
// 添加组1 // 配置默认组和按钮
this.addGroup('group-1'); this.addGroup('group-1');
this.addButton(homeButton); this.addButton(homeButton);
this.addButton(walkMenuButton); this.addButton(walkMenuButton);
@@ -135,6 +189,9 @@ export class OptBtnGroups {
this.render(); this.render();
} }
/**
* 渲染整个工具栏
*/
public render(): void { public render(): void {
this.container.innerHTML = ''; this.container.innerHTML = '';
this.btnRefs.clear(); this.btnRefs.clear();
@@ -142,7 +199,7 @@ export class OptBtnGroups {
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.className = 'toolbar-container'; wrapper.className = 'toolbar-container';
// 直接遍历数组,顺序由 addGroup 控制 // 渲染所有组
this.groups.forEach((group, index) => { this.groups.forEach((group, index) => {
const groupElement = this.renderGroup(group, index, this.groups.length); const groupElement = this.renderGroup(group, index, this.groups.length);
wrapper.appendChild(groupElement); wrapper.appendChild(groupElement);
@@ -151,6 +208,9 @@ export class OptBtnGroups {
this.container.appendChild(wrapper); this.container.appendChild(wrapper);
} }
/**
* 渲染单个按钮组
*/
private renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement { private renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement {
const groupEl = document.createElement('div'); const groupEl = document.createElement('div');
groupEl.className = 'opt-btn-group'; groupEl.className = 'opt-btn-group';
@@ -169,6 +229,9 @@ export class OptBtnGroups {
return groupEl; return groupEl;
} }
/**
* 渲染单个按钮
*/
private renderButton(button: OptButton): HTMLElement { private renderButton(button: OptButton): HTMLElement {
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.className = 'opt-btn-wrapper'; wrapper.className = 'opt-btn-wrapper';
@@ -176,6 +239,7 @@ export class OptBtnGroups {
const btnEl = document.createElement('div'); const btnEl = document.createElement('div');
btnEl.className = 'opt-btn'; btnEl.className = 'opt-btn';
// 设置激活状态
if (this.activeBtnIds.has(button.id)) { if (this.activeBtnIds.has(button.id)) {
btnEl.classList.add('active'); btnEl.classList.add('active');
} }
@@ -191,11 +255,13 @@ export class OptBtnGroups {
} }
} }
// 渲染图标
const icon = document.createElement('div'); const icon = document.createElement('div');
icon.className = 'opt-btn-icon'; icon.className = 'opt-btn-icon';
icon.innerHTML = this.getIcon(button.icon); icon.innerHTML = this.getIcon(button.icon);
btnEl.appendChild(icon); btnEl.appendChild(icon);
// 渲染标签
if (this.options.showLabel && button.label) { if (this.options.showLabel && button.label) {
const label = document.createElement('span'); const label = document.createElement('span');
label.className = 'opt-btn-label'; label.className = 'opt-btn-label';
@@ -203,6 +269,7 @@ export class OptBtnGroups {
btnEl.appendChild(label); btnEl.appendChild(label);
} }
// 如果有子菜单,渲染箭头
if (button.children && button.children.length > 0) { if (button.children && button.children.length > 0) {
const arrow = document.createElement('span'); const arrow = document.createElement('span');
arrow.className = 'opt-btn-arrow'; arrow.className = 'opt-btn-arrow';
@@ -210,6 +277,7 @@ export class OptBtnGroups {
btnEl.appendChild(arrow); btnEl.appendChild(arrow);
} }
// 绑定事件
btnEl.addEventListener('click', () => this.handleClick(button)); btnEl.addEventListener('click', () => this.handleClick(button));
btnEl.addEventListener('mouseenter', () => this.handleMouseEnter(button, btnEl)); btnEl.addEventListener('mouseenter', () => this.handleMouseEnter(button, btnEl));
btnEl.addEventListener('mouseleave', () => this.handleMouseLeave()); btnEl.addEventListener('mouseleave', () => this.handleMouseLeave());
@@ -220,9 +288,13 @@ export class OptBtnGroups {
return wrapper; return wrapper;
} }
/**
* 处理按钮点击事件
*/
private handleClick(button: OptButton): void { private handleClick(button: OptButton): void {
if (button.disabled) return; if (button.disabled) return;
// 如果没有子菜单,直接触发
if (!button.children || button.children.length === 0) { if (!button.children || button.children.length === 0) {
if (button.keepActive) { if (button.keepActive) {
const wasActive = this.activeBtnIds.has(button.id); const wasActive = this.activeBtnIds.has(button.id);
@@ -242,6 +314,9 @@ export class OptBtnGroups {
} }
} }
/**
* 处理子菜单项点击事件
*/
private handleSubClick(button: OptButton): void { private handleSubClick(button: OptButton): void {
if (button.keepActive) { if (button.keepActive) {
const wasActive = this.activeBtnIds.has(button.id); const wasActive = this.activeBtnIds.has(button.id);
@@ -260,6 +335,9 @@ export class OptBtnGroups {
} }
} }
/**
* 处理鼠标移入事件(显示菜单)
*/
private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void { private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void {
if (this.hoverTimeout) { if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout); clearTimeout(this.hoverTimeout);
@@ -278,12 +356,18 @@ export class OptBtnGroups {
} }
} }
/**
* 处理鼠标移出事件(隐藏菜单)
*/
private handleMouseLeave(): void { private handleMouseLeave(): void {
this.hoverTimeout = window.setTimeout(() => { this.hoverTimeout = window.setTimeout(() => {
this.closeDropdown(); this.closeDropdown();
}, 200); }, 200);
} }
/**
* 显示下拉菜单
*/
private showDropdown(button: OptButton, btnEl: HTMLElement): void { private showDropdown(button: OptButton, btnEl: HTMLElement): void {
this.closeDropdown(); this.closeDropdown();
@@ -291,6 +375,22 @@ export class OptBtnGroups {
const dropdown = document.createElement('div'); const dropdown = document.createElement('div');
dropdown.className = 'opt-btn-dropdown'; dropdown.className = 'opt-btn-dropdown';
// 下拉菜单也应用当前的 CSS 变量样式,因为它们通常挂载在 body 上,所以需要单独设置或者确保能继承
// 简单起见,我们可以直接将容器上的 CSS 变量复制过来,或者设置内联样式
// 更好的是:如果我们在 this.container 上设置 CSS 变量,
// 而 dropdown 挂载在 body 上,它无法继承。
// 所以我们需要将 CSS 变量也应用到 dropdown 上。
const style = dropdown.style;
if (this.options.backgroundColor) style.setProperty('--bim-toolbar-bg', this.options.backgroundColor);
if (this.options.btnBackgroundColor) style.setProperty('--bim-btn-bg', this.options.btnBackgroundColor);
if (this.options.btnHoverColor) style.setProperty('--bim-btn-hover-bg', this.options.btnHoverColor);
if (this.options.btnActiveColor) style.setProperty('--bim-btn-active-bg', this.options.btnActiveColor);
if (this.options.iconColor) style.setProperty('--bim-icon-color', this.options.iconColor);
if (this.options.iconActiveColor) style.setProperty('--bim-icon-active-color', this.options.iconActiveColor);
if (this.options.textColor) style.setProperty('--bim-btn-text-color', this.options.textColor);
if (this.options.textActiveColor) style.setProperty('--bim-btn-text-active-color', this.options.textActiveColor);
const rect = btnEl.getBoundingClientRect(); const rect = btnEl.getBoundingClientRect();
const centerX = rect.left + rect.width / 2; const centerX = rect.left + rect.width / 2;
@@ -305,6 +405,7 @@ export class OptBtnGroups {
} }
}); });
// 保持菜单显<E58D95><E698BE><EFBFBD>
dropdown.addEventListener('mouseenter', () => { dropdown.addEventListener('mouseenter', () => {
if (this.hoverTimeout) { if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout); clearTimeout(this.hoverTimeout);
@@ -318,6 +419,9 @@ export class OptBtnGroups {
this.dropdownElement = dropdown; this.dropdownElement = dropdown;
} }
/**
* 渲染下拉菜单项
*/
private renderDropdownItem(button: OptButton): HTMLElement { private renderDropdownItem(button: OptButton): HTMLElement {
const item = document.createElement('div'); const item = document.createElement('div');
item.className = 'opt-btn-dropdown-item'; item.className = 'opt-btn-dropdown-item';
@@ -341,6 +445,9 @@ export class OptBtnGroups {
return item; return item;
} }
/**
* 关闭所有下拉菜单
*/
private closeDropdown(): void { private closeDropdown(): void {
if (this.dropdownElement) { if (this.dropdownElement) {
this.dropdownElement.remove(); this.dropdownElement.remove();
@@ -355,6 +462,9 @@ export class OptBtnGroups {
}); });
} }
/**
* 更新按钮的激活状态样式
*/
private updateButtonState(buttonId: string): void { private updateButtonState(buttonId: string): void {
const btnEl = this.btnRefs.get(buttonId); const btnEl = this.btnRefs.get(buttonId);
if (btnEl) { if (btnEl) {
@@ -366,14 +476,53 @@ export class OptBtnGroups {
} }
} }
/**
* 获取图标 SVG 字符串
*/
private getIcon(icon?: string): string { private getIcon(icon?: string): string {
return icon || this.DEFAULT_ICON; return icon || this.DEFAULT_ICON;
} }
/**
* 更新按钮可见性
* @param buttonId 按钮ID
* @param visible 是否可见
*/
public updateButtonVisibility(buttonId: string, visible: boolean): void {
if (!this.options.visibility) {
this.options.visibility = {};
}
this.options.visibility[buttonId] = visible;
this.render();
}
/**
* 设置是否显示标签
* @param show 是否显示
*/
public setShowLabel(show: boolean): void {
this.options.showLabel = show;
this.render();
}
/**
* 设置背景颜色 (兼容旧接口)
* @param color CSS 颜色值
*/
public setBackgroundColor(color: string): void {
this.setColors({ backgroundColor: color });
}
/**
* 检查按钮是否可见
*/
private isVisible(id: string): boolean { private isVisible(id: string): boolean {
return this.options.visibility?.[id] !== false; return this.options.visibility?.[id] !== false;
} }
/**
* 销毁组件,清理资源
*/
public destroy(): void { public destroy(): void {
this.closeDropdown(); this.closeDropdown();
if (this.hoverTimeout) { if (this.hoverTimeout) {
@@ -382,6 +531,6 @@ export class OptBtnGroups {
this.container.innerHTML = ''; this.container.innerHTML = '';
this.btnRefs.clear(); this.btnRefs.clear();
this.activeBtnIds.clear(); this.activeBtnIds.clear();
this.groups = []; // 清空数组 this.groups = [];
} }
} }

View File

@@ -4,40 +4,78 @@ export type ButtonType = 'button' | 'menu';
* 按钮配置接口(用于外部定义按钮) * 按钮配置接口(用于外部定义按钮)
*/ */
export interface ButtonConfig { export interface ButtonConfig {
id: string; // 唯一标识 /** 唯一标识 */
type: ButtonType; // 按钮类型 id: string;
label: string; // 按钮文字 /** 按钮类型:普通按钮或菜单按钮 */
icon?: string; // SVG 图标(内联 SVG 字符串) type: ButtonType;
keepActive?: boolean; // 是否保持激活状态(默认 false /** 按钮显示文字 */
disabled?: boolean; // 是否禁用 label: string;
onClick?: (button: OptButton) => void; // 点击回调 /** SVG 图标(内联 SVG 字符串) */
children?: ButtonConfig[]; // 子按钮配置(可选,用于菜单按钮) icon?: string;
/** 是否保持激活状态(默认 false */
keepActive?: boolean;
/** 是否禁用 */
disabled?: boolean;
/** 点击回调函数 */
onClick?: (button: OptButton) => void;
/** 子按钮配置(可选,用于菜单按钮) */
children?: ButtonConfig[];
groupId?: string; // 所属组ID /** 所属组ID */
parentId?: string; // 父按钮ID如果是子按钮 groupId?: string;
/** 父按钮ID如果是子按钮则必填 */
parentId?: string;
} }
/** /**
* 操作按钮接口(内部使用,继承配置) * 操作按钮接口(内部使用,继承配置)
*/ */
export interface OptButton extends ButtonConfig { export interface OptButton extends ButtonConfig {
children?: OptButton[]; // 内部使用的子按钮列表 /** 内部使用的子按钮列表 */
children?: OptButton[];
} }
/** /**
* 按钮组接口 * 按钮组接口
*/ */
export interface ButtonGroup { export interface ButtonGroup {
/** 组 ID */
id: string; id: string;
/** 组内按钮列表 */
buttons: OptButton[]; buttons: OptButton[];
} }
/**
* 工具栏颜色配置接口
*/
export interface ToolbarColors {
/** 工具栏背景颜色 */
backgroundColor?: string;
/** 按钮默认背景颜色 */
btnBackgroundColor?: string;
/** 按钮 Hover 背景颜色 */
btnHoverColor?: string;
/** 按钮激活状态背景颜色 */
btnActiveColor?: string;
/** 图标默认颜色 */
iconColor?: string;
/** 图标激活/Hover 颜色 */
iconActiveColor?: string;
/** 文字默认颜色 */
textColor?: string;
/** 文字激活/Hover 颜色 */
textActiveColor?: string;
}
/** /**
* OptBtnGroups 配置选项 * OptBtnGroups 配置选项
*/ */
export interface OptBtnGroupsOptions { export interface OptBtnGroupsOptions extends ToolbarColors {
/** 容器元素或 ID */
container: HTMLElement | string; container: HTMLElement | string;
/** 是否显示标签 */
showLabel?: boolean; showLabel?: boolean;
/** 按钮可见性配置 Map */
visibility?: Record<string, boolean>; visibility?: Record<string, boolean>;
} }
@@ -45,7 +83,10 @@ export interface OptBtnGroupsOptions {
* 点击事件载荷 * 点击事件载荷
*/ */
export interface ClickPayload { export interface ClickPayload {
/** 被点击的按钮对象 */
button: OptButton; button: OptButton;
/** 触发的动作类型 */
action: 'activate' | 'deactivate' | 'trigger'; action: 'activate' | 'deactivate' | 'trigger';
/** 当前激活状态 */
isActive?: boolean; isActive?: boolean;
} }