# realibox-ui-sdk API 参考

本页说明面向宿主应用开放的 `product-3d-viewer`、Graphics2D 和场景快照接口。组件在授权初始化完成后使用；所有 Viewer 方法均为异步方法，应等待 `viewer-loaded` 或直接 `await` 方法返回。

```ts
import { initSDK, type Product3dViewer } from "realibox-ui-sdk";

initSDK({ key: "YOUR_KEY", mode: "production" });

const viewer = document.querySelector<Product3dViewer>("product-3d-viewer");
if (!viewer) throw new Error("Viewer not found");
await viewer.exportSceneSnapshot();
```

React 和 Vue 使用 `projectId` 属性；原生 HTML 使用 `project-id`。Vue 还需要引入 `realibox-ui-sdk/vue` 并配置 `isCustomElement`，完整框架接入请见项目根目录 [README](../../README.md)。

## 导入和类型

主入口导出以下 Graphics2D 类型：

```ts
import type {
  Graphics2DClient,
  Graphics2DEditorOptions,
  Graphics2DEditorState,
  Graphics2DElement,
  Graphics2DTarget,
  Graphics2DErrorDetail,
  SceneSnapshot,
  SceneSnapshotBundle,
  SceneSnapshotApplyOptions,
} from "realibox-ui-sdk";
```

也可从 `realibox-ui-sdk/components/product-3d-viewer` 按需导入同一组类型。协议版本常量为 `GRAPHICS_2D_PROTOCOL_VERSION`，当前值为 `1`。

## 语言与静态文案

`SiteConfig.messages` 接受旧版字符串或按语言的对象。自定义文案先匹配精确 locale，再匹配基础语言，最后回退 `zh-CN`；例如 `en-US → en → zh-CN`。语言 key 不区分大小写。

```ts
initSDK({
  key: "YOUR_KEY",
  mode: "production",
  locale: "en-US",
  siteConfig: {
    messages: {
      "configurator.graphicsDefaultText": {
        "zh-CN": "新文字",
        en: "New text",
      },
      // 兼容旧格式：所有语言显示相同文案。
      "detail.params": "TECHNICAL DETAILS",
    },
  },
});
```

语言优先级为：组件 `site-config.locale` > `initSDK({ locale })` > `initSDK({ siteConfig: { locale } })` > 浏览器语言 > `zh-CN`。`setMessages`、`createTranslator`、`getMissingTranslations` 也由主入口导出，适用于宿主自行渲染的静态 UI。

## `product-3d-viewer`

### 常用配置方法

| 方法 | 说明 |
| --- | --- |
| `getConfiguratorState()` | 获取当前配置器完整状态。 |
| `getObjectConfiguratorState(objectId)` | 获取单个部件状态。 |
| `selectObject(objectId)` | 选中部件。 |
| `setObjectVisible(objectId, visible)` | 切换部件显示状态。 |
| `selectMaterial(materialId)` | 应用材质。 |
| `selectSurfaceProcess(processId)` | 应用表面工艺。 |
| `setColor(payload)` | 应用颜色数据。 |
| `getGradientState()` / `getGradientCraftState()` | 读取渐变及渐变工艺状态。 |
| `setGradientColor(payload)` / `setGradientCraftValue(payload)` | 更新渐变配置。 |
| `resetConfig()` | 恢复项目首次加载完成时的基线状态。不会读取浏览器保存的场景。 |

`resetConfig()` 会复原部件、材质、颜色、表面工艺和 Graphics2D 内容。它与 `loadScene*` 完全独立：保存的场景只在宿主明确调用加载方法时才会生效。

### 场景快照

| 方法 | 返回 | 用途 |
| --- | --- | --- |
| `exportSceneSnapshot()` | `Promise<SceneSnapshot>` | 导出可序列化的配置状态；本地 Blob 图片不在普通快照中保存。 |
| `exportSceneJSON(space?)` | `Promise<string>` | 导出 JSON，`space` 会被限制在 `0–10`。 |
| `loadSceneSnapshot(snapshot, options?)` | `Promise<SceneSnapshotApplyResult>` | 应用对象状态和 Graphics2D 状态。 |
| `loadSceneJSON(json, options?)` | `Promise<SceneSnapshotApplyResult>` | 解析并应用 JSON 快照。 |
| `exportSceneBundle()` | `Promise<SceneSnapshotBundle>` | 同时返回 JSON 快照和本地图片 Blob 资源。 |
| `loadSceneBundle(bundle, options?)` | `Promise<SceneSnapshotApplyResult>` | 校验并注入资源包中的 Blob 后应用快照。 |

