Refactor: modularize toolbar buttons, integrate config, and fix layout positioning

This commit is contained in:
yuding
2025-12-03 15:46:18 +08:00
parent 5aeb381add
commit 14ac91aa6e
22 changed files with 1290 additions and 51 deletions

View File

@@ -1,19 +0,0 @@
export class BimEngine {
private container: HTMLElement;
constructor(container: HTMLElement | string) {
const el = typeof container === 'string' ? document.getElementById(container) : container;
if (!el) throw new Error('Container not found');
this.container = el;
this.init();
}
private init() {
this.container.innerHTML = `
<div style="font-family: sans-serif; color: #333;">
<h1>BimEngine</h1>
<p>这是一个纯 TypeScript 组件入口。</p>
</div>
`;
}
}

26
src/bim-engine.css Normal file
View File

@@ -0,0 +1,26 @@
.bim-engine-wrapper {
position: relative;
/* 添加相对定位作为参照物 */
width: 100%;
height: 100%;
font-family: sans-serif;
color: #333;
padding: 20px;
background-color: #e16969;
border-radius: 8px;
border: 1px solid #e0e0e0;
box-sizing: border-box;
/* 确保 padding 不会撑大容器 */
}
/* ... (中间代码不变) ... */
/* 操作按钮组容器 */
.bim-engine-opt-btn-container {
position: absolute;
/* 改为绝对定位 */
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
}

69
src/bim-engine.ts Normal file
View File

@@ -0,0 +1,69 @@
import './bim-engine.css';
import { OptBtnGroups } from './toolbar';
export class BimEngine {
private container: HTMLElement;
private optBtnGroups: OptBtnGroups | null = null;
constructor(container: HTMLElement | string) {
const el = typeof container === 'string' ? document.getElementById(container) : container;
if (!el) throw new Error('Container not found');
this.container = el;
this.init();
}
private init() {
// 1. 清空容器可能存在的旧内容
this.container.innerHTML = '';
// 2. 创建外层容器 div
const wrapper = document.createElement('div');
wrapper.className = 'bim-engine-wrapper';
// 3. 创建标题 h1
const title = document.createElement('h1');
title.textContent = 'BimEngine';
title.className = 'bim-engine-title';
// 4. 创建段落 p
const desc = document.createElement('p');
desc.textContent = '这是一个使用BIM-ENGINE。';
desc.className = 'bim-engine-desc';
// 6. 创建操作按钮组容器
const btnGroupContainer = document.createElement('div');
btnGroupContainer.id = 'opt-btn-groups';
btnGroupContainer.className = 'bim-engine-opt-btn-container';
// 7. 组装元素
wrapper.appendChild(title);
wrapper.appendChild(desc);
wrapper.appendChild(btnGroupContainer); // 将按钮组放入 wrapper 中
// 8. 挂载到主容器
this.container.appendChild(wrapper);
// 9. 初始化操作按钮组
this.initOptBtnGroups(btnGroupContainer);
}
private initOptBtnGroups(container: HTMLElement) {
this.optBtnGroups = new OptBtnGroups({
container,
showLabel: true
});
// 初始化并加载默认按钮
this.optBtnGroups.init().catch(err => {
console.error('Failed to initialize OptBtnGroups:', err);
});
}
public destroy() {
if (this.optBtnGroups) {
this.optBtnGroups.destroy();
this.optBtnGroups = null;
}
this.container.innerHTML = '';
}
}

View File

@@ -1,3 +1,4 @@
import { BimEngine } from './BimEngine';
import { BimEngine } from './bim-engine';
export { OptBtnGroups } from './toolbar';
export type { OptButton, ButtonGroup, OptBtnGroupsOptions, ClickPayload } from './toolbar/index.type';
export { BimEngine };

View File

@@ -0,0 +1,16 @@
import type { ButtonConfig } from '../../index.type';
/**
* 首页按钮配置
*/
export const homeButton: ButtonConfig = {
id: 'home',
groupId: 'group-1',
type: 'button',
label: '首页',
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 21V9l8-6l8 6v12h-6v-7h-4v7z"/></svg>',
keepActive: true,
onClick: (button) => {
console.log('首页按钮被点击:', button.id);
}
};

View File

