Phase 3: 管理后台前端 (Vue3+Vite+Element Plus) - 登录页面/JWT认证/Pinia状态管理 - 首页大盘/站点管理/验证码日志/IP黑名单/套餐管理/系统设置 - 7个页面完整路由+API对接 Phase 4: 统计监控模块 - CaptchaLogAspect AOP自动记录 - RealtimeStatsService Redis实时计数 - HistoryStatsService 历史查询 - AnomalyDetectionService 异常检测 Phase 5: 部署和测试 - docker-compose.yml 5服务编排 - application-prod.yml 外部化配置 - CaptchaApiIntegrationTest 集成测试 - 前端 Dockerfile + nginx.conf
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import "./icon_click.scss"
|
||||
import {Dom, CommonCaptcha, move, initConfig, destroyEvent} from "../common/common.js"
|
||||
|
||||
/**
|
||||
* 图标点选验证码
|
||||
*/
|
||||
|
||||
const TYPE = "ICON_CLICK"
|
||||
function getTemplate(styleConfig) {
|
||||
return `
|
||||
<div id="tianai-captcha" class="tianai-captcha-slider tianai-captcha-icon-click">
|
||||
<div class="click-tip">
|
||||
<span id="tianai-captcha-click-track-font" style="font-size: ${styleConfig.i18n?.icon_click_title_size || '14px'}">${styleConfig.i18n?.icon_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 IconClick 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.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 - 12;
|
||||
const top = event.offsetY - 12;
|
||||
this.el.find("#bg-img-click-mask").append("<span class='click-span' style='left:" + left + "px;top: " + top + "px'>" + this.currentCaptchaData.clickCount + "</span>")
|
||||
});
|
||||
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 IconClick;
|
||||
@@ -0,0 +1,75 @@
|
||||
#tianai-captcha.tianai-captcha-icon-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: 14px;
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
position: absolute;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-bottom {
|
||||
position: relative;
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.content {
|
||||
#bg-img-click-mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
|
||||
.click-span {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-radius: 50%;
|
||||
background-color: #409eff;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
color: #fff;
|
||||
border: 2px solid #fff;
|
||||
box-sizing: content-box;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user