`SceneSnapshotApplyOptions`：

```ts
await viewer.loadSceneSnapshot(snapshot, {
  allowProjectMismatch: false, // 默认 false
  missingObject: "error", // 或 "skip"
  missingOption: "error", // 或 "skip"
  missingGraphicsTarget: "error", // 或 "skip"
});
```

普通 JSON 快照只能安全地携带可重新访问的 URL。需要跨页面保留用户本地图片时，使用资源包：场景清单可存入 `sessionStorage` 或服务端，`assets[].blob` 请存入 IndexedDB 等二进制存储。SDK 不会把 Blob 自动序列化进 `sessionStorage`。

```ts
const bundle = await viewer.exportSceneBundle();
sessionStorage.setItem("scene-manifest", JSON.stringify(bundle.snapshot));
await saveAssetsToIndexedDB(bundle.assets);

const snapshot = JSON.parse(sessionStorage.getItem("scene-manifest") ?? "{}");
const assets = await loadAssetsFromIndexedDB();
await viewer.loadSceneBundle({ snapshot, assets });
```

资源包会拒绝空或重复 asset ID、缺失引用、无效编码引用和非 `image/*` Blob；这些错误发生在场景修改前。`asset://graphics2d/...` 是 SDK 资源包内部引用，不应由业务手动拼接。

## Graphics2D

在 `viewer-loaded` 后从 `viewer.graphics2d` 访问底层协议客户端。它会等待 Viewer 就绪，并自动使用协议版本 1。

```ts
const capabilities = await viewer.graphics2d.getCapabilities();
if (!capabilities.blobInput) throw new Error("Viewer does not accept local image Blobs");

const targets = await viewer.graphics2d.listTargets();
const target = targets.find((item) => item.editable);
if (!target) return;

const { sessionId } = await viewer.graphics2d.beginSession(target.id);
try {
  const elements = await viewer.graphics2d.addImage({
    sessionId,
    src: file, // File 是 Blob，可直接传入；SDK 不上传或预压缩它
  });
  await viewer.graphics2d.commit(sessionId);
} catch (error) {
  await viewer.graphics2d.rollback(sessionId);
  throw error;
}
```

每个 target 同一时刻只允许一个会话。底层客户端使用时，宿主必须在离开编辑流程时 `commit(sessionId)` 或 `rollback(sessionId)`；不要同时让多个 UI 对同一 target 调用 `beginSession`。

### 底层命令

| 方法 | 说明 |
| --- | --- |
| `getCapabilities()` | 读取协议、Blob、变换和表面工艺能力。先检查 `protocolVersion`。 |
| `listTargets()` | 返回可编辑的 2D 区域和映射状态。 |
| `beginSession(targetId)` | 为一个 target 开启事务会话。 |
| `listElements({ sessionId?, targetId? })` | 列出该会话或 target 的图文元素。 |
| `addImage(input)` / `addText(input)` | 添加图片或文字。图片 `src` 为 URL 或 `Blob`。 |
| `updateElement(input)` / `removeElement(sessionId, elementId)` | 更新或删除元素。 |
| `listFonts()` | 读取 Viewer 当前可用字体及字重；SDK 不上传字体。 |
| `listSurfaceFinishes()` | 读取预设与有权限的库工艺。 |
| `applySurfaceFinish(...)` / `clearSurfaceFinish(...)` | 设置或移除元素工艺。 |
| `commit(sessionId)` / `rollback(sessionId)` | 提交或撤销当前会话。 |
| `exportState()` / `importState(state, options?)` | 导出、导入 Graphics2D 局部状态。通常优先使用场景快照 API。 |
| `setSelectionMode(enabled)` / `getSelectionMode()` | 控制 Viewer 内的 2D target 点选模式。 |

图片和文字位置使用归一化坐标。图片的推荐尺寸字段是 `sizeScale`：`1` 表示该图片按原始比例 contain 到当前贴图区域；`sizeScaleRange` 告诉 UI 可用范围和可选 cover 阈值。不要根据屏幕像素换算，或缓存一次编辑会话的旧 `scale`；`sizeScale` 在替换图片、重置和快照恢复后稳定有效。

