Skip to content

耗材购买 SDK 接入指南

@atomm-developer/generator-material-purchase · 当前版本 0.2.0

框架无关的耗材购买弹窗:拉取当前生成器对应的耗材清单,用户勾选、选规格后一键生成 Shopify 结算链接。Vue / React / 原生 JS 均可通过 <script src>import 接入。


目录


一分钟了解

它做什么init() 一次、open() 一下,SDK 在页面上弹出一个耗材清单。清单从后端按生成器自动匹配(也可以由你指定),用户勾选商品、切换规格、调数量,点「立即购买」后 SDK 创建 Shopify 购物车并把结算页交给你(默认新开标签)。站点(US / EU / JP …)按 IP 自动选择,缺货商品可订阅到货通知,全程带埋点事件。

三种弹窗形态open() 时决定,同一个实例可以按调用点切换):

形态怎么触发长什么样适合
侧抽屉 draweropen(),且没配 entry右侧滑入,PC 320px、移动端全屏,带蒙层页面上已有自己的「购买耗材」按钮
卡片 + 常驻入口 cardinit({ entry }) 后点击 SDK 渲染的入口按钮页面角落常驻一个「Get the Materials」红色胶囊,点开是锚定在它旁边的 330px 卡片想要一个免维护的常驻购买入口
居中弹窗 modalopen({ variant: 'modal' })480×480 居中,「文件下载成功」+ 积分横幅 + 紧凑商品行导出 / 下载成功后顺势推荐耗材

两种数据源init() 时决定):

mode商品从哪来你需要提供
default(默认)后端按生成器自动匹配:CMS 配置的耗材包 → 用户常用机型适配的包 → 运营配置的全局兜底包只要 generatorId
material你自己指定的商品 id 列表generatorId + materialIds

我该怎么选

你的场景推荐
生成器跑在 generator-workbench 壳子里,只想要「导出后推荐耗材」什么都不用接,壳子已内置且默认开启,见 跑在 generator-workbench 里
页面上有自己的购买按钮default 模式 + 抽屉,见 最小接入
想要页面角落常驻一个购买入口default 模式 + entry 卡片,见 场景 A
自己管导出流程,想在导出成功后弹推荐open({ variant: 'modal' }),见 场景 B
商品清单由业务侧决定,不走生成器配置mode: 'material',见 场景 C

安装

通过 CDN(UMD)

html
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>

加载后 window.GeneratorMaterialPurchase 即为 SDK 对象。适合非 Vue / React 工程,或者不想引入构建依赖的页面。

通过 npm(ESM)

bash
# 内部 registry,需先配置 .npmrc → http://repository.makeblock.com/repository/npm-group/
pnpm add @atomm-developer/generator-material-purchase
ts
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'
// 或具名导入
import { GeneratorMaterialPurchase } from '@atomm-developer/generator-material-purchase'

包内同时提供 ESM(index.es.js)与 UMD(index.umd.js),以及合并后的类型声明 index.d.ts

跑在 generator-workbench 里(免接入)

如果生成器运行在 generator-workbench 应用壳里,「导出后弹耗材」这条链路已经内置且默认开启:SVG 下载成功或 Open in Studio 成功后,壳子会按 sdk.getAppKey() 拉取耗材并弹出 modal 形态,每个壳子实例一个会话只弹一次;说明书 PDF 不弹。

  • 不想要:materialPurchaseEnabled: false
  • 联调未发布的 SDK 产物:materialPurchaseScriptUrl 指到自己的 UMD 地址
  • 壳子的 atommProEnv 会自动映射为 SDK 的 envatommProLocale: 'zh' 对应弹窗 zh,其余回退 en

完整说明见 应用壳功能与配置参考 · 导出后的耗材购买弹窗。这种情况下你不需要读本文剩余部分,除非想在壳子之外再接一个常驻入口。


快速开始

最小接入

ts
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'

GeneratorMaterialPurchase.init({ generatorId: 'light_sign' })
GeneratorMaterialPurchase.preload() // 可选:提前拉数据,open 时无 loading

document.querySelector('#buyBtn')!.addEventListener('click', () => {
  GeneratorMaterialPurchase.open()
})

三件事:init 传生成器编码、preload 预热、open 打开。不传 env 默认 prod,不传 locale 默认 en,商品列表由后端按生成器自动匹配。

场景 A:常驻入口 + 卡片

ts
GeneratorMaterialPurchase.init({
  generatorId: 'light_sign',
  entry: {
    style: { top: '16px', right: '16px' }, // 只接受定位属性
    onClick: () => {
      // 可选:返回 false 阻止打开,例如先要求登录
    },
  },
})
GeneratorMaterialPurchase.preload()
// 之后不需要再调 open():用户点击入口按钮即打开卡片,再点一次收起

入口按钮由 SDK 渲染并常驻页面,卡片锚定在按钮位置展开,两者以 morph 过渡衔接,任何一帧不会同时可见。点击卡片外部会收起,页面上有些元素点击时不想触发收起(比如切模板的卡片),用 outsideClickIgnoreSelectors 声明。

场景 B:导出成功后推荐(modal)

ts
GeneratorMaterialPurchase.init({ generatorId: 'light_sign', locale: 'zh' })
GeneratorMaterialPurchase.preload() // 导出前就预热,弹出时无 loading

