Appearance
BizSliderCaptcha 滑块验证码
滑块行为验证交互件:拖到底触发校验——request 注入后端风控(payload 含位移 / 轨迹采样 / 时长,供机器轨迹识别),未注入时纯前端交互模式(防脚本提交的行为门槛)。通过锁定、失败抖动回弹自动复位、reset() 编程复位。登录 / 注册 / 短信发送前的防刷标准件。
基础用法
按住滑块,拖动到最右侧完成验证
未验证
纯前端模式:拖到底即通过并锁定;
reset() 在表单提交失败后复位重验。<script setup lang="ts">
/** 基础用法:纯前端交互模式(拖到底即通过,锁定)+ reset 编程复位 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizSliderCaptcha } from 'vue3-ep-biz-lib'
import type { BizSliderCaptchaInstance } from 'vue3-ep-biz-lib'
const captchaRef = ref<BizSliderCaptchaInstance | null>(null)
const passed = ref(false)
const onSuccess = (): void => {
passed.value = true
ElMessage.success('滑块验证通过')
}
</script>
<template>
<div>
<BizSliderCaptcha ref="captchaRef" @success="onSuccess" />
<div class="ops">
<ElTag :type="passed ? 'success' : 'info'">{{ passed ? '已通过' : '未验证' }}</ElTag>
<ElButton size="small" @click="() => { captchaRef?.reset(); passed = false }">reset() 重置</ElButton>
</div>
</div>
</template>
<style scoped>
.ops {
display: flex;
align-items: center;
gap: 8px;
margin-top: 12px;
}
</style>
显示代码
后端风控注入
按住滑块拖动完成安全校验
request 拒绝即失败(抖动回弹自动复位);payload 含 x / 轨迹采样 / 时长,供后端行为风控
request resolve 通过 / reject 失败(mock 第二次拒绝,演示抖动回弹);强安全场景务必接后端校验。<script setup lang="ts">
/** 进阶:request 注入后端风控(含轨迹)——mock 轨迹过直判失败 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizSliderCaptcha } from 'vue3-ep-biz-lib'
import type { BizSliderCaptchaPayload } from 'vue3-ep-biz-lib'
const count = ref(0)
/** mock 后端:首次放行,之后模拟轨迹风控拒绝 */
const request = async (_payload: BizSliderCaptchaPayload): Promise<void> => {
await new Promise((resolve) => setTimeout(resolve, 500))
count.value += 1
if (count.value > 1) {
throw new Error('轨迹异常')
}
}
const onFail = (): void => {
ElMessage.error('后端风控拒绝:轨迹异常,请重试')
}
</script>
<template>
<div>
<BizSliderCaptcha :request="request" :width="360" text="按住滑块拖动完成安全校验" @fail="onFail" />
<div class="tip">request 拒绝即失败(抖动回弹自动复位);payload 含 x / 轨迹采样 / 时长,供后端行为风控</div>
</div>
</template>
<style scoped>
.tip {
margin-top: 12px;
font-size: 13px;
color: var(--el-text-color-secondary);
}
</style>
显示代码
登录表单集成
+86
按住滑块,拖动到最右侧完成验证
通过后登录按钮才可用(防脚本撞库的最小集成)。
<script setup lang="ts">
/** 登录集成:滑块通过后才能点登录 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizSliderCaptcha } from 'vue3-ep-biz-lib'
const passed = ref(false)
const phone = ref('')
const code = ref('')
const login = (): void => {
ElMessage.success(`登录成功(${phone.value || '138****5678'})`)
}
</script>
<template>
<div class="login">
<ElInput v-model="phone" placeholder="手机号" style="width: 100%">
<template #prepend>+86</template>
</ElInput>
<ElInput v-model="code" placeholder="短信验证码" style="width: 100%" />
<BizSliderCaptcha @success="passed = true" />
<ElButton type="primary" style="width: 100%" :disabled="!passed" @click="login">
{{ passed ? '登 录' : '请先完成滑块验证' }}
</ElButton>
</div>
</template>
<style scoped>
.login {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 320px;
}
</style>
显示代码
连续失败锁定
按住滑块,拖动到最右侧完成验证
已失败 0 次
fail 事件计数达阈值锁定,降级改用短信验证码。
<script setup lang="ts">
/** 连续失败锁定:fail 计数达 3 次提示改用短信验证 */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizSliderCaptcha } from 'vue3-ep-biz-lib'
import type { BizSliderCaptchaInstance } from 'vue3-ep-biz-lib'
const fails = ref(0)
const locked = ref(false)
const captchaRef = ref<BizSliderCaptchaInstance | null>(null)
const request = async (): Promise<void> => {
await new Promise((resolve) => setTimeout(resolve, 400))
fails.value += 1
throw new Error('mock 拒绝')
}
const onFail = (): void => {
if (fails.value >= 3) {
locked.value = true
ElMessage.warning('失败次数过多,请改用短信验证码登录')
}
}
const unlock = (): void => {
fails.value = 0
locked.value = false
captchaRef.value?.reset()
}
</script>
<template>
<div>
<BizSliderCaptcha v-if="!locked" :request="request" @fail="onFail" />
<ElAlert v-else type="warning" :closable="false" title="滑块验证已锁定" description="连续失败 3 次,请改用短信验证码或稍后再试。" />
<div class="ops">
<span class="tip">已失败 {{ fails }} 次</span>
<ElButton size="small" @click="unlock">重置计数并解锁</ElButton>
</div>
</div>
</template>
<style scoped>
.ops {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.tip {
font-size: 13px;
color: var(--el-text-color-secondary);
}
</style>
显示代码
拖动轨迹可视化
按住滑块,拖动到最右侧完成验证
采样 0 点 · 时长 0ms(匀速直线 = 高风险)
payload.trail 采样画折线——与后端风控消费的是同一份数据。
<script setup lang="ts">
/** 轨迹可视化:fail/success payload 的 trail 采样画折线(后端风控同款数据) */
import { ref } from 'vue'
import { BizSliderCaptcha } from 'vue3-ep-biz-lib'
import type { BizSliderCaptchaPayload } from 'vue3-ep-biz-lib'
const trail = ref<number[]>([])
const duration = ref(0)
const points = ref('')
const draw = (payload: BizSliderCaptchaPayload): void => {
trail.value = payload.trail
duration.value = payload.duration
const max = Math.max(...payload.trail, 1)
points.value = payload.trail
.map((x, i) => `${(i / Math.max(payload.trail.length - 1, 1)) * 280},${36 - (x / max) * 32}`)
.join(' ')
}
const request = async (payload: BizSliderCaptchaPayload): Promise<void> => {
await new Promise((resolve) => setTimeout(resolve, 300))
draw(payload)
}
</script>
<template>
<div>
<BizSliderCaptcha :request="request" @fail="draw" />
<div class="chart">
<svg width="280" height="40" style="border: 1px dashed var(--el-border-color); border-radius: 4px">
<polyline :points="points" fill="none" stroke="var(--el-color-primary)" stroke-width="1.5" />
</svg>
<div class="meta">采样 {{ trail.length }} 点 · 时长 {{ duration }}ms(匀速直线 = 高风险)</div>
</div>
</div>
</template>
<style scoped>
.chart {
margin-top: 12px;
}
.meta {
margin-top: 6px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
</style>
显示代码
发短信前置校验
1
拖动滑块完成安全校验后可发短信
2未通过校验前此按钮链路不可达(业务侧在 onSend 前判断)
与 BizCountdownButton 组成防刷链路:滑块 → 验证码。
<script setup lang="ts">
/** 发短信前置滑块:通过后触发验证码按钮(防刷关键链路) */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizCountdownButton, BizSliderCaptcha } from 'vue3-ep-biz-lib'
const passed = ref(false)
const sent = ref(false)
const onSend = (): void => {
sent.value = true
ElMessage.success('验证码已发送(滑块通过后才允许触发)')
}
</script>
<template>
<div class="box">
<div class="step">
<span class="no">1</span>
<BizSliderCaptcha
v-if="!passed"
:lock="true"
text="拖动滑块完成安全校验后可发短信"
@success="passed = true"
/>
<ElTag v-else type="success">安全校验通过</ElTag>
</div>
<div class="step">
<span class="no">2</span>
<BizCountdownButton :duration="60" start-text="发送验证码" @click="onSend" />
<span class="tip">{{ sent ? '已发送,60s 冷却中' : '未通过校验前此按钮链路不可达(业务侧在 onSend 前判断)' }}</span>
</div>
</div>
</template>
<style scoped>
.box {
display: flex;
flex-direction: column;
gap: 14px;
}
.step {
display: flex;
align-items: center;
gap: 10px;
}
.no {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--el-color-primary);
color: #fff;
font-size: 12px;
flex-shrink: 0;
}
.tip {
font-size: 12px;
color: var(--el-text-color-secondary);
}
</style>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| request | (payload) => Promise<void> | — | 校验请求(后端风控);缺省纯前端模式 |
| width | number | 320 | 轨道宽度(px) |
| height | number | 40 | 轨道高度(px) |
| text | string | '按住滑块,拖动到最右侧完成验证' | 引导文案 |
| success-text | string | '验证通过' | 通过文案 |
| fail-text | string | '验证失败,请重试' | 失败文案 |
| lock | boolean | true | 通过后锁定不可再拖 |
BizSliderCaptchaPayload:x 位移、trail 轨迹采样数组、duration 拖动时长(ms)。
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| success | — | 校验通过 |
| fail | (payload) | 校验失败(含轨迹,可上报风控日志) |
Slots
| 插槽名 | 说明 |
|---|---|
| — | 无插槽 |
Expose
| 属性/方法 | 说明 |
|---|---|
| reset | () => void 复位重验 |