> ## 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 广播的浏览器事件，在测验加载、步骤显示、答案变更、校验失败和访客提交时运行自定义代码，并推送到 dataLayer 进行跟踪。

你的测验会以浏览器事件的形式在 `window` 上广播所发生的情况。监听这些事件，你就可以在数据分析工具中跟踪访客行为、响应访客的回答，或在恰当的时机运行你自己的代码。

```javascript theme={null}
window.addEventListener('ll-quiz-step-view', (event) => {
  console.log('Visitor is on step', event.detail.stepName);
});
```

每个事件都会把数据放在 `event.detail` 中。其中始终包含两项：

* `quizId`：触发该事件的测验
* `llQuizApi`：[测验 API](/zh/features/quizzes/api/reference)，让你可以直接在监听器中读取或修改测验

<Note>
  `ll-quiz-init` 会在测验准备就绪后立即触发，这可能早于你的脚本运行。不必担心：即使监听器注册得较晚，也仍会收到 `ll-quiz-init`。其他所有事件都由访客操作触发，发生在你的脚本就位之后。
</Note>

## 事件一览

| 事件 | 触发时机 | 有用的数据 |
| :- | :- | :- |
| `ll-quiz-init` | 测验准备就绪 | `llQuizApi` |
| `ll-quiz-step-view` | 显示某个步骤 | `stepName`、`fields` |
| `ll-quiz-step-leave` | 访客离开某个步骤 | `stepName` |
| `ll-quiz-value-change` | 提交了一个答案 | `blockName`、`value`、`previousValue`、`source` |
| `ll-quiz-input-click` | 点击了某个选项 | `blockName`、`optionLabel`、`optionValue` |
| `ll-quiz-button-click` | 点击了某个按钮 | `blockName` |
| `ll-quiz-validation-error` | 离开步骤被阻止 | `errors`、`source` |
| `ll-quiz-google-address-select` | 选择了一个地址 | `address` |
| `ll-quiz-submit` | 测验已提交 | `fields`、`fieldsSimple`、`response` |
| `ll-quiz-exit` | 标签页被关闭 | |

## ll-quiz-init

在测验出现在页面上并准备就绪后触发一次。你可以在这里获取 `llQuizApi`、预填值或进行初始化设置。

<ResponseField name="quizId" type="string" />

<ResponseField name="llQuizApi" type="LlQuizApi">
  该测验的测验 API
</ResponseField>

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

## ll-quiz-step-view

每次显示步骤时都会触发，包括加载时显示的第一个步骤，以及访客返回上一步时。

<ResponseField name="stepId" type="string" />

<ResponseField name="stepName" type="string" />

<ResponseField name="fields" type="Field[]">
  目前为止给出的答案，参见下文的“fields 和 fieldsSimple”部分
</ResponseField>

<ResponseField name="fieldsSimple" type="object">
  以“标签 → 值”形式表示的相同答案
</ResponseField>

```javascript theme={null}
window.addEventListener('ll-quiz-step-view', (event) => {
  const { stepName, quizId } = event.detail;
  window.dataLayer?.push({ event: 'quiz_step', quiz: quizId, step: stepName });
});
```

## ll-quiz-step-leave

在访客离开某个步骤时触发，早于下一个 `ll-quiz-step-view`。可用于衡量某个步骤所花的时间，或保存草稿。

<ResponseField name="stepName" type="string" />

```javascript theme={null}
let shownAt = Date.now();

window.addEventListener('ll-quiz-step-view', () => {
  shownAt = Date.now();
});

window.addEventListener('ll-quiz-step-leave', (event) => {
  const seconds = Math.round((Date.now() - shownAt) / 1000);
  window.dataLayer?.push({ event: 'quiz_step_time', step: event.detail.stepName, seconds });
});
```

## ll-quiz-value-change

在提交答案时触发。需要输入的字段（文本、邮箱、电话、数字、多行文本、日期、出生日期、邮编、地址、OTP）会在访客离开该字段或该步骤时提交，因此每个答案只会触发一次事件，而不是每次按键都触发。其他所有输入，以及通过 API 设置的所有值，都会立即提交。

