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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -1,369 +0,0 @@
/**
* 业务逻辑 - 全局脚本版本
* 适配全局脚本方式调用
*/
(function (window) {
'use strict';
// API 配置
var apiConfig = {
baseUrl: 'https://ztserver.zguiy.com',
getApiUrl: function (path) {
return this.baseUrl + path;
}
};
// 存储 kernel 实例
var kernelInstance = null;
/**
* 初始化应用逻辑 - 注入 kernel 实例
*/
function initApp(kernel) {
if (!kernel) {
throw new Error('kernel 实例是必需的');
}
kernelInstance = kernel;
console.log('应用逻辑已初始化kernel 实例已注入');
return kernelInstance;
}
/**
* 获取当前 kernel 实例
*/
function getKernel() {
if (!kernelInstance) {
throw new Error('请先调用 initApp(kernel) 初始化 kernel 实例');
}
return kernelInstance;
}
/**
* 初始化
*/
async function init(customConfig) {
var kernel = getKernel();
customConfig = customConfig || {};
var defaultConfig = {
container: document.querySelector('#renderDom'),
modelUrlList: [],
env: {
envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env',
intensity: 1.2,
rotationY: 0.3,
background: true
},
gizmo: {
position: true,
rotation: true,
scale: true
},
outline: {
enable: true,
color: "#2196F3",
thickness: 1,
occlusionStrength: 0.1,
occlusionThreshold: 0.0002
}
};
// 合并配置
var config = Object.assign({}, defaultConfig, customConfig);
kernel.init(config);
}
/**
* 初始化加载模型
*/
async function getAutoLoadModelList() {
var kernel = getKernel();
var url = apiConfig.getApiUrl('/api/models/auto-load/list');
console.log('API URL:', url);
var response = await fetch(url);
var data = await response.json();
var models = data.data;
models.forEach(function (model) {
console.log(model.placement_zone);
if (model.placement_zone) {
var placementZone = model.placement_zone;
kernel.dropZone.setData({
color: placementZone.color,
alpha: +placementZone.alpha,
thickness: placementZone.thickness,
showBorder: !placementZone.show_border,
borderColor: placementZone.border_color,
walls: placementZone.walls
});
}
kernel.model.add({
modelId: model.category,
modelUrl: model.file_url,
modelControlType: model.model_control_type
});
});
}
/**
* 获取放置区域
*/
async function getPlacementZone(sku) {
var kernel = getKernel();
var response = await fetch(apiConfig.getApiUrl('/api/product-configs/by-sku/' + sku));
var result = await response.json();
if (result.code === 200) {
var data = result.data;
var enable_placement_zone = data.enable_placement_zone;
var wall_divisions = data.wall_divisions;
if (enable_placement_zone && wall_divisions !== undefined) {
kernel.dropZone.clearZones();
var divisions = wall_divisions.map(function (wall) {
return {
name: wall.name,
divisions: wall.divisions
};
});
kernel.dropZone.updateDivisions(divisions);
kernel.dropZone.show();
}
}
}
/**
* 执行事件
*/
async function getEvent(dropzone_data, sku) {
console.log(sku);
try {
var response = await fetch(apiConfig.getApiUrl('/api/product-configs/by-sku/' + sku));
var result = await response.json();
if (result.code === 200 && result.data) {
console.log('SKU配置数据:', result.data);
console.log('关联事件:', result.data.events);
await executeEvent(dropzone_data, result);
} else {
console.log('未查询到数据');
}
} catch (error) {
console.error('查询SKU配置或替换模型失败:', error);
}
}
/**
* 点击放置区域执行事件
*/
async function executeEvent(dropzone_data, result) {
var kernel = getKernel();
var wallName = dropzone_data.wallName;
var index = dropzone_data.index;
var transform = dropzone_data.transform;
var position = transform.position;
var rotation = transform.rotation;
var events = result.data.events;
for (var i = 0; i < events.length; i++) {
var event = events[i];
if (event.event_type === 'change_model') {
console.log(event);
var targetData = event.target_data;
var id = targetData.id;
var name = targetData.name;
var file_url = targetData.file_url;
var model_control_type = targetData.model_control_type;
var category = targetData.category;
console.log('替换百叶模型:', event);
console.log('替换百叶模型类型:', category);
var modelId = id + '_' + Date.now();
kernel.dropZone.recordModelPlacement(wallName, index, modelId);
await kernel.model.add({
modelId: modelId,
modelUrl: file_url,
modelControlType: model_control_type,
drag: {
enable: true,
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
step: 0.1
},
transform: {
position: position,
rotation: rotation
}
});
console.log('百叶模型已放置为 ' + name);
}
if (event.event_type === 'change_color') {
var materialName = event.material_name;
var colorData = event.target_data;
console.log('替换百叶模型颜色:', colorData);
kernel.material.apply({
target: materialName,
albedoColor: colorData.color,
albedoTexture: colorData.color_map_url,
normalMap: colorData.normal_map_url,
metallic: colorData.metallic,
roughness: colorData.roughness
});
console.log('百叶模型颜色已替换为 ' + colorData.color);
}
}
}
/**
* 换棚子
*/
const executeEvent2 = async (result) => {
const kernel = getKernel();
const { wallName, index, transform } = dropzone_data;
const { position, rotation } = transform;
for (const event of result.data.events) {
if (event.event_type === 'change_model') {
console.log(event.target_data);
const { id, name, file_url, model_control_type, category } = event.target_data;
console.log('替换百叶模型:', event);
console.log('替换百叶模型类型:', category);
// 生成唯一的模型ID
const modelId = id + '_' + Date.now();
// 先记录模型放置(会自动处理替换逻辑)
kernel.dropZone.recordModelPlacement(wallName, index, modelId);
console.log(Math.abs(rotation.y - 90), Math.abs(rotation.y - 90) > 5 ? 'x' : 'z');
// 加载并放置模型
await kernel.model.add({
modelId: modelId,
modelUrl: file_url,
modelControlType: model_control_type,
drag: {
enable: true,
axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z',
step: 0.1,
},
transform: {
position: position,
rotation: rotation,
}
});
console.log(`百叶模型已放置为 ${name}`);
}
if (event.event_type === 'change_color') {
const materialName = event.material_name;
const { color, color_map_url, normal_map_url, metallic, roughness } = event.target_data;
console.log('替换百叶模型颜色:', event.target_data);
kernel.material.apply({
target: materialName,
albedoColor: color,
albedoTexture: color_map_url,
normalMap: normal_map_url,
metallic: metallic,
roughness: roughness
});
console.log(`百叶模型颜色已替换为 ${color}`);
}
}
}
/**
* 加载热点
*/
async function getHotspot() {
var kernel = getKernel();
try {
var response = await fetch(apiConfig.getApiUrl('/api/hotspots?status=active&page=1&pageSize=100'));
var result = await response.json();
if (result.code === 200 && result.data.list.length > 0) {
var hotspots = result.data.list.map(function (item) {
return {
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);
}
}
/**
* 获取产品配置
*/
async function getProductConfig(sku) {
try {
var response = await fetch(apiConfig.getApiUrl('/api/product-configs/by-sku/' + sku));
var result = await response.json();
if (result.code === 200) {
console.log(result.data);
var enable_placement_zone = result.data.enable_placement_zone;
if (enable_placement_zone) {
getPlacementZone(sku);
} else {
executeEvent2(result);
}
}
} catch (error) {
console.error('获取产品配置失败:', error);
}
}
// 暴露到全局
window.AppLogic = {
initApp: initApp,
init: init,
getAutoLoadModelList: getAutoLoadModelList,
getPlacementZone: getPlacementZone,
getEvent: getEvent,
executeEvent: executeEvent,
executeEvent2: executeEvent2,
getHotspot: getHotspot,
getProductConfig: getProductConfig
};
})(window);

View File

@ -15,6 +15,9 @@ const initApp = (kernel) => {
return kernelInstance; return kernelInstance;
}; };
//全局唯一棚子sku
let pergolaSku = ""
/** /**
* 获取当前 kernel 实例 * 获取当前 kernel 实例
*/ */
@ -32,7 +35,7 @@ const init = async (customConfig = {}) => {
const defaultConfig = { const defaultConfig = {
container: document.querySelector('#renderDom'), container: document.querySelector('#renderDom'),
modelUrlList: [], modelUrlList: [],
env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: true }, env: { envPath: 'https://cdn.files.zguiy.com/zt/environment.env', intensity: 1.2, rotationY: 0.3, background: false },
gizmo: { gizmo: {
position: false, position: false,
rotation: false, rotation: false,
@ -57,15 +60,15 @@ const getAutoLoadModelList = async () => {
const kernel = getKernel(); const kernel = getKernel();
const url = getApiUrl('/api/models/auto-load/list') const url = getApiUrl('/api/models/auto-load/list')
console.log('API URL:', url)
const response = await fetch(url) const response = await fetch(url)
const data = await response.json() const data = await response.json()
const models = data.data // 这就是模型列表 const models = data.data // 这就是模型列表
models.forEach(model => { models.forEach(model => {
console.log(model.placement_zone);
if (model.placement_zone) { if (model.placement_zone) {
const { alpha, border_color, color, show_border, thickness, walls } = model.placement_zone const { alpha, border_color, color, show_border, thickness, walls } = model.placement_zone
kernel.dropZone.setData({ kernel.dropZone.setData({
color: color, color: color,
@ -78,6 +81,7 @@ const getAutoLoadModelList = async () => {
} }
kernel.model.add({ kernel.model.add({
modelName: model.name + '_' + model.category,
modelId: model.category, modelId: model.category,
modelUrl: model.file_url, modelUrl: model.file_url,
modelControlType: model.model_control_type, modelControlType: model.model_control_type,
@ -88,7 +92,45 @@ const getAutoLoadModelList = async () => {
//获取放置区域 //获取放置区域
const getPlacementZone = async (sku) => { const getPlacementZone = async (sku) => {
//pergolaSku 是需要在加载棚子的时取其引用传进来的sku则是配件的sku根据配件的sku来判断放置区域
const kernel = getKernel(); const kernel = getKernel();
let division_include = []
// 同时包含10和13
const only10_13 = /(?=.*10)(?=.*13)/.test(pergolaSku)
// 只包含10 无13 无12
const only10 = /(?=.*10)(?!.*13)(?!.*12)/.test(pergolaSku)
// 同时包含10和12
const only10_12 = /(?=.*10)(?=.*12)/.test(pergolaSku)
// 1. 只要字符串里包含 10就返回 true
const has10 = /10/.test(sku);
// 2. 只要字符串里包含 13就返回 true
const has13 = /13/.test(sku);
// 2. 只要字符串里包含 12就返回 true
const has12 = /12/.test(sku);
//棚子包含10不包含13 并且配件是10 说明是正方体 或者是10*20的
if (only10 && has10) {
division_include.push('前', '后', '左', '右', "前1", "后1", "前2", "后2")
}
//棚子同时包10和13的并且含配件是10
if (only10_13 && has10) {
division_include.push('左', '右')
}
//棚子同时包10和13的并且含配件是13
if (only10_13 && has13) {
division_include.push('前', '后')
}
//棚子同时包10和12的并且含配件是12
if (only10_12 && has12) {
division_include.push('前', '后')
}
//棚子同时包10和12的并且含配件是10
if (only10_12 && has10) {
division_include.push('左', '右')
}
const response = await fetch(getApiUrl(`/api/product-configs/by-sku/${sku}`)); const response = await fetch(getApiUrl(`/api/product-configs/by-sku/${sku}`));
const result = await response.json(); const result = await response.json();
@ -97,11 +139,14 @@ const getPlacementZone = async (sku) => {
const { enable_placement_zone, wall_divisions } = result.data; const { enable_placement_zone, wall_divisions } = result.data;
// const {position_x, position_y, position_z} = data; // const {position_x, position_y, position_z} = data;
if (enable_placement_zone && wall_divisions != undefined) { if (enable_placement_zone && wall_divisions != undefined) {
console.log(wall_divisions);
const filteredDivisions = wall_divisions.filter(item => division_include.includes(item.name))
console.log(filteredDivisions);
// 只清除旧的放置区域网格,不清除模型 // 只清除旧的放置区域网格,不清除模型
kernel.dropZone.clearZones(); kernel.dropZone.clearZones();
const divisions = wall_divisions.map(wall => ({ const divisions = filteredDivisions.map(wall => ({
name: wall.name, // 获取最后一个下划线后的部分 name: wall.name,
divisions: wall.divisions divisions: wall.divisions
})) }))
@ -114,6 +159,7 @@ const getPlacementZone = async (sku) => {
//执行事件 //执行事件
const getEvent = async (dropzone_data, sku) => { const getEvent = async (dropzone_data, sku) => {
// 将模型放置到该区域 // 将模型放置到该区域
try { try {
const response = await fetch(getApiUrl(`/api/product-configs/by-sku/${sku}`)); const response = await fetch(getApiUrl(`/api/product-configs/by-sku/${sku}`));
@ -124,7 +170,7 @@ const getEvent = async (dropzone_data, sku) => {
console.log('关联事件:', result.data.events); console.log('关联事件:', result.data.events);
// 使用 for...of 循环以支持 await // 使用 for...of 循环以支持 await
await executeEvent(dropzone_data, result) await executeEvent(dropzone_data, result, sku)
} else { } else {
console.log(`未查询到数据`); console.log(`未查询到数据`);
} }
@ -133,8 +179,8 @@ const getEvent = async (dropzone_data, sku) => {
} }
} }
//点击放置区域执行事件 //点击放置区域执行事件 一般是换配件
const executeEvent = async (dropzone_data, result) => { const executeEvent = async (dropzone_data, result, sku) => {
const kernel = getKernel(); const kernel = getKernel();
const { wallName, index, transform } = dropzone_data; const { wallName, index, transform } = dropzone_data;
@ -142,6 +188,8 @@ const executeEvent = async (dropzone_data, result) => {
let modelId = null; // 在外部声明,用于在两个循环之间传递 let modelId = null; // 在外部声明,用于在两个循环之间传递
let modelName = null; let modelName = null;
let pergolaSku = null; // 用于存储棚子的 SKU
// 第一次循环:处理 change_model // 第一次循环:处理 change_model
for (const event of result.data.events) { for (const event of result.data.events) {
if (event.event_type === 'change_model') { if (event.event_type === 'change_model') {
@ -191,6 +239,18 @@ const executeEvent = async (dropzone_data, result) => {
console.log(`百叶模型颜色已替换为 ${color}`); console.log(`百叶模型颜色已替换为 ${color}`);
} }
} }
// 查找棚子的 SKU从已加载的模型中查找 model_control_type 为 'pergola' 的模型)
const allModels = kernel.model.getAllMetadata();
for (const model of allModels) {
if (model.modelControlType === 'pergola') {
// 棚子模型找到了,可以在这里做其他处理
console.log('找到棚子模型:', model.modelId);
break;
}
}
return pergolaSku;
} }
/** /**
@ -205,7 +265,7 @@ const isModelExists = (modelId) => {
} }
//一般是换棚子/换颜色/设置放置区域 //一般是换棚子/换颜色/设置放置区域
const executeEvent2 = async (result) => { const executeEvent2 = async (result, sku) => {
const kernel = getKernel(); const kernel = getKernel();
// 检查是否有模型更换事件 // 检查是否有模型更换事件
@ -221,19 +281,18 @@ const isModelExists = (modelId) => {
console.log(`检查模型 ${name + '_' + category} 是否存在:`, modelAlreadyExists); console.log(`检查模型 ${name + '_' + category} 是否存在:`, modelAlreadyExists);
} }
} }
kernel.dropZone.hide();
// 只有在需要更换模型且模型不存在时才清除 // 只有在需要更换模型且模型不存在时才清除
if (hasModelChange && !modelAlreadyExists) { if (hasModelChange && !modelAlreadyExists) {
console.log('模型不存在,执行清除操作'); console.log('模型不存在,执行清除操作');
kernel.model.removeAll(); kernel.model.removeAll();
} else if (modelAlreadyExists) {
kernel.dropZone.hide();
console.log('模型已存在,跳过清除操作,仅更新材质');
} }
// 先处理所有 change_model 事件 // 先处理所有 change_model 事件
for (const event of result.data.events) { for (const event of result.data.events) {
console.log(event);
if (event.event_type === 'change_model') { if (event.event_type === 'change_model') {
const { target_data } = event; const { target_data } = event;
console.log(event.target_data); console.log(event.target_data);
@ -338,12 +397,19 @@ const getProductConfig = async (sku) => {
if (result.code === 200) { if (result.code === 200) {
console.log(result.data); console.log(result.data);
const { enable_placement_zone } = result.data; const { enable_placement_zone } = result.data;
// await initPlacementZoneConfig(); //如果触发的是配件,需要显示放置区域
if (enable_placement_zone) { if (enable_placement_zone) {
if (pergolaSku === "") {
console.error("请先加载棚子模型")
return;
}
getPlacementZone(sku) getPlacementZone(sku)
} }
//如果触发的是换棚子模型
else { else {
executeEvent2(result) pergolaSku = sku;
executeEvent2(result, sku)
} }
} }
} catch (error) { } catch (error) {

BIN
examples/app-global.zip Normal file

Binary file not shown.

View File

@ -15,7 +15,7 @@
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
overflow: hidden; overflow: hidden;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); background-color: rgb(227 226 226);
} }
#app { #app {
@ -37,7 +37,7 @@
} }
#config-panel { #config-panel {
width: 320px; width: 400px;
background: rgba(30, 30, 45, 0.95); background: rgba(30, 30, 45, 0.95);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
overflow-y: auto; overflow-y: auto;
@ -221,6 +221,60 @@
cursor: pointer; cursor: pointer;
} }
/* 标签页样式 */
.tabs-container {
margin-bottom: 15px;
}
.tabs-header {
display: flex;
gap: 8px;
margin-bottom: 15px;
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
}
.tab-btn {
padding: 10px 20px;
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.6);
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
font-size: 13px;
transition: all 0.3s;
flex: 1;
margin-bottom: -2px;
}
.tab-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.8);
}
.tab-btn.active {
background: rgba(76, 175, 80, 0.2);
color: #fff;
border-bottom-color: #4CAF50;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* 系列分割线样式 */
.series-divider {
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
text-align: center;
margin: 10px 0;
padding: 5px 0;
letter-spacing: 2px;
}
/* 进度条样式 */ /* 进度条样式 */
#progress-container { #progress-container {
position: absolute; position: absolute;
@ -289,78 +343,145 @@
<div id="click-info-content"></div> <div id="click-info-content"></div>
</div> </div>
<!-- 标签页容器 -->
<div class="tabs-container">
<div class="tabs-header">
<button class="tab-btn active" data-tab="size-1013">10x13</button>
<button class="tab-btn" data-tab="size-1010">10x10</button>
<button class="tab-btn" data-tab="size-1020">10x20</button>
<button class="tab-btn" data-tab="size-1012">10x12</button>
</div>
<!-- 10x13 尺寸配置 -->
<div class="tab-content active" id="tab-size-1013">
<!-- 棚子尺寸 --> <!-- 棚子尺寸 -->
<div class="config-category"> <div class="config-category">
<div class="category-header" data-category="size"> <div class="category-header active" data-category="size-1013">
<span class="category-title">棚子尺寸</span> <span class="category-title">棚子尺寸</span>
<span class="category-arrow"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content"> <div class="category-content expanded">
<!-- 111 系列 -->
<div class="series-divider">----- 111 -----</div>
<div class="option-group"> <div class="option-group">
<button class="option-btn" data-option="size-1">3*3</button> <button class="option-btn" data-option="size-1">SPF111S1013W</button>
<button class="option-btn" data-option="size-2">3x6</button> <button class="option-btn" data-option="size-2">SPF111S1013TA</button>
<button class="option-btn" data-option="size-3">10x13EM星空篷</button> <button class="option-btn" data-option="size-3">SPF111S1013C</button>
<button class="option-btn" data-option="size-4">全铁3x6</button> </div>
<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>
</div> </div>
<!-- 棚子类型 --> <!-- 10x10 尺寸配置 -->
<div class="tab-content" id="tab-size-1010">
<!-- 棚子尺寸 -->
<div class="config-category"> <div class="config-category">
<div class="category-header" data-category="type"> <div class="category-header active" data-category="size-1010">
<span class="category-title">棚子类型</span> <span class="category-title">棚子尺寸</span>
<span class="category-arrow"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content"> <div class="category-content expanded">
<!-- 80 系列 -->
<div class="series-divider">----- 80 -----</div>
<div class="option-group"> <div class="option-group">
<button class="option-btn" data-option="type-1">平顶</button> <button class="option-btn" data-option="size-4">SPF80S1010L</button>
<button class="option-btn" data-option="type-2">尖顶</button> </div>
<button class="option-btn" data-option="type-3">弧形</button> <!-- 111 系列 -->
<button class="option-btn" data-option="type-4">异形</button> <div class="series-divider">----- 111 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-4">SPF111S1010C</button>
<button class="option-btn" data-option="size-5">SPF111S1010TA</button>
<button class="option-btn" data-option="size-6">SPF111S1010W</button>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 百叶 (单选) --> <!-- 10x20 尺寸配置 -->
<div class="tab-content" id="tab-size-1020">
<!-- 棚子尺寸 -->
<div class="config-category"> <div class="config-category">
<div class="category-header" data-category="louver"> <div class="category-header active" data-category="size-1020">
<span class="category-title">百叶</span> <span class="category-title">棚子尺寸</span>
<span class="category-arrow"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content"> <div class="category-content expanded">
<!-- 80 系列 -->
<div class="series-divider">----- 80 -----</div>
<div class="option-group"> <div class="option-group">
<button class="option-btn" data-option="louver-1">SPFPDS13FTW</button> <button class="option-btn" data-option="size-4">SPF80S1020C</button>
<button class="option-btn" data-option="louver-2">SPFPDS13FTC</button> </div>
<button class="option-btn" data-option="louver-3">3m下拉帘</button> <!-- 111 系列 -->
<button class="option-btn" data-option="louver-4">SPFSW13FTW</button> <div class="series-divider">----- 111 -----</div>
<button class="option-btn" data-option="louver-4">SPFSW10FTW</button> <div class="option-group">
<button class="option-btn" data-option="louver-4">SPFPDS10FTC</button> <button class="option-btn" data-option="size-7">SPF111S1020C</button>
<button class="option-btn" data-option="louver-4">SPFPDS10FTW</button> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 配色 --> <!-- 10x12 尺寸配置 -->
<div class="tab-content" id="tab-size-1012">
<!-- 棚子尺寸 -->
<div class="config-category"> <div class="config-category">
<div class="category-header" data-category="color"> <div class="category-header active" data-category="size-1012">
<span class="category-title">配色</span> <span class="category-title">棚子尺寸</span>
<span class="category-arrow"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content"> <div class="category-content expanded">
<!-- 88 系列 -->
<div class="series-divider">----- 88 -----</div>
<div class="option-group"> <div class="option-group">
13 <button class="option-btn" data-option="size-10">SPF88S1012C</button>
<button class="option-btn" data-option="color-1">SPF111S1013W</button> </div>
<button class="option-btn" data-option="color-2">SPF111S1013C</button> </div>
<button class="option-btn" data-option="color-3">SPF111S1013TA</button> </div>
10 </div>
<button class="option-btn" data-option="color-1">SPF111S1010W</button> </div>
<button class="option-btn" data-option="color-2">SPF111S1010C</button>
<button class="option-btn" data-option="color-3">SPF111S1010TA</button> <!-- 百叶配件(独立区域) -->
<div class="louver-section" style="margin-top: 20px; padding-top: 20px; border-top: 2px solid #e0e0e0;">
<div class="config-title" style="font-size: 16px; margin-bottom: 15px;">百叶配件</div>
<!-- 10x13 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1013">
<span class="category-title">13 百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-1">SPFPDS13FTW</button>
<button class="option-btn" data-option="color-2">SPFPDS13FTC</button>
</div>
</div>
</div>
<!-- 10x10 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1010">
<span class="category-title">10 百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-3">SPFPDS10FTW</button>
<button class="option-btn" data-option="color-4">SPFPDS10FTC</button>
</div>
</div>
</div>
<!-- 10x12 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1012">
<span class="category-title">12 百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-7">SPF80CS12FTC</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -457,45 +578,26 @@
</div> </div>
</div> </div>
</div> </div>
<script src="https://sdk.zguiy.com/zt/assets/index.global.js?v=1234"></script>
<!-- 全局脚本方式引入 SDK -->
<script src="https://sdk.zguiy.com/zt/assets/index.global.js"></script>
<!-- 业务逻辑脚本 -->
<script src="./app-global.js"></script> <script src="./app-global.js"></script>
<script> <script>
// 从全局对象获取 SDK kernel // 从全局对象获取 SDK kernel
var kernel = window.faceSDK && window.faceSDK.kernel; var kernel = window.faceSDK && window.faceSDK.kernel;
console.log('kernel:', kernel.on);
if (!kernel) { if (!kernel) {
console.error('faceSDK kernel 不可用,请确认 index.global.js 已正确加载'); console.error('faceSDK kernel 不可用,请确认 index.global.js 已正确加载');
alert('SDK 加载失败,请检查网络连接'); 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) => { // 初始化 AppLogic
window.AppLogic.initApp(kernel);
window.AppLogic.init();
// 存储当前选中的材质名和网格
var currentMaterialName = '';
var currentPickedMesh = null;
kernel.on('all:ready', function (data) {
console.log('所有模块加载完,', data); console.log('所有模块加载完,', data);
kernel.material.apply({ kernel.material.apply({
target: 'Material__2', target: 'Material__2',
@ -506,6 +608,21 @@
// ========== UI 交互逻辑 ========== // ========== UI 交互逻辑 ==========
// 标签页切换
document.querySelectorAll('.tab-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
var tabId = this.dataset.tab;
// 移除所有标签页的激活状态
document.querySelectorAll('.tab-btn').forEach(function (b) { b.classList.remove('active'); });
document.querySelectorAll('.tab-content').forEach(function (c) { c.classList.remove('active'); });
// 激活当前标签页
this.classList.add('active');
document.getElementById('tab-' + tabId).classList.add('active');
});
});
// 折叠面板切换 // 折叠面板切换
document.querySelectorAll('.category-header').forEach(function (header) { document.querySelectorAll('.category-header').forEach(function (header) {
header.addEventListener('click', function () { header.addEventListener('click', function () {
@ -518,7 +635,8 @@
this.classList.toggle('active'); this.classList.toggle('active');
}); });
}); });
var sku = ""
var sku = "";
// 单选按钮逻辑 // 单选按钮逻辑
document.querySelectorAll('.option-btn').forEach(function (btn) { document.querySelectorAll('.option-btn').forEach(function (btn) {
btn.addEventListener('click', async function () { btn.addEventListener('click', async function () {
@ -534,16 +652,6 @@
// 选中当前按钮 // 选中当前按钮
this.classList.add('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('配置变更:', { console.log('配置变更:', {
category: categoryName, category: categoryName,
value: this.dataset.option, value: this.dataset.option,
@ -551,152 +659,107 @@
}); });
var currentText = this.textContent; var currentText = this.textContent;
sku = currentText; sku = currentText;
await window.AppLogic.getProductConfig(currentText) await window.AppLogic.getProductConfig(currentText);
}); });
}); });
document.querySelector('#hotspot-btn').addEventListener('click', async function () { document.querySelector('#hotspot-btn').addEventListener('click', async function () {
await window.AppLogic.getHotspot(); await window.AppLogic.getHotspot();
}) });
document.querySelector('#prevent-btn').addEventListener('click', async function () {
await window.AppLogic.getPlacementZone();
});
// 监听放置区域点击事件
kernel.on('dropzone:click', async function (dropzone_data) {
// 监听热点点击事件 await window.AppLogic.getEvent(dropzone_data, sku);
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) { kernel.on('model:click', function (data) {
console.log('模型点击:', event.detail); console.log('模型点击事件', data);
var meshName = event.detail.meshName; console.log('模型控制类型:', data.modelControlType);
var materialName = event.detail.materialName; switch (data.modelControlType) {
var modelControlType = event.detail.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;
var clickInfoDiv = document.getElementById('click-info'); // 获取已创建的DOM元素
var clickInfoContent = document.getElementById('click-info-content'); 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) + ']';
var html = '<div class="click-info-item">' + // 显示颜色按钮,隐藏旋转按钮
'<span class="click-info-label">类型:</span>' + document.getElementById('color-buttons').style.display = 'flex';
'<span class="click-info-value">模型</span>' + document.getElementById('rotation-buttons').style.display = 'none';
'</div>' +
'<div class="click-info-item">' +
'<span class="click-info-label">网格名称:</span>' +
'<span class="click-info-value">' + meshName + '</span>' +
'</div>';
if (materialName) { // 将DOM附加到点击的3D坐标会自动显示
html += '<div class="click-info-item">' + kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
'<span class="click-info-label">材质名称:</span>' +
'<span class="click-info-value">' + materialName + '</span>' +
'</div>';
} }
break;
case "rotation":
// 显示旋转控制UI
if (data.pickedMesh && data.pickedPoint) {
var meshName = data.pickedMesh.name;
var position = data.pickedPoint;
currentPickedMesh = data.pickedMesh;
if (modelControlType) { // 获取已创建的DOM元素
html += '<div class="click-info-item">' + var infoDiv = document.getElementById('model-info-box');
'<span class="click-info-label">控制类型:</span>' + // 更新信息内容
'<span class="click-info-value">' + modelControlType + '</span>' + document.getElementById('info-name').textContent = '名称: ' + meshName;
'</div>'; 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;
clickInfoContent.innerHTML = html; default:
clickInfoDiv.style.display = 'block'; break;
});
// 多选复选框逻辑
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, window.getCurrentMaterialName = function () { return currentMaterialName; };
selectedValues: selectedValues, window.getCurrentPickedMesh = function () { return currentPickedMesh; };
checked: this.checked,
currentValue: this.dataset.option 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);
} else {
console.log('该热点没有关联SKU');
}
}); });
// 监听配置变更事件(供外部使用) // 移除按钮事件
document.addEventListener('config:change', function (e) { document.getElementById('remove-model-btn').addEventListener('click', function () {
// 这里可以根据配置变更来操作 3D 模型 var pickedMesh = window.getCurrentPickedMesh();
// 例如: if (pickedMesh) {
// if (e.detail.category === 'size') { var modelName = kernel.model.findModelNameByMesh?.(pickedMesh);
// kernel.model.replace({ modelId: 'shed', modelUrl: `/models/shed-${e.detail.value}.glb`, modelControlType: 'rotation' }); if (modelName) {
// } console.log('移除模型:', modelName);
// if (e.detail.category === 'color') { kernel.model.remove({ modelId: modelName });
// kernel.material.apply({ kernel.domTo3D.detach('model-info');
// target: 'ShedMaterial', }
// attribute: 'baseColor', }
// value: getColorValue(e.detail.value)
// });
// }
}); });
// ========== 模型信息框按钮事件 ==========
// 关闭按钮事件 // 关闭按钮事件
document.getElementById('close-info-btn').addEventListener('click', function () { document.getElementById('close-info-btn').addEventListener('click', function () {
kernel.domTo3D.detach('model-info'); kernel.domTo3D.detach('model-info');
@ -758,7 +821,7 @@
console.log('旋转180度模型名:', modelName); console.log('旋转180度模型名:', modelName);
kernel.transform.rotation({ kernel.transform.rotation({
modelId: modelName, modelId: modelName,
vector3: { x: 0, y: 30, z: 0 } vector3: { x: 0, y: 180, z: 0 }
}); });
} else { } else {
console.log('未找到模型名称'); console.log('未找到模型名称');
@ -768,6 +831,8 @@
} }
}); });
// 移除按钮事件 // 移除按钮事件
document.getElementById('remove-model-btn').addEventListener('click', () => { document.getElementById('remove-model-btn').addEventListener('click', () => {
const pickedMesh = window.getCurrentPickedMesh(); const pickedMesh = window.getCurrentPickedMesh();
@ -791,32 +856,25 @@
var dropZoneVisible = false; var dropZoneVisible = false;
document.getElementById('dropzone-btn').addEventListener('click', function () { document.getElementById('dropzone-btn').addEventListener('click', function () {
if (!dropZoneVisible) { if (!dropZoneVisible) {
kernel.dropZone.showAll();
dropZoneVisible = true;
// 更新按钮文字
document.getElementById('dropzone-btn').textContent = '隐藏放置区域'; document.getElementById('dropzone-btn').textContent = '隐藏放置区域';
console.log('已生成并显示放置区域'); console.log('已生成并显示放置区域');
} else { } else {
// 隐藏放置区域
kernel.dropZone.hideAll(); kernel.dropZone.hideAll();
dropZoneVisible = false; dropZoneVisible = false;
// 更新按钮文字
document.getElementById('dropzone-btn').textContent = '生成放置区域'; document.getElementById('dropzone-btn').textContent = '生成放置区域';
console.log('已隐藏放置区域'); console.log('已隐藏放置区域');
} }
}); });
console.log(kernel);
// 监听放置区域点击事件 // 监听放置区域点击事件
kernel.on('dropzone:click', async function (dropzone_data) { kernel.on('dropzone:click', function (dropzone_data) {
window.AppLogic.getEvent(dropzone_data, sku) window.AppLogic.getEvent(dropzone_data, sku);
}); });
// 存储当前选中的材质名和网格 // 存储当前选中的材质名和网格
var currentMaterialName = ''; var currentMaterialName = '';
var currentPickedMesh = null; var currentPickedMesh = null;
@ -824,47 +882,43 @@
kernel.on('model:click', function (data) { kernel.on('model:click', function (data) {
console.log('模型点击事件', data); console.log('模型点击事件', data);
console.log('模型控制类型:', data.modelControlType); console.log('模型控制类型:', data.modelControlType);
var modelName = data.modelName;
console.log('点击的模型ID:', modelName);
switch (data.modelControlType) { switch (data.modelControlType) {
case "color": case "color":
// DOM 2D转3D 示例:点击模型时显示信息框
if (data.pickedMesh && data.pickedPoint) { if (data.pickedMesh && data.pickedPoint) {
var meshName = data.pickedMesh.name; var meshName = data.pickedMesh.name;
var position = data.pickedPoint; // 使用点击位置的坐标 var modelName = kernel.model.findModelNameByMesh(data.pickedMesh) || meshName;
currentMaterialName = data.materialName || ''; // 保存材质名 var position = data.pickedPoint;
currentPickedMesh = data.pickedMesh; // 保存网格对象 currentMaterialName = data.materialName || '';
currentPickedMesh = data.pickedMesh;
// 获取已创建的DOM元素
var infoDiv = document.getElementById('model-info-box'); var infoDiv = document.getElementById('model-info-box');
// 更新信息内容 document.getElementById('info-name').textContent = '模型: ' + modelName;
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('info-position').textContent = '坐标: [' + position.x.toFixed(2) + ', ' + position.y.toFixed(2) + ', ' + position.z.toFixed(2) + ']';
// 显示颜色按钮,隐藏旋转按钮
document.getElementById('color-buttons').style.display = 'flex'; document.getElementById('color-buttons').style.display = 'flex';
document.getElementById('rotation-buttons').style.display = 'none'; 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 }); kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
} }
break; break;
case "rotation": case "rotation":
// 显示旋转控制UI
if (data.pickedMesh && data.pickedPoint) { if (data.pickedMesh && data.pickedPoint) {
var meshName = data.pickedMesh.name; var meshName = data.pickedMesh.name;
var modelName = kernel.model.findModelNameByMesh(data.pickedMesh) || meshName;
var position = data.pickedPoint; var position = data.pickedPoint;
currentPickedMesh = data.pickedMesh; // 保存网格对象 currentPickedMesh = data.pickedMesh;
// 获取已创建的DOM元素
var infoDiv = document.getElementById('model-info-box'); var infoDiv = document.getElementById('model-info-box');
// 更新信息内容
document.getElementById('info-name').textContent = '名称: ' + meshName; 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('info-position').textContent = '坐标: [' + position.x.toFixed(2) + ', ' + position.y.toFixed(2) + ', ' + position.z.toFixed(2) + ']';
// 显示旋转按钮,隐藏颜色按钮
document.getElementById('rotation-buttons').style.display = 'flex'; document.getElementById('rotation-buttons').style.display = 'flex';
document.getElementById('color-buttons').style.display = 'none'; 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 }); kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
} }
break; break;
@ -874,13 +928,9 @@
}); });
// 暴露到全局,供 index.html 使用
window.getCurrentMaterialName = function () { return currentMaterialName; }; window.getCurrentMaterialName = function () { return currentMaterialName; };
window.getCurrentPickedMesh = function () { return currentPickedMesh; }; window.getCurrentPickedMesh = function () { return currentPickedMesh; };
// 暴露 kernel 到全局,方便调试
kernel.on('hotspot:click', function (event) { kernel.on('hotspot:click', function (event) {
console.log('热点被点击:', event); console.log('热点被点击:', event);
@ -890,22 +940,10 @@
if (payload && payload.skus && payload.skus.length > 0) { if (payload && payload.skus && payload.skus.length > 0) {
console.log('热点关联的SKU列表:', payload.skus); console.log('热点关联的SKU列表:', payload.skus);
// 这里可以根据 SKU 列表做进一步处理,比如显示产品信息
} else { } else {
console.log('该热点没有关联SKU'); 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> </script>
</body> </body>

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>

951
examples/demo-global1.html Normal file
View File

@ -0,0 +1,951 @@
<!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-color: rgb(227 226 226);
}
#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: 400px;
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;
}
/* 标签页样式 */
.tabs-container {
margin-bottom: 15px;
}
.tabs-header {
display: flex;
gap: 8px;
margin-bottom: 15px;
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
}
.tab-btn {
padding: 10px 20px;
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.6);
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
font-size: 13px;
transition: all 0.3s;
flex: 1;
margin-bottom: -2px;
}
.tab-btn:hover {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.8);
}
.tab-btn.active {
background: rgba(76, 175, 80, 0.2);
color: #fff;
border-bottom-color: #4CAF50;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* 系列分割线样式 */
.series-divider {
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
text-align: center;
margin: 10px 0;
padding: 5px 0;
letter-spacing: 2px;
}
/* 进度条样式 */
#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="tabs-container">
<div class="tabs-header">
<button class="tab-btn active" data-tab="size-1013">10x13</button>
<button class="tab-btn" data-tab="size-1010">10x10</button>
<button class="tab-btn" data-tab="size-1020">10x20</button>
<button class="tab-btn" data-tab="size-1012">10x12</button>
</div>
<!-- 10x13 尺寸配置 -->
<div class="tab-content active" id="tab-size-1013">
<!-- 棚子尺寸 -->
<div class="config-category">
<div class="category-header active" data-category="size-1013">
<span class="category-title">棚子尺寸</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<!-- 111 系列 -->
<div class="series-divider">----- 111 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-1">SPF111S1013W</button>
<button class="option-btn" data-option="size-2">SPF111S1013TA</button>
<button class="option-btn" data-option="size-3">SPF111S1013C</button>
</div>
</div>
</div>
</div>
<!-- 10x10 尺寸配置 -->
<div class="tab-content" id="tab-size-1010">
<!-- 棚子尺寸 -->
<div class="config-category">
<div class="category-header active" data-category="size-1010">
<span class="category-title">棚子尺寸</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<!-- 80 系列 -->
<div class="series-divider">----- 80 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-4">SPF80S1010L</button>
</div>
<!-- 111 系列 -->
<div class="series-divider">----- 111 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-4">SPF111S1010C</button>
<button class="option-btn" data-option="size-5">SPF111S1010TA</button>
<button class="option-btn" data-option="size-6">SPF111S1010W</button>
</div>
</div>
</div>
</div>
<!-- 10x20 尺寸配置 -->
<div class="tab-content" id="tab-size-1020">
<!-- 棚子尺寸 -->
<div class="config-category">
<div class="category-header active" data-category="size-1020">
<span class="category-title">棚子尺寸</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<!-- 80 系列 -->
<div class="series-divider">----- 80 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-4">SPF80S1020C</button>
</div>
<!-- 111 系列 -->
<div class="series-divider">----- 111 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-7">SPF111S1020C</button>
</div>
</div>
</div>
</div>
<!-- 10x12 尺寸配置 -->
<div class="tab-content" id="tab-size-1012">
<!-- 棚子尺寸 -->
<div class="config-category">
<div class="category-header active" data-category="size-1012">
<span class="category-title">棚子尺寸</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<!-- 88 系列 -->
<div class="series-divider">----- 88 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-10">SPF88S1012C</button>
</div>
</div>
</div>
</div>
</div>
<!-- 百叶配件(独立区域) -->
<div class="louver-section" style="margin-top: 20px; padding-top: 20px; border-top: 2px solid #e0e0e0;">
<div class="config-title" style="font-size: 16px; margin-bottom: 15px;">百叶配件</div>
<!-- 10x13 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1013">
<span class="category-title">13 百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-1">SPFPDS13FTW</button>
<button class="option-btn" data-option="color-2">SPFPDS13FTC</button>
</div>
</div>
</div>
<!-- 10x10 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1010">
<span class="category-title">10 百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-3">SPFPDS10FTW</button>
<button class="option-btn" data-option="color-4">SPFPDS10FTC</button>
</div>
</div>
</div>
<!-- 10x12 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1012">
<span class="category-title">12 百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-7">SPF80CS12FTC</button>
</div>
</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>
<script src="https://sdk.zguiy.com/zt/asstes/index.global.js"></script>
<script src="https://sdk.zguiy.com/zt/asstes/app-global.js"></script>
<script>
// 从全局对象获取 SDK kernel
var kernel = window.faceSDK && window.faceSDK.kernel;
console.log('kernel:', kernel.on);
if (!kernel) {
console.error('faceSDK kernel 不可用,请确认 index.global.js 已正确加载');
alert('SDK 加载失败,请检查网络连接');
}
// 初始化 AppLogic
window.AppLogic.initApp(kernel);
window.AppLogic.init();
// 存储当前选中的材质名和网格
var currentMaterialName = '';
var currentPickedMesh = null;
kernel.on('all:ready', function (data) {
console.log('所有模块加载完,', data);
kernel.material.apply({
target: 'Material__2',
attribute: 'alpha',
value: 0.5,
});
});
// ========== UI 交互逻辑 ==========
// 标签页切换
document.querySelectorAll('.tab-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
var tabId = this.dataset.tab;
// 移除所有标签页的激活状态
document.querySelectorAll('.tab-btn').forEach(function (b) { b.classList.remove('active'); });
document.querySelectorAll('.tab-content').forEach(function (c) { c.classList.remove('active'); });
// 激活当前标签页
this.classList.add('active');
document.getElementById('tab-' + tabId).classList.add('active');
});
});
// 折叠面板切换
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');
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();
});
document.querySelector('#prevent-btn').addEventListener('click', async function () {
await window.AppLogic.getPlacementZone();
});
// 监听放置区域点击事件
kernel.on('dropzone:click', async function (dropzone_data) {
await window.AppLogic.getEvent(dropzone_data, sku);
});
// 监听模型点击事件
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;
}
});
// 暴露到全局,供使用
window.getCurrentMaterialName = function () { return currentMaterialName; };
window.getCurrentPickedMesh = function () { return currentPickedMesh; };
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);
} else {
console.log('该热点没有关联SKU');
}
});
// 移除按钮事件
document.getElementById('remove-model-btn').addEventListener('click', function () {
var pickedMesh = window.getCurrentPickedMesh();
if (pickedMesh) {
var modelName = kernel.model.findModelNameByMesh?.(pickedMesh);
if (modelName) {
console.log('移除模型:', modelName);
kernel.model.remove({ modelId: modelName });
kernel.domTo3D.detach('model-info');
}
}
});
// 关闭按钮事件
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: 180, 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) {
kernel.dropZone.showAll();
dropZoneVisible = true;
document.getElementById('dropzone-btn').textContent = '隐藏放置区域';
console.log('已生成并显示放置区域');
} else {
kernel.dropZone.hideAll();
dropZoneVisible = false;
document.getElementById('dropzone-btn').textContent = '生成放置区域';
console.log('已隐藏放置区域');
}
});
console.log(kernel);
// 监听放置区域点击事件
kernel.on('dropzone:click', 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);
var modelName = data.modelName;
console.log('点击的模型ID:', modelName);
switch (data.modelControlType) {
case "color":
if (data.pickedMesh && data.pickedPoint) {
var meshName = data.pickedMesh.name;
var modelName = kernel.model.findModelNameByMesh(data.pickedMesh) || meshName;
var position = data.pickedPoint;
currentMaterialName = data.materialName || '';
currentPickedMesh = data.pickedMesh;
var infoDiv = document.getElementById('model-info-box');
document.getElementById('info-name').textContent = '模型: ' + modelName;
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';
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
}
break;
case "rotation":
if (data.pickedMesh && data.pickedPoint) {
var meshName = data.pickedMesh.name;
var modelName = kernel.model.findModelNameByMesh(data.pickedMesh) || meshName;
var position = data.pickedPoint;
currentPickedMesh = data.pickedMesh;
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';
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
}
break;
default:
break;
}
});
window.getCurrentMaterialName = function () { return currentMaterialName; };
window.getCurrentPickedMesh = function () { return currentPickedMesh; };
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);
} else {
console.log('该热点没有关联SKU');
}
});
</script>
</body>
</html>

359378
examples/index.global.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
overflow: hidden; overflow: hidden;
background-color: rgb(255, 255, 255); background-color: rgb(227 226 226);
} }
#app { #app {
@ -37,7 +37,7 @@
} }
#config-panel { #config-panel {
width: 320px; width: 400px;
background: rgba(30, 30, 45, 0.95); background: rgba(30, 30, 45, 0.95);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
overflow-y: auto; overflow-y: auto;
@ -265,6 +265,16 @@
display: block; display: block;
} }
/* 系列分割线样式 */
.series-divider {
color: rgba(255, 255, 255, 0.5);
font-size: 12px;
text-align: center;
margin: 10px 0;
padding: 5px 0;
letter-spacing: 2px;
}
/* 进度条样式 */ /* 进度条样式 */
#progress-container { #progress-container {
position: absolute; position: absolute;
@ -351,6 +361,8 @@
<span class="category-arrow expanded"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content expanded"> <div class="category-content expanded">
<!-- 111 系列 -->
<div class="series-divider">----- 111 -----</div>
<div class="option-group"> <div class="option-group">
<button class="option-btn" data-option="size-1">SPF111S1013W</button> <button class="option-btn" data-option="size-1">SPF111S1013W</button>
<button class="option-btn" data-option="size-2">SPF111S1013TA</button> <button class="option-btn" data-option="size-2">SPF111S1013TA</button>
@ -358,20 +370,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1013">
<span class="category-title">百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-1">SPFPDS13FTW</button>
<button class="option-btn" data-option="color-2">SPFPDS13FTC</button>
</div>
</div>
</div>
</div> </div>
<!-- 10x10 尺寸配置 --> <!-- 10x10 尺寸配置 -->
@ -383,6 +381,13 @@
<span class="category-arrow expanded"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content expanded"> <div class="category-content expanded">
<!-- 80 系列 -->
<div class="series-divider">----- 80 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-4">SPF80S1010L</button>
</div>
<!-- 111 系列 -->
<div class="series-divider">----- 111 -----</div>
<div class="option-group"> <div class="option-group">
<button class="option-btn" data-option="size-4">SPF111S1010C</button> <button class="option-btn" data-option="size-4">SPF111S1010C</button>
<button class="option-btn" data-option="size-5">SPF111S1010TA</button> <button class="option-btn" data-option="size-5">SPF111S1010TA</button>
@ -390,20 +395,6 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1010">
<span class="category-title">百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-3">SPFPDS10FTW</button>
<button class="option-btn" data-option="color-4">SPFPDS10FTC</button>
</div>
</div>
</div>
</div> </div>
<!-- 10x20 尺寸配置 --> <!-- 10x20 尺寸配置 -->
@ -415,25 +406,19 @@
<span class="category-arrow expanded"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content expanded"> <div class="category-content expanded">
<!-- 80 系列 -->
<div class="series-divider">----- 80 -----</div>
<div class="option-group">
<button class="option-btn" data-option="size-4">SPF80S1020C</button>
</div>
<!-- 111 系列 -->
<div class="series-divider">----- 111 -----</div>
<div class="option-group"> <div class="option-group">
<button class="option-btn" data-option="size-7">SPF111S1020C</button> <button class="option-btn" data-option="size-7">SPF111S1020C</button>
</div> </div>
</div> </div>
</div> </div>
<!-- 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1020">
<span class="category-title">百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-5">SPFPDS10FTW</button>
<button class="option-btn" data-option="color-6">SPFPDS10FTC</button>
</div>
</div>
</div>
</div> </div>
<!-- 10x12 尺寸配置 --> <!-- 10x12 尺寸配置 -->
@ -445,16 +430,52 @@
<span class="category-arrow expanded"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content expanded"> <div class="category-content expanded">
<!-- 88 系列 -->
<div class="series-divider">----- 88 -----</div>
<div class="option-group"> <div class="option-group">
<button class="option-btn" data-option="size-10">SPF88S1012C</button> <button class="option-btn" data-option="size-10">SPF88S1012C</button>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<!-- 百叶 --> <!-- 百叶配件(独立区域) -->
<div class="louver-section" style="margin-top: 20px; padding-top: 20px; border-top: 2px solid #e0e0e0;">
<div class="config-title" style="font-size: 16px; margin-bottom: 15px;">百叶配件</div>
<!-- 10x13 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1013">
<span class="category-title">13 百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-1">SPFPDS13FTW</button>
<button class="option-btn" data-option="color-2">SPFPDS13FTC</button>
</div>
</div>
</div>
<!-- 10x10 百叶 -->
<div class="config-category">
<div class="category-header active" data-category="louver-1010">
<span class="category-title">10 百叶</span>
<span class="category-arrow expanded"></span>
</div>
<div class="category-content expanded">
<div class="option-group">
<button class="option-btn" data-option="color-3">SPFPDS10FTW</button>
<button class="option-btn" data-option="color-4">SPFPDS10FTC</button>
</div>
</div>
</div>
<!-- 10x12 百叶 -->
<div class="config-category"> <div class="config-category">
<div class="category-header active" data-category="louver-1012"> <div class="category-header active" data-category="louver-1012">
<span class="category-title">百叶</span> <span class="category-title">12 百叶</span>
<span class="category-arrow expanded"></span> <span class="category-arrow expanded"></span>
</div> </div>
<div class="category-content expanded"> <div class="category-content expanded">
@ -464,7 +485,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<button id="hotspot-btn">生成热点</button> <button id="hotspot-btn">生成热点</button>
<button id="prevent-btn">生成防止区域</button> <button id="prevent-btn">生成防止区域</button>

View File

@ -43,7 +43,7 @@ export const init = async (customConfig = {}) => {
const defaultConfig = { const defaultConfig = {
container: document.querySelector('#renderDom'), container: document.querySelector('#renderDom'),
modelUrlList: [], modelUrlList: [],
env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: false }, env: { envPath: 'https://cdn.files.zguiy.com/zt/environment.env', intensity: 1.2, rotationY: 0.3, background: false },
gizmo: { gizmo: {
position: false, position: false,
rotation: false, rotation: false,
@ -100,10 +100,9 @@ export const getAutoLoadModelList = async () => {
//获取放置区域 //获取放置区域
export const getPlacementZone = async (sku) => { export const getPlacementZone = async (sku) => {
//pergolaSku 是需要在加载棚子的时取其引用传进来的sku则是配件的sku根据配件的sku来判断放置区域
const kernel = getKernel(); const kernel = getKernel();
console.log(pergolaSku, sku);
let division_include = [] let division_include = []
// 同时包含10和13 // 同时包含10和13
const only10_13 = /(?=.*10)(?=.*13)/.test(pergolaSku) const only10_13 = /(?=.*10)(?=.*13)/.test(pergolaSku)
// 只包含10 无13 无12 // 只包含10 无13 无12
@ -111,7 +110,6 @@ export const getPlacementZone = async (sku) => {
// 同时包含10和12 // 同时包含10和12
const only10_12 = /(?=.*10)(?=.*12)/.test(pergolaSku) const only10_12 = /(?=.*10)(?=.*12)/.test(pergolaSku)
// 1. 只要字符串里包含 10就返回 true // 1. 只要字符串里包含 10就返回 true
const has10 = /10/.test(sku); const has10 = /10/.test(sku);
@ -121,33 +119,27 @@ export const getPlacementZone = async (sku) => {
// 2. 只要字符串里包含 12就返回 true // 2. 只要字符串里包含 12就返回 true
const has12 = /12/.test(sku); const has12 = /12/.test(sku);
//棚子包含10不包含13 并且配件是10 说明是正方体 或者是10*20的
//包含10不包含13
if (only10 && has10) { if (only10 && has10) {
console.log('棚子包含10不包含13 并且配件是10 说明是正方体 或者是10*20的');
division_include.push('前', '后', '左', '右', "前1", "后1", "前2", "后2") division_include.push('前', '后', '左', '右', "前1", "后1", "前2", "后2")
} }
//同时包10和13 //棚子同时包10和13的并且含配件是10
if (only10_13 && has10) { if (only10_13 && has10) {
console.log('棚子同时包10和13的并且含配件是10');
division_include.push('左', '右') division_include.push('左', '右')
} }
//同时包10和13 //棚子同时包10和13的并且含配件是13
if (only10_13 && has13) { if (only10_13 && has13) {
console.log('棚子同时包10和13的并且含配件是13');
division_include.push('前', '后') division_include.push('前', '后')
} }
//棚子同时包10和12的并且含配件是12
if (only10_12 && has12) { if (only10_12 && has12) {
console.log('棚子同时包10和12的并且含配件是12');
division_include.push('前', '后') division_include.push('前', '后')
} }
//棚子同时包10和12的并且含配件是10
if (only10_12 && has10) { if (only10_12 && has10) {
console.log('棚子同时包10和12的并且含配件是12');
division_include.push('左', '右') division_include.push('左', '右')
} }
const response = await fetch(apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`)); const response = await fetch(apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`));
const result = await response.json(); const result = await response.json();
if (result.code === 200) { if (result.code === 200) {
@ -155,6 +147,7 @@ export const getPlacementZone = async (sku) => {
const { enable_placement_zone, wall_divisions } = result.data; const { enable_placement_zone, wall_divisions } = result.data;
// const {position_x, position_y, position_z} = data; // const {position_x, position_y, position_z} = data;
if (enable_placement_zone && wall_divisions != undefined) { if (enable_placement_zone && wall_divisions != undefined) {
console.log(wall_divisions);
const filteredDivisions = wall_divisions.filter(item => division_include.includes(item.name)) const filteredDivisions = wall_divisions.filter(item => division_include.includes(item.name))
console.log(filteredDivisions); console.log(filteredDivisions);

View File

@ -31,7 +31,7 @@ export class AppCamera extends Monobehiver {
// 限制垂直角范围,实现上帝视角 // 限制垂直角范围,实现上帝视角
this.object.upperBetaLimit = Tools.ToRadians(90); // 最大垂直角接近90度避免万向锁 this.object.upperBetaLimit = Tools.ToRadians(90); // 最大垂直角接近90度避免万向锁
this.object.position = new Vector3(0, 10, 0); this.object.position = new Vector3(0,0, 10);
this.setTarget(0, 0.5, 0); this.setTarget(0, 0.5, 0);
} }

View File

@ -1,6 +1,7 @@
import { Engine } from '@babylonjs/core/Engines/engine'; import { Engine } from '@babylonjs/core/Engines/engine';
import { Monobehiver } from '../base/Monobehiver'; import { Monobehiver } from '../base/Monobehiver';
import { AppConfig } from './AppConfig'; import { AppConfig } from './AppConfig';
import { DefaultRenderingPipeline } from '@babylonjs/core';
/** /**
* 渲染引擎管理类 - 负责创建和管理3D渲染引擎 * 渲染引擎管理类 - 负责创建和管理3D渲染引擎
@ -27,6 +28,9 @@ export class AppEngin extends Monobehiver {
}); });
this.object.setSize(window.innerWidth, window.innerHeight); this.object.setSize(window.innerWidth, window.innerHeight);
this.object.setHardwareScalingLevel(1); // 1:1像素比例 this.object.setHardwareScalingLevel(1); // 1:1像素比例
} }
/** 处理窗口大小变化 */ /** 处理窗口大小变化 */

View File

@ -1,21 +1,22 @@
import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture'; import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture';
import { Monobehiver } from '../base/Monobehiver'; import { Monobehiver } from '../base/Monobehiver';
import { AppConfig } from './AppConfig'; import { AppConfig } from './AppConfig';
import { PhotoDome, StandardMaterial } from '@babylonjs/core';
/** /**
* 环境管理类- 负责创建和管理HDR环境贴图 * 环境管理类- 负责创建和管理HDR环境贴图
*/ */
export class AppEnv extends Monobehiver { export class AppEnv extends Monobehiver {
object: CubeTexture | null; object: CubeTexture | null;
photoDome: PhotoDome | null;
constructor(mainApp: any) { constructor(mainApp: any) {
super(mainApp); super(mainApp);
this.object = null;
} }
/** 初始化 - 创建默认HDR环境 */ /** 初始化 - 创建默认HDR环境 */
Awake(): void { Awake(): void {
this.createHDR(); this.createHDR();
// this.createPanorama();
} }
/** /**
@ -24,7 +25,7 @@ export class AppEnv extends Monobehiver {
*/ */
createHDR(): void { createHDR(): void {
const envPath = AppConfig.env.envPath; const envPath = AppConfig.env.envPath;
const intensity = AppConfig.env.intensity ?? 1.5; const intensity = AppConfig.env.intensity ?? 3;
const rotationY = AppConfig.env.rotationY ?? 0; const rotationY = AppConfig.env.rotationY ?? 0;
const scene = this.mainApp.appScene.object; const scene = this.mainApp.appScene.object;
if (!scene) return; if (!scene) return;
@ -57,6 +58,31 @@ export class AppEnv extends Monobehiver {
} }
createPanorama() {
if (!this.photoDome) {
this.photoDome = new PhotoDome(
'sphere_yundong',
'https://cdn.files.zguiy.com/zt/bg2.jpg', // 全景图路径
{
resolution: 64,
size: 30, // 球体大小,越大越远
generateMipMaps: false,
},
this.mainApp.appScene.object
);
this.photoDome.imageMode = PhotoDome.MODE_MONOSCOPIC; // 单镜头图像
// const clipPlane = new Plane(0, -1, 0, 0); // y < 0 裁掉下半球
// this.mainApp.appScene.object.clipPlane = clipPlane;
(this.photoDome.mesh.material as StandardMaterial).alpha = 0;
// 添加旋转
this.photoDome.mesh.rotation.y = 160 / 180 * Math.PI;
(this.photoDome.mesh.material as StandardMaterial).alpha = 1;
}
}
/** /**
* 修改HDR环境光强度 * 修改HDR环境光强度
* @param intensity 强度值 * @param intensity 强度值

View File

@ -58,10 +58,10 @@ export class AppGround extends Monobehiver {
receiveShadows: true, receiveShadows: true,
position: new Vector3(0, 0, 0), position: new Vector3(0, 0, 0),
textureScale: { u: 10, v: 10 }, textureScale: { u: 10, v: 10 },
color: new Color3(0.5, 0.5, 0.5), color: new Color3(1,1,1),
textureUrl: "https://cdn.files.zguiy.com/zt/ground1.jpg", // 默认贴图 textureUrl: "https://cdn.files.zguiy.com/zt/ground2.png", // 默认贴图
showGrid: true, // 默认显示网格 showGrid: true, // 默认显示网格
gridColor: new Color3(1,1,1), gridColor: new Color3(0,0,0),
gridRatio: 1.0, gridRatio: 1.0,
...config ...config
}; };
@ -100,6 +100,7 @@ export class AppGround extends Monobehiver {
if (this.config.receiveShadows) { if (this.config.receiveShadows) {
this.ground.receiveShadows = true; this.ground.receiveShadows = true;
} }
this.ground.isPickable = false;
} }
/** 创建材质 */ /** 创建材质 */
@ -130,7 +131,7 @@ export class AppGround extends Monobehiver {
// PBR 材质属性设置 // PBR 材质属性设置
this.material.metallic = 0.0; // 非金属 this.material.metallic = 0.0; // 非金属
this.material.roughness = 1.0; // 粗糙表面 this.material.roughness = 1.0; // 粗糙表面
this.material.alpha = 0.2;
this.ground.material = this.material; this.ground.material = this.material;
} }
@ -153,7 +154,7 @@ export class AppGround extends Monobehiver {
const gridPosition = this.config.position ? this.config.position.clone() : new Vector3(0, 0, 0); const gridPosition = this.config.position ? this.config.position.clone() : new Vector3(0, 0, 0);
gridPosition.y += 0.01; // 抬高0.01单位避免z-fighting gridPosition.y += 0.01; // 抬高0.01单位避免z-fighting
this.gridGround.position = gridPosition; this.gridGround.position = gridPosition;
this.gridGround.isPickable = false;
// 创建网格材质 // 创建网格材质
this.gridMaterial = new GridMaterial('gridMaterial', this.mainApp.appScene.object); this.gridMaterial = new GridMaterial('gridMaterial', this.mainApp.appScene.object);
this.gridMaterial.mainColor = this.config.gridColor || new Color3(0.3, 0.3, 0.3); this.gridMaterial.mainColor = this.config.gridColor || new Color3(0.3, 0.3, 0.3);

View File

@ -34,9 +34,8 @@ export class AppLight extends Monobehiver {
/** 初始化灯光并开启阴影 */ /** 初始化灯光并开启阴影 */
Awake(): void { Awake(): void {
const light = new DirectionalLight( // 主光源(模拟太阳)
"mainLight", const light = new DirectionalLight("sun", new Vector3(-1, -2, -1), this.mainApp.appScene.object);
new Vector3(0, -0.5, -1), light.intensity = 1.2;
);
} }
} }

View File

@ -145,7 +145,7 @@ class AppRay extends Monobehiver {
const modelMetadata = this.mainApp.appModel.getMetadataByMesh(pickInfo.pickedMesh); const modelMetadata = this.mainApp.appModel.getMetadataByMesh(pickInfo.pickedMesh);
// 获取模型名称(优先使用 modelName如果没有则使用 modelId // 获取模型名称(优先使用 modelName如果没有则使用 modelId
const modelName = this.mainApp.appModel.findModelNameByMesh(pickInfo.pickedMesh); const modelName = this.mainApp.appModel.findModelNameByMesh(pickInfo.pickedMesh);
console.log(modelName); console.log(modelName);
EventBridge.modelClick({ EventBridge.modelClick({
meshName: pickInfo.pickedMesh.name, meshName: pickInfo.pickedMesh.name,
@ -156,8 +156,7 @@ console.log(modelName);
modelControlType: modelMetadata?.modelControlType, modelControlType: modelMetadata?.modelControlType,
}); });
} }
else{ else {
console.log(1111);
this.mainApp.appSelectionOutline.clear(); this.mainApp.appSelectionOutline.clear();
this.mainApp.appPositionGizmo.detach(); this.mainApp.appPositionGizmo.detach();

View File

@ -75,10 +75,10 @@ export class GameManager extends Monobehiver {
// 初始化材质和网格字典 // 初始化材质和网格字典
this.updateDictionaries(); this.updateDictionaries();
this.cacheRollerDoorMeshes(); // this.cacheRollerDoorMeshes();
this.setRollerDoorScale("Box006.001", new Vector3(0.12, 0.02, 0.118)); // this.setRollerDoorScale("Box006.001", new Vector3(0.12, 0.02, 0.118));
this.setRollerDoorScale("Box005.001", new Vector3(0.13, 0.02, 0.12)); // this.setRollerDoorScale("Box005.001", new Vector3(0.13, 0.02, 0.12));
} }
/** /**