@@ -0,0 +1,16 @@
import type { ButtonConfig } from '../../index.type';
/**
* 定位按钮配置
*/
export const infoButton: ButtonConfig = {
id: 'info',
groupId: 'group-2',
type: 'button',
label: '信息',
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 7q.425 0 .713-.288T13 6t-.288-.712T12 5t-.712.288T11 6t.288.713T12 7m0 8q.425 0 .713-.288T13 14v-4q0-.425-.288-.712T12 9t-.712.288T11 10v4q0 .425.288.713T12 15m-6 3l-2.3 2.3q-.475.475-1.088.213T2 19.575V4q0-.825.588-1.412T4 2h16q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18z"/></svg>',
keepActive: false,
onClick: (button) => {
console.log('信息按钮被点击:', button.id);
}
};

View File

@@ -0,0 +1,16 @@
import type { ButtonConfig } from '../../index.type';
/**
* 定位按钮配置
*/
export const locationButton: ButtonConfig = {
id: 'location',
groupId: 'group-1',
type: 'button',
label: '定位',
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9 13h2v-2.75h2V13h2V8.25l-3-2l-3 2zm3 9q-4.025-3.425-6.012-6.362T4 10.2q0-3.75 2.413-5.975T12 2t5.588 2.225T20 10.2q0 2.5-1.987 5.438T12 22"/></svg>',
keepActive: false,
onClick: (button) => {
console.log('定位按钮被点击:', button.id);
}
};

View File

@@ -0,0 +1,16 @@
import type { ButtonConfig } from '../../index.type';
/**
* 定位按钮配置
*/
export const settingButton: ButtonConfig = {
id: 'setting',
groupId: 'group-2',
type: 'button',
label: '设置',
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m9.25 22l-.4-3.2q-.325-.125-.612-.3t-.563-.375L4.7 19.375l-2.75-4.75l2.575-1.95Q4.5 12.5 4.5 12.338v-.675q0-.163.025-.338L1.95 9.375l2.75-4.75l2.975 1.25q.275-.2.575-.375t.6-.3l.4-3.2h5.5l.4 3.2q.325.125.613.3t.562.375l2.975-1.25l2.75 4.75l-2.575 1.95q.025.175.025.338v.674q0 .163-.05.338l2.575 1.95l-2.75 4.75l-2.95-1.25q-.275.2-.575.375t-.6.3l-.4 3.2zM11 20h1.975l.35-2.65q.775-.2 1.438-.587t1.212-.938l2.475 1.025l.975-1.7l-2.15-1.625q.125-.35.175-.737T17.5 12t-.05-.787t-.175-.738l2.15-1.625l-.975-1.7l-2.475 1.05q-.55-.575-1.212-.962t-1.438-.588L13 4h-1.975l-.35 2.65q-.775.2-1.437.588t-1.213.937L5.55 7.15l-.975 1.7l2.15 1.6q-.125.375-.175.75t-.05.8q0 .4.05.775t.175.75l-2.15 1.625l.975 1.7l2.475-1.05q.55.575 1.213.963t1.437.587zm1.05-4.5q1.45 0 2.475-1.025T15.55 12t-1.025-2.475T12.05 8.5q-1.475 0-2.488 1.025T8.55 12t1.013 2.475T12.05 15.5M12 12"/></svg>',
keepActive: false,
onClick: (button) => {
console.log('设置按钮被点击:', button.id);
}
};

View File

@@ -0,0 +1,13 @@
import type { ButtonConfig } from '../../../index.type';
export const walkBirdButton: ButtonConfig = {
id: 'walk-bird',
groupId: 'group-1',
parentId: 'walk',
type: 'button',
label: '鸟瞰漫游',
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9 22V8.775q-2.275-.6-3.637-2.512T4 2h2q0 2.075 1.338 3.538T10.75 7h2.5q.75 0 1.4.275t1.175.8L20.35 12.6l-1.4 1.4L15 10.05V22h-2v-6h-2v6zm3-16q-.825 0-1.412-.587T10 4t.588-1.412T12 2t1.413.588T14 4t-.587 1.413T12 6"/></svg>',
onClick: (button) => {
console.log('鸟瞰漫游被点击:', button.id);
}
};

