1
This commit is contained in:
380
index copy.html
380
index copy.html
@ -67,6 +67,40 @@
|
|||||||
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
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 {
|
.config-category {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -227,12 +261,34 @@
|
|||||||
<div id="progress-bar"></div>
|
<div id="progress-bar"></div>
|
||||||
<div id="progress-text">0%</div>
|
<div id="progress-text">0%</div>
|
||||||
</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>
|
||||||
|
|
||||||
<!-- 配置面板 -->
|
<!-- 配置面板 -->
|
||||||
<div id="config-panel">
|
<div id="config-panel">
|
||||||
<div class="config-title">选装选配</div>
|
<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="config-category">
|
||||||
<div class="category-header" data-category="size">
|
<div class="category-header" data-category="size">
|
||||||
@ -273,7 +329,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="category-content">
|
<div class="category-content">
|
||||||
<div class="option-group">
|
<div class="option-group">
|
||||||
<button class="option-btn" data-option="louver-1">百叶1</button>
|
<button class="option-btn" data-option="louver-1">111</button>
|
||||||
<button class="option-btn" data-option="louver-2">百叶2</button>
|
<button class="option-btn" data-option="louver-2">百叶2</button>
|
||||||
<button class="option-btn" data-option="louver-3">百叶3</button>
|
<button class="option-btn" data-option="louver-3">百叶3</button>
|
||||||
<button class="option-btn" data-option="louver-4">百叶4</button>
|
<button class="option-btn" data-option="louver-4">百叶4</button>
|
||||||
@ -290,13 +346,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="category-content">
|
<div class="category-content">
|
||||||
<div class="option-group">
|
<div class="option-group">
|
||||||
<button class="option-btn" data-option="color-1">白色</button>
|
<button class="option-btn" data-option="color-1">222222</button>
|
||||||
<button class="option-btn" data-option="color-2">灰色</button>
|
<button class="option-btn" data-option="color-2">灰色</button>
|
||||||
<button class="option-btn" data-option="color-3">黑色</button>
|
<button class="option-btn" data-option="color-3">黑色</button>
|
||||||
<button class="option-btn" data-option="color-4">木色</button>
|
<button class="option-btn" data-option="color-4">木色</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button id="hotspot-btn">生成热点</button>
|
||||||
|
<button id="prevent-btn">生成防止区域</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -390,6 +449,10 @@
|
|||||||
|
|
||||||
<script type="module" src="./index.js"></script>
|
<script type="module" src="./index.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { kernel } from './src/main.ts';
|
import { kernel } from './src/main.ts';
|
||||||
|
|
||||||
// ========== UI 交互逻辑 ==========
|
// ========== UI 交互逻辑 ==========
|
||||||
@ -439,9 +502,21 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 百叶模型替换逻辑
|
// 百叶模型替换逻辑
|
||||||
if (categoryName === "louver") {
|
// if (categoryName === "louver") {
|
||||||
const currentText = this.textContent;
|
const currentText = this.textContent;
|
||||||
|
console.log(currentText);
|
||||||
|
|
||||||
|
skuToFunc(currentText);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
document.querySelector('#hotspot-btn').addEventListener('click', async function () {
|
||||||
|
|
||||||
|
await hotspotRequest();
|
||||||
|
})
|
||||||
|
const skuToFunc = async (currentText) => {
|
||||||
// 根据 SKU 查询配置和事件
|
// 根据 SKU 查询配置和事件
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${currentText}`);
|
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${currentText}`);
|
||||||
@ -450,37 +525,154 @@
|
|||||||
if (result.code === 200 && result.data) {
|
if (result.code === 200 && result.data) {
|
||||||
console.log('SKU配置数据:', result.data);
|
console.log('SKU配置数据:', result.data);
|
||||||
console.log('关联事件:', result.data.events);
|
console.log('关联事件:', result.data.events);
|
||||||
|
placementWall(1);
|
||||||
|
// 使用 for...of 循环以支持 await
|
||||||
|
for (const event of result.data.events) {
|
||||||
|
if (event.event_type === 'change_model') {
|
||||||
|
const { file_url, model_control_type, category } = event.target_data;
|
||||||
|
console.log('替换百叶模型:', event);
|
||||||
|
|
||||||
// 使用配置数据中的模型路径(如果有)
|
|
||||||
const modelUrl = result.data.model_id
|
|
||||||
? `https://sdk.zguiy.com/resurces/model/${result.data.model_id}.glb`
|
|
||||||
: `https://sdk.zguiy.com/resurces/model/${currentText}.glb`;
|
|
||||||
|
|
||||||
console.log('替换百叶模型:', modelUrl);
|
|
||||||
await kernel.model.replace({
|
await kernel.model.replace({
|
||||||
modelId: '卷帘小',
|
modelId: category,
|
||||||
modelUrl: modelUrl,
|
modelUrl: file_url,
|
||||||
modelControlType: 'color'
|
modelControlType: model_control_type,
|
||||||
|
drag: {
|
||||||
|
enable: true,
|
||||||
|
axis: 'x',
|
||||||
|
step: 0.1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`百叶模型已替换为 ${currentText}`);
|
console.log(`百叶模型已替换为 ${currentText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event_type === 'change_color') {
|
||||||
|
const materialName = event.material_name;
|
||||||
|
const { color, color_map_url, normal_map_url } = event.target_data;
|
||||||
|
console.log('替换百叶模型颜色:', event.target_data);
|
||||||
|
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: color,
|
||||||
|
albedoTexture: color_map_url,
|
||||||
|
normalMap: normal_map_url,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('未找到SKU配置,使用默认模型路径');
|
console.log(`未查询到数据`);
|
||||||
const modelUrl = `https://sdk.zguiy.com/resurces/model/${currentText}.glb`;
|
|
||||||
console.log('替换百叶模型:', modelUrl);
|
|
||||||
await kernel.model.replace({
|
|
||||||
modelId: '卷帘小',
|
|
||||||
modelUrl: modelUrl,
|
|
||||||
modelControlType: 'color'
|
|
||||||
});
|
|
||||||
console.log(`百叶模型已替换为 ${currentText}`);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`查询SKU配置或替换模型失败:`, error);
|
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hotspotRequest = async () => {
|
||||||
|
try {
|
||||||
|
// 从后端获取激活状态的热点列表
|
||||||
|
const response = await fetch('http://localhost:3001/api/hotspots?status=active&page=1&pageSize=100');
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200 && result.data.list.length > 0) {
|
||||||
|
// 将后端数据转换为 SDK 需要的格式
|
||||||
|
const hotspots = result.data.list.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
type: 'hotspot',
|
||||||
|
name: item.name,
|
||||||
|
meshName: item.name, // 可以根据实际情况调整
|
||||||
|
icon: item.image_url,
|
||||||
|
position: [item.position_x, item.position_y, item.position_z],
|
||||||
|
radius: item.radius,
|
||||||
|
color: "#000000",
|
||||||
|
payload: {
|
||||||
|
skus: item.skus || [],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 渲染热点
|
||||||
|
kernel.hotspot.render(hotspots);
|
||||||
|
console.log('热点渲染成功:', hotspots);
|
||||||
|
} else {
|
||||||
|
console.log('没有可用的热点数据');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取热点数据失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听热点点击事件
|
||||||
|
window.addEventListener('hotspot:click', (event) => {
|
||||||
|
console.log('热点被点击:', event.detail);
|
||||||
|
const { id, name, payload } = event.detail;
|
||||||
|
|
||||||
|
const clickInfoDiv = document.getElementById('click-info');
|
||||||
|
const clickInfoContent = document.getElementById('click-info-content');
|
||||||
|
|
||||||
|
let 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', (event) => {
|
||||||
|
console.log('模型被点击:', event.detail);
|
||||||
|
const { meshName, materialName, modelControlType } = event.detail;
|
||||||
|
|
||||||
|
const clickInfoDiv = document.getElementById('click-info');
|
||||||
|
const clickInfoContent = document.getElementById('click-info-content');
|
||||||
|
|
||||||
|
let 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(checkbox => {
|
document.querySelectorAll('.option-checkbox input[type="checkbox"]').forEach(checkbox => {
|
||||||
checkbox.addEventListener('change', async function () {
|
checkbox.addEventListener('change', async function () {
|
||||||
@ -545,7 +737,10 @@
|
|||||||
const materialName = window.getCurrentMaterialName();
|
const materialName = window.getCurrentMaterialName();
|
||||||
if (materialName) {
|
if (materialName) {
|
||||||
console.log('切换为白色,材质名:', materialName);
|
console.log('切换为白色,材质名:', materialName);
|
||||||
kernel.material.color(materialName, '#FFFFFF');
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: '#FFFFFF',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('没有选中材质');
|
console.log('没有选中材质');
|
||||||
}
|
}
|
||||||
@ -556,7 +751,10 @@
|
|||||||
const materialName = window.getCurrentMaterialName();
|
const materialName = window.getCurrentMaterialName();
|
||||||
if (materialName) {
|
if (materialName) {
|
||||||
console.log('切换为黑色,材质名:', materialName);
|
console.log('切换为黑色,材质名:', materialName);
|
||||||
kernel.material.color(materialName, '#000000');
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: '#000000',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('没有选中材质');
|
console.log('没有选中材质');
|
||||||
}
|
}
|
||||||
@ -617,6 +815,142 @@
|
|||||||
console.log('没有选中的网格');
|
console.log('没有选中的网格');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 生成放置区域按钮事件
|
||||||
|
let dropZoneVisible = false;
|
||||||
|
document.getElementById('dropzone-btn').addEventListener('click', () => {
|
||||||
|
if (!dropZoneVisible) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 更新按钮文字
|
||||||
|
document.getElementById('dropzone-btn').textContent = '隐藏放置区域';
|
||||||
|
console.log('已生成并显示放置区域');
|
||||||
|
} else {
|
||||||
|
// 隐藏放置区域
|
||||||
|
kernel.dropZone.hideAll();
|
||||||
|
dropZoneVisible = false;
|
||||||
|
|
||||||
|
// 更新按钮文字
|
||||||
|
document.getElementById('dropzone-btn').textContent = '生成放置区域';
|
||||||
|
console.log('已隐藏放置区域');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const placementWall = (divisions) => {
|
||||||
|
|
||||||
|
// 先清除旧的放置区域
|
||||||
|
kernel.dropZone.clearAll();
|
||||||
|
|
||||||
|
// 生成新的放置区域(使用新的墙面参数化API)
|
||||||
|
// 调整 baseY 来控制整体高度(正数向上,负数向下)
|
||||||
|
const baseY = 0.08; // 修改这个值来调整整体高度
|
||||||
|
|
||||||
|
// 调整 offset 来控制每个面向外或向内的偏移
|
||||||
|
// 正数 = 向外移动,负数 = 向内移动
|
||||||
|
const wallOffset = -0.07; // 修改这个值来调整墙面偏移
|
||||||
|
|
||||||
|
kernel.dropZone.generate({
|
||||||
|
walls: [
|
||||||
|
{
|
||||||
|
name: 'front',
|
||||||
|
startPoint: [-1.43, baseY, -1.4],
|
||||||
|
endPoint: [1.37, baseY, -1.4],
|
||||||
|
height: 2.2,
|
||||||
|
divisions: divisions,
|
||||||
|
offset: wallOffset // 向外或向内偏移
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'back',
|
||||||
|
startPoint: [1.37, baseY, 1.4],
|
||||||
|
endPoint: [-1.43, baseY, 1.4],
|
||||||
|
height: 2.2,
|
||||||
|
divisions: divisions,
|
||||||
|
offset: wallOffset
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'left',
|
||||||
|
startPoint: [-1.43, baseY, 1.39],
|
||||||
|
endPoint: [-1.43, baseY, -1.43],
|
||||||
|
height: 2.2,
|
||||||
|
divisions: divisions,
|
||||||
|
offset: wallOffset
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'right',
|
||||||
|
startPoint: [1.37, baseY, -1.43],
|
||||||
|
endPoint: [1.37, baseY, 1.4],
|
||||||
|
height: 2.2,
|
||||||
|
divisions: divisions,
|
||||||
|
offset: wallOffset
|
||||||
|
}
|
||||||
|
],
|
||||||
|
color: "#21c7ff",
|
||||||
|
alpha: 0.3,
|
||||||
|
thickness: 2,
|
||||||
|
showBorder: true,
|
||||||
|
borderColor: "#ffffff"
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示放置区域
|
||||||
|
kernel.dropZone.showAll();
|
||||||
|
dropZoneVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 监听放置区域点击事件
|
||||||
|
kernel.on('dropzone:click', async (data) => {
|
||||||
|
// 将模型放置到该区域
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${currentText}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200 && result.data) {
|
||||||
|
console.log('SKU配置数据:', result.data);
|
||||||
|
console.log('关联事件:', result.data.events);
|
||||||
|
placementWall(1);
|
||||||
|
// 使用 for...of 循环以支持 await
|
||||||
|
for (const event of result.data.events) {
|
||||||
|
if (event.event_type === 'change_model') {
|
||||||
|
const { file_url, model_control_type, category } = event.target_data;
|
||||||
|
console.log('替换百叶模型:', event);
|
||||||
|
|
||||||
|
await kernel.model.replace({
|
||||||
|
modelId: category,
|
||||||
|
modelUrl: file_url,
|
||||||
|
modelControlType: model_control_type,
|
||||||
|
drag: {
|
||||||
|
enable: true,
|
||||||
|
axis: 'x',
|
||||||
|
step: 0.1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型已替换为 ${currentText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event_type === 'change_color') {
|
||||||
|
const materialName = event.material_name;
|
||||||
|
const { color, color_map_url, normal_map_url } = event.target_data;
|
||||||
|
console.log('替换百叶模型颜色:', event.target_data);
|
||||||
|
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: color,
|
||||||
|
albedoTexture: color_map_url,
|
||||||
|
normalMap: normal_map_url,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`未查询到数据`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
116
index.html
116
index.html
@ -516,58 +516,10 @@
|
|||||||
|
|
||||||
await hotspotRequest();
|
await hotspotRequest();
|
||||||
})
|
})
|
||||||
|
let sku = ""
|
||||||
const skuToFunc = async (currentText) => {
|
const skuToFunc = async (currentText) => {
|
||||||
// 根据 SKU 查询配置和事件
|
sku = currentText;
|
||||||
try {
|
|
||||||
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${currentText}`);
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.code === 200 && result.data) {
|
|
||||||
console.log('SKU配置数据:', result.data);
|
|
||||||
console.log('关联事件:', result.data.events);
|
|
||||||
placementWall(1);
|
placementWall(1);
|
||||||
// 使用 for...of 循环以支持 await
|
|
||||||
for (const event of result.data.events) {
|
|
||||||
if (event.event_type === 'change_model') {
|
|
||||||
const { file_url, model_control_type, category } = event.target_data;
|
|
||||||
console.log('替换百叶模型:', event);
|
|
||||||
|
|
||||||
await kernel.model.replace({
|
|
||||||
modelId: category,
|
|
||||||
modelUrl: file_url,
|
|
||||||
modelControlType: model_control_type,
|
|
||||||
drag: {
|
|
||||||
enable: true,
|
|
||||||
axis: 'x',
|
|
||||||
step: 0.1,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`百叶模型已替换为 ${currentText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.event_type === 'change_color') {
|
|
||||||
const materialName = event.material_name;
|
|
||||||
const { color, color_map_url, normal_map_url } = event.target_data;
|
|
||||||
console.log('替换百叶模型颜色:', event.target_data);
|
|
||||||
|
|
||||||
kernel.material.apply({
|
|
||||||
target: materialName,
|
|
||||||
albedoColor: color,
|
|
||||||
albedoTexture: color_map_url,
|
|
||||||
normalMap: normal_map_url,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`百叶模型颜色已替换为 ${color}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(`未查询到数据`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`查询SKU配置或替换模型失败:`, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -899,12 +851,64 @@
|
|||||||
|
|
||||||
|
|
||||||
// 监听放置区域点击事件
|
// 监听放置区域点击事件
|
||||||
kernel.on('dropzone:click', (data) => {
|
kernel.on('dropzone:click', async (dropzone_data) => {
|
||||||
console.log('点击了放置区域:', data);
|
const { position,rotation } = dropzone_data.transform;
|
||||||
console.log('中心点:', data.center);
|
|
||||||
console.log('宽度:', data.width);
|
// 将模型放置到该区域
|
||||||
console.log('高度:', data.height);
|
try {
|
||||||
console.log('法线:', data.normal);
|
const response = await fetch(`http://localhost:3000/api/product-configs/by-sku/${sku}`);
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.code === 200 && result.data) {
|
||||||
|
console.log('SKU配置数据:', result.data);
|
||||||
|
console.log('关联事件:', result.data.events);
|
||||||
|
|
||||||
|
// 使用 for...of 循环以支持 await
|
||||||
|
for (const event of result.data.events) {
|
||||||
|
if (event.event_type === 'change_model') {
|
||||||
|
const { file_url, model_control_type, category } = event.target_data;
|
||||||
|
console.log('替换百叶模型:', event);
|
||||||
|
|
||||||
|
await kernel.model.replace({
|
||||||
|
modelId: category,
|
||||||
|
modelUrl: file_url,
|
||||||
|
modelControlType: model_control_type,
|
||||||
|
drag: {
|
||||||
|
enable: true,
|
||||||
|
axis: 'x',
|
||||||
|
step: 0.1,
|
||||||
|
}
|
||||||
|
,
|
||||||
|
transform: {
|
||||||
|
position: position,
|
||||||
|
rotation: rotation,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型已替换为 ${currentText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.event_type === 'change_color') {
|
||||||
|
const materialName = event.material_name;
|
||||||
|
const { color, color_map_url, normal_map_url } = event.target_data;
|
||||||
|
console.log('替换百叶模型颜色:', event.target_data);
|
||||||
|
|
||||||
|
kernel.material.apply({
|
||||||
|
target: materialName,
|
||||||
|
albedoColor: color,
|
||||||
|
albedoTexture: color_map_url,
|
||||||
|
normalMap: normal_map_url,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`百叶模型颜色已替换为 ${color}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`未查询到数据`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`查询SKU配置或替换模型失败:`, error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -24,11 +24,18 @@ type ModelConfig = {
|
|||||||
|
|
||||||
type ModelControlType = 'rotation' | 'color';
|
type ModelControlType = 'rotation' | 'color';
|
||||||
|
|
||||||
|
type ModelTransform = {
|
||||||
|
position?: { x: number; y: number; z: number };
|
||||||
|
rotation?: { x: number; y: number; z: number };
|
||||||
|
scale?: { x: number; y: number; z: number };
|
||||||
|
};
|
||||||
|
|
||||||
type ModelMetadata = {
|
type ModelMetadata = {
|
||||||
modelId: string;
|
modelId: string;
|
||||||
modelUrl: string;
|
modelUrl: string;
|
||||||
modelControlType?: ModelControlType;
|
modelControlType?: ModelControlType;
|
||||||
drag?: DragConfig;
|
drag?: DragConfig;
|
||||||
|
transform?: ModelTransform;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,14 +231,15 @@ export class AppModel extends Monobehiver {
|
|||||||
modelConfig.modelId,
|
modelConfig.modelId,
|
||||||
modelConfig.modelUrl,
|
modelConfig.modelUrl,
|
||||||
modelConfig.modelControlType,
|
modelConfig.modelControlType,
|
||||||
modelConfig.drag
|
modelConfig.drag,
|
||||||
|
modelConfig.transform
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加单个模型
|
* 添加单个模型
|
||||||
*/
|
*/
|
||||||
private async addSingle(modelName: string, modelUrl: string, modelControlType?: ModelControlType, drag?: DragConfig): Promise<LoadResult> {
|
private async addSingle(modelName: string, modelUrl: string, modelControlType?: ModelControlType, drag?: DragConfig, transform?: ModelTransform): Promise<LoadResult> {
|
||||||
// 检查是否已存在
|
// 检查是否已存在
|
||||||
const existingMeshes = this.modelDic.Get(modelName);
|
const existingMeshes = this.modelDic.Get(modelName);
|
||||||
if (existingMeshes?.length && !existingMeshes[0].isDisposed()) {
|
if (existingMeshes?.length && !existingMeshes[0].isDisposed()) {
|
||||||
@ -254,9 +262,15 @@ export class AppModel extends Monobehiver {
|
|||||||
modelId: modelName,
|
modelId: modelName,
|
||||||
modelUrl: modelUrl,
|
modelUrl: modelUrl,
|
||||||
modelControlType: modelControlType,
|
modelControlType: modelControlType,
|
||||||
drag: drag
|
drag: drag,
|
||||||
|
transform: transform
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 应用 transform
|
||||||
|
if (transform) {
|
||||||
|
this.applyTransform(modelName, transform);
|
||||||
|
}
|
||||||
|
|
||||||
// 配置拖拽功能
|
// 配置拖拽功能
|
||||||
if (drag) {
|
if (drag) {
|
||||||
this.mainApp.appModelDrag?.configureDrag(modelName, drag);
|
this.mainApp.appModelDrag?.configureDrag(modelName, drag);
|
||||||
@ -283,7 +297,7 @@ export class AppModel extends Monobehiver {
|
|||||||
EventBridge.modelLoadProgress({ loaded: 0, total, progress: 0, percentage: 0 });
|
EventBridge.modelLoadProgress({ loaded: 0, total, progress: 0, percentage: 0 });
|
||||||
|
|
||||||
for (let i = 0; i < models.length; i++) {
|
for (let i = 0; i < models.length; i++) {
|
||||||
const { modelId, modelUrl, modelControlType, drag } = models[i];
|
const { modelId, modelUrl, modelControlType, drag, transform } = models[i];
|
||||||
|
|
||||||
const result = await this.loadSingleModel(modelUrl, (event) => {
|
const result = await this.loadSingleModel(modelUrl, (event) => {
|
||||||
this.emitProgress(i, total, modelUrl, event);
|
this.emitProgress(i, total, modelUrl, event);
|
||||||
@ -298,9 +312,15 @@ export class AppModel extends Monobehiver {
|
|||||||
modelId: modelId,
|
modelId: modelId,
|
||||||
modelUrl: modelUrl,
|
modelUrl: modelUrl,
|
||||||
modelControlType: modelControlType,
|
modelControlType: modelControlType,
|
||||||
drag: drag
|
drag: drag,
|
||||||
|
transform: transform
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 应用 transform
|
||||||
|
if (transform) {
|
||||||
|
this.applyTransform(modelId, transform);
|
||||||
|
}
|
||||||
|
|
||||||
// 配置拖拽功能
|
// 配置拖拽功能
|
||||||
if (drag) {
|
if (drag) {
|
||||||
this.mainApp.appModelDrag?.configureDrag(modelId, drag);
|
this.mainApp.appModelDrag?.configureDrag(modelId, drag);
|
||||||
@ -426,7 +446,8 @@ export class AppModel extends Monobehiver {
|
|||||||
modelConfig.modelId,
|
modelConfig.modelId,
|
||||||
modelConfig.modelUrl,
|
modelConfig.modelUrl,
|
||||||
modelConfig.modelControlType,
|
modelConfig.modelControlType,
|
||||||
modelConfig.drag
|
modelConfig.drag,
|
||||||
|
modelConfig.transform
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,4 +612,59 @@ export class AppModel extends Monobehiver {
|
|||||||
mesh.scaling.z = scale.z;
|
mesh.scaling.z = scale.z;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将模型放置到指定的放置区域
|
||||||
|
* @param modelId 模型ID
|
||||||
|
* @param zoneInfo 放置区域信息
|
||||||
|
* @param offsetDistance 距离墙面的偏移距离(默认0.1,正数向外)
|
||||||
|
*/
|
||||||
|
placeToZone(modelId: string, zoneInfo: any, offsetDistance: number = 0): void {
|
||||||
|
const meshes = this.modelDic.Get(modelId);
|
||||||
|
if (!meshes?.length) {
|
||||||
|
console.warn(`Model not found: ${modelId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算放置位置:中心点 + 法线方向的偏移
|
||||||
|
const targetPosition = zoneInfo.center.add(zoneInfo.normal.scale(offsetDistance));
|
||||||
|
|
||||||
|
// 计算旋转角度:让模型面向墙面(法线的反方向)
|
||||||
|
const targetDirection = zoneInfo.normal.scale(-1);
|
||||||
|
const angle = Math.atan2(targetDirection.x, targetDirection.z);
|
||||||
|
|
||||||
|
this.getModelTransformTargets(meshes).forEach(mesh => {
|
||||||
|
// 设置位置
|
||||||
|
mesh.position.copyFrom(targetPosition);
|
||||||
|
|
||||||
|
// 设置旋转(只旋转Y轴,让模型面向墙面)
|
||||||
|
if (mesh.rotationQuaternion) {
|
||||||
|
mesh.rotationQuaternion = Quaternion.FromEulerAngles(0, angle, 0);
|
||||||
|
} else {
|
||||||
|
mesh.rotation.set(0, angle, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用 transform 到模型
|
||||||
|
* @param modelId 模型ID
|
||||||
|
* @param transform 变换信息
|
||||||
|
*/
|
||||||
|
private applyTransform(modelId: string, transform: ModelTransform): void {
|
||||||
|
// 应用位置
|
||||||
|
if (transform.position) {
|
||||||
|
this.setPosition(modelId, transform.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用旋转(角度制)
|
||||||
|
if (transform.rotation) {
|
||||||
|
this.setRotation(modelId, transform.rotation, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用缩放
|
||||||
|
if (transform.scale) {
|
||||||
|
this.setScale(modelId, transform.scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,6 +92,12 @@ class AppRay extends Monobehiver {
|
|||||||
const zones = this.mainApp.appDropZone.getPlacementZones();
|
const zones = this.mainApp.appDropZone.getPlacementZones();
|
||||||
const clickedZone = zones.find(zone => zone.mesh === pickInfo.pickedMesh);
|
const clickedZone = zones.find(zone => zone.mesh === pickInfo.pickedMesh);
|
||||||
if (clickedZone) {
|
if (clickedZone) {
|
||||||
|
// 计算该放置区域的目标位置和旋转
|
||||||
|
const offsetDistance = 0.1;
|
||||||
|
const targetPosition = clickedZone.center.add(clickedZone.normal.scale(offsetDistance));
|
||||||
|
const targetDirection = clickedZone.normal.scale(-1);
|
||||||
|
const angle = Math.atan2(targetDirection.x, targetDirection.z);
|
||||||
|
|
||||||
EventBridge.dropZoneClick({
|
EventBridge.dropZoneClick({
|
||||||
wallName: clickedZone.wallName,
|
wallName: clickedZone.wallName,
|
||||||
index: clickedZone.index,
|
index: clickedZone.index,
|
||||||
@ -99,7 +105,24 @@ class AppRay extends Monobehiver {
|
|||||||
width: clickedZone.width,
|
width: clickedZone.width,
|
||||||
height: clickedZone.height,
|
height: clickedZone.height,
|
||||||
normal: clickedZone.normal,
|
normal: clickedZone.normal,
|
||||||
mesh: clickedZone.mesh
|
mesh: clickedZone.mesh,
|
||||||
|
transform: {
|
||||||
|
position: {
|
||||||
|
x: targetPosition.x,
|
||||||
|
y: targetPosition.y,
|
||||||
|
z: targetPosition.z
|
||||||
|
},
|
||||||
|
rotation: {
|
||||||
|
x: 0,
|
||||||
|
y: angle * 180 / Math.PI, // 转换为角度
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
x: 1,
|
||||||
|
y: 1,
|
||||||
|
z: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,4 +55,21 @@ export type DropZoneClickPayload = {
|
|||||||
height: number;
|
height: number;
|
||||||
normal: any;
|
normal: any;
|
||||||
mesh: any;
|
mesh: any;
|
||||||
|
transform: {
|
||||||
|
position: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
};
|
||||||
|
rotation: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
};
|
||||||
|
scale: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -176,6 +176,22 @@ export class KernelAdapter {
|
|||||||
*/
|
*/
|
||||||
scale: (options: { modelId: string; vector3: { x: number; y: number; z: number } }): void => {
|
scale: (options: { modelId: string; vector3: { x: number; y: number; z: number } }): void => {
|
||||||
this.mainApp.appModel.setScale(options.modelId, options.vector3);
|
this.mainApp.appModel.setScale(options.modelId, options.vector3);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 将模型放置到指定的放置区域
|
||||||
|
* @param options 放置配置 { modelId: string, zoneInfo: any, offsetDistance?: number }
|
||||||
|
* @example
|
||||||
|
* // 监听放置区域点击,将模型放置到该区域
|
||||||
|
* kernel.on('dropzone:click', (zoneInfo) => {
|
||||||
|
* kernel.transform.placeToZone({
|
||||||
|
* modelId: "myModel",
|
||||||
|
* zoneInfo: zoneInfo,
|
||||||
|
* offsetDistance: 0.1 // 可选,距离墙面的偏移距离
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
placeToZone: (options: { modelId: string; zoneInfo: any; offsetDistance?: number }): void => {
|
||||||
|
this.mainApp.appModel.placeToZone(options.modelId, options.zoneInfo, options.offsetDistance);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user