async function onExportSuccess() {
  await GeneratorMaterialPurchase.open({ variant: 'modal' })
}

variantopen() 的参数,所以即使同一个实例配了 entry,也可以在导出成功时按需弹这一种,两者共用同一份商品缓存。

场景 C:自定义耗材清单(material 模式)

ts
GeneratorMaterialPurchase.init({
  generatorId: 'light_sign', // 结算 / 到货通知仍用到,必填
  mode: 'material',
  materialIds: [889, 890, 891], // 统一商品卡的 supplySkuId
})
GeneratorMaterialPurchase.open({
  selectIds: [889], // 默认勾选并置顶
  quantities: { 889: 2 },
})

商品完全由 materialIds 决定,不再走生成器的耗材包配置。运行中切换清单用 update({ materialIds }),不会重挂入口、弹窗开着会原地刷新。


核心概念

形态:variant

形态在 open() 时决定:

ts
open()                       // 配了 entry → card,否则 drawer
open({ variant: 'modal' })   // 居中弹窗,与 entry 无关
open({ variant: 'drawer' })  // 强制抽屉,即使配了 entry

三种形态的数据源、规格切换、到货通知、结算逻辑完全一致,只换布局:

侧抽屉 drawer卡片 card居中弹窗 modal
尺寸PC 320px 宽、满屏高;≤ 767px 全屏330px 宽,高度随内容,上限视口 − 32px480 × 480 定高,圆角 12
蒙层有,点击关闭有,点击不关闭(底部已有「返回编辑」)
站点 / 国家切换条显示显示不显示(一次性推荐不承担改配送地)
数量控件可购即显示可购即显示「可购 已勾选」才显示
底部操作栏合计 + 购买合计 + 购买两态:未勾选只有描边的「返回编辑」;勾选后出现合计 + 购买
顶部标题「Materials List」+ 副标题绿色对勾 + 「文件下载成功」+ 积分横幅(Learn more 跳创作者计划页)
Materials Lab 入口列表底部显示不显示列表底部显示,内容不满一屏时贴底
关闭方式蒙层 / 右上角 × / close()点击卡片外部 / 右上角收起 / 再点入口 / close()「返回编辑」/ 右上角 × / close()
移动端全屏适配固定 330px固定 480px,未做小屏适配

只有卡片形态受 entry 影响:open() 不传 variant 且存在 entry 时才是卡片;类型上 variant 只接受 'drawer' | 'modal',卡片无法显式指定。

数据源:mode 与三级兵线

default 模式:后端按生成器匹配,逐级兜底,取到商品即停:

  1. 生成器配置的耗材包:开发者平台里该生成器(generatorId)绑定的耗材包,全部拼接
  2. 机型适配包:没配且用户已登录时,取用户常用机型(最多前两台)适配的耗材包,包内商品全量展示。未登录跳过这一级
  3. 全局兜底包:运营在效能平台配置的兜底耗材包;也没配就是空列表

因此即使生成器没配耗材,也可能展示兜底耗材;反过来,列表为空只说明三级都没配到,不是接入错误。

多包合并规则:按配置顺序拼接、同一商品跨包只保留第一次出现、缺货商品沉到列表底部;单个包拉不到(已删除 / 已下架)只是不贡献商品,不影响其他包。

material 模式:列表完全由 materialIds 决定,GET /community/v1/web/product/list?ids=…&store=…,不做去重与缺货沉底。generatorId 仍用于结算分销与到货通知,必填。

两种模式返回的是同一张「统一商品卡」,字段口径完全一致。

耗材 id 口径

open()selectIds / hideIds / quantitiesinit()materialIds,匹配的都是统一商品卡的 supplySkuId(商品自增 id),两种 mode 一致。

0.2.0 之前这里用的是 accessory-pack 接口的 accessoryId,升级后值要换,参数形状不变。老值传进去不会报错,只是匹配不上、静默失效。详见 升级指引

自定义渲染或读取 ProductDataType 时,item.accessoryId 字段仍然存在,SDK 已把它映射为 supplySkuId,直接用它做匹配即可。

缓存与生命周期

SDK 是单例,内部维护一份商品缓存,命中时 open() 直接渲染、无 loading。

操作缓存入口挂件弹窗(若开着)
init() 再调一次generatorId / mode / materialIds 变化时作废卸掉重挂(会闪一下)不受影响
update(patch)上述三个字段变化时作废;env / apiBaseUrl / platformBaseUrl / materialsLabUrl 变化时作废并重置站点只在 entry.style 真变了才重挂数据源变了会原地静默刷新,保留旧列表直到新数据到达
refresh()无条件作废并重拉不动现场刷新(短 loading)
preload()缓存为空或已失效时拉取填充不动不涉及
用户切换站点重拉并覆盖不动刷新
登录态变化下次取用时自动作废(游客与登录用户的兵线不同)不动不涉及
destroy()清空卸载卸载

推荐节奏:init() 之后立刻 preload()(不必 await,失败只走 onError),用户触发时 open() 即秒开。运行中切换清单或环境用 update(),只在想要「配置不变但强制拉最新库存 / 价格」时用 refresh()