<ResponseField name="blockId" type="string" />

<ResponseField name="blockName" type="string">
  区块在构建器中的名称
</ResponseField>

<ResponseField name="blockType" type="string">
  例如 `text-field` 或 `multiple-choice`
</ResponseField>

<ResponseField name="stepId" type="string" />

<ResponseField name="stepName" type="string" />

<ResponseField name="value" type="string">
  新的答案，格式与 `Field.value` 相同
</ResponseField>

<ResponseField name="previousValue" type="string">
  上一次报告的答案，或测验加载时的值
</ResponseField>

<ResponseField name="source" type="'user' | 'api' | 'system'">
  `user`：访客。`api`：你的脚本通过测验 API 设置。`system`：测验自身，例如提交后清空答案
</ResponseField>

如果值没有变化，则不会触发。测验加载时已存在的值（默认值、URL 参数、恢复的答案）作为起始值，不会触发事件；它们包含在 `ll-quiz-step-view` 的 `fields` 中。

```javascript theme={null}
window.addEventListener('ll-quiz-value-change', (event) => {
  const { blockName, value, source } = event.detail;
  if (source !== 'user') return; // 忽略脚本触发和自动发生的变更

  window.dataLayer?.push({ event: 'quiz_answer', field: blockName, value });
});
```

## ll-quiz-input-click

在访客点击多项选择或图片选择区块中的某个选项时触发。对于同一次点击，它会在 `ll-quiz-value-change` 之前触发。

<ResponseField name="blockId" type="string" />

<ResponseField name="blockName" type="string" />

<ResponseField name="stepId" type="string" />

<ResponseField name="stepName" type="string" />

<ResponseField name="optionId" type="string" />

<ResponseField name="optionValue" type="string">
  选项的值，与答案中存储的值相同
</ResponseField>

<ResponseField name="optionLabel" type="string">
  访客看到的文本
</ResponseField>

```javascript theme={null}
window.addEventListener('ll-quiz-input-click', (event) => {
  const { blockName, optionLabel, optionValue, llQuizApi } = event.detail;

  window.dataLayer?.push({ event: 'quiz_option', field: blockName, option: optionLabel });

  // 选择某个选项时，在套餐选择器下方显示提示
  if (blockName === 'plan') {
    const hint = llQuizApi.getBlock('planHint');
    optionValue === 'enterprise' ? hint.visibility.show() : hint.visibility.hide();
  }
});
```

## ll-quiz-button-click

在点击 Continue、Previous、Submit 或 Button 区块时触发，早于任何校验。即使这次点击最终被阻止也会触发，因此它统计的是尝试次数，而不是成功次数。

<ResponseField name="blockId" type="string" />

<ResponseField name="blockName" type="string" />

<ResponseField name="stepId" type="string" />

<ResponseField name="stepName" type="string" />

```javascript theme={null}
window.addEventListener('ll-quiz-button-click', (event) => {
  const { blockName, stepName } = event.detail;
  window.dataLayer?.push({ event: 'quiz_button', step: stepName, button: blockName });
});
```

<Tip>
  要知道这次点击是否真的让访客进入了下一步，可以将它与 `ll-quiz-step-leave`（已进入）或 `ll-quiz-validation-error`（未进入）结合使用。
</Tip>

## ll-quiz-validation-error

当访客尝试离开某个步骤、但因步骤中有无效内容而被阻止时触发。每次尝试触发一个事件，在所有检查（包括邮箱、电话和 OTP 字段的服务器检查）完成后触发，并准确列出访客看到的错误。隐藏区块永远不会包含在内。

<ResponseField name="stepId" type="string" />

<ResponseField name="stepName" type="string" />

