初始化

This commit is contained in:
yuding
2025-12-25 15:47:57 +08:00
parent 04a5e74284
commit 9b6959585d
28 changed files with 6464 additions and 4197 deletions

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

206
dist/index.d.ts vendored
View File

@@ -191,6 +191,8 @@ export declare class BimEngine extends EventEmitter {
sectionPlane: SectionPlaneDialogManager | null;
sectionAxis: SectionAxisDialogManager | null;
sectionBox: SectionBoxDialogManager | null;
walkControl: WalkControlManager | null;
map: MapDialogManager | null;
constructor(container: HTMLElement | string, options?: {
locale?: LocaleType;
theme?: ThemeType;
@@ -349,6 +351,11 @@ export declare interface ButtonGroupOptions extends ButtonGroupColors {
declare type ButtonType = 'button' | 'menu';
/**
* 角色模型类型
*/
declare type CharacterModel = 'office-male' | 'construction-worker';
export declare interface CollapseItemConfig {
/** 唯一标识符 */
id: string;
@@ -587,6 +594,26 @@ export declare interface EngineEvents {
'sys:locale-changed': {
locale: string;
};
'walk:path-mode-toggle': {
isActive: boolean;
};
'walk:walk-mode-toggle': {
isActive: boolean;
};
'walk:plan-view-toggle': {
isActive: boolean;
};
'walk:speed-change': {
speed: number;
};
'walk:gravity-toggle': {
enabled: boolean;
};
'walk:collision-toggle': {
enabled: boolean;
};
'map:opened': {};
'map:closed': {};
}
/**
@@ -704,6 +731,33 @@ declare type Listener<T = any> = (payload: T) => void;
*/
declare type LocaleType = 'zh-CN' | 'en-US';
/**
* 地图弹窗管理器(独立通用组件)
*/
declare class MapDialogManager extends BimComponent {
private dialogId;
private dialog;
private panel;
constructor(engine: BimEngine);
init(): void;
/**
* 显示弹窗
*/
show(): void;
/**
* 隐藏弹窗
*/
hide(): void;
/**
* 检查地图是否打开
*/
isOpen(): boolean;
/**
* 销毁弹窗和面板
*/
destroy(): void;
}
/**
* 测量配置项(由组件内部维护默认值,并读取/写入缓存)
*/
@@ -881,6 +935,7 @@ declare interface OptButton extends ButtonConfig {
*/
declare class PropertyPanelManager extends BimComponent {
private dialogId;
private dialog;
constructor(engine: BimEngine);
init(): void;
/**
@@ -898,6 +953,14 @@ declare class PropertyPanelManager extends BimComponent {
private createBaseInfoContent;
private createAdvancedInfoContent;
private createMaterialContent;
/**
* 检查属性面板是否打开
*/
isOpen(): boolean;
/**
* 隐藏属性面板
*/
hide(): void;
destroy(): void;
}
@@ -1134,6 +1197,18 @@ declare class ToolbarManager extends BimComponent {
setVisible(visible: boolean): void;
setBackgroundColor(color: string): void;
setColors(colors: ButtonGroupColors): void;
/**
* 隐藏工具栏
*/
hide(): void;
/**
* 显示工具栏
*/
show(): void;
/**
* 获取工具栏容器
*/
getContainer(): HTMLElement | null;
}
/**
@@ -1206,4 +1281,135 @@ export declare interface TreeOptions {
renderActions?: (node: TreeNodeConfig) => HTMLElement | string;
}
/**
* 漫游控制管理器
*/
declare class WalkControlManager extends BimComponent {
panel: WalkControlPanel | null;
private pathManager;
constructor(engine: BimEngine);
init(): void;
/**
* 显示漫游控制面板
*/
show(): void;
/**
* 隐藏漫游控制面板
*/
hide(): void;
/**
* 销毁管理器
*/
destroy(): void;
}
/**
* 漫游控制模式
*/
declare type WalkControlMode = 'none' | 'path' | 'walk';
declare class WalkControlPanel implements IBimComponent {
element: HTMLElement;
private options;
private state;
private planViewBtn;
private pathModeBtn;
private walkModeBtn;
private settingsContainer;
private speedControl;
private speedDecreaseBtn;
private speedIncreaseBtn;
private speedDisplay;
private gravityCheckbox;
private gravityLabel;
private collisionCheckbox;
private collisionLabel;
private characterModelSelect;
private characterModelLabel;
private walkModeSelect;
private walkModeLabel;
private exitBtn;
private unsubscribeLocale;
private unsubscribeTheme;
constructor(options?: WalkControlPanelOptions);
init(): void;
setPlanViewActive(active: boolean): void;
setPathModeActive(active: boolean): void;
getState(): WalkControlState;
private createPanel;
private createLeftButtons;
private createSettingsContainer;
private createSpeedControl;
private createIconButton;
private createExitButton;
private setMode;
private updateButtonStates;
private updateSettingsView;
private updateSpeedDisplay;
private updateSpeedButtonStates;
private getIconSVG;
setLocales(): void;
setTheme(theme: ThemeConfig): void;
destroy(): void;
}
/**
* 漫游控制面板配置选项
*/
declare interface WalkControlPanelOptions {
/** 平面图切换回调 */
onPlanViewToggle?: (isActive: boolean) => void;
/** 路径漫游模式切换回调 */
onPathModeToggle?: (isActive: boolean) => void;
/** 漫游模式切换回调 */
onWalkModeToggle?: (isActive: boolean) => void;
/** 速度变化回调 */
onSpeedChange?: (speed: number) => void;
/** 重力切换回调 */
onGravityToggle?: (enabled: boolean) => void;
/** 碰撞切换回调 */
onCollisionToggle?: (enabled: boolean) => void;
/** 角色模型变化回调 */
onCharacterModelChange?: (model: CharacterModel) => void;
/** 行走模式变化回调 */
onWalkModeChange?: (mode: WalkMode) => void;
/** 退出回调 */
onExit?: () => void;
/** 默认速度 (0-100) */
defaultSpeed?: number;
/** 默认重力状态 */
defaultGravity?: boolean;
/** 默认碰撞状态 */
defaultCollision?: boolean;
/** 默认角色模型 */
defaultCharacterModel?: CharacterModel;
/** 默认行走模式 */
defaultWalkMode?: WalkMode;
}
/**
* 漫游控制状态
*/
declare interface WalkControlState {
/** 当前模式 */
mode: WalkControlMode;
/** 平面图是否激活 */
isPlanViewActive: boolean;
/** 移动速度 (0-100) */
speed: number;
/** 重力是否启用 */
gravity: boolean;
/** 碰撞是否启用 */
collision: boolean;
/** 当前角色模型 */
characterModel: CharacterModel;
/** 当前行走模式 */
walkMode: WalkMode;
}
/**
* 行走模式类型
*/
declare type WalkMode = 'walk' | 'run';
export { }

View File

@@ -10,6 +10,8 @@ import {MeasureDialogManager} from './managers/measure-dialog-manager';
import {SectionPlaneDialogManager} from './managers/section-plane-dialog-manager';
import {SectionAxisDialogManager} from './managers/section-axis-dialog-manager';
import {SectionBoxDialogManager} from './managers/section-box-dialog-manager';
import {WalkControlManager} from './managers/walk-control-manager';
import {MapDialogManager} from './managers/map-dialog-manager';
import type {EngineOptions, ModelLoadOptions} from './components/engine';
import {localeManager} from './services/locale';
import {themeManager} from './services/theme';
@@ -35,6 +37,8 @@ export class BimEngine extends EventEmitter {
public sectionPlane: SectionPlaneDialogManager | null = null; // 拾取面剖切面板
public sectionAxis: SectionAxisDialogManager | null = null; // 轴向剖切面板
public sectionBox: SectionBoxDialogManager | null = null; // 剖切盒面板
public walkControl: WalkControlManager | null = null; // 漫游控制面板
public map: MapDialogManager | null = null; // 地图面板
constructor(
@@ -104,6 +108,10 @@ export class BimEngine extends EventEmitter {
this.sectionPlane = new SectionPlaneDialogManager(this);
this.sectionAxis = new SectionAxisDialogManager(this);
this.sectionBox = new SectionBoxDialogManager(this);
this.walkControl = new WalkControlManager(this);
this.walkControl.init();
this.map = new MapDialogManager(this);
this.map.init();
// 初始主题
this.updateTheme(themeManager.getTheme());
@@ -129,6 +137,10 @@ export class BimEngine extends EventEmitter {
this.rightKey?.destroy();
this.propertyPanel?.destroy();
this.measure?.destroy();
this.sectionPlane?.destroy();
this.sectionAxis?.destroy();
this.sectionBox?.destroy();
this.walkControl?.destroy();
this.container.innerHTML = '';
this.clear();
}

View File

@@ -0,0 +1,91 @@
import type { ButtonConfig } from '../../../index.type';
import type { BimEngine } from '../../../../../bim-engine';
/**
* 全屏按钮配置
*/
export const createFullscreenButton = (_engine: BimEngine): ButtonConfig => {
return {
id: 'fullscreen',
groupId: 'group-2',
type: 'button',
label: 'toolbar.fullscreen',
align: 'vertical',
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M5 5h5v2H7v3H5V5m9 0h5v5h-2V7h-3V5m3 9h2v5h-5v-2h3v-3m-7 3v2H5v-5h2v3h3z"/></svg>',
onClick: async () => {
console.log('全屏按钮被点击');
// 0. 环境检查 (帮助调试 Iframe 问题)
const isIframe = window.self !== window.top;
if (isIframe) {
console.warn('检测到在 Iframe 中运行,请确保父级 iframe 标签拥有 allow="fullscreen" 属性');
}
try {
// 1. 获取当前全屏状态 (使用 any 绕过 TS 检查)
const doc = document as any;
const fullscreenElement = doc.fullscreenElement ||
doc.webkitFullscreenElement ||
doc.mozFullScreenElement ||
doc.msFullscreenElement;
const isFullscreen = !!fullscreenElement;
console.log('当前是否全屏:', isFullscreen);
// 2. 确定要全屏的目标元素
// 优先查找 BIM 容器,如果找不到则使用 document.body
const bimContainer = document.querySelector('.bim-engine-container') as HTMLElement;
const targetElem = bimContainer || document.body;
// 将 targetElem 断言为 any解决 "Property 'webkitRequestFullscreen' does not exist" 报错
const el = targetElem as any;
if (!isFullscreen) {
// === 进入全屏 ===
console.log('准备进入全屏...');
// 关键:防止全屏后背景变黑
if (targetElem.style.backgroundColor === '' || targetElem.style.backgroundColor === 'transparent') {
targetElem.style.backgroundColor = '#ffffff'; // 根据你的主题颜色调整
}
// 兼容不同浏览器的 API
const requestMethod = el.requestFullscreen ||
el.webkitRequestFullscreen ||
el.mozRequestFullScreen ||
el.msRequestFullscreen;
if (requestMethod) {
// 使用 call 绑定正确的上下文
await requestMethod.call(el, { navigationUI: 'hide' });
console.log('全屏请求已发送');
} else {
console.warn('当前浏览器不支持全屏 API');
alert('当前浏览器不支持全屏功能');
}
} else {
// === 退出全屏 ===
console.log('准备退出全屏...');
// 兼容不同浏览器的退出 API
const exitMethod = doc.exitFullscreen ||
doc.webkitExitFullscreen ||
doc.mozCancelFullScreen ||
doc.msExitFullscreen;
if (exitMethod) {
await exitMethod.call(doc);
console.log('退出全屏请求已发送');
}
}
} catch (error: any) {
console.error('全屏操作失败:', error);
// 专门提示权限问题
if (error && error.message && error.message.includes('denied')) {
console.error('全屏请求被拒绝。如果是 Iframe请检查 allow="fullscreen"。如果是自动触发,请确保由用户点击触发。');
}
}
}
};
};

View File

@@ -0,0 +1,34 @@
import type { ButtonConfig } from '../../../index.type';
import type { BimEngine } from '../../../../../bim-engine';
/**
* 地图按钮配置(开关按钮)
*/
export const createMapButton = (engine: BimEngine): ButtonConfig => {
// 监听地图打开/关闭事件,同步按钮状态
engine.on('map:opened', () => {
engine.toolbar?.setBtnActive('map', true);
});
engine.on('map:closed', () => {
engine.toolbar?.setBtnActive('map', false);
});
return {
id: 'map',
groupId: 'group-1',
type: 'button',
label: 'toolbar.map',
align: 'vertical',
keepActive: true,
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M20.5 3l-.16.03L15 5.1L9 3L3.36 4.9c-.21.07-.36.25-.36.48V20.5c0 .28.22.5.5.5l.16-.03L9 18.9l6 2.1l5.64-1.9c.21-.07.36-.25.36-.48V3.5c0-.28-.22-.5-.5-.5zM15 19l-6-2.11V5l6 2.11V19z"/></svg>',
onClick: () => {
// 切换地图显示状态
if (engine.map?.isOpen()) {
engine.map?.hide();
} else {
engine.map?.show();
}
}
};
};

View File

@@ -0,0 +1,20 @@
import type { ButtonConfig } from '../../../index.type';
import type { BimEngine } from '../../../../../bim-engine';
/**
* 构件详情按钮配置
*/
export const createPropertyButton = (engine: BimEngine): ButtonConfig => {
return {
id: 'property',
groupId: 'group-1',
type: 'button',
label: 'toolbar.property',
align: 'vertical',
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m0 18h12v-8H6v8m2-6h8v2H8v-2m0 4h5v2H8v-2z"/></svg>',
onClick: () => {
console.log('构件详情按钮被点击');
engine.propertyPanel?.show();
}
};
};

View File

@@ -2,19 +2,19 @@ import type { ButtonConfig } from '../../../../index.type';
import type { BimEngine } from '../../../../../../bim-engine';
/**
* 漫游菜单按钮配置
* 漫游按钮配置(普通按钮,不带子菜单)
*/
export const createWalkMenuButton = (_engine: BimEngine): ButtonConfig => {
export const createWalkMenuButton = (engine: BimEngine): ButtonConfig => {
return {
id: 'walk',
groupId: 'group-1',
type: 'menu',
type: 'button',
label: 'toolbar.walk',
align: 'vertical',
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);
onClick: () => {
console.log('漫游按钮被点击');
engine.walkControl?.show();
}
};
};

View File

@@ -14,12 +14,12 @@ export class Toolbar extends BimButtonGroup {
// 动态加载默认按钮配置
const { createHomeButton } = await import('./buttons/home');
const { createZoomBoxButton } = await import('./buttons/zoom-box');
const { createLocationButton } = await import('./buttons/location');
const { createWalkMenuButton } = await import('./buttons/walk/walk-menu');
const { createWalkPersonButton } = await import('./buttons/walk/walk-person');
const { createWalkBirdButton } = await import('./buttons/walk/walk-bird');
const { createMapButton } = await import('./buttons/map');
const { createPropertyButton } = await import('./buttons/property');
const { createSettingButton } = await import('./buttons/setting');
const { createInfoButton } = await import('./buttons/info');
const { createFullscreenButton } = await import('./buttons/fullscreen');
const { createMeasureButton } = await import('./buttons/measure');
const { createSectionMenuButton } = await import('./buttons/section/section-menu');
const { createSectionPlaneButton } = await import('./buttons/section/section-plane');
@@ -39,12 +39,12 @@ export class Toolbar extends BimButtonGroup {
this.addButton(createSectionAxisButton(this.engine));
this.addButton(createSectionBoxButton(this.engine));
this.addButton(createWalkMenuButton(this.engine));
this.addButton(createWalkPersonButton(this.engine));
this.addButton(createWalkBirdButton(this.engine));
this.addButton(createLocationButton(this.engine));
this.addButton(createMapButton(this.engine));
this.addButton(createPropertyButton(this.engine));
this.addGroup('group-2');
this.addButton(createSettingButton(this.engine));
this.addButton(createInfoButton(this.engine));
this.addButton(createFullscreenButton(this.engine));
} else {
console.warn('[Toolbar] Engine not available when creating buttons.');
}

View File

@@ -0,0 +1,49 @@
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { localeManager } from '../../services/locale';
import { themeManager } from '../../services/theme';
/**
* 地图面板组件
*/
export class MapPanel implements IBimComponent {
public element!: HTMLElement;
private unsubscribeLocale: (() => void) | null = null;
private unsubscribeTheme: (() => void) | null = null;
constructor() {}
public init(): void {
this.element = this.createPanel();
this.unsubscribeLocale = localeManager.subscribe(() => this.setLocales());
this.unsubscribeTheme = themeManager.subscribe((theme) => this.setTheme(theme));
this.setLocales();
this.setTheme(themeManager.getTheme());
}
private createPanel(): HTMLElement {
const panel = document.createElement('div');
panel.className = 'map-panel';
panel.style.padding = '20px';
panel.style.color = '#fff';
panel.textContent = '地图内容待实现';
return panel;
}
public setLocales(): void {
// 更新文本
}
public setTheme(_theme: ThemeConfig): void {
// 应用主题
}
public destroy(): void {
this.unsubscribeLocale?.();
this.unsubscribeTheme?.();
if (this.element && this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
}
}

View File

@@ -131,24 +131,3 @@
background: var(--bim-primary-color);
}
/* 数值显示气泡 */
.section-box-slider-handle::after {
content: attr(data-value);
position: absolute;
top: -22px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 2px 5px;
border-radius: 3px;
font-size: 10px;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.section-box-slider-handle:hover::after,
.section-box-slider-handle.dragging::after {
opacity: 1;
}

View File

@@ -0,0 +1,186 @@
/* 漫游控制面板 */
.walk-control-panel {
display: flex;
align-items: center;
gap: 20px;
padding: 8px 16px;
background: var(--bim-walk-control-bg, rgba(0, 0, 0, 0.8));
border-radius: 8px;
user-select: none;
}
/* 分割线 */
.walk-divider {
width: 1px;
height: 40px;
background: var(--bim-divider-color, rgba(255, 255, 255, 0.2));
flex-shrink: 0;
}
/* 左侧按钮区 */
.walk-control-left {
display: flex;
gap: 8px;
}
.walk-icon-btn {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 2px solid transparent;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
color: var(--bim-icon-color, #ccc);
}
.walk-icon-btn:hover {
background: var(--bim-walk-btn-hover, rgba(255, 255, 255, 0.15));
}
.walk-icon-btn.active {
background: var(--bim-walk-btn-active, rgba(255, 255, 255, 0.3));
}
.walk-icon-btn svg {
width: 24px;
height: 24px;
}
/* 中间设置区 */
.walk-control-settings {
display: flex;
align-items: center;
gap: 16px;
flex: 1;
}
/* 速度控件 */
.walk-speed-control {
display: flex;
align-items: center;
gap: 12px;
}
.walk-speed-label {
color: var(--bim-text-color, #fff);
font-size: 14px;
white-space: nowrap;
}
.walk-speed-group {
display: flex;
align-items: center;
gap: 8px;
background: var(--bim-speed-group-bg, rgba(255, 255, 255, 0.1));
border-radius: 4px;
padding: 4px;
}
.walk-speed-btn {
width: 32px;
height: 32px;
background: var(--bim-speed-btn-bg, rgba(255, 255, 255, 0.1));
border: none;
border-radius: 4px;
color: var(--bim-text-color, #fff);
font-size: 18px;
cursor: pointer;
transition: background 0.2s;
}
.walk-speed-btn:hover {
background: var(--bim-speed-btn-hover, rgba(255, 255, 255, 0.2));
}
.walk-speed-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.walk-speed-display {
min-width: 40px;
text-align: center;
color: var(--bim-text-color, #fff);
font-size: 14px;
font-weight: bold;
}
/* 复选框 */
.walk-checkbox-wrapper {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.walk-checkbox {
width: 18px;
height: 18px;
cursor: pointer;
}
.walk-checkbox:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.walk-checkbox-label {
color: var(--bim-text-color, #fff);
font-size: 14px;
white-space: nowrap;
}
.walk-checkbox-wrapper input:disabled + .walk-checkbox-label {
opacity: 0.5;
}
/* 下拉框 */
.walk-select-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.walk-select-label {
color: var(--bim-text-color, #fff);
font-size: 14px;
white-space: nowrap;
}
.walk-select {
padding: 6px 12px;
background: var(--bim-select-bg, rgba(255, 255, 255, 0.1));
border: 1px solid var(--bim-select-border, rgba(255, 255, 255, 0.2));
border-radius: 4px;
color: var(--bim-text-color, #fff);
font-size: 14px;
cursor: pointer;
min-width: 120px;
}
.walk-select option {
background: var(--bim-select-option-bg, #333);
color: var(--bim-text-color, #fff);
}
/* 退出按钮 */
.walk-exit-btn {
padding: 10px 24px;
background: var(--bim-primary-color, #1890ff);
border: none;
border-radius: 6px;
color: #fff;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.walk-exit-btn:hover {
background: var(--bim-primary-hover, #40a9ff);
}

View File

@@ -0,0 +1,459 @@
import './index.css';
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { localeManager, t } from '../../services/locale';
import { themeManager } from '../../services/theme';
import type { WalkControlPanelOptions, WalkControlState, WalkControlMode, CharacterModel, WalkMode } from './types';
export class WalkControlPanel implements IBimComponent {
public element!: HTMLElement;
private options: WalkControlPanelOptions;
// 状态
private state: WalkControlState = {
mode: 'none',
isPlanViewActive: false,
speed: 1,
gravity: false,
collision: false,
characterModel: 'construction-worker',
walkMode: 'walk'
};
// DOM 引用 - 左侧按钮
private planViewBtn!: HTMLButtonElement;
private pathModeBtn!: HTMLButtonElement;
private walkModeBtn!: HTMLButtonElement;
// DOM 引用 - 中间设置区
private settingsContainer!: HTMLElement;
private speedControl!: HTMLElement;
private speedDecreaseBtn!: HTMLButtonElement;
private speedIncreaseBtn!: HTMLButtonElement;
private speedDisplay!: HTMLElement;
private gravityCheckbox!: HTMLInputElement;
private gravityLabel!: HTMLElement;
private collisionCheckbox!: HTMLInputElement;
private collisionLabel!: HTMLElement;
private characterModelSelect!: HTMLSelectElement;
private characterModelLabel!: HTMLElement;
private walkModeSelect!: HTMLSelectElement;
private walkModeLabel!: HTMLElement;
// DOM 引用 - 退出按钮
private exitBtn!: HTMLButtonElement;
// 国际化订阅
private unsubscribeLocale: (() => void) | null = null;
private unsubscribeTheme: (() => void) | null = null;
constructor(options: WalkControlPanelOptions = {}) {
this.options = options;
this.state.speed = options.defaultSpeed ?? 1;
this.state.gravity = options.defaultGravity ?? false;
this.state.collision = options.defaultCollision ?? false;
this.state.characterModel = options.defaultCharacterModel ?? 'construction-worker';
this.state.walkMode = options.defaultWalkMode ?? 'walk';
}
public init(): void {
this.element = this.createPanel();
this.updateSettingsView();
// 订阅
this.unsubscribeLocale = localeManager.subscribe(() => this.setLocales());
this.unsubscribeTheme = themeManager.subscribe((theme) => this.setTheme(theme));
this.setLocales();
this.setTheme(themeManager.getTheme());
}
// --- 公共方法 ---
public setPlanViewActive(active: boolean): void {
this.state.isPlanViewActive = active;
this.updateButtonStates();
}
public setPathModeActive(active: boolean): void {
// 只有当前是路径模式时,取消才设置为 none
// 避免在其他模式下被误设置为 none
if (!active && this.state.mode !== 'path') {
return;
}
const newMode: WalkControlMode = active ? 'path' : 'none';
this.setMode(newMode);
}
public getState(): WalkControlState {
return { ...this.state };
}
// --- 私有方法 ---
private createPanel(): HTMLElement {
const panel = document.createElement('div');
panel.className = 'walk-control-panel';
// 左侧按钮区
const leftButtons = this.createLeftButtons();
// 分割线1
const divider1 = document.createElement('div');
divider1.className = 'walk-divider';
// 中间设置区
this.settingsContainer = this.createSettingsContainer();
// 分割线2
const divider2 = document.createElement('div');
divider2.className = 'walk-divider';
// 右侧退出按钮
const exitBtn = this.createExitButton();
panel.appendChild(leftButtons);
panel.appendChild(divider1);
panel.appendChild(this.settingsContainer);
panel.appendChild(divider2);
panel.appendChild(exitBtn);
return panel;
}
private createLeftButtons(): HTMLElement {
const container = document.createElement('div');
container.className = 'walk-control-left';
this.planViewBtn = this.createIconButton('plan-view', () => {
this.state.isPlanViewActive = !this.state.isPlanViewActive;
this.updateButtonStates();
this.options.onPlanViewToggle?.(this.state.isPlanViewActive);
});
this.pathModeBtn = this.createIconButton('path', () => {
const newMode: WalkControlMode = this.state.mode === 'path' ? 'none' : 'path';
this.setMode(newMode);
this.options.onPathModeToggle?.(newMode === 'path');
});
this.walkModeBtn = this.createIconButton('walk', () => {
const newMode: WalkControlMode = this.state.mode === 'walk' ? 'none' : 'walk';
this.setMode(newMode);
this.options.onWalkModeToggle?.(newMode === 'walk');
});
container.appendChild(this.planViewBtn);
container.appendChild(this.pathModeBtn);
container.appendChild(this.walkModeBtn);
return container;
}
private createSettingsContainer(): HTMLElement {
const container = document.createElement('div');
container.className = 'walk-control-settings';
// 移动速度控件
this.speedControl = this.createSpeedControl();
// 重力复选框
const gravityWrapper = document.createElement('label');
gravityWrapper.className = 'walk-checkbox-wrapper walk-checkbox-gravity';
this.gravityCheckbox = document.createElement('input');
this.gravityCheckbox.type = 'checkbox';
this.gravityCheckbox.className = 'walk-checkbox';
this.gravityCheckbox.checked = this.state.gravity;
this.gravityCheckbox.addEventListener('change', () => {
this.state.gravity = this.gravityCheckbox.checked;
this.options.onGravityToggle?.(this.state.gravity);
});
this.gravityLabel = document.createElement('span');
this.gravityLabel.className = 'walk-checkbox-label';
gravityWrapper.appendChild(this.gravityCheckbox);
gravityWrapper.appendChild(this.gravityLabel);
// 碰撞复选框
const collisionWrapper = document.createElement('label');
collisionWrapper.className = 'walk-checkbox-wrapper walk-checkbox-collision';
this.collisionCheckbox = document.createElement('input');
this.collisionCheckbox.type = 'checkbox';
this.collisionCheckbox.className = 'walk-checkbox';
this.collisionCheckbox.checked = this.state.collision;
this.collisionCheckbox.addEventListener('change', () => {
this.state.collision = this.collisionCheckbox.checked;
this.options.onCollisionToggle?.(this.state.collision);
});
this.collisionLabel = document.createElement('span');
this.collisionLabel.className = 'walk-checkbox-label';
collisionWrapper.appendChild(this.collisionCheckbox);
collisionWrapper.appendChild(this.collisionLabel);
// 角色模型选择
const characterWrapper = document.createElement('div');
characterWrapper.className = 'walk-select-wrapper walk-select-wrapper-character-model';
this.characterModelLabel = document.createElement('label');
this.characterModelLabel.className = 'walk-select-label';
this.characterModelSelect = document.createElement('select');
this.characterModelSelect.className = 'walk-select walk-select-character-model';
this.characterModelSelect.addEventListener('change', () => {
this.state.characterModel = this.characterModelSelect.value as CharacterModel;
this.options.onCharacterModelChange?.(this.state.characterModel);
});
characterWrapper.appendChild(this.characterModelLabel);
characterWrapper.appendChild(this.characterModelSelect);
// 行走模式选择
const walkModeWrapper = document.createElement('div');
walkModeWrapper.className = 'walk-select-wrapper walk-select-wrapper-walk-mode';
this.walkModeLabel = document.createElement('label');
this.walkModeLabel.className = 'walk-select-label';
this.walkModeSelect = document.createElement('select');
this.walkModeSelect.className = 'walk-select walk-select-walk-mode';
this.walkModeSelect.addEventListener('change', () => {
this.state.walkMode = this.walkModeSelect.value as WalkMode;
this.options.onWalkModeChange?.(this.state.walkMode);
});
walkModeWrapper.appendChild(this.walkModeLabel);
walkModeWrapper.appendChild(this.walkModeSelect);
// 添加所有控件
// 注意:顺序为 速度、角色模型、行走模式、重力、碰撞
// 这样在漫游模式下显示的顺序就是:角色模型、行走模式、重力、碰撞
container.appendChild(this.speedControl);
container.appendChild(characterWrapper);
container.appendChild(walkModeWrapper);
container.appendChild(gravityWrapper);
container.appendChild(collisionWrapper);
return container;
}
private createSpeedControl(): HTMLElement {
const container = document.createElement('div');
container.className = 'walk-speed-control';
const label = document.createElement('label');
label.className = 'walk-speed-label';
label.textContent = t('walkControl.speed');
const controlGroup = document.createElement('div');
controlGroup.className = 'walk-speed-group';
// 减速按钮
this.speedDecreaseBtn = document.createElement('button');
this.speedDecreaseBtn.className = 'walk-speed-btn';
this.speedDecreaseBtn.textContent = '-';
this.speedDecreaseBtn.addEventListener('click', () => {
if (this.state.speed > 1) {
this.state.speed--;
this.updateSpeedDisplay();
this.options.onSpeedChange?.(this.state.speed);
}
});
// 速度显示
this.speedDisplay = document.createElement('div');
this.speedDisplay.className = 'walk-speed-display';
this.speedDisplay.textContent = `${this.state.speed}X`;
// 加速按钮
this.speedIncreaseBtn = document.createElement('button');
this.speedIncreaseBtn.className = 'walk-speed-btn';
this.speedIncreaseBtn.textContent = '+';
this.speedIncreaseBtn.addEventListener('click', () => {
if (this.state.speed < 10) {
this.state.speed++;
this.updateSpeedDisplay();
this.options.onSpeedChange?.(this.state.speed);
}
});
controlGroup.appendChild(this.speedDecreaseBtn);
controlGroup.appendChild(this.speedDisplay);
controlGroup.appendChild(this.speedIncreaseBtn);
container.appendChild(label);
container.appendChild(controlGroup);
return container;
}
private createIconButton(type: string, onClick: () => void): HTMLButtonElement {
const btn = document.createElement('button');
btn.className = `walk-icon-btn walk-icon-btn-${type}`;
btn.innerHTML = this.getIconSVG(type);
btn.addEventListener('click', onClick);
return btn;
}
private createExitButton(): HTMLButtonElement {
const btn = document.createElement('button');
btn.className = 'walk-exit-btn';
btn.addEventListener('click', () => {
this.options.onExit?.();
});
this.exitBtn = btn;
return btn;
}
private setMode(mode: WalkControlMode): void {
const oldMode = this.state.mode;
// 如果从walk模式切换到其他模式触发walk关闭事件
if (oldMode === 'walk' && mode !== 'walk') {
this.options.onWalkModeToggle?.(false);
}
// 如果从path模式切换到其他模式触发path关闭事件
if (oldMode === 'path' && mode !== 'path') {
this.options.onPathModeToggle?.(false);
}
this.state.mode = mode;
// 路径模式:禁用重力和碰撞
if (mode === 'path') {
this.state.gravity = false;
this.state.collision = false;
this.gravityCheckbox.checked = false;
this.gravityCheckbox.disabled = true;
this.collisionCheckbox.checked = false;
this.collisionCheckbox.disabled = true;
} else {
this.gravityCheckbox.disabled = false;
this.collisionCheckbox.disabled = false;
}
this.updateButtonStates();
this.updateSettingsView();
this.updateSpeedButtonStates();
}
private updateButtonStates(): void {
// 平面图按钮
this.planViewBtn.classList.toggle('active', this.state.isPlanViewActive);
// 路径漫游按钮
this.pathModeBtn.classList.toggle('active', this.state.mode === 'path');
// 漫游按钮
this.walkModeBtn.classList.toggle('active', this.state.mode === 'walk');
}
private updateSettingsView(): void {
// 根据模式显示/隐藏不同的控件
const speedWrapper = this.speedControl;
const gravityWrapper = this.gravityCheckbox.parentElement!;
const collisionWrapper = this.collisionCheckbox.parentElement!;
const characterWrapper = this.characterModelSelect.parentElement!;
const walkModeWrapper = this.walkModeSelect.parentElement!;
if (this.state.mode === 'walk') {
// 漫游模式:隐藏速度,显示模型、行走模式、重力、碰撞
speedWrapper.style.display = 'none';
gravityWrapper.style.display = 'flex';
collisionWrapper.style.display = 'flex';
characterWrapper.style.display = 'flex';
walkModeWrapper.style.display = 'flex';
} else {
// 默认或路径模式:显示速度、重力、碰撞,隐藏模型和行走模式
speedWrapper.style.display = 'flex';
gravityWrapper.style.display = 'flex';
collisionWrapper.style.display = 'flex';
characterWrapper.style.display = 'none';
walkModeWrapper.style.display = 'none';
}
}
private updateSpeedDisplay(): void {
this.speedDisplay.textContent = `${this.state.speed}X`;
this.updateSpeedButtonStates();
}
private updateSpeedButtonStates(): void {
this.speedDecreaseBtn.disabled = this.state.speed <= 1;
this.speedIncreaseBtn.disabled = this.state.speed >= 10;
}
private getIconSVG(type: string): string {
const icons: Record<string, string> = {
'plan-view': '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z"/></svg>',
'path': '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>',
'walk': '<svg viewBox="0 0 24 24" fill="currentColor"><path 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>'
};
return icons[type] || '';
}
public setLocales(): void {
// 更新速度标签
const speedLabel = this.speedControl.querySelector('.walk-speed-label');
if (speedLabel) {
speedLabel.textContent = t('walkControl.speed');
}
// 更新复选框标签
this.gravityLabel.textContent = t('walkControl.gravity');
this.collisionLabel.textContent = t('walkControl.collision');
// 更新角色模型下拉框
this.characterModelLabel.textContent = t('walkControl.characterModel.label');
this.characterModelSelect.innerHTML = '';
const constructionWorkerOption = document.createElement('option');
constructionWorkerOption.value = 'construction-worker';
constructionWorkerOption.textContent = t('walkControl.characterModel.constructionWorker');
constructionWorkerOption.selected = this.state.characterModel === 'construction-worker';
this.characterModelSelect.appendChild(constructionWorkerOption);
const officeMaleOption = document.createElement('option');
officeMaleOption.value = 'office-male';
officeMaleOption.textContent = t('walkControl.characterModel.officeMale');
officeMaleOption.selected = this.state.characterModel === 'office-male';
this.characterModelSelect.appendChild(officeMaleOption);
// 更新行走模式下拉框
this.walkModeLabel.textContent = t('walkControl.walkMode.label');
this.walkModeSelect.innerHTML = '';
const walkOption = document.createElement('option');
walkOption.value = 'walk';
walkOption.textContent = t('walkControl.walkMode.walk');
walkOption.selected = this.state.walkMode === 'walk';
this.walkModeSelect.appendChild(walkOption);
const runOption = document.createElement('option');
runOption.value = 'run';
runOption.textContent = t('walkControl.walkMode.run');
runOption.selected = this.state.walkMode === 'run';
this.walkModeSelect.appendChild(runOption);
// 更新退出按钮
this.exitBtn.textContent = t('walkControl.exit');
}
public setTheme(theme: ThemeConfig): void {
if (!this.element) return;
const style = this.element.style;
style.setProperty('--bim-walk-control-bg', theme.panelBackground ?? 'rgba(0, 0, 0, 0.8)');
style.setProperty('--bim-walk-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.15)');
style.setProperty('--bim-walk-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.3)');
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
style.setProperty('--bim-primary-hover', theme.primaryHover ?? '#40a9ff');
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
style.setProperty('--bim-text-color', theme.textPrimary ?? '#fff');
style.setProperty('--bim-divider-color', theme.border ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-speed-group-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-speed-btn-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-speed-btn-hover', theme.componentActive ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-select-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
style.setProperty('--bim-select-border', theme.border ?? 'rgba(255, 255, 255, 0.2)');
style.setProperty('--bim-select-option-bg', theme.panelBackground ?? '#333');
}
public destroy(): void {
this.unsubscribeLocale?.();
this.unsubscribeTheme?.();
if (this.element && this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
}
}

View File

@@ -0,0 +1,69 @@
/**
* 漫游控制模式
*/
export type WalkControlMode = 'none' | 'path' | 'walk';
/**
* 角色模型类型
*/
export type CharacterModel = 'office-male' | 'construction-worker';
/**
* 行走模式类型
*/
export type WalkMode = 'walk' | 'run';
/**
* 漫游控制面板配置选项
*/
export interface WalkControlPanelOptions {
/** 平面图切换回调 */
onPlanViewToggle?: (isActive: boolean) => void;
/** 路径漫游模式切换回调 */
onPathModeToggle?: (isActive: boolean) => void;
/** 漫游模式切换回调 */
onWalkModeToggle?: (isActive: boolean) => void;
/** 速度变化回调 */
onSpeedChange?: (speed: number) => void;
/** 重力切换回调 */
onGravityToggle?: (enabled: boolean) => void;
/** 碰撞切换回调 */
onCollisionToggle?: (enabled: boolean) => void;
/** 角色模型变化回调 */
onCharacterModelChange?: (model: CharacterModel) => void;
/** 行走模式变化回调 */
onWalkModeChange?: (mode: WalkMode) => void;
/** 退出回调 */
onExit?: () => void;
/** 默认速度 (0-100) */
defaultSpeed?: number;
/** 默认重力状态 */
defaultGravity?: boolean;
/** 默认碰撞状态 */
defaultCollision?: boolean;
/** 默认角色模型 */
defaultCharacterModel?: CharacterModel;
/** 默认行走模式 */
defaultWalkMode?: WalkMode;
}
/**
* 漫游控制状态
*/
export interface WalkControlState {
/** 当前模式 */
mode: WalkControlMode;
/** 平面图是否激活 */
isPlanViewActive: boolean;
/** 移动速度 (0-100) */
speed: number;
/** 重力是否启用 */
gravity: boolean;
/** 碰撞是否启用 */
collision: boolean;
/** 当前角色模型 */
characterModel: CharacterModel;
/** 当前行走模式 */
walkMode: WalkMode;
}

View File

@@ -0,0 +1,54 @@
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { localeManager } from '../../services/locale';
import { themeManager } from '../../services/theme';
/**
* 路径漫游面板组件(暂时空内容)
*/
export class WalkPathPanel implements IBimComponent {
public element!: HTMLElement;
private unsubscribeLocale: (() => void) | null = null;
private unsubscribeTheme: (() => void) | null = null;
constructor() {
// 暂时无配置
}
public init(): void {
this.element = this.createPanel();
// 订阅
this.unsubscribeLocale = localeManager.subscribe(() => this.setLocales());
this.unsubscribeTheme = themeManager.subscribe((theme) => this.setTheme(theme));
this.setLocales();
this.setTheme(themeManager.getTheme());
}
private createPanel(): HTMLElement {
const panel = document.createElement('div');
panel.className = 'walk-path-panel';
panel.style.padding = '20px';
panel.style.color = 'var(--bim-text-color, #fff)';
panel.textContent = '路径漫游内容待实现';
return panel;
}
public setLocales(): void {
// 更新文本
}
public setTheme(_theme: ThemeConfig): void {
// 应用主题
}
public destroy(): void {
this.unsubscribeLocale?.();
this.unsubscribeTheme?.();
if (this.element && this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
}
}

View File

@@ -0,0 +1,54 @@
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { localeManager } from '../../services/locale';
import { themeManager } from '../../services/theme';
/**
* 平面图面板组件(暂时空内容)
*/
export class WalkPlanViewPanel implements IBimComponent {
public element!: HTMLElement;
private unsubscribeLocale: (() => void) | null = null;
private unsubscribeTheme: (() => void) | null = null;
constructor() {
// 暂时无配置
}
public init(): void {
this.element = this.createPanel();
// 订阅
this.unsubscribeLocale = localeManager.subscribe(() => this.setLocales());
this.unsubscribeTheme = themeManager.subscribe((theme) => this.setTheme(theme));
this.setLocales();
this.setTheme(themeManager.getTheme());
}
private createPanel(): HTMLElement {
const panel = document.createElement('div');
panel.className = 'walk-plan-view-panel';
panel.style.padding = '20px';
panel.style.color = 'var(--bim-text-color, #fff)';
panel.textContent = '平面图内容待实现';
return panel;
}
public setLocales(): void {
// 更新文本
}
public setTheme(_theme: ThemeConfig): void {
// 应用主题
}
public destroy(): void {
this.unsubscribeLocale?.();
this.unsubscribeTheme?.();
if (this.element && this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
}
}

View File

@@ -15,6 +15,9 @@ export const enUS: TranslationDictionary = {
location: 'Location',
setting: 'Settings',
walk: 'Walk',
map: 'Map',
property: 'Property',
fullscreen: 'Fullscreen',
walkPerson: 'Person',
walkBird: 'Bird Eye',
walkMenu: 'Menu',
@@ -139,5 +142,27 @@ export const enUS: TranslationDictionary = {
y: 'Y',
z: 'Z'
}
},
walkControl: {
speed: 'Speed:',
gravity: 'Gravity',
collision: 'Collision',
characterModel: {
label: 'Construction Worker',
constructionWorker: 'Construction Worker',
officeMale: 'Office Male'
},
walkMode: {
label: 'Walk Mode',
walk: 'Walk',
run: 'Run'
},
exit: 'Exit',
path: {
dialogTitle: 'Path Walk'
}
},
map: {
dialogTitle: 'Map'
}
};

View File

@@ -18,6 +18,9 @@ export interface TranslationDictionary {
location: string;
setting: string;
walk: string;
map: string;
property: string;
fullscreen: string;
walkMenu: string;
walkPerson: string;
walkBird: string;
@@ -163,6 +166,28 @@ export interface TranslationDictionary {
z: string;
};
};
walkControl: {
speed: string;
gravity: string;
collision: string;
characterModel: {
label: string;
constructionWorker: string;
officeMale: string;
};
walkMode: {
label: string;
walk: string;
run: string;
};
exit: string;
path: {
dialogTitle: string;
};
};
map: {
dialogTitle: string;
};
}
/**

View File

@@ -15,6 +15,9 @@ export const zhCN: TranslationDictionary = {
location: '定位',
setting: '设置',
walk: '漫游',
map: '地图',
property: '构件详情',
fullscreen: '全屏',
walkMenu: '漫游菜单',
walkPerson: '第一人称',
walkBird: '第三人称',
@@ -139,5 +142,27 @@ export const zhCN: TranslationDictionary = {
y: 'Y',
z: 'Z'
}
},
walkControl: {
speed: '移动速度:',
gravity: '重力',
collision: '碰撞',
characterModel: {
label: '建筑工人',
constructionWorker: '建筑工人',
officeMale: '办公室男性'
},
walkMode: {
label: '行走模式',
walk: '行走模式',
run: '奔跑模式'
},
exit: '退出',
path: {
dialogTitle: '路径漫游'
}
},
map: {
dialogTitle: '地图'
}
};

View File

@@ -0,0 +1,107 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
import { MapPanel } from '../components/map-panel';
/**
* 地图弹窗管理器(独立通用组件)
*/
export class MapDialogManager extends BimComponent {
private dialogId = 'map-dialog';
private dialog: BimDialog | null = null;
private panel: MapPanel | null = null;
constructor(engine: BimEngine) {
super(engine);
}
public init(): void {
// 可以在这里监听事件
}
/**
* 显示弹窗
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
// 如果已打开,不重复打开
if (this.isOpen()) {
return;
}
// 创建面板
this.panel = new MapPanel();
this.panel.init();
const dialogWidth = 300;
const dialogHeight = 400;
const paddingLeft = 20;
const paddingBottom = 20;
const container = this.engine.container;
const containerHeight = container.clientHeight;
// 左下角left: 20px, bottom: 20px
const x = paddingLeft;
const y = containerHeight - dialogHeight - paddingBottom;
this.dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'map.dialogTitle',
width: dialogWidth,
height: dialogHeight,
position: { x, y },
draggable: true,
resizable: false,
content: this.panel.element,
onClose: () => {
this.hide();
}
});
this.dialog.init();
// 触发地图打开事件
this.emit('map:opened', {});
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
// 触发地图关闭事件
this.emit('map:closed', {});
}
/**
* 检查地图是否打开
*/
public isOpen(): boolean {
return this.dialog !== null;
}
/**
* 销毁弹窗和面板
*/
public destroy(): void {
// 先保存 dialog 引用,避免在回调中重复调用
const dialog = this.dialog;
// 立即清空引用,防止递归
this.dialog = null;
// 关闭弹窗
if (dialog) {
dialog.destroy();
}
// 销毁面板
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
}

View File

@@ -10,6 +10,7 @@ import { BimTab } from '../components/tab/index';
*/
export class PropertyPanelManager extends BimComponent {
private dialogId = 'property-panel-dialog';
private dialog: any = null; // 保存 dialog 引用
constructor(engine: BimEngine) {
super(engine);
@@ -31,12 +32,17 @@ export class PropertyPanelManager extends BimComponent {
return;
}
// 如果已打开,不重复打开
if (this.isOpen()) {
return;
}
// 1. 创建弹窗
const width = 360; // 稍微加宽一点以容纳 Tab
const x = document.body.clientWidth - width - 40;
console.log('x', x)
const dialog = this.engine.dialog.create({
this.dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'panel.property.title', // '构件详情'
content: '',
@@ -44,7 +50,10 @@ export class PropertyPanelManager extends BimComponent {
height: '500px',
position: { x, y: 20 },
showMask: false,
resizable: true
resizable: true,
onClose: () => {
this.hide();
}
} as any);
// 2. 创建内容容器
@@ -53,7 +62,7 @@ export class PropertyPanelManager extends BimComponent {
contentContainer.style.display = 'flex';
contentContainer.style.flexDirection = 'column';
dialog.setContent(contentContainer);
this.dialog.setContent(contentContainer);
// 3. 创建标签页组件
const tab = new BimTab({
@@ -194,7 +203,24 @@ export class PropertyPanelManager extends BimComponent {
return container;
}
/**
* 检查属性面板是否打开
*/
public isOpen(): boolean {
return this.dialog !== null;
}
/**
* 隐藏属性面板
*/
public hide(): void {
if (this.dialog) {
this.dialog.destroy();
this.dialog = null;
}
}
public destroy(): void {
// 如果有需要清理的资源
this.hide();
}
}

View File

@@ -68,4 +68,29 @@ export class ToolbarManager extends BimComponent {
}
public setBackgroundColor(color: string) { this.toolbar?.setBackgroundColor(color); }
public setColors(colors: ButtonGroupColors) { this.toolbar?.setColors(colors); }
/**
* 隐藏工具栏
*/
public hide(): void {
if (this.toolbarContainer) {
this.toolbarContainer.style.display = 'none';
}
}
/**
* 显示工具栏
*/
public show(): void {
if (this.toolbarContainer) {
this.toolbarContainer.style.display = '';
}
}
/**
* 获取工具栏容器
*/
public getContainer(): HTMLElement | null {
return this.toolbarContainer;
}
}

View File

@@ -0,0 +1,151 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { WalkControlPanel } from '../components/walk-control-panel';
import { WalkPathDialogManager } from './walk-path-dialog-manager';
/**
* 漫游控制管理器
*/
export class WalkControlManager extends BimComponent {
public panel: WalkControlPanel | null = null;
private pathManager: WalkPathDialogManager | null = null;
constructor(engine: BimEngine) {
super(engine);
}
public init(): void {
// 初始化子 manager
this.pathManager = new WalkPathDialogManager(this.engine);
this.pathManager.init();
}
/**
* 显示漫游控制面板
*/
public show(): void {
if (!this.engine.toolbar) {
console.warn('Toolbar not initialized');
return;
}
// 隐藏 toolbar
this.engine.toolbar.hide();
// 创建漫游控制面板
this.panel = new WalkControlPanel({
onPlanViewToggle: (isActive) => {
console.log('[WalkControl] 地图:', isActive);
if (isActive) {
this.engine.map?.show();
} else {
this.engine.map?.hide();
}
// 触发事件
this.emit('walk:plan-view-toggle', { isActive });
},
onPathModeToggle: (isActive) => {
console.log('[WalkControl] 路径漫游:', isActive);
if (isActive) {
this.pathManager?.show();
} else {
this.pathManager?.hide();
}
// 触发事件
this.emit('walk:path-mode-toggle', { isActive });
},
onWalkModeToggle: (isActive) => {
console.log('[WalkControl] 漫游模式:', isActive);
// 切换到漫游模式时,关闭路径漫游弹窗
if (isActive) {
this.pathManager?.hide();
}
// 触发事件
this.emit('walk:walk-mode-toggle', { isActive });
},
onSpeedChange: (speed) => {
console.log('[WalkControl] 速度变化:', speed);
// 触发事件
this.emit('walk:speed-change', { speed });
},
onGravityToggle: (enabled) => {
console.log('[WalkControl] 重力:', enabled);
// 触发事件
this.emit('walk:gravity-toggle', { enabled });
},
onCollisionToggle: (enabled) => {
console.log('[WalkControl] 碰撞:', enabled);
// 触发事件
this.emit('walk:collision-toggle', { enabled });
},
onCharacterModelChange: (model) => {
console.log('[WalkControl] 角色模型:', model);
// TODO: 实现角色模型变化逻辑
},
onWalkModeChange: (mode) => {
console.log('[WalkControl] 行走模式:', mode);
// TODO: 实现行走模式变化逻辑
},
onExit: () => {
this.hide();
}
});
this.panel.init();
// 如果地图已经打开,同步按钮状态
if (this.engine.map?.isOpen()) {
this.panel.setPlanViewActive(true);
}
// 监听地图事件,同步漫游面板中的地图按钮状态
this.engine.on('map:opened', () => {
this.panel?.setPlanViewActive(true);
});
this.engine.on('map:closed', () => {
this.panel?.setPlanViewActive(false);
});
// 将面板添加到主容器中定位在底部中间类似toolbar的位置
if (this.engine.container) {
// 添加定位样式
this.panel.element.style.position = 'absolute';
this.panel.element.style.bottom = '20px';
this.panel.element.style.left = '50%';
this.panel.element.style.transform = 'translateX(-50%)';
this.panel.element.style.zIndex = '1000';
this.engine.container.appendChild(this.panel.element);
} else {
console.warn('[WalkControlManager] Container not found');
}
}
/**
* 隐藏漫游控制面板
*/
public hide(): void {
// 关闭路径漫游弹窗(但不关闭地图,因为地图可能是用户单独打开的)
this.pathManager?.hide();
// 销毁面板
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
// 显示 toolbar
if (this.engine.toolbar) {
this.engine.toolbar.show();
}
}
/**
* 销毁管理器
*/
public destroy(): void {
this.hide();
this.pathManager?.destroy();
this.pathManager = null;
}
}

View File

@@ -0,0 +1,97 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
import { WalkPathPanel } from '../components/walk-path-panel';
/**
* 路径漫游弹窗管理器
*/
export class WalkPathDialogManager extends BimComponent {
private dialogId = 'walk-path-dialog';
private dialog: BimDialog | null = null;
private panel: WalkPathPanel | null = null;
constructor(engine: BimEngine) {
super(engine);
}
public init(): void {
// 可以在这里监听事件
}
/**
* 显示弹窗
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
// 如果已打开,先销毁
this.destroy();
// 创建面板(暂时空内容)
this.panel = new WalkPathPanel();
this.panel.init();
const dialogWidth = 300;
const dialogHeight = 400;
const paddingRight = 20;
const container = this.engine.container;
const containerHeight = container.clientHeight;
const containerWidth = container.clientWidth;
// 右边中间right: 20px, 垂直居中
const x = containerWidth - dialogWidth - paddingRight;
const y = (containerHeight - dialogHeight) / 2;
this.dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'walkControl.path.dialogTitle',
width: dialogWidth,
height: dialogHeight,
position: { x, y },
draggable: true,
resizable: false,
content: this.panel.element,
onClose: () => {
// 通知主控制面板更新状态
if (this.engine.walkControl && this.engine.walkControl.panel) {
this.engine.walkControl.panel.setPathModeActive(false);
}
this.hide();
}
});
this.dialog.init();
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
}
/**
* 销毁弹窗和面板
*/
public destroy(): void {
// 先保存 dialog 引用,避免在回调中重复调用
const dialog = this.dialog;
// 立即清空引用,防止递归
this.dialog = null;
// 关闭弹窗
if (dialog) {
dialog.destroy();
}
// 销毁面板
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
}

View File

@@ -0,0 +1,97 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
import { WalkPlanViewPanel } from '../components/walk-plan-view-panel';
/**
* 平面图弹窗管理器
*/
export class WalkPlanViewDialogManager extends BimComponent {
private dialogId = 'walk-plan-view-dialog';
private dialog: BimDialog | null = null;
private panel: WalkPlanViewPanel | null = null;
constructor(engine: BimEngine) {
super(engine);
}
public init(): void {
// 可以在这里监听事件
}
/**
* 显示弹窗
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
// 如果已打开,先销毁
this.destroy();
// 创建面板(暂时空内容)
this.panel = new WalkPlanViewPanel();
this.panel.init();
const dialogWidth = 300;
const dialogHeight = 400;
const paddingLeft = 20;
const paddingBottom = 20;
const container = this.engine.container;
const containerHeight = container.clientHeight;
// 左下角left: 20px, bottom: 20px
const x = paddingLeft;
const y = containerHeight - dialogHeight - paddingBottom;
this.dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'walkControl.planView.dialogTitle',
width: dialogWidth,
height: dialogHeight,
position: { x, y },
draggable: true,
resizable: false,
content: this.panel.element,
onClose: () => {
// 通知主控制面板更新状态
if (this.engine.walkControl && this.engine.walkControl.panel) {
this.engine.walkControl.panel.setPlanViewActive(false);
}
this.hide();
}
});
this.dialog.init();
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
}
/**
* 销毁弹窗和面板
*/
public destroy(): void {
// 先保存 dialog 引用,避免在回调中重复调用
const dialog = this.dialog;
// 立即清空引用,防止递归
this.dialog = null;
// 关闭弹窗
if (dialog) {
dialog.destroy();
}
// 销毁面板
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
}

View File

@@ -29,6 +29,17 @@ export interface EngineEvents {
'sys:locale-changed': { locale: string };
// 漫游控制事件
'walk:path-mode-toggle': { isActive: boolean };
'walk:walk-mode-toggle': { isActive: boolean };
'walk:plan-view-toggle': { isActive: boolean };
'walk:speed-change': { speed: number };
'walk:gravity-toggle': { enabled: boolean };
'walk:collision-toggle': { enabled: boolean };
// 地图事件
'map:opened': {};
'map:closed': {};
}