diff --git a/components.d.ts b/components.d.ts index eab8558..bbfb868 100644 --- a/components.d.ts +++ b/components.d.ts @@ -9,6 +9,7 @@ declare module '@vue/runtime-core' { export interface GlobalComponents { ElAside: typeof import('element-plus/es')['ElAside'] ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] ElCol: typeof import('element-plus/es')['ElCol'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] ElContainer: typeof import('element-plus/es')['ElContainer'] @@ -32,6 +33,8 @@ declare module '@vue/runtime-core' { ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElOption: typeof import('element-plus/es')['ElOption'] 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'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] diff --git a/src/config/menu.ts b/src/config/menu.ts index 01ed255..aabe1c4 100644 --- a/src/config/menu.ts +++ b/src/config/menu.ts @@ -33,6 +33,14 @@ const menus: MenuGroup[] = [ { title: '图片资源库', path: '/api/sourceImage', permission: 'sourceImage:list' }, { title: '歌曲库', path: '/api/music', permission: 'music:list' } ] + }, + { + name: 'debug', + title: '调试工具', + icon: 'Tools', + child: [ + { title: '图形验证码', path: '/debug/captcha', permission: 'captcha:list' } + ] } ] diff --git a/src/router.ts b/src/router.ts index 335cd67..ba2f271 100644 --- a/src/router.ts +++ b/src/router.ts @@ -18,6 +18,8 @@ const routes: Array = [ { 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/sourceImage', name: 'SourceImage', component: () => import('@/views/api/SourceImage.vue'), meta: { title: '图片资源库' } }, + + { path: '/debug/captcha', name: 'CaptchaSandbox', component: () => import('@/views/debug/CaptchaSandbox.vue'), meta: { title: '图形验证码' } }, ]} ] diff --git a/src/vendor/tianai-captcha/captcha/captcha.js b/src/vendor/tianai-captcha/captcha/captcha.js new file mode 100644 index 0000000..e90c1ed --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/captcha.js @@ -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 = ` +
+
+
+
+
+ +
+ +
+
+
+
+ `; +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 }; diff --git a/src/vendor/tianai-captcha/captcha/captcha.less b/src/vendor/tianai-captcha/captcha/captcha.less new file mode 100644 index 0000000..29f7b8a --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/captcha.less @@ -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; + } +} diff --git a/src/vendor/tianai-captcha/captcha/common/common.js b/src/vendor/tianai-captcha/captcha/common/common.js new file mode 100644 index 0000000..ee90b17 --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/common/common.js @@ -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, +}; diff --git a/src/vendor/tianai-captcha/captcha/common/common.less b/src/vendor/tianai-captcha/captcha/common/common.less new file mode 100644 index 0000000..5d9ed4c --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/common/common.less @@ -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; + } +} diff --git a/src/vendor/tianai-captcha/captcha/concat/concat.js b/src/vendor/tianai-captcha/captcha/concat/concat.js new file mode 100644 index 0000000..9e2ec1a --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/concat/concat.js @@ -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 ` +
+
+ 拖动滑块完成拼图 +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ `; +} + +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; diff --git a/src/vendor/tianai-captcha/captcha/concat/concat.less b/src/vendor/tianai-captcha/captcha/concat/concat.less new file mode 100644 index 0000000..ef645bc --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/concat/concat.less @@ -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; + } +} diff --git a/src/vendor/tianai-captcha/captcha/config/config.js b/src/vendor/tianai-captcha/captcha/config/config.js new file mode 100644 index 0000000..8c354b2 --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/config/config.js @@ -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 }; diff --git a/src/vendor/tianai-captcha/captcha/config/styleConfig.js b/src/vendor/tianai-captcha/captcha/config/styleConfig.js new file mode 100644 index 0000000..b01f855 --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/config/styleConfig.js @@ -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", + }, +}; diff --git a/src/vendor/tianai-captcha/captcha/disable/disable.js b/src/vendor/tianai-captcha/captcha/disable/disable.js new file mode 100644 index 0000000..0ee18ef --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/disable/disable.js @@ -0,0 +1,58 @@ +const TYPE = "DISABLE"; +import "./disable.less"; + +function getTemplate(styleConfig) { + return ` +
+
+ ${styleConfig.i18n.disable_title} +
+
+
+ + + + + +
+
+
+ `; +} +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; diff --git a/src/vendor/tianai-captcha/captcha/disable/disable.less b/src/vendor/tianai-captcha/captcha/disable/disable.less new file mode 100644 index 0000000..a8e932f --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/disable/disable.less @@ -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; + } + } + } +} diff --git a/src/vendor/tianai-captcha/captcha/image_click/image_click.js b/src/vendor/tianai-captcha/captcha/image_click/image_click.js new file mode 100644 index 0000000..b1e2b53 --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/image_click/image_click.js @@ -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 ` +
+
+ ${styleConfig.i18n.image_click_title} + +
+
+
+ + +
+
+
+
+
确定
+
+`; +} +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( + "" + + this.currentCaptchaData.clickCount + + "", + ); + // 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; diff --git a/src/vendor/tianai-captcha/captcha/image_click/image_click.less b/src/vendor/tianai-captcha/captcha/image_click/image_click.less new file mode 100644 index 0000000..820a3df --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/image_click/image_click.less @@ -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; + } +} diff --git a/src/vendor/tianai-captcha/captcha/rotate/rotate.js b/src/vendor/tianai-captcha/captcha/rotate/rotate.js new file mode 100644 index 0000000..64513ff --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/rotate/rotate.js @@ -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 ` +
+
+ ${styleConfig.i18n.rotate_title} +
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+`; +} +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; diff --git a/src/vendor/tianai-captcha/captcha/rotate/rotate.less b/src/vendor/tianai-captcha/captcha/rotate/rotate.less new file mode 100644 index 0000000..441f2b0 --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/rotate/rotate.less @@ -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; + } + } +} diff --git a/src/vendor/tianai-captcha/captcha/slider/slider.js b/src/vendor/tianai-captcha/captcha/slider/slider.js new file mode 100644 index 0000000..2d957d8 --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/slider/slider.js @@ -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 ` +
+
+ ${styleConfig.i18n.slider_title} +
+
+
+ + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+`; +} + +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; diff --git a/src/vendor/tianai-captcha/captcha/slider/slider.less b/src/vendor/tianai-captcha/captcha/slider/slider.less new file mode 100644 index 0000000..9745e79 --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/slider/slider.less @@ -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; +} diff --git a/src/vendor/tianai-captcha/captcha/word_image_click/word_image_click.js b/src/vendor/tianai-captcha/captcha/word_image_click/word_image_click.js new file mode 100644 index 0000000..787d110 --- /dev/null +++ b/src/vendor/tianai-captcha/captcha/word_image_click/word_image_click.js @@ -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; diff --git a/src/vendor/tianai-captcha/images/dun.jpeg b/src/vendor/tianai-captcha/images/dun.jpeg new file mode 100644 index 0000000..347a371 Binary files /dev/null and b/src/vendor/tianai-captcha/images/dun.jpeg differ diff --git a/src/vendor/tianai-captcha/images/icon.png b/src/vendor/tianai-captcha/images/icon.png new file mode 100644 index 0000000..586a123 Binary files /dev/null and b/src/vendor/tianai-captcha/images/icon.png differ diff --git a/src/vendor/tianai-captcha/images/loading.gif b/src/vendor/tianai-captcha/images/loading.gif new file mode 100644 index 0000000..2a12a7f Binary files /dev/null and b/src/vendor/tianai-captcha/images/loading.gif differ diff --git a/src/vendor/tianai-captcha/index.ts b/src/vendor/tianai-captcha/index.ts new file mode 100644 index 0000000..72815bd --- /dev/null +++ b/src/vendor/tianai-captcha/index.ts @@ -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 +} + +export interface TianAiCaptchaConfig { + bindEl: TianAiCaptchaBindTarget + requestCaptchaDataUrl: string + validCaptchaUrl: string + requestHeaders?: Record + 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 } diff --git a/src/views/debug/CaptchaSandbox.vue b/src/views/debug/CaptchaSandbox.vue new file mode 100644 index 0000000..eec2fd3 --- /dev/null +++ b/src/views/debug/CaptchaSandbox.vue @@ -0,0 +1,209 @@ + + + + +