This commit is contained in:
2026-05-22 13:33:13 +08:00
parent c504fca3de
commit 84c8752e0b
17 changed files with 361939 additions and 921 deletions

View File

@ -0,0 +1,912 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Model Showcase SDK - TS</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
overflow: hidden;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
}
#app {
width: 100vw;
height: 100vh;
display: flex;
position: relative;
}
#canvas-container {
flex: 1;
position: relative;
}
#renderDom {
width: 100%;
height: 100%;
display: block;
}
#config-panel {
width: 320px;
background: rgba(30, 30, 45, 0.95);
backdrop-filter: blur(10px);
overflow-y: auto;
padding: 20px;
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
}
#config-panel::-webkit-scrollbar {
width: 6px;
}
#config-panel::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
}
#config-panel::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.config-title {
color: #fff;
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
}
.click-info {
background: rgba(76, 175, 80, 0.2);
border: 1px solid rgba(76, 175, 80, 0.5);
border-radius: 8px;
padding: 12px;
margin-bottom: 15px;
color: #fff;
font-size: 13px;
line-height: 1.6;
}
.click-info-title {
font-weight: bold;
color: #4caf50;
margin-bottom: 8px;
font-size: 14px;
}
.click-info-item {
margin-bottom: 4px;
display: flex;
gap: 8px;
}
.click-info-label {
color: rgba(255, 255, 255, 0.7);
min-width: 70px;
}
.click-info-value {
color: #fff;
word-break: break-all;
}
.config-category {
margin-bottom: 15px;
border-radius: 8px;
overflow: hidden;
background: rgba(255, 255, 255, 0.05);
}
.category-header {
padding: 12px 15px;
background: rgba(255, 255, 255, 0.1);
color: #fff;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.3s;
user-select: none;
}
.category-header:hover {
background: rgba(255, 255, 255, 0.15);
}
.category-header.active {
background: rgba(76, 175, 80, 0.3);
}
.category-title {
font-size: 14px;
font-weight: 600;
}
.category-arrow {
transition: transform 0.3s;
font-size: 12px;
}
.category-arrow.expanded {
transform: rotate(180deg);
}
.category-content {
display: grid;
grid-template-rows: 0fr;
overflow: hidden;
transition: grid-template-rows 0.3s ease, padding 0.3s ease;
padding: 0 15px;
}
.category-content.expanded {
grid-template-rows: 1fr;
padding: 15px;
}
.category-content>* {
min-height: 0;
}
.option-item {
margin-bottom: 12px;
}
.option-label {
color: rgba(255, 255, 255, 0.8);
font-size: 13px;
margin-bottom: 8px;
display: block;
}
.option-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.option-btn {
padding: 8px 16px;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.05);
color: #fff;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
}
.option-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.option-btn.selected {
background: rgba(76, 175, 80, 0.6);
border-color: #4CAF50;
}
.option-checkbox {
display: flex;
align-items: center;
padding: 8px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.option-checkbox:hover {
background: rgba(255, 255, 255, 0.05);
}
.option-checkbox input[type="checkbox"] {
margin-right: 8px;
cursor: pointer;
}
.option-checkbox label {
color: rgba(255, 255, 255, 0.8);
font-size: 12px;
cursor: pointer;
}
/* 进度条样式 */
#progress-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 500px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
padding: 10px;
z-index: 1000;
}
#progress-bar {
width: 0%;
height: 10px;
background: linear-gradient(90deg, #4CAF50, #45a049);
border-radius: 5px;
transition: width 0.1s ease;
}
#progress-text {
color: white;
text-align: center;
margin-top: 5px;
font-size: 14px;
}
</style>
</head>
<body>
<div id="app">
<!-- 画布区域 -->
<div id="canvas-container">
<canvas id="renderDom"></canvas>
<div id="progress-container" style="display: none;">
<div id="progress-bar"></div>
<div id="progress-text">0%</div>
</div>
<!-- 生成放置区域按钮 -->
<button id="dropzone-btn" style="
position: absolute;
top: 20px;
left: 20px;
padding: 10px 20px;
background: #21c7ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
z-index: 100;
">生成放置区域</button>
</div>
<!-- 配置面板 -->
<div id="config-panel">
<div class="config-title">选装选配</div>
<!-- 点击信息显示区域 -->
<div id="click-info" class="click-info" style="display: none;">
<div class="click-info-title">点击信息</div>
<div id="click-info-content"></div>
</div>
<!-- 棚子尺寸 -->
<div class="config-category">
<div class="category-header" data-category="size">
<span class="category-title">棚子尺寸</span>
<span class="category-arrow">▼</span>
</div>
<div class="category-content">
<div class="option-group">
<button class="option-btn" data-option="size-1">3*3</button>
<button class="option-btn" data-option="size-2">3x6</button>
<button class="option-btn" data-option="size-3">10x13EM星空篷</button>
<button class="option-btn" data-option="size-4">全铁3x6</button>
<button class="option-btn" data-option="size-1">10x12</button>
<button class="option-btn" data-option="size-2">10x10星空篷</button>
<button class="option-btn" data-option="size-3">10x13星空篷</button>
<button class="option-btn" data-option="size-4">10x20星空篷</button>
</div>
</div>
</div>
<!-- 棚子类型 -->
<div class="config-category">
<div class="category-header" data-category="type">
<span class="category-title">棚子类型</span>
<span class="category-arrow">▼</span>
</div>
<div class="category-content">
<div class="option-group">
<button class="option-btn" data-option="type-1">平顶</button>
<button class="option-btn" data-option="type-2">尖顶</button>
<button class="option-btn" data-option="type-3">弧形</button>
<button class="option-btn" data-option="type-4">异形</button>
</div>
</div>
</div>
<!-- 百叶 (单选) -->
<div class="config-category">
<div class="category-header" data-category="louver">
<span class="category-title">百叶</span>
<span class="category-arrow">▼</span>
</div>
<div class="category-content">
<div class="option-group">
<button class="option-btn" data-option="louver-1">SPFPDS13FTW</button>
<button class="option-btn" data-option="louver-2">SPFPDS13FTC</button>
<button class="option-btn" data-option="louver-3">3m下拉帘</button>
<button class="option-btn" data-option="louver-4">SPFSW13FTW</button>
<button class="option-btn" data-option="louver-4">SPFSW10FTW</button>
<button class="option-btn" data-option="louver-4">SPFPDS10FTC</button>
<button class="option-btn" data-option="louver-4">SPFPDS10FTW</button>
</div>
</div>
</div>
<!-- 配色 -->
<div class="config-category">
<div class="category-header" data-category="color">
<span class="category-title">配色</span>
<span class="category-arrow">▼</span>
</div>
<div class="category-content">
<div class="option-group">
13
<button class="option-btn" data-option="color-1">SPF111S1013W</button>
<button class="option-btn" data-option="color-2">SPF111S1013C</button>
<button class="option-btn" data-option="color-3">SPF111S1013TA</button>
10
<button class="option-btn" data-option="color-1">SPF111S1010W</button>
<button class="option-btn" data-option="color-2">SPF111S1010C</button>
<button class="option-btn" data-option="color-3">SPF111S1010TA</button>
</div>
</div>
</div>
<button id="hotspot-btn">生成热点</button>
<button id="prevent-btn">生成防止区域</button>
</div>
</div>
<!-- 模型信息框用于2D转3D显示 -->
<div id="model-info-box" style="display: none;">
<div style="
background: rgba(255, 255, 255, 0.95);
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
font-size: 14px;
color: #333;
min-width: 200px;
">
<div style="font-weight: bold; margin-bottom: 8px; color: #4CAF50;">模型信息</div>
<div id="info-name" style="margin-bottom: 5px;">名称: -</div>
<div id="info-position" style="margin-bottom: 10px; font-size: 12px; color: #666;">坐标: -</div>
<!-- 颜色按钮 -->
<div id="color-buttons" style="display: none; gap: 8px; margin-bottom: 8px;">
<button id="color-btn-1" style="
flex: 1;
padding: 8px;
background: #FFFFFF;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">白色</button>
<button id="color-btn-2" style="
flex: 1;
padding: 8px;
background: #000000;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">黑色</button>
</div>
<!-- 旋转按钮 -->
<div id="rotation-buttons" style="display: none; gap: 8px; margin-bottom: 8px;">
<button id="rotation-btn-90" style="
flex: 1;
padding: 8px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">旋转90°</button>
<button id="rotation-btn-180" style="
flex: 1;
padding: 8px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">旋转180°</button>
</div>
<div style="display: flex; gap: 8px;">
<button id="remove-model-btn" style="
flex: 1;
padding: 8px;
background: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">移除</button>
<button id="close-info-btn" style="
flex: 1;
padding: 8px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">关闭</button>
</div>
</div>
</div>
<!-- 全局脚本方式引入 SDK -->
<script src="https://sdk.zguiy.com/zt/assets/index.global.js"></script>
<!-- 业务逻辑脚本 -->
<script src="./app-global.js"></script>
<script>
// 从全局对象获取 SDK kernel
var kernel = window.faceSDK && window.faceSDK.kernel;
if (!kernel) {
console.error('faceSDK kernel 不可用,请确认 index.global.js 已正确加载');
alert('SDK 加载失败,请检查网络连接');
} else {
// 注入 kernel 实例到业务逻辑
window.AppLogic.initApp(kernel);
(async function () {
await window.AppLogic.init();
await window.AppLogic.getAutoLoadModelList();
kernel.on('model:load:progress', (data) => {
console.log('模型加载事件', data);
});
kernel.on('model:loaded', (data) => {
console.log('模型加载完成', data);
// 隐藏进度条
const progressContainer = document.getElementById('progress-container');
if (progressContainer) {
progressContainer.style.display = 'none';
}
});
kernel.on('all:ready', (data) => {
console.log('所有模块加载完,', data);
kernel.material.apply({
target: 'Material__2',
attribute: 'alpha',
value: 0.5,
});
});
// ========== UI 交互逻辑 ==========
// 折叠面板切换
document.querySelectorAll('.category-header').forEach(function (header) {
header.addEventListener('click', function () {
var content = this.nextElementSibling;
var arrow = this.querySelector('.category-arrow');
// 切换展开/收起状态
content.classList.toggle('expanded');
arrow.classList.toggle('expanded');
this.classList.toggle('active');
});
});
var sku = ""
// 单选按钮逻辑
document.querySelectorAll('.option-btn').forEach(function (btn) {
btn.addEventListener('click', async function () {
var optionGroup = this.parentElement;
var category = this.closest('.config-category');
var categoryName = category.querySelector('.category-header').dataset.category;
// 同一组内取消其他选中状态
optionGroup.querySelectorAll('.option-btn').forEach(function (b) {
b.classList.remove('selected');
});
// 选中当前按钮
this.classList.add('selected');
// 触发自定义事件
var event = new CustomEvent('config:change', {
detail: {
category: categoryName,
value: this.dataset.option,
text: this.textContent
}
});
document.dispatchEvent(event);
console.log('配置变更:', {
category: categoryName,
value: this.dataset.option,
text: this.textContent
});
var currentText = this.textContent;
sku = currentText;
await window.AppLogic.getProductConfig(currentText)
});
});
document.querySelector('#hotspot-btn').addEventListener('click', async function () {
await window.AppLogic.getHotspot();
})
// 监听热点点击事件
window.addEventListener('hotspot:click', function (event) {
console.log('热点被点击:', event.detail);
var id = event.detail.id;
var name = event.detail.name;
var payload = event.detail.payload;
var clickInfoDiv = document.getElementById('click-info');
var clickInfoContent = document.getElementById('click-info-content');
var html = '<div class="click-info-item">' +
'<span class="click-info-label">类型:</span>' +
'<span class="click-info-value">热点</span>' +
'</div>' +
'<div class="click-info-item">' +
'<span class="click-info-label">名称:</span>' +
'<span class="click-info-value">' + name + '</span>' +
'</div>';
if (payload && payload.skus && payload.skus.length > 0) {
html += '<div class="click-info-item">' +
'<span class="click-info-label">关联SKU:</span>' +
'<span class="click-info-value">' + payload.skus.join(', ') + '</span>' +
'</div>';
} else {
html += '<div class="click-info-item">' +
'<span class="click-info-label">关联SKU:</span>' +
'<span class="click-info-value">无</span>' +
'</div>';
}
clickInfoContent.innerHTML = html;
clickInfoDiv.style.display = 'block';
});
// 监听模型点击事件
window.addEventListener('model:click', function (event) {
console.log('模型被点击:', event.detail);
var meshName = event.detail.meshName;
var materialName = event.detail.materialName;
var modelControlType = event.detail.modelControlType;
var clickInfoDiv = document.getElementById('click-info');
var clickInfoContent = document.getElementById('click-info-content');
var html = '<div class="click-info-item">' +
'<span class="click-info-label">类型:</span>' +
'<span class="click-info-value">模型</span>' +
'</div>' +
'<div class="click-info-item">' +
'<span class="click-info-label">网格名称:</span>' +
'<span class="click-info-value">' + meshName + '</span>' +
'</div>';
if (materialName) {
html += '<div class="click-info-item">' +
'<span class="click-info-label">材质名称:</span>' +
'<span class="click-info-value">' + materialName + '</span>' +
'</div>';
}
if (modelControlType) {
html += '<div class="click-info-item">' +
'<span class="click-info-label">控制类型:</span>' +
'<span class="click-info-value">' + modelControlType + '</span>' +
'</div>';
}
clickInfoContent.innerHTML = html;
clickInfoDiv.style.display = 'block';
});
// 多选复选框逻辑
document.querySelectorAll('.option-checkbox input[type="checkbox"]').forEach(function (checkbox) {
checkbox.addEventListener('change', async function () {
var category = this.closest('.config-category');
var categoryName = category.querySelector('.category-header').dataset.category;
var optionGroup = this.closest('.option-group');
var checked = this.checked;
// 获取当前组所有选中的值
var selectedValues = Array.from(
optionGroup.querySelectorAll('input[type="checkbox"]:checked')
).map(function (cb) {
return {
value: cb.dataset.option,
text: cb.nextElementSibling.textContent
};
});
// 触发自定义事件
var event = new CustomEvent('config:change', {
detail: {
category: categoryName,
values: selectedValues,
checked: this.checked,
currentValue: this.dataset.option
}
});
document.dispatchEvent(event);
console.log('配置变更(多选):', {
category: categoryName,
selectedValues: selectedValues,
checked: this.checked,
currentValue: this.dataset.option
});
});
});
// 监听配置变更事件(供外部使用)
document.addEventListener('config:change', function (e) {
// 这里可以根据配置变更来操作 3D 模型
// 例如:
// if (e.detail.category === 'size') {
// kernel.model.replace({ modelId: 'shed', modelUrl: `/models/shed-${e.detail.value}.glb`, modelControlType: 'rotation' });
// }
// if (e.detail.category === 'color') {
// kernel.material.apply({
// target: 'ShedMaterial',
// attribute: 'baseColor',
// value: getColorValue(e.detail.value)
// });
// }
});
// ========== 模型信息框按钮事件 ==========
// 关闭按钮事件
document.getElementById('close-info-btn').addEventListener('click', function () {
kernel.domTo3D.detach('model-info');
});
// 白色按钮事件
document.getElementById('color-btn-1').addEventListener('click', function () {
var materialName = window.getCurrentMaterialName();
if (materialName) {
console.log('切换为白色,材质名:', materialName);
kernel.material.apply({
target: materialName,
albedoColor: '#FFFFFF',
});
} else {
console.log('没有选中材质');
}
});
// 黑色按钮事件
document.getElementById('color-btn-2').addEventListener('click', function () {
var materialName = window.getCurrentMaterialName();
if (materialName) {
console.log('切换为黑色,材质名:', materialName);
kernel.material.apply({
target: materialName,
albedoColor: '#000000',
});
} else {
console.log('没有选中材质');
}
});
// 旋转90度按钮事件
document.getElementById('rotation-btn-90').addEventListener('click', function () {
var pickedMesh = window.getCurrentPickedMesh();
if (pickedMesh) {
var modelName = kernel.model.findModelNameByMesh?.(pickedMesh);
if (modelName) {
console.log('旋转90度模型名:', modelName);
kernel.transform.rotation({
modelId: modelName,
vector3: { x: 0, y: 90, z: 0 }
});
} else {
console.log('未找到模型名称');
}
} else {
console.log('没有选中的网格');
}
});
// 旋转180度按钮事件
document.getElementById('rotation-btn-180').addEventListener('click', function () {
var pickedMesh = window.getCurrentPickedMesh();
if (pickedMesh) {
var modelName = kernel.model.findModelNameByMesh?.(pickedMesh);
if (modelName) {
console.log('旋转180度模型名:', modelName);
kernel.transform.rotation({
modelId: modelName,
vector3: { x: 0, y: 30, z: 0 }
});
} else {
console.log('未找到模型名称');
}
} else {
console.log('没有选中的网格');
}
});
// 移除按钮事件
document.getElementById('remove-model-btn').addEventListener('click', () => {
const pickedMesh = window.getCurrentPickedMesh();
if (pickedMesh) {
const meshName = pickedMesh.name;
const modelName = kernel.model.findModelNameByMesh(pickedMesh);
const success = kernel.model.removeByName(modelName);
if (success) {
console.log('模型已移除');
// 关闭信息框
kernel.domTo3D.detach('model-info');
} else {
console.log('移除失败:未找到该网格所属的模型');
}
} else {
console.log('没有选中的网格');
}
});
// 生成放置区域按钮事件
var dropZoneVisible = false;
document.getElementById('dropzone-btn').addEventListener('click', function () {
if (!dropZoneVisible) {
// 更新按钮文字
document.getElementById('dropzone-btn').textContent = '隐藏放置区域';
console.log('已生成并显示放置区域');
} else {
// 隐藏放置区域
kernel.dropZone.hideAll();
dropZoneVisible = false;
// 更新按钮文字
document.getElementById('dropzone-btn').textContent = '生成放置区域';
console.log('已隐藏放置区域');
}
});
// 监听放置区域点击事件
kernel.on('dropzone:click', async function (dropzone_data) {
window.AppLogic.getEvent(dropzone_data, sku)
});
// 存储当前选中的材质名和网格
var currentMaterialName = '';
var currentPickedMesh = null;
kernel.on('model:click', function (data) {
console.log('模型点击事件', data);
console.log('模型控制类型:', data.modelControlType);
switch (data.modelControlType) {
case "color":
// DOM 2D转3D 示例:点击模型时显示信息框
if (data.pickedMesh && data.pickedPoint) {
var meshName = data.pickedMesh.name;
var position = data.pickedPoint; // 使用点击位置的坐标
currentMaterialName = data.materialName || ''; // 保存材质名
currentPickedMesh = data.pickedMesh; // 保存网格对象
// 获取已创建的DOM元素
var infoDiv = document.getElementById('model-info-box');
// 更新信息内容
document.getElementById('info-name').textContent = '名称: ' + meshName;
document.getElementById('info-position').textContent = '坐标: [' + position.x.toFixed(2) + ', ' + position.y.toFixed(2) + ', ' + position.z.toFixed(2) + ']';
// 显示颜色按钮,隐藏旋转按钮
document.getElementById('color-buttons').style.display = 'flex';
document.getElementById('rotation-buttons').style.display = 'none';
// 将DOM附加到点击的3D坐标会自动显示
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
}
break;
case "rotation":
// 显示旋转控制UI
if (data.pickedMesh && data.pickedPoint) {
var meshName = data.pickedMesh.name;
var position = data.pickedPoint;
currentPickedMesh = data.pickedMesh; // 保存网格对象
// 获取已创建的DOM元素
var infoDiv = document.getElementById('model-info-box');
// 更新信息内容
document.getElementById('info-name').textContent = '名称: ' + meshName;
document.getElementById('info-position').textContent = '坐标: [' + position.x.toFixed(2) + ', ' + position.y.toFixed(2) + ', ' + position.z.toFixed(2) + ']';
// 显示旋转按钮,隐藏颜色按钮
document.getElementById('rotation-buttons').style.display = 'flex';
document.getElementById('color-buttons').style.display = 'none';
// 将DOM附加到点击的3D坐标会自动显示
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
}
break;
default:
break;
}
});
// 暴露到全局,供 index.html 使用
window.getCurrentMaterialName = function () { return currentMaterialName; };
window.getCurrentPickedMesh = function () { return currentPickedMesh; };
// 暴露 kernel 到全局,方便调试
kernel.on('hotspot:click', function (event) {
console.log('热点被点击:', event);
var id = event.id;
var name = event.name;
var payload = event.payload;
if (payload && payload.skus && payload.skus.length > 0) {
console.log('热点关联的SKU列表:', payload.skus);
// 这里可以根据 SKU 列表做进一步处理,比如显示产品信息
} else {
console.log('该热点没有关联SKU');
}
// if (name === "卷帘门") {
// kernel.door.toggle({ upY: 28, downY: 0, speed: 12 });
// // Y轴剖切只作用于卷帘门网格保留下方剖掉上方
// var clipHeight = 28; // 调整这个值找到合适的剖切高度
// console.log('设置剖切:', clipHeight);
// kernel.clipping.setY(clipHeight, true, ['Box005.001', 'Box006.001']);
// }
});
})();
}
</script>
</body>
</html>