Skip to content

手把手接入应用壳

本教程适用于已有生成器的开发者,目标是把登录、积分、计费、导出这四个平台能力接入你现有的生成器,而不改动生成器的业务代码。

本教程使用 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(工程化项目)

bash
pnpm add @atomm-developer/generator-sdk @atomm-developer/generator-workbench

方式 B — CDN 引入(纯 HTML 项目)

index.html<head> 中加入:

html
<!-- 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 上。你仍需在入口脚本里显式调用一次注册函数:

js
// CDN 方式下,从 window.GeneratorWorkbench 取注册函数
window.GeneratorWorkbench.defineGeneratorWorkbench()

与 npm 方式的唯一区别是:npm 方式从包里 import { defineGeneratorWorkbench },CDN 方式从 window.GeneratorWorkbench 上取。调用本身两种方式都必须做,否则 customElements.whenDefined('generator-workbench') 将永远 pending,页面会挂起且无报错。


步骤二:在 HTML 中放置应用壳标签

index.html<body> 里,添加 <generator-workbench> 元素。它会作为宿主壳层包裹你的生成器:

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>

TIP

不需要手动在 body 里放你原来生成器的根节点。应用壳会在 mount() 时把你的生成器挂载到内部容器里。


步骤三:初始化 SDK 并注册导出能力

在入口脚本(main.js / main.ts)中:

js
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 个方法:

js
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() 里只是告诉应用壳"如何启动你的生成器"。你的画布渲染、参数处理、交互逻辑保持不变,应用壳不会干预这些内容。


步骤五:配置并启动应用壳

js
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 = sdkmount() 前执行
workbench.runtime 已绑定确认 workbench.runtime = runtimemount() 前执行
atommProEnv 已配置env 自动从 SDK 派生,确认 SDK init 时传入了 env
mount() 在赋值之后调用调整代码顺序,mount() 必须是最后一步
生成器接入协议接口正确检查接入对象是否有 mount / getState / setState / getPanelSchema

完整代码示例

以下是一个最小化的 basic 档位接入完整示例(ESM 版本):

js
// 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

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 取对应命名空间即可:

html
<!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>

常见问题排查

导出按钮点击没有任何反应

原因 1sdk.export.register() 没有在 mount() 前调用。

js
// ✅ 正确顺序
sdk.export.register({ getExportCanvas: () => document.getElementById('canvas') })
workbench.sdk = sdk
// ...
await workbench.mount()

// ❌ 错误:在 mount 之后才注册
await workbench.mount()
sdk.export.register({ ... })

原因 2exportEnabled 没有设为 true,导出按钮被隐藏。

点头像 / 登录按钮没有弹窗

原因:SDK 的 env 未正确设置,或 workbench.sdk 未在 mount() 前赋值。

打开 ?debug=true 查看自检面板,找到失败的检查项按提示修复。

mount() 之后生成器显示空白

原因mount() 里没有正确把生成器挂载到应用壳给的 container 里。

检查 mount() 里的代码,确认使用的是应用壳传入的 container 参数,而不是直接操作 document.body

js
// ✅ 正确:挂载到应用壳给的 container
mount({ container }) {
  myGenerator.init(container)
}

// ❌ 错误:挂载到 document.body,不在应用壳管理范围内
mount({ container }) {
  myGenerator.init(document.body)
}

顶栏不显示 / 样式异常

原因<generator-workbench> 没有设置高度。

css
/* 确保应用壳撑满视口 */
generator-workbench { height: 100vh; }

CDN 接入后页面持续空白且没有任何报错

原因:CDN 脚本加载后忘记调用 defineGeneratorWorkbench()

CDN 包不会自动注册 <generator-workbench> 自定义元素。<script> 标签加载完只是把导出挂到 window.GeneratorWorkbench 上,必须显式调用一次注册函数。否则 customElements.whenDefined('generator-workbench') 会返回永远 pending 的 Promise,页面挂起且 catch 不会触发。

js
// ✅ 正确:CDN 方式仍需手动注册
window.GeneratorWorkbench.defineGeneratorWorkbench()

可在浏览器控制台快速排查:

js
// 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 档位

相关文档

MIT Licensed