Files
bim_engine/demo/multi-tab.html
yuding e91a460fea feat(demo): add multi-tab demo with dual BimEngine instances
New demo page with two independent BimEngine instances side by side,
draggable divider for resizing, and proper SDK-layer resize handling.
Replaces the iframe-based approach now that ManagerRegistry is instance-based.
Removes unused panelViewer vite config entry.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-02-28 10:09:17 +08:00

405 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>iFlow Engine - 多Tab加载</title>
<script src="./lib/iflow-engine.umd.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #1e1e1e;
color: #e0e0e0;
}
/* ===== 顶部控制栏 ===== */
.top-bar {
height: 48px;
background: #2d2d2d;
border-bottom: 1px solid #404040;
display: flex;
align-items: center;
padding: 0 16px;
gap: 16px;
flex-shrink: 0;
}
.top-bar .title {
font-size: 14px;
font-weight: 600;
color: #fff;
white-space: nowrap;
}
.top-bar .back-btn {
padding: 4px 12px;
font-size: 13px;
background: transparent;
color: #8ab4f8;
border: 1px solid #555;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
transition: all 0.15s;
}
.top-bar .back-btn:hover {
background: #3a3a3a;
border-color: #8ab4f8;
}
.top-bar .separator {
width: 1px;
height: 24px;
background: #404040;
}
/* 模型 URL 输入区 */
.model-inputs {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.model-input-group {
display: flex;
align-items: center;
gap: 6px;
flex: 1;
min-width: 0;
}
.model-input-group label {
font-size: 12px;
color: #aaa;
white-space: nowrap;
}
.model-input-group input {
flex: 1;
min-width: 0;
height: 28px;
padding: 0 8px;
font-size: 12px;
background: #1e1e1e;
color: #e0e0e0;
border: 1px solid #555;
border-radius: 3px;
outline: none;
transition: border-color 0.15s;
}
.model-input-group input:focus {
border-color: #0078d4;
}
.load-btn {
height: 28px;
padding: 0 14px;
font-size: 12px;
font-weight: 500;
background: #0078d4;
color: #fff;
border: none;
border-radius: 3px;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s;
}
.load-btn:hover {
background: #1a88e0;
}
.load-btn:active {
background: #006abc;
}
/* ===== 主内容区:双面板 ===== */
.panels-container {
display: flex;
height: calc(100vh - 48px);
position: relative;
}
.panel {
position: relative;
overflow: hidden;
background: #1a1a1a;
}
.panel-left {
flex: 1 1 0;
min-width: 200px;
}
.panel-right {
flex: 1 1 0;
min-width: 200px;
}
/* 引擎容器 */
.engine-container {
width: 100%;
height: 100%;
}
/* 面板标签 */
.panel-label {
position: absolute;
top: 8px;
left: 12px;
z-index: 10;
font-size: 12px;
font-weight: 600;
color: rgba(255, 255, 255, 0.7);
background: rgba(0, 0, 0, 0.5);
padding: 3px 10px;
border-radius: 3px;
pointer-events: none;
user-select: none;
}
/* ===== 拖拽分割线 ===== */
.divider {
width: 6px;
background: #333;
cursor: col-resize;
flex-shrink: 0;
position: relative;
z-index: 20;
transition: background 0.15s;
}
.divider:hover,
.divider.active {
background: #0078d4;
}
.divider::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 32px;
background: rgba(255, 255, 255, 0.3);
border-radius: 1px;
}
.divider:hover::after,
.divider.active::after {
background: rgba(255, 255, 255, 0.6);
}
.panels-container.dragging {
cursor: col-resize;
user-select: none;
}
</style>
</head>
<body>
<!-- 顶部控制栏 -->
<div class="top-bar">
<a class="back-btn" href="./index.html">← 返回 Demo</a>
<div class="separator"></div>
<span class="title">多Tab加载双实例</span>
<div class="separator"></div>
<div class="model-inputs">
<!-- 左面板 -->
<div class="model-input-group">
<label>左面板:</label>
<input type="text" id="url-left"
value="https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e/"
placeholder="模型 URL">
<button class="load-btn" onclick="loadLeft()">加载</button>
</div>
<!-- 右面板 -->
<div class="model-input-group">
<label>右面板:</label>
<input type="text" id="url-right"
value="https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/e49a5fd1-3018-4938-9a52-6862b56a190b/"
placeholder="模型 URL">
<button class="load-btn" onclick="loadRight()">加载</button>
</div>
</div>
</div>
<!-- 双面板区域 -->
<div class="panels-container" id="panels-container">
<div class="panel panel-left" id="panel-left">
<div class="panel-label">面板 A</div>
<div class="engine-container" id="engine-left"></div>
</div>
<div class="divider" id="divider"></div>
<div class="panel panel-right" id="panel-right">
<div class="panel-label">面板 B</div>
<div class="engine-container" id="engine-right"></div>
</div>
</div>
<script>
// ===== 元素引用 =====
const panelsContainer = document.getElementById('panels-container');
const panelLeft = document.getElementById('panel-left');
const panelRight = document.getElementById('panel-right');
const divider = document.getElementById('divider');
const urlLeft = document.getElementById('url-left');
const urlRight = document.getElementById('url-right');
// ===== 两个独立的 BimEngine 实例 =====
const BimEngine = window.IflowEngine.BimEngine;
let engineLeft = null;
let engineRight = null;
/**
* 初始化单个引擎实例
*/
function createEngine(containerId) {
const engine = new BimEngine(containerId, { locale: 'zh-CN', theme: 'dark' });
const ok = engine.engine.initialize({
backgroundColor: 0x333333,
version: 'v2',
showStats: false,
showViewCube: true
});
if (!ok) {
console.error('[MultiTab] 引擎初始化失败:', containerId);
return null;
}
console.log('[MultiTab] 引擎初始化成功:', containerId);
return engine;
}
/**
* 加载模型到指定引擎
*/
function loadModel(engine, url) {
if (!engine || !engine.engine) return;
const trimmed = url.trim();
if (!trimmed) return;
engine.engine.loadModel([trimmed], {
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1]
});
console.log('[MultiTab] 加载模型:', trimmed);
}
function loadLeft() {
loadModel(engineLeft, urlLeft.value);
}
function loadRight() {
loadModel(engineRight, urlRight.value);
}
/**
* 通知引擎调整渲染器尺寸
*/
function resizeEngine(engine) {
if (!engine || !engine.engine) return;
engine.engine.resize();
}
function resizeAll() {
//resizeEngine(engineLeft);
//resizeEngine(engineRight);
}
// ===== 分割线拖拽逻辑 =====
let isDragging = false;
let startX = 0;
let startLeftWidth = 0;
divider.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startLeftWidth = panelLeft.getBoundingClientRect().width;
panelsContainer.classList.add('dragging');
divider.classList.add('active');
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const containerWidth = panelsContainer.getBoundingClientRect().width;
const dividerWidth = divider.getBoundingClientRect().width;
const available = containerWidth - dividerWidth;
let newLeftWidth = startLeftWidth + dx;
const minWidth = 200;
newLeftWidth = Math.max(minWidth, Math.min(newLeftWidth, available - minWidth));
const leftPercent = (newLeftWidth / available) * 100;
const rightPercent = 100 - leftPercent;
panelLeft.style.flex = `0 0 ${leftPercent}%`;
panelRight.style.flex = `0 0 ${rightPercent}%`;
// 拖拽时实时 resize 引擎
resizeAll();
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
panelsContainer.classList.remove('dragging');
divider.classList.remove('active');
resizeAll();
});
// 窗口 resize
window.addEventListener('resize', resizeAll);
// 回车键触发加载
urlLeft.addEventListener('keydown', (e) => {
if (e.key === 'Enter') loadLeft();
});
urlRight.addEventListener('keydown', (e) => {
if (e.key === 'Enter') loadRight();
});
// ===== 启动 =====
window.onload = () => {
try {
engineLeft = createEngine('engine-left');
engineRight = createEngine('engine-right');
// 自动加载默认模型
const leftUrl = urlLeft.value.trim();
const rightUrl = urlRight.value.trim();
if (leftUrl) loadLeft();
if (rightUrl) loadRight();
} catch (err) {
console.error('[MultiTab] 初始化失败:', err);
}
};
</script>
</body>
</html>