拖动时可在 `updateElement` 传 `preview: true`，指针释放时必须再传一次 `preview: false`（或省略）。前者使用低成本预览纹理，后者触发 Viewer 的自适应最终纹理；该过程不会改变逻辑位置或 `sizeScale`。

### 推荐：`Graphics2DEditorController`

对于常见面板，优先创建高层编辑器。它负责 target 选择、串行命令、拖动合并、预览/最终渲染切换和会话关闭，避免 `already has an active session` 错误。

```ts
const editor = viewer.graphics2d.createEditor({
  transform: {
    minScale: 0.1,
    maxScale: 2,
    previewMode: "auto", // "auto" | "realtime" | "release"
  },
  text: { defaultContent: "Brand name" }, // 可选；未传时取当前语言配置
  closeAction: "rollback", // 默认 rollback
});

const stop = editor.subscribe((state) => renderGraphicsPanel(state));
await editor.open();
await editor.addImage(file);
await editor.addText();
await editor.updateTransform({ scale: 1.2 }, { final: false });
await editor.updateTransform({ scale: 1.2 }, { final: true });
await editor.applyFinish("gold");
await editor.commit();
stop();
editor.dispose();
```

主要方法：`refresh`、`open`、`selectTarget`、`selectElement`、`addImage`、`replaceImage`、`loadFonts`、`addText`、`updateSelectedText`、`removeSelectedElement`、`updateTransform`、`rotate`、`toggleFlip`、`loadFinishes`、`applyFinish`、`rollback`、`reset`、`commit`、`close`、`dispose`。`addText()` 未传内容时使用 `configurator.graphicsDefaultText` 的当前语言值，默认是“新文字”。

`Graphics2DEditorError` 提供 `code`、`stage`（如 `image`、`transform`、`finish`）和 `recoverable`，适合在 UI 中显示可恢复错误。

## 事件与错误

所有组件事件均可冒泡并穿过 Shadow DOM。`Product3dViewerEventDetailMap` 提供 TypeScript 事件 detail 类型。

| 事件 | detail | 说明 |
| --- | --- | --- |
| `viewer-frame-load` | 无 | iframe 已完成浏览器加载。 |
| `viewer-ready` | Viewer 事件数据 | Viewer 可以开始接收命令。 |
| `viewer-loaded` | 初始配置状态 | 项目和初始配置已完成加载。 |
| `viewer-error` | `{ error }` | Viewer 未就绪或加载失败。 |
| `config-change` | 配置状态增量 | 配置发生变化。 |
| `graphics2d.changed` | `Graphics2DChangedDetail` | 某个 Graphics2D 会话状态更新。 |
| `graphics2d.session-ended` | `Graphics2DSessionEndedDetail` | 会话关闭，含 `committed`。 |
| `graphics2d.error` | `Graphics2DErrorDetail` | Viewer Graphics2D 命令错误。 |
| `graphics2d.target-selected` | `Graphics2DTargetSelectedDetail` | 用户在 Viewer 里选中了 2D 区域。 |
| `debug-log` | 调试记录 | `debug` 属性开启时输出通信轨迹。 |

底层 Graphics2D 命令错误会抛出 `Graphics2DCommandError`，包含 `code`、`message` 和可选 `details`。通信超时会在控制台记录 Viewer URL、目标 origin、父页面 origin 和协议版本；优先检查 `pvParentOrigin`、Viewer 的 Embed SDK 模式和 `getCapabilities()` 是否可响应。

## 兼容性和发布边界

- 当前 Graphics2D 协议版本为 `1`。宿主应以 `getCapabilities().protocolVersion` 为准，并在不支持时隐藏编辑入口。
- `Blob` 输入依赖 Viewer `blobInput: true`。普通 URL、`File` 与 `Blob` 都可作为图片源；ui-sdk 不执行上传。
- `loadSceneSnapshot`、`loadSceneBundle` 在结构验证阶段失败时不会应用场景。底层引擎应用失败时，Viewer 会尝试恢复应用前状态，并在错误 details 中标记回滚结果。
- 构建产物包含主入口、`/react`、`/vue`、`/init` 与组件按需入口；Vue 类型只从 `realibox-ui-sdk/vue` 导入。