View File

@@ -0,0 +1,16 @@
import type { ButtonConfig } from '../../../index.type';
/**
* 漫游菜单按钮配置
*/
export const walkMenuButton: ButtonConfig = {
id: 'walk',
groupId: 'group-1',
type: 'menu',
label: '漫游',
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9 22V8.775q-2.275-.6-3.637-2.512T4 2h2q0 2.075 1.338 3.538T10.75 7h2.5q.75 0 1.4.275t1.175.8L20.35 12.6l-1.4 1.4L15 10.05V22h-2v-6h-2v6zm3-16q-.825 0-1.412-.587T10 4t.588-1.412T12 2t1.413.588T14 4t-.587 1.413T12 6"/></svg>',
keepActive: true,
onClick: (button) => {
console.log('漫游按钮被点击:', button.id);
}
};

View File

@@ -0,0 +1,13 @@
import type { ButtonConfig } from '../../../index.type';
export const walkPersonButton: ButtonConfig = {
id: 'walk-person',
groupId: 'group-1',
parentId: 'walk',
type: 'button',
label: '人视漫游',
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9 22V8.775q-2.275-.6-3.637-2.512T4 2h2q0 2.075 1.338 3.538T10.75 7h2.5q.75 0 1.4.275t1.175.8L20.35 12.6l-1.4 1.4L15 10.05V22h-2v-6h-2v6zm3-16q-.825 0-1.412-.587T10 4t.588-1.412T12 2t1.413.588T14 4t-.587 1.413T12 6"/></svg>',
onClick: (button) => {
console.log('人视漫游被点击:', button.id);
}
};

181
src/toolbar/index.css Normal file
View File

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

387
src/toolbar/index.ts Normal file
View File

