Skip to content

Generator Material Purchase 接入文档

框架无关的 xTool 耗材购买弹窗 SDK,可在 Vue / React / 原生 JS 任意项目中通过 <script src>import 接入。


目录

  1. 功能概述
  2. 安装与引入
  3. 快速开始
  4. 对外 API
  5. 配置项详解
  6. 鉴权机制
  7. 不同框架接入示例
  8. 国际化(i18n)
  9. 样式与视觉规格
  10. 常见问题(FAQ)
  11. 打包产物说明
  12. 版本与变更

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,默认开启,initenableReplenishNotify: false 关闭);
  • 内置 en / zh 词条。

2. 安装与引入

2.1 通过 CDN(推荐给非 Vue/React 项目)

html
<!-- 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 工程)

bash
# 内部 registry 需先配置 .npmrc → http://repository.makeblock.com/repository/npm-group/
pnpm add @atomm-developer/generator-material-purchase
# 或
npm install @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),构建工具会自动选用合适的入口。


3. 快速开始

最小可用示例 —— 两步:init + open

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

ts
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)必须最先调用。设置业务参数、语言、回调。可重复调用以更新配置;若 generatorIdaccessoryType 发生变化,内部商品缓存会自动失效,下次 open() 重新拉取
open(options?)document.body 插入弹窗 DOM,并触发首次商品加载(耗材包 + IP 定位)。返回 Promise,初始化失败时会触发 onError。可传入 selectIds(默认选中并置顶)、hideIds(隐藏指定耗材)、quantities(按耗材预置初始数量)
close()播放退出动画后卸载弹窗 DOM。也可由用户点击蒙层 / X 按钮触发
destroy()强制卸载、清空内部缓存(如商品缓存),后续如需再次使用要先重新 init

open() 可被反复调用:每次打开都会重置 UI 状态(勾选、变体选择、滚动位置),但保留 init 设置和上一轮拉到的商品缓存(同站点不重复请求)。每次 openselectIds / hideIds / quantities 仅对本次生效,下次不传则恢复默认。

selectIds:默认选中并置顶指定耗材

accessoryId/community/v1/web/generator-accessory-pack 接口返回的每个耗材的 accessoryId 字段。

ts
// 仅选中并置顶 accessoryId 为 1001、1002 的耗材
GeneratorMaterialPurchase.open({ selectIds: [1001, 1002] })

// 不传或空数组 → 默认选中全部可购买耗材
GeneratorMaterialPurchase.open()

规则:

  • 置顶:命中的耗材按传入顺序整体排到列表最前(不受库存影响);
  • 选中:仅勾选命中且可购买的耗材,缺货项即使命中也不会被勾选(避免结算被拦截);
  • 切换站点:切换国家/站点重新加载后,selectIds 依然生效(置顶 + 选中);
  • 匹配方式:按 accessoryId 转字符串比较,传 numberstring 均可。

hideIds:隐藏指定耗材

ts
// 打开时不展示 accessoryId 为 2001、2002 的耗材
GeneratorMaterialPurchase.open({ hideIds: [2001, 2002] })

规则:

  • 过滤时机:在接口返回后立即按 accessoryId 过滤,命中的耗材不进入渲染、勾选、结算任何环节;
  • 优先级:与 selectIdsquantities 中的同一 accessoryId 冲突时,以 hideIds 为准;
  • 生效范围:仅对本次 open() 生效,下次不传则恢复完整列表;
  • 匹配方式:与 selectIds 一致,按 accessoryId 转字符串比较。

quantities:按耗材预置初始数量

对指定耗材设置弹窗打开时的初始数量,未指定的耗材保持默认值 1

ts
// 打开时 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)的商品行会显示「到货通知」按钮,initenableReplenishNotify: false 可关闭:

  • 点击后调用 /community/v1/web/subscribe/product/message,传当前站点 store、变体 variantIdinit 传入的 generatorId 与该商品的 supplySkuId(即 accessoryId);
  • 订阅成功后按钮切到「已订阅」态(勾图标 + 置灰),并 toast 提示到货后邮件通知;
  • 「已订阅」状态仅当次浏览器会话有效,刷新页面后重置(与后端订阅记录无关,仅是前端防重复点击);
  • 该接口需要登录态:未登录点击时 SDK 不发请求,触发 onRequireLogin 回调交由宿主唤起登录流程;宿主未配置该回调时仅 toast 提示「请先登录」。
ts
GeneratorMaterialPurchase.init({
  generatorId: 'light-sign',
  accessoryType: 'default',
  onRequireLogin: () => {
    // 唤起宿主自己的登录浮层 / 跳转登录页
    openLoginDialog()
  },
})

5. 配置项详解

ts
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
langlocalStorage.LANG_KEY,没有则默认 'en'

跨域部署时请保证:

  1. 宿主页面已经把 utoken 写入 cookie 或 localStorage(通常登录态已经完成);
  2. xcs-api.xtool.com 已经在 CORS 白名单里放行调用方域名,并允许携带 uToken / lang 自定义头。

如果出现 401 / 403,先排查 uToken 是否存在、是否过期、是否在跨域请求中被剥离。


7. 不同框架接入示例

7.1 原生 JS(<script src>

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',
    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)

vue
<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)

tsx
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)

vue
<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 自带 enzh 两套完整词条,覆盖:

  • 弹窗标题 / 关闭按钮 / 国家通知文案 / 加载中 / 空状态 / 售罄;
  • 合计 / 折扣提示 / 立即购买;
  • 各种错误 toast(加载失败、结算失败、缺货等);
  • 8 个站点的国家名(USA / Canada / Australia / EU / Germany / France / UK / Japan)。

通过 init({ locale: 'zh' }) 切换。

8.2 扩展或覆盖词条

ts
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:弹窗打开了但商品列表一直转圈 / 显示空

依次排查:

  1. 浏览器开发者工具 Network 看 /generator-accessory-pack 接口是否 200 且返回 code: 0
  2. 若是 401 / 403,确认 uToken 是否已写入 cookie 或 localStorage;
  3. 若是 CORS 报错,找后端把调用方域名加入白名单。

Q3:结算按钮置灰,怎么办

当所有勾选项都被识别为售罄(outOfStock === trueavailableForSale === false)时按钮会被置灰。换一个变体或换一个站点重试。

Q4:如何在当前页跳转而不是新开标签

传入 onCheckoutSuccess

ts
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. 打包产物说明

构建命令:

bash
pnpm build

输出到 dist/

文件用途体积(gzip)
index.es.jsESM 入口,给 npm / 现代打包工具~15 KB
index.umd.jsUMD,浏览器 <script src> 或 Node require~12 KB
index.d.tsTypeScript 声明文件(rollup 合并后的单文件)
*.mapsourcemap,定位运行时报错

发布到 npm 时 files 字段会带上:

  • dist/(产物)
  • README.md

12. 版本与变更

0.1.6

  • 新参数open() 新增 hideIds,命中的耗材直接从列表中过滤,不参与展示 / 勾选 / 结算;与 selectIdsquantities 冲突时以 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),默认开启,initenableReplenishNotify: 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 时列表不刷新(generatorIdaccessoryType 变更时自动清除商品缓存)
  • 移动端:屏幕宽度 ≤ 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,或在团队飞书群里 @ 维护者。

MIT Licensed