open() 可以反复调用,每次都会重置 UI 状态(勾选、规格、滚动位置),但复用 init 配置与商品缓存;selectIds / hideIds / quantities 只对本次 open 生效。同一时刻只能有一个弹窗。

登录态影响什么

SDK 不维护登录状态,只在请求头里带上宿主页面已有的 uToken(见 鉴权与环境)。

功能未登录已登录
浏览商品、切换规格、结算可用(Shopify 结算是游客购物车)可用
default 模式的机型适配包(第二级)跳过参与
到货通知订阅不发请求,触发 onRequireLogin(未配置则 toast 提示)可用

站点解析

SDK 内置 8 个正式 Shopify 站点(US / CA / EU / UK / FR / DE / JP / AU)。首次打开时按以下顺序确定站点:localStorage.xtool_current_shop_name → IP 定位接口 → 兜底 US。用户可在抽屉 / 卡片顶部手动切换,切换后重拉商品(价格、库存、上架状态都按站点口径)。

envdev / test / test_us 时自动切到 testxtool 单一测试站点,不会碰正式店。


API 参考

方法总表

ts
interface GeneratorMaterialPurchaseApi {
  init(options: PurchaseModalInitOptions): void
  update(patch: Partial<PurchaseModalInitOptions>): void
  refresh(): Promise<void>
  preload(): Promise<void>
  open(options?: OpenModalOptions): Promise<void>
  close(): void
  destroy(): void
}
方法说明
init(options)必须最先调用。缺 generatorId,或 mode: 'material' 却没给非空 materialIds,会直接抛错。可重复调用,语义是「用新配置整体覆盖」,entry 会卸掉重挂。首次调用会在控制台打印一次 SDK 版本号
update(patch)局部合并配置。entry.style 没变就不重挂入口;数据源字段变了会作废缓存,弹窗开着则原地刷新;env 等环境字段变了会重置站点。函数字段(onClick / 回调)不做比较,替换请重新 init
refresh()无条件作废缓存并按当前配置重拉。弹窗开着现场刷新;没开则只清缓存,下次 open() 拉新
preload()预热:解析站点 + 拉商品填缓存。best-effort,网络失败只走 onError 不 reject;但没 init 就调会抛错
open(options?)打开弹窗。未预热时先显示 loading。返回的 Promise 在列表加载完成后 resolve,加载失败会 toast 并回调 onError,Promise 仍 resolve
close()播放退出动画后卸载弹窗;卡片形态下入口在弹窗完全收起后再出现
destroy()卸载弹窗和入口、清空缓存与配置。再用需重新 init

init() 参数

PurchaseModalInitOptions,仅 generatorId 必填。按用途分组:

基础

字段类型默认说明
generatorIdstring必填生成器编码,如 'light_sign'default 模式用它查耗材包配置;所有模式都用它做结算分销归属与到货通知
localestring'en'界面语言。内置 en / zh,其它语言从 i18n 平台 CDN 拉取,缺词回退英文。见 国际化
zIndexnumber9999弹窗与入口按钮的层级;宿主有更高层级元素(全局 toast 等)时调高

数据源

字段类型默认说明
mode'default' | 'material''default'default 按生成器三级兵线自动匹配;materialmaterialIds 指定。见 数据源
materialIdsArray<number | string>mode: 'material' 时必填,统一商品卡的 supplySkuId 数组。数字或数字字符串均可,空数组视为未传并抛错

环境与域

字段类型默认说明
env'dev' | 'test' | 'test_us' | 'prod' | 'prod_cn''prod'一键切换三个后端域 + Shopify 站点配置。映射表见 鉴权与环境
apiBaseUrlstringenv显式覆盖社区域(耗材包 / 商品 / 订阅 / 分销),优先级高于 env
platformBaseUrlstringenv显式覆盖平台域(生成器绑定的耗材包 id、全局兜底包)。只有传了自定义 apiBaseUrl 时才需要一并传,两个域独立,SDK 不会从一个推出另一个
materialsLabUrlstringenv 拼内容站域列表底部「没找到需要的材料?」入口的跳转地址

形态与入口

字段类型默认说明
entryEntryOptions不渲染入口传了就渲染常驻入口按钮,open() 默认走卡片形态。entry.style 只接受 top / right / bottom / left / zIndex(至少给一个方位),其它外观由 SDK 统一控制;entry.onClick 在打开前触发,返回 false 阻止打开
outsideClickIgnoreSelectorsstring[][]卡片形态「点击外部收起」的豁免名单。mousedown 目标或其祖先链(含 shadow DOM 的 composedPath)命中任一 CSS 选择器就不收起。用于宿主在卡片打开时切模板、切素材等操作。可通过 update() 动态调整;对抽屉 / modal 无意义

行为开关

字段类型默认说明
enableReplenishNotifybooleantrue缺货商品行是否显示「到货通知」按钮。按钮的显隐还受后端变体级 showNotifyMe 控制
messagesPartial<Record<Locale, Record<string, string>>>覆盖或新增词条,深合并。key 可省略 sdk. 前缀,SDK 自动补齐

回调

