手把手接入应用壳
本教程适用于已有生成器的开发者,目标是把登录、积分、计费、导出这四个平台能力接入你现有的生成器,而不改动生成器的业务代码。
本教程使用 basic 能力档位,不涉及发布模板、云保存、历史记录。如果你之后需要升级,可以阅读 应用壳功能与配置参考。
接入前确认
在开始之前,先回答这两个问题:
| 问题 | 如果是 | 如果否 |
|---|---|---|
| 生成器需要发布模板(Publish as Template)? | 看 Template 档位说明 | 继续本教程 |
| 生成器需要云保存 / 历史记录? | 看 Cloud 档位说明 | 继续本教程 |
两个都是「否」,就是 basic 档位,本教程完全覆盖。
接入后你将获得
| 能力 | 说明 |
|---|---|
| 顶栏 + 登录 / 头像 / 退出 | 用户可在生成器内登录平台账号 |
| Credits 积分展示 | 顶栏自动显示用户当前积分余额 |
| Billing 计费闸门 | 导出前自动校验积分/免费次数,余额不足时弹充值弹窗 |
| Download 按钮 | 用户可下载生成器导出的文件(SVG / PNG / JPEG / WebP) |
| Open in Studio 按钮 | 用户可将导出内容直接发送到 Studio 编辑 |
以下能力不会出现:
- Publish as Template 按钮
- 云保存 / 历史记录
- 模板模式(Embed 模式) / iframe bridge
步骤一:安装依赖
根据项目类型选择其中一种方式。
方式 A — npm / pnpm(工程化项目)
pnpm add @atomm-developer/generator-sdk @atomm-developer/generator-workbench方式 B — CDN 引入(纯 HTML 项目)
在 index.html 的 <head> 中加入:
<!-- generator-sdk -->
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/index.umd.js"></script>
<!-- generator-workbench(应用壳) -->
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-workbench/index.umd.js"></script>CDN 方式仍需手动注册自定义元素
CDN 脚本加载后不会自动注册 <generator-workbench> 自定义元素,它只是把导出挂到 window.GeneratorWorkbench 上。你仍需在入口脚本里显式调用一次注册函数:
// CDN 方式下,从 window.GeneratorWorkbench 取注册函数
window.GeneratorWorkbench.defineGeneratorWorkbench()与 npm 方式的唯一区别是:npm 方式从包里 import { defineGeneratorWorkbench },CDN 方式从 window.GeneratorWorkbench 上取。调用本身两种方式都必须做,否则 customElements.whenDefined('generator-workbench') 将永远 pending,页面会挂起且无报错。
步骤二:在 HTML 中放置应用壳标签
在 index.html 的 <body> 里,添加 <generator-workbench> 元素。它会作为宿主壳层包裹你的生成器:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>我的生成器</title>
<style>
html, body { margin: 0; height: 100%; }
generator-workbench { height: 100vh; }
</style>
</head>
<body>
<generator-workbench id="workbench"></generator-workbench>
<script type="module" src="./main.js"></script>
</body>
</html>TIP
不需要手动在 body 里放你原来生成器的根节点。应用壳会在 mount() 时把你的生成器挂载到内部容器里。
步骤三:初始化 SDK 并注册导出能力
在入口脚本(main.js / main.ts)中:
import { GeneratorSDK } from '@atomm-developer/generator-sdk'
// 1. 初始化 SDK(env 会自动同步给应用壳)
const sdk = GeneratorSDK.init({
appKey: 'your_app_key', // 在开发者控制台注册获取
env: 'prod', // 'dev' | 'test' | 'pre' | 'prod' | 'prod_cn'
})
// 2. 注册导出能力 —— 导出按钮能点击的前提
// getExportCanvas 返回你生成器的画布元素(canvas 或包含 SVG 的容器)
sdk.export.register({
getExportCanvas(purpose) {
return document.getElementById('my-canvas')
}
})重要顺序
sdk.export.register() 必须在 workbench.mount() 之前调用。如果没有注册 export provider,用户点击导出按钮时不会触发任何动作。
步骤四:封装你的生成器,实现接入协议
这是接入应用壳唯一需要做的代码改动。你需要提供一个符合生成器接入协议的对象,让应用壳知道如何挂载和读取你的生成器状态。
basic 档位只需要 4 个方法:
const runtime = {
/**
* 应用壳调用此方法,把你的生成器挂载到 container 里。
* container 是应用壳内部创建的宿主容器,替代了你原来的 document.body 挂载点。
*/
mount({ container }) {
// 把你的生成器初始化/渲染到应用壳给的容器里
// 示例 1:如果你的生成器通过函数初始化
myGenerator.init(container)
// 示例 2:如果你的生成器已经渲染了某个 DOM 节点,移动它
// container.appendChild(document.getElementById('my-generator-root'))
return {
unmount() {
// 可选:卸载时的清理逻辑
container.innerHTML = ''
}
}
},
/**
* 返回生成器当前的业务状态(必须是可 JSON 序列化的对象)。
* 导出动作触发前,应用壳会调用 getState() 传给 sdk.export。
*/
getState() {
return myGenerator.getState()
// 如果你的生成器没有统一状态管理,可以手动收集:
// return { params: { color: '#ff0000', size: 'large' } }
},
/**
* 接收外部传入的状态并恢复。
* basic 档位下通常不会被调用,但接口必须存在。
*/
setState(nextState) {
myGenerator.setState(nextState)
},
/**
* 返回参数面板的 schema。
* basic 档位(shell 模式)不使用右侧参数面板,返回空结构即可。
*/
getPanelSchema() {
return {
version: '1.0.0',
generatorId: 'your-generator-id',
groups: []
}
}
}你不需要修改生成器业务代码
mount() 里只是告诉应用壳"如何启动你的生成器"。你的画布渲染、参数处理、交互逻辑保持不变,应用壳不会干预这些内容。
步骤五:配置并启动应用壳
const workbench = document.getElementById('workbench')
// 必须按顺序:先赋值 sdk / 生成器接入对象 / config,最后再调用 mount()
workbench.sdk = sdk
workbench.runtime = runtime
workbench.config = {
title: '我的生成器', // 顶栏显示的标题
mode: 'shell', // shell 模式:顶栏 + 右下角浮动导出按钮,workspace 完全由生成器控制
// ✅ 需要的能力
exportEnabled: true, // 显示 Download 按钮
studioEnabled: true, // 显示 Open in Studio 按钮
// ❌ basic 档位:明确关闭以下能力
templateEnabled: false, // 不显示 Publish as Template 按钮
cloudEnabled: false, // 不接入云保存
historyEnabled: false, // 不接入历史记录
autoSaveEnabled: false, // 不启用自动保存
onError(error, source) {
console.error('[应用壳]', source, error)
}
}
await workbench.mount()shell 模式 vs full 模式
shell模式(推荐):应用壳只提供顶栏和右下角浮动导出按钮,你的生成器自己控制整个内容区域的布局。full模式:应用壳渲染经典的右侧参数面板分栏布局,生成器只填充 canvas 区和 panel 区。
如果你的生成器已经有自己的布局,使用 shell 模式不会破坏原有结构。
步骤六:验证接入是否正确
在 URL 末尾加 ?debug=true,浏览器页面上会自动弹出集成自检面板,无需打开 DevTools:
https://your-generator.pages.dev/?debug=true面板会逐条显示检查结果:
┌─────────────────────────────────────────────────────┐
│ 应用壳接入自检 [关闭 ×] │
├─────────────────────────────────────────────────────┤
│ ✅ workbench.sdk 已绑定到元素 │
│ ✅ workbench.runtime 已绑定到元素 │
│ ✅ atommProEnv 已配置: "prod" │
│ ✅ mount() 在所有赋值之后调用 │
│ ✅ 生成器接入协议接口正确 │
│ ✅ window.__GENERATOR_RUNTIME__ 已暴露 │
└─────────────────────────────────────────────────────┘basic 档位重点检查这几条:
| 检查项 | 预期状态 | 如果失败 |
|---|---|---|
workbench.sdk 已绑定 | ✅ | 确认 workbench.sdk = sdk 在 mount() 前执行 |
workbench.runtime 已绑定 | ✅ | 确认 workbench.runtime = runtime 在 mount() 前执行 |
atommProEnv 已配置 | ✅ | env 自动从 SDK 派生,确认 SDK init 时传入了 env |
mount() 在赋值之后调用 | ✅ | 调整代码顺序,mount() 必须是最后一步 |
| 生成器接入协议接口正确 | ✅ | 检查接入对象是否有 mount / getState / setState / getPanelSchema |
完整代码示例
以下是一个最小化的 basic 档位接入完整示例(ESM 版本):
// main.js
import { GeneratorSDK } from '@atomm-developer/generator-sdk'
import { defineGeneratorWorkbench } from '@atomm-developer/generator-workbench'
// 注册 Web Component(CDN 方式改为 window.GeneratorWorkbench.defineGeneratorWorkbench())
defineGeneratorWorkbench()
// 步骤一:初始化 SDK
const sdk = GeneratorSDK.init({
appKey: 'your_app_key',
env: 'prod',
})
// 步骤二:注册导出能力
sdk.export.register({
getExportCanvas(purpose) {
return document.getElementById('my-canvas')
}
})
// 步骤三:封装你的生成器,实现接入协议
const runtime = {
mount({ container }) {
myGenerator.init(container)
return {
unmount() { container.innerHTML = '' }
}
},
getState() {
return myGenerator.getState()
},
setState(nextState) {
myGenerator.setState(nextState)
},
getPanelSchema() {
return { version: '1.0.0', generatorId: 'my-generator', groups: [] }
}
}
// 步骤四:配置应用壳
const workbench = document.getElementById('workbench')
workbench.sdk = sdk
workbench.runtime = runtime
workbench.config = {
title: '我的生成器',
mode: 'shell',
exportEnabled: true,
studioEnabled: true,
templateEnabled: false,
cloudEnabled: false,
historyEnabled: false,
autoSaveEnabled: false,
onError(error, source) {
console.error('[应用壳]', source, error)
}
}
// 步骤五:启动
await workbench.mount()对应的 index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>我的生成器</title>
<style>
html, body { margin: 0; height: 100%; }
generator-workbench { height: 100vh; }
</style>
</head>
<body>
<generator-workbench id="workbench"></generator-workbench>
<script type="module" src="./main.js"></script>
</body>
</html>CDN 版本(纯 HTML,不使用打包器)
如果使用 步骤一 → 方式 B(CDN 引入),把 import 改成从 window 取对应命名空间即可:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>我的生成器</title>
<style>
html, body { margin: 0; height: 100%; }
generator-workbench { height: 100vh; }
</style>
</head>
<body>
<generator-workbench id="workbench"></generator-workbench>
<!-- 1. 先加载 SDK -->
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/index.umd.js"></script>
<!-- 2. 再加载 Workbench(仅挂载到 window.GeneratorWorkbench,不会自动注册自定义元素) -->
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-workbench/index.umd.js"></script>
<script type="module">
// window.GeneratorSDK 由第一个 CDN script 提供
const sdk = window.GeneratorSDK.GeneratorSDK.init({
appKey: 'your_app_key',
env: 'prod',
})
sdk.export.register({
getExportCanvas: () => document.getElementById('my-canvas'),
})
// ⚠️ 必须调用!CDN 不会自动注册,只是暴露这个函数到 window.GeneratorWorkbench
window.GeneratorWorkbench.defineGeneratorWorkbench()
const runtime = { /* 同上:mount / getState / setState / getPanelSchema */ }
const workbench = document.getElementById('workbench')
workbench.sdk = sdk
workbench.runtime = runtime
workbench.config = {
title: '我的生成器',
mode: 'shell',
exportEnabled: true,
studioEnabled: true,
templateEnabled: false,
cloudEnabled: false,
historyEnabled: false,
autoSaveEnabled: false,
}
await workbench.mount()
</script>
</body>
</html>常见问题排查
导出按钮点击没有任何反应
原因 1:sdk.export.register() 没有在 mount() 前调用。
// ✅ 正确顺序
sdk.export.register({ getExportCanvas: () => document.getElementById('canvas') })
workbench.sdk = sdk
// ...
await workbench.mount()
// ❌ 错误:在 mount 之后才注册
await workbench.mount()
sdk.export.register({ ... })原因 2:exportEnabled 没有设为 true,导出按钮被隐藏。
点头像 / 登录按钮没有弹窗
原因:SDK 的 env 未正确设置,或 workbench.sdk 未在 mount() 前赋值。
打开 ?debug=true 查看自检面板,找到失败的检查项按提示修复。
mount() 之后生成器显示空白
原因:mount() 里没有正确把生成器挂载到应用壳给的 container 里。
检查 mount() 里的代码,确认使用的是应用壳传入的 container 参数,而不是直接操作 document.body:
// ✅ 正确:挂载到应用壳给的 container
mount({ container }) {
myGenerator.init(container)
}
// ❌ 错误:挂载到 document.body,不在应用壳管理范围内
mount({ container }) {
myGenerator.init(document.body)
}顶栏不显示 / 样式异常
原因:<generator-workbench> 没有设置高度。
/* 确保应用壳撑满视口 */
generator-workbench { height: 100vh; }CDN 接入后页面持续空白且没有任何报错
原因:CDN 脚本加载后忘记调用 defineGeneratorWorkbench()。
CDN 包不会自动注册 <generator-workbench> 自定义元素。<script> 标签加载完只是把导出挂到 window.GeneratorWorkbench 上,必须显式调用一次注册函数。否则 customElements.whenDefined('generator-workbench') 会返回永远 pending 的 Promise,页面挂起且 catch 不会触发。
// ✅ 正确:CDN 方式仍需手动注册
window.GeneratorWorkbench.defineGeneratorWorkbench()可在浏览器控制台快速排查:
// 1. CDN 是否加载成功
typeof window.GeneratorWorkbench // 应为 'object'
// 2. 自定义元素是否已注册
customElements.get('generator-workbench') // 未调用时为 undefined
// 3. 手动触发注册
window.GeneratorWorkbench.defineGeneratorWorkbench()
customElements.get('generator-workbench') // 现在应输出类引用接入完成后的下一步
如果将来需要增加能力,不需要重新接入,只需修改 config 中的开关:
| 想增加的能力 | 修改方式 | 需要额外实现 |
|---|---|---|
| 云保存 / 历史记录 | cloudEnabled: true, historyEnabled: true | 实现 getCloudSaveOptions |
| 自动保存 | autoSaveEnabled: true | 无(依赖云保存开启) |
| 发布模板 | templateEnabled: true | 实现模板序列化逻辑,参考 Template 档位 |