@@ -0,0 +1,387 @@
import './index.css';
import type {
OptButton,
ButtonGroup,
OptBtnGroupsOptions,
ButtonConfig
} from './index.type';
export class OptBtnGroups {
private container: HTMLElement;
private options: OptBtnGroupsOptions;
// 改用 Array 存储 Group方便控制顺序
private groups: ButtonGroup[] = [];
private activeBtnIds: Set<string> = new Set();
private btnRefs: Map<string, HTMLElement> = new Map();
private dropdownElement: HTMLElement | null = null;
private hoverTimeout: number | null = null;
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>';
constructor(options: OptBtnGroupsOptions) {
const el = typeof options.container === 'string'
? document.getElementById(options.container)
: options.container;
if (!el) throw new Error('Container not found');
this.container = el;
this.options = {
showLabel: true,
visibility: {},
...options
};
this.initContainer();
}
private initContainer(): void {
this.container.innerHTML = '';
}
/**
* 添加按钮组
* @param groupId 组ID
* @param beforeGroupId 在哪个组之前插入(可选),不传则插入到最后
*/
public addGroup(groupId: string, beforeGroupId?: string): void {
if (this.groups.some(g => g.id === groupId)) {
console.warn('Group ' + groupId + ' already exists');
return;
}
const newGroup: ButtonGroup = { id: groupId, buttons: [] };
if (beforeGroupId) {
const index = this.groups.findIndex(g => g.id === beforeGroupId);
if (index !== -1) {
this.groups.splice(index, 0, newGroup);
} else {
console.warn(`Target group ${beforeGroupId} not found, appending ${groupId} to end.`);
this.groups.push(newGroup);
}
} else {
this.groups.push(newGroup);
}
}
/**
* 添加按钮
* @param config 按钮配置(必须包含 groupId可选包含 parentId
*/
public addButton(config: ButtonConfig): void {
const { groupId, parentId } = config;
if (!groupId) {
throw new Error(`Button ${config.id} config must contain 'groupId'`);
}
const group = this.groups.find(g => g.id === groupId);
if (!group) {
throw new Error(`Group ${groupId} not found. Please call addGroup first.`);
}
const button: OptButton = {
...config,
children: config.children || []
};
if (parentId) {
// Add as sub-button
const parentBtn = this.findButton(group.buttons, parentId);
if (!parentBtn) {
throw new Error(`Parent button ${parentId} not found in group ${groupId}`);
}
if (!parentBtn.children) {
parentBtn.children = [];
}
parentBtn.children.push(button);
} else {
// Add as main button
group.buttons.push(button);
}
}
private findButton(buttons: OptButton[], id: string): OptButton | undefined {
for (const btn of buttons) {
if (btn.id === id) return btn;
if (btn.children) {
const found = this.findButton(btn.children, id);
if (found) return found;
}
}
return undefined;
}
public async init(): Promise<void> {
const { homeButton } = await import('./buttons/home');
const { locationButton } = await import('./buttons/location');
const { walkMenuButton } = await import('./buttons/walk/walk-menu');
const { walkPersonButton } = await import('./buttons/walk/walk-person');
const { walkBirdButton } = await import('./buttons/walk/walk-bird');
const { settingButton } = await import('./buttons/setting');
const { infoButton } = await import('./buttons/info');
// 添加组1
this.addGroup('group-1');
this.addButton(homeButton);
this.addButton(walkMenuButton);
this.addButton(walkPersonButton);
this.addButton(walkBirdButton);
this.addButton(locationButton);
this.addGroup('group-2');
this.addButton(settingButton);
this.addButton(infoButton);
this.render();
}
public render(): void {
this.container.innerHTML = '';
this.btnRefs.clear();
const wrapper = document.createElement('div');
wrapper.className = 'toolbar-container';
// 直接遍历数组,顺序由 addGroup 控制
this.groups.forEach((group, index) => {
const groupElement = this.renderGroup(group, index, this.groups.length);
wrapper.appendChild(groupElement);
});
this.container.appendChild(wrapper);
}
private renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement {
const groupEl = document.createElement('div');
groupEl.className = 'opt-btn-group';
if (index < total - 1) {
groupEl.classList.add('has-divider');
}
group.buttons.forEach(button => {
if (this.isVisible(button.id)) {
const btnWrapper = this.renderButton(button);
groupEl.appendChild(btnWrapper);
}
});
return groupEl;
}
private renderButton(button: OptButton): HTMLElement {
const wrapper = document.createElement('div');
wrapper.className = 'opt-btn-wrapper';
const btnEl = document.createElement('div');
btnEl.className = 'opt-btn';
if (this.activeBtnIds.has(button.id)) {
btnEl.classList.add('active');
}
if (button.disabled) {
btnEl.classList.add('disabled');
}
if (!this.options.showLabel) {
btnEl.classList.add('no-label');
if (button.label) {
btnEl.title = button.label;
}
}
const icon = document.createElement('div');
icon.className = 'opt-btn-icon';
icon.innerHTML = this.getIcon(button.icon);
btnEl.appendChild(icon);
if (this.options.showLabel && button.label) {
const label = document.createElement('span');
label.className = 'opt-btn-label';
label.textContent = button.label;
btnEl.appendChild(label);
}
if (button.children && button.children.length > 0) {
const arrow = document.createElement('span');
arrow.className = 'opt-btn-arrow';
arrow.textContent = '▼';
btnEl.appendChild(arrow);
}
btnEl.addEventListener('click', () => this.handleClick(button));
btnEl.addEventListener('mouseenter', () => this.handleMouseEnter(button, btnEl));
btnEl.addEventListener('mouseleave', () => this.handleMouseLeave());
this.btnRefs.set(button.id, btnEl);
wrapper.appendChild(btnEl);
return wrapper;
}
private handleClick(button: OptButton): void {
if (button.disabled) return;
if (!button.children || button.children.length === 0) {
if (button.keepActive) {
const wasActive = this.activeBtnIds.has(button.id);
if (wasActive) {
this.activeBtnIds.delete(button.id);
} else {
this.activeBtnIds.add(button.id);
}
this.updateButtonState(button.id);
}
this.closeDropdown();
if (button.onClick) {
button.onClick(button);
}
}
}
private handleSubClick(button: OptButton): void {
if (button.keepActive) {
const wasActive = this.activeBtnIds.has(button.id);
if (wasActive) {
this.activeBtnIds.delete(button.id);
} else {
this.activeBtnIds.add(button.id);
}
this.updateButtonState(button.id);
}
this.closeDropdown();
if (button.onClick) {
button.onClick(button);
}
}
private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void {
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
this.hoverTimeout = null;
}
if (button.children && button.children.length > 0) {
this.showDropdown(button, btnEl);
const arrow = btnEl.querySelector('.opt-btn-arrow');
if (arrow) {
arrow.classList.add('rotated');
}
} else {
this.closeDropdown();
}
}
private handleMouseLeave(): void {
this.hoverTimeout = window.setTimeout(() => {
this.closeDropdown();
}, 200);
}
private showDropdown(button: OptButton, btnEl: HTMLElement): void {
this.closeDropdown();
if (!button.children) return;
const dropdown = document.createElement('div');
dropdown.className = 'opt-btn-dropdown';
const rect = btnEl.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
dropdown.style.top = rect.top - 8 + 'px';
dropdown.style.left = centerX + 'px';
button.children.forEach(subBtn => {
if (this.isVisible(subBtn.id)) {
const item = this.renderDropdownItem(subBtn);
dropdown.appendChild(item);
}
});
dropdown.addEventListener('mouseenter', () => {
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
this.hoverTimeout = null;
}
});
dropdown.addEventListener('mouseleave', () => this.handleMouseLeave());
document.body.appendChild(dropdown);
this.dropdownElement = dropdown;
}
private renderDropdownItem(button: OptButton): HTMLElement {
const item = document.createElement('div');
item.className = 'opt-btn-dropdown-item';
const icon = document.createElement('div');
icon.className = 'opt-btn-icon small';
icon.innerHTML = this.getIcon(button.icon);
item.appendChild(icon);
if (this.options.showLabel) {
const label = document.createElement('span');
label.textContent = button.label;
item.appendChild(label);
}
item.addEventListener('click', (e) => {
e.stopPropagation();
this.handleSubClick(button);
});
return item;
}
private closeDropdown(): void {
if (this.dropdownElement) {
this.dropdownElement.remove();
this.dropdownElement = null;
}
this.btnRefs.forEach(btnEl => {
const arrow = btnEl.querySelector('.opt-btn-arrow');
if (arrow) {
arrow.classList.remove('rotated');
}
});
}
private updateButtonState(buttonId: string): void {
const btnEl = this.btnRefs.get(buttonId);
if (btnEl) {
if (this.activeBtnIds.has(buttonId)) {
btnEl.classList.add('active');
} else {
btnEl.classList.remove('active');
}
}
}
private getIcon(icon?: string): string {
return icon || this.DEFAULT_ICON;
}
private isVisible(id: string): boolean {
return this.options.visibility?.[id] !== false;
}
public destroy(): void {
this.closeDropdown();
if (this.hoverTimeout) {
clearTimeout(this.hoverTimeout);
}
this.container.innerHTML = '';
this.btnRefs.clear();
this.activeBtnIds.clear();
this.groups = []; // 清空数组
}
}