字段签名说明
onCheckoutSuccess(checkoutUrl: string) => void结算链接创建成功。不传时默认 window.open(url, '_blank');想当前页跳转就在这里 location.href = url
onClose() => void弹窗关闭(任何来源)
onRequireLogin() => void未登录点击「到货通知」时触发,由宿主唤起登录。不传则 SDK toast「请先登录」
onError(err: unknown) => void商品加载 / 结算 / 站点切换 / 订阅等异常,拿到原始异常对象。SDK 已同时 toast,无需再做 UI 反馈
onActionEvent(event: ActionEvent) => void统一交互事件上报,20 类事件。见 事件上报

分销埋点(结算前调用分销接口生成 trackId,以下字段透传给它;都支持 update() 热更新,下次结算生效)

字段类型默认说明
pageSourcestring'generator'调用来源标识
relatedObjectIdnumber | string1关联对象 id
relatedObjectTitlestringgeneratorId关联对象标题

open() 参数

OpenModalOptions,全部可选,仅对本次 open 生效,下次不传即恢复默认。

字段类型说明
variant'drawer' | 'modal'本次形态。不传按 entry 推断(有 → 卡片,无 → 抽屉)。见 形态
selectIdsArray<string | number>默认勾选的耗材 supplySkuId。传了:命中项整体置顶(保持原有相对顺序),命中且可购的全部勾选,不受 3 个上限约束;不传或空数组:默认只勾前 3 个可购项(跳过不可购的继续往下数)
hideIdsArray<string | number>直接从列表过滤掉的耗材 supplySkuId,不展示、不勾选、不结算。与 selectIds / quantities 冲突时以 hideIds 为准
quantitiesRecord<string | number, number | string>supplySkuId 预置初始数量,未指定的为 1。接受数字或数字字符串,非整数向上取整,NaN / 非数字 / ≤ 0 回退为 1。与 selectIds 独立,未勾选的商品也可预置数量
ts
// 勾选并置顶 1001、1002,1001 数量 3,隐藏 2001
GeneratorMaterialPurchase.open({
  selectIds: [1001, 1002],
  quantities: { 1001: 3 },
  hideIds: [2001],
})

匹配规则:所有 id 转字符串后比较,传 numberstring 均可;缺货项即使命中 selectIds 也不会被勾选,避免结算被拦。

类型导出与版本号

ts
import GeneratorMaterialPurchase, {
  SDK_VERSION,
  type PurchaseModalInitOptions,
  type OpenModalOptions,
  type ActionEvent,
  type ProductDataType,
} from '@atomm-developer/generator-material-purchase'

console.log(SDK_VERSION) // '0.2.0'

src/types.ts 里的全部类型都对外导出(export * from './types')。UMD 下版本号在 window.GeneratorMaterialPurchase.SDK_VERSION;首次 init() 也会在控制台打印 [GeneratorMaterialPurchase] SDK version: x.y.z,生产构建也保留,用于确认页面加载的是哪个版本。


事件上报 onActionEvent

init() 时传入 onActionEvent,SDK 在所有关键交互发生时以结构化对象回调宿主,适合接埋点、数据仓库或业务联动。相比逐个新增回调,一个统一入口更易扩展且不易遗漏。

ts
GeneratorMaterialPurchase.init({
  generatorId: 'light_sign',
  onActionEvent: (event) => {
    // event 是判别联合类型,按 action 分派自动收窄
    switch (event.action) {
      case 'open':
        console.log('opened from', event.source)
        break
      case 'buy_click':
        analytics.track('material_buy_click', { items: event.list })
        break
      case 'checkout_success':
        analytics.track('material_checkout', { url: event.checkoutUrl, trackId: event.trackId })
        break
    }
  },
})

通用上下文(每个事件自动带上)

字段说明
timestampDate.now()
sdkVersion当前 SDK 版本
generatorIdinit 传入的值
store当前 Shopify 站点名;未初始化为空字符串

事件清单(20 类)

action触发时机负载字段
initinit() 校验通过、内部就绪
preloadpreload() 结束(无论成败)
refreshrefresh() 结束
updateupdate(patch) 结束changedKeys: string[],patch 里的字段名
destroydestroy() 开始(清理前发出)
open弹窗挂载完成source: 'api' | 'entry'selectIds?hideIds?
close弹窗关闭source: 'api' | 'overlay' | 'header_close' | 'outside_click' | 'entry_toggle'
entry_click点击入口按钮,在 entry.onClick 之前发出,即使被 hook 阻止打开也照发
product_list_loaded商品列表加载并渲染成功count: number
product_list_load_failed商品列表加载失败message: string
shop_menu_toggle站点下拉展开 / 收起open: boolean
shop_change用户切换站点from: stringto: string
variant_change商品规格下拉选择productIdoptionKeyvalue
quantity_change数量 ± 按钮productIdfromtodelta: 1 | -1
item_check商品行勾选 / 取消(真实用户操作,不含初始化默认勾选)productIdchecked: boolean
notify_click「到货通知」按钮productIdvariantId?state: 'require_login' | 'already_subscribed' | 'subscribing' | 'success' | 'failed'
buy_click点击「立即购买」,先于库存过滤与结算请求,表达购买意图list: ActionBuyItem[]id / name / num / variantId / price
checkout_successShopify 结算链接创建成功checkoutUrl: stringtrackId?: string
checkout_failed结算失败reason: 'no_valid' | 'all_out_of_stock' | 'network' | 'unknown'message?
erroronError 并行的埋点专用错误事件scope: 'open' | 'checkout' | 'shop_change' | 'notify' | 'preload' | 'refresh'message: string(脱敏)

