添加测试信息
This commit is contained in:
@@ -8,12 +8,12 @@ import { localeManager } from './services/locale';
|
||||
import { themeManager } from './services/theme';
|
||||
import type { LocaleType } from './locales/types';
|
||||
import type { ThemeType, ThemeConfig } from './themes/types';
|
||||
import { EventEmitter } from './core/event-emitter';
|
||||
import { EngineEvents } from './types/events';
|
||||
|
||||
export type { EngineOptions, ModelLoadOptions };
|
||||
|
||||
|
||||
|
||||
export class BimEngine {
|
||||
export class BimEngine extends EventEmitter {
|
||||
private container: HTMLElement;
|
||||
private wrapper: HTMLElement | null = null;
|
||||
private topLeftGroup: any = null; // 保存左上角按钮组的引用
|
||||
@@ -33,6 +33,7 @@ export class BimEngine {
|
||||
theme?: ThemeType;
|
||||
}
|
||||
) {
|
||||
super();
|
||||
const el = typeof container === 'string' ? document.getElementById(container) : container;
|
||||
if (!el) throw new Error('Container not found');
|
||||
this.container = el;
|
||||
@@ -49,6 +50,15 @@ export class BimEngine {
|
||||
this.init();
|
||||
}
|
||||
|
||||
// Typed wrappers for events
|
||||
public emit<K extends keyof EngineEvents>(event: K, payload: EngineEvents[K]) {
|
||||
super.emit(event, payload);
|
||||
}
|
||||
|
||||
public on<K extends keyof EngineEvents>(event: K, listener: (payload: EngineEvents[K]) => void): () => void {
|
||||
return super.on(event, listener);
|
||||
}
|
||||
|
||||
public setLocale(locale: LocaleType) { localeManager.setLocale(locale); }
|
||||
public getLocale(): LocaleType { return localeManager.getLocale(); }
|
||||
public setTheme(theme: 'dark' | 'light') { themeManager.setTheme(theme); }
|
||||
@@ -61,12 +71,12 @@ export class BimEngine {
|
||||
this.container.appendChild(this.wrapper);
|
||||
|
||||
// 创建 3D 引擎管理器
|
||||
this.engine = new EngineManager(this.wrapper);
|
||||
this.engine = new EngineManager(this, this.wrapper);
|
||||
|
||||
// 初始化其他管理器
|
||||
this.dialog = new DialogManager(this.wrapper);
|
||||
this.toolbar = new ToolbarManager(this.wrapper);
|
||||
this.buttonGroup = new ButtonGroupManager(this.wrapper);
|
||||
this.dialog = new DialogManager(this, this.wrapper);
|
||||
this.toolbar = new ToolbarManager(this, this.wrapper);
|
||||
this.buttonGroup = new ButtonGroupManager(this, this.wrapper);
|
||||
|
||||
|
||||
// 初始主题
|
||||
@@ -112,5 +122,6 @@ export class BimEngine {
|
||||
this.engine?.destroy();
|
||||
this.dialog = null;
|
||||
this.container.innerHTML = '';
|
||||
this.clear(); // Clear all events
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import { t, localeManager } from '../../services/locale';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { ThemeConfig } from '../../themes/types';
|
||||
import { IBimComponent } from '../../types/component';
|
||||
import type { BimEngine } from '../../bim-engine';
|
||||
import { EngineEvents } from '../../types/events';
|
||||
|
||||
/**
|
||||
* 通用按钮组组件 (BimButtonGroup)
|
||||
@@ -26,6 +28,8 @@ export class BimButtonGroup implements IBimComponent {
|
||||
private unsubscribeLocale: (() => void) | null = null;
|
||||
private unsubscribeTheme: (() => void) | null = null;
|
||||
|
||||
protected engine: BimEngine | 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: ButtonGroupOptions) {
|
||||
@@ -63,6 +67,18 @@ export class BimButtonGroup implements IBimComponent {
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public setEngine(engine: BimEngine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
protected emit<K extends keyof EngineEvents>(event: K, payload: EngineEvents[K]) {
|
||||
if (this.engine) {
|
||||
this.engine.emit(event, payload);
|
||||
} else {
|
||||
console.warn('[BimButtonGroup] Engine not set, cannot emit event:', event);
|
||||
}
|
||||
}
|
||||
|
||||
private initContainer(): void {
|
||||
this.container.innerHTML = '';
|
||||
this.container.classList.add('bim-btn-group-root');
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
import type { ButtonConfig } from '../../../index.type';
|
||||
import type { BimEngine } from '../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 首页按钮配置
|
||||
* 使用工厂函数模式,注入 engine 实例
|
||||
*/
|
||||
export const homeButton: ButtonConfig = {
|
||||
id: 'home',
|
||||
groupId: 'group-1',
|
||||
type: 'button',
|
||||
label: 'toolbar.home',
|
||||
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);
|
||||
}
|
||||
export const createHomeButton = (engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'home',
|
||||
groupId: 'group-1',
|
||||
type: 'button',
|
||||
label: 'toolbar.home',
|
||||
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);
|
||||
// 演示:使用 engine 发送事件
|
||||
// engine.dialog?.showInfoDialog()
|
||||
engine.emit('ui:open-dialog', { id: 'home-info' });
|
||||
|
||||
// 或者直接调用 engine 的方法
|
||||
// if (engine.engine) {
|
||||
// engine.engine.loadModel('...');
|
||||
// }
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ export class Toolbar extends BimButtonGroup {
|
||||
await super.init();
|
||||
|
||||
// 动态加载默认按钮配置
|
||||
const { homeButton } = await import('./buttons/home');
|
||||
const { createHomeButton } = 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');
|
||||
@@ -21,7 +21,14 @@ export class Toolbar extends BimButtonGroup {
|
||||
const { infoButton } = await import('./buttons/info');
|
||||
|
||||
this.addGroup('group-1');
|
||||
this.addButton(homeButton);
|
||||
|
||||
// 使用工厂函数创建按钮,并注入 engine
|
||||
if (this.engine) {
|
||||
this.addButton(createHomeButton(this.engine));
|
||||
} else {
|
||||
console.warn('[Toolbar] Engine not available when creating buttons.');
|
||||
}
|
||||
|
||||
this.addButton(walkMenuButton);
|
||||
this.addButton(walkPersonButton);
|
||||
this.addButton(walkBirdButton);
|
||||
|
||||
27
src/core/component.ts
Normal file
27
src/core/component.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { BimEngine } from '../bim-engine';
|
||||
import { EngineEvents } from '../types/events';
|
||||
|
||||
export abstract class BimComponent {
|
||||
protected engine: BimEngine;
|
||||
|
||||
constructor(engine: BimEngine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to send events easily
|
||||
*/
|
||||
protected emit<K extends keyof EngineEvents>(event: K, payload: EngineEvents[K]): void {
|
||||
this.engine.emit(event, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to listen to events easily
|
||||
* Returns an unsubscribe function
|
||||
*/
|
||||
protected on<K extends keyof EngineEvents>(event: K, listener: (payload: EngineEvents[K]) => void): () => void {
|
||||
return this.engine.on(event, listener);
|
||||
}
|
||||
|
||||
abstract destroy(): void;
|
||||
}
|
||||
42
src/core/event-emitter.ts
Normal file
42
src/core/event-emitter.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
type Listener<T = any> = (payload: T) => void;
|
||||
|
||||
export class EventEmitter {
|
||||
private events: Map<string, Listener[]> = new Map();
|
||||
|
||||
public on(event: string, listener: Listener): () => void {
|
||||
if (!this.events.has(event)) {
|
||||
this.events.set(event, []);
|
||||
}
|
||||
this.events.get(event)!.push(listener);
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => this.off(event, listener);
|
||||
}
|
||||
|
||||
public off(event: string, listener: Listener): void {
|
||||
const listeners = this.events.get(event);
|
||||
if (!listeners) return;
|
||||
|
||||
const index = listeners.indexOf(listener);
|
||||
if (index !== -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public emit(event: string, payload?: any): void {
|
||||
const listeners = this.events.get(event);
|
||||
if (listeners) {
|
||||
listeners.forEach(listener => {
|
||||
try {
|
||||
listener(payload);
|
||||
} catch (error) {
|
||||
console.error(`[EventEmitter] Error in listener for event "${event}":`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.events.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,46 @@
|
||||
import { BimButtonGroup } from '../components/button-group';
|
||||
import type { ButtonGroupOptions } from '../components/button-group/index.type';
|
||||
import type { ThemeConfig } from '../themes/types';
|
||||
import { BimComponent } from '../core/component';
|
||||
import type { BimEngine } from '../bim-engine';
|
||||
|
||||
/**
|
||||
* 通用按钮组管理器
|
||||
* 负责创建和管理除底部工具栏以外的其他按钮组。
|
||||
* 通用按钮组管理器 (ButtonGroupManager)
|
||||
* 负责创建和管理通用的按钮组实例。
|
||||
*/
|
||||
export class ButtonGroupManager {
|
||||
private activeGroups: BimButtonGroup[] = [];
|
||||
export class ButtonGroupManager extends BimComponent {
|
||||
private groups: Map<string, BimButtonGroup> = new Map();
|
||||
private container: HTMLElement;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
constructor(engine: BimEngine, container: HTMLElement) {
|
||||
super(engine);
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的按钮组
|
||||
*/
|
||||
public create(options: Omit<ButtonGroupOptions, 'container'>): BimButtonGroup {
|
||||
// 自动创建一个 div 作为容器
|
||||
const groupContainer = document.createElement('div');
|
||||
this.container.appendChild(groupContainer);
|
||||
|
||||
public create(id: string, options: Omit<ButtonGroupOptions, 'container'>): BimButtonGroup {
|
||||
const group = new BimButtonGroup({
|
||||
container: groupContainer,
|
||||
container: this.container,
|
||||
...options
|
||||
});
|
||||
|
||||
// 立即初始化
|
||||
|
||||
// @ts-ignore
|
||||
group.setEngine(this.engine);
|
||||
|
||||
group.init();
|
||||
this.activeGroups.push(group);
|
||||
this.groups.set(id, group);
|
||||
return group;
|
||||
}
|
||||
|
||||
public updateTheme(theme: ThemeConfig) {
|
||||
this.activeGroups.forEach(g => g.setTheme(theme));
|
||||
public get(id: string): BimButtonGroup | undefined {
|
||||
return this.groups.get(id);
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
this.activeGroups.forEach(g => g.render());
|
||||
public updateTheme(theme: ThemeConfig) {
|
||||
this.groups.forEach(group => group.setTheme(theme));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.activeGroups.forEach(g => g.destroy());
|
||||
this.activeGroups = [];
|
||||
this.groups.forEach(group => group.destroy());
|
||||
this.groups.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@ import { BimDialog } from '../components/dialog';
|
||||
import { BimInfoDialog } from '../components/dialog/bimInfoDialog';
|
||||
import type { DialogOptions } from '../components/dialog/index.type';
|
||||
import type { ThemeConfig } from '../themes/types';
|
||||
import { themeManager } from '../services/theme'; // 修正路径
|
||||
import { themeManager } from '../services/theme';
|
||||
import { BimComponent } from '../core/component';
|
||||
import type { BimEngine } from '../bim-engine';
|
||||
|
||||
/**
|
||||
* 弹窗管理器
|
||||
* 负责创建和管理应用中的各类弹窗。
|
||||
*/
|
||||
export class DialogManager {
|
||||
export class DialogManager extends BimComponent {
|
||||
/** 弹窗挂载的父容器 */
|
||||
private container: HTMLElement;
|
||||
/** 活跃的弹窗实例列表 */
|
||||
@@ -16,10 +18,22 @@ export class DialogManager {
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param engine 引擎实例
|
||||
* @param container 弹窗挂载的目标容器
|
||||
*/
|
||||
constructor(container: HTMLElement) {
|
||||
constructor(engine: BimEngine, container: HTMLElement) {
|
||||
super(engine);
|
||||
this.container = container;
|
||||
|
||||
// 监听打开弹窗事件
|
||||
this.on('ui:open-dialog', (payload) => {
|
||||
// 这里可以根据 payload.id 做更复杂的逻辑,目前简单演示
|
||||
console.log('[DialogManager] Received open-dialog event:', payload);
|
||||
// 示例:如果 payload.id 是 'info',则打开 info dialog
|
||||
if (payload.id === 'info') {
|
||||
this.showInfoDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,4 +80,9 @@ export class DialogManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.activeDialogs.forEach(d => d.destroy());
|
||||
this.activeDialogs = [];
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,25 @@
|
||||
import { Engine, type EngineOptions, type ModelLoadOptions } from '../components/engine';
|
||||
import { BimComponent } from '../core/component';
|
||||
import type { BimEngine } from '../bim-engine';
|
||||
|
||||
/**
|
||||
* 3D 引擎管理器
|
||||
* 负责连接 Engine 组件和 BimEngine,向外部暴露简化的 API
|
||||
* 采用延迟初始化模式,用户需主动调用 initialize() 方法
|
||||
*/
|
||||
export class EngineManager {
|
||||
export class EngineManager extends BimComponent {
|
||||
/** 3D 引擎挂载的父容器 */
|
||||
private container: HTMLElement;
|
||||
/** 3D 引擎组件实例 */
|
||||
private engine: Engine | null = null;
|
||||
private engineInstance: Engine | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param engine 引擎实例
|
||||
* @param container 3D 引擎挂载的目标容器
|
||||
*/
|
||||
constructor(container: HTMLElement) {
|
||||
constructor(engine: BimEngine, container: HTMLElement) {
|
||||
super(engine);
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
@@ -26,27 +30,27 @@ export class EngineManager {
|
||||
*/
|
||||
public initialize(options?: Omit<EngineOptions, 'container'>): boolean {
|
||||
// 如果已经初始化,先销毁旧的实例
|
||||
if (this.engine && this.engine.isInitialized()) {
|
||||
if (this.engineInstance && this.engineInstance.isInitialized()) {
|
||||
console.warn('[EngineManager] 3D Engine already initialized. Destroying old instance...');
|
||||
this.engine.destroy();
|
||||
this.engine = null;
|
||||
this.engineInstance.destroy();
|
||||
this.engineInstance = null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建 Engine 组件实例
|
||||
// options 中的配置会自动复制给 createEngine 使用
|
||||
this.engine = new Engine({
|
||||
this.engineInstance = new Engine({
|
||||
container: this.container,
|
||||
...options, // 合并配置选项
|
||||
});
|
||||
|
||||
// 调用组件的 init 方法初始化引擎
|
||||
this.engine.init();
|
||||
this.engineInstance.init();
|
||||
|
||||
return this.engine.isInitialized();
|
||||
return this.engineInstance.isInitialized();
|
||||
} catch (error) {
|
||||
console.error('[EngineManager] Failed to initialize 3D engine:', error);
|
||||
this.engine = null;
|
||||
this.engineInstance = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -55,7 +59,7 @@ export class EngineManager {
|
||||
* 检查 3D 引擎是否已初始化
|
||||
*/
|
||||
public isInitialized(): boolean {
|
||||
return this.engine !== null && this.engine.isInitialized();
|
||||
return this.engineInstance !== null && this.engineInstance.isInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,11 +68,11 @@ export class EngineManager {
|
||||
* @param options 加载选项(位置、旋转、缩放)
|
||||
*/
|
||||
public loadModel(url: string, options?: ModelLoadOptions): void {
|
||||
if (!this.engine || !this.engine.isInitialized()) {
|
||||
if (!this.engineInstance || !this.engineInstance.isInitialized()) {
|
||||
console.error('[EngineManager] 3D Engine not initialized. Please call initialize() first.');
|
||||
return;
|
||||
}
|
||||
this.engine.loadModel(url, options);
|
||||
this.engineInstance.loadModel(url, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,20 +80,20 @@ export class EngineManager {
|
||||
* 用于直接调用第三方引擎的其他 API
|
||||
*/
|
||||
public getEngine(): any {
|
||||
if (!this.engine) {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return null;
|
||||
}
|
||||
return this.engine.getEngine();
|
||||
return this.engineInstance.getEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁 3D 引擎实例
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.engine) {
|
||||
this.engine.destroy();
|
||||
this.engine = null;
|
||||
if (this.engineInstance) {
|
||||
this.engineInstance.destroy();
|
||||
this.engineInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import type { ButtonGroupColors, ButtonConfig } from '../components/button-group/index.type';
|
||||
import { Toolbar } from '../components/button-group/toolbar';
|
||||
import type { ThemeConfig } from '../themes/types';
|
||||
import { BimComponent } from '../core/component';
|
||||
import type { BimEngine } from '../bim-engine';
|
||||
|
||||
/**
|
||||
* 底部工具栏管理器 (ToolbarManager)
|
||||
* 仅负责管理底部工具栏实例。
|
||||
*/
|
||||
export class ToolbarManager {
|
||||
export class ToolbarManager extends BimComponent {
|
||||
private toolbar: Toolbar | null = null;
|
||||
private toolbarContainer: HTMLElement | null = null;
|
||||
private container: HTMLElement;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
constructor(engine: BimEngine, container: HTMLElement) {
|
||||
super(engine);
|
||||
this.container = container;
|
||||
this.init();
|
||||
}
|
||||
@@ -32,6 +35,10 @@ export class ToolbarManager {
|
||||
expand: 'up' // 向上展开
|
||||
});
|
||||
|
||||
// 注入 engine 到 Toolbar
|
||||
// @ts-ignore - Toolbar 还没更新类型,暂时忽略
|
||||
this.toolbar.setEngine(this.engine);
|
||||
|
||||
this.toolbar.init();
|
||||
}
|
||||
|
||||
|
||||
13
src/types/events.ts
Normal file
13
src/types/events.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface EngineEvents {
|
||||
// UI Events
|
||||
'ui:open-dialog': { id: string; data?: any };
|
||||
'ui:close-dialog': { id: string };
|
||||
|
||||
// Engine Events
|
||||
'engine:model-loaded': { url: string };
|
||||
'engine:object-clicked': { objectId: string; position: { x: number, y: number, z: number } };
|
||||
|
||||
// System Events
|
||||
'sys:theme-changed': { theme: string };
|
||||
'sys:locale-changed': { locale: string };
|
||||
}
|
||||
Reference in New Issue
Block a user