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

# 使用测验 API 进行高级自定义

> 使用测验 API（llQuizApi）通过 JavaScript 读取访客答案、修改区块的值和标签、控制可见性、校验步骤，并在步骤之间跳转或提交测验。

测验 API 让你自己的 JavaScript 可以在访客填写测验时与测验交互。你可以读取访客的回答、修改区块、跳转到某个步骤或提交测验，全程无需在构建器中改动测验。

你可以在页面上任何能运行 JavaScript 的地方使用它：测验自身的自定义代码、脚本区块，或标签管理器代码片段。

## 获取 API

API 由 `ll-quiz-init` 事件通过 `event.detail.llQuizApi` 提供给你。保存这个对象，需要时随时使用。

```javascript theme={null}
window.addEventListener('ll-quiz-init', (event) => {
  const quiz = event.detail.llQuizApi;

  quiz.getBlock('firstName').setValue('Ada');
});
```

<Note>
  你的脚本可以在测验加载之前或之后运行。如果脚本注册得较晚，`ll-quiz-init` 会自动向它重放，因此这种写法始终有效。
</Note>

其他所有测验事件都携带同一个 `llQuizApi`，因此你也可以在某个步骤显示或某个答案变化时立即响应并执行操作。参见[测验事件](/zh/features/quizzes/api/events)。

## 按名称引用步骤和区块

所有接受步骤或区块参数的函数都支持**名称或 ID**。名称就是你在构建器中输入的内容，因此通常使用名称即可。ID 是自动生成的，形如 `block-3f9c…`。

```javascript theme={null}
quiz.getBlock('email'); // 按名称
quiz.getBlock('block-3f9c1a2e-…'); // 按 ID，结果相同
quiz.getBlocksByStep('contact');
```

<Tip>
  在构建器中为每个区块和步骤起一个清晰的名称。这是使用 API 唯一需要做的设置。
</Tip>

如果两个区块或两个步骤同名，将使用测验顺序中的第一个。

## 读取答案

`getAnswers()` 返回目前为止给出的所有答案，每个区块一条。`getAnswersSimple()` 以标签为键的简单对象返回相同的答案。隐藏区块和空答案不包含在内。

```javascript theme={null}
quiz.getAnswers();
// [{ id: 'firstName', label: 'First name', value: 'Ada', key: 'firstName', stepId: 'step-…', stepName: 'contact' }, …]

quiz.getAnswersSimple();
// { 'First name': 'Ada', 'Plan': 'pro' }
```

它们与事件携带的 `fields` 和 `fieldsSimple` 完全一致，因此处理其中一种的代码也能处理另一种。

## 了解访客所在位置

```javascript theme={null}
quiz.getCurrentStep(); // { id: 'step-…', name: 'contact', index: 1, total: 4 }
quiz.getCurrentStepBlocks(); // 该步骤上的区块
quiz.getSteps(); // [{ id, name, index }, …]，按测验顺序
quiz.getInfo(); // { quizId: 'quiz-…', isSubmitted: false }
```

`index` 从 0 开始。本次访问提交测验后，`isSubmitted` 会变为 true。

## 查找区块

| 调用 | 返回 |
| :- | :- |
| `getBlocks()` | 所有可存储值的区块 |
| `getBlock(nameOrId)` | 单个区块 |
| `getBlocksByStep(nameOrId)` | 某个步骤中的区块 |
| `getCurrentStepBlocks()` | 当前显示步骤中的区块 |

列表只包含可存储值的区块，标题、段落、图片等永远不会被列出。`getBlock()` 还能找到按钮（Continue、Previous、Submit、Button），因此你可以修改按钮文字或禁用按钮。

<Warning>
  `getBlock()` 永远不会返回 `undefined`。如果没有匹配项，或该区块无法存储值，你会得到一个占位区块：对它的每次调用都会记录一条警告且不执行任何操作，`getValue()` 返回 `""`。你的代码永远不会崩溃，但如果某个操作没有生效，请检查控制台。
</Warning>

<Accordion title="哪些区块可以存储值？">
  输入字段（文本、邮箱、电话、数字、多行文本、日期、出生日期、邮编、地址、OTP）、选项类区块（多项选择、图片选择、下拉选择）、复选框、范围滑块、上传、签名和隐藏字段。

  其他区块都用于展示或导航：标题、段落、图片、视频、分隔线、列表、加载器、倒计时、进度条、折叠面板、链接、自定义代码、跟踪，以及各类按钮。对这些区块调用 `getValue()`、`setValue()`、`reset()` 或 `validate()` 会抛出错误。容器是透明的：容器内的区块会像其他区块一样被列出。