<ResponseField name="source" type="'user' | 'api' | 'system'">
  `user`：按钮或会触发跳转的选项。`api`：你的脚本（`validateStep()`、`goNext()`、`goToStep()`、`submit()`）。`system`：测验尝试自动前进（加载器、倒计时、支付）
</ResponseField>

<ResponseField name="triggerBlockId" type="string">
  发起这次尝试的按钮或选项；`api` 来源时为空
</ResponseField>

<ResponseField name="triggerBlockName" type="string">
  `api` 来源时为空
</ResponseField>

<ResponseField name="errors" type="array">
  每个校验失败的区块对应一条，按步骤中的顺序排列：`blockId`、`blockName`、`blockType` 和 `messages`（显示的文本；只展示第一条）
</ResponseField>

以下情况不会触发：步骤校验通过时、使用 Previous 按钮时（它从不校验），或点击不会触发跳转的选项时。

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

  // 找出哪些字段阻碍了访客
  window.dataLayer?.push({ event: 'quiz_blocked', step: stepName, fields: errors.map((e) => e.blockName) });

  // 并帮助访客：将光标移到第一个出错的字段
  llQuizApi.getBlock(errors[0].blockId).focus();
});
```

## ll-quiz-google-address-select

在访客从 Google Address 区块中选择一条建议地址时触发。

<ResponseField name="blockId" type="string" />

<ResponseField name="blockName" type="string" />

<ResponseField name="stepId" type="string" />

<ResponseField name="stepName" type="string" />

<ResponseField name="address" type="object">
  `street`、`city`、`state`、`stateShort`、`postalCode`、`country`、`countryShort`
</ResponseField>

```javascript theme={null}
window.addEventListener('ll-quiz-google-address-select', (event) => {
  const { address, llQuizApi } = event.detail;
  llQuizApi.getBlock('city').setValue(address.city);
});
```

## ll-quiz-submit

在测验提交、线索保存之后触发。这里适合把答案发送到其他地方或触发转化。

<ResponseField name="stepId" type="string">
  访客提交时所在的步骤
</ResponseField>

<ResponseField name="stepName" type="string" />

<ResponseField name="fields" type="Field[]">
  所有答案
</ResponseField>

<ResponseField name="fieldsSimple" type="object">
  以“标签 → 值”形式表示的所有答案
</ResponseField>

<ResponseField name="response" type="any">
  LanderLab 服务器返回的响应；如果没有保存线索（例如在测验设置中关闭了线索保存），则为 `null`
</ResponseField>

```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),
  });
});
```

## ll-quiz-exit

在标签页或窗口被关闭或跳转离开时触发，基于浏览器的 `pagehide` 事件。

<Warning>
  浏览器不保证每次关闭时都会触发此事件。请将它与 `navigator.sendBeacon` 配合用于尽力而为的跟踪，切勿用于必须执行的操作。
</Warning>

```javascript theme={null}
window.addEventListener('ll-quiz-exit', (event) => {
  const { llQuizApi } = event.detail;
  if (llQuizApi.getInfo().isSubmitted) return; // 已完成的访问不算放弃

  const { name } = llQuizApi.getCurrentStep();
  navigator.sendBeacon('https://example.com/abandoned', JSON.stringify({ lastStep: name }));
});
```

## fields 和 fieldsSimple

`fields` 是一个列表，每个已回答的区块对应一条：

```javascript theme={null}
[
  { id: 'firstName', label: 'First name', value: 'Ada', key: 'firstName', stepId: 'step-…', stepName: 'contact' },
  { id: 'plan', label: 'Plan', value: 'pro', key: 'plan', stepId: 'step-…', stepName: 'plan' },
]
```

`fieldsSimple` 包含相同的信息，但以标签为键组成一个对象，便于转发：

```javascript theme={null}
{ 'First name': 'Ada', 'Plan': 'pro' }
```

两者都不包含隐藏区块和空答案。如果两个区块的标签相同，`fieldsSimple` 会用逗号连接它们的值。测验 API 的 `getAnswers()` 和 `getAnswersSimple()` 返回的数据结构与此相同。


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