productId 字段的取值是耗材的 supplySkuId(与 open() 参数口径一致)。

注意

  • 回调抛错不影响 SDK 主流程,SDK 用 try/catch 兜住并 console.warn
  • onError 拿原始异常对象(含堆栈)用于排查,error 事件只带脱敏 message 用于埋点,二者可同时接
  • close.source 的五种来源:api 宿主调 close()overlay 点抽屉蒙层;header_close 右上角关闭;outside_click 卡片形态点外部;entry_toggle 卡片形态再点入口

框架接入示例

原生 JS

html
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>
<script>
  GeneratorMaterialPurchase.init({
    generatorId: 'light_sign',
    locale: 'zh',
    onCheckoutSuccess: (url) => (location.href = url), // 当前页跳转而非新开
  })
  GeneratorMaterialPurchase.preload()
  document.querySelector('#buyBtn').onclick = () => GeneratorMaterialPurchase.open()
</script>

Vue 3

vue
<script setup lang="ts">
import { onMounted, onBeforeUnmount } from 'vue'
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'

onMounted(() => {
  GeneratorMaterialPurchase.init({ generatorId: 'light_sign' })
  GeneratorMaterialPurchase.preload()
})
onBeforeUnmount(() => GeneratorMaterialPurchase.destroy())
</script>

<template>
  <button @click="GeneratorMaterialPurchase.open()">购买耗材</button>
</template>

React

tsx
import { useEffect } from 'react'
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'

export function BuyButton() {
  useEffect(() => {
    GeneratorMaterialPurchase.init({ generatorId: 'light_sign' })
    GeneratorMaterialPurchase.preload()
    return () => GeneratorMaterialPurchase.destroy()
  }, [])
  return <button onClick={() => GeneratorMaterialPurchase.open()}>Buy materials</button>
}

Vue 2

vue
<script>
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'

export default {
  mounted() {
    GeneratorMaterialPurchase.init({ generatorId: 'light_sign' })
    GeneratorMaterialPurchase.preload()
  },
  beforeDestroy() {
    GeneratorMaterialPurchase.destroy()
  },
  methods: {
    openModal() {
      GeneratorMaterialPurchase.open()
    },
  },
}
</script>

<template>
  <button @click="openModal">购买耗材</button>
</template>

SDK 是全局单例,多个组件共用同一实例;在应用级(而非每个组件)做一次 init / destroy 即可。


国际化 i18n

语言来源与优先级

  1. 内置en / zh 两套完整词条,作为首帧兜底
  2. i18n 平台 CDNinit()update({ locale }) 时会拉取该语言的线上词条并覆盖内置。所以改文案请去 i18n 平台改,改包内 JSON 会被线上词条覆盖
  3. messages:宿主传入的覆盖,优先级最高

locale 支持 en / zh / zh-CN / zh-TW / zh-HK / de / es / fr / it / ja / ko / ru / uk / sl / vi / id 等;不在列表内回退 en。非 en / zh 的语言没有内置词条,CDN 到达前显示英文。

覆盖词条

ts
GeneratorMaterialPurchase.init({
  generatorId: 'light_sign',
  locale: 'en',
  messages: {
    en: { buy_now: 'Checkout securely' },   // key 可省略 sdk. 前缀
    ja: { 'sdk.buy_now': '今すぐ購入' },     // 带前缀也行
  },
})

运行中切换语言用 update({ locale: 'zh' }),弹窗开着也会即时重渲染。

词条一览

所有 key 实际带 sdk. 前缀,表中省略。

区域key
抽屉标题 / 关闭shopify_supplies_kitclose
卡片形态materials_listmatched_for_this_designentry_get_materials(入口按钮)、collapse
modal 形态download_success_titlecredits_tiplearn_moreselect_materials_tipback_to_edit
站点条current_country_notice(含 {country})、store_usstore_jpstore_testcountry_usacountry_japan
列表loadingno_items_in_cartsold_outcant_find_material(Materials Lab 入口)
到货通知notify_menotify_subscribednotify_subscribe_successnotify_subscribe_failnotify_login_required
底栏 / 结算totalitems_selected(含 {count})、buy_now
错误 toastfailed_load_product_listcheckout_failedselected_items_out_of_stockno_valid_products_selected

鉴权与环境

请求头

SDK 调用 atomm 后端的每个请求自动带两个 header,来源都是宿主页面已有的存储:

Header来源
uTokendocument.cookieutoken,没有则 localStorage.utoken
langlocalStorage.LANG_KEY,没有则 'en'

跨域部署时请确认:宿主已把 utoken 写入 cookie 或 localStorage;后端 CORS 白名单已放行调用方域名并允许 uToken / lang 自定义头。出现 401 / 403 先查 token 是否存在、过期、被跨域剥离。

env 映射

env 一次决定三个域与 Shopify 站点配置:

