添加行为验证码

This commit is contained in:
2026-09-10 20:32:19 +08:00
parent ff19c2a0fb
commit c982ecee68
6 changed files with 137 additions and 44 deletions
+3
View File
@@ -2,6 +2,9 @@
VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
# 后台请求地址
VITE_API_TARGET=http://localhost:8080
# 开发环境配置
VITE_APP_ENV = 'development'
-11
View File
@@ -1,11 +0,0 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped lang="scss">
</style>
+94
View File
@@ -0,0 +1,94 @@
// 返回状态定义
export type CaptchaResult = -1 | 0 | 1
type CaptchaStatusCallback = (status: CaptchaResult) => void
/**
* 唤起 tianai TAC行为验证码
* ✅ 动态创建全局遮罩容器,无需模板写div
* ✅ 弹窗视口永久居中,全局最高层级
* ✅ 完全对齐官方loadTAC配置参数
* @param onStatusChange 状态回调,0=未完成
* @returns Promise<CaptchaResult> 1成功,-1失败/关闭/加载异常
*/
export async function showTACCaptcha(onStatusChange?: CaptchaStatusCallback): Promise<CaptchaResult> {
await nextTick()
return new Promise((resolve) => {
// ========= 动态创建全局全屏遮罩容器 =========
const maskWrap = document.createElement('div')
const tempId = 'tac-captcha-temp-' + Date.now()
maskWrap.id = tempId
// 全局顶层全屏遮罩 + flex居中
maskWrap.style.position = 'fixed'
maskWrap.style.top = '0'
maskWrap.style.left = '0'
maskWrap.style.width = '100vw'
maskWrap.style.height = '100vh'
maskWrap.style.zIndex = '99999'
maskWrap.style.backgroundColor = 'rgba(0,0,0,0.5)'
maskWrap.style.display = 'flex'
maskWrap.style.alignItems = 'center'
maskWrap.style.justifyContent = 'center'
maskWrap.style.margin = '0'
maskWrap.style.padding = '0'
maskWrap.style.overflow = 'hidden'
document.body.appendChild(maskWrap)
const bindSelector = `#${tempId}`
// 打开弹窗,状态=未完成
onStatusChange?.(0)
const captchaConfig = {
// 请求验证码接口
requestCaptchaDataUrl: "http://localhost:8080/resource/captcha/behavior/generate",
// 验证验证码接口
validCaptchaUrl: "http://localhost:8080/resource/captcha/behavior/verify",
// 绑定的div
bindEl: bindSelector,
// 验证成功回调函数
validSuccess: (res, c, t) => {
console.log("验证码验证成功回调...")
// 销毁验证码
t.destroyWindow();
document.body.removeChild(maskWrap)
console.log(res)
onStatusChange?.(1)
resolve(1) // ✅ 成功,返回1,await解除阻塞
},
// 验证失败的回调函数(可忽略,如果不自定义 validFail 方法时,会使用默认的)
validFail: (res, c, t) => {
console.log("验证码验证失败回调...");
// 验证失败后重新拉取验证码
t.reloadCaptcha();
},
// 刷新按钮回调事件
btnRefreshFun: (el, tac) => {
console.log("刷新按钮触发事件...");
tac.reloadCaptcha();
},
// 关闭按钮回调事件
btnCloseFun: (el, tac) => {
console.log("关闭按钮触发事件...");
tac.destroyWindow();
document.body.removeChild(maskWrap)
onStatusChange?.(-1)
resolve(-1) // ✅ 用户关闭弹窗,返回 -1
}
}
// 一些样式配置, 可不传
let style = {
logoUrl: null// 去除logo
}
// 参数1 为 tac文件是目录地址, 目录里包含 tac的js和css等文件
// 参数2 为 tac验证码相关配置
// 参数3 为 tac窗口一些样式配置
window.initTAC("./tac", captchaConfig, style).then(tac => {
tac.init(); // 调用init则显示验证码
}).catch(e => {
console.log("初始化tac失败", e);
document.body.removeChild(maskWrap)
onStatusChange?.(-1)
resolve(-1) // ✅ 初始化异常,返回 -1
})
})
}
+16 -31
View File
@@ -5,39 +5,24 @@
</div>
</template>
<script setup name="Index" lang="ts">
<script setup lang="ts">
import {CaptchaResult,showTACCaptcha} from "@/utils/captcha";
async function login() {
await nextTick()
const captchaConfig = {
// 请求验证码接口
requestCaptchaDataUrl: "http://localhost:8080/resource/captcha/behavior/generate",
// 验证验证码接口
validCaptchaUrl: "http://localhost:8080/resource/captcha/behavior/verify",
// 绑定的div
bindEl: "#captcha-box",
// 验证成功回调函数
validSuccess: (res, c, t) => {
console.log("验证码验证成功回调...")
// 销毁验证码
t.destroyWindow();
alert("验证成功: token:" + res.data.token)
console.log(res)
// todo 携带token调用登录接口
}
}
// 一些样式配置, 可不传
let style = {
logoUrl: null// 去除logo
// logoUrl: "/xx/xx/xxx.png" // 替换成自定义的logo
// 参数1 为 tac文件是目录地址, 目录里包含 tac的js和css等文件
// 参数2 为 tac验证码相关配置
// 参数3 为 tac窗口一些样式配置
}
window.initTAC("./tac", captchaConfig, style).then(tac => {
tac.init(); // 调用init则显示验证码
}).catch(e => {
console.log("初始化tac失败", e);
const captchaRes: CaptchaResult = await showTACCaptcha((status) => {
console.log('验证码实时状态:', status)
})
switch (captchaRes) {
case 1:
console.log("✅验证码校验成功,拿token调用登录接口")
// todo: 在这里调用登录,带上后端返回的token
break
case -1:
console.log("❌ 关闭弹窗 / 加载失败")
break
}
}
</script>
+22
View File
@@ -121,6 +121,7 @@ import { authRouterUrl } from '@/api/system/social/auth';
import { LoginData } from '@/api/types';
import { HttpStatus } from '@/enums/RespEnum';
import { useUserStore } from '@/store/modules/user';
import {CaptchaResult, showTACCaptcha} from "@/utils/captcha";
const title = import.meta.env.VITE_APP_TITLE;
const currentYear = new Date().getFullYear();
@@ -185,6 +186,26 @@ const handleLogin = () => {
loginRef.value?.validate(async (valid: boolean, fields: any) => {
if (valid) {
loading.value = true;
const captchaRes: CaptchaResult = await showTACCaptcha((status) => {
console.log('验证码实时状态:', status)
})
console.log("captchaRes结果:", captchaRes) // 打印看实际返回值!重点调试
switch (captchaRes) {
case 1:
console.log("✅验证码校验成功,拿token调用登录接口")
break // 跳出switch,继续往下执行登录
case -1:
console.log("❌ 关闭弹窗 / 加载失败")
loading.value = false;
return; // 退出当前async回调,不再执行后面登录代码
default:
console.log("⚠️ 验证码其他状态", captchaRes)
loading.value = false;
return;
}
// ========== 验证码成功才会执行到这里 ==========
if (loginForm.value.rememberMe) {
localStorage.setItem('username', String(loginForm.value.username));
localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
@@ -210,6 +231,7 @@ const handleLogin = () => {
});
};
const getCode = async () => {
const res = await getCodeImg();
const { data } = res;
+2 -2
View File
@@ -1,5 +1,5 @@
import { defineConfig, loadEnv } from 'vite';
import createPlugins from './vite/plugins';
import createPlugins from './vite/plugins/index';
import autoprefixer from 'autoprefixer'; // css自动添加兼容性前缀
export default defineConfig(({ mode, command }) => {
@@ -30,7 +30,7 @@ export default defineConfig(({ mode, command }) => {
open: true,
proxy: {
[env.VITE_APP_BASE_API]: {
target: 'http://localhost:8080',
target: env.VITE_API_TARGET,
changeOrigin: true,
ws: true,
rewrite: path => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')