---
type: how-to
title: 前端集成示例
summary: 在纯 HTML、React、Vue、Svelte、Angular 和 Next.js 里挂载组件。这个元素到处都一样。
---

组件是一个标准的自定义元素，所以它在每个框架里都能用，没有包装层要安装。形状始终一样：加载这个包一次（[客户端集成](/docs/integration-guides/cdn)），在你的表单里渲染 `<caputchin-widget sitekey="cpt_pub_...">`，并读取访客通过挑战时它注入的隐藏 `caputchin-token` 字段。要用游戏，就换成 `<caputchin-game ... game="caputchin/games/leaf-memory">`。

<Callout type="warn">
如果一个站点密钥打开了游戏关卡（它的 **要求一个游戏来验证** 设置），那么复选框式的 `<caputchin-widget>` 无法在那个密钥上通过：服务器要求一个游戏，所以组件会抛出一个 `gate-unavailable` 错误。在任何设了关卡的密钥上都用 `<caputchin-game>`。这是有意为之的，因为关卡是在 Caputchin 这一侧强制执行的，而一个复选框满足不了它。见 [游戏关卡](/docs/site-keys/games)。
</Callout>

下面的示例仅在各框架的语法和它那一处自定义元素的怪癖上有所不同。

## 纯 HTML

注入的字段会随一次普通的表单 POST 一起带上，所以没有什么要接线的：

```html
<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@3/dist/widget.js"></script>

<form action="/contact" method="POST">
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>
```

## React

在启动时导入一次，然后在提交时从 `FormData` 读取该字段：

```jsx
import "@caputchin/widget";

export function ContactForm() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    await fetch("/api/contact", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ email: data.get("email"), token: data.get("caputchin-token") }),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..." />
      <button type="submit">Send</button>
    </form>
  );
}
```

如果你在 React 里用 TypeScript，就把这两个元素声明一次，好让 JSX 接受它们：

```ts
// caputchin.d.ts
import type { DetailedHTMLProps, HTMLAttributes } from "react";
declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "caputchin-widget": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string }, HTMLElement>;
      "caputchin-game": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string; game?: string; games?: string }, HTMLElement>;
    }
  }
}
```

这个 `declare module "react"` 形式是 React JSX 专用的。在纯 TypeScript 里，这些元素就只是 `HTMLElement`，不需要声明，而下面其他框架以各自的方式为自定义元素标注类型，所以你只在一个 React 项目里需要这一段。

## Vue

告诉 Vue 的编译器 `caputchin-` 标签是自定义元素，好让它不要把它们当作组件去解析：

```js
// vite.config.js
export default {
  plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("caputchin-") } } })],
};
```

```vue
<script setup>
import "@caputchin/widget";
function onSubmit(e) {
  const data = new FormData(e.target);
  // send data.get("caputchin-token") to your backend
}
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input name="email" type="email" required />
    <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
    <button type="submit">Send</button>
  </form>
</template>
```

## Svelte

Svelte 原样渲染自定义元素，不用配置：

```svelte
<script>
  import "@caputchin/widget";
  function onSubmit(e) {
    const data = new FormData(e.currentTarget);
    // send data.get("caputchin-token") to your backend
  }
</script>

<form on:submit|preventDefault={onSubmit}>
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>
```

## Angular

给模块或独立组件加上 `CUSTOM_ELEMENTS_SCHEMA`，好让 Angular 允许这个未知标签，并把这个包导入一次（在 `main.ts` 里）：

```ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@caputchin/widget";

@Component({
  selector: "contact-form",
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <form (submit)="onSubmit($event)">
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
      <button type="submit">Send</button>
    </form>
  `,
})
export class ContactFormComponent {
  onSubmit(e: Event) {
    const data = new FormData(e.target as HTMLFormElement);
    // send data.get("caputchin-token") to your backend
  }
}
```

## Next.js

同样的 React 代码就能用。这个自定义元素在服务端渲染时渲染为一个空标签，并在包加载后于客户端升级，所以除了在一个客户端组件里导入 `@caputchin/widget`（或用 `next/script` 加载 CDN 脚本）之外，不需要任何特殊处理。

## 另见

- [客户端集成](/docs/integration-guides/cdn)，看加载选项。
- [服务端集成](/docs/integration-guides/verify-on-your-backend)，看后端那一半。
