Appearance
BizCaptchaInput 图形验证码
图形验证码输入框:local 模式前端生成干扰字符 SVG(内网 / 演示),remote 模式服务端出图 + captchaId 回传;输入满位自动校验、通过 / 失败自动换图防重放、连续失败达阈值额外触发 refresh。
与 BizSliderCaptcha(行为验证)、BizVerifyCodeInput(短信 OTP)构成完整验证码家族。适用于登录 / 注册防刷、短信发送前置校验、敏感操作二次确认。
基础用法(本地模式)
输满 4 位自动校验:通过清空输入并换图;失败计数并换图。点击图可手动刷新。
<script setup lang="ts">
/** 场景:本地模式基础——输满自动校验,通过 / 失败均换图防重放 */
import { ref } from 'vue'
import { BizCaptchaInput } from 'vue3-ep-biz-lib'
import type { BizCaptchaVerifyPayload } from 'vue3-ep-biz-lib'
const code = ref('')
const result = ref<BizCaptchaVerifyPayload | null>(null)
const onVerify = (payload: BizCaptchaVerifyPayload): void => {
result.value = payload
if (payload.valid) code.value = ''
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 10px">
<BizCaptchaInput v-model="code" @verify="onVerify" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">
{{ result === null ? '输入满 4 位自动校验' : result.valid ? '✅ 校验通过(已换图防重放)' : `❌ 校验失败,连续失败 ${result.failCount} 次` }}
</span>
</div>
</template>
显示代码
远程模式(服务端出图)
mode="remote" 传 image-url(url / base64)与 captcha-id:change 事件回传 (code, captchaId),verify 仅做非空判定——校验权在服务端;点击图抛 refresh 由业务重新拉图。<script setup lang="ts">
/** 场景:远程模式——服务端出图 + captchaId 回传(change / verify 事件负载),refresh 换图 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizCaptchaInput } from 'vue3-ep-biz-lib'
/* 模拟服务端:生成 base64 SVG 图与 token(真实业务为后端接口返回) */
let seed = 0
const makeCaptcha = (): { url: string; id: string } => {
seed += 1
const chars = Array.from({ length: 4 }, () => 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'[Math.floor(Math.random() * 31)]).join('')
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="96" height="34"><rect width="96" height="34" rx="4" fill="#f0f2f5"/><text x="48" y="23" font-size="20" font-weight="bold" text-anchor="middle" fill="#409eff">${chars}</text></svg>`
return { url: `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`, id: `cap-${Date.now()}-${seed}` }
}
const captcha = ref(makeCaptcha())
const code = ref('')
const onRefresh = (): void => {
captcha.value = makeCaptcha()
code.value = ''
}
const onVerify = (): void => {
ElMessage.success(`提交 { captchaId: ${captcha.value.id}, code: ${code.value || '(空)'} } 到服务端判定`)
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 10px">
<BizCaptchaInput
v-model="code"
mode="remote"
:image-url="captcha.url"
:captcha-id="captcha.id"
@refresh="onRefresh"
@verify="onVerify"
/>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">点击图换一张;change 事件回传 (code, captchaId),判定权在服务端</span>
</div>
</template>
显示代码
字符集与位数
char-set 自定义字符集(默认剔除易混淆的 0/O/1/I/l)、length 位数、noise 干扰强度(0~3)。<script setup lang="ts">
/** 场景:字符集与位数定制——纯数字 6 位(低安全内部系统)/ 纯算式风自定义集 */
import { ref } from 'vue'
import { BizCaptchaInput } from 'vue3-ep-biz-lib'
const numeric = ref('')
const hexish = ref('')
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 16px">
<div style="display: flex; align-items: center; gap: 8px">
<BizCaptchaInput v-model="numeric" :length="6" char-set="0123456789" placeholder="6 位数字" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">纯数字 6 位</span>
</div>
<div style="display: flex; align-items: center; gap: 8px">
<BizCaptchaInput v-model="hexish" :length="5" char-set="0123456789ABCDEF" :noise="3" placeholder="HEX 码" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">HEX 字符集 + 干扰强度 3</span>
</div>
</div>
</template>
显示代码
大小写敏感与尺寸
ignore-case 默认忽略大小写;size 三档(输入框宽与图高联动)。<script setup lang="ts">
/** 场景:大小写敏感 + 尺寸档位(small / default / large) */
import { ref } from 'vue'
import { BizCaptchaInput } from 'vue3-ep-biz-lib'
const strict = ref('')
const small = ref('')
const large = ref('')
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 16px">
<div style="display: flex; align-items: center; gap: 8px">
<BizCaptchaInput v-model="strict" :ignore-case="false" placeholder="严格大小写" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">ignore-case=false(默认忽略)</span>
</div>
<div style="display: flex; align-items: center; gap: 8px">
<BizCaptchaInput v-model="small" size="small" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">small</span>
</div>
<div style="display: flex; align-items: center; gap: 8px">
<BizCaptchaInput v-model="large" size="large" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">large</span>
</div>
</div>
</template>
显示代码
连续失败自动换图
每次校验失败都换图;连续失败达
auto-refresh-after-fails(默认 3)额外触发 refresh 事件供业务提示(可设 0 关闭)。<script setup lang="ts">
/** 场景:连续失败自动换图——failCount 达阈值(默认 3)额外抛 refresh 事件供业务提示 */
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizCaptchaInput } from 'vue3-ep-biz-lib'
import type { BizCaptchaVerifyPayload } from 'vue3-ep-biz-lib'
const code = ref('')
const history = ref<BizCaptchaVerifyPayload[]>([])
const fails = computed(() => history.value.filter((item) => !item.valid).length)
const onVerify = (payload: BizCaptchaVerifyPayload): void => {
history.value = [...history.value, payload]
if (payload.valid) code.value = ''
}
const onRefresh = (): void => {
code.value = ''
ElMessage.warning('连续失败达 3 次,已自动换图')
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 10px">
<BizCaptchaInput v-model="code" @verify="onVerify" @refresh="onRefresh" />
<span style="font-size: 13px; color: var(--el-text-color-secondary)">累计失败 {{ fails }} 次(故意输错试试;每次失败都会换图,达阈值额外触发 refresh)</span>
</div>
</template>
显示代码
表单校验集成
el-form 自定义 validator 内调
verify()——提交前随表单整校验,错误文案走表单体系。<script setup lang="ts">
/** 场景:表单校验集成——el-form required 规则 + verify() 程序化提交前校验 */
import { ref } from 'vue'
import { ElButton, ElForm, ElFormItem, ElInput, ElMessage } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { BizCaptchaInput } from 'vue3-ep-biz-lib'
import type { BizCaptchaInputInstance } from 'vue3-ep-biz-lib'
const formRef = ref<FormInstance>()
const captchaRef = ref<BizCaptchaInputInstance>()
const form = ref({ username: '', password: '', captcha: '' })
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
captcha: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{
validator: (_rule: unknown, _value: string, callback: (error?: Error) => void): void => {
const result = captchaRef.value?.verify()
if (result && !result.valid) callback(new Error('图形验证码不正确'))
else callback()
},
trigger: 'blur',
},
],
}
const submit = (): void => {
void formRef.value?.validate().then((valid) => {
if (valid) ElMessage.success(`登录参数:${JSON.stringify({ ...form.value, captcha: '已校验' })}`)
})
}
</script>
<template>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="80px" style="max-width: 420px">
<ElFormItem label="用户名" prop="username">
<ElInput v-model="form.username" placeholder="admin" />
</ElFormItem>
<ElFormItem label="密码" prop="password">
<ElInput v-model="form.password" type="password" show-password />
</ElFormItem>
<ElFormItem label="验证码" prop="captcha">
<BizCaptchaInput ref="captchaRef" v-model="form.captcha" />
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="submit">登 录</ElButton>
</ElFormItem>
</ElForm>
</template>
显示代码
图形码 → 短信码组合
<script setup lang="ts">
/** 场景:图形码 → 短信 OTP 组合登录——图形码通过才允许发送短信(与 BizVerifyCodeInput 联动) */
import { ref } from 'vue'
import { ElButton, ElInput, ElMessage } from 'element-plus'
import { BizCaptchaInput, BizVerifyCodeInput } from 'vue3-ep-biz-lib'
import type { BizCaptchaVerifyPayload } from 'vue3-ep-biz-lib'
const phone = ref('')
const captcha = ref('')
const sms = ref('')
const captchaPassed = ref(false)
const smsSent = ref(false)
const onCaptchaVerify = (payload: BizCaptchaVerifyPayload): void => {
captchaPassed.value = payload.valid
if (payload.valid) {
captcha.value = ''
ElMessage.success('图形验证码通过,可以发送短信验证码')
}
}
const sendSms = (): void => {
if (!captchaPassed.value) {
ElMessage.warning('请先通过图形验证码')
return
}
smsSent.value = true
ElMessage.success(`短信已发送至 ${phone.value || '138****8000'}(模拟)`)
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 16px; max-width: 420px">
<ElInput v-model="phone" placeholder="手机号" maxlength="11" />
<BizCaptchaInput v-model="captcha" @verify="onCaptchaVerify" />
<div style="display: flex; align-items: center; gap: 12px">
<BizVerifyCodeInput v-model="sms" :disabled="!smsSent" style="flex: 1" @complete="() => ElMessage.success('短信码校验交给服务端')" />
<ElButton :disabled="!captchaPassed" @click="sendSms">{{ smsSent ? '重新发送' : '发送短信' }}</ElButton>
</div>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">防刷链路:图形码本地通过 → 解锁发短信按钮 → OTP 校验交服务端</span>
</div>
</template>
显示代码
程序化方法
(操作日志)
refresh 换图 / reset 重置(清空 + 归零失败计数 + 换图)/ verify 主动校验 / focus 聚焦。<script setup lang="ts">
/** 场景:expose 方法面板——refresh / reset / verify 程序化驱动 */
import { ref } from 'vue'
import { ElButton } from 'element-plus'
import { BizCaptchaInput } from 'vue3-ep-biz-lib'
import type { BizCaptchaInputInstance } from 'vue3-ep-biz-lib'
const captchaRef = ref<BizCaptchaInputInstance>()
const code = ref('')
const log = ref<string[]>([])
const push = (line: string): void => {
log.value = [...log.value.slice(-4), line]
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<BizCaptchaInput ref="captchaRef" v-model="code" @verify="(p) => push(`verify → valid=${p.valid} failCount=${p.failCount}`)" />
<div style="display: flex; gap: 8px">
<ElButton size="small" @click="() => { captchaRef?.refresh(); push('refresh 换图') }">refresh</ElButton>
<ElButton size="small" @click="() => { captchaRef?.reset(); push('reset 重置') }">reset</ElButton>
<ElButton size="small" @click="() => captchaRef?.verify()">verify</ElButton>
<ElButton size="small" @click="() => captchaRef?.focus()">focus</ElButton>
</div>
<pre style="margin: 0; padding: 8px 12px; font-size: 12px; border-radius: 6px; background: var(--el-fill-color-light)">{{ log.join('\n') || '(操作日志)' }}</pre>
</div>
</template>
显示代码
禁用态
锁定
disabled 同时锁定输入与刷新,图形置灰——登录重试次数超限等场景。<script setup lang="ts">
/** 场景:禁用态与刷新限制——disabled 时输入与换图均锁定(图形置灰) */
import { ref } from 'vue'
import { ElSwitch } from 'element-plus'
import { BizCaptchaInput } from 'vue3-ep-biz-lib'
const code = ref('')
const locked = ref(true)
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 14px">
<div style="display: flex; align-items: center; gap: 8px">
<ElSwitch v-model="locked" active-text="锁定" />
<BizCaptchaInput v-model="code" :disabled="locked" />
</div>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">如「登录已达最大重试次数,请 10 分钟后再试」场景直接置灰整个验证码区</span>
</div>
</template>
显示代码
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| model-value / v-model | string | '' | 用户输入的验证码 |
| mode | 'local' | 'remote' | 'local' | 本地生成 / 服务端出图 |
| length | number | 4 | 字符数(local 模式;remote 固定 maxlength 4) |
| char-set | string | 剔除易混淆字符集 | 本地字符集 |
| noise | number | 2 | 干扰强度 0~3(local 模式) |
| ignore-case | boolean | true | 校验忽略大小写(local 模式) |
| auto-refresh-after-fails | number | 3 | 连续失败 N 次额外触发 refresh;0 关闭 |
| image-url | string | — | remote 模式图形地址(url / base64) |
| captcha-id | string | — | remote 模式随图下发的 token,事件原样回传 |
| placeholder | string | '请输入验证码' | 输入框占位 |
| size | 'small' | 'default' | 'large' | 'default' | 尺寸 |
| disabled | boolean | false | 禁用输入与刷新 |
| load-fail-text | string | '加载失败' | 图加载失败文案 |
事件
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:model-value | (code: string) | 输入变化(换图自动清空时也回发空串) |
| change | (code: string, captchaId?: string) | 输入变化(remote 模式携带 captchaId) |
| verify | (payload: { valid, input, captchaId?, failCount }) | 校验结果(输满自动触发或 verify() 主动触发) |
| refresh | (captchaId?: string) | 换图:local 重生成 / remote 由业务重新拉图 |
插槽
| 插槽名 | 说明 |
|---|---|
| — | 无插槽 |
Expose
| 名称 | 类型 | 说明 |
|---|---|---|
| verify | () => { valid, input, captchaId?, failCount } | 主动校验并返回结果 |
| refresh | () => void | 换图 |
| reset | () => void | 重置(清空输入、归零失败计数、换图) |
| focus | () => void | 聚焦输入框 |