env社区域(耗材包 / 商品 / 订阅 / 分销)平台域(生成器配置 / 兜底包)内容站域(Materials Lab)Shopify 站点
devxcs-api-dev.makeblock.comapi-dev.makeblock.comwww-dev.atomm.comtestxtool 单站
testxcs-api-test.makeblock.comapi-test.makeblock.comxtool-community-test.makeblock.comtestxtool 单站
test_usxcs-api-test.xtool.comapi-test.xtool.comwww-test.atomm.comtestxtool 单站
prod(默认)xcs-api.xtool.comapi.xtool.comwww.atomm.com8 个正式站
prod_cnxcs-api.makextool.comapi.makextool.comwww.atomm.com.cn8 个正式站

apiBaseUrl / platformBaseUrl / materialsLabUrl 分别显式覆盖对应的域,优先级高于 env。三个域互相独立,覆盖了社区域不会自动推出平台域,所以自定义 apiBaseUrl 时通常要一并给 platformBaseUrl,否则会出现「耗材包从新环境拉、但配了哪几个包还从旧环境查」。


样式与 DOM

  • 挂载init({ entry }) 时向 document.body 追加入口 host;open() 时追加弹窗 host,close() / destroy() 后移除。两者都使用 Shadow DOM,宿主的 CSS reset / Tailwind / 全局样式不会影响弹窗内部,弹窗样式也不会泄漏
  • 层级:弹窗与入口默认 z-index: 9999init({ zIndex }) 统一调整,entry.style.zIndex 可单独覆盖入口
  • 尺寸:抽屉 320px × 100vh,≤ 767px 全屏;卡片 330px 宽,高度随内容且不超过视口 − 32px;modal 480 × 480 居中定高。移动端只有抽屉做了全屏适配
  • 动效:抽屉横向滑入、卡片从入口位置生长、modal 缩放淡入;均支持 prefers-reduced-motionprefers-reduced-transparency
  • 全局变量:UMD 下只在 window 挂一个 GeneratorMaterialPurchase;不修改 body 样式
  • 不可定制:SDK 不开放主题 / 样式注入,视觉由设计统一控制;文案可通过 i18n 改

从 0.1.x 升级到 0.2.0

数据源从 generator-accessory-pack 换成耗材包接口,字段口径统一到「统一商品卡」。

必改

变更说明
移除 init({ accessoryType })老接口的必填参数,新数据源不需要。TS 继续传会类型报错,JS 会被静默忽略
open() 三个 id 参数换口径selectIds / hideIds / quantities 的值从旧 accessoryId 换成 supplySkuId。形状不变、值要换;老值不报错只是匹配不上
新增 init({ platformBaseUrl })只有传了自定义 apiBaseUrl 的调用方需要一并传;按 env 走的不用管

行为变化(不改代码,但表现会变)

  • 列表来源改为三级兵线:即使生成器没配耗材,也可能展示机型适配包或全局兜底包。第二级(机型)此前是占位恒空,本版接上真实接口
  • 多包合并:按配置顺序拼接、跨包去重、缺货沉底;单个坏包不再拖垮整个列表
  • 默认勾选:从「全选可购项」改为「只勾前 3 个可购项」。传了 selectIds 不受影响
  • 价格改用后端口径:现价取 displayPrice(后端已按「优先促销价、缺省回退售价」算好),折扣角标取后端 discount,前端不再反算。促销商品显示促销价
  • 到货通知显隐改为变体级 showNotifyMe(后端合并了「缺货 + 商城可上架」),切换规格按钮会跟着变
  • 卡片弹窗的商品行样式随抽屉一起调整(去行分隔线、缩略图内缩、折扣角标移到商品名后)
  • 列表底部新增 Materials Lab 入口(抽屉、modal 有,卡片无);到货通知按钮图标从铃铛改为 + /

