This commit is contained in:
2026-04-24 11:20:27 +08:00
parent e7c1611f6b
commit 09359a1647
20 changed files with 1565 additions and 259 deletions

View File

@ -0,0 +1,173 @@
(function () {
'use strict';
if (!document.querySelector('.customization-3d-wrapper')) return;
const get3DViewer = () => window.Customization3DViewer;
const load3DModel = (modelUrl, productId) => {
const viewer = get3DViewer();
return viewer ? viewer.loadModel(modelUrl, productId) : Promise.resolve(false);
};
const clear3DModel = () => {
const viewer = get3DViewer();
return viewer ? viewer.clearModel() : Promise.resolve();
};
const show3DEmpty = () => {
get3DViewer()?.showEmpty();
};
const get3DModelUrl = (productId, variantId, wrapper) => {
const viewer = get3DViewer();
return viewer ? viewer.getModelUrl(productId, variantId, wrapper) : Promise.resolve(null);
};
const get3DHotspots = () => {
const hotspots = window.CUSTOMIZATION_3D_HOTSPOTS;
if (!Array.isArray(hotspots)) return [];
return hotspots
.filter((item) => item && typeof item === 'object')
.map((item) => {
const meshName = String(item.meshName || '').trim();
if (!meshName) return null;
let offset = [0, 0, 0];
if (Array.isArray(item.offset) && item.offset.length >= 3) {
const parsed = item.offset.slice(0, 3).map((v) => Number(v));
if (parsed.every(Number.isFinite)) {
const maxAbs = Math.max(...parsed.map((v) => Math.abs(v)));
offset = parsed;
}
}
const next = {
id: String(item.id || meshName),
name: String(item.name || item.id || meshName),
meshName,
offset,
};
const color = String(item.color || '').trim();
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color)) next.color = color;
const r = Number(item.radius);
if (Number.isFinite(r) && r > 0) {
next.radius = Math.min(Math.max(r, 0.5), 30);
} else {
const defaultRadius = Number(window.CUSTOMIZATION_3D_HOTSPOT_RADIUS_DEFAULT);
next.radius = Number.isFinite(defaultRadius) && defaultRadius > 0
? Math.min(30, defaultRadius)
: 18;
}
const icon = String(item.icon || '').trim();
if (icon && (/^https?:\/\//i.test(icon) || icon.startsWith('//'))) {
next.icon = icon;
}
if (item.payload && typeof item.payload === 'object' && !Array.isArray(item.payload)) {
next.payload = item.payload;
}
return next;
})
.filter(Boolean);
};
const getHotspotActionConfig = (detail = {}) => {
const all = window.CUSTOMIZATION_3D_HOTSPOT_ACTIONS;
if (!all || typeof all !== 'object') return null;
return all[detail.id] || all[detail.name] || null;
};
const handle3DHotspotClick = (event) => {
const detail = event?.detail || {};
const viewer = get3DViewer();
if (!viewer) return;
if (typeof window.CUSTOMIZATION_3D_ON_HOTSPOT_CLICK === 'function') {
try {
window.CUSTOMIZATION_3D_ON_HOTSPOT_CLICK(detail, viewer);
} catch (err) {
console.warn('[Customization] CUSTOMIZATION_3D_ON_HOTSPOT_CLICK failed:', err);
}
}
const action = getHotspotActionConfig(detail);
if (action?.door) {
viewer.door?.toggle(action.door);
}
if (action?.clipping) {
const clip = action.clipping;
if (typeof clip.height === 'number') {
viewer.clipping?.setY(
clip.height,
clip.keepBelow !== false,
Array.isArray(clip.meshNames) ? clip.meshNames : []
);
}
}
};
const setup3DEventBridge = () => {
if (document.documentElement.dataset.customization3dEventsBoundCopy === '1') return;
document.documentElement.dataset.customization3dEventsBoundCopy = '1';
let hotspotRenderTimer = null;
const renderConfiguredHotspots = () => {
if (hotspotRenderTimer) clearTimeout(hotspotRenderTimer);
hotspotRenderTimer = setTimeout(() => {
hotspotRenderTimer = null;
const hotspots = get3DHotspots();
const viewer = get3DViewer();
viewer?.hotspot?.clear?.();
if (!hotspots.length) return;
viewer?.hotspot?.render(hotspots);
}, 200);
};
document.addEventListener('3d:scene:ready', renderConfiguredHotspots);
document.addEventListener('3d:hotspots:update', renderConfiguredHotspots);
document.addEventListener('3d:hotspot:click', handle3DHotspotClick);
};
const setupWheelScrollLockOn3DContainer = () => {
const container = document.querySelector('[data-3d-container]');
if (!container || container.dataset.customization3dWheelLockCopy === '1') return;
container.dataset.customization3dWheelLockCopy = '1';
container.addEventListener(
'wheel',
(e) => {
if (!container.contains(e.target)) return;
e.preventDefault();
},
{ capture: true, passive: false }
);
};
const refreshHotspots = () => {
document.dispatchEvent(new CustomEvent('3d:hotspots:update', { bubbles: true }));
};
window.Customization3DInteractions = {
load3DModel,
clear3DModel,
show3DEmpty,
get3DModelUrl,
get3DHotspots,
refreshHotspots,
};
const init3DInteractions = () => {
setup3DEventBridge();
setupWheelScrollLockOn3DContainer();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init3DInteractions);
} else {
init3DInteractions();
}
})();