添加测试信息
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
.bim-engine-wrapper {
|
||||
position: relative;
|
||||
/* 添加相对定位作为参照物 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: sans-serif;
|
||||
color: #333;
|
||||
padding: 20px;
|
||||
background-color: #e16969;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
color: #bf1d1d;
|
||||
box-sizing: border-box;
|
||||
/* 确保 padding 不会撑大容器 */
|
||||
overflow: hidden;
|
||||
/* 防止内容溢出 */
|
||||
}
|
||||
|
||||
|
||||
/* ... (中间代码不变) ... */
|
||||
|
||||
/* 操作按钮组容器 */
|
||||
|
||||
@@ -2,11 +2,17 @@ import './bim-engine.css';
|
||||
import { ToolbarManager } from './managers/toolbar-manager';
|
||||
import { ButtonGroupManager } from './managers/button-group-manager';
|
||||
import { DialogManager } from './managers/dialog-manager';
|
||||
import { EngineManager } from './managers/engine-manager';
|
||||
import type { EngineOptions, ModelLoadOptions } from './components/engine';
|
||||
import { localeManager } from './services/locale';
|
||||
import { themeManager } from './services/theme';
|
||||
import type { LocaleType } from './locales/types';
|
||||
import type { ThemeType, ThemeConfig } from './themes/types';
|
||||
|
||||
export type { EngineOptions, ModelLoadOptions };
|
||||
|
||||
|
||||
|
||||
export class BimEngine {
|
||||
private container: HTMLElement;
|
||||
private wrapper: HTMLElement | null = null;
|
||||
@@ -15,11 +21,18 @@ export class BimEngine {
|
||||
public toolbar: ToolbarManager | null = null; // 底部专用
|
||||
public buttonGroup: ButtonGroupManager | null = null; // 通用
|
||||
public dialog: DialogManager | null = null;
|
||||
public engine: EngineManager | null = null; // 3D 引擎管理器
|
||||
|
||||
public get localeManager() { return localeManager; }
|
||||
public get themeManager() { return themeManager; }
|
||||
|
||||
constructor(container: HTMLElement | string, options?: { locale?: LocaleType; theme?: ThemeType }) {
|
||||
constructor(
|
||||
container: HTMLElement | string,
|
||||
options?: {
|
||||
locale?: LocaleType;
|
||||
theme?: ThemeType;
|
||||
}
|
||||
) {
|
||||
const el = typeof container === 'string' ? document.getElementById(container) : container;
|
||||
if (!el) throw new Error('Container not found');
|
||||
this.container = el;
|
||||
@@ -47,13 +60,14 @@ export class BimEngine {
|
||||
this.wrapper.className = 'bim-engine-wrapper';
|
||||
this.container.appendChild(this.wrapper);
|
||||
|
||||
// 初始化管理器
|
||||
// 创建 3D 引擎管理器
|
||||
this.engine = new EngineManager(this.wrapper);
|
||||
|
||||
// 初始化其他管理器
|
||||
this.dialog = new DialogManager(this.wrapper);
|
||||
this.toolbar = new ToolbarManager(this.wrapper);
|
||||
this.buttonGroup = new ButtonGroupManager(this.wrapper);
|
||||
|
||||
// --- 创建左上角按钮组 (需求 1 & 2) ---
|
||||
this.createTopLeftGroup();
|
||||
|
||||
// 初始主题
|
||||
this.updateTheme(themeManager.getTheme());
|
||||
@@ -69,34 +83,23 @@ export class BimEngine {
|
||||
});
|
||||
}
|
||||
|
||||
private createTopLeftGroup() {
|
||||
if (!this.buttonGroup) return;
|
||||
/**
|
||||
* 初始化 3D 引擎组件
|
||||
* 注意:只初始化引擎,不加载模型。模型加载在使用层(如 demo.html)进行
|
||||
* @param options 引擎配置选项(可选)
|
||||
*/
|
||||
public initEngine(options?: Omit<EngineOptions, 'container'>): boolean {
|
||||
if (!this.engine) {
|
||||
console.error('[BimEngine] Engine manager not available.');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.topLeftGroup = this.buttonGroup.create({
|
||||
position: 'top-left',
|
||||
direction: 'column',
|
||||
align: 'vertical',
|
||||
backgroundColor: '#ff00ff', // 自定义背景色,不会被主题覆盖
|
||||
showLabel: false
|
||||
});
|
||||
|
||||
this.topLeftGroup.addGroup('main');
|
||||
this.topLeftGroup.addButton({
|
||||
id: 'menu-btn',
|
||||
groupId: 'main',
|
||||
type: 'button',
|
||||
label: 'Menu', // 应该用 translation key
|
||||
icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>',
|
||||
onClick: () => {
|
||||
alert("点击按钮")
|
||||
}
|
||||
});
|
||||
|
||||
// 手动 render 一次以显示
|
||||
this.topLeftGroup.render();
|
||||
// 调用 manager 的 initialize 方法初始化引擎
|
||||
return this.engine.initialize(options);
|
||||
}
|
||||
|
||||
private updateTheme(theme: ThemeConfig) {
|
||||
|
||||
if (this.wrapper) {
|
||||
this.wrapper.style.backgroundColor = theme.background;
|
||||
this.wrapper.style.color = theme.textPrimary;
|
||||
@@ -106,6 +109,7 @@ export class BimEngine {
|
||||
public destroy() {
|
||||
this.toolbar?.destroy();
|
||||
this.buttonGroup?.destroy();
|
||||
this.engine?.destroy();
|
||||
this.dialog = null;
|
||||
this.container.innerHTML = '';
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
background-color: var(--bim-btn-group-section-bg, rgba(17, 17, 17, 0.88));
|
||||
border-radius: 6px;
|
||||
padding: 4px;
|
||||
/* 添加阴影效果,增强视觉层次感 */
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.bim-btn-group-root.dir-row .bim-btn-group-section {
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1000;
|
||||
z-index: 10001; /* 提高 z-index,确保在第三方 SDK (10000) 之上 */
|
||||
color: var(--bim-dialog-title-color);
|
||||
overflow: hidden;
|
||||
min-width: 200px;
|
||||
min-height: 100px;
|
||||
pointer-events: auto; /* 确保弹窗可以接收事件 */
|
||||
}
|
||||
|
||||
.bim-dialog-header {
|
||||
|
||||
@@ -20,6 +20,9 @@ export class BimDialog implements IBimComponent {
|
||||
private unsubscribeTheme: (() => void) | null = null;
|
||||
private unsubscribeLocale: (() => void) | null = null;
|
||||
|
||||
// 性能优化:用于存储 requestAnimationFrame 的 ID
|
||||
private rafId: number | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param options 弹窗配置选项
|
||||
@@ -116,7 +119,7 @@ export class BimDialog implements IBimComponent {
|
||||
|
||||
if (this.options.id) el.id = this.options.id;
|
||||
|
||||
// 应用颜色配置到 CSS 变量 (局部作用域)
|
||||
// 应用颜色配置到 CSS 变量
|
||||
const style = el.style;
|
||||
if (this.options.backgroundColor) style.setProperty('--bim-dialog-bg', this.options.backgroundColor);
|
||||
if (this.options.headerBackgroundColor) style.setProperty('--bim-dialog-header-bg', this.options.headerBackgroundColor);
|
||||
@@ -139,7 +142,10 @@ export class BimDialog implements IBimComponent {
|
||||
const closeBtn = document.createElement('span');
|
||||
closeBtn.className = 'bim-dialog-close';
|
||||
closeBtn.innerHTML = '×';
|
||||
closeBtn.onclick = () => this.close();
|
||||
// 修复 TS 报错:去掉未使用的参数 e
|
||||
closeBtn.onclick = () => {
|
||||
this.close();
|
||||
};
|
||||
|
||||
header.appendChild(title);
|
||||
header.appendChild(closeBtn);
|
||||
@@ -163,6 +169,26 @@ export class BimDialog implements IBimComponent {
|
||||
el.appendChild(resizeHandle);
|
||||
}
|
||||
|
||||
// ==================== 事件拦截核心逻辑 ====================
|
||||
// 定义阻断逻辑:只阻止冒泡,不阻止捕获,也不阻止默认行为(除非显式阻止)
|
||||
const stopPropagation = (e: Event) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
// 现代浏览器和 3D 引擎 (Three.js/Cesium) 交互事件
|
||||
const events = [
|
||||
'click', 'dblclick', 'contextmenu', 'wheel',
|
||||
'mousedown', 'mouseup', 'mousemove',
|
||||
'touchstart', 'touchend', 'touchmove',
|
||||
'pointerdown', 'pointerup', 'pointermove', 'pointerenter', 'pointerleave', 'pointerover', 'pointerout'
|
||||
];
|
||||
|
||||
// 绑定监听器 (默认冒泡阶段)
|
||||
// 这样内部元素(如关闭按钮)先触发,然后冒泡到这里被拦截,不再传给地图
|
||||
events.forEach(eventType => {
|
||||
el.addEventListener(eventType, stopPropagation, { passive: false });
|
||||
});
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
@@ -183,7 +209,6 @@ export class BimDialog implements IBimComponent {
|
||||
*/
|
||||
private initPosition() {
|
||||
const pos = this.options.position;
|
||||
|
||||
const elRect = this.element.getBoundingClientRect();
|
||||
|
||||
// 计算相对父容器的定位
|
||||
@@ -218,7 +243,6 @@ export class BimDialog implements IBimComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// 简单的边界检查,防止初始位置溢出
|
||||
left = Math.max(0, Math.min(left, pW - elW));
|
||||
top = Math.max(0, Math.min(top, pH - elH));
|
||||
|
||||
@@ -227,53 +251,81 @@ export class BimDialog implements IBimComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化拖拽功能
|
||||
* 初始化拖拽功能 (性能优化 + 解决粘手)
|
||||
*/
|
||||
private initDrag() {
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let startLeft = 0;
|
||||
let startTop = 0;
|
||||
let containerW = 0;
|
||||
let containerH = 0;
|
||||
let elW = 0;
|
||||
let elH = 0;
|
||||
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault(); // 阻止默认行为(如选中文本),非常重要,防止卡顿
|
||||
e.stopPropagation(); // 阻止传递给 Three.js
|
||||
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
startLeft = this.element.offsetLeft;
|
||||
startTop = this.element.offsetTop;
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
// 缓存尺寸,减少 reflow
|
||||
containerW = this.container.clientWidth;
|
||||
containerH = this.container.clientHeight;
|
||||
elW = this.element.offsetWidth;
|
||||
elH = this.element.offsetHeight;
|
||||
|
||||
// 关键:使用 capture: true
|
||||
// 确保即使 createDom 阻止了冒泡,document 也能在捕获阶段收到事件
|
||||
document.addEventListener('mousemove', onMouseMove, { capture: true });
|
||||
document.addEventListener('mouseup', onMouseUp, { capture: true });
|
||||
};
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let newLeft = startLeft + dx;
|
||||
let newTop = startTop + dy;
|
||||
// 节流优化:使用 requestAnimationFrame
|
||||
if (this.rafId) return;
|
||||
|
||||
// 边界限制,防止拖出容器
|
||||
const maxLeft = this.container.clientWidth - this.element.offsetWidth;
|
||||
const maxTop = this.container.clientHeight - this.element.offsetHeight;
|
||||
this.rafId = requestAnimationFrame(() => {
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
|
||||
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
|
||||
newTop = Math.max(0, Math.min(newTop, maxTop));
|
||||
let newLeft = startLeft + dx;
|
||||
let newTop = startTop + dy;
|
||||
|
||||
this.element.style.left = `${newLeft}px`;
|
||||
this.element.style.top = `${newTop}px`;
|
||||
const maxLeft = containerW - elW;
|
||||
const maxTop = containerH - elH;
|
||||
|
||||
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
|
||||
newTop = Math.max(0, Math.min(newTop, maxTop));
|
||||
|
||||
this.element.style.left = `${newLeft}px`;
|
||||
this.element.style.top = `${newTop}px`;
|
||||
|
||||
this.rafId = null;
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
// 移除监听
|
||||
document.removeEventListener('mousemove', onMouseMove, { capture: true });
|
||||
document.removeEventListener('mouseup', onMouseUp, { capture: true });
|
||||
};
|
||||
|
||||
this.header.addEventListener('mousedown', onMouseDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化缩放功能
|
||||
* 初始化缩放功能 (性能优化 + 解决粘手)
|
||||
*/
|
||||
private initResize() {
|
||||
const handle = this.element.querySelector('.bim-dialog-resize-handle') as HTMLElement;
|
||||
@@ -292,24 +344,38 @@ export class BimDialog implements IBimComponent {
|
||||
startW = this.element.offsetWidth;
|
||||
startH = this.element.offsetHeight;
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
// 关键:使用 capture: true
|
||||
document.addEventListener('mousemove', onMouseMove, { capture: true });
|
||||
document.addEventListener('mouseup', onMouseUp, { capture: true });
|
||||
};
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const newW = Math.max(this.options.minWidth || 100, startW + dx);
|
||||
const newH = Math.max(this.options.minHeight || 50, startH + dy);
|
||||
if (this.rafId) return;
|
||||
|
||||
this.element.style.width = `${newW}px`;
|
||||
this.element.style.height = `${newH}px`;
|
||||
this.rafId = requestAnimationFrame(() => {
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
|
||||
const newW = Math.max(this.options.minWidth || 100, startW + dx);
|
||||
const newH = Math.max(this.options.minHeight || 50, startH + dy);
|
||||
|
||||
this.element.style.width = `${newW}px`;
|
||||
this.element.style.height = `${newH}px`;
|
||||
|
||||
this.rafId = null;
|
||||
});
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
document.removeEventListener('mousemove', onMouseMove, { capture: true });
|
||||
document.removeEventListener('mouseup', onMouseUp, { capture: true });
|
||||
};
|
||||
|
||||
handle.addEventListener('mousedown', onMouseDown);
|
||||
@@ -333,6 +399,13 @@ export class BimDialog implements IBimComponent {
|
||||
*/
|
||||
public close() {
|
||||
if (this._isDestroyed) return;
|
||||
|
||||
// 清理可能存在的动画帧,防止报错
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId);
|
||||
this.rafId = null;
|
||||
}
|
||||
|
||||
if (this.unsubscribeTheme) {
|
||||
this.unsubscribeTheme();
|
||||
this.unsubscribeTheme = null;
|
||||
@@ -354,4 +427,4 @@ export class BimDialog implements IBimComponent {
|
||||
public destroy() {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/index.ts
36
src/index.ts
@@ -8,4 +8,38 @@ export { Toolbar } from './components/button-group/toolbar';
|
||||
export type { OptButton, ButtonGroup, ButtonGroupOptions, ClickPayload } from './components/button-group/index.type';
|
||||
|
||||
// 导出主引擎类
|
||||
export { BimEngine };
|
||||
export { BimEngine };
|
||||
|
||||
// 导出 3D 引擎相关类型
|
||||
export type { EngineOptions, ModelLoadOptions } from './components/engine';
|
||||
|
||||
// 导出 createEngine 函数(从第三方 SDK 重新导出)
|
||||
// 注意:createEngine 的实际实现来自 bim-engine-sdk.es.js
|
||||
//
|
||||
// 使用方式:
|
||||
// 1. 直接从 SDK 文件导入(推荐,如 Vue 示例):
|
||||
// import { createEngine } from '/engine/bim-engine-sdk.es.js';
|
||||
//
|
||||
// 2. 从主入口导入(如果构建配置支持):
|
||||
// import { createEngine } from 'bim-engine-sdk';
|
||||
//
|
||||
// 示例:
|
||||
// ```javascript
|
||||
// const engine = createEngine({
|
||||
// containerId: 'vue2-container',
|
||||
// backgroundColor: 0x333333,
|
||||
// version: 'v1',
|
||||
// showStats: true,
|
||||
// showViewCube: true
|
||||
// });
|
||||
//
|
||||
// engine.loader.loadModel(url, {
|
||||
// position: [10, -5, 0],
|
||||
// rotation: [0, 0, 0],
|
||||
// scale: [1, 1, 1]
|
||||
// });
|
||||
// ```
|
||||
|
||||
// 重新导出 createEngine(从 SDK 文件)
|
||||
// 注意:如果直接导入失败,用户应该直接从 bim-engine-sdk.es.js 文件导入
|
||||
export { createEngine } from './bim-engine-sdk.es.js';
|
||||
Reference in New Issue
Block a user