> ## 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.

# 在购物车行项目上显示 metafield 数据

> 一个 Cart SDK 用例：使用 registerCartEnricher 从 Shopify Storefront API 拉取 metafields 并附加到每个购物车行项目，然后进行渲染。

购物车只知道 Shopify Ajax API 告诉它的内容：标题、价格、数量、属性。它并不知道你的 metafields。\*\*cart enricher（购物车增强器）\*\*可以从 Storefront API 获取额外的产品或变体字段，并将其附加到每个匹配的行项目上，这样你就可以显示配送时间预估、"单独发货"提示、成分列表，或你存储在产品上的任何其他信息。

典型用途：按产品显示配送时间窗口、过敏原或成分徽章、自定义"库存不足"标记、积分倍数、订阅者专属价格。

<div id="register-the-enricher">
  ## 注册 enricher
</div>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'delivery',
  onType: 'Product',
  fragment: `
    deliveryWindow: metafield(namespace: "custom", key: "delivery_window") { value }
    shipsSeparately: metafield(namespace: "custom", key: "ships_separately") { value }
  `,
});
```

三个字段：

| 字段         | 说明                                                           |
| ---------- | ------------------------------------------------------------ |
| `id`       | 结果的命名空间；数据会落在 `line.metadata[id]`。必须唯一；使用相同 `id` 的第二次注册会被忽略。 |
| `onType`   | `'Product'` 或 `'ProductVariant'`。同时也是关联键，即产品 ID 或变体 ID。      |
| `fragment` | 一段 GraphQL 字段选择（不带外层花括号），会被拼接进 Storefront 查询。花括号必须配对。        |

每当购物车加载或发生变化时，Aftersell 会为购物车中的每个产品或变体获取你的 fragment。这个获取是**非阻塞的**：购物车会立即渲染，并在数据到达后重新触发 `cart_updated`。缓慢或失败的 fragment 永远不会延迟或破坏购物车。

<div id="read-the-data">
  ## 读取数据
</div>

由于数据增强是异步的，在第一次获取完成之前 `line.metadata.delivery` 是 `undefined`。读取时务必加保护判断。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    const deliveryWindow = line.metadata.delivery?.deliveryWindow?.value;
    if (!deliveryWindow) return; // not fetched yet

    console.log(line.title, 'arrives in', deliveryWindow);
  });
});
```

<div id="render-it-in-the-cart">
  ## 在购物车中渲染
</div>

数据增强把数据放到行项目上；用 React 模式的[自定义代码块](/zh/aftersell/cart/custom-code-blocks)来绘制它。将该块添加为 **Cart items 子块**，这样它会为每个行项目渲染一次，并通过 `props.line` 接收该行项目：

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomCode(props) {
  const data = props.line?.metadata?.delivery;
  const window_ = data?.deliveryWindow?.value;

  if (!window_) return null;

  return (
    <div className="cart-external-custom-code_html" style={{ fontSize: '12px', opacity: 0.7 }}>
      Arrives in {window_}
    </div>
  );
}
```

<Warning>
  增强后的值从 Storefront API **原样返回且未经清理**。请像上面那样以文本形式渲染，绝不要使用 `dangerouslySetInnerHTML` 或写入原始 HTML。
</Warning>

<div id="variant-level-data">
  ## 变体级数据
</div>

当 metafield 存放在变体而不是产品上时，将 `onType` 设为 `'ProductVariant'`：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'pricing',
  onType: 'ProductVariant',
  fragment: `
    anchorPrice: metafield(namespace: "custom", key: "anchor_price") { value }
    subscriberPrice: metafield(namespace: "custom", key: "subscriber_price") { value }
  `,
});
```

<div id="more-than-metafields">
  ## 不止于 metafields
</div>

fragment 会被拼接进 Storefront API 查询，因此 API 在 `Product` 或 `ProductVariant` 上暴露的任何内容都可以用，不仅仅是 metafields：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'productInfo',
  onType: 'Product',
  fragment: `
    tags
    vendor
    productType
    availableForSale
  `,
});
```

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Then: warn about anything from a drop-ship vendor.
window.aftersell.cart.events.on('cart_updated', (state) => {
  const hasDropship = state.items.some((line) => {
    const tags = line.metadata.productInfo?.tags;
    return tags?.includes('dropship');
  });
});
```

<div id="things-to-get-right">
  ## 需要注意的要点
</div>

* **每次读取都要加保护。** `metadata` 默认是 `{}`，在获取完成之前你的命名空间是 `undefined`。购物车总是在数据到达之前渲染。
* **每个 `id` 都是独立的命名空间。** 多个 enricher 可以共存而不冲突，包括其他应用注册的。
* **Metafields 必须对 Storefront 可见。** 未对 Storefront API 开放的 metafield 会返回 `null`。如果什么都拿不到，请在 Shopify 后台检查其定义。
* **保持 fragment 精简。** 它会在每次购物车变化时为购物车中的每个产品运行。只请求你要用的字段，而不是所有字段。
* **在初始化阶段注册。** 它是一个 hook，所以应放在 Initialization 脚本的开头。
* **花括号必须配对。** fragment 外层不加花括号，但任何嵌套选择都需要自己配对的括号。不配对的 fragment 会被拒绝。

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

* **[`registerCartEnricher`](/zh/aftersell/cart/sdk-hooks#registercartenricher)**：完整的 hook 参考。
* **[自定义代码块](/zh/aftersell/cart/custom-code-blocks)**：渲染数据。
* **[Cart 对象](/zh/aftersell/cart/sdk-cart-object)**：`metadata` 在行项目上的位置。