</Accordion>

## 读取和修改区块

你获取到的每个区块都是同一类对象，因此下面的示例适用于任何支持该调用的区块。

### 值

```javascript theme={null}
const name = quiz.getBlock('firstName');

name.getValue(); // 'Ada'
name.setValue('Grace');
name.reset(); // 恢复为空
```

通过 API 设置值会触发 `ll-quiz-value-change`，并带有 `source: 'api'`，因此你的监听器可以将其与访客输入区分开。

<AccordionGroup>
  <Accordion title="各区块的值格式">
    * **复选框**：`'checked'` 或 `'unchecked'`
    * **多项选择、图片选择、下拉选择**：所选选项的值，例如 `'pro'`
    * **可多选的多项选择**：用区块上配置的分隔符连接起来的多个值
    * **其他所有区块**：访客输入的文本
  </Accordion>
</AccordionGroup>

### 标签和占位符

```javascript theme={null}
name.getLabel(); // 'First name'
name.setLabel('Given name');

name.getPlaceholder(); // 'Ada'
name.setPlaceholder('Type your first name');

quiz.getBlock('continue-1').setLabel('Next step'); // 按钮也适用
```

标签是纯文本，但复选框例外：复选框的标签允许使用 HTML，以便添加条款链接。在没有标签的区块上调用 `setLabel()` 和 `getLabel()` 会抛出错误。

### 选项

多项选择、图片选择和下拉选择区块会公开其选项：

```javascript theme={null}
quiz.getBlock('plan').getOptions();
// [{ id: 'option-…', label: 'Pro', value: 'pro', selected: true }, …]
```

在其他任何区块上调用 `getOptions()` 都会抛出错误。

### 错误和焦点

```javascript theme={null}
name.getErrors(); // 校验失败后为 ['This field is required.']，否则为 []
name.focus(); // 将光标移到该区块
```

### 按钮

```javascript theme={null}
const next = quiz.getBlock('continue-1');
next.disable();
await checkSomething(); // 你自己的异步操作
next.enable();
```

`enable()` 和 `disable()` 适用于 Continue、Submit 和 Button 区块，在其他区块上调用会抛出错误。

### 可见性

```javascript theme={null}
const promo = quiz.getBlock('promoCode');

promo.visibility.hide();
promo.visibility.show();
promo.visibility.get(); // true、false 或 undefined（未覆盖）
promo.visibility.clear(); // 移除你的覆盖设置
```

隐藏的区块不会被校验、不会随线索一起发送，也不会显示。`undefined` 表示你从未修改过它，因此该区块遵循自身的设置。

## 校验

```javascript theme={null}
const step = await quiz.validateStep('contact'); // { success: false }
const block = await quiz.getBlock('email').validate(); // { success: false, errors: ['Invalid email address.'] }
```

校验是异步的，因为邮箱、电话和 OTP 字段需要通过服务器检查。隐藏区块始终通过校验。

