refactor: 重构 Manager 架构,引入 ManagerRegistry 和 BaseManager 基类

- 新增 ManagerRegistry 单例注册表,统一管理所有 Manager 实例
- 新增 BaseManager 基类,自动管理事件订阅清理
- 新增 BaseDialogManager 基类,统一对话框生命周期管理
- 重构 15 个 Manager 使用新基类
- 重构 Toolbar 按钮和 Menu 按钮移除 engine 参数依赖
- 删除 BimComponent 基类(已不再使用)
- 为所有 Manager 和核心模块添加中文 JSDoc 注释
This commit is contained in:
yuding
2026-01-22 15:23:57 +08:00
parent f2460fb981
commit 31b60e84ce
47 changed files with 5580 additions and 5341 deletions

View File

@@ -1,46 +1,65 @@
/**
* 按钮组管理器
* 负责创建和管理按钮组实例
*/
import { BimButtonGroup } from '../components/button-group';
import type { ButtonGroupOptions } from '../components/button-group/index.type';
import type { ThemeConfig } from '../themes/types';
import { BimComponent } from '../core/component';
import type { BimEngine } from '../bim-engine';
import { BaseManager } from '../core/base-manager';
/**
* 通用按钮组管理器 (ButtonGroupManager)
* 负责创建和管理通用的按钮组实例。
* 按钮组管理器
* 统一管理多个按钮组的创建、主题更新和销毁
*/
export class ButtonGroupManager extends BimComponent {
export class ButtonGroupManager extends BaseManager {
/** 按钮组映射表 */
private groups: Map<string, BimButtonGroup> = new Map();
/** 容器元素 */
private container: HTMLElement;
constructor(engine: BimEngine, container: HTMLElement) {
super(engine);
constructor(container: HTMLElement) {
super();
this.container = container;
}
/**
* 创建按钮组
* @param id 按钮组 ID
* @param options 按钮组配置
* @returns 按钮组实例
*/
public create(id: string, options: Omit<ButtonGroupOptions, 'container'>): BimButtonGroup {
const group = new BimButtonGroup({
container: this.container,
...options
});
// @ts-ignore
group.setEngine(this.engine);
group.init();
this.groups.set(id, group);
return group;
}
/**
* 获取按钮组
* @param id 按钮组 ID
* @returns 按钮组实例
*/
public get(id: string): BimButtonGroup | undefined {
return this.groups.get(id);
}
/**
* 更新所有按钮组的主题
* @param theme 主题配置
*/
public updateTheme(theme: ThemeConfig) {
this.groups.forEach(group => group.setTheme(theme));
}
/** 销毁管理器和所有按钮组 */
public destroy() {
this.groups.forEach(group => group.destroy());
this.groups.clear();
super.destroy();
}
}

View File

@@ -1,15 +1,18 @@
import type {ButtonGroupColors, ButtonConfig} from '../components/button-group/index.type';
import {Toolbar} from '../components/button-group/toolbar';
import {BimComponent} from '../core/component';
import type {BimEngine} from '../bim-engine';
import {BimButtonGroup} from "../components/button-group";
import {BimTree} from "../components/tree";
import {TreeNodeConfig} from "../components/tree/types.ts";
import {BimDialog} from "../components/dialog";
import {BimTab} from "../components/tab";
import {getIcon} from "../utils/icon-manager";
/**
* 构件树管理器
* 负责管理构件树按钮和构件树对话框
*/
import type { ButtonGroupColors, ButtonConfig } from '../components/button-group/index.type';
import { BaseManager } from '../core/base-manager';
import { BimButtonGroup } from '../components/button-group';
import { BimTree } from '../components/tree';
import { TreeNodeConfig } from '../components/tree/types';
import { BimDialog } from '../components/dialog';
import { BimTab } from '../components/tab';
import { getIcon } from '../utils/icon-manager';
const MOCK_STRUCT_DATA: TreeNodeConfig[] =[
/** 模拟的构件树数据 */
const MOCK_STRUCT_DATA: TreeNodeConfig[] = [
{
id: 'root',
label: '全部构件',
@@ -20,10 +23,10 @@ const MOCK_STRUCT_DATA: TreeNodeConfig[] =[
id: 'level-1',
label: '一层',
expanded: false,
icon:'<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M20.73 16.52V7.59a.7.7 0 0 0-.08-.33a.74.74 0 0 0-.36-.36l-8-3.58a.75.75 0 0 0-.62 0l-8 3.58a.8.8 0 0 0-.44.69v8.82a.83.83 0 0 0 .44.69l8 3.58a.72.72 0 0 0 .62 0l8-3.58a.77.77 0 0 0 .44-.58m-16-7.78l6.5 2.92v7.18l-6.5-2.91Zm8 2.92l6.5-2.92v7.19l-6.5 2.91ZM12 4.82l6.17 2.77L12 10.35L5.83 7.59Z"/></svg>',
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M20.73 16.52V7.59a.7.7 0 0 0-.08-.33a.74.74 0 0 0-.36-.36l-8-3.58a.75.75 0 0 0-.62 0l-8 3.58a.8.8 0 0 0-.44.69v8.82a.83.83 0 0 0 .44.69l8 3.58a.72.72 0 0 0 .62 0l8-3.58a.77.77 0 0 0 .44-.58m-16-7.78l6.5 2.92v7.18l-6.5-2.91Zm8 2.92l6.5-2.92v7.19l-6.5 2.91ZM12 4.82l6.17 2.77L12 10.35L5.83 7.59Z"/></svg>',
clickAction: 'expand',
children: [
{ id: 'l1-wall', label: '墙体128'},
{ id: 'l1-wall', label: '墙体128' },
{ id: 'l1-column', label: '柱46' },
{ id: 'l1-beam', label: '梁82' },
{ id: 'l1-slab', label: '楼板12' },
@@ -75,23 +78,27 @@ const MOCK_STRUCT_DATA: TreeNodeConfig[] =[
];
/**
* 底部工具栏管理器 (ToolbarManager)
* 仅负责管理底部工具栏实例。
* 构件树管理器
* 管理左上角的构件树按钮和对话框
*/
export class ConstructTreeManagerBtn extends BimComponent {
private toolbar: Toolbar | null = null;
export class ConstructTreeManagerBtn extends BaseManager {
/** 按钮组实例 */
private toolbar: BimButtonGroup | null = null;
/** 按钮容器元素 */
private toolbarContainer: HTMLElement | null = null;
/** 主容器元素 */
private container: HTMLElement;
/** 构件树对话框实例 */
private dialog: BimDialog | null = null;
constructor(engine: BimEngine, container: HTMLElement) {
super(engine);
constructor(container: HTMLElement) {
super();
this.container = container;
this.init();
}
/** 初始化按钮 */
private init() {
// 创建底部工具栏专用容器
this.toolbarContainer = document.createElement('div');
this.toolbarContainer.id = 'bim-construct-tree';
this.container.appendChild(this.toolbarContainer);
@@ -99,12 +106,11 @@ export class ConstructTreeManagerBtn extends BimComponent {
container: this.toolbarContainer,
showLabel: false,
direction: 'column',
position: 'top-left', // 底部居中
align: 'vertical', // 图标在上
expand: 'up' // 向上展开
position: 'top-left',
align: 'vertical',
expand: 'up'
});
this.toolbar.init();
this.toolbar.setEngine(this.engine);
this.toolbar.addGroup('construct-tree');
this.toolbar.addButton({
id: 'construct-tree-btn',
@@ -113,16 +119,16 @@ export class ConstructTreeManagerBtn extends BimComponent {
label: 'construct-tree',
icon: getIcon('目录树'),
onClick: () => {
this.openConstructTreeDialog()
this.openConstructTreeDialog();
}
});
this.toolbar.render();
}
/** 打开构件树对话框 */
public openConstructTreeDialog() {
this.setVisible(false);
// 构件树实例(放在“构件”标签内)
const tree = new BimTree({
data: MOCK_STRUCT_DATA,
checkable: true,
@@ -146,18 +152,15 @@ export class ConstructTreeManagerBtn extends BimComponent {
});
tree.init();
// 系统/空间暂留空占位,可后续填充业务内容
const systemPlaceholder = document.createElement('div');
systemPlaceholder.className = 'construct-tab__panel-content';
const spacePlaceholder = document.createElement('div');
spacePlaceholder.className = 'construct-tab__panel-content';
// 构件面板容器,确保内部树区域可滚动
const componentPanel = document.createElement('div');
componentPanel.className = 'construct-tab__panel-content';
componentPanel.appendChild(tree.element);
// 创建 Tab 容器(仅在本弹窗内使用,不额外挂 Manager
const tabMount = document.createElement('div');
tabMount.className = 'construct-tab__container';
tabMount.style.height = '100%';
@@ -165,24 +168,23 @@ export class ConstructTreeManagerBtn extends BimComponent {
const tab = new BimTab({
container: tabMount,
tabs: [
{id: 'component', title: 'tab.component', content: componentPanel},
{id: 'system', title: 'tab.system', content: systemPlaceholder},
{id: 'space', title: 'tab.space', content: spacePlaceholder},
{ id: 'component', title: 'tab.component', content: componentPanel },
{ id: 'system', title: 'tab.system', content: systemPlaceholder },
{ id: 'space', title: 'tab.space', content: spacePlaceholder },
],
activeId: 'component',
onChange: () => {
// 切换后根据内容宽度刷新弹窗
this.dialog?.fitWidth();
}
});
tab.init();
this.dialog = this.engine.dialog!.create({
this.dialog = this.registry.dialog!.create({
title: 'constructTree.title',
minWidth: 320,
height: 420,
content: tabMount,
position: {x: 20, y: 20},
position: { x: 20, y: 20 },
resizable: false,
onClose: () => {
tab.destroy();
@@ -193,44 +195,76 @@ export class ConstructTreeManagerBtn extends BimComponent {
this.dialog?.fitWidth();
}
/** 刷新渲染 */
public refresh() {
this.toolbar?.render();
}
/** 销毁管理器 */
public destroy() {
this.toolbar?.destroy();
this.toolbar = null;
super.destroy();
}
// --- 转发 API ---
/**
* 添加按钮组
* @param groupId 组 ID
* @param beforeGroupId 插入位置
*/
public addGroup(groupId: string, beforeGroupId?: string) {
this.toolbar?.addGroup(groupId, beforeGroupId);
this.toolbar?.render();
}
/**
* 添加按钮
* @param config 按钮配置
*/
public addButton(config: ButtonConfig) {
this.toolbar?.addButton(config);
this.toolbar?.render();
}
/**
* 设置按钮可见性
* @param id 按钮 ID
* @param v 是否可见
*/
public setButtonVisibility(id: string, v: boolean) {
this.toolbar?.updateButtonVisibility(id, v);
}
/**
* 设置是否显示标签
* @param show 是否显示
*/
public setShowLabel(show: boolean) {
this.toolbar?.setShowLabel(show);
}
/**
* 设置按钮组可见性
* @param visible 是否可见
*/
public setVisible(visible: boolean) {
if (this.toolbarContainer) {
this.toolbarContainer.style.visibility = visible ? 'visible' : 'hidden';
}
}
/**
* 设置背景颜色
* @param color 颜色值
*/
public setBackgroundColor(color: string) {
this.toolbar?.setBackgroundColor(color);
}
/**
* 设置按钮组颜色
* @param colors 颜色配置
*/
public setColors(colors: ButtonGroupColors) {
this.toolbar?.setColors(colors);
}

View File

@@ -1,35 +1,30 @@
/**
* 对话框管理器
* 负责创建和管理所有对话框实例
*/
import { BimDialog } from '../components/dialog';
import { BimInfoDialog } from '../components/dialog/bimInfoDialog';
import type { DialogOptions } from '../components/dialog/index.type';
import type { ThemeConfig } from '../themes/types';
import { themeManager } from '../services/theme';
import { BimComponent } from '../core/component';
import type { BimEngine } from '../bim-engine';
import { BaseManager } from '../core/base-manager';
/**
* 弹窗管理器
* 负责创建和管理应用中的各类弹窗。
* 对话框管理器
* 统一管理对话框的创建、主题更新和销毁
*/
export class DialogManager extends BimComponent {
/** 弹窗挂载的父容器 */
export class DialogManager extends BaseManager {
/** 容器元素 */
private container: HTMLElement;
/** 活跃的弹窗实例列表 */
/** 活跃的对话框列表 */
private activeDialogs: BimDialog[] = [];
/**
* 构造函数
* @param engine 引擎实例
* @param container 弹窗挂载的目标容器
*/
constructor(engine: BimEngine, container: HTMLElement) {
super(engine);
constructor(container: HTMLElement) {
super();
this.container = container;
// 监听打开弹窗事件
this.on('ui:open-dialog', (payload) => {
// 这里可以根据 payload.id 做更复杂的逻辑,目前简单演示
this.subscribe('ui:open-dialog', (payload) => {
console.log('[DialogManager] Received open-dialog event:', payload);
// 示例:如果 payload.id 是 'info',则打开 info dialog
if (payload.id === 'info') {
this.showInfoDialog();
}
@@ -37,41 +32,33 @@ export class DialogManager extends BimComponent {
}
/**
* 创建一个通用弹窗
* @param options 弹窗配置选项(不需要传 container自动使用管理器绑定的容器
* @returns BimDialog 实例
* 创建对话框
* @param options 对话框配置选项
* @returns 对话框实例
*/
public create(options: Omit<DialogOptions, 'container'>): BimDialog {
const dialog = new BimDialog({
container: this.container,
...options,
onClose: () => {
// 从活跃列表中移除
this.activeDialogs = this.activeDialogs.filter(d => d !== dialog);
if (options.onClose) options.onClose();
}
});
// 应用当前主题
dialog.setTheme(themeManager.getTheme());
this.activeDialogs.push(dialog);
return dialog;
}
/**
* 显示二次封装的模型信息弹窗
* 演示如何调用特定的业务弹窗组件
*/
/** 显示信息对话框 */
public showInfoDialog() {
// 最佳实践:所有弹窗应通过 create 统一管理,或者手动加入管理。
new BimInfoDialog(this.container);
// 暂时不做主题追踪,作为遗留逻辑保留
}
/**
* 响应全局主题变更
* @param theme 全局主题配置
* 更新所有对话框的主题
* @param theme 主题配置
*/
public updateTheme(theme: ThemeConfig) {
this.activeDialogs.forEach(dialog => {
@@ -81,8 +68,10 @@ export class DialogManager extends BimComponent {
});
}
/** 销毁管理器和所有对话框 */
public destroy() {
this.activeDialogs.forEach(d => d.destroy());
this.activeDialogs = [];
super.destroy();
}
}

View File

@@ -1,6 +1,9 @@
/**
* 3D 引擎管理器
* 负责管理 3D 渲染引擎的初始化、模型加载和测量功能
*/
import { Engine, type EngineOptions, type ModelLoadOptions } from '../components/engine';
import { BimComponent } from '../core/component';
import type { BimEngine } from '../bim-engine';
import { BaseManager } from '../core/base-manager';
import { RightKeyManager } from './right-key-manager';
import { infoMenuButton } from '../components/menu/buttons/info';
import { homeMenuButton } from '../components/menu/buttons/home';
@@ -8,34 +11,27 @@ import type { MeasureMode } from '../types/measure';
/**
* 3D 引擎管理器
* 负责连接 Engine 组件和 BimEngine向外部暴露简化的 API
* 采用延迟初始化模式,用户需主动调用 initialize() 方法
* 封装底层 3D 引擎,提供模型加载、相机控制、测量等功能
*/
export class EngineManager extends BimComponent {
/** 3D 引擎挂载的父容器 */
export class EngineManager extends BaseManager {
/** 容器元素 */
private container: HTMLElement;
/** 3D 引擎组件实例 */
/** 引擎实例 */
private engineInstance: Engine | null = null;
/** 右键菜单管理器 */
public rightKey: RightKeyManager | null = null;
public rightKey: RightKeyManager | null = null; // 右键菜单管理器
/**
* 构造函数
* @param engine 引擎实例
* @param container 3D 引擎挂载的目标容器
*/
constructor(engine: BimEngine, container: HTMLElement) {
super(engine);
constructor(container: HTMLElement) {
super();
this.container = container;
}
/**
* 初始化 3D 引擎
* @param options 引擎配置选项(可选,如果不提供则使用默认配置)
* @param options 引擎配置选项
* @returns 是否初始化成功
*/
public initialize(options?: Omit<EngineOptions, 'container'>): boolean {
// 如果已经初始化,先销毁旧的实例
if (this.engineInstance && this.engineInstance.isInitialized()) {
console.warn('[EngineManager] 3D Engine already initialized. Destroying old instance...');
this.engineInstance.destroy();
@@ -43,24 +39,19 @@ export class EngineManager extends BimComponent {
}
try {
// 创建 Engine 组件实例
// options 中的配置会自动复制给 createEngine 使用
this.engineInstance = new Engine({
container: this.container,
...options, // 合并配置选项
...options,
});
// 调用组件的 init 方法初始化引擎
this.engineInstance.init();
// 初始化右键 (移到 return 之前)
this.rightKey = new RightKeyManager(this.engine, this.container);
this.rightKey = new RightKeyManager(this.container);
// 注册默认右键菜单
this.rightKey.registerHandler((_e) => {
return [
infoMenuButton(this.engine),
homeMenuButton(this.engine)
infoMenuButton(),
homeMenuButton()
];
});
@@ -71,16 +62,19 @@ export class EngineManager extends BimComponent {
return false;
}
}
/**
* 检<EFBFBD><EFBFBD><EFBFBD> 3D 引擎是否已初始化
* 检引擎是否已初始化
* @returns 是否已初始化
*/
public isInitialized(): boolean {
return this.engineInstance !== null && this.engineInstance.isInitialized();
}
/**
* 加载 3D 模型
* @param url 模型文件 URL
* @param options 加载选项(位置、旋转、缩放)
* 加载模型
* @param url 模型 URL
* @param options 加载选项
*/
public loadModel(url: string, options?: ModelLoadOptions): void {
if (!this.engineInstance) {
@@ -90,10 +84,9 @@ export class EngineManager extends BimComponent {
this.engineInstance.loadModel(url, options);
}
/**
* 获取原始 3D 引擎实例
* 用于直接调用第三方引擎的其他 API
* 获取底层引擎实例
* @returns 引擎实例
*/
public getEngine(): any {
if (!this.engineInstance) {
@@ -103,9 +96,7 @@ export class EngineManager extends BimComponent {
return this.engineInstance.getEngine();
}
/**
* 回到主视角
*/
/** 相机回到初始位置 */
public CameraGoHome(): void {
if (!this.engineInstance) {
console.warn('[EngineManager] 3D Engine not initialized.');
@@ -113,9 +104,10 @@ export class EngineManager extends BimComponent {
}
this.engineInstance.CameraGoHome();
}
/**
* 激活测量功能
* @param mode 测量类型
* 激活测量模式
* @param mode 测量模式
*/
public activateMeasure(mode: MeasureMode): void {
if (!this.engineInstance) {
@@ -125,9 +117,7 @@ export class EngineManager extends BimComponent {
this.engineInstance.activateMeasure(mode);
}
/**
* 停用测量功能
*/
/** 停用测量模式 */
public deactivateMeasure(): void {
if (!this.engineInstance) {
return;
@@ -136,7 +126,8 @@ export class EngineManager extends BimComponent {
}
/**
* 获取当前激活的测量类型
* 获取当前测量类型
* @returns 当前测量模式
*/
public getCurrentMeasureType(): MeasureMode | null {
if (!this.engineInstance) {
@@ -145,11 +136,7 @@ export class EngineManager extends BimComponent {
return this.engineInstance.getCurrentMeasureType();
}
// ==================== 结束:测量功能方法 ====================
/**
* 销毁 3D 引擎实例
*/
/** 销毁引擎管理器 */
public destroy(): void {
if (this.engineInstance) {
this.engineInstance.destroy();
@@ -159,8 +146,6 @@ export class EngineManager extends BimComponent {
this.rightKey.destroy();
this.rightKey = null;
}
super.destroy();
}
}

View File

@@ -1,107 +1,76 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
/**
* 地图对话框管理器
* 负责管理地图/平面图对话框的显示和交互
*/
import { BaseDialogManager } from '../core/base-dialog-manager';
import { MapPanel } from '../components/map-panel';
/**
* 地图弹窗管理器(独立通用组件)
* 地图对话框管理器
* 继承自 BaseDialogManager提供地图面板的对话框管理功能
*/
export class MapDialogManager extends BimComponent {
private dialogId = 'map-dialog';
private dialog: BimDialog | null = null;
export class MapDialogManager extends BaseDialogManager {
/** 地图面板实例 */
private panel: MapPanel | null = null;
constructor(engine: BimEngine) {
super(engine);
}
/** 对话框唯一标识 */
protected get dialogId() { return 'map-dialog'; }
/** 对话框标题(国际化 key */
protected get dialogTitle() { return 'map.dialogTitle'; }
/** 对话框宽度 */
protected get dialogWidth() { return 300; }
/** 对话框高度 */
protected get dialogHeight(): number { return 400; }
public init(): void {
// 可以在这里监听事件
}
/** 初始化 */
public init(): void {}
/**
* 显示弹窗
* 获取对话框位置
* 定位在容器左下角
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
protected getDialogPosition() {
const container = this.registry.container;
if (!container) return { x: 20, y: 100 };
// 如果已打开,不重复打开
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;
return {
x: paddingLeft,
y: containerHeight - this.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();
/** 创建对话框内容 */
protected createContent(): HTMLElement {
this.panel = new MapPanel();
this.panel.init();
return this.panel.element;
}
// 触发地图打开事件
/** 对话框创建后的回调 */
protected onDialogCreated(): void {
this.emit('map:opened', {});
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
// 触发地图关闭事件
/** 对话框关闭时的回调 */
protected onDialogClose(): void {
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();
}
// 销毁面板
/** 销毁前的清理 */
protected onBeforeDestroy(): void {
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
/** 隐藏对话框 */
public hide(): void {
super.hide();
this.emit('map:closed', {});
}
}

View File

@@ -1,114 +1,100 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from "../components/dialog";
/**
* 测量对话框管理器
* 负责管理测量工具对话框的显示、隐藏和测量面板的交互
*/
import { BaseDialogManager } from '../core/base-dialog-manager';
import { MeasurePanel } from '../components/measure-panel';
import type { MeasureConfig, MeasureMode, MeasureResult } from '../components/measure-panel/types';
/**
* 测量弹窗管理器
* 测量对话框管理器
* 继承自 BaseDialogManager提供测量工具的对话框管理功能
*/
export class MeasureDialogManager extends BimComponent {
private dialogId = 'measure-dialog';
private dialog: BimDialog | null = null;
export class MeasureDialogManager extends BaseDialogManager {
/** 测量面板实例 */
private panel: MeasurePanel | null = null;
/**
* 测量配置项(单位/精度)
* 说明MeasurePanel 会自行从缓存加载默认配置Manager 这里只做“对外读取/设置”的镜像。
*/
/** 测量配置(单位、精度等) */
private config: MeasureConfig | null = null;
constructor(engine: BimEngine) {
super(engine);
/** 对话框唯一标识 */
protected get dialogId(): string {
return 'measure-dialog';
}
public init(): void {
// 可以在这里监听事件
/** 对话框标题(国际化 key */
protected get dialogTitle(): string {
return 'measure.dialogTitle';
}
/** 对话框宽度 */
protected get dialogWidth(): number {
return 250;
}
/**
* 显示测量弹窗
* 创建对话框内容
* 初始化测量面板并设置回调
*/
public show() {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
const dialogWidth = 250;
const dialogHeight = 300;
const paddingRight = 20; // 你想要的右边距
const container = this.engine.container;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const x = containerWidth - dialogWidth - paddingRight;
const y = (containerHeight - dialogHeight) / 2;
// 如果已打开过,先销毁旧实例,避免重复创建/重复订阅
this.destroy();
// 创建测量面板(只做 UI不实现真实测量
protected createContent(): HTMLElement {
this.panel = new MeasurePanel({
defaultMode: 'distance', // 默认展示前四个,且默认选中"距离"
defaultMode: 'distance',
defaultExpanded: false,
onModeChange: (mode) => {
console.log('[MeasureDialogManager] 当前测量方式已切换:', mode);
this.engine.engine?.activateMeasure(mode);
this.registry.engine3d?.activateMeasure(mode);
},
onClearAll: () => {
// 预留:未来可清理引擎测量绘制/标注
console.log('[MeasureDialogManager] 删除全部(仅 UI 清空,本次不清理引擎侧内容)');
console.log('[MeasureDialogManager] 删除全部');
},
onSettings: () => {
// 预留:未来可打开设置弹窗/面板
console.log('[MeasureDialogManager] 打开设置(仅预留接口)');
console.log('[MeasureDialogManager] 打开设置');
},
onExpandedChange: () => {
// 展开/收起时,动态适配 Dialog 高度,避免遮挡底部操作按钮
this.dialog?.fitHeight(false);
}
});
this.panel.init();
// 同步一次当前配置(由组件从缓存/默认加载)
this.config = this.panel.getConfig();
// 注意:你要求“组件本身不加边距”,因此在 Manager 这里用 wrapper 增加左右内边距
// 这样 MeasurePanel 可以保持通用性,避免在不同场景复用时产生多余 padding。
const panelWrapper = document.createElement('div');
panelWrapper.style.padding = '12px';
panelWrapper.appendChild(this.panel.element);
this.dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'measure.dialogTitle',
content: panelWrapper,
width: dialogWidth,
// 高度交给 fitHeight 动态计算(避免内容展开后遮挡底部操作区)
height: 'auto',
position: {
x: x,
y: y
},
onClose: () => {
this.engine.toolbar?.setBtnActive('measure', false)
this.destroy()
}
});
this.dialog.init();
return panelWrapper;
}
// 初次打开时也执行一次自适应高度(收起态)
this.dialog.fitHeight(false);
/** 对话框创建后的回调,自适应高度 */
protected onDialogCreated(): void {
this.dialog?.fitHeight(false);
}
/** 对话框关闭时的回调,取消工具栏按钮激活状态 */
protected onDialogClose(): void {
this.registry.toolbar?.setBtnActive('measure', false);
}
/** 销毁前的清理,停用测量功能并销毁面板 */
protected onBeforeDestroy(): void {
if (this.registry.engine3d) {
this.registry.engine3d.deactivateMeasure();
}
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
/**
* 获取当前测量
* 说明:如果面板未创建,则返回 null
* 获取当前激活的测量
* @returns 当前测量模式,如 'distance'、'angle' 等
*/
public getActiveMode(): MeasureMode | null {
return this.panel ? this.panel.getActiveMode() : null;
}
/**
* 切换测量方式(你要求的“切换类型的方法”)
* @param mode 测量
* 切换测量模式
* @param mode 目标测量
*/
public switchMode(mode: MeasureMode): void {
if (!this.panel) return;
@@ -116,22 +102,17 @@ export class MeasureDialogManager extends BimComponent {
}
/**
* 设置测量结果(推荐使用的新方法名)
* 说明:内部直接调用 MeasurePanel.setResult()
* @param result 测量结果;传 null 表示清空
* 设置测量结果
* @param result 测量结果对象
*/
public setMeasureResult(result: MeasureResult | null): void {
// 按你的要求:仅当 panel 存在时才调用,不做缓存
if (!this.panel) {
return;
}
if (!this.panel) return;
this.panel.setResult(result);
}
/**
* 获取测量配置(单位/精度)
* - 如果面板存在:返回面板当前配置
* - 否则:返回 Manager 缓存的最后一次配置(可能为 null
* 获取测量配置
* @returns 测量配置副本
*/
public getConfig(): MeasureConfig | null {
if (this.panel) {
@@ -141,63 +122,35 @@ export class MeasureDialogManager extends BimComponent {
}
/**
* 设置测量配置(单位/精度)
* @param partial 部分更新
* @param persist 是否写入缓存(默认 true
* 设置测量配置
* @param partial 部分配置
* @param persist 是否持久化
*/
public setConfig(partial: Partial<MeasureConfig>, persist: boolean = true): void {
// 面板存在则直接设置面板;否则仅更新 Manager 缓存
if (this.panel) {
this.panel.setConfig(partial, persist);
this.config = this.panel.getConfig();
// 配置变化可能影响高度(比如设置面板显示/隐藏),安全起见做一次 fit
this.dialog?.fitHeight(false);
return;
}
// 面板未创建:只更新本地缓存
const prev = this.config;
const next: MeasureConfig = {
unit: partial.unit ?? prev?.unit ?? 'mm',
precision: partial.precision ?? prev?.precision ?? 2
};
this.config = next;
// 注意:缓存写入由 MeasurePanel 负责(你要求默认维护在组件里)
// 这里不写 localStorage避免重复逻辑。
}
/**
* 删除全部(仅清空 UI真实测量清理逻辑后续再接
*/
/** 清除所有测量结果 */
public clearAll(): void {
if (!this.panel) return;
this.panel.clearAll();
}
/**
* 打开设置(仅预留方法/回调)
*/
/** 打开测量设置面板 */
public openSettings(): void {
if (!this.panel) return;
this.panel.openSettings();
}
public destroy(): void {
// 停用测量功能
if (this.engine.engine) {
this.engine.engine.deactivateMeasure();
}
// 关闭弹窗
if (this.dialog) {
this.dialog.destroy();
this.dialog = null;
}
// 销毁测量面板(清理订阅与 DOM
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
}

View File

@@ -1,50 +1,50 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
/**
* 属性面板管理器
* 负责管理构件属性面板的显示和内容
*/
import { BaseManager } from '../core/base-manager';
import { BimCollapse } from '../components/collapse/index';
import { BimDescription } from '../components/description/index';
import { BimTab } from '../components/tab/index';
/**
* 属性面板管理器
* 负责展示和管理属性面板弹窗 (演示 Tab + Collapse + Description 组件)
* 显示选中构件的属性信息和材质信息
*/
export class PropertyPanelManager extends BimComponent {
export class PropertyPanelManager extends BaseManager {
/** 对话框 ID */
private dialogId = 'property-panel-dialog';
private dialog: any = null; // 保存 dialog 引用
/** 对话框实例 */
private dialog: any = null;
constructor(engine: BimEngine) {
super(engine);
constructor() {
super();
}
/** 初始化,监听打开事件 */
public init(): void {
// 监听来自 Demo 的打开属性面板事件
document.addEventListener('bim-demo:open-property-panel', () => {
this.show();
});
}
/**
* 显示属性面板
*/
/** 显示属性面板 */
public show() {
if (!this.engine.dialog) {
if (!this.registry.dialog) {
console.warn('Dialog manager is not initialized');
return;
}
// 如果已打开,不重复打开
if (this.isOpen()) {
return;
}
// 1. 创建弹窗
const width = 360; // 稍微加宽一点以容纳 Tab
const width = 360;
const x = document.body.clientWidth - width - 40;
console.log('x', x)
this.dialog = this.engine.dialog.create({
this.dialog = this.registry.dialog.create({
id: this.dialogId,
title: 'panel.property.title', // '构件详情'
title: 'panel.property.title',
content: '',
width: `${width}px`,
height: '500px',
@@ -56,7 +56,6 @@ export class PropertyPanelManager extends BimComponent {
}
} as any);
// 2. 创建内容容器
const contentContainer = document.createElement('div');
contentContainer.style.height = '100%';
contentContainer.style.display = 'flex';
@@ -64,33 +63,29 @@ export class PropertyPanelManager extends BimComponent {
this.dialog.setContent(contentContainer);
// 3. 创建标签页组件
const tab = new BimTab({
container: contentContainer,
tabs: [
{
id: 'props',
title: 'panel.property.tab.props', // '属性'
title: 'panel.property.tab.props',
content: this.createPropsTabContent()
},
{
id: 'material',
title: 'panel.property.tab.material', // '材质'
title: 'panel.property.tab.material',
content: this.createMaterialTabContent()
}
]
});
tab.init();
}
/**
* 创建"属性"标签页的内容 (包含 Collapse)
*/
/** 创建属性标签页内容 */
private createPropsTabContent(): HTMLElement {
const container = document.createElement('div');
container.style.height = '100%';
container.style.overflowY = 'auto'; // 内容区域滚动
container.style.overflowY = 'auto';
new BimCollapse({
container: container,
@@ -99,13 +94,13 @@ export class PropertyPanelManager extends BimComponent {
items: [
{
id: 'base',
title: 'panel.property.base', // '基本属性'
title: 'panel.property.base',
content: this.createBaseInfoContent(),
},
{
id: 'advanced',
title: 'panel.property.advanced', // '高级设置'
content: this.createAdvancedInfoContent(), // 新增一个内容
title: 'panel.property.advanced',
content: this.createAdvancedInfoContent(),
disabled: false
}
]
@@ -114,9 +109,7 @@ export class PropertyPanelManager extends BimComponent {
return container;
}
/**
* 创建"材质"标签页的内容 (包含 Collapse)
*/
/** 创建材质标签页内容 */
private createMaterialTabContent(): HTMLElement {
const container = document.createElement('div');
container.style.height = '100%';
@@ -129,7 +122,7 @@ export class PropertyPanelManager extends BimComponent {
items: [
{
id: 'material',
title: 'panel.property.material', // '材质信息'
title: 'panel.property.material',
content: this.createMaterialContent(),
}
]
@@ -138,6 +131,7 @@ export class PropertyPanelManager extends BimComponent {
return container;
}
/** 创建基本信息内容 */
private createBaseInfoContent(): HTMLElement {
const container = document.createElement('div');
@@ -156,6 +150,7 @@ export class PropertyPanelManager extends BimComponent {
return container;
}
/** 创建高级信息内容 */
private createAdvancedInfoContent(): HTMLElement {
const container = document.createElement('div');
@@ -174,10 +169,10 @@ export class PropertyPanelManager extends BimComponent {
return container;
}
/** 创建材质内容 */
private createMaterialContent(): HTMLElement {
const container = document.createElement('div');
// 材质预览块
const preview = document.createElement('div');
preview.style.display = 'flex';
preview.style.alignItems = 'center';
@@ -204,15 +199,14 @@ export class PropertyPanelManager extends BimComponent {
}
/**
* 检查属性面板是否打开
* 检查面板是否打开
* @returns 是否打开
*/
public isOpen(): boolean {
return this.dialog !== null;
}
/**
* 隐藏属性面板
*/
/** 隐藏面板 */
public hide(): void {
if (this.dialog) {
this.dialog.destroy();
@@ -220,7 +214,9 @@ export class PropertyPanelManager extends BimComponent {
}
}
/** 销毁管理器 */
public destroy(): void {
this.hide();
super.destroy();
}
}

View File

@@ -1,30 +1,28 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
/**
* 右键菜单管理器
* 负责管理右键上下文菜单的显示和交互
*/
import { BaseManager } from '../core/base-manager';
import { BimRightKey } from '../components/right-key';
import { BimMenu } from '../components/menu';
import { MenuItemConfig } from '../components/menu/item';
/**
* 右键菜单管理器 (RightKeyManager)
* 负责协调右键交互流程:
* 1. 监听 Canvas/容器的 contextmenu 事件
* 2. 通过注册的处理器 (Handler) 获取需要显示的菜单项
* 3. 实例化 Menu 组件并装载到 RightKey 容器中显示
* 右键菜单管理器
* 支持注册多个上下文处理器,动态生成右键菜单
*/
export class RightKeyManager extends BimComponent {
export class RightKeyManager extends BaseManager {
/** 容器元素 */
private container: HTMLElement;
/** 右键面板实例 */
private rightKeyPanel: BimRightKey;
// 存储注册的上下文处理器
// 每个处理器接收鼠标事件,返回一组菜单项(如果没有对应菜单则返回 null
/** 上下文处理器列表 */
private contextHandlers: Array<(e: MouseEvent) => MenuItemConfig[] | null> = [];
constructor(engine: BimEngine, container: HTMLElement) {
super(engine);
constructor(container: HTMLElement) {
super();
this.container = container;
// 初始化右键容器,设置极高的层级以覆盖所有 UI
// 将事件监听和触发逻辑下放给 BimRightKey 组件
this.rightKeyPanel = new BimRightKey({
zIndex: 9000,
container: this.container,
@@ -33,55 +31,44 @@ export class RightKeyManager extends BimComponent {
this.rightKeyPanel.init();
}
/** 销毁管理器 */
public destroy(): void {
this.rightKeyPanel.destroy();
super.destroy();
}
/**
* 注册上下文菜单处理器
* @param handler 处理函数,接收鼠标事件,返回菜单项数组
* 注册上下文处理器
* @param handler 处理函数,返回菜单项配置
*/
public registerHandler(handler: (e: MouseEvent) => MenuItemConfig[] | null): void {
this.contextHandlers.push(handler);
}
/**
* 手动显示菜单
* 允许外部直接调用以显示特定的菜单,不一定依赖右键事件
* @param x 屏幕 X 坐标
* @param y 屏幕 Y 坐标
* @param items 菜单项列表
* @param groupOrder 可选的分组顺序
* 显示菜单
* @param x 横坐标
* @param y 纵坐标
* @param items 菜单项配置
* @param groupOrder 分组顺序
*/
public showMenu(x: number, y: number, items: MenuItemConfig[], groupOrder?: string[]): void {
if (!items || items.length === 0) return;
// 1. 创建菜单内容组件
const menu = new BimMenu({ items, groupOrder });
menu.init(); // 必须初始化以生成 DOM
menu.init();
// 2. 将菜单挂载到右键容器
this.rightKeyPanel.mount(menu);
// 3. 显示容器
this.rightKeyPanel.show(x, y);
}
/**
* 隐藏右键菜单
*/
/** 隐藏菜单 */
public hide(): void {
this.rightKeyPanel.hide();
}
/**
* 处理右键点击事件
* 由 BimRightKey 组件在检测到有效右键点击时调用
*/
/** 处理右键点击事件 */
private handleContextMenu = (e: MouseEvent): void => {
// 1. 确定上下文项
// 遍历所有注册的处理器,找到第一个返回非空结果的处理器
// 这种责任链模式允许插件优先处理特定对象的右键
let items: MenuItemConfig[] | null = null;
for (const handler of this.contextHandlers) {
const result = handler(e);
@@ -91,11 +78,9 @@ export class RightKeyManager extends BimComponent {
}
}
// 2. 如果有菜单项,则显示
if (items && items.length > 0) {
this.showMenu(e.clientX, e.clientY, items);
} else {
// 如果没有任何内容,则关闭可能存在的菜单
this.hide();
}
};

View File

@@ -1,135 +1,122 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
/**
* 轴向剖切对话框管理器
* 负责管理轴向剖切工具对话框的显示、隐藏和剖切面板的交互
*/
import { BaseDialogManager } from '../core/base-dialog-manager';
import { SectionAxisPanel } from '../components/section-axis-panel';
import type { SectionAxis } from '../components/section-axis-panel/types';
/**
* 轴向剖切弹窗管理器
* 轴向剖切对话框管理器
* 继承自 BaseDialogManager提供 X/Y/Z 轴向剖切的对话框管理功能
*/
export class SectionAxisDialogManager extends BimComponent {
private dialogId = 'section-axis-dialog';
private dialog: BimDialog | null = null;
export class SectionAxisDialogManager extends BaseDialogManager {
/** 轴向剖切面板实例 */
private panel: SectionAxisPanel | null = null;
constructor(engine: BimEngine) {
super(engine);
/** 对话框唯一标识 */
protected get dialogId(): string {
return 'section-axis-dialog';
}
public init(): void {
// 可以在这里监听事件
/** 对话框标题(国际化 key */
protected get dialogTitle(): string {
return 'sectionAxis.dialogTitle';
}
/** 对话框宽度 */
protected get dialogWidth(): number {
return 240;
}
/**
* 显示弹窗
* 获取对话框位置
* 定位在容器右下角
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
protected getDialogPosition(): { x: number; y: number } {
const container = this.registry.container;
if (!container) return { x: 100, y: 100 };
// 如果已打开,先销毁
this.destroy();
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const paddingRight = 20;
const paddingBottom = 50;
// 创建面板
return {
x: containerWidth - this.dialogWidth - paddingRight,
y: containerHeight - paddingBottom - 200
};
}
/**
* 创建对话框内容
* 初始化轴向剖切面板并设置回调
*/
protected createContent(): HTMLElement {
this.panel = new SectionAxisPanel({
defaultAxis: 'x',
defaultHidden: false,
onHideToggle: (isHidden) => {
console.log('[SectionAxisDialogManager] 隐藏切换:', isHidden);
// TODO: 实现隐藏/显示剖切面的逻辑
},
onReverse: () => {
console.log('[SectionAxisDialogManager] 反向剖切');
// TODO: 实现反向剖切的逻辑
},
onAxisChange: (axis) => {
console.log('[SectionAxisDialogManager] 切换轴向:', axis);
// TODO: 实现轴向切换的逻辑
}
});
this.panel.init();
return this.panel.element;
}
// 创建弹窗
const dialogWidth = 240;
const paddingRight = 20;
const paddingBottom = 50;
const container = this.engine.container;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const x = containerWidth - dialogWidth - paddingRight;
const y = containerHeight - paddingBottom - 200; // 临时y值会被fitHeight调整
/** 对话框创建后的回调,自适应高度 */
protected onDialogCreated(): void {
this.dialog?.fitHeight(false);
}
this.dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'sectionAxis.dialogTitle',
width: dialogWidth,
height: 'auto', // 自动高度
position: { x, y },
draggable: true,
resizable: false,
content: this.panel.element,
onClose: () => {
this.engine.toolbar?.setBtnActive('section-axis', false);
this.hide();
}
});
this.dialog.init();
/** 对话框关闭时的回调,取消工具栏按钮激活状态 */
protected onDialogClose(): void {
this.registry.toolbar?.setBtnActive('section-axis', false);
}
// 自适应高度
this.dialog.fitHeight(false);
/** 销毁前的清理,销毁面板实例 */
protected onBeforeDestroy(): void {
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
}
/**
* 获取隐藏状态
* 获取剖切面隐藏状态
* @returns 是否隐藏
*/
public getHiddenState(): boolean {
return this.panel?.getHiddenState() ?? false;
}
/**
* 设置隐藏状态
* 设置剖切面隐藏状态
* @param isHidden 是否隐藏
*/
public setHiddenState(isHidden: boolean): void {
this.panel?.setHiddenState(isHidden);
}
/**
* 获取当前激活的轴向
* 获取当前激活的剖切轴向
* @returns 当前轴向 'x' | 'y' | 'z'
*/
public getActiveAxis(): SectionAxis {
return this.panel?.getActiveAxis() ?? 'x';
}
/**
* 设置激活的轴向
* 设置剖切轴向
* @param axis 目标轴向
*/
public setActiveAxis(axis: SectionAxis): void {
this.panel?.setActiveAxis(axis);
}
/**
* 销毁弹窗和面板
*/
public destroy(): void {
// 关闭弹窗
if (this.dialog) {
this.dialog.destroy();
this.dialog = null;
}
// 销毁面板
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
}

View File

@@ -1,159 +1,144 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
/**
* 剖切盒对话框管理器
* 负责管理剖切盒工具对话框的显示、隐藏和剖切盒面板的交互
*/
import { BaseDialogManager } from '../core/base-dialog-manager';
import { SectionBoxPanel } from '../components/section-box-panel';
import type { SectionBoxRange } from '../components/section-box-panel/types';
/**
* 剖切盒弹窗管理器
* 剖切盒对话框管理器
* 继承自 BaseDialogManager提供六面体剖切盒的对话框管理功能
*/
export class SectionBoxDialogManager extends BimComponent {
private dialogId = 'section-box-dialog';
private dialog: BimDialog | null = null;
export class SectionBoxDialogManager extends BaseDialogManager {
/** 剖切盒面板实例 */
private panel: SectionBoxPanel | null = null;
constructor(engine: BimEngine) {
super(engine);
/** 对话框唯一标识 */
protected get dialogId(): string {
return 'section-box-dialog';
}
public init(): void {
// 可以在这里监听事件
/** 对话框标题(国际化 key */
protected get dialogTitle(): string {
return 'sectionBox.dialogTitle';
}
/** 对话框宽度 */
protected get dialogWidth(): number {
return 280;
}
/**
* 显示弹窗
* 获取对话框位置
* 定位在容器右下角
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
protected getDialogPosition(): { x: number; y: number } {
const container = this.registry.container;
if (!container) return { x: 100, y: 100 };
// 如果已打开,先销毁
this.destroy();
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const paddingRight = 20;
const paddingBottom = 50;
// 创建面板
return {
x: containerWidth - this.dialogWidth - paddingRight,
y: containerHeight - paddingBottom - 300
};
}
/**
* 创建对话框内容
* 初始化剖切盒面板并设置回调
*/
protected createContent(): HTMLElement {
this.panel = new SectionBoxPanel({
defaultHidden: false,
defaultReversed: false,
onHideToggle: (isHidden) => {
console.log('[SectionBoxDialogManager] 隐藏切换:', isHidden);
// TODO: 实现隐藏/显示剖切盒的逻辑
},
onReverseToggle: (isReversed) => {
console.log('[SectionBoxDialogManager] 反向切换:', isReversed);
// TODO: 实现反向剖切的逻辑
},
onFitToModel: () => {
console.log('[SectionBoxDialogManager] 适应到模型');
// TODO: 实现自动适应模型的逻辑
},
onReset: () => {
console.log('[SectionBoxDialogManager] 重置');
// 注意:不要在这里调用 panel.reset(),会造成无限递归
// panel 的 reset 按钮已经在内部处理了状态重置
// TODO: 这里只需要通知 3D 引擎重置剖切盒即可
},
onRangeChange: (range) => {
console.log('[SectionBoxDialogManager] 范围变化:', range);
// TODO: 实现范围变化的逻辑
}
});
this.panel.init();
return this.panel.element;
}
// 创建弹窗
const dialogWidth = 280;
const paddingRight = 20;
const paddingBottom = 50;
const container = this.engine.container;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const x = containerWidth - dialogWidth - paddingRight;
const y = containerHeight - paddingBottom - 300; // 临时y值会被fitHeight调整
/** 对话框创建后的回调,自适应高度 */
protected onDialogCreated(): void {
this.dialog?.fitHeight(false);
}
this.dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'sectionBox.dialogTitle',
width: dialogWidth,
height: 'auto',
position: { x, y },
draggable: true,
resizable: false,
content: this.panel.element,
onClose: () => {
this.engine.toolbar?.setBtnActive('section-box', false);
this.hide();
}
});
this.dialog.init();
/** 对话框关闭时的回调,取消工具栏按钮激活状态 */
protected onDialogClose(): void {
this.registry.toolbar?.setBtnActive('section-box', false);
}
// 自适应高度
this.dialog.fitHeight(false);
/** 销毁前的清理,销毁面板实例 */
protected onBeforeDestroy(): void {
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
}
/**
* 获取隐藏状态
* 获取剖切盒隐藏状态
* @returns 是否隐藏
*/
public getHiddenState(): boolean {
return this.panel?.getHiddenState() ?? false;
}
/**
* 设置隐藏状态
* 设置剖切盒隐藏状态
* @param isHidden 是否隐藏
*/
public setHiddenState(isHidden: boolean): void {
this.panel?.setHiddenState(isHidden);
}
/**
* 获取反向状态
* 获取剖切盒反向状态
* @returns 是否反向(显示盒内/盒外)
*/
public getReversedState(): boolean {
return this.panel?.getReversedState() ?? false;
}
/**
* 设置反向状态
* 设置剖切盒反向状态
* @param isReversed 是否反向
*/
public setReversedState(isReversed: boolean): void {
this.panel?.setReversedState(isReversed);
}
/**
* 获取范围
* 获取剖切盒范围
* @returns 六面体范围 { minX, maxX, minY, maxY, minZ, maxZ }
*/
public getRange(): SectionBoxRange | null {
return this.panel?.getRange() ?? null;
}
/**
* 设置范围
* 设置剖切盒范围
* @param range 部分或全部范围值
*/
public setRange(range: Partial<SectionBoxRange>): void {
this.panel?.setRange(range);
}
/**
* 销毁弹窗和面板
*/
public destroy(): void {
// 关闭弹窗
if (this.dialog) {
this.dialog.destroy();
this.dialog = null;
}
// 销毁面板
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
}

View File

@@ -1,95 +1,73 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
/**
* 拾取面剖切对话框管理器
* 负责管理拾取面剖切工具对话框的显示和交互
*/
import { BaseDialogManager } from '../core/base-dialog-manager';
import { SectionPlanePanel } from '../components/section-plane-panel';
/**
* 拾取面剖切弹窗管理器
* 拾取面剖切对话框管理器
* 继承自 BaseDialogManager提供拾取面剖切功能的对话框管理
*/
export class SectionPlaneDialogManager extends BimComponent {
private dialogId = 'section-plane-dialog';
private dialog: BimDialog | null = null;
export class SectionPlaneDialogManager extends BaseDialogManager {
/** 剖切面板实例 */
private panel: SectionPlanePanel | null = null;
constructor(engine: BimEngine) {
super(engine);
}
/** 对话框唯一标识 */
protected get dialogId() { return 'section-plane-dialog'; }
/** 对话框标题(国际化 key */
protected get dialogTitle() { return 'sectionPlane.dialogTitle'; }
/** 对话框宽度 */
protected get dialogWidth() { return 240; }
/** 对话框高度 */
protected get dialogHeight(): number { return 120; }
public init(): void {
// 可以在这里监听事件
}
/** 初始化 */
public init(): void {}
/**
* 显示拾取面剖切弹窗
* 获取对话框位置
* 定位在容器右下角
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
protected getDialogPosition() {
const container = this.registry.container;
if (!container) return { x: 100, y: 100 };
// 如果已打开过,先销毁旧实例
this.destroy();
const paddingRight = 20;
const paddingBottom = 50;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
// 创建面板
return {
x: containerWidth - this.dialogWidth - paddingRight,
y: containerHeight - this.dialogHeight - paddingBottom
};
}
/** 创建对话框内容 */
protected createContent(): HTMLElement {
this.panel = new SectionPlanePanel({
onHide: () => {
console.log('[SectionPlaneDialogManager] 隐藏');
// TODO: 调用引擎的隐藏功能
},
onReverse: () => {
console.log('[SectionPlaneDialogManager] 反向');
// TODO: 调用引擎的反向功能
},
onReset: () => {
console.log('[SectionPlaneDialogManager] 重置');
// TODO: 调用引擎的重置功能
}
});
this.panel.init();
// 创建弹窗
const dialogWidth = 240;
const dialogHeight = 120;
const paddingRight = 20;
const paddingBottom = 50;
const container = this.engine.container;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const x = containerWidth - dialogWidth - paddingRight;
const y = containerHeight - dialogHeight - paddingBottom;
this.dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'sectionPlane.dialogTitle',
width: dialogWidth,
height: dialogHeight,
position: { x, y },
draggable: true,
resizable: false,
content: this.panel.element,
onClose: () => {
this.engine.toolbar?.setBtnActive('section-plane', false);
this.hide();
}
});
return this.panel.element;
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
/** 对话框关闭时的回调 */
protected onDialogClose(): void {
this.registry.toolbar?.setBtnActive('section-plane', false);
}
/**
* 销毁弹窗
*/
public destroy(): void {
if (this.dialog) {
this.dialog.destroy();
this.dialog = null;
}
/** 销毁前的清理 */
protected onBeforeDestroy(): void {
if (this.panel) {
this.panel.destroy();
this.panel = null;

View File

@@ -1,26 +1,32 @@
/**
* 工具栏管理器
* 负责管理底部工具栏的创建、按钮配置和显示控制
*/
import type { ButtonGroupColors, ButtonConfig } from '../components/button-group/index.type';
import { Toolbar } from '../components/button-group/toolbar';
import type { ThemeConfig } from '../themes/types';
import { BimComponent } from '../core/component';
import type { BimEngine } from '../bim-engine';
import { BaseManager } from '../core/base-manager';
/**
* 底部工具栏管理器 (ToolbarManager)
* 仅负责管理底部工具栏实例。
* 工具栏管理器
* 提供工具栏按钮的添加、显示/隐藏、主题更新等功能
*/
export class ToolbarManager extends BimComponent {
export class ToolbarManager extends BaseManager {
/** 工具栏实例 */
private toolbar: Toolbar | null = null;
/** 工具栏容器元素 */
private toolbarContainer: HTMLElement | null = null;
/** 主容器元素 */
private container: HTMLElement;
constructor(engine: BimEngine, container: HTMLElement) {
super(engine);
constructor(container: HTMLElement) {
super();
this.container = container;
this.init();
}
/** 初始化工具栏 */
private init() {
// 创建底部工具栏专用容器
this.toolbarContainer = document.createElement('div');
this.toolbarContainer.id = 'opt-btn-groups';
this.toolbarContainer.className = 'bim-engine-opt-btn-container is-bottom-toolbar';
@@ -36,52 +42,108 @@ export class ToolbarManager extends BimComponent {
expand: 'up'
});
// 注入 engine 到 Toolbar
// @ts-ignore - Toolbar 还没更新类型,暂时忽略
this.toolbar.setEngine(this.engine);
this.toolbar.init();
}
/**
* 更新工具栏主题
* @param theme 主题配置
*/
public updateTheme(theme: ThemeConfig) {
this.toolbar?.setTheme(theme);
}
/** 刷新工具栏渲染 */
public refresh() {
this.toolbar?.render();
}
/** 销毁工具栏 */
public destroy() {
this.toolbar?.destroy();
this.toolbar = null;
super.destroy();
}
// --- 转发 API ---
public addGroup(groupId: string, beforeGroupId?: string) { this.toolbar?.addGroup(groupId, beforeGroupId); this.toolbar?.render(); }
public addButton(config: ButtonConfig) { this.toolbar?.addButton(config); this.toolbar?.render(); }
public setButtonVisibility(id: string, v: boolean) { this.toolbar?.updateButtonVisibility(id, v); }
public setShowLabel(show: boolean) { this.toolbar?.setShowLabel(show); }
public setBtnActive(id: string, active?: boolean) { this.toolbar?.setBtnActive(id, active); }
/**
* 添加按钮组
* @param groupId 组 ID
* @param beforeGroupId 插入到指定组之前
*/
public addGroup(groupId: string, beforeGroupId?: string) {
this.toolbar?.addGroup(groupId, beforeGroupId);
this.toolbar?.render();
}
/**
* 添加按钮
* @param config 按钮配置
*/
public addButton(config: ButtonConfig) {
this.toolbar?.addButton(config);
this.toolbar?.render();
}
/**
* 设置按钮可见性
* @param id 按钮 ID
* @param v 是否可见
*/
public setButtonVisibility(id: string, v: boolean) {
this.toolbar?.updateButtonVisibility(id, v);
}
/**
* 设置是否显示标签
* @param show 是否显示
*/
public setShowLabel(show: boolean) {
this.toolbar?.setShowLabel(show);
}
/**
* 设置按钮激活状态
* @param id 按钮 ID
* @param active 是否激活
*/
public setBtnActive(id: string, active?: boolean) {
this.toolbar?.setBtnActive(id, active);
}
/**
* 设置工具栏可见性
* @param visible 是否可见
*/
public setVisible(visible: boolean) {
if (this.toolbarContainer) {
this.toolbarContainer.style.visibility = visible ? 'visible' : 'hidden';
}
}
public setBackgroundColor(color: string) { this.toolbar?.setBackgroundColor(color); }
public setColors(colors: ButtonGroupColors) { this.toolbar?.setColors(colors); }
/**
* 隐藏工具栏
* 设置背景颜色
* @param color 颜色值
*/
public setBackgroundColor(color: string) {
this.toolbar?.setBackgroundColor(color);
}
/**
* 设置按钮组颜色
* @param colors 颜色配置
*/
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 = '';
@@ -90,6 +152,7 @@ export class ToolbarManager extends BimComponent {
/**
* 获取工具栏容器
* @returns 容器元素
*/
public getContainer(): HTMLElement | null {
return this.toolbarContainer;

View File

@@ -1,47 +1,48 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
/**
* 漫游控制管理器
* 负责管理漫游模式的控制面板和相关交互
*/
import { BaseManager } from '../core/base-manager';
import { WalkControlPanel } from '../components/walk-control-panel';
import { WalkPathDialogManager } from './walk-path-dialog-manager';
/**
* 漫游控制管理器
* 提供第一人称漫游、路径漫游等功能的控制界面
*/
export class WalkControlManager extends BimComponent {
export class WalkControlManager extends BaseManager {
/** 漫游控制面板实例 */
public panel: WalkControlPanel | null = null;
/** 路径漫游对话框管理器 */
private pathManager: WalkPathDialogManager | null = null;
constructor(engine: BimEngine) {
super(engine);
constructor() {
super();
}
/** 初始化管理器 */
public init(): void {
// 初始化子 manager
this.pathManager = new WalkPathDialogManager(this.engine);
this.pathManager = new WalkPathDialogManager();
this.pathManager.init();
}
/**
* 显示漫游控制面板
*/
/** 显示漫游控制面板 */
public show(): void {
if (!this.engine.toolbar) {
if (!this.registry.toolbar) {
console.warn('Toolbar not initialized');
return;
}
// 隐藏 toolbar
this.engine.toolbar.hide();
this.registry.toolbar.hide();
// 创建漫游控制面板
this.panel = new WalkControlPanel({
onPlanViewToggle: (isActive) => {
console.log('[WalkControl] 地图:', isActive);
if (isActive) {
this.engine.map?.show();
this.registry.map?.show();
} else {
this.engine.map?.hide();
this.registry.map?.hide();
}
// 触发事件
this.emit('walk:plan-view-toggle', { isActive });
},
onPathModeToggle: (isActive) => {
@@ -51,40 +52,32 @@ export class WalkControlManager extends BimComponent {
} 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();
@@ -92,60 +85,50 @@ export class WalkControlManager extends BimComponent {
});
this.panel.init();
// 如果地图已经打开,同步按钮状态
if (this.engine.map?.isOpen()) {
if (this.registry.map?.isOpen()) {
this.panel.setPlanViewActive(true);
}
// 监听地图事件,同步漫游面板中的地图按钮状态
this.engine.on('map:opened', () => {
this.subscribe('map:opened', () => {
this.panel?.setPlanViewActive(true);
});
this.engine.on('map:closed', () => {
this.subscribe('map:closed', () => {
this.panel?.setPlanViewActive(false);
});
// 将面板添加到主容器中定位在底部中间类似toolbar的位置
if (this.engine.container) {
// 添加定位样式
if (this.registry.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);
this.registry.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();
if (this.registry.toolbar) {
this.registry.toolbar.show();
}
}
/**
* 销毁管理器
*/
/** 销毁管理器 */
public destroy(): void {
this.hide();
this.pathManager?.destroy();
this.pathManager = null;
super.destroy();
}
}

View File

@@ -1,94 +1,64 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
/**
* 漫游路径对话框管理器
* 负责管理漫游路径设置对话框的显示和交互
*/
import { BaseDialogManager } from '../core/base-dialog-manager';
import { WalkPathPanel } from '../components/walk-path-panel';
/**
* 路径漫游弹窗管理器
* 漫游路径对话框管理器
* 继承自 BaseDialogManager提供漫游路径配置的对话框管理功能
*/
export class WalkPathDialogManager extends BimComponent {
private dialogId = 'walk-path-dialog';
private dialog: BimDialog | null = null;
export class WalkPathDialogManager extends BaseDialogManager {
/** 漫游路径面板实例 */
private panel: WalkPathPanel | null = null;
constructor(engine: BimEngine) {
super(engine);
}
/** 对话框唯一标识 */
protected get dialogId() { return 'walk-path-dialog'; }
/** 对话框标题(国际化 key */
protected get dialogTitle() { return 'walkControl.path.dialogTitle'; }
/** 对话框宽度 */
protected get dialogWidth() { return 300; }
/** 对话框高度 */
protected get dialogHeight(): number { return 400; }
public init(): void {
// 可以在这里监听事件
}
/** 初始化 */
public init(): void {}
/**
* 显示弹窗
* 获取对话框位置
* 定位在容器右侧居中
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
protected getDialogPosition() {
const container = this.registry.container;
if (!container) return { x: 100, y: 100 };
// 如果已打开,先销毁
this.destroy();
const paddingRight = 20;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
// 创建面板(暂时空内容)
return {
x: containerWidth - this.dialogWidth - paddingRight,
y: (containerHeight - this.dialogHeight) / 2
};
}
/** 创建对话框内容 */
protected createContent(): HTMLElement {
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();
return this.panel.element;
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
}
/**
* 销毁弹窗和面板
*/
public destroy(): void {
// 先保存 dialog 引用,避免在回调中重复调用
const dialog = this.dialog;
// 立即清空引用,防止递归
this.dialog = null;
// 关闭弹窗
if (dialog) {
dialog.destroy();
/** 对话框关闭时的回调 */
protected onDialogClose(): void {
if (this.registry.walkControl && this.registry.walkControl.panel) {
this.registry.walkControl.panel.setPathModeActive(false);
}
}
// 销毁面板
/** 销毁前的清理 */
protected onBeforeDestroy(): void {
if (this.panel) {
this.panel.destroy();
this.panel = null;

View File

@@ -1,94 +1,64 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimDialog } from '../components/dialog';
/**
* 漫游平面图对话框管理器
* 负责管理漫游平面图对话框的显示和交互
*/
import { BaseDialogManager } from '../core/base-dialog-manager';
import { WalkPlanViewPanel } from '../components/walk-plan-view-panel';
/**
* 平面图弹窗管理器
* 漫游平面图对话框管理器
* 继承自 BaseDialogManager提供漫游平面图的对话框管理功能
*/
export class WalkPlanViewDialogManager extends BimComponent {
private dialogId = 'walk-plan-view-dialog';
private dialog: BimDialog | null = null;
export class WalkPlanViewDialogManager extends BaseDialogManager {
/** 漫游平面图面板实例 */
private panel: WalkPlanViewPanel | null = null;
constructor(engine: BimEngine) {
super(engine);
}
/** 对话框唯一标识 */
protected get dialogId() { return 'walk-plan-view-dialog'; }
/** 对话框标题(国际化 key */
protected get dialogTitle() { return 'walkControl.planView.dialogTitle'; }
/** 对话框宽度 */
protected get dialogWidth() { return 300; }
/** 对话框高度 */
protected get dialogHeight(): number { return 400; }
public init(): void {
// 可以在这里监听事件
}
/** 初始化 */
public init(): void {}
/**
* 显示弹窗
* 获取对话框位置
* 定位在容器左下角
*/
public show(): void {
if (!this.engine.dialog || !this.engine.container) {
console.warn('Dialog manager or container is not initialized');
return;
}
protected getDialogPosition() {
const container = this.registry.container;
if (!container) return { x: 20, y: 100 };
// 如果已打开,先销毁
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();
return {
x: paddingLeft,
y: containerHeight - this.dialogHeight - paddingBottom
};
}
/**
* 隐藏弹窗
*/
public hide(): void {
this.destroy();
/** 创建对话框内容 */
protected createContent(): HTMLElement {
this.panel = new WalkPlanViewPanel();
this.panel.init();
return this.panel.element;
}
/**
* 销毁弹窗和面板
*/
public destroy(): void {
// 先保存 dialog 引用,避免在回调中重复调用
const dialog = this.dialog;
// 立即清空引用,防止递归
this.dialog = null;
// 关闭弹窗
if (dialog) {
dialog.destroy();
/** 对话框关闭时的回调 */
protected onDialogClose(): void {
if (this.registry.walkControl && this.registry.walkControl.panel) {
this.registry.walkControl.panel.setPlanViewActive(false);
}
}
// 销毁面板
/** 销毁前的清理 */
protected onBeforeDestroy(): void {
if (this.panel) {
this.panel.destroy();
this.panel = null;