- fix(engine): adapt click handler to base engine array format data[0].url/ids - feat(toolbar): add perspective/orthographic camera switch button with dynamic icon - fix(dialog): clamp resize to container bounds to prevent overflow - feat(demo): add auto-combine feature with robust URL parsing and validation - fix(walk): close minimap on walk exit and sync map state between toolbar and walk panel - fix(engine): correct MiniMap getstate() casing to match base engine API - build: rebuild demo libs
675 lines
24 KiB
HTML
675 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>iFlow Engine Demo</title>
|
||
<!-- 从本地 lib 目录加载 SDK 文件 -->
|
||
<script src="./lib/iflow-engine.umd.js"></script>
|
||
<style>
|
||
* {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background-color: #f5f5f5;
|
||
height: 100vh;
|
||
display: flex;
|
||
overflow: hidden;
|
||
/* 防止整个页面滚动 */
|
||
}
|
||
|
||
/* 左侧侧边栏:控制面板 */
|
||
.sidebar {
|
||
width: 320px;
|
||
background: white;
|
||
border-right: 1px solid #e0e0e0;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
/* 内容过多时可滚动 */
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05);
|
||
z-index: 10;
|
||
}
|
||
|
||
.sidebar h1 {
|
||
font-size: 1.4rem;
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.control-group {
|
||
background: #f9f9f9;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
border: 1px solid #eee;
|
||
}
|
||
|
||
.control-group h2 {
|
||
font-size: 1rem;
|
||
color: #555;
|
||
margin-bottom: 10px;
|
||
padding-bottom: 5px;
|
||
border-bottom: 2px solid #e0e0e0;
|
||
}
|
||
|
||
.btn-container {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
button {
|
||
padding: 6px 12px;
|
||
font-size: 0.9rem;
|
||
border: 1px solid #ddd;
|
||
background: #fff;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
flex: 1 1 auto;
|
||
/* 按钮自动填充 */
|
||
}
|
||
|
||
button:hover {
|
||
background: #f0f0f0;
|
||
border-color: #ccc;
|
||
}
|
||
|
||
button.primary {
|
||
background: #0078d4;
|
||
color: white;
|
||
border-color: #0063b1;
|
||
}
|
||
|
||
button.primary:hover {
|
||
background: #0063b1;
|
||
}
|
||
|
||
/* 右侧主内容区:引擎展示 */
|
||
.main-content {
|
||
flex: 1;
|
||
position: relative;
|
||
background: #eef2f5;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
#app {
|
||
width: 100%;
|
||
height: 100%;
|
||
background: #fff;
|
||
position: relative;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<!-- 左侧控制面板 -->
|
||
<aside class="sidebar">
|
||
<h1>BIM SDK Demo</h1>
|
||
|
||
<!-- 1. 语言设置 -->
|
||
<div class="control-group">
|
||
<h2>🌍 语言 (Language)</h2>
|
||
<div class="btn-container">
|
||
<button class="primary" onclick="setLang('zh-CN')">中文</button>
|
||
<button class="primary" onclick="setLang('en-US')">English</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 2. 弹窗测试 -->
|
||
<div class="control-group">
|
||
<h2>🪟 弹窗 (Dialog)</h2>
|
||
<div class="btn-container">
|
||
<button onclick="openTestDialog()">测试弹窗</button>
|
||
<button onclick="openInfoDialog()">信息弹窗</button>
|
||
<button onclick="openRedDialog()">警告弹窗</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 3. 工具栏操作 -->
|
||
<div class="control-group">
|
||
<h2>🛠️ 工具栏 (Toolbar)</h2>
|
||
<div class="btn-container">
|
||
<button onclick="toggleToolbar()">显隐工具栏</button>
|
||
<button onclick="toggleLabel()">显隐标签</button>
|
||
<button onclick="toggleLocationBtn()">显隐定位按钮</button>
|
||
</div>
|
||
<div class="btn-container" style="margin-top: 8px;">
|
||
<button onclick="addCustomGroup()">加组</button>
|
||
<button onclick="addCustomButton()">加按钮</button>
|
||
</div>
|
||
<div class="btn-container" style="margin-top: 8px;">
|
||
<button onclick="setToolbarType('default')">默认样式</button>
|
||
<button onclick="setToolbarType('glass-pill')">胶囊样式</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 4. 样式主题 -->
|
||
<div class="control-group">
|
||
<h2>🎨 样式 (Theme)</h2>
|
||
<div class="btn-container">
|
||
<button onclick="setTheme('dark')">深色 (Dark)</button>
|
||
<button onclick="setTheme('light')">浅色 (Light)</button>
|
||
<button onclick="setCustomTheme()">自定义 (Red)</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 6. 功能面板 -->
|
||
<div class="control-group">
|
||
<h2>📑 功能面板 (Panels)</h2>
|
||
<div class="btn-container">
|
||
<button onclick="openPropertyPanel()">属性面板</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 7. 多Tab加载 -->
|
||
<div class="control-group">
|
||
<h2>📺 多Tab加载 (Multi-Tab)</h2>
|
||
<div class="btn-container">
|
||
<button class="primary" onclick="window.open('./multi-tab.html', '_blank')">打开多Tab视图</button>
|
||
</div>
|
||
<div style="margin-top: 8px; font-size: 0.8rem; color: #888;">
|
||
两个独立面板,各自加载模型,可拖拽分割线调整宽度
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 8. 构件树数据 -->
|
||
<div class="control-group">
|
||
<h2>🌳 构件树 (Tree Data)</h2>
|
||
<div class="btn-container">
|
||
<button onclick="getLevelTree()">楼层树</button>
|
||
<button onclick="getTypeTree()">类型树</button>
|
||
<button onclick="getMajorTree()">专业树</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 5. 3D 引擎 -->
|
||
<div class="control-group">
|
||
<h2>🎮 3D 引擎 (Engine3D)</h2>
|
||
<div class="btn-container">
|
||
<button class="primary" onclick="initEngine3D()">初始化引擎</button>
|
||
<button class="primary" onclick="loadModel()">加载模型</button>
|
||
<button onclick="switchModel()">切换模型</button>
|
||
<button onclick="loadCombinedModel()">组合模型</button>
|
||
<button onclick="openCombineDialog()">自动组合</button>
|
||
</div>
|
||
<div class="btn-container" style="margin-top: 8px;">
|
||
<button onclick="pauseRendering()">暂停渲染</button>
|
||
<button onclick="resumeRendering()">恢复渲染</button>
|
||
</div>
|
||
<div style="margin-top: 10px; font-size: 0.85rem; color: #666;">
|
||
<div>状态: <span id="engine-status">未初始化</span></div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- 右侧主区域 -->
|
||
<main class="main-content">
|
||
<div id="app"></div>
|
||
</main>
|
||
|
||
<!-- 自动组合弹窗 -->
|
||
<div id="combine-overlay" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:9999; display:none; justify-content:center; align-items:center;">
|
||
<div style="background:#fff; border-radius:10px; padding:24px; width:520px; max-width:90vw; box-shadow:0 8px 32px rgba(0,0,0,.18);">
|
||
<h3 style="margin:0 0 12px; font-size:1.1rem; color:#333;">自动组合加载</h3>
|
||
<p style="margin:0 0 10px; font-size:.85rem; color:#888;">请输入模型 URL,每行一个或用英文逗号分隔:</p>
|
||
<textarea id="combine-urls" rows="6" style="width:100%; padding:10px; font-size:.85rem; border:1px solid #ddd; border-radius:6px; resize:vertical; font-family:monospace;" placeholder="https://example.com/model1/ https://example.com/model2/"></textarea>
|
||
<div style="display:flex; justify-content:flex-end; gap:8px; margin-top:14px;">
|
||
<button onclick="closeCombineDialog()" style="min-width:72px;">取消</button>
|
||
<button class="primary" onclick="confirmCombineLoad()" style="min-width:72px;">确定加载</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let engine = null;
|
||
let isToolbarVisible = true;
|
||
let isLabelVisible = true;
|
||
let isLocationVisible = true;
|
||
let customGroupAdded = false;
|
||
let engine3DInitialized = false;
|
||
|
||
// 初始化引擎
|
||
window.onload = () => {
|
||
if (window.IflowEngine) {
|
||
const Engine = window.IflowEngine.BimEngine;
|
||
try {
|
||
engine = new Engine('app', { locale: 'zh-CN' });
|
||
initEngine3D();
|
||
console.log('Engine initialized:', engine);
|
||
} catch (err) {
|
||
console.error('Init failed:', err);
|
||
}
|
||
} else {
|
||
console.error('SDK not found');
|
||
}
|
||
};
|
||
|
||
// --- 语言设置 ---
|
||
function setLang(lang) {
|
||
if (engine) engine.setLocale(lang);
|
||
}
|
||
|
||
// --- 弹窗测试 ---
|
||
function openTestDialog() {
|
||
if (!engine || !engine.dialog) return;
|
||
engine.dialog.create({
|
||
title: 'dialog.testTitle',
|
||
width: 350,
|
||
height: 200,
|
||
position: 'center',
|
||
draggable: true,
|
||
resizable: true
|
||
});
|
||
}
|
||
|
||
function openInfoDialog() {
|
||
if (!engine || !engine.dialog) return;
|
||
engine.dialog.showInfoDialog();
|
||
}
|
||
|
||
function openRedDialog() {
|
||
if (!engine || !engine.dialog) return;
|
||
engine.dialog.create({
|
||
title: 'Alert',
|
||
content: '<div style="color: #ffcccc;">Critical Warning!</div>',
|
||
width: 300,
|
||
height: 150,
|
||
backgroundColor: 'rgba(100, 0, 0, 0.95)',
|
||
headerBackgroundColor: '#cc0000',
|
||
titleColor: '#ffffff',
|
||
borderColor: '#ff6666',
|
||
position: { x: 50, y: 50 }
|
||
});
|
||
}
|
||
|
||
// --- 工具栏操作 ---
|
||
function toggleToolbar() {
|
||
if (!engine || !engine.toolbar) return;
|
||
isToolbarVisible = !isToolbarVisible;
|
||
engine.toolbar.setVisible(isToolbarVisible);
|
||
}
|
||
|
||
function toggleLabel() {
|
||
if (!engine || !engine.toolbar) return;
|
||
isLabelVisible = !isLabelVisible;
|
||
engine.toolbar.setShowLabel(isLabelVisible);
|
||
}
|
||
|
||
function toggleLocationBtn() {
|
||
if (!engine || !engine.toolbar) return;
|
||
isLocationVisible = !isLocationVisible;
|
||
engine.toolbar.setButtonVisibility('location', isLocationVisible);
|
||
}
|
||
|
||
function addCustomGroup() {
|
||
if (!engine || !engine.toolbar) return;
|
||
if (customGroupAdded) {
|
||
alert('已添加过');
|
||
return;
|
||
}
|
||
engine.toolbar.addGroup('custom-group', 'group-1');
|
||
customGroupAdded = true;
|
||
}
|
||
|
||
function addCustomButton() {
|
||
if (!engine || !engine.toolbar) return;
|
||
if (!customGroupAdded) {
|
||
alert('Please add custom group first / 请先添加自定义组');
|
||
return;
|
||
}
|
||
const btnId = 'custom-btn-' + Date.now();
|
||
engine.toolbar.addButton({
|
||
id: btnId,
|
||
groupId: 'custom-group',
|
||
type: 'button',
|
||
label: 'New',
|
||
icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>',
|
||
onClick: (btn) => {
|
||
alert('Clicked: ' + btn.label);
|
||
}
|
||
});
|
||
}
|
||
|
||
function setToolbarType(type) {
|
||
if (!engine || !engine.toolbar) return;
|
||
engine.toolbar.setType(type);
|
||
console.log('Toolbar type changed to:', type);
|
||
}
|
||
|
||
// --- 主题操作 ---
|
||
function setTheme(themeName) {
|
||
if (engine) engine.setTheme(themeName);
|
||
}
|
||
|
||
function setCustomTheme() {
|
||
if (!engine) return;
|
||
// 定义一个红色主题
|
||
engine.setCustomTheme({
|
||
name: 'red-alert',
|
||
primary: '#d32f2f',
|
||
primaryHover: '#b71c1c',
|
||
|
||
background: '#ffebee', // 浅红背景
|
||
panelBackground: 'rgba(255, 255, 255, 0.9)',
|
||
|
||
textPrimary: '#b71c1c',
|
||
textSecondary: '#e57373',
|
||
|
||
border: '#ffcdd2',
|
||
|
||
icon: '#d32f2f',
|
||
iconActive: '#b71c1c',
|
||
|
||
componentBackground: 'rgba(255, 205, 210, 0.3)',
|
||
componentHover: 'rgba(255, 205, 210, 0.8)',
|
||
componentActive: '#e57373'
|
||
});
|
||
}
|
||
|
||
// --- 3D 引擎操作 ---
|
||
/**
|
||
* 初始化 3D 引擎
|
||
*/
|
||
function initEngine3D() {
|
||
if (!engine || !engine.engine) {
|
||
alert('引擎未创建,请先等待页面加载完成');
|
||
return;
|
||
}
|
||
|
||
if (engine.engine.isInitialized()) {
|
||
alert('3D 引擎已经初始化过了');
|
||
updateEngineStatus('已初始化');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 初始化引擎,使用默认配置
|
||
const success = engine.engine.initialize({
|
||
backgroundColor: 0x333333, // 深色背景
|
||
version: 'v2', // WebGL 版本
|
||
showStats: false, // 显示性能统计
|
||
showViewCube: true // 显示视图立方体
|
||
});
|
||
|
||
if (success) {
|
||
engine3DInitialized = true;
|
||
updateEngineStatus('已初始化');
|
||
console.log('✅ 3D 引擎初始化成功');
|
||
loadModel();
|
||
} else {
|
||
updateEngineStatus('初始化失败');
|
||
console.error('❌ 3D 引擎初始化失败');
|
||
}
|
||
} catch (error) {
|
||
updateEngineStatus('初始化错误');
|
||
console.error('❌ 3D 引擎初始化错误:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载 3D 模型
|
||
*/
|
||
function loadModel() {
|
||
if (!engine || !engine.engine) {
|
||
alert('引擎未创建,请先等待页面加载完成');
|
||
return;
|
||
}
|
||
|
||
if (!engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎!');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 加载模型文件(从 model 目录)
|
||
const modelUrl = 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/417664a3-76c8-4d94-9344-1337246a5d4e/';
|
||
|
||
engine.engine.loadModel([modelUrl], {
|
||
position: [0, 0, 0], // 初始位置
|
||
rotation: [0, 0, 0], // 初始旋转
|
||
scale: [1, 1, 1] // 初始缩放
|
||
});
|
||
|
||
console.log('✅ 模型加载请求已发送:', modelUrl);
|
||
} catch (error) {
|
||
console.error('❌ 模型加载错误:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载组合模型 - 同时加载多个模型
|
||
*/
|
||
function loadCombinedModel() {
|
||
if (!engine || !engine.engine) {
|
||
alert('引擎未创建,请先等待页面加载完成');
|
||
return;
|
||
}
|
||
|
||
if (!engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎!');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const modelUrls = [
|
||
'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/974feae2-4be3-4e85-9d5e-ea655b0c890d/',
|
||
'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/6129815d-9ae4-4414-a9ab-e7c3ee1b2584/',
|
||
'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/8ddf95f4-28cc-4218-94c3-165e69fa51b1/'
|
||
];
|
||
|
||
engine.engine.loadModel(modelUrls, {
|
||
position: [0, 0, 0],
|
||
rotation: [0, 0, 0],
|
||
scale: [1, 1, 1]
|
||
});
|
||
|
||
console.log('✅ 组合模型加载请求已发送:', modelUrls);
|
||
} catch (error) {
|
||
console.error('❌ 组合模型加载错误:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 暂停渲染
|
||
*/
|
||
function pauseRendering() {
|
||
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎!');
|
||
return;
|
||
}
|
||
engine.engine.pauseRendering();
|
||
console.log('✅ 渲染已暂停');
|
||
}
|
||
|
||
/**
|
||
* 恢复渲染
|
||
*/
|
||
function resumeRendering() {
|
||
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎!');
|
||
return;
|
||
}
|
||
engine.engine.resumeRendering();
|
||
console.log('✅ 渲染已恢复');
|
||
}
|
||
|
||
/**
|
||
* 切换模型 - 输入新的模型 URL
|
||
*/
|
||
function switchModel() {
|
||
if (!engine || !engine.engine) {
|
||
alert('引擎未创建,请先等待页面加载完成');
|
||
return;
|
||
}
|
||
|
||
if (!engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎!');
|
||
return;
|
||
}
|
||
|
||
const newUrl = prompt('请输入新的模型 URL:', 'https://lyz-1259524260.cos.ap-guangzhou.myqcloud.com/iflow/models/8634e556-a94e-4ba7-be3e-2ea1507cced5/');
|
||
|
||
if (!newUrl || newUrl.trim() === '') {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
engine.engine.loadModel([newUrl.trim()], {
|
||
position: [0, 0, 0],
|
||
rotation: [0, 0, 0],
|
||
scale: [1, 1, 1]
|
||
});
|
||
console.log('✅ 切换模型请求已发送:', newUrl);
|
||
} catch (error) {
|
||
console.error('❌ 切换模型错误:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 打开属性面板
|
||
*/
|
||
function openPropertyPanel() {
|
||
if (!engine || !engine.propertyPanel) {
|
||
console.error('Property panel not available');
|
||
return;
|
||
}
|
||
engine.propertyPanel.show();
|
||
}
|
||
|
||
// --- 构件树数据 ---
|
||
function getLevelTree() {
|
||
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎并加载模型!');
|
||
return;
|
||
}
|
||
const data = engine.engine.getLevelTreeData();
|
||
console.log('🌳 楼层树数据:', data);
|
||
}
|
||
|
||
function getTypeTree() {
|
||
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎并加载模型!');
|
||
return;
|
||
}
|
||
const data = engine.engine.getTypeTreeData();
|
||
console.log('🌳 类型树数据:', data);
|
||
}
|
||
|
||
function getMajorTree() {
|
||
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎并加载模型!');
|
||
return;
|
||
}
|
||
const data = engine.engine.getMajorTreeData();
|
||
console.log('🌳 专业树数据:', data);
|
||
}
|
||
|
||
/**
|
||
* 更新引擎状态显示
|
||
*/
|
||
function updateEngineStatus(status) {
|
||
const statusEl = document.getElementById('engine-status');
|
||
if (statusEl) {
|
||
statusEl.textContent = status;
|
||
// 根据状态设置颜色
|
||
if (status === '已初始化') {
|
||
statusEl.style.color = '#28a745';
|
||
} else if (status === '初始化失败' || status === '初始化错误') {
|
||
statusEl.style.color = '#dc3545';
|
||
} else {
|
||
statusEl.style.color = '#666';
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 自动组合加载 ---
|
||
function openCombineDialog() {
|
||
if (!engine || !engine.engine || !engine.engine.isInitialized()) {
|
||
alert('请先初始化 3D 引擎!');
|
||
return;
|
||
}
|
||
const overlay = document.getElementById('combine-overlay');
|
||
overlay.style.display = 'flex';
|
||
}
|
||
|
||
function closeCombineDialog() {
|
||
const overlay = document.getElementById('combine-overlay');
|
||
overlay.style.display = 'none';
|
||
}
|
||
|
||
function confirmCombineLoad() {
|
||
let raw = document.getElementById('combine-urls').value.trim();
|
||
if (!raw) {
|
||
alert('请输入至少一个模型 URL');
|
||
return;
|
||
}
|
||
|
||
// 1) 统一清洗:去除智能引号、零宽字符、BOM 等不可见字符
|
||
raw = raw
|
||
.replace(/[\u2018\u2019]/g, "'") // “” → '
|
||
.replace(/[\u201C\u201D]/g, '"') // “” → "
|
||
.replace(/[\u200B\uFEFF\u00A0]/g, '') // 零宽空格、BOM、不换行空格
|
||
.trim();
|
||
|
||
// 2) 尝试当 JSON 数组解析
|
||
let urls = [];
|
||
try {
|
||
const jsonStr = raw.replace(/'/g, '"');
|
||
const parsed = JSON.parse(jsonStr);
|
||
if (Array.isArray(parsed)) {
|
||
urls = parsed.map(s => String(s).trim()).filter(Boolean);
|
||
}
|
||
} catch(e) {
|
||
// 3) JSON 失败,走分割逻辑
|
||
urls = raw.split(/[,;\n\r]+/)
|
||
.map(s => s.trim().replace(/^['"|\[\]\s]+|['"|\[\]\s]+$/g, ''))
|
||
.filter(Boolean);
|
||
}
|
||
|
||
// 4) 只保留 http/https 开头的合法 URL
|
||
urls = urls.filter(s => /^https?:\/\//i.test(s));
|
||
|
||
if (urls.length === 0) {
|
||
alert('未解析到有效的 URL,请确保以 http:// 或 https:// 开头');
|
||
return;
|
||
}
|
||
|
||
// 5) 逐个校验 URL 是否合法
|
||
for (let i = 0; i < urls.length; i++) {
|
||
try {
|
||
new URL(urls[i]);
|
||
} catch(e) {
|
||
alert('第 ' + (i+1) + ' 个 URL 无效:\n' + urls[i] + '\n\n请检查是否包含特殊字符');
|
||
console.error('❌ 无效 URL,原始字符:', JSON.stringify(urls[i]));
|
||
return;
|
||
}
|
||
}
|
||
|
||
try {
|
||
engine.engine.loadModel(urls, {
|
||
position: [0, 0, 0],
|
||
rotation: [0, 0, 0],
|
||
scale: [1, 1, 1]
|
||
});
|
||
console.log('✅ 自动组合加载已发送:', urls);
|
||
closeCombineDialog();
|
||
document.getElementById('combine-urls').value = '';
|
||
} catch (error) {
|
||
console.error('❌ 自动组合加载错误:', error);
|
||
}
|
||
}
|
||
|
||
</script>
|
||
</body>
|
||
|
||
</html> |