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

# Custom code 区块

> Aftersell Cart 的 Custom code 区块：在抽屉的任意位置添加你自己的 HTML 或 React，包括在 Cart items 内部。

> **Custom code** 区块将你自己的 HTML 或 React 添加到购物车中。你可以将它放在抽屉的任意版块中，或作为子区块嵌套在 [**Cart items**](/zh/aftersell/cart/cart-items-block) 内，使其按每行商品重复显示。与其他区块不同，它没有 Content 设置，也没有 Design 部分：该区块*本身就是*代码，因此你完全在其 **Code** 标签页中工作。

<Frame>
  <img src="https://mintcdn.com/aftersell/1Y3gBpUfxv16VGSW/images/aftersell/cart-custom-code-block-add-and-enable.gif?s=6717cc64a8765b0c06b65990f99e12ff" alt="在 Aftersell Cart 编辑器中添加并开启 Custom code 区块的动画预览" title="在 Aftersell Cart 编辑器中添加并开启 Custom code 区块的动画预览" width="1200" height="558" data-path="images/aftersell/cart-custom-code-block-add-and-enable.gif" />
</Frame>

<div id="add-and-turn-on-a-custom-code-block">
  ## 添加并开启 Custom code 区块
</div>

1. 将 **Custom code** 区块添加到任意版块，或作为 **Cart items** 下的子区块。
2. 选中它并打开 **Code** 标签页。
3. 选择 **HTML** 或 **React component**。新区块默认为 HTML。
4. 编写你的代码。
5. 如果你选择了 React，点击 <span style={{display:'inline-flex',alignItems:'center',gap:'4px',background:'#1C1C1C',color:'#fff',border:'1px solid #0A0A0A',borderRadius:'4px',padding:'0 6px',fontSize:'0.85em',fontWeight:500,lineHeight:'1.4em',verticalAlign:'middle'}}><svg width="8" height="9" viewBox="0 0 10 12" fill="none" aria-hidden="true" style={{display:'block'}}><path d="M1.5 1.2v9.6L8.8 6 1.5 1.2Z" stroke="#fff" strokeWidth="1.5" strokeLinejoin="round" /></svg>Compile</span>。
6. 开启 **"Use custom template"**（使用自定义模板）。对于此区块，该开关的含义是"显示我的自定义代码"，且默认关闭，因此在启用之前不会渲染任何内容。
7. 保持侧边栏的眼睛开关处于开启状态，使区块对顾客保持可见。

眼睛开关和 **"Use custom template"** 都必须开启，区块才会显示。

<div id="behavior">
  ## 行为
</div>

* 在购物车加载完成之前，区块不渲染任何内容。
* 当侧边栏眼睛关闭、**"Use custom template"** 关闭、代码为空，或 React 编译或渲染失败时，它同样不渲染任何内容。由于失败是静默的，请在发布前在[预览](/zh/aftersell/cart/previewing-carts)中检查你的区块。

<div id="html-mode">
  ## HTML 模式
</div>

HTML 模式会将一小组令牌替换到你的标记中。它适用于静态或由令牌驱动的内容，而不是运行逻辑。

