前端集成示例
组件是一个标准的自定义元素,所以它在每个框架里都能用,没有包装层要安装。形状始终一样:加载这个包一次(客户端集成),在你的表单里渲染 <caputchin-widget sitekey="cpt_pub_...">,并读取访客通过挑战时它注入的隐藏 caputchin-token 字段。要用游戏,就换成 <caputchin-game ... game="caputchin/games/leaf-memory">。
下面的示例仅在各框架的语法和它那一处自定义元素的怪癖上有所不同。
纯 HTML
注入的字段会随一次普通的表单 POST 一起带上,所以没有什么要接线的:
<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 读取该字段:
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 接受它们:
// 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- 标签是自定义元素,好让它不要把它们当作组件去解析:
// vite.config.js
export default {
plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("caputchin-") } } })],
};<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 原样渲染自定义元素,不用配置:
<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 里):
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 脚本)之外,不需要任何特殊处理。