@zixinit/vue-captcha

Extensible Vue 3 image and slider CAPTCHA components with server adapters and SSR support.
732
Install
npm install @zixinit/vue-captcha
Latest Version:0.1.2
Vue:3
License:MIT
Last Updated:Sep 9, 2026
Links: GitHub  ·  npm
Maintainer: zixinit

@zixinit/vue-captcha

可扩展的 Vue 3 验证码组件,包含图片验证码与滑块拼图。不依赖 UI 框架,支持 SSR、键盘操作、触屏、过期处理和请求取消。

安装

pnpm add @zixinit/vue-captcha

需要 Vue 3.5+,提供 ESM 和 TypeScript 声明。所有入口均需在应用中引入一次样式:

import '@zixinit/vue-captcha/style.css'

Nuxt 可将上述样式路径加入 nuxt.config.ts 的 css 数组。组件仅在客户端挂载后加载挑战;无需全局插件或 Pinia。

图片验证码

<script setup lang="ts">
import { ref } from 'vue'
import { ImageCaptcha } from '@zixinit/vue-captcha/image'
import type { ChallengeLoader, ImageChallenge } from '@zixinit/vue-captcha/core'

const challengeId = ref('')
const code = ref('')
const load: ChallengeLoader<ImageChallenge> = async ({ signal }) => {
  const response = await fetch('/api/captcha/image', { signal })
  if (!response.ok) throw new Error('加载失败')
  return response.json()
}
function invalidate() {
  challengeId.value = ''
  code.value = ''
}
</script>

<template>
  <input v-model="code" aria-label="验证码" autocomplete="off">
  <ImageCaptcha :load="load" @loaded="challengeId = $event.id" @invalidated="invalidate" />
</template>

服务端返回 { id, imageUrl, expiresAt? }。expiresAt 是 Unix 毫秒时间戳。业务提交时将 challengeId 与用户输入一起交给服务端校验;组件不持有正确答案。

滑块拼图

<script setup lang="ts">
import { ref } from 'vue'
import { SliderCaptcha } from '@zixinit/vue-captcha/slider'
import type { ChallengeLoader, SliderChallenge, SliderVerifier } from '@zixinit/vue-captcha/core'

const token = ref('')
const load: ChallengeLoader<SliderChallenge> = async ({ signal }) => {
  const response = await fetch('/api/captcha/slider', { signal })
  if (!response.ok) throw new Error('加载失败')
  return response.json()
}
const verify: SliderVerifier = async (answer, { signal }) => {
  const response = await fetch('/api/captcha/verify', {
    method: 'POST', signal, headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(answer),
  })
  if (!response.ok) throw new Error('验证失败')
  return response.json()
}
</script>

<template>
  <SliderCaptcha :load="load" :verify="verify"
    @success="token = $event.token" @invalidated="token = ''" />
</template>

挑战结构:

interface SliderChallenge {
  id: string
  backgroundUrl: string
  pieceUrl: string
  width: number
  height: number
  pieceWidth: number
  pieceHeight: number
  pieceTop: number
  expiresAt?: number
}

尺寸与 pieceTop 使用原始图片坐标单位,组件会按显示宽度缩放。独立拼块图片不应包含整个背景画布。提交参数为 { challengeId, offsetX, durationMs },offsetX 为拼块左边缘在原始背景中的横坐标;成功返回 { token, expiresAt? }。没有有效 token 不触发成功事件。刷新、重置、禁用、失败和过期均触发 invalidated,消费方应清空已保存的凭证。

服务端负责生成挑战、验证位置、控制尝试次数与有效期、签发并一次性消费凭证。durationMs 等客户端数据不可作为可信依据。业务接口必须核验 token,不能信任浏览器的成功事件。

公共 API

属性 说明
load 必填异步加载函数,接收 { signal }
verify 仅滑块必填,接收答案与 { signal }
autoLoad 默认 true;关闭后通过组件 ref 的 refresh() 加载
disabled 默认 false;禁用时取消请求并使当前挑战失效
messages Partial<CaptchaMessages>,覆盖默认中文文案和图片描述

公共事件:loaded(challenge)、invalidated()、expired()、error(Error);滑块额外提供 success(proof)。独立组件通过 ref 暴露 refresh(): Promise<void> 和 reset(): void,类型为 CaptchaHandle。