* **内联 `<script>` 标签不会执行**，且 HTML 模式**无法访问 SDK 或 `window`。**
* 如需逻辑，请使用 [**React 模式**](#react-mode)，或结合 [Cart SDK](/zh/aftersell/cart/sdk-overview) 使用[自定义脚本](/zh/aftersell/cart/custom-scripts)。

<div id="tokens">
  ### 令牌
</div>

令牌的值是**格式化后的字符串**（商店货币格式、带 `%` 的百分比或数量），可直接放入标记中：

| 令牌                       | 显示内容                           |
| ------------------------ | ------------------------------ |
| `{{pre_cart_total}}`     | 折扣前的购物车总额。                     |
| `{{post_cart_total}}`    | 折扣后的购物车总额。                     |
| `{{savings_amount}}`     | 节省的金额（折扣前总额减去折扣后总额）。           |
| `{{savings_percentage}}` | 以百分比表示的节省，包含 `%` 符号（例如 `15%`）。 |
| `{{cart_quantity}}`      | 购物车中可见商品的数量。                   |

<div id="example">
  ### 示例
</div>

```html theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
<div class="cart-external-custom-code_html">
  You saved {{savings_amount}} ({{savings_percentage}})
</div>
```

<div id="react-mode">
  ## React 模式
</div>

React 模式会编译一个组件，并向其传递购物车数据和一个 `add-to-cart` 操作。

* 编辑器将外层包裹固定为 `function CustomCode(props: CustomCodeProps) { … }`，你只能编辑这两行之间的函数体。
* 你必须先点击 <span style={{display:'inline-flex',alignItems:'center',gap:'4px',background:'#1C1C1C',color:'#fff',border:'1px solid #0A0A0A',borderRadius:'4px',padding:'0 6px',fontSize:'0.85em',fontWeight:500,lineHeight:'1.4em',verticalAlign:'middle'}}><svg width="8" height="9" viewBox="0 0 10 12" fill="none" aria-hidden="true" style={{display:'block'}}><path d="M1.5 1.2v9.6L8.8 6 1.5 1.2Z" stroke="#fff" strokeWidth="1.5" strokeLinejoin="round" /></svg>Compile</span>，然后开启 **"Use custom template"**，区块才会显示。
* 你的组件可以使用 `useState`、`useEffect`、`useMemo`、`useRef` 和 `useCallback`。
* 与 HTML 模式不同，React 在页面上下文中运行，因此在可用时可以调用 `window` 和 [Cart SDK](/zh/aftersell/cart/sdk-overview)。
* 如果你的组件在运行时抛出错误，区块将不渲染任何内容，购物车的其余部分继续正常工作。

<div id="props">
  ### Props
</div>

总额和节省金额是以货币的[最小单位](/zh/aftersell/cart/sdk-actions#formatmoneycents)（美元为美分）表示的整数，因此 `$12.50` 是 `1250` 而不是 `12.50`。它们不像 HTML 令牌那样是格式化后的货币字符串。

| Prop                                            | 类型                          | 说明                                                                   |
| ----------------------------------------------- | --------------------------- | -------------------------------------------------------------------- |
| `cart`                                          | `AftersellCart`             | 当前购物车。参见[购物车对象参考](/zh/aftersell/cart/sdk-cart-object)。               |
| `line`                                          | `AftersellCartLine \| null` | 仅当区块是 Cart items 子区块时设置（每行渲染一次）；在版块中为 `null`。                        |
| `preCartTotal`                                  | `number`                    | **折扣前**的购物车总额（Shopify 的 `original_total_price`），以货币最小单位（如美分）表示。      |
| `postCartTotal`                                 | `number`                    | **折扣后**的购物车总额，以货币最小单位表示。                                             |
| `savings`                                       | `{ amount, percentage }`    | 节省的金额和百分比。                                                           |
| `addProduct(variantId, quantity?, properties?)` | `function`                  | 将产品添加到购物车，并附带此区块的归因标记，以便[分析](/zh/aftersell/cart/analytics)可以将其记入该区块。 |

<div id="the-cart-and-line-shapes">
  ### cart 和 line 的结构
</div>

`cart` 和 `line` 与 SDK 在其他所有地方暴露的对象相同，因此它们在\*\*[购物车对象参考](/zh/aftersell/cart/sdk-cart-object)\*\*中统一记录：购物车、商品行和捆绑上的每个字段。

你最常用的字段：`cart.items`、`cart.itemCount`、`cart.totalPrice`、`line.title`、`line.quantity`、`line.finalLinePrice`。

此区块特有的三点：

* **`line` 仅在 Cart items 子区块上设置**，此时你的组件每行渲染一次。作为版块放置时，`line` 为 `null`，你应改为读取 `cart.items`。
* **捆绑的子商品不在 `cart.items` 中。** 当商品行被[组合成捆绑](/zh/aftersell/cart/sdk-use-case-bundles)时，只有锚点行会出现；其子商品位于 `line.bundle.children` 中。
* **被[行转换](/zh/aftersell/cart/sdk-hooks#registerlinetransform)隐藏的行也不在其中**，尽管它们仍计入 `cart.totalPrice`。

<div id="examples">
  ### 示例
</div>

显示商品数量：

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomCode(props) {
  return (
    <div className="cart-external-custom-code_jsx">
      {props.cart.itemCount} items
    </div>
  );
}
```

作为 Cart items 子区块时，使用 `props.line` 呈现按产品的内容。区块每行渲染一次，并带有该行的产品和变体标记：

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomCode(props) {
  if (!props.line) return null;
  return (
    <div className="cart-external-custom-code_jsx">
      {props.line.productTitle}
      {props.line.variantTitle ? ` · ${props.line.variantTitle}` : ''}
    </div>
  );
}
```

<div id="reading-enrichment-metadata">
  ### 读取增强元数据
</div>

`cart.items` 中的每个商品都带有一个 `metadata` 字段：在[购物车增强器（cart enricher）](/zh/aftersell/cart/sdk-hooks#registercartenricher)填充之前是空对象 `{}`。填充后，它以增强器的 `id` 为键，包含该行产品或变体的 Storefront 数据：

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomCode(props) {
  return (
    <ul>
      {(props.cart.items ?? []).map((item) => {
        const note = item.metadata?.shipping?.shippingNote;
        return (
          <li key={item.key}>
            {item.title}
            {note ? ` · ${note.value}` : ''}
          </li>
        );
      })}
    </ul>
  );
}
```

`metadata` 始终存在，在增强器的异步获取完成之前默认为空对象 `{}`（"尚未增强"的判断是 `Object.keys(item.metadata).length === 0`）。读取特定增强器的键时请使用可选链（`item.metadata?.enricherId`），因为在增强完成之前该键不存在。

<div id="reading-discount-codes-and-line-discounts">
  ### 读取折扣码和行折扣
</div>

`cart.discountCodes` 列出应用于购物车的折扣码，每行的 `discountAllocations` 列出应用于该特定行的折扣：

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomCode(props) {
  const codes = props.cart.discountCodes;
  return (
    <div>
      {codes.length > 0 && (
        <p>Active discounts: {codes.join(', ')}</p>
      )}
      <ul>
        {(props.cart.items ?? []).map((item) => {
          return (
            <li key={item.key}>
              {item.title}
              {item.discountAllocations.map(
                (discount) => ` · ${discount.title} (-${(discount.amount / 100).toFixed(2)})`
              )}
            </li>
          );
        })}
      </ul>
    </div>
  );
}
```

<div id="placement-and-limits">
  ## 位置与限制
</div>

* **区域：** 任意（顶部、主体或底部）。也可作为 Cart items 的子区块使用。
* **最大数量：** 无限制。
* **状态：** 有商品和空购物车均可（作为版块区块时）。作为 Cart items 子区块时，仅在购物车有商品行时渲染，每行一个实例。
* 未锁定，因此你可以移除或隐藏它。
* 没有按区块的 Design 部分。请通过你自己的标记、[**自定义 CSS**](/zh/aftersell/cart/custom-css) 和全局[**设计设置**](/zh/aftersell/cart/design-settings)来设置样式。

<div id="when-to-use-custom-code-block-vs-custom-template-vs-custom-script">
  ## 何时使用 Custom code 区块、自定义模板或自定义脚本
</div>

|                                                  | 作用                                                                | 使用时机                     | 示例                                                                                         |
| ------------------------------------------------ | ----------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------ |
| **Custom code 区块**                               | 添加一个由你自己的 HTML 或 React 构成的*新*区块。                                  | 内置区块无法覆盖的功能。             | 一个将固定运费加到购物车总额上的预估总额行，或结账按钮上方的配送截止倒计时。                                                     |
| **[自定义模板](/zh/aftersell/cart/custom-templates)** | 使用该区块的数据，用你的 JSX 替换*现有*区块的渲染。                                     | 内置区块已经接近需求，但你需要不同的标记。    | 重构 [Product 行](/zh/aftersell/cart/cart-items-block#custom-template)，使变体名称、节省金额和数量选择器排在同一行。 |
| **[自定义脚本](/zh/aftersell/cart/custom-scripts)**   | 通过 [Cart SDK](/zh/aftersell/cart/sdk-overview) 对购物车运行 JavaScript。 | 面向整个购物车的逻辑、事件和配置，而非抽屉标记。 | 消费满 \$75 送免费手提袋：当购物车跨过阈值时[添加赠品](/zh/aftersell/cart/sdk-use-case-free-gift)，当顾客低于阈值时将其移除。   |
