@atomm-developer/generator-material-purchase 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/dist/README.md +424 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.es.js +1925 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @atomm-developer/generator-material-purchase
|
|
2
|
+
|
|
3
|
+
Atomm Generator 平台的耗材购买弹窗 SDK,框架无关,支持 Vue / React / 原生 JS 项目通过 `<script src>` 或 `import` 引入。
|
|
4
|
+
|
|
5
|
+
> **完整接入文档**:[docs/USAGE.md](./docs/USAGE.md)
|
|
6
|
+
|
|
7
|
+
## 安装与使用
|
|
8
|
+
|
|
9
|
+
### CDN(UMD)
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-material-purchase/index.umd.js"></script>
|
|
13
|
+
<script>
|
|
14
|
+
GeneratorMaterialPurchase.init({
|
|
15
|
+
generatorId: 'light-sign',
|
|
16
|
+
accessoryType: 'default',
|
|
17
|
+
});
|
|
18
|
+
document.querySelector('#buyBtn').addEventListener('click', () => {
|
|
19
|
+
GeneratorMaterialPurchase.open();
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### ESM / npm
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import GeneratorMaterialPurchase from '@atomm-developer/generator-material-purchase';
|
|
28
|
+
|
|
29
|
+
GeneratorMaterialPurchase.init({
|
|
30
|
+
generatorId: 'light-sign',
|
|
31
|
+
accessoryType: 'default',
|
|
32
|
+
});
|
|
33
|
+
GeneratorMaterialPurchase.open();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
init(options: {
|
|
40
|
+
generatorId: string; // 必填
|
|
41
|
+
accessoryType: string; // 必填
|
|
42
|
+
locale?: 'en' | 'zh'; // 默认 en
|
|
43
|
+
zIndex?: number; // 默认 9999
|
|
44
|
+
apiBaseUrl?: string; // 默认 https://xcs-api.xtool.com
|
|
45
|
+
messages?: Record<string, Record<string, string>>;
|
|
46
|
+
onCheckoutSuccess?(url): void;
|
|
47
|
+
onClose?(): void;
|
|
48
|
+
onError?(err): void;
|
|
49
|
+
}): void;
|
|
50
|
+
|
|
51
|
+
open(): Promise<void>;
|
|
52
|
+
close(): void;
|
|
53
|
+
destroy(): void;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`open()` 后弹窗会被插入 `document.body`,固定右侧滑入,宽 320px、满屏高度、带半透明蒙层,点击蒙层或右上角 X 关闭。
|
|
57
|
+
|
|
58
|
+
## 鉴权
|
|
59
|
+
|
|
60
|
+
每次请求都会带上下面两个 header:
|
|
61
|
+
|
|
62
|
+
- `uToken`:从 `document.cookie.utoken` 或 `localStorage.utoken` 读取
|
|
63
|
+
- `lang`:从 `localStorage.LANG_KEY` 读取,默认 `en`
|
|
64
|
+
|
|
65
|
+
## 开发
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 在子目录内
|
|
69
|
+
pnpm install
|
|
70
|
+
pnpm dev # 启动 demo(http://localhost:5174)
|
|
71
|
+
pnpm build # 构建 dist
|
|
72
|
+
|
|
73
|
+
# 或在 generator-sdk 根目录
|
|
74
|
+
pnpm dev:material-purchase
|
|
75
|
+
pnpm build:material-purchase # 同时把 index.umd.js 复制到 docs/public
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Shopify 站点
|
|
79
|
+
|
|
80
|
+
SDK 内置 8 个 prod 站点(US/CA/EU/UK/FR/DE/JP/AU)的 `shopDomain` 与 Storefront Token。当前站点选择:优先读 `localStorage.xtool_current_shop_name`,否则调用 `/community/v2/web/ip/location` 探测 IP。
|
package/dist/README.md
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Generator Material Purchase — 接入指南
|
|
2
|
+
|
|
3
|
+
你现在拿到的是一个**框架无关的耗材购买弹窗 SDK**。把它丢到任何前端项目里(Vue / React / Angular / 原生 HTML),通过两个方法即可在页面右侧弹出一个 320px 宽、满屏高度、带蒙层的购物面板,用户可以挑选耗材并跳转 Shopify 完成支付。
|
|
4
|
+
|
|
5
|
+
> 这份文档同时面向**人类开发者**和 **AI 编程助手**。如果你想把接入工作交给 AI,请把整份文档一起喂给它([第 11 章](#11-给-ai-编程助手的接入提示词) 提供了现成提示词模板)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目录
|
|
10
|
+
|
|
11
|
+
1. [包内文件清单](#1-包内文件清单)
|
|
12
|
+
2. [60 秒接入(最快路径)](#2-60-秒接入最快路径)
|
|
13
|
+
3. [对外 API](#3-对外-api)
|
|
14
|
+
4. [配置项详解](#4-配置项详解)
|
|
15
|
+
5. [鉴权](#5-鉴权)
|
|
16
|
+
6. [按框架接入示例](#6-按框架接入示例)
|
|
17
|
+
7. [国际化(i18n)](#7-国际化i18n)
|
|
18
|
+
8. [视觉规格](#8-视觉规格)
|
|
19
|
+
9. [事件回调](#9-事件回调)
|
|
20
|
+
10. [常见问题(FAQ)](#10-常见问题faq)
|
|
21
|
+
11. [给 AI 编程助手的接入提示词](#11-给-ai-编程助手的接入提示词)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. 包内文件清单
|
|
26
|
+
|
|
27
|
+
把整个 `dist/` 目录拷到你项目里(一般放 `public/` 或 `assets/` 下),结构如下:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
dist/
|
|
31
|
+
├── index.es.js # ESM 入口(给 Vite / Webpack / Rollup)
|
|
32
|
+
├── index.umd.js # UMD 入口(给浏览器 <script src> 或 Node require)
|
|
33
|
+
├── index.*.map # sourcemap,调试用,可选保留
|
|
34
|
+
├── index.d.ts # TypeScript 声明文件
|
|
35
|
+
└── README.md # 本文档
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
挑选规则:
|
|
39
|
+
|
|
40
|
+
| 你的项目类型 | 用哪个文件 |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| 纯 HTML / 静态站 | `index.umd.js` |
|
|
43
|
+
| 现代打包工具(Vite/Webpack)+ 需要 `import` | `index.es.js`(ESM)|
|
|
44
|
+
| 老项目用 `require()` | `index.umd.js` |
|
|
45
|
+
|
|
46
|
+
> UMD 加载后会在 `window` 上挂一个全局对象 `GeneratorMaterialPurchase`。ESM 通过 `import` 拿到默认导出。
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 2. 60 秒接入(最快路径)
|
|
51
|
+
|
|
52
|
+
复制下面这段 HTML 改两个参数就能跑:
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<!doctype html>
|
|
56
|
+
<html>
|
|
57
|
+
<head>
|
|
58
|
+
<meta charset="UTF-8" />
|
|
59
|
+
<title>Purchase Modal Demo</title>
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<button id="buyBtn">购买耗材</button>
|
|
63
|
+
|
|
64
|
+
<!-- 1. 引入 SDK:路径替换为你自己放置 dist 文件的位置 -->
|
|
65
|
+
<script src="/path/to/dist/index.umd.js"></script>
|
|
66
|
+
|
|
67
|
+
<script>
|
|
68
|
+
// 2. 初始化(必须最先调用,且只需调一次)
|
|
69
|
+
GeneratorMaterialPurchase.init({
|
|
70
|
+
generatorId: 'light-sign', // 改成你的 generator code
|
|
71
|
+
accessoryType: 'default', // 通常保持 'default'
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// 3. 在任意时机调用 open() 打开弹窗
|
|
75
|
+
document.getElementById('buyBtn').onclick = () => {
|
|
76
|
+
GeneratorMaterialPurchase.open()
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
79
|
+
</body>
|
|
80
|
+
</html>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
启动后点按钮 → 弹窗从右侧滑入 → 显示耗材列表 → 用户勾选 → 点 "Buy now" → 自动跳转 Shopify 结算页。
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 3. 对外 API
|
|
88
|
+
|
|
89
|
+
SDK 只暴露 4 个方法:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
GeneratorMaterialPurchase.init(options) // 设置参数(先调用一次)
|
|
93
|
+
GeneratorMaterialPurchase.open() // 打开弹窗
|
|
94
|
+
GeneratorMaterialPurchase.close() // 关闭弹窗(也可由用户点蒙层 / X 关闭)
|
|
95
|
+
GeneratorMaterialPurchase.destroy() // 销毁实例并清空内部缓存
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| 方法 | 时机 | 备注 |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| `init(options)` | 应用启动 / 组件挂载 | 必须最先调;可重复调以更新配置 |
|
|
101
|
+
| `open()` | 用户点击 "购买" 时 | 返回 `Promise<void>`;初始化失败会触发 `onError` |
|
|
102
|
+
| `close()` | 通常不需要手动调 | 弹窗内部点 X 或蒙层都会自动调 |
|
|
103
|
+
| `destroy()` | 组件卸载 / 路由切换 | 可选,但推荐在 SPA 中调用以释放资源 |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 4. 配置项详解
|
|
108
|
+
|
|
109
|
+
`init(options)` 接受以下参数:
|
|
110
|
+
|
|
111
|
+
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
|
112
|
+
| --- | --- | --- | --- | --- |
|
|
113
|
+
| `generatorId` | `string` | ✅ | — | 生成器标识,决定后端拉哪个生成器的耗材包 |
|
|
114
|
+
| `accessoryType` | `string` | ✅ | — | 耗材类型,目前线上统一传 `'default'` |
|
|
115
|
+
| `locale` | `'en' \| 'zh'` | ❌ | `'en'` | 弹窗语言 |
|
|
116
|
+
| `zIndex` | `number` | ❌ | `9999` | 当宿主页面有更高层级 UI 时调高 |
|
|
117
|
+
| `apiBaseUrl` | `string` | ❌ | `'https://xcs-api.xtool.com'` | 后端 API 基础地址,一般不用改 |
|
|
118
|
+
| `messages` | `Record<string, Record<string, string>>` | ❌ | — | 覆盖/扩展词条,详见第 7 章 |
|
|
119
|
+
| `onCheckoutSuccess` | `(url: string) => void` | ❌ | `window.open(url, '_blank')` | 拿到 checkoutUrl 后的行为 |
|
|
120
|
+
| `onClose` | `() => void` | ❌ | — | 弹窗关闭时触发 |
|
|
121
|
+
| `onError` | `(err: unknown) => void` | ❌ | — | 接口失败 / 结算失败时触发;SDK 内部会自带 toast,业务方不必再 UI 反馈 |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 5. 鉴权
|
|
126
|
+
|
|
127
|
+
SDK 调用后端时会**自动**在请求头注入:
|
|
128
|
+
|
|
129
|
+
| Header | 来源 |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `uToken` | 优先读 `document.cookie.utoken`,没有则读 `localStorage.utoken` |
|
|
132
|
+
| `lang` | 读 `localStorage.LANG_KEY`,没有则默认 `'en'` |
|
|
133
|
+
|
|
134
|
+
> 业务方需要保证宿主页面已经把 `utoken` 写入 cookie 或 localStorage(通常登录态完成后已经写入)。
|
|
135
|
+
>
|
|
136
|
+
> 跨域部署时,请确认 `xcs-api.xtool.com` 已允许调用方域名跨域,并允许携带 `uToken / lang` 自定义请求头。
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 6. 按框架接入示例
|
|
141
|
+
|
|
142
|
+
### 6.1 原生 HTML / Vanilla JS
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<script src="/path/to/dist/index.umd.js"></script>
|
|
146
|
+
<script>
|
|
147
|
+
GeneratorMaterialPurchase.init({
|
|
148
|
+
generatorId: 'light-sign',
|
|
149
|
+
accessoryType: 'default',
|
|
150
|
+
locale: 'zh',
|
|
151
|
+
onCheckoutSuccess: (url) => (location.href = url), // 当前页跳转而非新开标签
|
|
152
|
+
onClose: () => console.log('modal closed'),
|
|
153
|
+
})
|
|
154
|
+
document.querySelector('#buyBtn').onclick = () => GeneratorMaterialPurchase.open()
|
|
155
|
+
</script>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 6.2 Vue 3(Composition API)
|
|
159
|
+
|
|
160
|
+
如果使用打包工具(Vite/Webpack),把 `index.es.js` 放在项目某处(例如 `src/lib/purchase-modal/`),然后:
|
|
161
|
+
|
|
162
|
+
```vue
|
|
163
|
+
<script setup lang="ts">
|
|
164
|
+
import { onMounted, onBeforeUnmount } from 'vue'
|
|
165
|
+
import GeneratorMaterialPurchase from '@/lib/purchase-modal/index.es.js'
|
|
166
|
+
|
|
167
|
+
onMounted(() => {
|
|
168
|
+
GeneratorMaterialPurchase.init({
|
|
169
|
+
generatorId: 'light-sign',
|
|
170
|
+
accessoryType: 'default',
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
onBeforeUnmount(() => GeneratorMaterialPurchase.destroy())
|
|
174
|
+
|
|
175
|
+
function handleBuy() {
|
|
176
|
+
GeneratorMaterialPurchase.open()
|
|
177
|
+
}
|
|
178
|
+
</script>
|
|
179
|
+
|
|
180
|
+
<template>
|
|
181
|
+
<button @click="handleBuy">购买耗材</button>
|
|
182
|
+
</template>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
如果你的项目用 `<script src>` 引入(如某些 SSR 模板),改成在 mounted 里访问 `window.GeneratorMaterialPurchase` 即可。
|
|
186
|
+
|
|
187
|
+
### 6.3 React
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { useEffect } from 'react'
|
|
191
|
+
import GeneratorMaterialPurchase from './lib/purchase-modal/index.es.js'
|
|
192
|
+
|
|
193
|
+
export function BuyButton() {
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
GeneratorMaterialPurchase.init({
|
|
196
|
+
generatorId: 'flower-generator',
|
|
197
|
+
accessoryType: 'default',
|
|
198
|
+
})
|
|
199
|
+
return () => GeneratorMaterialPurchase.destroy()
|
|
200
|
+
}, [])
|
|
201
|
+
|
|
202
|
+
return <button onClick={() => GeneratorMaterialPurchase.open()}>Buy accessories</button>
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 6.4 Vue 2(Options API)
|
|
207
|
+
|
|
208
|
+
```vue
|
|
209
|
+
<script>
|
|
210
|
+
import GeneratorMaterialPurchase from '@/lib/purchase-modal/index.es.js'
|
|
211
|
+
|
|
212
|
+
export default {
|
|
213
|
+
mounted() {
|
|
214
|
+
GeneratorMaterialPurchase.init({
|
|
215
|
+
generatorId: 'light-sign',
|
|
216
|
+
accessoryType: 'default',
|
|
217
|
+
})
|
|
218
|
+
},
|
|
219
|
+
beforeDestroy() {
|
|
220
|
+
GeneratorMaterialPurchase.destroy()
|
|
221
|
+
},
|
|
222
|
+
methods: {
|
|
223
|
+
open() {
|
|
224
|
+
GeneratorMaterialPurchase.open()
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<template>
|
|
231
|
+
<button @click="open">购买耗材</button>
|
|
232
|
+
</template>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 6.5 ESM 工程的另一种集成方式(推荐)
|
|
236
|
+
|
|
237
|
+
不想把 dist 文件复制进 src,可以放在 `public/` 或独立 CDN:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
// 动态加载 UMD,从而无关打包工具
|
|
241
|
+
function loadSdk(): Promise<any> {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
if ((window as any).GeneratorMaterialPurchase) return resolve((window as any).GeneratorMaterialPurchase)
|
|
244
|
+
const s = document.createElement('script')
|
|
245
|
+
s.src = '/sdk/index.umd.js'
|
|
246
|
+
s.onload = () => resolve((window as any).GeneratorMaterialPurchase)
|
|
247
|
+
s.onerror = reject
|
|
248
|
+
document.head.appendChild(s)
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const sdk = await loadSdk()
|
|
253
|
+
sdk.init({ generatorId: 'light-sign', accessoryType: 'default' })
|
|
254
|
+
sdk.open()
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 7. 国际化(i18n)
|
|
260
|
+
|
|
261
|
+
内置 `en` / `zh` 两套完整词条,用 `init({ locale: 'zh' })` 切换。
|
|
262
|
+
|
|
263
|
+
### 7.1 覆盖某条 / 新增语言
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
GeneratorMaterialPurchase.init({
|
|
267
|
+
generatorId: 'light-sign',
|
|
268
|
+
accessoryType: 'default',
|
|
269
|
+
locale: 'en',
|
|
270
|
+
messages: {
|
|
271
|
+
en: { buy_now: 'Checkout securely' }, // 覆盖
|
|
272
|
+
ja: { shopify_supplies_kit: '消耗品キット' }, // 新增日语;未提供的 key 会 fallback 到 en
|
|
273
|
+
},
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### 7.2 词条键名一览
|
|
278
|
+
|
|
279
|
+
| key | 含义 |
|
|
280
|
+
| --- | --- |
|
|
281
|
+
| `shopify_supplies_kit` | 弹窗标题 |
|
|
282
|
+
| `close` | 关闭按钮 aria-label |
|
|
283
|
+
| `current_country_notice` | 国家提示,模板含 `{country}` |
|
|
284
|
+
| `loading` | 加载文案 |
|
|
285
|
+
| `no_items_in_cart` | 空商品状态 |
|
|
286
|
+
| `sold_out` | 售罄角标 |
|
|
287
|
+
| `total` | 合计标签 |
|
|
288
|
+
| `discount_notice` | 折扣码提示 |
|
|
289
|
+
| `buy_now` | 立即购买按钮 |
|
|
290
|
+
| `failed_load_product_list` | 拉取失败 toast |
|
|
291
|
+
| `checkout_failed` | 结算失败 toast |
|
|
292
|
+
| `selected_items_out_of_stock` | 勾选项已售罄 toast |
|
|
293
|
+
| `no_valid_products_selected` | 无可购买商品 toast |
|
|
294
|
+
| `store_us` / `store_ca` / `store_au` / `store_eu` / `store_de` / `store_fr` / `store_uk` / `store_jp` | 站点下拉短名 |
|
|
295
|
+
| `country_usa` / `country_canada` / `country_australia` / `country_eu` / `country_germany` / `country_france` / `country_uk` / `country_japan` | 国家长名 |
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## 8. 视觉规格
|
|
300
|
+
|
|
301
|
+
| 维度 | 规格 |
|
|
302
|
+
| --- | --- |
|
|
303
|
+
| 挂载方式 | `document.body.appendChild(host)`,host 使用 **Shadow DOM** 隔离样式 |
|
|
304
|
+
| 蒙层 | `position: fixed; inset: 0; background: rgba(0,0,0,0.4);`,点击关闭 |
|
|
305
|
+
| 弹窗 | `position: fixed; top: 0; right: 0; width: 320px; height: 100vh;` |
|
|
306
|
+
| 动画 | 蒙层 0.2s 透明度淡入,弹窗 0.25s `translateX(100% → 0)` |
|
|
307
|
+
| 主色 | 购买按钮 `#ff0035`,强调 `#070b10`,提示橙 `#ff7c23` |
|
|
308
|
+
| 字体 | `Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif` |
|
|
309
|
+
| CSS 命名 | BEM 风格,前缀 `xpm-` |
|
|
310
|
+
|
|
311
|
+
由于使用 Shadow DOM,宿主页面的 CSS reset / Tailwind / 全局样式都不会污染弹窗内部;同时弹窗内部样式也不会泄漏到宿主页面。
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 9. 事件回调
|
|
316
|
+
|
|
317
|
+
如果你需要在用户结算或关闭时做埋点 / 业务联动:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
GeneratorMaterialPurchase.init({
|
|
321
|
+
generatorId: 'light-sign',
|
|
322
|
+
accessoryType: 'default',
|
|
323
|
+
onCheckoutSuccess: (url) => {
|
|
324
|
+
// 上报埋点 + 跳转
|
|
325
|
+
track('purchase_modal_checkout', { url })
|
|
326
|
+
location.href = url
|
|
327
|
+
},
|
|
328
|
+
onClose: () => track('purchase_modal_close'),
|
|
329
|
+
onError: (err) => track('purchase_modal_error', { err: String(err) }),
|
|
330
|
+
})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## 10. 常见问题(FAQ)
|
|
336
|
+
|
|
337
|
+
### Q1:调用 `open()` 报错 "call init() before open()"
|
|
338
|
+
没有先调 `init()`。在挂载阶段(Vue `onMounted` / React `useEffect`)调用一次 `init` 即可。
|
|
339
|
+
|
|
340
|
+
### Q2:弹窗打开了但商品列表一直转圈 / 显示空
|
|
341
|
+
依次排查:
|
|
342
|
+
1. 浏览器 DevTools → Network 看 `/generator-accessory-pack` 接口是否 200 且返回 `code: 0`;
|
|
343
|
+
2. 401 / 403 → 确认 `utoken` 是否已写入 cookie 或 localStorage;
|
|
344
|
+
3. CORS 报错 → 找后端把调用方域名加入白名单。
|
|
345
|
+
|
|
346
|
+
### Q3:结算按钮置灰
|
|
347
|
+
当所有勾选项都被识别为售罄时按钮置灰。换变体或换站点重试。
|
|
348
|
+
|
|
349
|
+
### Q4:如何在当前页跳转而不是新开标签
|
|
350
|
+
```ts
|
|
351
|
+
GeneratorMaterialPurchase.init({
|
|
352
|
+
generatorId: '...',
|
|
353
|
+
accessoryType: 'default',
|
|
354
|
+
onCheckoutSuccess: (url) => (window.location.href = url),
|
|
355
|
+
})
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Q5:切换语言后弹窗没刷新
|
|
359
|
+
若弹窗当前正打开,先 `close()` 再 `init({ locale: 'xx' })` 再 `open()`。
|
|
360
|
+
|
|
361
|
+
### Q6:会污染全局变量吗
|
|
362
|
+
UMD 模式仅在 `window` 挂一个 `GeneratorMaterialPurchase`;不会修改 `body` 的样式;只在弹窗存在期间向 `body` 追加一个 host 元素,`close/destroy` 后移除。
|
|
363
|
+
|
|
364
|
+
### Q7:Shadow DOM 调试不便
|
|
365
|
+
Chrome DevTools → Settings → Preferences → Elements → 勾选 "Show user agent shadow DOM"。也可以直接在 Console 里 `window.GeneratorMaterialPurchase.open()`。
|
|
366
|
+
|
|
367
|
+
### Q8:可以同时打开多个弹窗吗
|
|
368
|
+
不可以。SDK 是单例,第二次 `open()` 会等第一次的退出动画完成才挂载。
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 11. 给 AI 编程助手的接入提示词
|
|
373
|
+
|
|
374
|
+
如果你想把接入工作交给 Claude / Cursor / Copilot / 其他 AI 助手,把下面这段提示词连同**本文档全文**一起喂给它即可:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
你是一个前端集成专家。我需要你帮我把一个第三方弹窗 SDK
|
|
378
|
+
(Purchase Modal SDK)集成到我的项目里。
|
|
379
|
+
|
|
380
|
+
【SDK 文档】
|
|
381
|
+
{此处粘贴本 README 全文}
|
|
382
|
+
|
|
383
|
+
【我的项目信息】
|
|
384
|
+
- 框架:{Vue 3 / Vue 2 / React / Angular / 纯 HTML,二选一}
|
|
385
|
+
- 构建工具:{Vite / Webpack / 无}
|
|
386
|
+
- TypeScript:{是 / 否}
|
|
387
|
+
- dist 文件存放路径:{例如 public/sdk/ 或 src/lib/purchase-modal/}
|
|
388
|
+
- 触发弹窗的入口:{例如 "右上角购买按钮" / "商品详情页 CTA"}
|
|
389
|
+
- generatorId:{填入你的 generator code,例如 light-sign}
|
|
390
|
+
- accessoryType:{通常填 default}
|
|
391
|
+
- 期望的语言:{en / zh}
|
|
392
|
+
- 结算后行为:{新开标签 / 当前页跳转}
|
|
393
|
+
|
|
394
|
+
【你需要做的事】
|
|
395
|
+
1. 告诉我应该把 dist 里哪个文件放到哪个位置;
|
|
396
|
+
2. 给出完整的接入代码(包含 init / open / 卸载清理);
|
|
397
|
+
3. 如果是 TypeScript 项目,给出类型声明的引入方式;
|
|
398
|
+
4. 给出一段可以在浏览器 Console 里手动测试 open/close 的代码片段;
|
|
399
|
+
5. 列出我接入后应该如何自测(接口是否通、鉴权是否生效、视觉是否正确)。
|
|
400
|
+
|
|
401
|
+
【限制】
|
|
402
|
+
- 不要修改 SDK 本身,只做接入。
|
|
403
|
+
- 不要引入额外的 UI 库。
|
|
404
|
+
- 不要把弹窗封装成新的组件(直接调用 SDK 即可)。
|
|
405
|
+
- 优先用项目已有的依赖完成。
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
把上面的 `{...}` 占位符替换成你项目的真实信息,然后把整段(包括本 README)一次性发给 AI 助手即可。
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## 附:最简自测清单
|
|
413
|
+
|
|
414
|
+
接入后跑一遍下面的清单:
|
|
415
|
+
|
|
416
|
+
- [ ] 页面加载后 `console.log(window.GeneratorMaterialPurchase)` 能拿到对象(UMD 引入方式下);
|
|
417
|
+
- [ ] 点击按钮 → 弹窗从右侧滑入,宽 320px、撑满高度;
|
|
418
|
+
- [ ] 弹窗外有半透明蒙层,点蒙层能关闭;
|
|
419
|
+
- [ ] 商品列表能正常加载,DevTools → Network 看 `generator-accessory-pack` 返回 `code: 0`;
|
|
420
|
+
- [ ] 切换站点(右上角下拉)→ 列表会重新拉取;
|
|
421
|
+
- [ ] 勾选商品 + 调整数量 → 底部合计金额变化正确;
|
|
422
|
+
- [ ] 点 "Buy now" → 浏览器跳转到 Shopify checkout 页(或触发了你自定义的 `onCheckoutSuccess`)。
|
|
423
|
+
|
|
424
|
+
完成 ✅。
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
declare const api: GeneratorMaterialPurchaseApi;
|
|
2
|
+
export { api as GeneratorMaterialPurchase }
|
|
3
|
+
export default api;
|
|
4
|
+
|
|
5
|
+
export declare interface ChangeEventData {
|
|
6
|
+
uid: string;
|
|
7
|
+
[key: string]: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare interface GeneratorMaterialPurchaseApi {
|
|
11
|
+
init(options: PurchaseModalInitOptions): void;
|
|
12
|
+
open(): Promise<void>;
|
|
13
|
+
close(): void;
|
|
14
|
+
destroy(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export declare type Locale = 'en' | 'zh' | string;
|
|
18
|
+
|
|
19
|
+
export declare interface ProductDataType {
|
|
20
|
+
uid: string;
|
|
21
|
+
id: number | string;
|
|
22
|
+
title?: string;
|
|
23
|
+
image?: string;
|
|
24
|
+
quantity?: number;
|
|
25
|
+
defaultVariant?: number | string;
|
|
26
|
+
options?: ProductOptionType[];
|
|
27
|
+
variants?: ProductVariantType[];
|
|
28
|
+
displayName?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export declare interface ProductOptionType {
|
|
32
|
+
key?: string;
|
|
33
|
+
value?: string[];
|
|
34
|
+
values?: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export declare interface ProductVariantType {
|
|
38
|
+
id?: number | string;
|
|
39
|
+
title?: string;
|
|
40
|
+
image?: string;
|
|
41
|
+
compareAtPrice?: string;
|
|
42
|
+
price?: string;
|
|
43
|
+
isBind?: boolean;
|
|
44
|
+
availableForSale?: boolean;
|
|
45
|
+
outOfStock?: boolean;
|
|
46
|
+
storeUrl?: string;
|
|
47
|
+
option?: Record<string, string>;
|
|
48
|
+
displayPrice?: string;
|
|
49
|
+
displayCompareAtPrice?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export declare interface PurchaseModalInitOptions {
|
|
53
|
+
/** 必填:生成器编码,例如 'light-sign'、'flower' 等 */
|
|
54
|
+
generatorId: string;
|
|
55
|
+
/** 必填:耗材类型,例如 'default' */
|
|
56
|
+
accessoryType: string;
|
|
57
|
+
/** 可选:默认语言,'en' | 'zh',默认 'en' */
|
|
58
|
+
locale?: Locale;
|
|
59
|
+
/** 可选:z-index,默认 9999 */
|
|
60
|
+
zIndex?: number;
|
|
61
|
+
/** 可选:覆盖默认 prod apiBaseUrl */
|
|
62
|
+
apiBaseUrl?: string;
|
|
63
|
+
/** 可选:覆盖词条(深合并) */
|
|
64
|
+
messages?: Partial<Record<Locale, Record<string, string>>>;
|
|
65
|
+
/** 可选:结算完成时回调,默认 window.open(checkoutUrl, '_blank') */
|
|
66
|
+
onCheckoutSuccess?: (checkoutUrl: string) => void;
|
|
67
|
+
/** 可选:关闭回调 */
|
|
68
|
+
onClose?: () => void;
|
|
69
|
+
/** 可选:错误回调 */
|
|
70
|
+
onError?: (err: unknown) => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export declare interface StoreOption {
|
|
74
|
+
label: string;
|
|
75
|
+
value: string;
|
|
76
|
+
icon: string;
|
|
77
|
+
shopDomain: string;
|
|
78
|
+
storeFrontAccessToken: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { }
|