验证码调试页面
This commit is contained in:
parent
a22f9e5ff4
commit
3e331f58ce
3
components.d.ts
vendored
3
components.d.ts
vendored
@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
ElAside: typeof import('element-plus/es')['ElAside']
|
ElAside: typeof import('element-plus/es')['ElAside']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
@ -32,6 +33,8 @@ declare module '@vue/runtime-core' {
|
|||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
|
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||||
|
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
|
|||||||
@ -33,6 +33,14 @@ const menus: MenuGroup[] = [
|
|||||||
{ title: '图片资源库', path: '/api/sourceImage', permission: 'sourceImage:list' },
|
{ title: '图片资源库', path: '/api/sourceImage', permission: 'sourceImage:list' },
|
||||||
{ title: '歌曲库', path: '/api/music', permission: 'music:list' }
|
{ title: '歌曲库', path: '/api/music', permission: 'music:list' }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'debug',
|
||||||
|
title: '调试工具',
|
||||||
|
icon: 'Tools',
|
||||||
|
child: [
|
||||||
|
{ title: '图形验证码', path: '/debug/captcha', permission: 'captcha:list' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,8 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
{ path: '/api/hitokoto', name: 'Hitokoto', component: () => import('@/views/api/Hitokoto.vue'), meta: { title: '一言' } },
|
{ path: '/api/hitokoto', name: 'Hitokoto', component: () => import('@/views/api/Hitokoto.vue'), meta: { title: '一言' } },
|
||||||
{ path: '/api/photoWall', name: 'PhotoWall', component: () => import('@/views/api/PhotoWall.vue'), meta: { title: '照片墙' } },
|
{ path: '/api/photoWall', name: 'PhotoWall', component: () => import('@/views/api/PhotoWall.vue'), meta: { title: '照片墙' } },
|
||||||
{ path: '/api/sourceImage', name: 'SourceImage', component: () => import('@/views/api/SourceImage.vue'), meta: { title: '图片资源库' } },
|
{ path: '/api/sourceImage', name: 'SourceImage', component: () => import('@/views/api/SourceImage.vue'), meta: { title: '图片资源库' } },
|
||||||
|
|
||||||
|
{ path: '/debug/captcha', name: 'CaptchaSandbox', component: () => import('@/views/debug/CaptchaSandbox.vue'), meta: { title: '图形验证码' } },
|
||||||
]}
|
]}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
193
src/vendor/tianai-captcha/captcha/captcha.js
vendored
Normal file
193
src/vendor/tianai-captcha/captcha/captcha.js
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import "./captcha.less";
|
||||||
|
import Slider from "./slider/slider";
|
||||||
|
import Rotate from "./rotate/rotate";
|
||||||
|
import Concat from "./concat/concat";
|
||||||
|
import Disable from "./disable/disable";
|
||||||
|
import ImageClick from "./image_click/image_click";
|
||||||
|
import WordImageClick from "./word_image_click/word_image_click";
|
||||||
|
import { CaptchaConfig, wrapConfig, wrapStyle } from "./config/config";
|
||||||
|
import { clearAllPreventDefault } from "./common/common";
|
||||||
|
const template = `
|
||||||
|
<div id="tianai-captcha-parent">
|
||||||
|
<div id="tianai-captcha-bg-img"></div>
|
||||||
|
<div id="tianai-captcha-box">
|
||||||
|
<div id="tianai-captcha-loading" class="loading"></div>
|
||||||
|
</div>
|
||||||
|
<!-- 底部 -->
|
||||||
|
<div class="slider-bottom">
|
||||||
|
<img class="logo" id="tianai-captcha-logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAMAAAAM7l6QAAAAMFBMVEVHcEz3tkX3tkX3tkX3tkX3tkX3tkX3tkX3tkX3tkX3tkX3tkX3tkX3tkX3tkX3tkVmTmjZAAAAD3RSTlMASbTm8wh12hOGoCNiyTV98jvOAAABB0lEQVR42nVT0aIFEQiMorD0/397Lc5a7J0n1UylgIniLRKyDcbBDudZH2DYCAabn3PmTrjeUX+7rJGWx0SqVpzReAfTtKU5fgVCNfxWjB69USUDGwoOiauHpZEpSr0tCx8ILb3Dm3WgBbAlifAJk6+Ww6wqEUmpmIorQVZ1JtqKnDMjkb7AgIpO/wMCaQbuBuEtsBUxhuD9daUaZnApiQB8NAKotMwirGGr6mbXpPnHLHDmy6oy3FgP+1j8IBdVklFc01xUJwv3NR0rIeXV5zpzdlruiijzNq/ufOeKWzZLP3160u5P8RjT1M+HHFtx+PwGyOZqT/D8ROOfjOInTLBIHjy/hvwHxkwPu5cCE1QAAAAASUVORK5CYII=" id="tianai-captcha-logo"></img>
|
||||||
|
<div class="close-btn" id="tianai-captcha-slider-close-btn"></div>
|
||||||
|
<div class="refresh-btn" id="tianai-captcha-slider-refresh-btn"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
function createCaptchaByType(type, tac) {
|
||||||
|
const box = tac.config.domBindEl.find("#tianai-captcha-box");
|
||||||
|
const styleConfig = tac.style;
|
||||||
|
switch (type) {
|
||||||
|
case "SLIDER":
|
||||||
|
return new Slider(box, styleConfig);
|
||||||
|
case "ROTATE":
|
||||||
|
case "ROTATE_DEGREE":
|
||||||
|
return new Rotate(box, styleConfig);
|
||||||
|
case "CONCAT":
|
||||||
|
return new Concat(box, styleConfig);
|
||||||
|
case "IMAGE_CLICK":
|
||||||
|
return new ImageClick(box, styleConfig);
|
||||||
|
case "WORD_IMAGE_CLICK":
|
||||||
|
return new WordImageClick(box, styleConfig);
|
||||||
|
case "DISABLED":
|
||||||
|
return new Disable(box, styleConfig);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class TianAiCaptcha {
|
||||||
|
constructor(config, style) {
|
||||||
|
this.config = wrapConfig(config);
|
||||||
|
if (this.config.btnRefreshFun) {
|
||||||
|
this.btnRefreshFun = this.config.btnRefreshFun;
|
||||||
|
}
|
||||||
|
if (this.config.btnCloseFun) {
|
||||||
|
this.btnCloseFun = this.config.btnCloseFun;
|
||||||
|
}
|
||||||
|
this.style = wrapStyle(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.destroyWindow();
|
||||||
|
this.config.domBindEl.append(template);
|
||||||
|
this.domTemplate = this.config.domBindEl.find("#tianai-captcha-parent");
|
||||||
|
clearAllPreventDefault(this.domTemplate);
|
||||||
|
this.loadStyle();
|
||||||
|
// 绑定按钮事件
|
||||||
|
this.config.domBindEl
|
||||||
|
.find("#tianai-captcha-slider-refresh-btn")
|
||||||
|
.click((el) => {
|
||||||
|
this.btnRefreshFun(el, this);
|
||||||
|
});
|
||||||
|
this.config.domBindEl
|
||||||
|
.find("#tianai-captcha-slider-close-btn")
|
||||||
|
.click((el) => {
|
||||||
|
this.btnCloseFun(el, this);
|
||||||
|
});
|
||||||
|
// 加载验证码
|
||||||
|
this.reloadCaptcha();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
btnRefreshFun(el, tac) {
|
||||||
|
tac.reloadCaptcha();
|
||||||
|
}
|
||||||
|
btnCloseFun(el, tac) {
|
||||||
|
tac.destroyWindow();
|
||||||
|
}
|
||||||
|
reloadCaptcha() {
|
||||||
|
this.showLoading();
|
||||||
|
this.destroyCaptcha(() => {
|
||||||
|
this.createCaptcha();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
showLoading() {
|
||||||
|
this.config.domBindEl
|
||||||
|
.find("#tianai-captcha-loading")
|
||||||
|
.css("display", "block");
|
||||||
|
}
|
||||||
|
|
||||||
|
closeLoading() {
|
||||||
|
this.config.domBindEl
|
||||||
|
.find("#tianai-captcha-loading")
|
||||||
|
.css("display", "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStyle() {
|
||||||
|
// 设置样式
|
||||||
|
const bgUrl = this.style.bgUrl;
|
||||||
|
const logoUrl = this.style.logoUrl;
|
||||||
|
if (bgUrl) {
|
||||||
|
// 背景图片
|
||||||
|
this.config.domBindEl
|
||||||
|
.find("#tianai-captcha-bg-img")
|
||||||
|
.css("background-image", "url(" + bgUrl + ")");
|
||||||
|
}
|
||||||
|
if (logoUrl && logoUrl !== "") {
|
||||||
|
// logo
|
||||||
|
this.config.domBindEl.find("#tianai-captcha-logo").attr("src", logoUrl);
|
||||||
|
} else if (logoUrl === null) {
|
||||||
|
// 删除logo
|
||||||
|
this.config.domBindEl.find("#tianai-captcha-logo").css("display", "none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyWindow() {
|
||||||
|
if (this.C) {
|
||||||
|
this.C.destroy();
|
||||||
|
this.C = undefined;
|
||||||
|
}
|
||||||
|
if (this.domTemplate) {
|
||||||
|
this.domTemplate.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openCaptcha() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.C.el.css("transform", "translateX(0)");
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCaptcha() {
|
||||||
|
this.config.requestCaptchaData().then((data) => {
|
||||||
|
this.closeLoading();
|
||||||
|
if (!data.code) {
|
||||||
|
throw new Error("[TAC] 后台验证码接口数据错误!!!");
|
||||||
|
}
|
||||||
|
let captchaType = data.code === 200 ? data.data?.type : "DISABLED";
|
||||||
|
const captcha = createCaptchaByType(captchaType, this);
|
||||||
|
if (captcha == null) {
|
||||||
|
throw new Error("[TAC] 未知的验证码类型[" + captchaType + "]");
|
||||||
|
}
|
||||||
|
captcha.init(data, (d, c) => {
|
||||||
|
// 验证
|
||||||
|
const currentCaptchaData = c.currentCaptchaData;
|
||||||
|
const data = {
|
||||||
|
bgImageWidth: currentCaptchaData.bgImageWidth,
|
||||||
|
bgImageHeight: currentCaptchaData.bgImageHeight,
|
||||||
|
templateImageWidth: currentCaptchaData.templateImageWidth,
|
||||||
|
templateImageHeight: currentCaptchaData.templateImageHeight,
|
||||||
|
startTime: currentCaptchaData.startTime.getTime(),
|
||||||
|
stopTime: currentCaptchaData.stopTime.getTime(),
|
||||||
|
trackList: currentCaptchaData.trackList,
|
||||||
|
};
|
||||||
|
if (c.type === "ROTATE_DEGREE" || c.type === "ROTATE") {
|
||||||
|
data.bgImageWidth = c.currentCaptchaData.end;
|
||||||
|
}
|
||||||
|
if (currentCaptchaData.data) {
|
||||||
|
data.data = currentCaptchaData.data;
|
||||||
|
}
|
||||||
|
// 清空
|
||||||
|
const id = c.currentCaptchaData.currentCaptchaId;
|
||||||
|
c.currentCaptchaData = undefined;
|
||||||
|
// 调用验证接口
|
||||||
|
this.config.validCaptcha(id, data, c, this);
|
||||||
|
});
|
||||||
|
this.C = captcha;
|
||||||
|
this.openCaptcha();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyCaptcha(callback) {
|
||||||
|
if (this.C) {
|
||||||
|
this.C.el.css("transform", "translateX(300px)");
|
||||||
|
setTimeout(() => {
|
||||||
|
this.C.destroy();
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { TianAiCaptcha, CaptchaConfig };
|
||||||
104
src/vendor/tianai-captcha/captcha/captcha.less
vendored
Normal file
104
src/vendor/tianai-captcha/captcha/captcha.less
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#tianai-captcha-parent {
|
||||||
|
box-shadow: 0 0 11px 0 #999999;
|
||||||
|
width: 318px;
|
||||||
|
height: 318px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 997;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 8px;
|
||||||
|
#tianai-captcha-box {
|
||||||
|
height: 260px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
.loading {
|
||||||
|
width: 120px;
|
||||||
|
height: 20px;
|
||||||
|
mask: linear-gradient(90deg, #000 70%, #0000 0) 0/20%;
|
||||||
|
background: linear-gradient(#f7b645 0 0) 0 / 0% no-repeat #dddddd6b;
|
||||||
|
animation: cartoon 1s infinite steps(6);
|
||||||
|
margin: 120px auto;
|
||||||
|
@keyframes cartoon {
|
||||||
|
100% {
|
||||||
|
background-size: 120%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#tianai-captcha {
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
will-change: transform;
|
||||||
|
transition-duration: 0.45s;
|
||||||
|
transform: translateX(-300px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#tianai-captcha-bg-img {
|
||||||
|
background-color: #fff;
|
||||||
|
background-position: top;
|
||||||
|
background-size: cover;
|
||||||
|
z-index: -1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 6px;
|
||||||
|
//background-image: url("");
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-bottom {
|
||||||
|
.close-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-image: url("../images/icon.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 0 -14px;
|
||||||
|
float: right;
|
||||||
|
margin-right: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.refresh-btn {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-image: url("../images/icon.png");
|
||||||
|
background-position: 0 -167px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
float: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
height: 30px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
height: 19px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.slider-move-shadow {
|
||||||
|
animation: myanimation 2s infinite;
|
||||||
|
height: 100%;
|
||||||
|
width: 5px;
|
||||||
|
background-color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
filter: opacity(0.5);
|
||||||
|
box-shadow: 1px 1px 1px #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
#tianai-captcha-slider-move-track-mask {
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #00f4ab;
|
||||||
|
|
||||||
|
width: 0;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #a9ffe5;
|
||||||
|
opacity: 0.5;
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: -1px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
462
src/vendor/tianai-captcha/captcha/common/common.js
vendored
Normal file
462
src/vendor/tianai-captcha/captcha/common/common.js
vendored
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
/** 是否打印日志 */
|
||||||
|
var isPrintLog = false;
|
||||||
|
|
||||||
|
function printLog(params) {
|
||||||
|
if (isPrintLog) {
|
||||||
|
console.log(JSON.stringify(params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除默认事件
|
||||||
|
* @param event event
|
||||||
|
*/
|
||||||
|
function clearPreventDefault(event) {
|
||||||
|
if (event.preventDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阻止某div默认事件
|
||||||
|
* @param dom
|
||||||
|
*/
|
||||||
|
function clearAllPreventDefault(dom) {
|
||||||
|
Dom(dom).each((el) => {
|
||||||
|
// 手机端
|
||||||
|
el.addEventListener("touchmove", clearPreventDefault, { passive: false });
|
||||||
|
// pc端
|
||||||
|
el.addEventListener("mousemove", clearPreventDefault, { passive: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前坐标
|
||||||
|
* @param event 事件
|
||||||
|
* @returns {{x: number, y: number}}
|
||||||
|
*/
|
||||||
|
function getCurrentCoordinate(event) {
|
||||||
|
if (event.pageX !== null && event.pageX !== undefined) {
|
||||||
|
return {
|
||||||
|
x: Math.round(event.pageX),
|
||||||
|
y: Math.round(event.pageY),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let targetTouches;
|
||||||
|
if (event.changedTouches) {
|
||||||
|
// 抬起事件
|
||||||
|
targetTouches = event.changedTouches;
|
||||||
|
} else if (event.targetTouches) {
|
||||||
|
// pc 按下事件
|
||||||
|
targetTouches = event.targetTouches;
|
||||||
|
} else if (event.originalEvent && event.originalEvent.targetTouches) {
|
||||||
|
// 鼠标触摸事件
|
||||||
|
targetTouches = event.originalEvent.targetTouches;
|
||||||
|
}
|
||||||
|
if (targetTouches[0].pageX !== null && targetTouches[0].pageX !== undefined) {
|
||||||
|
return {
|
||||||
|
x: Math.round(targetTouches[0].pageX),
|
||||||
|
y: Math.round(targetTouches[0].pageY),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: Math.round(targetTouches[0].clientX),
|
||||||
|
y: Math.round(targetTouches[0].clientY),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function down(currentCaptcha, event) {
|
||||||
|
// debugger
|
||||||
|
const coordinate = getCurrentCoordinate(event);
|
||||||
|
let startX = coordinate.x;
|
||||||
|
let startY = coordinate.y;
|
||||||
|
currentCaptcha.currentCaptchaData.startX = startX;
|
||||||
|
currentCaptcha.currentCaptchaData.startY = startY;
|
||||||
|
const trackList = currentCaptcha.currentCaptchaData.trackList;
|
||||||
|
currentCaptcha.currentCaptchaData.startTime = new Date();
|
||||||
|
const startTime = currentCaptcha.currentCaptchaData.startTime;
|
||||||
|
|
||||||
|
trackList.push({
|
||||||
|
x: coordinate.x,
|
||||||
|
y: coordinate.y,
|
||||||
|
type: "down",
|
||||||
|
t: new Date().getTime() - startTime.getTime(),
|
||||||
|
});
|
||||||
|
printLog(["start", startX, startY]);
|
||||||
|
currentCaptcha.__m__ = move.bind(null, currentCaptcha);
|
||||||
|
currentCaptcha.__u__ = up.bind(null, currentCaptcha);
|
||||||
|
// pc
|
||||||
|
window.addEventListener("mousemove", currentCaptcha.__m__);
|
||||||
|
window.addEventListener("mouseup", currentCaptcha.__u__);
|
||||||
|
// 手机端
|
||||||
|
window.addEventListener("touchmove", currentCaptcha.__m__, false);
|
||||||
|
window.addEventListener("touchend", currentCaptcha.__u__, false);
|
||||||
|
if (currentCaptcha && currentCaptcha.doDown) {
|
||||||
|
currentCaptcha.doDown(event, currentCaptcha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function move(currentCaptcha, event) {
|
||||||
|
if (event.touches && event.touches.length > 0) {
|
||||||
|
event = event.touches[0];
|
||||||
|
}
|
||||||
|
// debugger
|
||||||
|
const coordinate = getCurrentCoordinate(event);
|
||||||
|
let pageX = coordinate.x;
|
||||||
|
let pageY = coordinate.y;
|
||||||
|
const startX = currentCaptcha.currentCaptchaData.startX;
|
||||||
|
const startY = currentCaptcha.currentCaptchaData.startY;
|
||||||
|
const startTime = currentCaptcha.currentCaptchaData.startTime;
|
||||||
|
const end = currentCaptcha.currentCaptchaData.end;
|
||||||
|
const bgImageWidth = currentCaptcha.currentCaptchaData.bgImageWidth;
|
||||||
|
const trackList = currentCaptcha.currentCaptchaData.trackList;
|
||||||
|
let moveX = pageX - startX;
|
||||||
|
let moveY = pageY - startY;
|
||||||
|
const track = {
|
||||||
|
x: coordinate.x,
|
||||||
|
y: coordinate.y,
|
||||||
|
type: "move",
|
||||||
|
t: new Date().getTime() - startTime.getTime(),
|
||||||
|
};
|
||||||
|
trackList.push(track);
|
||||||
|
if (moveX < 0) {
|
||||||
|
moveX = 0;
|
||||||
|
} else if (moveX > end) {
|
||||||
|
moveX = end;
|
||||||
|
}
|
||||||
|
currentCaptcha.currentCaptchaData.moveX = moveX;
|
||||||
|
currentCaptcha.currentCaptchaData.moveY = moveY;
|
||||||
|
if (currentCaptcha.doMove) {
|
||||||
|
currentCaptcha.doMove(event, currentCaptcha);
|
||||||
|
}
|
||||||
|
printLog(["move", track]);
|
||||||
|
}
|
||||||
|
function destroyEvent(currentCaptcha) {
|
||||||
|
if (currentCaptcha) {
|
||||||
|
if (currentCaptcha.__m__) {
|
||||||
|
window.removeEventListener("mousemove", currentCaptcha.__m__);
|
||||||
|
window.removeEventListener("touchmove", currentCaptcha.__m__);
|
||||||
|
}
|
||||||
|
if (currentCaptcha.__u__) {
|
||||||
|
window.removeEventListener("mouseup", currentCaptcha.__u__);
|
||||||
|
window.removeEventListener("touchend", currentCaptcha.__u__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function up(currentCaptcha, event) {
|
||||||
|
destroyEvent(currentCaptcha);
|
||||||
|
const coordinate = getCurrentCoordinate(event);
|
||||||
|
currentCaptcha.currentCaptchaData.stopTime = new Date();
|
||||||
|
const startTime = currentCaptcha.currentCaptchaData.startTime;
|
||||||
|
const trackList = currentCaptcha.currentCaptchaData.trackList;
|
||||||
|
|
||||||
|
const track = {
|
||||||
|
x: coordinate.x,
|
||||||
|
y: coordinate.y,
|
||||||
|
type: "up",
|
||||||
|
t: new Date().getTime() - startTime.getTime(),
|
||||||
|
};
|
||||||
|
|
||||||
|
trackList.push(track);
|
||||||
|
printLog(["up", track]);
|
||||||
|
printLog(["tracks", trackList]);
|
||||||
|
if (currentCaptcha.doUp) {
|
||||||
|
currentCaptcha.doUp(event, currentCaptcha);
|
||||||
|
}
|
||||||
|
currentCaptcha.endCallback(currentCaptcha.currentCaptchaData, currentCaptcha);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initConfig(
|
||||||
|
bgImageWidth,
|
||||||
|
bgImageHeight,
|
||||||
|
templateImageWidth,
|
||||||
|
templateImageHeight,
|
||||||
|
end,
|
||||||
|
) {
|
||||||
|
// bugfix 图片宽高可能会有小数情况,强转一下整数
|
||||||
|
const currentCaptchaConfig = {
|
||||||
|
startTime: new Date(),
|
||||||
|
trackList: [],
|
||||||
|
movePercent: 0,
|
||||||
|
clickCount: 0,
|
||||||
|
bgImageWidth: Math.round(bgImageWidth),
|
||||||
|
bgImageHeight: Math.round(bgImageHeight),
|
||||||
|
templateImageWidth: Math.round(templateImageWidth),
|
||||||
|
templateImageHeight: Math.round(templateImageHeight),
|
||||||
|
end: end,
|
||||||
|
};
|
||||||
|
printLog(["init", currentCaptchaConfig]);
|
||||||
|
return currentCaptchaConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeTips(el, callback) {
|
||||||
|
const tipEl = Dom(el).find("#tianai-captcha-tips");
|
||||||
|
tipEl.removeClass("tianai-captcha-tips-on");
|
||||||
|
// tipEl.removeClass("tianai-captcha-tips-success")
|
||||||
|
// tipEl.removeClass("tianai-captcha-tips-error")
|
||||||
|
// 延时
|
||||||
|
if (callback) {
|
||||||
|
setTimeout(callback, 0.35);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTips(el, msg, type, callback) {
|
||||||
|
const tipEl = Dom(el).find("#tianai-captcha-tips");
|
||||||
|
tipEl.text(msg);
|
||||||
|
if (type === 1) {
|
||||||
|
// 成功
|
||||||
|
tipEl.removeClass("tianai-captcha-tips-error");
|
||||||
|
tipEl.addClass("tianai-captcha-tips-success");
|
||||||
|
} else {
|
||||||
|
// 失败
|
||||||
|
tipEl.removeClass("tianai-captcha-tips-success");
|
||||||
|
tipEl.addClass("tianai-captcha-tips-error");
|
||||||
|
}
|
||||||
|
tipEl.addClass("tianai-captcha-tips-on");
|
||||||
|
// 延时
|
||||||
|
setTimeout(callback, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommonCaptcha {
|
||||||
|
showTips(msg, type, callback) {
|
||||||
|
showTips(this.el, msg, type, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeTips(msg, callback) {
|
||||||
|
closeTips(this.el, msg, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Dom(domStr, dom) {
|
||||||
|
return new DomEl(domStr, dom);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DomEl {
|
||||||
|
constructor(domStr, dom) {
|
||||||
|
if (dom instanceof HTMLElement) {
|
||||||
|
this.dom = dom;
|
||||||
|
this.domStr = domStr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (domStr instanceof DomEl) {
|
||||||
|
this.dom = domStr.dom;
|
||||||
|
this.domStr = domStr.domStr;
|
||||||
|
} else if (typeof domStr === "string") {
|
||||||
|
this.dom = document.querySelector(domStr);
|
||||||
|
this.domStr = domStr;
|
||||||
|
} else if (domStr instanceof HTMLElement) {
|
||||||
|
this.dom = domStr;
|
||||||
|
this.domStr = domStr.nodeName;
|
||||||
|
} else {
|
||||||
|
throw new Error("不支持的类型");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
each(callback) {
|
||||||
|
this.getTarget().querySelectorAll("*").forEach(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeClass(className) {
|
||||||
|
let element = this.getTarget();
|
||||||
|
if (element.classList) {
|
||||||
|
// 使用 classList API 移除类
|
||||||
|
element.classList.remove(className);
|
||||||
|
} else {
|
||||||
|
// 兼容旧版本浏览器
|
||||||
|
const currentClass = element.className;
|
||||||
|
const regex = new RegExp("(?:^|\\s)" + className + "(?!\\S)", "g");
|
||||||
|
element.className = currentClass.replace(regex, "");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
addClass(className) {
|
||||||
|
const element = this.getTarget();
|
||||||
|
if (element.classList) {
|
||||||
|
// 使用 classList API 添加类
|
||||||
|
element.classList.add(className);
|
||||||
|
} else {
|
||||||
|
// 兼容旧版本浏览器
|
||||||
|
let currentClass = element.className;
|
||||||
|
if (currentClass.indexOf(className) === -1) {
|
||||||
|
element.className = currentClass + " " + className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
find(str) {
|
||||||
|
const el = this.getTarget().querySelector(str);
|
||||||
|
if (el) {
|
||||||
|
return new DomEl(str, el);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
children(selector) {
|
||||||
|
const childNodes = this.getTarget().childNodes;
|
||||||
|
for (let i = 0; i < childNodes.length; i++) {
|
||||||
|
if (childNodes[i].nodeType === 1 && childNodes[i].matches(selector)) {
|
||||||
|
return new DomEl(selector, childNodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
this.getTarget().remove();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
css(property, value) {
|
||||||
|
if (typeof property === "string" && typeof value === "string") {
|
||||||
|
// 设置单个属性
|
||||||
|
this.getTarget().style[property] = value;
|
||||||
|
} else if (typeof property === "object") {
|
||||||
|
// 设置多个属性
|
||||||
|
for (var prop in property) {
|
||||||
|
if (property.hasOwnProperty(prop)) {
|
||||||
|
this.getTarget().style[prop] = property[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof property === "string" && typeof value === "undefined") {
|
||||||
|
// 获取单个属性
|
||||||
|
return window.getComputedStyle(element)[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attr(attributeName, value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
// 如果未提供值,则返回属性的当前值
|
||||||
|
return this.getTarget().getAttribute(attributeName);
|
||||||
|
} else {
|
||||||
|
// 如果提供了值,则设置属性的值
|
||||||
|
this.getTarget().setAttribute(attributeName, value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
text(str) {
|
||||||
|
this.getTarget().innerText = str;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
html(str) {
|
||||||
|
this.getTarget().innerHtml = str;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
is(dom) {
|
||||||
|
if (dom && typeof dom === "object" && typeof dom.nodeType !== "undefined") {
|
||||||
|
return this.dom === dom;
|
||||||
|
}
|
||||||
|
if (dom instanceof DomEl) {
|
||||||
|
return this.dom === dom.dom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append(content) {
|
||||||
|
if (typeof content === "string") {
|
||||||
|
this.getTarget().insertAdjacentHTML("beforeend", content);
|
||||||
|
} else if (content instanceof HTMLElement) {
|
||||||
|
this.getTarget().appendChild(content);
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid content type");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
click(fun) {
|
||||||
|
this.on("click", fun);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
mousedown(fun) {
|
||||||
|
this.on("mousedown", fun);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
touchstart(fun) {
|
||||||
|
this.on("touchstart", fun);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
on(eventType, fun) {
|
||||||
|
this.getTarget().addEventListener(eventType, fun, { passive: true });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
width() {
|
||||||
|
return this.getTarget().offsetWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
height() {
|
||||||
|
return this.getTarget().offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTarget() {
|
||||||
|
if (this.dom) {
|
||||||
|
return this.dom;
|
||||||
|
}
|
||||||
|
throw new Error("dom不存在: [" + this.domStr + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function http(options) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open(options.method || "GET", options.url);
|
||||||
|
// 设置请求头
|
||||||
|
if (options.headers) {
|
||||||
|
for (const header in options.headers) {
|
||||||
|
if (options.headers.hasOwnProperty(header)) {
|
||||||
|
xhr.setRequestHeader(header, options.headers[header]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
|
if (xhr.status >= 200 && xhr.status <= 500) {
|
||||||
|
const contentType = xhr.getResponseHeader("Content-Type");
|
||||||
|
if (contentType && contentType.indexOf("application/json") !== -1) {
|
||||||
|
resolve(JSON.parse(xhr.responseText));
|
||||||
|
} else {
|
||||||
|
resolve(xhr.responseText);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error("Request failed with status: " + xhr.status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onerror = function () {
|
||||||
|
reject(new Error("Network Error"));
|
||||||
|
};
|
||||||
|
xhr.send(options.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyObject(obj) {
|
||||||
|
for (var key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
return false; // 对象不为空
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; // 对象为空
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
isEmptyObject,
|
||||||
|
http,
|
||||||
|
Dom,
|
||||||
|
DomEl,
|
||||||
|
CommonCaptcha,
|
||||||
|
clearAllPreventDefault,
|
||||||
|
down,
|
||||||
|
move,
|
||||||
|
up,
|
||||||
|
initConfig,
|
||||||
|
showTips,
|
||||||
|
closeTips,
|
||||||
|
destroyEvent,
|
||||||
|
};
|
||||||
86
src/vendor/tianai-captcha/captcha/common/common.less
vendored
Normal file
86
src/vendor/tianai-captcha/captcha/common/common.less
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#tianai-captcha {
|
||||||
|
text-align: left;
|
||||||
|
box-sizing: content-box;
|
||||||
|
width: 300px;
|
||||||
|
height: 260px;
|
||||||
|
z-index: 999;
|
||||||
|
.slider-bottom .logo {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.slider-bottom {
|
||||||
|
height: 19px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
.tianai-captcha-tips {
|
||||||
|
height: 25px;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -25px;
|
||||||
|
left: 0;
|
||||||
|
z-index: 999;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 25px;
|
||||||
|
/*background-color: #FF5D39;*/
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
/* transform: translateY(0px); */
|
||||||
|
/* display: none; */
|
||||||
|
/* transition: max-height 0.5s; */
|
||||||
|
transition: bottom 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.tianai-captcha-tips.tianai-captcha-tips-error {
|
||||||
|
background-color: #ff5d39;
|
||||||
|
}
|
||||||
|
.tianai-captcha-tips.tianai-captcha-tips-success {
|
||||||
|
background-color: #39c522;
|
||||||
|
}
|
||||||
|
.tianai-captcha-tips.tianai-captcha-tips-on {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tianai-captcha-loading {
|
||||||
|
z-index: 9999;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#tianai-captcha-slider-bg-canvas {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
#tianai-captcha-slider-bg-div {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
.tianai-captcha-slider-bg-div-slice {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes myanimation {
|
||||||
|
from {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
left: 289px;
|
||||||
|
}
|
||||||
|
}
|
||||||
142
src/vendor/tianai-captcha/captcha/concat/concat.js
vendored
Normal file
142
src/vendor/tianai-captcha/captcha/concat/concat.js
vendored
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import "../common/common.less";
|
||||||
|
import "../slider/slider.less";
|
||||||
|
import "./concat.less";
|
||||||
|
import {
|
||||||
|
Dom,
|
||||||
|
CommonCaptcha,
|
||||||
|
clearAllPreventDefault,
|
||||||
|
down,
|
||||||
|
initConfig,
|
||||||
|
destroyEvent,
|
||||||
|
} from "../common/common.js";
|
||||||
|
|
||||||
|
const TYPE = "CONCAT";
|
||||||
|
|
||||||
|
function getTemplate(styleConfig) {
|
||||||
|
return `
|
||||||
|
<div id="tianai-captcha" class="tianai-captcha-slider tianai-captcha-concat">
|
||||||
|
<div class="slider-tip">
|
||||||
|
<span id="tianai-captcha-slider-move-track-font" >拖动滑块完成拼图</span>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="tianai-captcha-slider-concat-img-div" id="tianai-captcha-slider-concat-img-div">
|
||||||
|
<img id="tianai-captcha-slider-concat-slider-img" src="" alt/>
|
||||||
|
</div>
|
||||||
|
<div class="tianai-captcha-slider-concat-bg-img"></div>
|
||||||
|
<div class="tianai-captcha-tips" id="tianai-captcha-tips"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-move">
|
||||||
|
<div class="slider-move-track">
|
||||||
|
<div id="tianai-captcha-slider-move-track-mask"></div>
|
||||||
|
<div class="slider-move-shadow"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-move-btn" id="tianai-captcha-slider-move-btn">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Concat extends CommonCaptcha {
|
||||||
|
constructor(divId, styleConfig) {
|
||||||
|
super();
|
||||||
|
this.boxEl = Dom(divId);
|
||||||
|
this.styleConfig = styleConfig;
|
||||||
|
this.type = TYPE;
|
||||||
|
this.currentCaptchaData = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
init(captchaData, endCallback, loadSuccessCallback) {
|
||||||
|
// 重载样式
|
||||||
|
this.destroy();
|
||||||
|
this.boxEl.append(getTemplate(this.styleConfig));
|
||||||
|
this.el = this.boxEl.find("#tianai-captcha");
|
||||||
|
this.loadStyle();
|
||||||
|
// 按钮绑定事件
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.mousedown(down.bind(null, this));
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.touchstart(down.bind(null, this));
|
||||||
|
clearAllPreventDefault(this.el);
|
||||||
|
// 绑定全局
|
||||||
|
window.currentCaptcha = this;
|
||||||
|
// 载入验证码
|
||||||
|
this.loadCaptchaForData(this, captchaData);
|
||||||
|
this.endCallback = endCallback;
|
||||||
|
if (loadSuccessCallback) {
|
||||||
|
// 加载成功
|
||||||
|
loadSuccessCallback(this);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
destroyEvent();
|
||||||
|
const existsCaptchaEl = this.boxEl.children("#tianai-captcha");
|
||||||
|
if (existsCaptchaEl) {
|
||||||
|
existsCaptchaEl.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doMove() {
|
||||||
|
const moveX = this.currentCaptchaData.moveX;
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.css("transform", "translate(" + moveX + "px, 0px)");
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-concat-img-div")
|
||||||
|
.css("background-position-x", moveX + "px");
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("width", moveX + "px");
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStyle() {
|
||||||
|
let sliderImg = "";
|
||||||
|
let moveTrackMaskBorderColor = "#00f4ab";
|
||||||
|
let moveTrackMaskBgColor = "#a9ffe5";
|
||||||
|
const styleConfig = this.styleConfig;
|
||||||
|
if (styleConfig) {
|
||||||
|
sliderImg = styleConfig.btnUrl;
|
||||||
|
moveTrackMaskBgColor = styleConfig.moveTrackMaskBgColor;
|
||||||
|
moveTrackMaskBorderColor = styleConfig.moveTrackMaskBorderColor;
|
||||||
|
}
|
||||||
|
this.el
|
||||||
|
.find(".slider-move .slider-move-btn")
|
||||||
|
.css("background-image", "url(" + sliderImg + ")");
|
||||||
|
// this.el.find("#tianai-captcha-slider-move-track-font").text(title);
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("border-color", moveTrackMaskBorderColor);
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("background-color", moveTrackMaskBgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCaptchaForData(that, data) {
|
||||||
|
const bgImg = that.el.find(".tianai-captcha-slider-concat-bg-img");
|
||||||
|
const sliderImg = that.el.find("#tianai-captcha-slider-concat-img-div");
|
||||||
|
bgImg.css("background-image", "url(" + data.data.backgroundImage + ")");
|
||||||
|
sliderImg.css("background-image", "url(" + data.data.backgroundImage + ")");
|
||||||
|
sliderImg.css("background-position", "0px 0px");
|
||||||
|
var backgroundImageHeight = data.data.backgroundImageHeight;
|
||||||
|
var height =
|
||||||
|
((backgroundImageHeight - data.data.data.randomY) /
|
||||||
|
backgroundImageHeight) *
|
||||||
|
180;
|
||||||
|
sliderImg.css("height", height + "px");
|
||||||
|
|
||||||
|
that.currentCaptchaData = initConfig(
|
||||||
|
bgImg.width(),
|
||||||
|
bgImg.height(),
|
||||||
|
sliderImg.width(),
|
||||||
|
sliderImg.height(),
|
||||||
|
300 - 63 + 5,
|
||||||
|
);
|
||||||
|
that.currentCaptchaData.currentCaptchaId = data.data.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Concat;
|
||||||
17
src/vendor/tianai-captcha/captcha/concat/concat.less
vendored
Normal file
17
src/vendor/tianai-captcha/captcha/concat/concat.less
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#tianai-captcha.tianai-captcha-concat {
|
||||||
|
.tianai-captcha-slider-concat-img-div {
|
||||||
|
background-size: 100% 180px;
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(0px, 0px);
|
||||||
|
/* border-bottom: 1px solid blue; */
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.tianai-captcha-slider-concat-bg-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(0px, 0px);
|
||||||
|
background-size: 100% 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
217
src/vendor/tianai-captcha/captcha/config/config.js
vendored
Normal file
217
src/vendor/tianai-captcha/captcha/config/config.js
vendored
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
import StyleConfig from "./styleConfig";
|
||||||
|
import { Dom, http } from "../common/common";
|
||||||
|
class CaptchaConfig {
|
||||||
|
constructor(args) {
|
||||||
|
if (!args.bindEl) {
|
||||||
|
throw new Error("[TAC] 必须配置 [bindEl]用于将验证码绑定到该元素上");
|
||||||
|
}
|
||||||
|
if (!args.requestCaptchaDataUrl) {
|
||||||
|
throw new Error("[TAC] 必须配置 [requestCaptchaDataUrl]请求验证码接口");
|
||||||
|
}
|
||||||
|
if (!args.validCaptchaUrl) {
|
||||||
|
throw new Error("[TAC] 必须配置 [validCaptchaUrl]验证验证码接口");
|
||||||
|
}
|
||||||
|
this.bindEl = args.bindEl;
|
||||||
|
this.domBindEl = Dom(args.bindEl);
|
||||||
|
this.requestCaptchaDataUrl = args.requestCaptchaDataUrl;
|
||||||
|
this.validCaptchaUrl = args.validCaptchaUrl;
|
||||||
|
if (args.validSuccess) {
|
||||||
|
this.validSuccess = args.validSuccess;
|
||||||
|
}
|
||||||
|
if (args.validFail) {
|
||||||
|
this.validFail = args.validFail;
|
||||||
|
}
|
||||||
|
if (args.requestHeaders) {
|
||||||
|
this.requestHeaders = args.requestHeaders;
|
||||||
|
} else {
|
||||||
|
this.requestHeaders = {};
|
||||||
|
}
|
||||||
|
if (args.btnCloseFun) {
|
||||||
|
this.btnCloseFun = args.btnCloseFun;
|
||||||
|
}
|
||||||
|
if (args.btnRefreshFun) {
|
||||||
|
this.btnRefreshFun = args.btnRefreshFun;
|
||||||
|
}
|
||||||
|
this.requestChain = [];
|
||||||
|
// 时间戳转换
|
||||||
|
this.timeToTimestamp = args.timeToTimestamp || true;
|
||||||
|
this.insertRequestChain(0, {
|
||||||
|
preRequest(type, param, c, tac) {
|
||||||
|
if (this.timeToTimestamp && param.data) {
|
||||||
|
for (let key in param.data) {
|
||||||
|
// 将date全部转换为时间戳
|
||||||
|
if (param.data[key] instanceof Date) {
|
||||||
|
param.data[key] = param.data[key].getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addRequestChain(fun) {
|
||||||
|
this.requestChain.push(fun);
|
||||||
|
}
|
||||||
|
insertRequestChain(index, chain) {
|
||||||
|
this.requestChain.splice(index, 0, chain);
|
||||||
|
}
|
||||||
|
removeRequestChain(index) {
|
||||||
|
this.requestChain.splice(index, 1);
|
||||||
|
}
|
||||||
|
requestCaptchaData() {
|
||||||
|
const requestParam = {};
|
||||||
|
requestParam.headers = this.requestHeaders || {};
|
||||||
|
requestParam.data = {};
|
||||||
|
// 设置默认值
|
||||||
|
requestParam.headers["Content-Type"] = "application/json;charset=UTF-8";
|
||||||
|
requestParam.method = "POST";
|
||||||
|
requestParam.url = this.requestCaptchaDataUrl;
|
||||||
|
// 请求前装载参数
|
||||||
|
this._preRequest("requestCaptchaData", requestParam);
|
||||||
|
// 发送请求
|
||||||
|
const request = this.doSendRequest(requestParam);
|
||||||
|
// 返回结果
|
||||||
|
return request.then((res) => {
|
||||||
|
// 装返回结果
|
||||||
|
this._postRequest("requestCaptchaData", requestParam, res);
|
||||||
|
// 返回结果
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
doSendRequest(requestParam) {
|
||||||
|
// 如果content-type是json,那么data就是json字符串, 这里直接匹配所有header是否包含application/json
|
||||||
|
if (requestParam.headers) {
|
||||||
|
for (const key in requestParam.headers) {
|
||||||
|
if (requestParam.headers[key].indexOf("application/json") > -1) {
|
||||||
|
if (typeof requestParam.data !== "string") {
|
||||||
|
requestParam.data = JSON.stringify(requestParam.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return http(requestParam).then((res) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(res);
|
||||||
|
} catch (e) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_preRequest(type, requestParam, c, tac) {
|
||||||
|
for (let i = 0; i < this.requestChain.length; i++) {
|
||||||
|
const r = this.requestChain[i];
|
||||||
|
if (r.preRequest) {
|
||||||
|
if (!r.preRequest(type, requestParam, this, c, tac)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_postRequest(type, requestParam, res, c, tac) {
|
||||||
|
for (let i = 0; i < this.requestChain.length; i++) {
|
||||||
|
const r = this.requestChain[i];
|
||||||
|
// 判断r是否存圩postRequest方法
|
||||||
|
if (r.postRequest) {
|
||||||
|
if (!r.postRequest(type, requestParam, res, this, c, tac)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validCaptcha(currentCaptchaId, data, c, tac) {
|
||||||
|
const sendParam = {
|
||||||
|
id: currentCaptchaId,
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
let requestParam = {};
|
||||||
|
requestParam.headers = this.requestHeaders || {};
|
||||||
|
requestParam.data = sendParam;
|
||||||
|
requestParam.headers["Content-Type"] = "application/json;charset=UTF-8";
|
||||||
|
requestParam.method = "POST";
|
||||||
|
requestParam.url = this.validCaptchaUrl;
|
||||||
|
|
||||||
|
this._preRequest("validCaptcha", requestParam, c, tac);
|
||||||
|
const request = this.doSendRequest(requestParam);
|
||||||
|
return request
|
||||||
|
.then((res) => {
|
||||||
|
this._postRequest("validCaptcha", requestParam, res, c, tac);
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
|
const useTimes = (data.stopTime - data.startTime) / 1000;
|
||||||
|
c.showTips(`验证成功,耗时${useTimes}秒`, 1, () =>
|
||||||
|
this.validSuccess(res, c, tac),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let tipMsg = "验证失败,请重新尝试!";
|
||||||
|
if (res.code) {
|
||||||
|
if (res.code != 4001) {
|
||||||
|
tipMsg = "验证码被黑洞吸走了!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.showTips(tipMsg, 0, () => this.validFail(res, c, tac));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
let tipMsg = c.styleConfig.i18n.tips_error;
|
||||||
|
if (e.code && e.code != 200) {
|
||||||
|
if (e.code != 4001) {
|
||||||
|
tipMsg = c.styleConfig.i18n.tips_4001;
|
||||||
|
}
|
||||||
|
c.showTips(tipMsg, 0, () => this.validFail(e, c, tac));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
c.showTips(tipMsg, 0, () => this.validFail(e, c, tac));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validSuccess(res, c, tac) {
|
||||||
|
console.log(
|
||||||
|
"验证码校验成功, 请重写 [config.validSuccess] 方法, 用于自定义逻辑处理",
|
||||||
|
);
|
||||||
|
window.currentCaptchaRes = res;
|
||||||
|
tac.destroyWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
validFail(res, c, tac) {
|
||||||
|
tac.reloadCaptcha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapConfig(config) {
|
||||||
|
if (config instanceof CaptchaConfig) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
return new CaptchaConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapStyle(style) {
|
||||||
|
// if (!style) {
|
||||||
|
// style = {}
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (!style.btnUrl) {
|
||||||
|
// // 设置默认图片
|
||||||
|
// style.btnUrl = "data:image/png;base64,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";
|
||||||
|
// }
|
||||||
|
// if (!style.moveTrackMaskBgColor && !style.moveTrackMaskBorderColor) {
|
||||||
|
// style.moveTrackMaskBgColor = "#89d2ff";
|
||||||
|
// style.moveTrackMaskBorderColor = "#0298f8";
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// return style;
|
||||||
|
|
||||||
|
let margeStyle = { ...StyleConfig, ...style };
|
||||||
|
margeStyle.i18n = { ...StyleConfig.i18n, ...style?.i18n };
|
||||||
|
return margeStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const captchaRequestChains = {};
|
||||||
|
|
||||||
|
export { CaptchaConfig, wrapConfig, wrapStyle };
|
||||||
23
src/vendor/tianai-captcha/captcha/config/styleConfig.js
vendored
Normal file
23
src/vendor/tianai-captcha/captcha/config/styleConfig.js
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export default {
|
||||||
|
// 按钮图片
|
||||||
|
btnUrl:
|
||||||
|
"data:image/png;base64,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",
|
||||||
|
// 移动边框背景颜色
|
||||||
|
moveTrackMaskBgColor: "#89d2ff",
|
||||||
|
// 移动边框颜色
|
||||||
|
moveTrackMaskBorderColor: "#0298f8",
|
||||||
|
// 文字提示
|
||||||
|
i18n: {
|
||||||
|
tips_success: "验证成功,耗时%s秒",
|
||||||
|
tips_error: "验证失败,请重新尝试!",
|
||||||
|
slider_title: "拖动滑块完成拼图",
|
||||||
|
concat_title: "拖动滑块完成拼图",
|
||||||
|
image_click_title: "请依次点击:",
|
||||||
|
rotate_title: "拖动滑块完成拼图",
|
||||||
|
// TITLE 大小
|
||||||
|
slider_title_size: "15px",
|
||||||
|
concat_title_size: "15px",
|
||||||
|
image_click_title_size: "20px",
|
||||||
|
rotate_title_size: "15px",
|
||||||
|
},
|
||||||
|
};
|
||||||
58
src/vendor/tianai-captcha/captcha/disable/disable.js
vendored
Normal file
58
src/vendor/tianai-captcha/captcha/disable/disable.js
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
const TYPE = "DISABLE";
|
||||||
|
import "./disable.less";
|
||||||
|
|
||||||
|
function getTemplate(styleConfig) {
|
||||||
|
return `
|
||||||
|
<div id="tianai-captcha" class="tianai-captcha-disable">
|
||||||
|
<div class="slider-tip">
|
||||||
|
<span id="tianai-captcha-slider-move-track-font" style="font-size: ${styleConfig.i18n.disable_title_size}">${styleConfig.i18n.disable_title}</span>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="bg-img-div">
|
||||||
|
<!-- <svg width="100" height="100" viewBox="0 0 100 100">-->
|
||||||
|
<!-- <polygon points="50,10 90,90 10,90" fill="none" stroke="#FF9900" stroke-width="4"/>-->
|
||||||
|
<!-- <path d="M50 35V65 M50 75V75" stroke="#FF9900" stroke-width="4" stroke-linecap="round"/>-->
|
||||||
|
<!-- </svg>-->
|
||||||
|
<span id="content-span"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
class Disable {
|
||||||
|
constructor(boxEl, styleConfig) {
|
||||||
|
this.boxEl = boxEl;
|
||||||
|
this.styleConfig = styleConfig;
|
||||||
|
this.type = TYPE;
|
||||||
|
this.currentCaptchaData = {};
|
||||||
|
}
|
||||||
|
init(captchaData, endCallback, loadSuccessCallback) {
|
||||||
|
// 重载样式
|
||||||
|
this.destroy();
|
||||||
|
this.boxEl.append(getTemplate(this.styleConfig));
|
||||||
|
this.el = this.boxEl.find("#tianai-captcha");
|
||||||
|
// 绑定全局
|
||||||
|
// window.currentCaptcha = this;
|
||||||
|
// 载入验证码
|
||||||
|
this.loadCaptchaForData(this, captchaData);
|
||||||
|
this.endCallback = endCallback;
|
||||||
|
if (loadSuccessCallback) {
|
||||||
|
// 加载成功
|
||||||
|
loadSuccessCallback(this);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
const existsCaptchaEl = this.boxEl.find("#tianai-captcha");
|
||||||
|
if (existsCaptchaEl) {
|
||||||
|
existsCaptchaEl.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadCaptchaForData(that, data) {
|
||||||
|
const msg = data.msg || data.message || "接口异常";
|
||||||
|
that.el.find("#content-span").text(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Disable;
|
||||||
25
src/vendor/tianai-captcha/captcha/disable/disable.less
vendored
Normal file
25
src/vendor/tianai-captcha/captcha/disable/disable.less
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#tianai-captcha.tianai-captcha-disable {
|
||||||
|
z-index: 999;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
.bg-img-div {
|
||||||
|
background-image: url("../../images/dun.jpeg");
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
#content-span {
|
||||||
|
color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 132px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
132
src/vendor/tianai-captcha/captcha/image_click/image_click.js
vendored
Normal file
132
src/vendor/tianai-captcha/captcha/image_click/image_click.js
vendored
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import "./image_click.less";
|
||||||
|
import {
|
||||||
|
CommonCaptcha,
|
||||||
|
move,
|
||||||
|
initConfig,
|
||||||
|
destroyEvent,
|
||||||
|
} from "../common/common.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑动验证码
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TYPE = "IMAGE_CLICK";
|
||||||
|
function getTemplate(styleConfig) {
|
||||||
|
return `
|
||||||
|
<div id="tianai-captcha" class="tianai-captcha-slider tianai-captcha-word-click">
|
||||||
|
<div class="click-tip">
|
||||||
|
<span id="tianai-captcha-click-track-font" style="font-size: ${styleConfig.i18n.image_click_title_size}">${styleConfig.i18n.image_click_title}</span>
|
||||||
|
<img src="" id="tianai-captcha-tip-img" class="tip-img">
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="bg-img-div">
|
||||||
|
<img id="tianai-captcha-slider-bg-img" src="" alt/>
|
||||||
|
<canvas id="tianai-captcha-slider-bg-canvas"></canvas>
|
||||||
|
<div id="bg-img-click-mask"></div>
|
||||||
|
</div>
|
||||||
|
<div class="tianai-captcha-tips" id="tianai-captcha-tips"></div>
|
||||||
|
</div>
|
||||||
|
<div class="click-confirm-btn">确定</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
class ImageClick extends CommonCaptcha {
|
||||||
|
constructor(boxEl, styleConfig) {
|
||||||
|
super();
|
||||||
|
this.boxEl = boxEl;
|
||||||
|
this.styleConfig = styleConfig;
|
||||||
|
this.type = TYPE;
|
||||||
|
this.currentCaptchaData = {};
|
||||||
|
}
|
||||||
|
init(captchaData, endCallback, loadSuccessCallback) {
|
||||||
|
// 重载样式
|
||||||
|
this.destroy();
|
||||||
|
this.boxEl.append(getTemplate(this.styleConfig));
|
||||||
|
this.el = this.boxEl.find("#tianai-captcha");
|
||||||
|
// 绑定全局
|
||||||
|
// window.currentCaptcha = this;
|
||||||
|
// 载入验证码
|
||||||
|
this.loadCaptchaForData(this, captchaData);
|
||||||
|
this.endCallback = endCallback;
|
||||||
|
const moveFun = move.bind(null, this);
|
||||||
|
// 绑定事件
|
||||||
|
this.el.find("#bg-img-click-mask").click((event) => {
|
||||||
|
if (event.target.className === "click-span") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.currentCaptchaData.clickCount++;
|
||||||
|
const trackList = this.currentCaptchaData.trackList;
|
||||||
|
if (this.currentCaptchaData.clickCount === 1) {
|
||||||
|
this.currentCaptchaData.startTime = new Date();
|
||||||
|
// move 轨迹
|
||||||
|
window.addEventListener("mousemove", moveFun);
|
||||||
|
this.currentCaptchaData.startX = event.offsetX;
|
||||||
|
this.currentCaptchaData.startY = event.offsetY;
|
||||||
|
}
|
||||||
|
const startTime = this.currentCaptchaData.startTime;
|
||||||
|
trackList.push({
|
||||||
|
x: Math.round(event.offsetX),
|
||||||
|
y: Math.round(event.offsetY),
|
||||||
|
type: "click",
|
||||||
|
t: new Date().getTime() - startTime.getTime(),
|
||||||
|
});
|
||||||
|
const left = event.offsetX - 10;
|
||||||
|
const top = event.offsetY - 10;
|
||||||
|
this.el
|
||||||
|
.find("#bg-img-click-mask")
|
||||||
|
.append(
|
||||||
|
"<span class='click-span' style='left:" +
|
||||||
|
left +
|
||||||
|
"px;top: " +
|
||||||
|
top +
|
||||||
|
"px'>" +
|
||||||
|
this.currentCaptchaData.clickCount +
|
||||||
|
"</span>",
|
||||||
|
);
|
||||||
|
// if (this.currentCaptchaData.clickCount === 4) {
|
||||||
|
// // 校验
|
||||||
|
// this.currentCaptchaData.stopTime = new Date();
|
||||||
|
// window.removeEventListener("mousemove", move);
|
||||||
|
// this.endCallback(this.currentCaptchaData,this);
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
this.el.find(".click-confirm-btn").click(() => {
|
||||||
|
if (this.currentCaptchaData.clickCount > 0) {
|
||||||
|
// 校验
|
||||||
|
this.currentCaptchaData.stopTime = new Date();
|
||||||
|
window.removeEventListener("mousemove", moveFun);
|
||||||
|
this.endCallback(this.currentCaptchaData, this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (loadSuccessCallback) {
|
||||||
|
// 加载成功
|
||||||
|
loadSuccessCallback(this);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
destroy() {
|
||||||
|
const existsCaptchaEl = this.boxEl.children("#tianai-captcha");
|
||||||
|
if (existsCaptchaEl) {
|
||||||
|
existsCaptchaEl.remove();
|
||||||
|
}
|
||||||
|
destroyEvent();
|
||||||
|
}
|
||||||
|
loadCaptchaForData(that, data) {
|
||||||
|
const bgImg = that.el.find("#tianai-captcha-slider-bg-img");
|
||||||
|
const tipImg = that.el.find("#tianai-captcha-tip-img");
|
||||||
|
bgImg.on("load", () => {
|
||||||
|
that.currentCaptchaData = initConfig(
|
||||||
|
bgImg.width(),
|
||||||
|
bgImg.height(),
|
||||||
|
tipImg.width(),
|
||||||
|
tipImg.height(),
|
||||||
|
);
|
||||||
|
that.currentCaptchaData.currentCaptchaId = data.data.id;
|
||||||
|
});
|
||||||
|
bgImg.attr("src", data.data.backgroundImage);
|
||||||
|
tipImg.attr("src", data.data.templateImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageClick;
|
||||||
66
src/vendor/tianai-captcha/captcha/image_click/image_click.less
vendored
Normal file
66
src/vendor/tianai-captcha/captcha/image_click/image_click.less
vendored
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#tianai-captcha.tianai-captcha-word-click {
|
||||||
|
box-sizing: border-box;
|
||||||
|
.click-tip {
|
||||||
|
position: relative;
|
||||||
|
height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
.tip-img {
|
||||||
|
height: 35px;
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
#tianai-captcha-click-track-font {
|
||||||
|
font-size: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.slider-bottom {
|
||||||
|
position: relative;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
#bg-img-click-mask {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
.click-span {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-color: #409eff;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
color: #fff;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.click-confirm-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
173deg,
|
||||||
|
hsl(38.09deg 91% 57.89%) 0%,
|
||||||
|
hsl(38.09deg 89.38% 71.74%) 100%
|
||||||
|
);
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
line-height: 35px;
|
||||||
|
color: #fff;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
.click-confirm-btn:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/vendor/tianai-captcha/captcha/rotate/rotate.js
vendored
Normal file
136
src/vendor/tianai-captcha/captcha/rotate/rotate.js
vendored
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import "../slider/slider.less";
|
||||||
|
import "./rotate.less";
|
||||||
|
import {
|
||||||
|
CommonCaptcha,
|
||||||
|
down,
|
||||||
|
initConfig,
|
||||||
|
destroyEvent,
|
||||||
|
} from "../common/common.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑动验证码
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TYPE = "ROTATE";
|
||||||
|
function getTemplate(styleConfig) {
|
||||||
|
return `
|
||||||
|
<div id="tianai-captcha" class="tianai-captcha-slider tianai-captcha-rotate">
|
||||||
|
<div class="slider-tip">
|
||||||
|
<span id="tianai-captcha-slider-move-track-font" style="font-size: ${styleConfig.i18n.rotate_title_size}">${styleConfig.i18n.rotate_title}</span>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="bg-img-div">
|
||||||
|
<img id="tianai-captcha-slider-bg-img" src="" alt/>
|
||||||
|
<canvas id="tianai-captcha-slider-bg-canvas"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="rotate-img-div" id="tianai-captcha-slider-img-div">
|
||||||
|
<img id="tianai-captcha-slider-move-img" src="" alt/>
|
||||||
|
</div>
|
||||||
|
<div class="tianai-captcha-tips" id="tianai-captcha-tips"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-move">
|
||||||
|
<div class="slider-move-track">
|
||||||
|
<div id="tianai-captcha-slider-move-track-mask"></div>
|
||||||
|
<div class="slider-move-shadow"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-move-btn" id="tianai-captcha-slider-move-btn">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
class Rotate extends CommonCaptcha {
|
||||||
|
constructor(boxEl, styleConfig) {
|
||||||
|
super();
|
||||||
|
this.boxEl = boxEl;
|
||||||
|
this.styleConfig = styleConfig;
|
||||||
|
this.type = TYPE;
|
||||||
|
this.currentCaptchaData = {};
|
||||||
|
}
|
||||||
|
init(captchaData, endCallback, loadSuccessCallback) {
|
||||||
|
// 重载样式
|
||||||
|
this.destroy();
|
||||||
|
this.boxEl.append(getTemplate(this.styleConfig));
|
||||||
|
this.el = this.boxEl.find("#tianai-captcha");
|
||||||
|
this.loadStyle();
|
||||||
|
// 按钮绑定事件
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.mousedown(down.bind(null, this));
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.touchstart(down.bind(null, this));
|
||||||
|
// 绑定全局
|
||||||
|
// window.currentCaptcha = this;
|
||||||
|
// 载入验证码
|
||||||
|
this.loadCaptchaForData(this, captchaData);
|
||||||
|
this.endCallback = endCallback;
|
||||||
|
if (loadSuccessCallback) {
|
||||||
|
// 加载成功
|
||||||
|
loadSuccessCallback(this);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
const existsCaptchaEl = this.boxEl.children("#tianai-captcha");
|
||||||
|
if (existsCaptchaEl) {
|
||||||
|
existsCaptchaEl.remove();
|
||||||
|
}
|
||||||
|
destroyEvent();
|
||||||
|
}
|
||||||
|
doMove() {
|
||||||
|
const moveX = this.currentCaptchaData.moveX;
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.css("transform", "translate(" + moveX + "px, 0px)");
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-img")
|
||||||
|
.css(
|
||||||
|
"transform",
|
||||||
|
"rotate(" + moveX / (this.currentCaptchaData.end / 360) + "deg)",
|
||||||
|
);
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("width", moveX + "px");
|
||||||
|
}
|
||||||
|
loadStyle() {
|
||||||
|
let sliderImg = "";
|
||||||
|
let moveTrackMaskBorderColor = "#00f4ab";
|
||||||
|
let moveTrackMaskBgColor = "#a9ffe5";
|
||||||
|
const styleConfig = this.styleConfig;
|
||||||
|
if (styleConfig) {
|
||||||
|
sliderImg = styleConfig.btnUrl;
|
||||||
|
moveTrackMaskBgColor = styleConfig.moveTrackMaskBgColor;
|
||||||
|
moveTrackMaskBorderColor = styleConfig.moveTrackMaskBorderColor;
|
||||||
|
}
|
||||||
|
this.el
|
||||||
|
.find(".slider-move .slider-move-btn")
|
||||||
|
.css("background-image", "url(" + sliderImg + ")");
|
||||||
|
// this.el.find("#tianai-captcha-slider-move-track-font").text(title);
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("border-color", moveTrackMaskBorderColor);
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("background-color", moveTrackMaskBgColor);
|
||||||
|
}
|
||||||
|
loadCaptchaForData(that, data) {
|
||||||
|
const bgImg = that.el.find("#tianai-captcha-slider-bg-img");
|
||||||
|
const sliderImg = that.el.find("#tianai-captcha-slider-move-img");
|
||||||
|
bgImg.attr("src", data.data.backgroundImage);
|
||||||
|
sliderImg.attr("src", data.data.templateImage);
|
||||||
|
bgImg.on("load", () => {
|
||||||
|
that.currentCaptchaData = initConfig(
|
||||||
|
bgImg.width(),
|
||||||
|
bgImg.height(),
|
||||||
|
sliderImg.width(),
|
||||||
|
sliderImg.height(),
|
||||||
|
300 - 63 + 5,
|
||||||
|
);
|
||||||
|
that.currentCaptchaData.currentCaptchaId = data.data.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Rotate;
|
||||||
12
src/vendor/tianai-captcha/captcha/rotate/rotate.less
vendored
Normal file
12
src/vendor/tianai-captcha/captcha/rotate/rotate.less
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#tianai-captcha.tianai-captcha-rotate {
|
||||||
|
.rotate-img-div {
|
||||||
|
height: 100%;
|
||||||
|
/*position: absolute;*/
|
||||||
|
text-align: center;
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
transform: rotate(0deg);
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
src/vendor/tianai-captcha/captcha/slider/slider.js
vendored
Normal file
144
src/vendor/tianai-captcha/captcha/slider/slider.js
vendored
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import "../common/common.less";
|
||||||
|
import "./slider.less";
|
||||||
|
import {
|
||||||
|
CommonCaptcha,
|
||||||
|
closeTips,
|
||||||
|
down,
|
||||||
|
initConfig,
|
||||||
|
showTips,
|
||||||
|
destroyEvent,
|
||||||
|
} from "../common/common.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑动验证码
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TYPE = "SLIDER";
|
||||||
|
function getTemplate(styleConfig) {
|
||||||
|
return `
|
||||||
|
<div id="tianai-captcha" class="tianai-captcha-slider">
|
||||||
|
<div class="slider-tip">
|
||||||
|
<span id="tianai-captcha-slider-move-track-font" style="font-size: ${styleConfig.i18n.slider_title_size}">${styleConfig.i18n.slider_title}</span>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="bg-img-div">
|
||||||
|
<img id="tianai-captcha-slider-bg-img" src="" alt/>
|
||||||
|
<canvas id="tianai-captcha-slider-bg-canvas"></canvas>
|
||||||
|
<div id="tianai-captcha-slider-bg-div"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-img-div" id="tianai-captcha-slider-img-div">
|
||||||
|
<img id="tianai-captcha-slider-move-img" src="" alt/>
|
||||||
|
</div>
|
||||||
|
<div class="tianai-captcha-tips" id="tianai-captcha-tips"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-move">
|
||||||
|
<div class="slider-move-track">
|
||||||
|
<div id="tianai-captcha-slider-move-track-mask"></div>
|
||||||
|
<div class="slider-move-shadow"></div>
|
||||||
|
</div>
|
||||||
|
<div class="slider-move-btn" id="tianai-captcha-slider-move-btn">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Slider extends CommonCaptcha {
|
||||||
|
constructor(boxEl, styleConfig) {
|
||||||
|
super();
|
||||||
|
this.boxEl = boxEl;
|
||||||
|
this.styleConfig = styleConfig;
|
||||||
|
this.type = TYPE;
|
||||||
|
this.currentCaptchaData = {};
|
||||||
|
}
|
||||||
|
init(captchaData, endCallback, loadSuccessCallback) {
|
||||||
|
// 重载样式
|
||||||
|
this.destroy();
|
||||||
|
this.boxEl.append(getTemplate(this.styleConfig));
|
||||||
|
this.el = this.boxEl.find("#tianai-captcha");
|
||||||
|
this.loadStyle();
|
||||||
|
// 按钮绑定事件
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.mousedown(down.bind(null, this));
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.touchstart(down.bind(null, this));
|
||||||
|
// 绑定全局
|
||||||
|
// window.currentCaptcha = this;
|
||||||
|
// 载入验证码
|
||||||
|
this.loadCaptchaForData(this, captchaData);
|
||||||
|
this.endCallback = endCallback;
|
||||||
|
if (loadSuccessCallback) {
|
||||||
|
// 加载成功
|
||||||
|
loadSuccessCallback(this);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
showTips(msg, type, callback) {
|
||||||
|
showTips(this.el, msg, type, callback);
|
||||||
|
}
|
||||||
|
closeTips(callback) {
|
||||||
|
closeTips(this.el, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
const existsCaptchaEl = this.boxEl.children("#tianai-captcha");
|
||||||
|
if (existsCaptchaEl) {
|
||||||
|
existsCaptchaEl.remove();
|
||||||
|
}
|
||||||
|
destroyEvent();
|
||||||
|
}
|
||||||
|
doMove() {
|
||||||
|
const moveX = this.currentCaptchaData.moveX;
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-btn")
|
||||||
|
.css("transform", "translate(" + moveX + "px, 0px)");
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-img-div")
|
||||||
|
.css("transform", "translate(" + moveX + "px, 0px)");
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("width", moveX + "px");
|
||||||
|
}
|
||||||
|
loadStyle() {
|
||||||
|
let sliderImg = "";
|
||||||
|
let moveTrackMaskBorderColor = "#00f4ab";
|
||||||
|
let moveTrackMaskBgColor = "#a9ffe5";
|
||||||
|
const styleConfig = this.styleConfig;
|
||||||
|
if (styleConfig) {
|
||||||
|
sliderImg = styleConfig.btnUrl;
|
||||||
|
moveTrackMaskBgColor = styleConfig.moveTrackMaskBgColor;
|
||||||
|
moveTrackMaskBorderColor = styleConfig.moveTrackMaskBorderColor;
|
||||||
|
}
|
||||||
|
this.el
|
||||||
|
.find(".slider-move .slider-move-btn")
|
||||||
|
.css("background-image", "url(" + sliderImg + ")");
|
||||||
|
// this.el.find("#tianai-captcha-slider-move-track-font").text(title);
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("border-color", moveTrackMaskBorderColor);
|
||||||
|
this.el
|
||||||
|
.find("#tianai-captcha-slider-move-track-mask")
|
||||||
|
.css("background-color", moveTrackMaskBgColor);
|
||||||
|
}
|
||||||
|
loadCaptchaForData(that, data) {
|
||||||
|
const bgImg = that.el.find("#tianai-captcha-slider-bg-img");
|
||||||
|
const sliderImg = that.el.find("#tianai-captcha-slider-move-img");
|
||||||
|
bgImg.attr("src", data.data.backgroundImage);
|
||||||
|
sliderImg.attr("src", data.data.templateImage);
|
||||||
|
bgImg.on("load", () => {
|
||||||
|
that.currentCaptchaData = initConfig(
|
||||||
|
bgImg.width(),
|
||||||
|
bgImg.height(),
|
||||||
|
sliderImg.width(),
|
||||||
|
sliderImg.height(),
|
||||||
|
300 - 63 + 5,
|
||||||
|
);
|
||||||
|
that.currentCaptchaData.currentCaptchaId = data.data.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Slider;
|
||||||
89
src/vendor/tianai-captcha/captcha/slider/slider.less
vendored
Normal file
89
src/vendor/tianai-captcha/captcha/slider/slider.less
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#tianai-captcha.tianai-captcha-slider {
|
||||||
|
z-index: 999;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
.content {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.bg-img-div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(0px, 0px);
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.slider-img-div {
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
transform: translate(0px, 0px);
|
||||||
|
#tianai-captcha-slider-move-img {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.slider-move {
|
||||||
|
height: 34px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 11px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.slider-move-track {
|
||||||
|
position: relative;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
text-align: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #999;
|
||||||
|
transition: 0s;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
border: 1px solid #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.refresh-btn,
|
||||||
|
.close-btn {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.slider-move {
|
||||||
|
line-height: 38px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #88949d;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
filter: opacity(0.8);
|
||||||
|
}
|
||||||
|
.slider-move .slider-move-btn {
|
||||||
|
transform: translate(0px, 0px);
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
left: 0;
|
||||||
|
width: 63px;
|
||||||
|
height: 45px;
|
||||||
|
background-color: #fff;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.slider-tip {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: normal;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.slider-move-btn:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
13
src/vendor/tianai-captcha/captcha/word_image_click/word_image_click.js
vendored
Normal file
13
src/vendor/tianai-captcha/captcha/word_image_click/word_image_click.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import ImageClick from "../image_click/image_click";
|
||||||
|
/**
|
||||||
|
* 滑动验证码
|
||||||
|
*/
|
||||||
|
const TYPE = "WORD_IMAGE_CLICK";
|
||||||
|
class WordImageClick extends ImageClick {
|
||||||
|
constructor(divId, styleConfig) {
|
||||||
|
super(divId, styleConfig);
|
||||||
|
this.type = TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WordImageClick;
|
||||||
BIN
src/vendor/tianai-captcha/images/dun.jpeg
vendored
Normal file
BIN
src/vendor/tianai-captcha/images/dun.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
BIN
src/vendor/tianai-captcha/images/icon.png
vendored
Normal file
BIN
src/vendor/tianai-captcha/images/icon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/vendor/tianai-captcha/images/loading.gif
vendored
Normal file
BIN
src/vendor/tianai-captcha/images/loading.gif
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
39
src/vendor/tianai-captcha/index.ts
vendored
Normal file
39
src/vendor/tianai-captcha/index.ts
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { CaptchaConfig, TianAiCaptcha } from "./captcha/captcha.js"
|
||||||
|
|
||||||
|
export type TianAiCaptchaBindTarget = string | HTMLElement
|
||||||
|
|
||||||
|
export interface TianAiCaptchaStyle {
|
||||||
|
btnUrl?: string
|
||||||
|
logoUrl?: string | null
|
||||||
|
bgUrl?: string
|
||||||
|
moveTrackMaskBgColor?: string
|
||||||
|
moveTrackMaskBorderColor?: string
|
||||||
|
i18n?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TianAiCaptchaConfig {
|
||||||
|
bindEl: TianAiCaptchaBindTarget
|
||||||
|
requestCaptchaDataUrl: string
|
||||||
|
validCaptchaUrl: string
|
||||||
|
requestHeaders?: Record<string, string>
|
||||||
|
timeToTimestamp?: boolean
|
||||||
|
validSuccess?: (res: any, captcha: any, tac: TianAiCaptchaInstance) => void
|
||||||
|
validFail?: (res: any, captcha: any, tac: TianAiCaptchaInstance) => void
|
||||||
|
btnRefreshFun?: (el: Event, tac: TianAiCaptchaInstance) => void
|
||||||
|
btnCloseFun?: (el: Event, tac: TianAiCaptchaInstance) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TianAiCaptchaInstance {
|
||||||
|
init: () => TianAiCaptchaInstance
|
||||||
|
reloadCaptcha: () => void
|
||||||
|
destroyWindow: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTianAiCaptcha(
|
||||||
|
config: TianAiCaptchaConfig,
|
||||||
|
style?: TianAiCaptchaStyle,
|
||||||
|
): TianAiCaptchaInstance {
|
||||||
|
return new TianAiCaptcha(config, style) as TianAiCaptchaInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CaptchaConfig, TianAiCaptcha }
|
||||||
209
src/views/debug/CaptchaSandbox.vue
Normal file
209
src/views/debug/CaptchaSandbox.vue
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="hero-actions">
|
||||||
|
<el-button type="primary" @click="startCaptcha">开始验证</el-button>
|
||||||
|
<el-button @click="reloadCaptcha" :disabled="!captchaInstance">刷新</el-button>
|
||||||
|
<el-button @click="destroyCaptcha" :disabled="!captchaInstance" plain>销毁</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sandbox-grid">
|
||||||
|
<el-card class="panel-card" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="panel-title">验证码类型</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-radio-group v-model="selectedType" class="type-group">
|
||||||
|
<el-radio-button v-for="item in captchaTypeOptions" :key="item.value" :label="item.value">
|
||||||
|
{{ item.label }}
|
||||||
|
</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
|
||||||
|
<div class="current-selection">
|
||||||
|
<span class="selection-label">当前选择</span>
|
||||||
|
<strong>{{ currentTypeLabel }}</strong>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="panel-card preview-card" shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="panel-title">验证码预览</div>
|
||||||
|
</template>
|
||||||
|
<div ref="captchaBoxRef" id="captcha-sandbox-box" class="captcha-box"></div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onBeforeUnmount, ref } from 'vue'
|
||||||
|
import {
|
||||||
|
createTianAiCaptcha,
|
||||||
|
type TianAiCaptchaConfig,
|
||||||
|
type TianAiCaptchaInstance
|
||||||
|
} from '@/vendor/tianai-captcha'
|
||||||
|
|
||||||
|
type CaptchaType = 'RANDOM' | 'SLIDER' | 'ROTATE' | 'ROTATE_DEGREE' | 'CONCAT' | 'IMAGE_CLICK' | 'WORD_IMAGE_CLICK'
|
||||||
|
|
||||||
|
const baseApiUrl = import.meta.env.VITE_APP_API_BASE
|
||||||
|
const requestCaptchaBaseUrl = buildApiUrl(baseApiUrl, '/captcha/gen')
|
||||||
|
const validCaptchaUrl = buildApiUrl(baseApiUrl, '/captcha/check')
|
||||||
|
const captchaTypeOptions: Array<{ label: string, value: CaptchaType }> = [
|
||||||
|
{ label: '随机', value: 'RANDOM' },
|
||||||
|
{ label: '滑块拼图', value: 'SLIDER' },
|
||||||
|
{ label: '旋转', value: 'ROTATE' },
|
||||||
|
{ label: '旋转(角度)', value: 'ROTATE_DEGREE' },
|
||||||
|
{ label: '拼接', value: 'CONCAT' },
|
||||||
|
{ label: '点选', value: 'IMAGE_CLICK' },
|
||||||
|
{ label: '汉字点选', value: 'WORD_IMAGE_CLICK' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const captchaInstance = ref<TianAiCaptchaInstance | null>(null)
|
||||||
|
const captchaBoxRef = ref<HTMLElement | null>(null)
|
||||||
|
const selectedType = ref<CaptchaType>('RANDOM')
|
||||||
|
|
||||||
|
const currentTypeLabel = computed(() => {
|
||||||
|
return captchaTypeOptions.find(item => item.value === selectedType.value)?.label || '随机'
|
||||||
|
})
|
||||||
|
|
||||||
|
const requestCaptchaDataUrl = computed(() => {
|
||||||
|
if (selectedType.value === 'RANDOM') {
|
||||||
|
return requestCaptchaBaseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${requestCaptchaBaseUrl}?type=${encodeURIComponent(selectedType.value)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
function startCaptcha() {
|
||||||
|
if (!captchaBoxRef.value) {
|
||||||
|
console.warn('[captcha sandbox] captcha container is not ready')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyCaptcha(false)
|
||||||
|
|
||||||
|
const config: TianAiCaptchaConfig = {
|
||||||
|
bindEl: captchaBoxRef.value,
|
||||||
|
requestCaptchaDataUrl: requestCaptchaDataUrl.value,
|
||||||
|
validCaptchaUrl,
|
||||||
|
validSuccess: (res, _captcha, tac) => {
|
||||||
|
console.log('[captcha sandbox] valid success', res)
|
||||||
|
tac.destroyWindow()
|
||||||
|
},
|
||||||
|
validFail: (res, _captcha, tac) => {
|
||||||
|
console.warn('[captcha sandbox] valid fail', res)
|
||||||
|
tac.reloadCaptcha()
|
||||||
|
},
|
||||||
|
btnRefreshFun: (_el, tac) => {
|
||||||
|
console.info('[captcha sandbox] refresh captcha')
|
||||||
|
tac.reloadCaptcha()
|
||||||
|
},
|
||||||
|
btnCloseFun: (_el, tac) => {
|
||||||
|
console.info('[captcha sandbox] close captcha window')
|
||||||
|
tac.destroyWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('[captcha sandbox] init captcha', {
|
||||||
|
type: selectedType.value,
|
||||||
|
requestCaptchaDataUrl: requestCaptchaDataUrl.value,
|
||||||
|
validCaptchaUrl
|
||||||
|
})
|
||||||
|
|
||||||
|
captchaInstance.value = createTianAiCaptcha(config)
|
||||||
|
captchaInstance.value.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadCaptcha() {
|
||||||
|
if (!captchaInstance.value) return
|
||||||
|
console.info('[captcha sandbox] manual reload')
|
||||||
|
captchaInstance.value.reloadCaptcha()
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyCaptcha(showMessage = true) {
|
||||||
|
if (!captchaInstance.value) return
|
||||||
|
captchaInstance.value.destroyWindow()
|
||||||
|
captchaInstance.value = null
|
||||||
|
if (showMessage) {
|
||||||
|
console.info('[captcha sandbox] destroy captcha instance')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildApiUrl(base: string | undefined, path: string) {
|
||||||
|
const normalizedBase = (base || '').replace(/\/+$/, '')
|
||||||
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`
|
||||||
|
|
||||||
|
if (!normalizedBase) {
|
||||||
|
return `/api/v2${normalizedPath}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${normalizedBase}${normalizedPath}`
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => destroyCaptcha(false))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.hero-actions {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.sandbox-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(320px, 420px) minmax(420px, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-card {
|
||||||
|
border-radius: 18px;
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
:deep(.el-radio-button__inner) {
|
||||||
|
min-width: 112px;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-selection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-label {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-box {
|
||||||
|
min-height: 350px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 16px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(64, 158, 255, 0.07), rgba(103, 194, 58, 0.05)),
|
||||||
|
#fff;
|
||||||
|
border: 1px dashed rgba(64, 158, 255, 0.3);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user