元框基本配置 API
TypeScript/JavaScript API用于将Metabox Basic配置器整合到网络应用程序. 提供3D产品可视化,材料,环境和配置状态的程序控制.
目录
特征
- 框架不可知融合(Angular、React、Vue、vanilla JS)
- 响应性设计支持
- 对材料、环境和产品的完全控制
- 截图和 PDF 导出能力
- 显示大小写/ 激活回放管理
- 完整类型脚本类型定义
- CDN 支持快速原型
- 实时状态同步事件驱动架构
先决条件
运行时间要求 :
- 有ES6模块支持的现代浏览器(Chrome 61+,Firefox 60+,Safari 11+,Edge 79+)
- HTTPS 连接( 非真实引擎流化需要)
- 有效的元框配置符 ID
发展环境:
- 节点.js 20
同伴依赖关系 :
rxjs >=7.8.0 <8.0.0
重要注释 :
integrateMetabox()运行时验证输入- 需要非空闲
configuratorId财务报告和审定财务报表containerId - Iframe URL 格式 :
https://{domain}/metabox-configurator/basic/{configuratorId} - 可选查询参数 :
introImage, (中文(简体) ),introVideo, (中文(简体) ),loadingImage, (中文(简体) ),state, (中文(简体) ),showLoadingProgress, (中文(简体) ),showResumeStreamPopup, (中文(简体) ),showUserInactivityTimer, (中文(简体) ),userInactivityTimeout, (中文(简体) ),autoStartStream - HTTPS 被严格执行( HTTP URL 将被拒绝)
安装
国家防范机制安装(建议)
安装 :
npm install @3dsource/metabox-front-api@latest --save
导入 :
import { integrateMetabox, Communicator, ConfiguratorEnvelope, SetProduct, SetProductMaterial, SetEnvironment, GetScreenshot, GetPdf, saveImage } from '@3dsource/metabox-front-api';
CDN 安装( 快速原型)
jsDelivr: (英语)
import { integrateMetabox, SetProduct, SetProductMaterial, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';
说明: 用于生产, 将特定版本标出, 而不是使用
@latest
快速启动
1. HTML 设置
确保您的 HTML 页面有以下结构, 元框配置器将在此集成 。 您必须为# embed3D 源元素提供 CSS, 以使配置器响应并适应您的布局需要 。 创建配置将嵌入的容器元素 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Metabox Configurator</title>
<style>
#embed3DSource {
width: 100%;
height: 600px;
border: 1px solid #ccc;
border-radius: 8px;
}
</style>
</head>
<body>
<div id="embed3DSource">
<!-- Metabox Configurator will be embedded here -->
</div>
</body>
</html>
2. 基本融合
JavaScript (ES6 模块)
<script type="module">
import { integrateMetabox, SetProduct, SetProductMaterial, SetEnvironment, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';
const configuratorId = 'configurator-id'; // your actual configurator id
// Ensure a container with id embed3DSource exists (see HTML Setup)
integrateMetabox(configuratorId, 'embed3DSource', (api) => {
api.addEventListener('configuratorDataUpdated', (env) => {
console.log('State updated:', env.productId, env.environmentId);
});
api.addEventListener('screenshotReady', (image) => {
if (image) saveImage(image, `configurator-${Date.now()}.png`);
});
// Initial commands example
api.sendCommandToMetabox(new SetProduct('product-1'));
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1280, y: 720 }));
});
</script>
类型脚本/ 角度/ 反应
import { integrateMetabox, Communicator, ConfiguratorEnvelope, SetProduct, SetProductMaterial, SetEnvironment, GetScreenshot, ShowEmbeddedMenu, ShowOverlayInterface, saveImage, type MimeType } from '@3dsource/metabox-front-api';
class MetaboxIntegrator {
private api: Communicator | null = null;
private readonly configuratorId: string;
constructor(configuratorId: string) {
this.configuratorId = configuratorId;
this.initialize();
}
private initialize(): void {
integrateMetabox(this.configuratorId, 'embed3DSource', (api: Communicator) => {
this.api = api;
this.setupEventListeners();
this.setupInitialState();
});
}
private setupEventListeners(): void {
if (!this.api) return;
// Listen for configuration data changes
this.api.addEventListener('configuratorDataUpdated', (data: ConfiguratorEnvelope) => {
console.log('Configurator data updated:', data);
this.handleConfiguratorData(data);
});
// Handle screenshot events
this.api.addEventListener('screenshotReady', (imageData: string | null) => {
saveImage(imageData ?? '', 'configurator-render.png');
});
// Handle viewport ready events
this.api.addEventListener('viewportReady', (isReady: boolean) => {
console.log('Viewport ready:', isReady);
});
// Handle status messages
this.api.addEventListener('statusMessageChanged', (message: string | null) => {
console.log('Status message:', message);
});
}
private setupInitialState(): void {
if (!this.api) return;
// Configure initial product and environment
this.api.sendCommandToMetabox(new SetProduct('your-product-id'));
this.api.sendCommandToMetabox(new SetEnvironment('your-environment-id'));
this.api.sendCommandToMetabox(new ShowOverlayInterface(true));
this.api.sendCommandToMetabox(new ShowEmbeddedMenu(true));
}
private handleConfiguratorData(data: ConfiguratorEnvelope): void {
// Access typed data properties
console.log('Product:', data.productId);
console.log('Materials:', data.productMaterialsIds);
console.log('Environment:', data.environmentId);
// Process configurator data according to your needs
this.updateUI(data);
}
private updateUI(data: ConfiguratorEnvelope): void {
// Update your application UI based on configurator state
// This method would contain your specific UI update logic
}
// Public API methods
public takeScreenshot(format: MimeType = 'image/png', size?: { x: number; y: number }): void {
if (this.api) {
this.api.sendCommandToMetabox(new GetScreenshot(format, size));
}
}
public changeMaterial(slotId: string, materialId: string): void {
if (this.api) {
this.api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
}
}
public changeEnvironment(environmentId: string): void {
if (this.api) {
this.api.sendCommandToMetabox(new SetEnvironment(environmentId));
}
}
public destroy(): void {
this.api = null;
}
}
// Usage example
const configurator = new MetaboxIntegrator('configurator-id');
// Take a high-resolution screenshot
configurator.takeScreenshot('image/png', { x: 1920, y: 1080 });
// Change material
configurator.changeMaterial('seat-fabric', 'leather-brown');
API 参考
系统术语
| 任期 | 说明 | A. 背景情况 |
|---|---|---|
| 产品 | Metabox系统中物理产品的3D数字双子 | 基本配置符 |
| 产品标识 | 独特的产品标识符(系统生成的UUID) | 所有背景 |
| 环境 | 产品可视化的三维场景/房间环境 | 视觉环境 |
| 环境编号 | 独特的环境标识符(系统生成的UUID) | 环境管理 |
| 外部图像 | 自定义与外部系统整合的 SKU/标识符 | 电子商务一体化 |
| 显示 | 产品附带的相机动画序列 | 产品属性 |
| 时间档 | 关于产品/环境的材料槽标识符 | 物资分配 |
| 材料编号 | 独一无二的材料标识符 | 物资分配 |
电子商务配置(CTA集成):
E-Com配置器扩展 Basic配置器,并带有呼叫到动作按钮. 配置 :
配置 :
- CTA 按钮标签 : 自定义文本(如"Get Quote","Add to Cart")
- 回调 URL : 接收配置数据的 HTTP POST 端点
CTA 工作流程 :
User clicks CTA → Metabox POSTs config JSON → Your backend processes → Returns { redirectUrl } → User redirected
后端响应格式 :
{
"redirectUrl": "https://your.site/checkout-or-thank-you"
}
使用大小写 :
- 生成 PDF 引文
- 创建购物卡条目
- 向客户关系管理发送线索信息
- 重定向到自定义的取出流程
核心一体化—— integrateMetabox()
嵌入了Metabox配置器 Iframe并建立了通信通道.
签署:
function integrateMetabox(configuratorId: string, containerId: string, apiReadyCallback: (api: Communicator) => void, config?: IntegrateMetaboxConfig): void;
参数:
| 参数 | 类型 | 需求 | 默认 | 说明 |
|---|---|---|---|---|
configuratorId | string | ✅ | - 说吧 | 配置器 UUID( 非完整 URL) |
containerId | string | ✅ | - 说吧 | DOM 容器元素标识 |
apiReadyCallback | function | ✅ | - 说吧 | API 准备时重新调用 |
config | IntegrateMetaboxConfig | ❌ | {} | 额外的配置选项 |
配置选项( E)IntegrateMetaboxConfig:
interface IntegrateMetaboxConfig {
standalone?: boolean; // Disable built-in Metabox UI
introImage?: string; // URL for intro image
introVideo?: string; // URL for intro video
loadingImage?: string; // URL for loading spinner
state?: string; // Predefined state for initial loading in rison format (see https://github.com/Nanonid/rison)
domain?: string; // Override hostname, without the https:// prefix
showLoadingProgress?: boolean; // Show loading progress bar inside the configurator
showResumeStreamPopup?: boolean; // Show resume stream popup when the stream is paused
showUserInactivityTimer?: boolean; // Show user inactivity countdown popup before disconnecting
userInactivityTimeout?: number; // Inactivity timeout in seconds before the user is warned and disconnected
autoStartStream?: boolean; // Whether the pixel stream starts automatically on load (set false to start it manually via ResumeStream)
}
验证出错( E) :
- 丢弃,如果
configuratorId为空 - 如果找不到容器元素, 则丢弃
- 如果 URL 并非 HTTPS 丢弃
- 用 ID 替换已存在的 Iframe
embeddedContent - 准许iframe
autoplay; fullscreen; encrypted-media附加时tools在使用WebMCP的浏览器中,因此您页面上的浏览器AI代理可以使用配置器注册的工具. 如果你自己建造这个框架,请加上tools联合国allow属性表示相同的效果.
示例
integrateMetabox(
'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
'my-container',
(api) => {
console.log('Configurator ready');
api.sendCommandToMetabox(new SetProduct('product-123'));
},
{
standalone: true,
loadingImage: 'https://cdn.example.com/loader.gif',
},
);
命令类
命令控制配置行为. 通过 api.sendCommandToMetabox(new CommandName(...))。 。 。 .
产品和环境管理
| 命令 | 参数 | 说明 |
|---|---|---|
SetProduct | productId: string | 切换到指定的产品 |
SetProductMaterial | slotId: string, materialId: string | 将材料应用到产品槽中 |
SetEnvironment | environmentId: string | 改变环境/场景 |
SetEnvironmentMaterial | slotId: string, materialId: string | 将材料应用于环境槽 |
SetMaterials | commands: MaterialCommand[] | 将多个材料同时应用到单个信件中 |
ResetConfiguration | 无 | 重置配置器状态为默认值( 产品、 材料、 环境) |
示例
api.sendCommandToMetabox(new SetProduct('product-uuid'));
api.sendCommandToMetabox(new SetProductMaterial('body', 'red-metallic'));
api.sendCommandToMetabox(new ResetConfiguration()); // Restore template defaults
SetMaterials 应用在一个消息中设置的完整材料,而不是每个插槽一个命令——将其用于预设,被保存的配置,或者从外部系统同步出的材料集. 它接受 SetProductMaterial 财务报告和审定财务报表 SetEnvironmentMaterial 实例并维护其顺序:
api.sendCommandToMetabox(new SetMaterials([new SetProductMaterial('body', 'red-metallic'), new SetProductMaterial('trim', 'brushed-steel'), new SetEnvironmentMaterial('floor', 'oak-planks')]));
UI 控制
- 注意: ShowEmberdedMenu和showOverlayInterface命令在独立模式下无效.
| 命令 | 参数 | 说明 |
|---|---|---|
ShowEmbeddedMenu | visible: boolean | 显示/ 隐藏右侧栏 |
ShowOverlayInterface | visible: boolean | 显示/隐藏取景口控制器 |
示例
api.sendCommandToMetabox(new ShowEmbeddedMenu(false)); // Hide for custom UI
相机控制器
| 命令 | 参数 | 说明 |
|---|---|---|
SetCamera | camera: CameraCommandPayload | 设置相机位置、旋转、起伏和限制 |
GetCamera | 无 | 请求当前相机; 通过 getCameraResult 事件 |
ResetCamera | 无 | 将相机重置为默认位置 |
ApplyZoom | zoom: number | 应用缩放三角洲(正=in,负=出) |
示例
api.sendCommandToMetabox(new ApplyZoom(50)); // Zoom in
api.sendCommandToMetabox(new ApplyZoom(-25)); // Zoom out
api.sendCommandToMetabox(new ResetCamera()); // Reset view
api.sendCommandToMetabox(
new SetCamera({
fov: 45,
mode: 'orbit',
position: { x: 100, y: 100, z: 100 },
rotation: { horizontal: 0, vertical: 0 },
restrictions: {
maxDistanceToPivot: 0,
maxFov: 0,
maxHorizontalRotation: 0,
maxVerticalRotation: 0,
minDistanceToPivot: 0,
minFov: 0,
minHorizontalRotation: 0,
minVerticalRotation: 0,
},
}),
);
// Request current camera state
api.sendCommandToMetabox(new GetCamera());
// Listen for the camera data
api.addEventListener('getCameraResult', (camera) => {
console.log('Current camera:', camera);
// camera is of type CameraCommandPayload
});
CameraCommandPayload
A. 使用的有效载荷 SetCamera 命令来配置相机.
interface CameraCommandPayload {
fov?: number;
mode?: 'fps' | 'orbit';
position?: { x: number; y: number; z: number };
rotation?: {
horizontal: number;
vertical: number;
};
restrictions?: {
maxDistanceToPivot?: number;
maxFov?: number;
maxHorizontalRotation?: number;
maxVerticalRotation?: number;
minDistanceToPivot?: number;
minFov?: number;
minHorizontalRotation?: number;
minVerticalRotation?: number;
};
}
fov(数字):以度为单位的视野.mode("fps" qq"轨道"):fps: 第一人称相机,在空间中自由移动.orbit:轨道相机围绕一个枢轴(典型的产品取景器行为).
position({x,y,z}):世界坐标中的相机位置.rotation({水平,垂直}):相机旋转角度以度为单位.horizontal:Yaw (左/右).vertical:皮奇 (上/下).
restrictions(可选性):由取景者用来限制用户/相机移动的限度.- 距离限制(轨道模式):
minDistanceToPivot, (中文(简体) ),maxDistanceToPivot。 。 。 . - 视野限制(第一人称模式):
minFov, (中文(简体) ),maxFov。 。 。 . - 旋转限制 :
minHorizontalRotation, (中文(简体) ),maxHorizontalRotation, (中文(简体) ),minVerticalRotation, (中文(简体) ),maxVerticalRotation。 。 。 .
- 距离限制(轨道模式):
注释:
- 仅提供您想要执行的限制; 保留目前由查看器配置的未指定值 .
- 旋转单位是度;正值/负值跟随取景者的右手坐标系统.
- 单位
orbit模式、距离限制相对于轨道中枢进行解释. ResetCamera恢复当前产品或环境模板所定义的默认位置/旋转/FOV.
显示大小写/ 动画控制
| 命令 | 参数 | 说明 |
|---|---|---|
InitShowcase | 无 | 初始化显示( 加载动画) |
PlayShowcase | 无 | 开始/ 恢复播放动画 |
PauseShowcase | 无 | 暂停动画 |
StopShowcase | 无 | 停止并重置动画 |
测量工具
| 命令 | 参数 | 说明 |
|---|---|---|
ShowMeasurement | 无 | 显示产品尺寸 |
HideMeasurement | 无 | 隐藏维度覆盖 |
流控
| 命令 | 参数 | 说明 |
|---|---|---|
ResumeStream | 无 | 暂停或断开后恢复像素流线 |
StopStream | 无 | 停止像素流出会话并释放其资源 |
ResetUserInactivityTimer | 无 | 重置 AFK 不活动计时器以保持会话的活性 |
示例
// Resume the stream after an idle timeout or network interruption
api.sendCommandToMetabox(new ResumeStream());
// Stop the stream, e.g. when closing a modal or navigating away
api.sendCommandToMetabox(new StopStream());
// Reset the inactivity timer when user confirms they are still present
api.sendCommandToMetabox(new ResetUserInactivityTimer());
导出媒体( M)
| 命令 | 参数 | 说明 | 事件触发 |
|---|---|---|---|
GetScreenshot | mimeType: MimeType, size?: {x: number, y: number} | 渲染截图 | screenshotReady |
GetPdf | 无 | 生成 PDF 导出 | 服务器侧( 无事件) |
GetCallToActionInformation | 无 | 触发 CTA 工作流程 | 后端重定向 |
屏幕截图示例 :
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
api.addEventListener('screenshotReady', (imageData) => {
saveImage(imageData, 'product.png');
});
事件处理
事件驱动的被动状态管理架构. 通过 api.addEventListener(eventName, handler)。 。 。 .
可用事件
| 活动 | 有效载荷类型 | 说明 | 使用大小写 |
|---|---|---|---|
configuratorDataUpdated | ConfiguratorEnvelope | 配置状态已更改 | 与产品/材料/环境同步UI |
ecomConfiguratorDataUpdated | EcomConfigurator | 已装入 CTA 配置 | 显示带有标签的 CTA 按钮 |
viewportReady | boolean | 三维视图端口准备状态 | 隐藏加载, 启用交互 |
showcaseStatusChanged | ShowCaseStatus | 动画回放状态 | 更新播放/暂停按钮状态 |
statusMessageChanged | string | null | 装入/ 进度消息 | 显示用户反馈 |
screenshotReady | string | null | Base64 截图数据 | 下载或显示图像 |
getCameraResult | CameraCommandPayload | 返回当前相机数据 | 抓取/存储当前相机 |
videoResolutionChanged | VideoResolution | 流解已更改 | 调整取景口布局 |
videoStatsUpdated | VideoStats | 视频流质量衡量标准 | QoS仪表板,适应行为 |
loadingStatusChanged | { percents: number; loaderVisible: boolean } | 正在装入进度或覆盖已更改 | 显示自定义加载进度栏 |
userInactivityDetected | { remainingSecondsToDisconnect: number; countdownVisible: boolean } | AFK 倒计时开始或更新 | 显示“ 保持连接” 提示 |
configuratorNotFound | true | 配置符 ID 不存在 | 显示错误而不是等待 |
事件实例
配置状态同步 :
api.addEventListener('configuratorDataUpdated', (data: ConfiguratorEnvelope) => {
console.log('Product:', data.productId);
console.log('Materials:', data.productMaterialsIds);
console.log('Environment:', data.environmentId);
// Update custom UI
updateProductSelector(data.productId);
updateMaterialSwatches(data.productMaterialsIds);
});
屏幕截图处理 :
api.addEventListener('screenshotReady', (imageData: string | null) => {
if (!imageData) {
console.error('Screenshot failed');
return;
}
saveImage(imageData, `product-${Date.now()}.png`);
});
装入状态 :
api.addEventListener('viewportReady', (isReady: boolean) => {
if (isReady) {
hideLoadingSpinner();
enableComponentSelectors();
}
});
api.addEventListener('statusMessageChanged', (message: string | null) => {
document.getElementById('status-text').textContent = message || '';
});
显示大小写控制 :
api.addEventListener('showcaseStatusChanged', (status: ShowCaseStatus) => {
const playButton = document.getElementById('play-btn');
switch (status) {
case 'play':
playButton.textContent = 'Pause';
break;
case 'pause':
case 'stop':
playButton.textContent = 'Play';
break;
}
});
装入进度 :
api.addEventListener('loadingStatusChanged', ({ percents, loaderVisible }) => {
const bar = document.getElementById('progress-bar');
if (bar) {
bar.style.display = loaderVisible ? 'block' : 'none';
bar.style.width = `${percents}%`;
}
});
AFK 无活动倒计时 :
import { ResetUserInactivityTimer } from '@3dsource/metabox-front-api';
api.addEventListener('userInactivityDetected', ({ remainingSecondsToDisconnect, countdownVisible }) => {
const dialog = document.getElementById('afk-dialog');
const countdown = document.getElementById('afk-countdown');
if (dialog) dialog.style.display = countdownVisible ? 'flex' : 'none';
if (countdown) countdown.textContent = String(remainingSecondsToDisconnect);
});
document.getElementById('stay-connected-btn')?.addEventListener('click', () => {
api.sendCommandToMetabox(new ResetUserInactivityTimer());
});
工具函数
saveImage(imageUrl: string, filename: string): void
将基数64-编码图像下载到用户的设备.
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
imageUrl | string | Base64数据 URL(例如, data:image/png;base64,...(中文(简体) ) |
filename | string | 希望的文件名带有扩展名 |
示例
api.addEventListener('screenshotReady', (imageData: string | null) => {
if (imageData) {
saveImage(imageData, `config-${Date.now()}.png`);
}
});
执行情况说明:
以 download 属性以触发浏览器下载.
接受输入 : 只有一类的基数64数据 URL image/png, (中文(简体) ), image/jpeg 或者说
image/webp ——格式 GetScreenshot 生产。 其他任何东西(遥控器)
https:// 图像, 一个 svg+xml 或者说 text/html 使用控制台忽略数据 URL)
警告,而不是下载,因为此功能被输入事件有效载荷
直接关闭后Message频道.
fromCommunicatorEvent(target: Communicator, eventName: string): Observable
RxJS 通讯器事件的包装. RxJS 之类的工作 fromEvent ——创建可输入的可观察器,每次指定事件起火时会发出. 备选案文 api.addEventListener()。 。 。 .
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
target | Communicator | 通讯员听 |
eventName | string | 事件名称(例如, configuratorDataUpdated(中文(简体) ) |
回返情况: Observable<T> - 与事件有效载荷相匹配的可观察型.
示例
import { fromCommunicatorEvent } from '@3dsource/metabox-front-api';
integrateMetabox('configurator-id', 'embed3DSource', (api) => {
fromCommunicatorEvent(api, 'configuratorDataUpdated').subscribe((data) => {
console.log('Product:', data.productId);
});
fromCommunicatorEvent(api, 'screenshotReady').subscribe((imageData) => {
if (imageData) saveImage(imageData, 'screenshot.png');
});
});
独立模式
自定义 UI 执行的无头渲染模式 。 禁用所有内置的元框 UI 元素 .
何时使用独立模式
✅ 使用时 :
- 构建一个完全自定义配置界面
- 实施特定品牌产品选择 UX
- 纳入现有设计系统
- 要求完整控制配置流程
❌ 不使用时间 :
- 用默认的 UI 快速原型已足够
- 需要最小定制
- 有限的发展资源
行为改变
| 特性 | 默认模式 | 独立模式 |
|---|---|---|
| 右侧栏菜单 | 可见 | 隐藏 |
| 视图端口覆盖 | 可见 | 隐藏 |
| 模板逻辑 | 活动 | 已禁用 |
| API 控制 | 部分 | 满 |
| 事件系统 | 可用 | 可用 |
执行情况
类型脚本 :
import { integrateMetabox, Communicator, SetProduct, SetEnvironment } from '@3dsource/metabox-front-api';
integrateMetabox(
'configurator-id',
'embed3DSource',
(api: Communicator) => {
// Work with API here
},
{ standalone: true }, // <- Enable standalone mode to disable default UI
);
Java 脚本( CDN) :
<script type="module">
import { integrateMetabox } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';
integrateMetabox(
'configurator-id',
'embed3DSource',
(api) => {
// Work with API here
},
{ standalone: true }, // <- Enable standalone mode to disable default UI
);
</script>
构建自定义用户界面
要创建自定义配置界面, 请使用 独立模式 然后在发送命令前订阅事件,然后遵循这些步骤:
1. 倾听国家变化
api.addEventListener('configuratorDataUpdated', (data) => {
// Update your UI based on configurator state
});
2. 控制产品配置
api.sendCommandToMetabox(new SetProduct('product-id')); // Change product
api.sendCommandToMetabox(new SetProductMaterial('slot-id', 'material-id')); // Apply material
api.sendCommandToMetabox(new SetEnvironment('environment-id')); // Change environment
3. 导出配置
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
api.sendCommandToMetabox(new GetPdf());
api.sendCommandToMetabox(new GetCallToActionInformation());
实例
E-Com CTA 实例
一旦API准备就绪,启动Call-To-Action的最小例子会从您的页面中流出.
<div id="metabox-container"></div>
<button id="cta-btn" disabled>Send configuration (CTA)</button>
<script type="module">
import { integrateMetabox, GetCallToActionInformation } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';
let api = null;
integrateMetabox('configurator-id', 'metabox-container', (apiInstance) => {
api = apiInstance;
document.getElementById('cta-btn').disabled = false;
});
document.getElementById('cta-btn').addEventListener('click', () => {
// Triggers sending the current configuration to your CTA Callback URL
api.sendCommandToMetabox(new GetCallToActionInformation());
});
</script>
基本产品配置器
一个完整的HTML示例,显示如何将元盒配置器与香草JavaScript集成.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Product Configurator</title>
<style>
#metabox-container {
width: 100%;
height: 600px;
border: 1px solid #ddd;
border-radius: 8px;
}
.controls {
margin: 20px 0;
}
button {
margin: 5px;
padding: 10px 15px;
border: 1px solid #ccc;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
}
button:hover {
background: #e5e5e5;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.error {
color: #d32f2f;
padding: 10px;
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 4px;
margin: 10px 0;
}
</style>
</head>
<body>
<div id="loading" class="loading">Loading configurator...</div>
<div id="error" class="error" style="display: none;"></div>
<div id="metabox-container"></div>
<div class="controls">
<button id="product1-btn" onclick="changeProduct('product-1')" disabled>Product 1</button>
<button id="product2-btn" onclick="changeProduct('product-2')" disabled>Product 2</button>
<button id="red-material-btn" onclick="applyMaterial('slot-1', 'material-red')" disabled>Red Material</button>
<button id="blue-material-btn" onclick="applyMaterial('slot-1', 'material-blue')" disabled>Blue Material</button>
<button id="screenshot-btn" onclick="takeScreenshot()" disabled>Take Screenshot</button>
</div>
<script type="module">
import { integrateMetabox, SetProduct, SetProductMaterial, GetScreenshot, saveImage } from 'https://cdn.jsdelivr.net/npm/@3dsource/metabox-front-api@latest/+esm';
let api = null;
let isApiReady = false;
// Helper function to show/hide loading state
function setLoadingState(loading) {
const loadingEl = document.getElementById('loading');
const buttons = document.querySelectorAll('button');
loadingEl.style.display = loading ? 'block' : 'none';
buttons.forEach((btn) => (btn.disabled = loading || !isApiReady));
}
// Helper function to show error messages
function showError(message) {
const errorEl = document.getElementById('error');
errorEl.textContent = message;
errorEl.style.display = 'block';
setLoadingState(false);
}
// Initialize configurator with error handling
try {
// Replace 'configurator-id' with your actual configurator ID from 3DSource
const configuratorId = 'configurator-id';
integrateMetabox(configuratorId, 'metabox-container', (apiInstance) => {
try {
api = apiInstance;
isApiReady = true;
setLoadingState(false);
console.log('Configurator ready!');
// Listen for configurator state changes
api.addEventListener('configuratorDataUpdated', (data) => {
console.log('Configurator state updated:', data);
// You can update your UI based on the current state
// For example, update available materials, products, etc.
});
// Listen for screenshot completion
api.addEventListener('screenshotReady', (imageData) => {
console.log('Screenshot captured successfully');
// Save the image with a timestamp
saveImage(imageData, `configurator-screenshot-${Date.now()}.png`);
});
} catch (error) {
console.error('Error setting up API:', error);
showError('Failed to initialize configurator API: ' + error.message);
}
});
} catch (error) {
console.error('Error initializing configurator:', error);
showError('Failed to load configurator: ' + error.message);
}
// Global functions for button interactions
window.changeProduct = (productId) => {
if (!isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Changing to product: ${productId}`);
api.sendCommandToMetabox(new SetProduct(productId));
} catch (error) {
console.error('Error changing product:', error);
showError('Failed to change product: ' + error.message);
}
};
window.applyMaterial = (slotId, materialId) => {
if (!isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Applying material ${materialId} to slot ${slotId}`);
api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
} catch (error) {
console.error('Error applying material:', error);
showError('Failed to apply material: ' + error.message);
}
};
window.takeScreenshot = () => {
if (!isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Taking screenshot...');
// Request high-quality screenshot in PNG format
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
} catch (error) {
console.error('Error taking screenshot:', error);
showError('Failed to take screenshot: ' + error.message);
}
};
</script>
</body>
</html>
反应集成示例
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { integrateMetabox, Communicator, ConfiguratorEnvelope, SetProduct, SetProductMaterial, GetScreenshot, saveImage } from '@3dsource/metabox-front-api';
interface MetaboxConfiguratorProps {
configuratorId: string;
onStateChange?: (data: ConfiguratorEnvelope) => void;
onError?: (error: string) => void;
className?: string;
}
interface ConfiguratorState {
isLoading: boolean;
error: string | null;
isApiReady: boolean;
}
const MetaboxConfigurator: React.FC<MetaboxConfiguratorProps> = ({ configuratorId, onStateChange, onError, className }) => {
const containerRef = useRef<HTMLDivElement>(null);
const [api, setApi] = useState<Communicator | null>(null);
const [state, setState] = useState<ConfiguratorState>({
isLoading: true,
error: null,
isApiReady: false,
});
// Generate unique container ID to avoid conflicts
const containerId = useRef(`metabox-container-${Math.random().toString(36).substr(2, 9)}`);
// Error handler
const handleError = useCallback(
(error: string) => {
setState((prev) => ({ ...prev, error, isLoading: false }));
onError?.(error);
console.error('Metabox Configurator Error:', error);
},
[onError],
);
// Initialize configurator
useEffect(() => {
if (!containerRef.current) return;
let mounted = true;
const initializeConfigurator = async () => {
try {
setState((prev) => ({ ...prev, isLoading: true, error: null }));
// Set the container ID
containerRef.current!.id = containerId.current;
integrateMetabox(configuratorId, containerId.current, (apiInstance) => {
if (!mounted) return; // Component was unmounted
try {
setApi(apiInstance);
setState((prev) => ({ ...prev, isLoading: false, isApiReady: true }));
// Set up event listeners with proper typing
apiInstance.addEventListener('configuratorDataUpdated', (data: ConfiguratorEnvelope) => {
if (mounted) {
onStateChange?.(data);
}
});
apiInstance.addEventListener('screenshotReady', (imageData: string) => {
if (mounted) {
console.log('Screenshot captured successfully');
saveImage(imageData, `configurator-screenshot-${Date.now()}.png`);
}
});
console.log('React Metabox Configurator initialized successfully');
} catch (error) {
if (mounted) {
handleError(`Failed to set up API: ${error instanceof Error ? error.message : String(error)}`);
}
}
});
} catch (error) {
if (mounted) {
handleError(`Failed to initialize configurator: ${error instanceof Error ? error.message : String(error)}`);
}
}
};
initializeConfigurator();
// Cleanup function
return () => {
mounted = false;
if (api) {
// Clean up any event listeners if the API provides cleanup methods
console.log('Cleaning up Metabox Configurator');
}
};
}, [configuratorId, onStateChange, handleError, api]);
// Command methods with error handling
const changeProduct = useCallback(
(productId: string) => {
if (!state.isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Changing to product: ${productId}`);
api.sendCommandToMetabox(new SetProduct(productId));
} catch (error) {
handleError(`Failed to change product: ${error instanceof Error ? error.message : String(error)}`);
}
},
[api, state.isApiReady, handleError],
);
const applyMaterial = useCallback(
(slotId: string, materialId: string) => {
if (!state.isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Applying material ${materialId} to slot ${slotId}`);
api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
} catch (error) {
handleError(`Failed to apply material: ${error instanceof Error ? error.message : String(error)}`);
}
},
[api, state.isApiReady, handleError],
);
const takeScreenshot = useCallback(() => {
if (!state.isApiReady || !api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Taking screenshot...');
api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
} catch (error) {
handleError(`Failed to take screenshot: ${error instanceof Error ? error.message : String(error)}`);
}
}, [api, state.isApiReady, handleError]);
// Render error state
if (state.error) {
return (
<div className={className}>
<div
style={{
color: '#d32f2f',
padding: '20px',
background: '#ffebee',
border: '1px solid #ffcdd2',
borderRadius: '4px',
textAlign: 'center',
}}
>
<h3>Configurator Error</h3>
<p>{state.error}</p>
<button
onClick={() => window.location.reload()}
style={{
padding: '10px 20px',
marginTop: '10px',
border: '1px solid #d32f2f',
background: '#fff',
color: '#d32f2f',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Reload Page
</button>
</div>
</div>
);
}
return (
<div className={className}>
{/* Loading indicator */}
{state.isLoading && (
<div
style={{
textAlign: 'center',
padding: '40px',
color: '#666',
background: '#f5f5f5',
borderRadius: '8px',
}}
>
<div>Loading configurator...</div>
<div style={{ marginTop: '10px', fontSize: '14px' }}>Please wait while we initialize the 3D viewer</div>
</div>
)}
{/* Configurator container */}
<div
ref={containerRef}
style={{
width: '100%',
height: '600px',
border: '1px solid #ddd',
borderRadius: '8px',
display: state.isLoading ? 'none' : 'block',
}}
/>
{/* Controls */}
{state.isApiReady && (
<div style={{ marginTop: '20px' }}>
<div style={{ marginBottom: '10px', fontWeight: 'bold', color: '#333' }}>Configurator Controls:</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
<button
onClick={() => changeProduct('product-1')}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #ccc',
borderRadius: '4px',
background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
transition: 'background-color 0.2s',
}}
onMouseOver={(e) => {
if (state.isApiReady) e.currentTarget.style.background = '#e5e5e5';
}}
onMouseOut={(e) => {
if (state.isApiReady) e.currentTarget.style.background = '#f5f5f5';
}}
>
Product 1
</button>
<button
onClick={() => changeProduct('product-2')}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #ccc',
borderRadius: '4px',
background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
}}
>
Product 2
</button>
<button
onClick={() => applyMaterial('slot-1', 'material-red')}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #ccc',
borderRadius: '4px',
background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
}}
>
Red Material
</button>
<button
onClick={() => applyMaterial('slot-1', 'material-blue')}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #ccc',
borderRadius: '4px',
background: state.isApiReady ? '#f5f5f5' : '#e0e0e0',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
}}
>
Blue Material
</button>
<button
onClick={takeScreenshot}
disabled={!state.isApiReady}
style={{
padding: '10px 15px',
border: '1px solid #007bff',
borderRadius: '4px',
background: state.isApiReady ? '#007bff' : '#6c757d',
color: 'white',
cursor: state.isApiReady ? 'pointer' : 'not-allowed',
}}
>
📸 Take Screenshot
</button>
</div>
</div>
)}
</div>
);
};
export default MetaboxConfigurator;
角融合示例
// metabox-configurator.component.ts
import { Component, input, OnInit, output, signal } from '@angular/core';
import { Communicator, ConfiguratorEnvelope, GetCallToActionInformation, GetPdf, GetScreenshot, InitShowcase, integrateMetabox, PauseShowcase, PlayShowcase, saveImage, SetProductMaterial, SetProduct, ShowEmbeddedMenu, ShowOverlayInterface, StopShowcase } from '@3dsource/metabox-front-api';
interface ConfiguratorState {
isLoading: boolean;
error: string | null;
isApiReady: boolean;
}
@Component({
selector: 'app-metabox-configurator',
template: `
@let _state = state();
<div class="configurator-container">
<!-- Loading State -->
@if (_state.isLoading) {
<div class="loading-container">
<div class="loading-content">
<div class="loading-spinner"></div>
<div class="loading-text">Loading configurator...</div>
<div class="loading-subtext">Please wait while we initialize the 3D viewer</div>
</div>
</div>
}
<!-- Error State -->
@if (_state.error) {
<div class="error-container">
<h3>Configurator Error</h3>
<p>{{ _state.error }}</p>
<button (click)="retryInitialization()" class="retry-button">Retry</button>
</div>
}
<!-- Configurator Container -->
<div [id]="containerId()" class="configurator-viewport" [hidden]="_state.isLoading || _state.error"></div>
<!-- Controls -->
@if (_state.isApiReady) {
<div class="controls">
<div class="controls-title">Configurator Controls:</div>
<div class="controls-buttons">
<button (click)="changeProduct('541f46ab-a86c-48e3-bcfa-f92341483db3')" [disabled]="!_state.isApiReady" class="control-button">Product Change</button>
<button (click)="initShowcase()" [disabled]="!_state.isApiReady" class="control-button">Init showcase</button>
<button (click)="stopShowcase()" [disabled]="!_state.isApiReady" class="control-button">Stop showcase</button>
<button (click)="playShowcase()" [disabled]="!_state.isApiReady" class="control-button">Play showcase</button>
<button (click)="pauseShowcase()" [disabled]="!_state.isApiReady" class="control-button">Pause showcase</button>
<button (click)="applyMaterial('slot-1', 'material-red')" [disabled]="!_state.isApiReady" class="control-button">Red Material</button>
<button (click)="applyMaterial('slot-1', 'material-blue')" [disabled]="!_state.isApiReady" class="control-button">Blue Material</button>
<button (click)="getPdf()" [disabled]="!_state.isApiReady" class="control-button">Get PDF</button>
<button (click)="takeScreenshot()" [disabled]="!_state.isApiReady" class="control-button screenshot-button">📸 Take Screenshot</button>
<button (click)="sendCallToActionInformation()" [disabled]="!_state.isApiReady" class="control-button">Send Call To Action Information</button>
</div>
</div>
}
</div>
`,
styles: [
`
.configurator-container {
width: 100%;
position: relative;
}
.configurator-viewport {
width: 100%;
height: 600px;
border: 1px solid #ddd;
border-radius: 8px;
}
.loading-container {
display: flex;
align-items: center;
justify-content: center;
height: 600px;
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 8px;
}
.loading-content {
text-align: center;
color: #666;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading-text {
font-size: 16px;
font-weight: 500;
margin-bottom: 8px;
}
.loading-subtext {
font-size: 14px;
opacity: 0.8;
}
.error-container {
padding: 40px;
text-align: center;
background: #ffebee;
border: 1px solid #ffcdd2;
border-radius: 8px;
color: #d32f2f;
}
.error-container h3 {
margin: 0 0 16px 0;
font-size: 18px;
}
.error-container p {
margin: 0 0 20px 0;
font-size: 14px;
}
.retry-button {
padding: 10px 20px;
border: 1px solid #d32f2f;
background: #fff;
color: #d32f2f;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.retry-button:hover {
background: #d32f2f;
color: #fff;
}
.controls {
margin-top: 20px;
}
.controls-title {
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.controls-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.control-button {
padding: 10px 15px;
border: 1px solid #ccc;
border-radius: 4px;
background: #f5f5f5;
cursor: pointer;
transition: all 0.2s;
font-size: 14px;
}
.control-button:hover:not(:disabled) {
background: #e5e5e5;
}
.control-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.screenshot-button {
border-color: #007bff;
background: #007bff;
color: white;
}
.screenshot-button:hover:not(:disabled) {
background: #0056b3;
}
`,
],
})
export class MetaboxConfiguratorComponent implements OnInit {
configuratorId = input.required<string>();
stateChange = output<ConfiguratorEnvelope>();
errorFired = output<string>();
state = signal<ConfiguratorState>({
isLoading: true,
error: null,
isApiReady: false,
});
containerId = signal(`metabox-container-${Math.random().toString(36).substring(2, 9)}`);
private api: Communicator | null = null;
ngOnInit(): void {
this.initializeConfigurator();
}
// Public methods for external control
changeProduct(productId: string): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Changing to product: ${productId}`);
this.api.sendCommandToMetabox(new SetProduct(productId));
} catch (error) {
this.handleError(`Failed to change product: ${this.getErrorMessage(error)}`);
}
}
initShowcase(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
this.api.sendCommandToMetabox(new InitShowcase());
} catch (error) {
this.handleError(`Failed to init showcase for product: ${this.getErrorMessage(error)}`);
}
}
stopShowcase(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Stop showcase`);
this.api.sendCommandToMetabox(new StopShowcase());
} catch (error) {
this.handleError(`Failed to init showcase for product: ${this.getErrorMessage(error)}`);
}
}
playShowcase(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Play showcase`);
this.api.sendCommandToMetabox(new PlayShowcase());
} catch {
this.handleError(`Failed to play showcase`);
}
}
pauseShowcase(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Pause showcase`);
this.api.sendCommandToMetabox(new PauseShowcase());
} catch {
this.handleError(`Failed to pause showcase`);
}
}
applyMaterial(slotId: string, materialId: string): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log(`Applying material ${materialId} to slot ${slotId}`);
this.api.sendCommandToMetabox(new SetProductMaterial(slotId, materialId));
} catch (error) {
this.handleError(`Failed to apply material: ${this.getErrorMessage(error)}`);
}
}
takeScreenshot(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Taking screenshot...');
this.api.sendCommandToMetabox(new GetScreenshot('image/png', { x: 1920, y: 1080 }));
} catch (error) {
this.handleError(`Failed to take screenshot: ${this.getErrorMessage(error)}`);
}
}
sendCallToActionInformation(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Generating cta information...');
this.api.sendCommandToMetabox(new GetCallToActionInformation());
} catch (error) {
this.handleError(`Failed to generating cta information: ${this.getErrorMessage(error)}`);
}
}
getPdf(): void {
if (!this.state().isApiReady || !this.api) {
console.warn('API not ready yet');
return;
}
try {
console.log('Generating PDF...');
this.api.sendCommandToMetabox(new GetPdf());
} catch (error) {
this.handleError(`Failed to generating pdf: ${this.getErrorMessage(error)}`);
}
}
retryInitialization(): void {
this.updateState({ error: null });
this.initializeConfigurator();
}
sendInitCommands() {
if (!this.api) {
return;
}
this.api.sendCommandToMetabox(new ShowEmbeddedMenu(true));
this.api.sendCommandToMetabox(new ShowOverlayInterface(true));
}
private initializeConfigurator() {
try {
this.updateState({ isLoading: true, error: null });
integrateMetabox(this.configuratorId(), this.containerId(), (apiInstance) => {
try {
this.api = apiInstance;
this.updateState({ isLoading: false, isApiReady: true });
this.sendInitCommands();
this.setupEventListeners();
console.log('Angular Metabox Configurator initialized successfully');
} catch (error) {
this.handleError(`Failed to set up API: ${this.getErrorMessage(error)}`);
}
});
} catch (error) {
this.handleError(`Failed to initialize configurator: ${this.getErrorMessage(error)}`);
}
}
private setupEventListeners(): void {
if (!this.api) {
return;
}
// Listen for configurator state changes
this.api.addEventListener('configuratorDataUpdated', (data: ConfiguratorEnvelope) => {
console.log('Configurator state updated:', data);
this.stateChange.emit(data);
});
// Listen for screenshot completion
this.api.addEventListener('screenshotReady', (imageData: string | null) => {
console.log(`Screenshot captured successfully ${imageData ?? ''}`);
saveImage(imageData ?? '', `configurator-screenshot-${Date.now()}.png`);
});
}
private updateState(updates: Partial<ConfiguratorState>): void {
this.state.set({ ...this.state(), ...updates });
}
private handleError(errorMessage: string): void {
console.error('Metabox Configurator Error:', errorMessage);
this.updateState({ error: errorMessage, isLoading: false });
this.errorFired.emit(errorMessage);
}
private getErrorMessage(error: any): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
}
此角示例的关键特征 :
- 综合错误处理: 尝试捕获块和方便用户的错误状态
- 装入国: 初始化期间带有自旋器的视觉反馈
- 类型脚本集成: 带有适当接口的完整类型安全
- 独特的集装箱标识: 自动生成ID以避免冲突
- 活动产出: 向父组件输入状态变化和错误
- 响应设计: 干净、可访问的按钮布局,并有适当的样式
用法示例:
// app.component.ts
import { Component } from '@angular/core';
import { ConfiguratorEnvelope } from '@3dsource/metabox-front-api';
@Component({
selector: 'app-root',
template: `
<div class="app-container">
<h1>My Product Configurator</h1>
<app-metabox-configurator [configuratorId]="configuratorId" (stateChange)="onConfiguratorStateChange($event)" (errorFired)="onConfiguratorError($event)"></app-metabox-configurator>
</div>
`,
styles: [
`
.app-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
`,
],
})
export class AppComponent {
configuratorId = 'configurator-id';
onConfiguratorStateChange(data: ConfiguratorEnvelope): void {
console.log('Configurator state changed:', data);
// Update your application state based on configurator changes
}
onConfiguratorError(error: string): void {
console.error('Configurator error:', error);
// Handle errors (show notifications, log to analytics, etc.)
}
}
要使用此组件 :
- 安装软件包 :
npm install @3dsource/metabox-front-api@latest - 导入您的角模块中的组件
- 替换
'configurator-id'使用您实际配置的 ID - 将占位符ID替换为您的真实产品、材料和插槽ID
- 通过修改组件样式自定义样式
解决问题
共同问题
1. 不装入配置器
症状: 空白屏幕, iframe 未出现, 无限期装入
根原因解决方案( S) :
| 问题 | 诊断 | 解决方案 |
|---|---|---|
| 无效的配置符 ID | 浏览器控制台显示404个错误 | 在元框管理员中验证配置器 ID |
| 未找到容器 | Error: Container element not found | 调用前确保元素存在 integrateMetabox() |
| HTTP 代替 HTTPS | 混合内容警告 | 在本地主机上使用 HTTPS 或测试 |
| 集装箱没有尺寸 | 隐形iframe (0x0) | 设置容器元素的明显宽度/ 高度 |
| CORS/框架封锁 | X- Frame- 选项错误 | 在元框设置中检查域允许列表 |
诊断代码 :
const containerId = 'embed3DSource';
const container = document.getElementById(containerId);
if (!container) {
throw new Error(`Container #${containerId} not found`);
}
// Verify container has dimensions
const rect = container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
console.warn('⚠️ Container has no dimensions');
}
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
console.warn('⚠️ HTTPS required for Unreal Engine streaming');
}
integrateMetabox('configurator-id', containerId, (api) => {
console.log('✅ Configurator loaded');
});
2. 不起作用的命令
症状: 命令已发送, 但配置中没有视觉变化
常见错误:
| 错误 | 问题 | 解决方案 |
|---|---|---|
| API 准备好前发送 | 忽略命令 | 只送进来 apiReadyCallback |
| 错误的产品/材料标识 | 未找到实体 | 验证身份 configuratorDataUpdated 事件 |
| 错误的位号 | 拒绝的材料 | 从配置器数据校验槽ID |
正确模式 :
integrateMetabox('configurator-id', 'container', (api) => {
// ✅ Correct: Commands sent after API ready
api.sendCommandToMetabox(new SetProduct('product-id'));
api.sendCommandToMetabox(new SetProductMaterial('slot-id', 'material-id'));
});
// ❌ Wrong: Command sent too early
// api.sendCommandToMetabox(new SetProduct('product-id'));
获取帮助
如果你还在经历问题:
- 检查浏览器控制台的错误消息
- 在单独的浏览器标签中校验您的配置 ID
- 用最小的例子进行测试以孤立问题
- 联系支持 : 浏览器版本、 配置器 URL、 控制台错误以及复制步骤
提示( T)
- 放弃快速命令. 避免快速相继发送过多命令——去除物质或产品变化(如300毫秒).
- 反应容器. 将配置容器设置为响应维度( S)
width: 100%; height: 100%;),并随媒体查询而调整移动. - 活动在所有模式中都有效.
configuratorDataUpdated, (中文(简体) ),screenshotReady,以及其他事件 点燃了两德法尔