初始化
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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"。如果是自动触发,请确保由用户点击触发。');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
34
src/components/button-group/toolbar/buttons/map/index.ts
Normal file
34
src/components/button-group/toolbar/buttons/map/index.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
|
||||
49
src/components/map-panel/index.ts
Normal file
49
src/components/map-panel/index.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
186
src/components/walk-control-panel/index.css
Normal file
186
src/components/walk-control-panel/index.css
Normal 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);
|
||||
}
|
||||
459
src/components/walk-control-panel/index.ts
Normal file
459
src/components/walk-control-panel/index.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/components/walk-control-panel/types.ts
Normal file
69
src/components/walk-control-panel/types.ts
Normal 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;
|
||||
}
|
||||
54
src/components/walk-path-panel/index.ts
Normal file
54
src/components/walk-path-panel/index.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/components/walk-plan-view-panel/index.ts
Normal file
54
src/components/walk-plan-view-panel/index.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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: '地图'
|
||||
}
|
||||
};
|
||||
|
||||
107
src/managers/map-dialog-manager.ts
Normal file
107
src/managers/map-dialog-manager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
151
src/managers/walk-control-manager.ts
Normal file
151
src/managers/walk-control-manager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
97
src/managers/walk-path-dialog-manager.ts
Normal file
97
src/managers/walk-path-dialog-manager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/managers/walk-plan-view-dialog-manager.ts
Normal file
97
src/managers/walk-plan-view-dialog-manager.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,22 @@ export interface EngineEvents {
|
||||
|
||||
|
||||
// 系统事件
|
||||
|
||||
|
||||
'sys:theme-changed': { theme: string };
|
||||
|
||||
|
||||
'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': {};
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user