<Info>
  当某个步骤校验失败时，测验还会触发 `ll-quiz-validation-error`，其中包含每个校验失败的区块及其消息，并标记为 `source: 'api'`。因此，一个监听器就能同时处理访客的操作和你的脚本调用。详见[事件页面](/zh/features/quizzes/api/events#ll-quiz-validation-error)。
</Info>

## 导航和提交

```javascript theme={null}
await quiz.navigation.goNext();
quiz.navigation.goBack();
await quiz.navigation.goToStep('summary'); // 名称或 ID
await quiz.navigation.submit();
quiz.navigation.goToUrl('https://example.com'); // 加上 `true` 可在新标签页中打开
```

`goNext()`、`goToStep()` 和 `submit()` 会先校验当前显示的步骤。如果校验不通过，它们会停留在原处，记录一条警告并触发 `ll-quiz-validation-error`。`goBack()` 从不校验。

`submit()` 的效果与访客点击 Submit 按钮完全相同：保存线索，触发带有服务器响应的 `ll-quiz-submit`，应用防重复提交保护，并执行 Submit 按钮的提交后操作。如果当前步骤没有 Submit 按钮，测验会完成提交并停留在当前位置。返回的 promise 会在上述所有操作完成后 resolve。

## 实用示例

<AccordionGroup>
  <Accordion title="从 URL 预填">
    ```javascript theme={null}
    window.addEventListener('ll-quiz-init', (event) => {
      const quiz = event.detail.llQuizApi;
      const params = new URLSearchParams(window.location.search);

      if (params.get('email')) quiz.getBlock('email').setValue(params.get('email'));
      if (params.get('plan')) quiz.getBlock('plan').setValue(params.get('plan'));
    });
    ```
  </Accordion>

  <Accordion title="显示自定义进度文本">
    ```javascript theme={null}
    window.addEventListener('ll-quiz-step-view', (event) => {
      const { index, total } = event.detail.llQuizApi.getCurrentStep();
      document.querySelector('#progress').textContent = `Step ${index + 1} of ${total}`;
    });
    ```
  </Accordion>

  <Accordion title="跳转到第一个出错的字段">
    ```javascript theme={null}
    window.addEventListener('ll-quiz-validation-error', (event) => {
      const { errors, llQuizApi } = event.detail;
      llQuizApi.getBlock(errors[0].blockId).focus();
    });
    ```
  </Accordion>

  <Accordion title="在你自己的检查通过之前锁定 Continue 按钮">
    ```javascript theme={null}
    window.addEventListener('ll-quiz-step-view', async (event) => {
      const quiz = event.detail.llQuizApi;
      if (quiz.getCurrentStep().name !== 'eligibility') return;

      const next = quiz.getBlock('continue-eligibility');
      next.disable();
      const ok = await fetch('/api/eligible?zip=' + quiz.getBlock('zip').getValue()).then((r) => r.ok);
      if (ok) next.enable();
      else next.setLabel('Not available in your area');
    });
    ```
  </Accordion>

  <Accordion title="仅在选择特定答案时显示某个区块">
    ```javascript theme={null}
    window.addEventListener('ll-quiz-value-change', (event) => {
      const { blockName, value, llQuizApi } = event.detail;
      if (blockName !== 'plan') return;

      const promo = llQuizApi.getBlock('promoCode');
      value === 'premium' ? promo.visibility.show() : promo.visibility.hide();
    });
    ```
  </Accordion>

  <Accordion title="通过你自己的按钮提交">
    ```javascript theme={null}
    window.addEventListener('ll-quiz-init', (event) => {
      const quiz = event.detail.llQuizApi;

      document.querySelector('#my-submit').addEventListener('click', async () => {
        await quiz.navigation.submit(); // 先校验；在 ll-quiz-submit 触发后 resolve
        if (quiz.getInfo().isSubmitted) window.location.href = '/thank-you';
      });
    });
    ```
  </Accordion>

  <Accordion title="将答案发送到你自己的端点">
    ```javascript theme={null}
    window.addEventListener('ll-quiz-submit', (event) => {
      fetch('https://example.com/leads', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(event.detail.fieldsSimple),
      });
    });
    ```
  </Accordion>
</AccordionGroup>

## 函数一览

| 函数 | 作用 |
| :- | :- |
| `getAnswers()` / `getAnswersSimple()` | 目前为止的答案 |
| `getCurrentStep()` / `getCurrentStepBlocks()` | 当前显示的步骤及其区块 |
| `getSteps()` / `getInfo()` | 所有步骤；测验 ID 及是否已提交 |
| `getBlocks()` / `getBlock()` / `getBlocksByStep()` | 查找区块 |
| `validateStep(nameOrId)` | 校验某个步骤 |
| `navigation.goNext()` / `goBack()` / `goToStep()` / `submit()` / `goToUrl()` | 跳转和提交 |

| 区块上的方法 | 作用 |
| :- | :- |
| `getValue()` / `setValue()` / `reset()` | 答案 |
| `getLabel()` / `setLabel()` / `getPlaceholder()` / `setPlaceholder()` | 文本 |
| `getOptions()` | 选项类或下拉选择区块的选项 |
| `validate()` / `getErrors()` / `focus()` | 校验 |
| `enable()` / `disable()` | 按钮 |
| `visibility.show()` / `hide()` / `get()` / `clear()` | 显示或隐藏 |
| `blockID` / `blockType` / `variableName` | 标识 |

<Accordion title="旧版函数名（仍受支持）">
  早期版本为每种查找方式提供了单独的函数。它们仍会和以前完全一样地工作，因此你已经写好的代码无需修改。新代码应使用统一的函数。

  | 旧名称 | 改用 |
  | :- | :- |
  | `getBlockByID(id)` / `getBlockByName(name)` | `getBlock()` |
  | `getBlocksByStepID(id)` / `getBlocksByStepName(name)` | `getBlocksByStep()` |
  | `validateStepByID(id)` / `validateStepByName(name)` | `validateStep()` |
</Accordion>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.