> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 概览

> Aftersell Cart SDK 的工作原理：全局入口点、API 的四个部分、加载时机，以及如何安全地对其运行代码。

**Cart SDK** 是用于店面上 Aftersell Cart 的 JavaScript API。它让你可以改变购物车的行为方式、响应购物者的操作，以及从代码中读取或更改购物车的内容。

你通过[自定义脚本](/zh/aftersell/cart/custom-scripts)运行 SDK 代码，或者通过[自定义代码区块](/zh/aftersell/cart/custom-code-blocks)的 React 模式来创建渲染自己 UI 的区块。

<Note>
  商家向 SDK 索求的很多功能已经是设置项。编写脚本之前，请先检查[购物车区块](/zh/aftersell/cart/blocks-overview)、[按市场/国家/货币的条件](/zh/aftersell/cart/blocks-overview#show-or-hide-by-market-country-or-currency)或[购物车设置](/zh/aftersell/cart/cart-settings)是否已经能做到。这些在购物车重新设计后仍能继续工作，而你的脚本可能不行。
</Note>

<div id="the-global-entry-point">
  ## 全局入口点
</div>

一切都挂在一个全局对象上：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart
```

<Note>
  **本文档中的每个代码片段都完整写出 `window.aftersell.cart`**，所以任何一个片段粘贴后都能独立工作。先起一次别名（`const cart = window.aftersell.cart;`）然后一直用 `cart` 也完全有效，即使在购物车加载之前也是安全的。只是缩短片段时记得带上那一行，因为单独的 `cart` 会抛出 `cart is not defined`。
</Note>

四个部分承担主要工作：

<Columns cols={2}>
  <Card title="配置" icon="sliders" href="/zh/aftersell/cart/sdk-configure">
    设定购物车的行为方式：抽屉何时打开、金额如何格式化、Aftersell 是否拦截加入购物车。
  </Card>

  <Card title="事件" icon="tower-broadcast" href="/zh/aftersell/cart/sdk-events">
    响应发生的事情：购物车已加载、添加了商品、抽屉打开了、点击了结账。
  </Card>

  <Card title="操作" icon="wand-magic-sparkles" href="/zh/aftersell/cart/sdk-actions">
    读取和更改购物车：打开它、添加商品、更新数量、读取当前状态。
  </Card>

  <Card title="Hooks" icon="plug" href="/zh/aftersell/cart/sdk-hooks">
    改变购物车本身的工作方式：隐藏或重新标记行、重新排序、附加额外数据、控制加入购物车。
  </Card>
</Columns>

<Note>
  如果你的某个脚本在加入购物车时不再触发，请从[加入购物车拦截](/zh/aftersell/cart/add-to-cart-interception)开始了解。它解释了 Aftersell 为什么接管添加，以及为表单退出的所有方式。
</Note>

外加三个较小的成员：

| 成员           | 用途                               |
| ------------ | -------------------------------- |
| `ready()`    | 一个在购物车首次加载完成后 resolve 的 Promise。 |
| `context`    | 服务器渲染的买家上下文，可同步读取。               |
| `shadowRoot` | 购物车的 shadow root，用于查询抽屉内的元素。     |

<div id="events-actions-or-hooks">
  ## 事件、操作还是 hooks？
</div>

这三者很容易混淆，而选错正是脚本没有达到作者预期的最常见原因：

| 你想要……           | 使用       | 示例                 |
| --------------- | -------- | ------------------ |
| 在*某事发生时*运行代码    | **事件**   | 添加商品时发送分析事件。       |
| *更改购物车里的*内容     | **操作**   | 总额超过 \$50 时添加免费赠品。 |
| 更改*购物车的工作或渲染方式* | **Hook** | 从抽屉中隐藏免费赠品行。       |

最重要的区别在于：**操作更改购物者的实际购物车**（以及其总额），而 **hook 只更改渲染的内容**。用 hook 隐藏一行，它仍留在购物车中并计入总额；用操作移除它才是真正拿出来。

<div id="how-and-when-it-loads">
  ## 加载方式与时机
</div>

购物车分两个阶段加载，SDK 的设计让你不必考虑顺序：

1. 一个小的**桩**会立即创建 `window.aftersell.cart`，所以它始终存在。
2. 完整的 SDK 随后很快加载并接管，原地升级这个桩，所以你之前捕获的引用会继续有效。

这给了你两类调用：

<Columns cols={2}>
  <Card title="设置类调用：立即可用" icon="circle-check">
    `configure(...)`、`events.on(...)` 以及每个 `hooks.register*` 调用。在启动前被缓冲，并在 SDK 加载后按顺序重放。把它们放在脚本顶部。
  </Card>

  <Card title="操作：等待 ready()" icon="clock">
    `actions.*` 下的一切。在 `ready()` 或事件处理函数内部运行它们。调用过早时它们会在控制台发出警告并安全地什么都不做：异步操作仍会 resolve，所以 `.then()` 链不会断。
  </Card>
</Columns>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Set-up: fine at the top level, before the cart has loaded.
window.aftersell.cart.configure({ open_on_add_to_cart: 'always' });

window.aftersell.cart.events.on('item_added', (payload) => {
  console.log('Added', payload.item.title);
});

// Actions: wait until the cart is ready.
window.aftersell.cart.ready().then(() => {
  const state = window.aftersell.cart.actions.getCart();
  console.log(state.itemCount, 'items');
});
```

<div id="ready">
  ### ready()
</div>

`ready()` 返回一个 Promise，在首次购物车加载**落定**时 resolve。失败和成功时它都会 resolve，所以网络不稳定的购物者永远不会让你的脚本悬挂。请检查 `getCart()` 是否为 `null`，而不要假设购物车一定到达了。

在购物车已加载后调用 `ready()` 会立即 resolve，所以可以在代码的任何地方安全地把它当作通用的"购物车现在存在了"闸门。

<Tip>
  在事件处理函数内部你不需要 `ready()`。当 `cart_loaded`、`cart_updated` 或 `item_added` 触发时，购物车已加载，可以安全地调用操作。
</Tip>

<div id="context">
  ## context
</div>

`window.aftersell.cart.context` 持有服务器渲染的买家数据，可同步读取，无需 `ready()`。用它来进行必须在购物车加载之前完成的市场或国家分支判断。

| 字段                        | 描述                       | 启动前可用            |
| ------------------------- | ------------------------ | ---------------- |
| `shopify_market`          | 买家的 Shopify 市场。          | 是                |
| `customer_country`        | 两位字母国家代码。                | 是                |
| `customer_currency`       | 生效的货币代码。                 | 是                |
| `money_format`            | 商店的 Shopify 货币格式。        | 是                |
| `backend_url`             | 直连后端主机，在应用代理未配置时用作回退。    | 是                |
| `storefront_access_token` | 用于 Storefront API 调用的令牌。 | **否**——在购物车启动时添加 |

<Warning>
  `storefront_access_token` 是服务器唯一不渲染进 `cart.context` 的 `context` 字段。它在购物车启动时才被添加到 `context`，所以在脚本顶部读取它会得到 `undefined`。请先 await `window.aftersell.cart.ready()`。
</Warning>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
if (window.aftersell.cart.context.customer_country === 'CA') {
  // Canada-only behavior, decided before the cart loads.
}
```

<Note>
  要按市场、国家或货币显示不同的区块设置，请改用[购物车编辑器中的条件](/zh/aftersell/cart/blocks-overview#show-or-hide-by-market-country-or-currency)。无需脚本。完整的 Conditions UI 目前已在 [Rewards](/zh/aftersell/cart/rewards-block#per-market-rewards) 上提供。
</Note>

<div id="shadowroot">
  ## shadowRoot
</div>

购物车渲染在 shadow root 内部，所以 `document.querySelector` **看不到抽屉内的任何东西**。要访问购物车中的元素，请查询 shadow root：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const root = window.aftersell.cart.shadowRoot;
const button = root?.querySelector('.cart-external-checkout-button');
```

请针对[自定义 CSS](/zh/aftersell/cart/custom-css) 使用的同一批**公开的 `cart-external-*` 类**。那些是受支持的把手。`cart-internal-*` 孪生类是购物车自身的内部机制，所以请查询外部类。

<Warning>
  只有在没有区块、设置或 hook 能完成任务时才使用 shadow root。Hook 能在购物车重新设计后存活；DOM 查询则是你的代码需要自己维护的问题。
</Warning>

Shadow root 只有在购物车启动后才存在，所以请在 `ready()` 或事件处理函数内部读取它，而不要在脚本顶部读取。

<div id="debugging">
  ## 调试
</div>

损坏的脚本绝不能搞垮加入购物车或抽屉，所以 SDK 会遏制失败而不是任其冒泡。失败在哪里显现取决于坏掉的是什么：

| 失败内容                                          | 显现位置                        |
| --------------------------------------------- | --------------------------- |
| 你的脚本在顶层抛出错误                                   | `console.error`，指明行号和未运行的内容 |
| [事件](/zh/aftersell/cart/sdk-events)处理函数抛出错误   | `console.error`；其他处理函数仍运行   |
| [Hook](/zh/aftersell/cart/sdk-hooks) 抛出错误     | 静默。进入下面的调试通道                |
| [操作](/zh/aftersell/cart/sdk-actions)在购物车加载前运行 | `console.warn`；调用什么都不做      |

<div id="when-your-script-throws">
  ### 脚本抛出错误时
</div>

自定义脚本**在第一个错误处停止**，所以该行以下的每个 `configure`、`events.on` 和 `hooks.register*` 都不会运行。购物车会明确指出：

```
[aftersell-cart] Initialization script error on line 12 — 4 more line(s) did not run;
any configure/events/hooks below are unregistered.
```

当你确定注册过的处理函数从不触发时，就要找这条消息：它可能根本没有被执行到。行号是执行停止处的顶层语句，而不是抛出错误的内层函数；如果浏览器的调用栈不可用，行号会被省略而不是猜测。

你的脚本还以各自的文件名运行，所以它们在 DevTools 中显示为 `aftersell-cart-init.js` 和 `aftersell-cart-cart-update.js`。你可以像其他任何文件一样从 Sources 面板打开它们并设置断点。

<div id="the-debug-channel">
  ### 调试通道
</div>

Hook 失败被有意地隐藏在控制台之外，这样购物者永远不会看到它们。它们改为进入这里：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// After reproducing the problem, inspect what was swallowed:
window.aftersellCartDebugEvents.filter((entry) => entry.level === 'ERROR');

// Or watch them live:
window.addEventListener('aftersell-cart-debug', (event) => console.log(event.detail));
```

<div id="where-to-go-next">
  ## 后续阅读
</div>

<Columns cols={2}>
  <Card title="配置" icon="sliders" href="/zh/aftersell/cart/sdk-configure">
    每个选项，各配一个示例。
  </Card>

  <Card title="事件" icon="tower-broadcast" href="/zh/aftersell/cart/sdk-events">
    每个事件、触发时机，以及在处理函数中不该做什么。
  </Card>

  <Card title="操作" icon="wand-magic-sparkles" href="/zh/aftersell/cart/sdk-actions">
    每个操作，各配一个代码片段。
  </Card>

  <Card title="Hooks" icon="plug" href="/zh/aftersell/cart/sdk-hooks">
    每个 hook，以及注册如何组合。
  </Card>

  <Card title="购物车对象" icon="table-list" href="/zh/aftersell/cart/sdk-cart-object">
    购物车及其行的结构。
  </Card>

  <Card title="使用案例" icon="book-open" href="/zh/aftersell/cart/sdk-use-cases">
    常见需求的完整可运行解决方案。
  </Card>
</Columns>
