耗材购买 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() 时决定,同一个实例可以按调用点切换):
| 形态 | 怎么触发 | 长什么样 | 适合 |
|---|---|---|---|
侧抽屉 drawer | open(),且没配 entry | 右侧滑入,PC 320px、移动端全屏,带蒙层 | 页面上已有自己的「购买耗材」按钮 |
卡片 + 常驻入口 card | init({ entry }) 后点击 SDK 渲染的入口按钮 | 页面角落常驻一个「Get the Materials」红色胶囊,点开是锚定在它旁边的 330px 卡片 | 想要一个免维护的常驻购买入口 |
居中弹窗 modal | open({ 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)
<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)
# 内部 registry,需先配置 .npmrc → http://repository.makeblock.com/repository/npm-group/
pnpm add @atomm-developer/generator-material-purchaseimport 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 的env,atommProLocale: 'zh'对应弹窗zh,其余回退en
完整说明见 应用壳功能与配置参考 · 导出后的耗材购买弹窗。这种情况下你不需要读本文剩余部分,除非想在壳子之外再接一个常驻入口。
快速开始
最小接入
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:常驻入口 + 卡片
GeneratorMaterialPurchase.init({
generatorId: 'light_sign',
entry: {
style: { top: '16px', right: '16px' }, // 只接受定位属性
onClick: () => {
// 可选:返回 false 阻止打开,例如先要求登录
},
},
})
GeneratorMaterialPurchase.preload()
// 之后不需要再调 open():用户点击入口按钮即打开卡片,再点一次收起入口按钮由 SDK 渲染并常驻页面,卡片锚定在按钮位置展开,两者以 morph 过渡衔接,任何一帧不会同时可见。点击卡片外部会收起,页面上有些元素点击时不想触发收起(比如切模板的卡片),用 outsideClickIgnoreSelectors 声明。
场景 B:导出成功后推荐(modal)
GeneratorMaterialPurchase.init({ generatorId: 'light_sign', locale: 'zh' })
GeneratorMaterialPurchase.preload() // 导出前就预热,弹出时无 loading
async function onExportSuccess() {
await GeneratorMaterialPurchase.open({ variant: 'modal' })
}variant 是 open() 的参数,所以即使同一个实例配了 entry,也可以在导出成功时按需弹这一种,两者共用同一份商品缓存。
场景 C:自定义耗材清单(material 模式)
GeneratorMaterialPurchase.init({
generatorId: 'light_sign', // 结算 / 到货通知仍用到,必填
mode: 'material',
materialIds: [889, 890, 891], // 统一商品卡的 supplySkuId
})
GeneratorMaterialPurchase.open({
selectIds: [889], // 默认勾选并置顶
quantities: { 889: 2 },
})商品完全由 materialIds 决定,不再走生成器的耗材包配置。运行中切换清单用 update({ materialIds }),不会重挂入口、弹窗开着会原地刷新。
核心概念
形态:variant
形态在 open() 时决定:
open() // 配了 entry → card,否则 drawer
open({ variant: 'modal' }) // 居中弹窗,与 entry 无关
open({ variant: 'drawer' }) // 强制抽屉,即使配了 entry三种形态的数据源、规格切换、到货通知、结算逻辑完全一致,只换布局:
侧抽屉 drawer | 卡片 card | 居中弹窗 modal | |
|---|---|---|---|
| 尺寸 | PC 320px 宽、满屏高;≤ 767px 全屏 | 330px 宽,高度随内容,上限视口 − 32px | 480 × 480 定高,圆角 12 |
| 蒙层 | 有,点击关闭 | 无 | 有,点击不关闭(底部已有「返回编辑」) |
| 站点 / 国家切换条 | 显示 | 显示 | 不显示(一次性推荐不承担改配送地) |
| 数量控件 | 可购即显示 | 可购即显示 | 「可购 且 已勾选」才显示 |
| 底部操作栏 | 合计 + 购买 | 合计 + 购买 | 两态:未勾选只有描边的「返回编辑」;勾选后出现合计 + 购买 |
| 顶部 | 标题 | 「Materials List」+ 副标题 | 绿色对勾 + 「文件下载成功」+ 积分横幅(Learn more 跳创作者计划页) |
| Materials Lab 入口 | 列表底部显示 | 不显示 | 列表底部显示,内容不满一屏时贴底 |
| 关闭方式 | 蒙层 / 右上角 × / close() | 点击卡片外部 / 右上角收起 / 再点入口 / close() | 「返回编辑」/ 右上角 × / close() |
| 移动端 | 全屏适配 | 固定 330px | 固定 480px,未做小屏适配 |
只有卡片形态受 entry 影响:open() 不传 variant 且存在 entry 时才是卡片;类型上 variant 只接受 'drawer' | 'modal',卡片无法显式指定。
数据源:mode 与三级兵线
default 模式:后端按生成器匹配,逐级兜底,取到商品即停:
- 生成器配置的耗材包:开发者平台里该生成器(
generatorId)绑定的耗材包,全部拼接 - 机型适配包:没配且用户已登录时,取用户常用机型(最多前两台)适配的耗材包,包内商品全量展示。未登录跳过这一级
- 全局兜底包:运营在效能平台配置的兜底耗材包;也没配就是空列表
因此即使生成器没配耗材,也可能展示兜底耗材;反过来,列表为空只说明三级都没配到,不是接入错误。
多包合并规则:按配置顺序拼接、同一商品跨包只保留第一次出现、缺货商品沉到列表底部;单个包拉不到(已删除 / 已下架)只是不贡献商品,不影响其他包。
material 模式:列表完全由 materialIds 决定,GET /community/v1/web/product/list?ids=…&store=…,不做去重与缺货沉底。generatorId 仍用于结算分销与到货通知,必填。
两种模式返回的是同一张「统一商品卡」,字段口径完全一致。
耗材 id 口径
open() 的 selectIds / hideIds / quantities 与 init() 的 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。用户可在抽屉 / 卡片顶部手动切换,切换后重拉商品(价格、库存、上架状态都按站点口径)。
env 为 dev / test / test_us 时自动切到 testxtool 单一测试站点,不会碰正式店。
API 参考
方法总表
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 必填。按用途分组:
基础
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
generatorId | string | 必填 | 生成器编码,如 'light_sign'。default 模式用它查耗材包配置;所有模式都用它做结算分销归属与到货通知 |
locale | string | 'en' | 界面语言。内置 en / zh,其它语言从 i18n 平台 CDN 拉取,缺词回退英文。见 国际化 |
zIndex | number | 9999 | 弹窗与入口按钮的层级;宿主有更高层级元素(全局 toast 等)时调高 |
数据源
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
mode | 'default' | 'material' | 'default' | default 按生成器三级兵线自动匹配;material 按 materialIds 指定。见 数据源 |
materialIds | Array<number | string> | mode: 'material' 时必填,统一商品卡的 supplySkuId 数组。数字或数字字符串均可,空数组视为未传并抛错 |
环境与域
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
env | 'dev' | 'test' | 'test_us' | 'prod' | 'prod_cn' | 'prod' | 一键切换三个后端域 + Shopify 站点配置。映射表见 鉴权与环境 |
apiBaseUrl | string | 按 env | 显式覆盖社区域(耗材包 / 商品 / 订阅 / 分销),优先级高于 env |
platformBaseUrl | string | 按 env | 显式覆盖平台域(生成器绑定的耗材包 id、全局兜底包)。只有传了自定义 apiBaseUrl 时才需要一并传,两个域独立,SDK 不会从一个推出另一个 |
materialsLabUrl | string | 按 env 拼内容站域 | 列表底部「没找到需要的材料?」入口的跳转地址 |
形态与入口
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
entry | EntryOptions | 不渲染入口 | 传了就渲染常驻入口按钮,open() 默认走卡片形态。entry.style 只接受 top / right / bottom / left / zIndex(至少给一个方位),其它外观由 SDK 统一控制;entry.onClick 在打开前触发,返回 false 阻止打开 |
outsideClickIgnoreSelectors | string[] | [] | 卡片形态「点击外部收起」的豁免名单。mousedown 目标或其祖先链(含 shadow DOM 的 composedPath)命中任一 CSS 选择器就不收起。用于宿主在卡片打开时切模板、切素材等操作。可通过 update() 动态调整;对抽屉 / modal 无意义 |
行为开关
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
enableReplenishNotify | boolean | true | 缺货商品行是否显示「到货通知」按钮。按钮的显隐还受后端变体级 showNotifyMe 控制 |
messages | Partial<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() 热更新,下次结算生效)
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
pageSource | string | 'generator' | 调用来源标识 |
relatedObjectId | number | string | 1 | 关联对象 id |
relatedObjectTitle | string | 同 generatorId | 关联对象标题 |
open() 参数
OpenModalOptions,全部可选,仅对本次 open 生效,下次不传即恢复默认。
| 字段 | 类型 | 说明 |
|---|---|---|
variant | 'drawer' | 'modal' | 本次形态。不传按 entry 推断(有 → 卡片,无 → 抽屉)。见 形态 |
selectIds | Array<string | number> | 默认勾选的耗材 supplySkuId。传了:命中项整体置顶(保持原有相对顺序),命中且可购的全部勾选,不受 3 个上限约束;不传或空数组:默认只勾前 3 个可购项(跳过不可购的继续往下数) |
hideIds | Array<string | number> | 直接从列表过滤掉的耗材 supplySkuId,不展示、不勾选、不结算。与 selectIds / quantities 冲突时以 hideIds 为准 |
quantities | Record<string | number, number | string> | 按 supplySkuId 预置初始数量,未指定的为 1。接受数字或数字字符串,非整数向上取整,NaN / 非数字 / ≤ 0 回退为 1。与 selectIds 独立,未勾选的商品也可预置数量 |
// 勾选并置顶 1001、1002,1001 数量 3,隐藏 2001
GeneratorMaterialPurchase.open({
selectIds: [1001, 1002],
quantities: { 1001: 3 },
hideIds: [2001],
})匹配规则:所有 id 转字符串后比较,传 number 或 string 均可;缺货项即使命中 selectIds 也不会被勾选,避免结算被拦。
类型导出与版本号
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 在所有关键交互发生时以结构化对象回调宿主,适合接埋点、数据仓库或业务联动。相比逐个新增回调,一个统一入口更易扩展且不易遗漏。
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
}
},
})通用上下文(每个事件自动带上)
| 字段 | 说明 |
|---|---|
timestamp | Date.now() |
sdkVersion | 当前 SDK 版本 |
generatorId | init 传入的值 |
store | 当前 Shopify 站点名;未初始化为空字符串 |
事件清单(20 类)
| action | 触发时机 | 负载字段 |
|---|---|---|
init | init() 校验通过、内部就绪 | |
preload | preload() 结束(无论成败) | |
refresh | refresh() 结束 | |
update | update(patch) 结束 | changedKeys: string[],patch 里的字段名 |
destroy | destroy() 开始(清理前发出) | |
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: string、to: string |
variant_change | 商品规格下拉选择 | productId、optionKey、value |
quantity_change | 数量 ± 按钮 | productId、from、to、delta: 1 | -1 |
item_check | 商品行勾选 / 取消(真实用户操作,不含初始化默认勾选) | productId、checked: boolean |
notify_click | 「到货通知」按钮 | productId、variantId?、state: 'require_login' | 'already_subscribed' | 'subscribing' | 'success' | 'failed' |
buy_click | 点击「立即购买」,先于库存过滤与结算请求,表达购买意图 | list: ActionBuyItem[](id / name / num / variantId / price) |
checkout_success | Shopify 结算链接创建成功 | checkoutUrl: string、trackId?: string |
checkout_failed | 结算失败 | reason: 'no_valid' | 'all_out_of_stock' | 'network' | 'unknown'、message? |
error | 与 onError 并行的埋点专用错误事件 | 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
<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
<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
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
<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
语言来源与优先级
- 内置:
en/zh两套完整词条,作为首帧兜底 - i18n 平台 CDN:
init()与update({ locale })时会拉取该语言的线上词条并覆盖内置。所以改文案请去 i18n 平台改,改包内 JSON 会被线上词条覆盖 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 到达前显示英文。
覆盖词条
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_kit、close |
| 卡片形态 | materials_list、matched_for_this_design、entry_get_materials(入口按钮)、collapse |
| modal 形态 | download_success_title、credits_tip、learn_more、select_materials_tip、back_to_edit |
| 站点条 | current_country_notice(含 {country})、store_us … store_jp、store_test、country_usa … country_japan |
| 列表 | loading、no_items_in_cart、sold_out、cant_find_material(Materials Lab 入口) |
| 到货通知 | notify_me、notify_subscribed、notify_subscribe_success、notify_subscribe_fail、notify_login_required |
| 底栏 / 结算 | total、items_selected(含 {count})、buy_now |
| 错误 toast | failed_load_product_list、checkout_failed、selected_items_out_of_stock、no_valid_products_selected |
鉴权与环境
请求头
SDK 调用 atomm 后端的每个请求自动带两个 header,来源都是宿主页面已有的存储:
| Header | 来源 |
|---|---|
uToken | document.cookie 的 utoken,没有则 localStorage.utoken |
lang | localStorage.LANG_KEY,没有则 'en' |
跨域部署时请确认:宿主已把 utoken 写入 cookie 或 localStorage;后端 CORS 白名单已放行调用方域名并允许 uToken / lang 自定义头。出现 401 / 403 先查 token 是否存在、过期、被跨域剥离。
env 映射
env 一次决定三个域与 Shopify 站点配置:
env | 社区域(耗材包 / 商品 / 订阅 / 分销) | 平台域(生成器配置 / 兜底包) | 内容站域(Materials Lab) | Shopify 站点 |
|---|---|---|---|---|
dev | xcs-api-dev.makeblock.com | api-dev.makeblock.com | www-dev.atomm.com | testxtool 单站 |
test | xcs-api-test.makeblock.com | api-test.makeblock.com | xtool-community-test.makeblock.com | testxtool 单站 |
test_us | xcs-api-test.xtool.com | api-test.xtool.com | www-test.atomm.com | testxtool 单站 |
prod(默认) | xcs-api.xtool.com | api.xtool.com | www.atomm.com | 8 个正式站 |
prod_cn | xcs-api.makextool.com | api.makextool.com | www.atomm.com.cn | 8 个正式站 |
apiBaseUrl / platformBaseUrl / materialsLabUrl 分别显式覆盖对应的域,优先级高于 env。三个域互相独立,覆盖了社区域不会自动推出平台域,所以自定义 apiBaseUrl 时通常要一并给 platformBaseUrl,否则会出现「耗材包从新环境拉、但配了哪几个包还从旧环境查」。
样式与 DOM
- 挂载:
init({ entry })时向document.body追加入口 host;open()时追加弹窗 host,close()/destroy()后移除。两者都使用 Shadow DOM,宿主的 CSS reset / Tailwind / 全局样式不会影响弹窗内部,弹窗样式也不会泄漏 - 层级:弹窗与入口默认
z-index: 9999,init({ zIndex })统一调整,entry.style.zIndex可单独覆盖入口 - 尺寸:抽屉 320px × 100vh,≤ 767px 全屏;卡片 330px 宽,高度随内容且不超过视口 − 32px;modal 480 × 480 居中定高。移动端只有抽屉做了全屏适配
- 动效:抽屉横向滑入、卡片从入口位置生长、modal 缩放淡入;均支持
prefers-reduced-motion与prefers-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' + materialIds、update / refresh / preload、entry 与卡片形态、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 匹配不上且不会报错。可以在 onActionEvent 的 product_list_loaded 之后打印 item_check 事件的 productId,那就是正确口径。
为什么默认只勾了 3 个
0.2.0 起默认只勾前 3 个可购项,避免合计金额一上来就很高。想全选或指定,请传 selectIds(给几个选几个,不受上限约束)。
结算按钮置灰
所有勾选项都不可购(availableForSale === false 或 outOfStock === true)时置灰。换规格或换站点重试。
想当前页跳转而不是新开标签
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/info的machineItems[].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
- 新参数
onActionEvent:init()新增统一交互事件上报回调,一次注册覆盖 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_click在entry.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')与materialIds:material模式走GET /community/v1/web/product/list?ids=..&store=..init()新增env(dev/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 字段
defaultVariant→configVariantId,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 或新需求,请在飞书上联系 邓时佳。