内部字段(自定义渲染或读过 ProductDataType 的才需要看)

  • ProductVariantType.displayPrice 语义变了:0.1.x 是前端格式化后的文案,0.2.0 起是后端下发的数值字符串。格式化结果移到新增的 priceText / compareText
  • 移除 configVariantId(统一为 defaultVariant)、移除 displayCompareAtPrice(改名 compareText
  • 新增 promotionPrice / discount / showNotifyMe / sort
  • variants[].option 接口按位置下发(option1/2/3),SDK 会按 options[] 顺序翻成规格名键,自定义渲染读到的是翻译后的

词条

新增 items_selected / cant_find_material / credits_tip / learn_more / download_success_title / select_materials_tip / back_to_edit;移除 discount_notice / download_success_tip

不变

mode: 'material' + materialIdsupdate / refresh / preloadentry 与卡片形态、onActionEvent、各回调签名。


常见问题 FAQ

报错 "call init() before open()"

没有先 init()。在应用挂载阶段(Vue onMounted / React useEffect)调一次即可;preload / update / refresh 同理。

传了 variant: 'modal' 却弹出抽屉

页面加载的是 0.2.0 之前的 CDN 产物,旧版会忽略 variant。看控制台 [GeneratorMaterialPurchase] SDK version: 打印确认版本;CDN 有缓存时强刷或等待缓存过期。

弹窗打开了但列表为空 / 一直转圈

mode 排查 Network:

  • default:平台域 /developer-platform/apps/<generatorId> 是否 200 并返回 materialPackIds;社区域 /community/v1/web/material-package/<id> 是否有 items。三级都没配到就是空列表(不是报错),见下一条
  • material:社区域 /community/v1/web/product/list?ids=…&store=… 是否有 list
  • 401 / 403:查 uToken;CORS 报错:找后端加白名单
  • 转圈不停通常是接口挂了,此时会 toast「加载商品列表失败」并回调 onError

生成器没配耗材,为什么还显示了商品

default 模式的三级兜底:没配生成器耗材包时会落到用户机型适配包或运营配的全局兜底包。这是预期行为;不想展示兜底商品请改用 mode: 'material' 精确控制。

selectIds / hideIds / quantities 不生效

检查传的是不是统一商品卡的 supplySkuId。0.1.x 的 accessoryId 值在 0.2.0 匹配不上且不会报错。可以在 onActionEventproduct_list_loaded 之后打印 item_check 事件的 productId,那就是正确口径。

为什么默认只勾了 3 个

0.2.0 起默认只勾前 3 个可购项,避免合计金额一上来就很高。想全选或指定,请传 selectIds(给几个选几个,不受上限约束)。

结算按钮置灰

所有勾选项都不可购(availableForSale === falseoutOfStock === true)时置灰。换规格或换站点重试。

想当前页跳转而不是新开标签

ts
GeneratorMaterialPurchase.init({
  generatorId: '...',
  onCheckoutSuccess: (url) => (location.href = url),
})

切换语言后弹窗没变

update({ locale: 'zh' }),弹窗开着也会即时重渲染。重新 init 也可以但会重挂入口。

商品缺货但没有「到货通知」按钮

两个条件都要满足:init 没传 enableReplenishNotify: false;后端该变体 showNotifyMe === true(缺货且商城可上架)。纯下架不可售的商品只显示「售罄」。

点「到货通知」没反应 / 弹了登录提示

订阅接口需要登录态。未登录时 SDK 不发请求:配了 onRequireLogin 就触发它,否则 toast「请先登录」。「已订阅」/「订阅中」状态下按钮禁用,属正常防重。

刷新页面后「已订阅」变回「到货通知」

预期行为。「已订阅」态只在当次会话内用于防重复点击,后端订阅记录不受影响,重复订阅同一变体也不会重复发邮件。

卡片形态下点我自己的页面元素就收起了

卡片默认「点击外部即收起」。把不该触发收起的元素选择器传给 outsideClickIgnoreSelectors,如 ['.template-card', '[data-role="material-switch"]'],支持 update() 动态调整。

显示的价格和商城详情页不一致

SDK 展示的是后端 displayPrice(有促销取促销价),划线价是 compareAtPrice 且只在高于现价时显示,折扣角标是后端 discount。口径由后端统一,前端不做计算。

可以同时开两个弹窗吗

不可以。SDK 是单例,同一时刻只有一个弹窗;再次 open() 会重建内容。

Shadow DOM 里不好调试

Chrome DevTools → Settings → Preferences → Elements 勾选「Show user agent shadow DOM」。或直接在 Console 用 window.GeneratorMaterialPurchase 调方法。


版本变更

0.2.0

破坏性变更(详见 升级指引

  • 数据源从 generator-accessory-pack 换成耗材包接口,default 模式改为三级兵线:生成器配置包 → 用户机型适配包(GET /community/v1/web/material-package/list?store=&deviceCodes=,机型取 my/infomachineItems[].code 前两台,未登录跳过)→ 全局兜底包
  • 移除 init({ accessoryType })selectIds / hideIds / quantities 的 id 口径从 accessoryId 换成 supplySkuId;新增 init({ platformBaseUrl })
  • 价格 / 折扣改用后端 displayPrice / discount;到货通知显隐改用变体级 showNotifyMe
  • ProductVariantType.displayPrice 语义变更,新增 priceText / compareText / promotionPrice / discount / showNotifyMe / sort;移除 configVariantId / displayCompareAtPrice

新增

  • 第三种形态 open({ variant: 'modal' }):导出成功后居中弹出的 480×480 弹窗,绿色对勾 + 「文件下载成功」+ 积分横幅(Learn more 跳创作者计划页),紧凑商品行,底部两态(未勾选只有「返回编辑」)。点蒙层不关闭、不显示站点切换条、数量控件需「可购且已勾选」才出现
  • open() 新增 variant?: 'drawer' | 'modal',不传行为不变
  • 列表底部新增 Materials Lab 入口(抽屉、modal),init({ materialsLabUrl }) 可覆盖地址
  • generator-workbench 内置「导出后弹耗材」链路并默认开启,见 应用壳功能与配置参考

行为变化

  • 默认勾选从「全选可购项」改为「前 3 个可购项」;传了 selectIds 不受影响
  • 多包合并:按配置顺序拼接、跨包去重、缺货沉底;单个坏包不影响整体
  • 登录态变化会自动作废商品缓存(游客与登录用户兵线不同)
  • 规格下拉菜单宽度改为随内容、不小于触发器、上限 200px,贴视口右缘时右对齐
  • 到货通知按钮图标从铃铛改为 + / ;商品行样式两种形态共用
  • 词条:新增 items_selected / cant_find_material / credits_tip / learn_more / download_success_title / select_materials_tip / back_to_edit,移除 discount_notice / download_success_tip

0.1.18

Bug 修复

  • availableForSale: false + outOfStock: false 的商品变成「哑行」:可购判定 isVariantPurchasable() 看的是 availableForSale === true && outOfStock !== true 两个条件,但禁用态、售罄角标、到货通知这些解释性 UI 此前只看 outOfStock。当接口返回上述组合时,该行 checkbox 不置灰、无「Sold out」角标、无到货通知按钮,但点击又被 if (!isPurchasable) return 静默拦截,用户看到的是一个「看着能点、点了没反应、也没有任何提示」的行。现在统一用 isUnavailable = !isVariantPurchasable(variant) 判定。真售罄(outOfStock: true)行为与此前完全一致
  • 未改动:售罄行「显示为勾选」仍按 outOfStock 判定,改用 isUnavailable 会让「不可售但有货」的行显示假勾选而实际不在 checkedUids 里,需单独评估

0.1.14 ~ 0.1.17 的变更记录缺失(代码已发布但文档未同步)。

0.1.13

  • 新参数 onActionEventinit() 新增统一交互事件上报回调,一次注册覆盖 20 类交互动作,事件对象是 TypeScript 判别联合类型(ActionEvent
  • 通用上下文自动补齐:所有事件带 timestamp / sdkVersion / generatorId / store
  • 事件清单:生命周期 init / preload / refresh / update / destroy / open / close / entry_click;商品加载 product_list_loaded / product_list_load_failed;店铺切换 shop_menu_toggle / shop_change;商品交互 item_check / variant_change / quantity_change;到货通知 notify_click(5 种状态);结算 buy_click / checkout_success / checkout_failed;通用错误 error
  • 回调抛错不影响 SDK 主流程;buy_click 在点击第一时间发出;entry_clickentry.onClick 之前发出
  • 向后兼容:未传时无副作用,与 onError / onClose / onCheckoutSuccess 并行

0.1.12

  • 新参数 relatedObjectTitle,透传到分销 trackId 接口,未传时用 generatorId 兜底;支持 update(patch) 热更新

0.1.9

  • 新参数 pageSource(默认 'generator')与 relatedObjectId(默认 1),透传到分销 trackId 接口,替代此前硬编码;支持 update(patch) 热更新

0.1.8

  • 入口挂件模式init() 新增 entry(fixed 定位 style + 可选 onClick)。传入后渲染红色胶囊「Get the Materials」入口,open() 切为 330px 圆角卡片锚定在按钮位置。卡片标题「Materials List」+ 副标题「Matched for this design」,收起图标
  • Morph 过渡:入口与卡片任何一帧不同时出现
  • 新 API preload():提前解析站点 + 拉商品填缓存,后续 open() 秒开;best-effort
  • 新 API update(patch):局部更新,entry.style 未变不重挂;数据源变化作废缓存并原地刷新;env / apiBaseUrl 变化重置 ShopifyClient
  • 新 API refresh():强制清缓存重拉
  • Bug 修复:下拉菜单被列表 overflow: auto 裁剪、卡片 transform 约束 fixed 上下文,现改为 portal 到 shadow root 下的浮层容器;列表滚动时自动关闭已打开的下拉
  • 新词条 materials_list / matched_for_this_design / entry_get_materials / collapse

0.1.7

  • init() 新增 mode'default' | 'material')与 materialIdsmaterial 模式走 GET /community/v1/web/product/list?ids=..&store=..
  • init() 新增 envdev / test / test_us / prod / prod_cn),优先级 apiBaseUrl > env > 默认 prod;非 prod / prod_cn 时自动切到 testxtool 单站点
  • 交互优化:Apple 风缓动、prefers-reduced-motion / prefers-reduced-transparency 支持、Buy Now loading 改 spinner、苹果风滚动条、下拉选项间距
  • Bug 修复:勾选后重渲染误播弹跳动画;下拉 trigger 按压反馈被打断
  • 接口层 GET 数组参数按重复 key 展开

0.1.6

  • open() 新增 hideIds(过滤耗材,优先级最高)与 quantities(预置初始数量,非整数向上取整、非法值回退 1);均仅对本次 open() 生效

0.1.5

  • 默认变体绑定:首次渲染 / 规格回退 / 默认勾选均按接口默认变体 id 命中
  • 默认变体 id 字段 defaultVariantconfigVariantId,SDK 兼容读取(0.2.0 已改回统一 defaultVariant

0.1.4

  • 缺货到货通知(Notify Me):默认开启,enableReplenishNotify: false 关闭;缺货行显示「到货通知」按钮,成功后切「已订阅」并 toast
  • 新增 onRequireLogin 回调;缺货行不再显示置灰数量控件
  • 新词条 notify_me / notify_subscribed / notify_subscribe_success / notify_subscribe_fail / notify_login_required

0.1.3

  • Bug 修复:切换规格时列表滚动位置重置;重新 init 不同 generatorId 列表不刷新
  • 移动端:≤ 767px 全屏、iOS 惯性滚动、关闭 / 数量按钮放大热区;所有按钮新增 :active 反馈

0.1.0(首发)

  • 暴露 init / open / close / destroy,挂全局 GeneratorMaterialPurchase
  • 内置 8 个 prod Shopify 站点;右侧 320px 抽屉 / 满屏 / 蒙层
  • 内置 en + zh 词条;请求自动注入 uToken + lang;Shadow DOM 样式隔离

反馈

使用中遇到 bug 或新需求,请在飞书上联系 邓时佳

MIT Licensed