原生滑块支持方向键,左右按钮提供单击微调,确认按钮提交验证。视觉验证码仍需要业务提供非视觉验证路径,可通过 #alternative 插槽放置其他验证方式。

通过 .vc-captcha 上的 --vc-accent、--vc-surface、--vc-text、--vc-muted、--vc-border 等 CSS 变量调整主题,完整变量见 src/style.css。

扩展其他验证码

import { Captcha, createCaptchaRegistry } from '@zixinit/vue-captcha'
import ClickCaptcha from './ClickCaptcha.vue'
const registry = createCaptchaRegistry({ click: ClickCaptcha })
// 后续也可 registry.register('rotate', RotateCaptcha)
<Captcha type="click" :registry="registry" :load="load" @success="onSuccess" />

内置类型为 image 和 slider。注册表属于应用实例,不使用跨请求的全局注册状态;自定义组件决定自己的 props/events。统一入口转发属性、事件和 alternative 插槽,未知类型显示不支持提示,也可用 unsupported 插槽替换。需要命令式控制时直接使用独立组件。core 导出无 UI 的 createCaptchaController、类型和校验函数,可复用请求竞态与过期管理。

开发与发布

fnm use
pnpm install
make dev
make preflight

打开 http://localhost:5173。Playground 的演示 API 仅由 Vite 开发插件提供,不包含在 npm 包中。它使用内存数据及演示凭证,图片验证码没有业务提交接口,不适用于生产防刷。build:demo 只生成静态页面,部署时需自行提供对应 API。

pnpm test:browser 运行浏览器交互测试(先 pnpm exec playwright install chromium)。发布流程见 deploy/DEPLOY.md。GitHub 保存完整源码,不执行其他包的文档镜像清理流程。

兼容已有 client-slider 协议

@zixinit/vue-captcha/client 导出浏览器端 createClientSliderChallenge({ imageUrl, challenge: { id, expiresAt }, signal })。返回 { challenge, checkPosition(offsetX) },可将 challenge 交给 SliderCaptcha;verify 适配器先调用 checkPosition,再调用应用的服务端验证接口并返回真实凭证。图片需要同源、data URL 或允许 CORS。

本地位置检查只提供交互反馈,不能抵御伪造请求,也不生成可信 token。该入口用于兼容已有的 client-slider 业务协议;服务端滑块直接使用服务端生成的 challenge。

原版客户端拼图

ClientSliderCaptcha(或 <Captcha type="client-slider">)内部使用 vue3-puzzle-vcode@1.1.7,保留原组件的弹窗、拖动、刷新与失败反馈。该依赖由本包统一管理,两端无需直接安装。组件仅在浏览器挂载后加载,适用于 Nuxt SSR。

<ClientSliderCaptcha v-model="visible" :imgs="images" :load="loadChallenge" :verify="verifyClient" @success="onProof" @invalidated="clearProof" />

load 返回 { id, expiresAt? };verify 接收含 challengeId 的答案和 { signal },由业务适配器请求服务端验证,返回 { token, expiresAt? }。客户端拼图不提供服务端位移答案,兼容 SliderVerifier 的 offsetX、durationMs 固定为 0。本地拼图通过后才调用 verify;服务端凭证过期、关闭未完成验证或刷新都会清空旧状态。imgs 与原组件一致,接收图片 URL 数组。图片验证码、服务端拼图仍使用 ImageCaptcha、SliderCaptcha。

Related Packages

@nuxtjs/recaptcha

Simple and easy Google reCAPTCHA integration with Nuxt.js

701,851 255
@nuxtjs/turnstile

Cloudflare Turnstile integration for Nuxt

1,052,067 296
@gladesinger/vue3-yandex-smartcaptcha

Yandex Smart Captcha Vue component

40,194 7
@cikian/captcha-web

CikVerify 浏览器封装:框架无关 createCaptcha(),支持原生 JS / Vue 2 / Vue 3 / Nu...

0

Version History

Version Released Vue License
0.1.2 ^3.5.0 MIT
0.1.1 ^3.5.0 MIT
0.1.0 ^3.5.0 MIT