重构系统项目结构, 将 tianai-captcha

tianai-captcha-springboot-starter
   tianai-captcha-web-sdk
   tianai-captcha-solon-plugin
   整合到一块
This commit is contained in:
天爱有情
2025-10-27 15:14:10 +08:00
parent af2df2c7e2
commit 5eb258215b
156 changed files with 16688 additions and 99 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

@@ -0,0 +1,183 @@
import "./captcha.scss"
import Slider from "./slider/slider"
import Rotate from "./rotate/rotate";
import Concat from "./concat/concat";
import Disable from "./disable/disable";
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":
return new Rotate(box, styleConfig);
case "CONCAT":
return new Concat(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}
@@ -0,0 +1,107 @@
#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;
-webkit-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: .45s;
//transition-timing-function: cubic-bezier(0.36, 0.3, 0.42, 1.5);
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("@/assets/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("@/assets/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: .5;
position: absolute;
top: -1px;
left: -1px;
border-radius: 5px;
}
}
@@ -0,0 +1,464 @@
/** 是否打印日志 */
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});
});
}
function reductionAllPreventDefault(dom) {
Dom(dom).each(function (el) {
el.removeEventListener('touchmove', clearPreventDefault);
el.addEventListener('mousemove', clearPreventDefault);
});
}
/**
* 获取当前坐标
* @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, .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 && typeof dom === 'object' && typeof dom.nodeType !== 'undefined') {
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 (typeof document === 'object' && typeof document.nodeType !== 'undefined') {
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
}
@@ -0,0 +1,87 @@
#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 .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;
}
}
@@ -0,0 +1,111 @@
import "../common/common.scss"
import "@/captcha/slider/slider.scss"
import "./concat.scss"
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;
@@ -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;
}
}
@@ -0,0 +1,211 @@
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 (res.code != 4001) {
tipMsg = c.styleConfig.i18n.tips_4001;
}
c.showTips(tipMsg, 0, () => this.validFail(res, 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}
@@ -0,0 +1,24 @@
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",
}
}
@@ -0,0 +1,59 @@
const TYPE = "DISABLE";
import "./disable.scss"
import {Dom} from "../common/common";
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;
@@ -0,0 +1,56 @@
#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("@/assets/images/dun.jpeg");
width: 100%;
height: 100%;
overflow: hidden;
#content-span {
color: #fff;
overflow: hidden;
margin-top: 132px;
display: block;
text-align: center;
}
}
}
}
//#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 {
// display: flex;
// justify-content: center;
// flex-direction: column;
// align-items: center;
// background-color: #0A3850;
// width: 100%;
// height: 100%;
// overflow: hidden;
//
// #content-span {
// color: #fff;
// overflow: hidden;
// display: block;
// text-align: center;
// }
// }
// }
//}
@@ -0,0 +1,114 @@
import "./image_click.scss"
import {Dom,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;
@@ -0,0 +1,64 @@
#tianai-captcha.tianai-captcha-word-click {
//position: relative;
box-sizing: border-box;
//padding-top: 10px;
.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
}
}
@@ -0,0 +1,106 @@
import "@/captcha/slider/slider.scss"
import "./rotate.scss"
import {Dom,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;
@@ -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;
}
}
}
@@ -0,0 +1,123 @@
import "../common/common.scss"
import "./slider.scss"
import {
Dom,
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;
@@ -0,0 +1,88 @@
#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(.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;
}
@@ -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;
+4
View File
@@ -0,0 +1,4 @@
import {CaptchaConfig, TianAiCaptcha} from "./captcha/captcha";
window.TAC = TianAiCaptcha;
window.CaptchaConfig = CaptchaConfig;
File diff suppressed because one or more lines are too long