51
src/toolbar/index.type.ts Normal file
View File

@@ -0,0 +1,51 @@
export type ButtonType = 'button' | 'menu';
/**
* 按钮配置接口(用于外部定义按钮)
*/
export interface ButtonConfig {
id: string; // 唯一标识
type: ButtonType; // 按钮类型
label: string; // 按钮文字
icon?: string; // SVG 图标(内联 SVG 字符串)
keepActive?: boolean; // 是否保持激活状态(默认 false
disabled?: boolean; // 是否禁用
onClick?: (button: OptButton) => void; // 点击回调
children?: ButtonConfig[]; // 子按钮配置(可选,用于菜单按钮)
groupId?: string; // 所属组ID
parentId?: string; // 父按钮ID如果是子按钮
}
/**
* 操作按钮接口(内部使用,继承配置)
*/
export interface OptButton extends ButtonConfig {
children?: OptButton[]; // 内部使用的子按钮列表
}
/**
* 按钮组接口
*/
export interface ButtonGroup {
id: string;
buttons: OptButton[];
}
/**
* OptBtnGroups 配置选项
*/
export interface OptBtnGroupsOptions {
container: HTMLElement | string;
showLabel?: boolean;
visibility?: Record<string, boolean>;
}
/**
* 点击事件载荷
*/
export interface ClickPayload {
button: OptButton;
action: 'activate' | 'deactivate' | 'trigger';
isActive?: boolean;
}