Generator Material Purchase 接入文档
框架无关的 xTool 耗材购买弹窗 SDK,可在 Vue / React / 原生 JS 任意项目中通过
<script src>或import接入。
目录
1. 功能概述
调用 GeneratorMaterialPurchase.init(...) + GeneratorMaterialPurchase.open() 后,SDK 会在当前页面的 document.body 下插入一个弹窗:
- 固定
position: fixed,从屏幕右侧滑入; - PC 端宽 320px,移动端(≤ 767px)自动全屏(100% 宽度),高度 100vh(满屏);
- 带半透明蒙层(
rgba(0, 0, 0, 0.4)),点击蒙层关闭; - 内部包含:标题栏 / 站点切换 / 商品列表(含变体下拉、数量调整、勾选)/ 合计 + 立即购买。
业务能力:
- 自动按 IP 选 Shopify 站点(US / CA / EU / UK / FR / DE / JP / AU),支持手动切换;
- 调用
@xtool/shopify-sdk创建购物车 → 拿checkoutUrl→ 默认window.open(_, '_blank'); - 调用 atomm 后端拿耗材包列表 / IP 定位 / 分佣 trackId;
- 缺货商品支持订阅到货通知(Notify Me,默认开启,
init传enableReplenishNotify: false关闭); - 内置 en / zh 词条。
2. 安装与引入
2.1 通过 CDN(推荐给非 Vue/React 项目)
<!-- UMD:兼容老环境,挂全局 GeneratorMaterialPurchase -->
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>引入成功后,window.GeneratorMaterialPurchase 即为对外 API。
2.2 通过 npm(推荐给 Vue / React 工程)
# 内部 registry 需先配置 .npmrc → http://repository.makeblock.com/repository/npm-group/
pnpm add @atomm-developer/generator-material-purchase
# 或
npm install @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),构建工具会自动选用合适的入口。
3. 快速开始
最小可用示例 —— 两步:init + open。
<!doctype html>
<html>
<head>
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>
</head>
<body>
<button id="buyBtn">购买耗材</button>
<script>
// 第一步:初始化,传入业务必需参数
GeneratorMaterialPurchase.init({
generatorId: 'light-sign',
accessoryType: 'default',
})
// 第二步:点击按钮时打开弹窗
document.getElementById('buyBtn').addEventListener('click', () => {
GeneratorMaterialPurchase.open()
})
</script>
</body>
</html>4. 对外 API
SDK 共暴露 4 个方法:
interface GeneratorMaterialPurchaseApi {
init(options: PurchaseModalInitOptions): void
open(options?: OpenModalOptions): Promise<void>
close(): void
destroy(): void
}
interface OpenModalOptions {
/**
* 可选:默认选中的耗材 accessoryId 列表。
* - 传入且非空:仅选中这些耗材,并将它们在列表中置顶;
* - 不传或为空数组:默认选中全部可购买耗材(保持原行为)。
*/
selectIds?: Array<string | number>
/**
* 可选:隐藏的耗材 accessoryId 列表。
* - 命中的耗材直接从列表中过滤掉,不参与展示 / 勾选 / 结算;
* - 与 selectIds、quantities 冲突时以 hideIds 为准。
*/
hideIds?: Array<string | number>
/**
* 可选:按 accessoryId 指定初始数量,未指定的耗材保持默认值 1。
* 与 selectIds 相互独立——未在 selectIds 中的耗材也可预置数量。
*
* 取值规则:
* - 支持数字或数字字符串(如 3、"3");
* - 非整数向上取整(如 2.1 → 3、"2.1" → 3);
* - 非数字、NaN、≤0 一律回退为默认值 1。
*/
quantities?: Record<string | number, number | string>
}| 方法 | 说明 |
|---|---|
init(options) | 必须最先调用。设置业务参数、语言、回调。可重复调用以更新配置;若 generatorId 或 accessoryType 发生变化,内部商品缓存会自动失效,下次 open() 重新拉取 |
open(options?) | 在 document.body 插入弹窗 DOM,并触发首次商品加载(耗材包 + IP 定位)。返回 Promise,初始化失败时会触发 onError。可传入 selectIds(默认选中并置顶)、hideIds(隐藏指定耗材)、quantities(按耗材预置初始数量) |
close() | 播放退出动画后卸载弹窗 DOM。也可由用户点击蒙层 / X 按钮触发 |
destroy() | 强制卸载、清空内部缓存(如商品缓存),后续如需再次使用要先重新 init |
open()可被反复调用:每次打开都会重置 UI 状态(勾选、变体选择、滚动位置),但保留init设置和上一轮拉到的商品缓存(同站点不重复请求)。每次open的selectIds/hideIds/quantities仅对本次生效,下次不传则恢复默认。
selectIds:默认选中并置顶指定耗材
accessoryId 即 /community/v1/web/generator-accessory-pack 接口返回的每个耗材的 accessoryId 字段。
// 仅选中并置顶 accessoryId 为 1001、1002 的耗材
GeneratorMaterialPurchase.open({ selectIds: [1001, 1002] })
// 不传或空数组 → 默认选中全部可购买耗材
GeneratorMaterialPurchase.open()规则:
- 置顶:命中的耗材按传入顺序整体排到列表最前(不受库存影响);
- 选中:仅勾选命中且可购买的耗材,缺货项即使命中也不会被勾选(避免结算被拦截);
- 切换站点:切换国家/站点重新加载后,
selectIds依然生效(置顶 + 选中); - 匹配方式:按
accessoryId转字符串比较,传number或string均可。
hideIds:隐藏指定耗材
// 打开时不展示 accessoryId 为 2001、2002 的耗材
GeneratorMaterialPurchase.open({ hideIds: [2001, 2002] })规则:
- 过滤时机:在接口返回后立即按
accessoryId过滤,命中的耗材不进入渲染、勾选、结算任何环节; - 优先级:与
selectIds、quantities中的同一accessoryId冲突时,以hideIds为准; - 生效范围:仅对本次
open()生效,下次不传则恢复完整列表; - 匹配方式:与
selectIds一致,按accessoryId转字符串比较。
quantities:按耗材预置初始数量
对指定耗材设置弹窗打开时的初始数量,未指定的耗材保持默认值 1:
// 打开时 accessoryId=1001 数量为 3、1002 为 5,其余仍为 1
GeneratorMaterialPurchase.open({
selectIds: [1001, 1002],
quantities: { 1001: 3, 1002: 5 },
})
// 也支持数字字符串
GeneratorMaterialPurchase.open({ quantities: { 1001: '3', 1002: '2.1' } })规则:
- 归一化:数字或数字字符串均可;非整数向上取整(
2.1 → 3);NaN、非数字、≤0一律回退为1; - 与
selectIds独立:未出现在selectIds中的耗材也可以预置数量,不影响其勾选状态; - 生效范围:仅对本次
open()生效,下次不传则恢复默认1; - 匹配方式:与
selectIds一致,按accessoryId转字符串比较。
缺货到货通知(Notify Me)
默认开启:缺货(outOfStock === true)的商品行会显示「到货通知」按钮,init 传 enableReplenishNotify: false 可关闭:
- 点击后调用
/community/v1/web/subscribe/product/message,传当前站点store、变体variantId、init传入的generatorId与该商品的supplySkuId(即accessoryId); - 订阅成功后按钮切到「已订阅」态(勾图标 + 置灰),并 toast 提示到货后邮件通知;
- 「已订阅」状态仅当次浏览器会话有效,刷新页面后重置(与后端订阅记录无关,仅是前端防重复点击);
- 该接口需要登录态:未登录点击时 SDK 不发请求,触发
onRequireLogin回调交由宿主唤起登录流程;宿主未配置该回调时仅 toast 提示「请先登录」。
GeneratorMaterialPurchase.init({
generatorId: 'light-sign',
accessoryType: 'default',
onRequireLogin: () => {
// 唤起宿主自己的登录浮层 / 跳转登录页
openLoginDialog()
},
})5. 配置项详解
interface PurchaseModalInitOptions {
/** 必填:生成器编码,例如 'light-sign'、'flower-generator' */
generatorId: string
/** 必填:耗材类型,目前线上默认 'default' */
accessoryType: string
/** 可选:默认语言,'en' | 'zh',默认 'en' */
locale?: 'en' | 'zh' | string
/** 可选:弹窗 z-index,默认 9999 */
zIndex?: number
/** 可选:覆盖默认 prod apiBaseUrl,默认 https://xcs-api.xtool.com */
apiBaseUrl?: string
/** 可选:扩展或覆盖词条 */
messages?: Partial<Record<string, Record<string, string>>>
/** 可选:缺货到货通知订阅(Notify Me)开关,默认 true 开启,传 false 关闭 */
enableReplenishNotify?: boolean
/** 可选:结算成功回调,默认 window.open(checkoutUrl, '_blank') */
onCheckoutSuccess?: (checkoutUrl: string) => void
/** 可选:弹窗关闭回调 */
onClose?: () => void
/** 可选:未登录时点击「到货通知」的回调,由宿主唤起自己的登录流程;未配置则 toast 提示请先登录 */
onRequireLogin?: () => void
/** 可选:异常回调(接口失败 / 结算失败等) */
onError?: (err: unknown) => void
}字段语义说明:
generatorId— 用作后端/generator-accessory-pack接口的入参,决定拉哪个生成器的耗材列表;同时作为分佣接口的relatedObjectType。accessoryType— 同上接口的入参,目前线上统一'default'。locale— SDK 内部 i18n 当前语言。若想动态切换,重新init({ locale: ... })再open()即可。messages— 用来覆盖单条词条或新增非内置语言。形如{ en: { buy_now: 'Checkout' }, ja: { ... } }。zIndex— 当宿主页面有更高层级元素(如全局 toast)时调高,避免被遮挡。apiBaseUrl— 默认指向生产 atomm 服务。本地开发或预发联调可以指向其它环境。onCheckoutSuccess— 默认行为是新开一个标签打开 Shopify checkout 页。如果想在当前页跳转,自行实现location.href = url。onClose— 用户主动关闭时触发,可用于埋点 / 业务联动。enableReplenishNotify— 缺货到货通知订阅开关。默认true:缺货行显示「到货通知」按钮;传false关闭后缺货行只显示「售罄」角标。onRequireLogin— 未登录(无uToken)时点击「到货通知」按钮触发,SDK 不发请求,由宿主唤起登录流程;未配置时 SDK 仅 toast「请先登录」。onError— 商品加载失败、结算失败、IP 定位失败等异常都会回调。SDK 内部会同时弹一个 toast,不需要业务方再做 UI 反馈。
6. 鉴权机制
SDK 在调用 atomm 后端的所有请求里都会自动注入两个请求头:
| Header | 来源 |
|---|---|
uToken | 优先读 document.cookie.utoken,没有则读 localStorage.utoken |
lang | 读 localStorage.LANG_KEY,没有则默认 'en' |
跨域部署时请保证:
- 宿主页面已经把
utoken写入 cookie 或 localStorage(通常登录态已经完成);xcs-api.xtool.com已经在 CORS 白名单里放行调用方域名,并允许携带uToken / lang自定义头。
如果出现 401 / 403,先排查 uToken 是否存在、是否过期、是否在跨域请求中被剥离。
7. 不同框架接入示例
7.1 原生 JS(<script src>)
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>
<script>
GeneratorMaterialPurchase.init({
generatorId: 'light-sign',
accessoryType: 'default',
locale: 'zh',
onCheckoutSuccess: (url) => (location.href = url), // 当前页跳转而非新开
onClose: () => console.log('modal closed'),
})
document.querySelector('#buyBtn').onclick = () => GeneratorMaterialPurchase.open()
</script>7.2 Vue 3 (Composition API)
<script setup lang="ts">
import { onMounted, onBeforeUnmount } from 'vue'
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'
onMounted(() => {
GeneratorMaterialPurchase.init({
generatorId: 'light-sign',
accessoryType: 'default',
})
})
onBeforeUnmount(() => {
GeneratorMaterialPurchase.destroy()
})
function handleClick() {
GeneratorMaterialPurchase.open()
}
</script>
<template>
<button @click="handleClick">购买耗材</button>
</template>7.3 React (Hooks)
import { useEffect } from 'react'
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'
export function BuyButton() {
useEffect(() => {
GeneratorMaterialPurchase.init({
generatorId: 'flower-generator',
accessoryType: 'default',
locale: 'en',
})
return () => GeneratorMaterialPurchase.destroy()
}, [])
return <button onClick={() => GeneratorMaterialPurchase.open()}>Buy accessories</button>
}7.4 Vue 2(Options API)
<script>
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase'
export default {
mounted() {
GeneratorMaterialPurchase.init({
generatorId: 'light-sign',
accessoryType: 'default',
})
},
beforeDestroy() {
GeneratorMaterialPurchase.destroy()
},
methods: {
openModal() {
GeneratorMaterialPurchase.open()
},
},
}
</script>
<template>
<button @click="openModal">购买耗材</button>
</template>8. 国际化(i18n)
8.1 内置语言
SDK 自带 en 与 zh 两套完整词条,覆盖:
- 弹窗标题 / 关闭按钮 / 国家通知文案 / 加载中 / 空状态 / 售罄;
- 合计 / 折扣提示 / 立即购买;
- 各种错误 toast(加载失败、结算失败、缺货等);
- 8 个站点的国家名(USA / Canada / Australia / EU / Germany / France / UK / Japan)。
通过 init({ locale: 'zh' }) 切换。
8.2 扩展或覆盖词条
GeneratorMaterialPurchase.init({
generatorId: 'light-sign',
accessoryType: 'default',
locale: 'en',
messages: {
en: {
buy_now: 'Checkout securely', // 覆盖某条
},
ja: {
// 新增日语
shopify_supplies_kit: '消耗品キット',
buy_now: '今すぐ購入',
// ...其余词条若不提供,会 fallback 到 en
},
},
})8.3 词条键名一览
| key | 含义 |
|---|---|
shopify_supplies_kit | 弹窗标题 |
close | 关闭按钮 aria-label |
current_country_notice | 国家提示,模板含 {country} |
loading | 加载文案 |
no_items_in_cart | 空商品状态 |
sold_out | 售罄角标 |
notify_me | 到货通知按钮 |
notify_subscribed | 已订阅按钮态 |
notify_subscribe_success | 订阅成功 toast |
notify_subscribe_fail | 订阅失败 toast |
notify_login_required | 未登录提示 toast |
total | 合计标签 |
discount_notice | 折扣码提示 |
buy_now | 立即购买按钮 |
failed_load_product_list | 拉取失败 toast |
checkout_failed | 结算失败 toast |
selected_items_out_of_stock | 勾选项已售罄 toast |
no_valid_products_selected | 无可购买商品 toast |
store_us / store_ca / store_au / store_eu / store_de / store_fr / store_uk / store_jp | 站点下拉短名 |
country_usa / country_canada / country_australia / country_eu / country_germany / country_france / country_uk / country_japan | 国家长名 |
9. 样式与视觉规格
| 维度 | 规格 |
|---|---|
| 挂载方式 | document.body.appendChild(host),host 使用 Shadow DOM 隔离样式 |
| 蒙层 | position: fixed; inset: 0; background: rgba(0,0,0,0.4);,点击关闭 |
| 弹窗 | position: fixed; top: 0; right: 0; width: 320px; height: 100vh;;移动端(≤ 767px):width: 100%; left: 0(全屏) |
| 动画 | 蒙层 0.2s 透明度淡入,弹窗 0.25s translateX(100% → 0) |
| 移动端滚动 | 列表区域启用 -webkit-overflow-scrolling: touch,iOS 惯性滚动 |
| 移动端热区 | 关闭按钮放大至 36×36px,数量加减按钮放大至 32×32px(PC 端保持 24/20px) |
| 触摸反馈 | 所有可交互按钮增加 :active 按压样式(与 :hover 并列) |
| 字体 | Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif |
| 主色 | 购买按钮 #ff0035 / 强调 #070b10 / 提示橙 #ff7c23 |
| CSS 命名 | BEM 风格,统一前缀 xpm-(Xtool Purchase Modal) |
由于使用 Shadow DOM,宿主页面的 CSS reset、Tailwind、* { box-sizing: ... } 等都不会污染弹窗内部。同时弹窗内部样式也不会泄漏到宿主页面。
10. 常见问题(FAQ)
Q1:调用 open() 报错 "call init() before open()"
没有先调 init() 就调 open()。在挂载阶段(Vue onMounted / React useEffect)调用一次 init 即可。
Q2:弹窗打开了但商品列表一直转圈 / 显示空
依次排查:
- 浏览器开发者工具 Network 看
/generator-accessory-pack接口是否 200 且返回code: 0; - 若是 401 / 403,确认
uToken是否已写入 cookie 或 localStorage; - 若是 CORS 报错,找后端把调用方域名加入白名单。
Q3:结算按钮置灰,怎么办
当所有勾选项都被识别为售罄(outOfStock === true 或 availableForSale === false)时按钮会被置灰。换一个变体或换一个站点重试。
Q4:如何在当前页跳转而不是新开标签
传入 onCheckoutSuccess:
GeneratorMaterialPurchase.init({
generatorId: '...',
accessoryType: 'default',
onCheckoutSuccess: (url) => (window.location.href = url),
})Q5:切换语言后弹窗没刷新
切换语言后请重新 init({ locale: 'xx' })。如果弹窗当前正打开,先 close() 再 open()。
Q6:可以同时打开多个弹窗吗
不可以。SDK 是单例,第二次 open() 会等第一次的退出动画完成才挂载。需要并发场景请反馈。
Q7:会污染全局变量吗
仅会在 window 上挂一个 GeneratorMaterialPurchase 对象(UMD 模式下);不会修改 body 的样式;只在弹窗存在期间向 body 追加一个 host 元素,close/destroy 后移除。
Q8:Shadow DOM 内 DevTools 调试不便怎么办
Chrome DevTools 在 Settings → Preferences → Elements 勾选 "Show user agent shadow DOM" 后可以展开。或者用 window.GeneratorMaterialPurchase 在 Console 直接调方法。
Q9:商品缺货但没显示「到货通知」按钮
检查 init 是否传了 enableReplenishNotify: false(该开关默认开启,传 false 后缺货行仅显示「售罄」角标)。
Q10:点击「到货通知」没反应 / 弹了登录提示
该接口需要登录态。未登录(无 uToken)时 SDK 不会发请求:配置了 onRequireLogin 则触发该回调,否则 toast「请先登录」。另外「已订阅」与「订阅中」状态下按钮为 disabled,属正常防重复。
Q11:刷新页面后「已订阅」按钮变回「到货通知」,是 bug 吗
不是。「已订阅」状态仅当次浏览器会话有效(与 3d_generator 行为一致),它只用于防止重复点击;后端的订阅记录不受影响,重复订阅同一变体也不会重复发邮件。
11. 打包产物说明
构建命令:
pnpm build输出到 dist/:
| 文件 | 用途 | 体积(gzip) |
|---|---|---|
index.es.js | ESM 入口,给 npm / 现代打包工具 | ~15 KB |
index.umd.js | UMD,浏览器 <script src> 或 Node require | ~12 KB |
index.d.ts | TypeScript 声明文件(rollup 合并后的单文件) | — |
*.map | sourcemap,定位运行时报错 | — |
发布到 npm 时 files 字段会带上:
dist/(产物)README.md
12. 版本与变更
0.1.6
- 新参数:
open()新增hideIds,命中的耗材直接从列表中过滤,不参与展示 / 勾选 / 结算;与selectIds、quantities冲突时以hideIds为准 - 新参数:
open()新增quantities,按accessoryId预置弹窗打开时的初始数量,未指定的耗材保持默认值 1;支持数字或数字字符串(非整数向上取整,NaN/ 非数字 /≤0回退为 1),与selectIds相互独立 - 生效范围:
hideIds/quantities均仅对本次open()生效,下次不传则恢复默认
0.1.5
- 默认变体绑定:耗材列表首次渲染 / 切换变体回退 / 计算默认勾选变体时,均按接口返回的默认变体 id 命中对应 SKU,宿主无需额外配置
- 接口字段调整:默认变体 id 字段由
defaultVariant更名为configVariantId;SDK 优先读新字段,未返回时自动回退到旧字段defaultVariant,向后兼容
0.1.4
- 新功能:缺货商品支持订阅到货通知(Notify Me),默认开启,
init传enableReplenishNotify: false可关闭。缺货商品行显示「到货通知」按钮,点击调用/community/v1/web/subscribe/product/message(传store+variantId+generatorId+supplySkuId),成功后切「已订阅」态并 toast 提示 - 新配置:
init新增enableReplenishNotify开关与可选回调onRequireLogin(未登录点击「到货通知」时触发,由宿主唤起登录流程;未配置则 toast 提示请先登录) - 交互调整:缺货商品行不再显示置灰的数量控件(与 3d_generator 一致)
- 新词条:
notify_me/notify_subscribed/notify_subscribe_success/notify_subscribe_fail/notify_login_required
0.1.3
- Bug 修复:切换变体选项时耗材列表滚动位置被重置(重渲染前后保留
scrollTop) - Bug 修复:重新调用
init()传入不同generatorId/accessoryType时列表不刷新(generatorId或accessoryType变更时自动清除商品缓存) - 移动端:屏幕宽度 ≤ 767px 时弹窗自动全屏(PC 端保持 320px 定宽)
- 移动端:列表区域启用
-webkit-overflow-scrolling: touch,iOS 原生惯性滚动 - 移动端:关闭按钮放大至 36×36px、数量加减按钮放大至 32×32px,改善触摸热区
- 交互优化:所有可交互按钮新增
:active触摸按压反馈样式
0.1.0(首发)
- 首个发布版本;
- 暴露
init / open / close / destroy四个方法,挂全局GeneratorMaterialPurchase; - 内置 8 个 prod Shopify 站点;
- 弹窗形态调整:右侧 320px / 满屏 / 蒙层;
- 内置 en + zh 词条;
- 接口请求自动注入
uToken+lang; - BEM CSS + Shadow DOM 样式隔离。
反馈
如使用过程中遇到 bug 或新需求,请在内部 GitLab 上对应仓库提 issue,或在团队飞书群里 @ 维护者。