slider-captcha-sdk 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/README.md +2 -2
- package/dist/slider-captcha.cjs.js +1 -1
- package/dist/slider-captcha.esm.js +1 -1
- package/dist/slider-captcha.js +1 -1
- package/dist/slider-captcha.min.js +50 -1
- package/dist/slider-captcha.min.js.map +1 -1
- package/dist/slider-captcha.ultra.min.js +50 -0
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -31,9 +31,9 @@ yarn add slider-captcha-sdk
|
|
|
31
31
|
|
|
32
32
|
### ES Module
|
|
33
33
|
```javascript
|
|
34
|
-
import
|
|
34
|
+
import { PopupSliderCaptcha} from 'slider-captcha-sdk'
|
|
35
35
|
|
|
36
|
-
const captcha = new
|
|
36
|
+
const captcha = new PopupSliderCaptcha({
|
|
37
37
|
apiUrl: '/api/captcha',
|
|
38
38
|
verifyUrl: '/api/captcha/verify',
|
|
39
39
|
onSuccess: (data) => {
|
package/dist/README.md
CHANGED
|
@@ -31,9 +31,9 @@ yarn add slider-captcha-sdk
|
|
|
31
31
|
|
|
32
32
|
### ES Module
|
|
33
33
|
```javascript
|
|
34
|
-
import
|
|
34
|
+
import { PopupSliderCaptcha} from 'slider-captcha-sdk'
|
|
35
35
|
|
|
36
|
-
const captcha = new
|
|
36
|
+
const captcha = new PopupSliderCaptcha({
|
|
37
37
|
apiUrl: '/api/captcha',
|
|
38
38
|
verifyUrl: '/api/captcha/verify',
|
|
39
39
|
onSuccess: (data) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class t{static DEFAULTS={width:350,height:200,sliderSize:42,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify"};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",finger:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,author:"Your Name",license:"MIT"}}constructor(e={}){this.options={...t.DEFAULTS,...e},this.elements={},this.state={isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0},this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.init()}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const e=document.createElement("style");e.id="slider-captcha-styles",e.textContent=`\n .${t.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${t.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${t.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${t.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${t.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${t.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${t.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${t.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${t.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${t.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${t.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${t.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${t.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(e)}createElements(){const{elements:e}=this,{CSS_CLASSES:n}=t;e.overlay=this.createElement("div",n.overlay),e.modal=this.createElement("div",n.modal),e.header=this.createElement("div",n.header),e.title=this.createElement("h3","slider-captcha-title","滑动验证"),e.closeBtn=this.createElement("button","slider-captcha-close","×"),e.refreshBtn=this.createElement("button","slider-captcha-refresh","⟳"),e.container=this.createElement("div",n.container),e.backgroundImg=this.createElement("img","slider-captcha-bg"),e.sliderImg=this.createElement("img","slider-captcha-piece"),e.loadingText=this.createElement("div",n.loading),e.timeDisplay=this.createElement("div","slider-captcha-time"),e.track=this.createElement("div",n.track),e.fingerAnimation=this.createElement("div",n.finger,"👉"),e.btn=this.createElement("div",n.btn),e.icon=this.createElement("div","","→"),e.hint=this.createElement("div",n.hint,"向右滑动完成验证"),e.error=this.createElement("div",n.error),e.retryBtn=this.createElement("button","slider-captcha-retry","重新获取"),e.loadingText.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,e="",n=""){const i=document.createElement(t);return e&&(i.className=e),n&&(i.textContent=n),i}assembleDOM(){const{elements:t}=this;t.btn.appendChild(t.icon),t.track.appendChild(t.fingerAnimation),t.track.appendChild(t.btn),t.track.appendChild(t.hint);const e=this.createElement("div","slider-captcha-header-buttons");e.appendChild(t.refreshBtn),e.appendChild(t.closeBtn),t.header.appendChild(t.title),t.header.appendChild(e),t.container.appendChild(t.backgroundImg),t.container.appendChild(t.sliderImg),t.container.appendChild(t.loadingText),t.modal.appendChild(t.header),t.modal.appendChild(t.container),t.modal.appendChild(t.track),t.modal.appendChild(t.timeDisplay),t.modal.appendChild(t.error),t.modal.appendChild(t.retryBtn),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){const{elements:t}=this;t.container.style.display="none",t.track.style.display="none"}addEventListenerWithTracking(t,e,n){t&&(t.addEventListener(e,n),this.eventListeners.push({element:t,event:e,handler:n}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:n})=>{t&&t.removeEventListener&&t.removeEventListener(e,n)}),this.eventListeners=[]}bindEvents(){const{elements:t}=this;this.addEventListenerWithTracking(t.closeBtn,"click",()=>this.hide()),this.addEventListenerWithTracking(t.refreshBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.retryBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.overlay,"click",e=>{e.target===t.overlay&&this.hide()}),this.addEventListenerWithTracking(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:this.handleStart.bind(this),move:this.handleMove.bind(this),end:this.handleEnd.bind(this)};this.addEventListenerWithTracking(t.btn,"mousedown",e.start),this.addEventListenerWithTracking(t.btn,"touchstart",e.start),this.addEventListenerWithTracking(t.sliderImg,"mousedown",e.start),this.addEventListenerWithTracking(t.sliderImg,"touchstart",e.start),this.addEventListenerWithTracking(document,"mousemove",e.move),this.addEventListenerWithTracking(document,"touchmove",e.move),this.addEventListenerWithTracking(document,"mouseup",e.end),this.addEventListenerWithTracking(document,"touchend",e.end)}getPosition(){const{elements:t,options:e}=this,n=t.track.offsetWidth-t.btn.offsetWidth,i=this.state.currentX/n;return Math.round(i*(e.width-e.sliderSize))}handleStart(t){if(!this.captchaData||this.state.isDragging)return;this.startTime||(this.startTime=Date.now()),this.times=[{time:Date.now(),position:this.getPosition()}],t.preventDefault(),t.stopPropagation(),this.state.isDragging=!0;const e=this.getClientX(t);this.state.startX=e-this.state.currentX,this.setTransition(!1),this.updateUIState("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,n=this.elements.track.offsetWidth-this.elements.btn.offsetWidth;this.state.currentX=Math.max(0,Math.min(e,n)),this.times.push({time:Date.now(),position:this.getPosition()}),this.updateSliderPosition()}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.verify())}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e}updateUIState(t){const{elements:e}=this;switch(t){case"dragging":e.hint.style.opacity="0",e.fingerAnimation.style.display="none";break;case"success":e.btn.style.background="#67c23a",e.icon.innerHTML="✓",e.icon.style.color="white",e.fingerAnimation.style.display="none",this.updateHintText("验证成功","#67c23a");break;case"fail":e.btn.style.background="#f56c6c",e.icon.innerHTML="✗",e.icon.style.color="white";break;case"reset":e.btn.style.background="white",e.icon.innerHTML="→",e.icon.style.color="#666",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","#999")}}updateHintText(t,e){const{elements:n}=this;if(n.hint.textContent=t,n.hint.style.color=e,n.hint.style.opacity="1","验证成功"===t){const t=parseInt(n.btn.style.left)||0,e=n.track.offsetWidth;let i="50%";t>.6*e?i="25%":t>.3*e&&(i="75%"),n.hint.style.left=i}else n.hint.style.left="50%"}updateSliderPosition(){const{elements:t,options:e,state:n}=this,i=t.track.offsetWidth-t.btn.offsetWidth,s=n.currentX/i*(e.width-e.sliderSize),a=n.currentX/i;t.btn.style.left=n.currentX+"px",t.sliderImg.style.left=s+"px",t.fingerAnimation.style.opacity=a>=.8?"0":"0.6"}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",this.elements.overlay.offsetHeight,requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),setTimeout(()=>{this.elements.overlay.style.display="none",this.reset(),this.options.onClose&&this.options.onClose()},300)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now();const t={place:2,timestamp:Date.now()},e=await this.fetchWithTimeout(this.options.apiUrl,{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!e.ok)throw new Error(`HTTP ${e.status}: 获取验证码失败`);const n=await e.json();if(!(n.data&&n.data.canvasSrc&&n.data.blockSrc))throw new Error(n.message||n.msg||"验证码数据格式错误");this.captchaData=n.data,await this.renderCaptcha()}catch(t){console.error("加载验证码失败:",t),this.showError("加载验证码失败: "+t.message)}}async fetchWithTimeout(t,e){const n=new AbortController,i=setTimeout(()=>n.abort(),this.options.timeout);try{const s=await fetch(t,{...e,signal:n.signal});return clearTimeout(i),s}catch(t){throw clearTimeout(i),t}}async renderCaptcha(){return new Promise((t,e)=>{let n=0;const i=()=>{n++,2===n&&(this.hideLoading(),this.showCaptcha(),t())},s=()=>e(new Error("图片加载失败"));this.loadImage(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight},i,s),this.loadImage(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY},i,s)})}loadImage(t,e,n,i,s){t.onload=i,t.onerror=s,t.src=e,Object.entries(n).forEach(([e,n])=>{t.style[e]="number"==typeof n?n+"px":n})}showLoading(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.retryBtn.style.display="none"}hideLoading(){this.elements.loadingText.style.display="none"}showCaptcha(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="none",t.track.style.display="block",t.error.style.display="none",t.retryBtn.style.display="none"}showError(t){this.hideLoading(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.retryBtn.style.display="inline-block"}async verify(){if(this.captchaData)try{const t={loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times]},e=await this.fetchWithTimeout(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const n=await e.json();"0"===n.code||!0===n.success?this.onVerifySuccess():this.onVerifyFail(n.message||n.msg||"验证失败")}catch(t){console.error("验证请求失败:",t),this.onVerifyFail("网络错误,请检查连接后重试")}else this.onVerifyFail("验证码数据丢失,请刷新重试")}onVerifySuccess(){const t=Date.now()-this.startTime,e=`${(t/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showTimeDisplay(e),setTimeout(()=>{this.options.onSuccess({captchaId:this.captchaData.captchaId,timestamp:Date.now(),duration:t}),this.hide()},2e3)}showTimeDisplay(t){const{elements:e}=this;e.timeDisplay.textContent=`验证成功!耗时:${t}`,e.timeDisplay.style.display="block",e.timeDisplay.style.opacity="0",setTimeout(()=>{e.timeDisplay.style.transition="opacity 0.3s ease",e.timeDisplay.style.opacity="1"},100)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.updateHintText(t,"#f56c6c"),setTimeout(()=>{this.reset(),this.state.retryCount>=this.options.maxRetries?(this.showError("验证失败次数过多,请刷新重试"),this.options.onFail({reason:"max_retries_exceeded",retryCount:this.state.retryCount})):this.refresh()},1500)}reset(){this.state.currentX=0,this.setTransition(!0),this.updateUIState("reset"),this.updateSliderPosition(),this.elements.timeDisplay.style.display="none",this.startTime=null}refresh(){this.reset(),this.captchaData=null,this.state.retryCount=0,this.loadCaptcha()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.removeAllEventListeners(),this.elements.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay),this.elements=null,this.captchaData=null,this.times=null,this.eventListeners=null,this.state=null,this.options=null,this.startTime=null}static create(e){return new t(e)}static show(e){const n=new t(e);return n.show(),n}}"undefined"!=typeof module&&module.exports?(module.exports=t,module.exports.default=t,module.exports.PopupSliderCaptcha=t):"function"==typeof define&&define.amd?define([],()=>t):"undefined"!=typeof window&&(window.PopupSliderCaptcha=t,window.SliderCaptcha=t),"undefined"!=typeof window&&(window.SliderCaptcha=t,window.PopupSliderCaptcha=t),exports.PopupSliderCaptcha=t,exports.default=t;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});class PopupSliderCaptcha{static DEFAULTS={width:350,height:200,sliderSize:42,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify"};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",finger:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,author:"Your Name",license:"MIT"}}constructor(t={}){this.options={...PopupSliderCaptcha.DEFAULTS,...t},this.elements={},this.state={isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0},this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.init()}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const t=document.createElement("style");t.id="slider-captcha-styles",t.textContent=`\n .${PopupSliderCaptcha.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${PopupSliderCaptcha.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(t)}createElements(){const{elements:t}=this,{CSS_CLASSES:e}=PopupSliderCaptcha;t.overlay=this.createElement("div",e.overlay),t.modal=this.createElement("div",e.modal),t.header=this.createElement("div",e.header),t.title=this.createElement("h3","slider-captcha-title","滑动验证"),t.closeBtn=this.createElement("button","slider-captcha-close","×"),t.refreshBtn=this.createElement("button","slider-captcha-refresh","⟳"),t.container=this.createElement("div",e.container),t.backgroundImg=this.createElement("img","slider-captcha-bg"),t.sliderImg=this.createElement("img","slider-captcha-piece"),t.loadingText=this.createElement("div",e.loading),t.timeDisplay=this.createElement("div","slider-captcha-time"),t.track=this.createElement("div",e.track),t.fingerAnimation=this.createElement("div",e.finger,"👉"),t.btn=this.createElement("div",e.btn),t.icon=this.createElement("div","","→"),t.hint=this.createElement("div",e.hint,"向右滑动完成验证"),t.error=this.createElement("div",e.error),t.retryBtn=this.createElement("button","slider-captcha-retry","重新获取"),t.loadingText.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,e="",n=""){const i=document.createElement(t);return e&&(i.className=e),n&&(i.textContent=n),i}assembleDOM(){const{elements:t}=this;t.btn.appendChild(t.icon),t.track.appendChild(t.fingerAnimation),t.track.appendChild(t.btn),t.track.appendChild(t.hint);const e=this.createElement("div","slider-captcha-header-buttons");e.appendChild(t.refreshBtn),e.appendChild(t.closeBtn),t.header.appendChild(t.title),t.header.appendChild(e),t.container.appendChild(t.backgroundImg),t.container.appendChild(t.sliderImg),t.container.appendChild(t.loadingText),t.modal.appendChild(t.header),t.modal.appendChild(t.container),t.modal.appendChild(t.track),t.modal.appendChild(t.timeDisplay),t.modal.appendChild(t.error),t.modal.appendChild(t.retryBtn),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){const{elements:t}=this;t.container.style.display="none",t.track.style.display="none"}addEventListenerWithTracking(t,e,n){t&&(t.addEventListener(e,n),this.eventListeners.push({element:t,event:e,handler:n}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:n})=>{t&&t.removeEventListener&&t.removeEventListener(e,n)}),this.eventListeners=[]}bindEvents(){const{elements:t}=this;this.addEventListenerWithTracking(t.closeBtn,"click",()=>this.hide()),this.addEventListenerWithTracking(t.refreshBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.retryBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.overlay,"click",e=>{e.target===t.overlay&&this.hide()}),this.addEventListenerWithTracking(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:this.handleStart.bind(this),move:this.handleMove.bind(this),end:this.handleEnd.bind(this)};this.addEventListenerWithTracking(t.btn,"mousedown",e.start),this.addEventListenerWithTracking(t.btn,"touchstart",e.start),this.addEventListenerWithTracking(t.sliderImg,"mousedown",e.start),this.addEventListenerWithTracking(t.sliderImg,"touchstart",e.start),this.addEventListenerWithTracking(document,"mousemove",e.move),this.addEventListenerWithTracking(document,"touchmove",e.move),this.addEventListenerWithTracking(document,"mouseup",e.end),this.addEventListenerWithTracking(document,"touchend",e.end)}getPosition(){const{elements:t,options:e}=this,n=t.track.offsetWidth-t.btn.offsetWidth,i=this.state.currentX/n;return Math.round(i*(e.width-e.sliderSize))}handleStart(t){if(!this.captchaData||this.state.isDragging)return;this.startTime||(this.startTime=Date.now()),this.times=[{time:Date.now(),position:this.getPosition()}],t.preventDefault(),t.stopPropagation(),this.state.isDragging=!0;const e=this.getClientX(t);this.state.startX=e-this.state.currentX,this.setTransition(!1),this.updateUIState("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,n=this.elements.track.offsetWidth-this.elements.btn.offsetWidth;this.state.currentX=Math.max(0,Math.min(e,n)),this.times.push({time:Date.now(),position:this.getPosition()}),this.updateSliderPosition()}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.verify())}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e}updateUIState(t){const{elements:e}=this;switch(t){case"dragging":e.hint.style.opacity="0",e.fingerAnimation.style.display="none";break;case"success":e.btn.style.background="#67c23a",e.icon.innerHTML="✓",e.icon.style.color="white",e.fingerAnimation.style.display="none",this.updateHintText("验证成功","#67c23a");break;case"fail":e.btn.style.background="#f56c6c",e.icon.innerHTML="✗",e.icon.style.color="white";break;case"reset":e.btn.style.background="white",e.icon.innerHTML="→",e.icon.style.color="#666",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","#999")}}updateHintText(t,e){const{elements:n}=this;if(n.hint.textContent=t,n.hint.style.color=e,n.hint.style.opacity="1","验证成功"===t){const t=parseInt(n.btn.style.left)||0,e=n.track.offsetWidth;let i="50%";t>.6*e?i="25%":t>.3*e&&(i="75%"),n.hint.style.left=i}else n.hint.style.left="50%"}updateSliderPosition(){const{elements:t,options:e,state:n}=this,i=t.track.offsetWidth-t.btn.offsetWidth,s=n.currentX/i*(e.width-e.sliderSize),a=n.currentX/i;t.btn.style.left=n.currentX+"px",t.sliderImg.style.left=s+"px",t.fingerAnimation.style.opacity=a>=.8?"0":"0.6"}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",this.elements.overlay.offsetHeight,requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),setTimeout(()=>{this.elements.overlay.style.display="none",this.reset(),this.options.onClose&&this.options.onClose()},300)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now();const t={place:2,timestamp:Date.now()},e=await this.fetchWithTimeout(this.options.apiUrl,{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!e.ok)throw new Error(`HTTP ${e.status}: 获取验证码失败`);const n=await e.json();if(!(n.data&&n.data.canvasSrc&&n.data.blockSrc))throw new Error(n.message||n.msg||"验证码数据格式错误");this.captchaData=n.data,await this.renderCaptcha()}catch(t){this.showError("加载验证码失败: "+t.message)}}async fetchWithTimeout(t,e){const n=new AbortController,i=setTimeout(()=>n.abort(),this.options.timeout);try{const s=await fetch(t,{...e,signal:n.signal});return clearTimeout(i),s}catch(t){throw clearTimeout(i),t}}async renderCaptcha(){return new Promise((t,e)=>{let n=0;const i=()=>{n++,2===n&&(this.hideLoading(),this.showCaptcha(),t())},s=()=>e(new Error("图片加载失败"));this.loadImage(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight},i,s),this.loadImage(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY},i,s)})}loadImage(t,e,n,i,s){t.onload=i,t.onerror=s,t.src=e,Object.entries(n).forEach(([e,n])=>{t.style[e]="number"==typeof n?n+"px":n})}showLoading(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.retryBtn.style.display="none"}hideLoading(){this.elements.loadingText.style.display="none"}showCaptcha(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="none",t.track.style.display="block",t.error.style.display="none",t.retryBtn.style.display="none"}showError(t){this.hideLoading(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.retryBtn.style.display="inline-block"}async verify(){if(this.captchaData)try{const t={loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times]},e=await this.fetchWithTimeout(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const n=await e.json();"0"===n.code||!0===n.success?this.onVerifySuccess():this.onVerifyFail(n.message||n.msg||"验证失败")}catch(t){this.onVerifyFail("网络错误,请检查连接后重试")}else this.onVerifyFail("验证码数据丢失,请刷新重试")}onVerifySuccess(){const t=Date.now()-this.startTime,e=`${(t/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showTimeDisplay(e),setTimeout(()=>{this.options.onSuccess({captchaId:this.captchaData.captchaId,timestamp:Date.now(),duration:t}),this.hide()},2e3)}showTimeDisplay(t){const{elements:e}=this;e.timeDisplay.textContent=`验证成功!耗时:${t}`,e.timeDisplay.style.display="block",e.timeDisplay.style.opacity="0",setTimeout(()=>{e.timeDisplay.style.transition="opacity 0.3s ease",e.timeDisplay.style.opacity="1"},100)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.updateHintText(t,"#f56c6c"),setTimeout(()=>{this.reset(),this.state.retryCount>=this.options.maxRetries?(this.showError("验证失败次数过多,请刷新重试"),this.options.onFail({reason:"max_retries_exceeded",retryCount:this.state.retryCount})):this.refresh()},1500)}reset(){this.state.currentX=0,this.setTransition(!0),this.updateUIState("reset"),this.updateSliderPosition(),this.elements.timeDisplay.style.display="none",this.startTime=null}refresh(){this.reset(),this.captchaData=null,this.state.retryCount=0,this.loadCaptcha()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.removeAllEventListeners(),this.elements.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay),this.elements=null,this.captchaData=null,this.times=null,this.eventListeners=null,this.state=null,this.options=null,this.startTime=null}static create(t){return new PopupSliderCaptcha(t)}static show(t){const e=new PopupSliderCaptcha(t);return e.show(),e}}"undefined"!=typeof module&&module.exports?(module.exports=PopupSliderCaptcha,module.exports.default=PopupSliderCaptcha,module.exports.PopupSliderCaptcha=PopupSliderCaptcha):"function"==typeof define&&define.amd?define([],()=>PopupSliderCaptcha):"undefined"!=typeof window&&(window.PopupSliderCaptcha=PopupSliderCaptcha,window.SliderCaptcha=PopupSliderCaptcha),"undefined"!=typeof window&&(window.SliderCaptcha=PopupSliderCaptcha,window.PopupSliderCaptcha=PopupSliderCaptcha),exports.PopupSliderCaptcha=PopupSliderCaptcha,exports.default=PopupSliderCaptcha;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
class t{static DEFAULTS={width:350,height:200,sliderSize:42,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify"};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",finger:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,author:"Your Name",license:"MIT"}}constructor(e={}){this.options={...t.DEFAULTS,...e},this.elements={},this.state={isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0},this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.init()}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const e=document.createElement("style");e.id="slider-captcha-styles",e.textContent=`\n .${t.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${t.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${t.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${t.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${t.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${t.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${t.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${t.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${t.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${t.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${t.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${t.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${t.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(e)}createElements(){const{elements:e}=this,{CSS_CLASSES:n}=t;e.overlay=this.createElement("div",n.overlay),e.modal=this.createElement("div",n.modal),e.header=this.createElement("div",n.header),e.title=this.createElement("h3","slider-captcha-title","滑动验证"),e.closeBtn=this.createElement("button","slider-captcha-close","×"),e.refreshBtn=this.createElement("button","slider-captcha-refresh","⟳"),e.container=this.createElement("div",n.container),e.backgroundImg=this.createElement("img","slider-captcha-bg"),e.sliderImg=this.createElement("img","slider-captcha-piece"),e.loadingText=this.createElement("div",n.loading),e.timeDisplay=this.createElement("div","slider-captcha-time"),e.track=this.createElement("div",n.track),e.fingerAnimation=this.createElement("div",n.finger,"👉"),e.btn=this.createElement("div",n.btn),e.icon=this.createElement("div","","→"),e.hint=this.createElement("div",n.hint,"向右滑动完成验证"),e.error=this.createElement("div",n.error),e.retryBtn=this.createElement("button","slider-captcha-retry","重新获取"),e.loadingText.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,e="",n=""){const i=document.createElement(t);return e&&(i.className=e),n&&(i.textContent=n),i}assembleDOM(){const{elements:t}=this;t.btn.appendChild(t.icon),t.track.appendChild(t.fingerAnimation),t.track.appendChild(t.btn),t.track.appendChild(t.hint);const e=this.createElement("div","slider-captcha-header-buttons");e.appendChild(t.refreshBtn),e.appendChild(t.closeBtn),t.header.appendChild(t.title),t.header.appendChild(e),t.container.appendChild(t.backgroundImg),t.container.appendChild(t.sliderImg),t.container.appendChild(t.loadingText),t.modal.appendChild(t.header),t.modal.appendChild(t.container),t.modal.appendChild(t.track),t.modal.appendChild(t.timeDisplay),t.modal.appendChild(t.error),t.modal.appendChild(t.retryBtn),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){const{elements:t}=this;t.container.style.display="none",t.track.style.display="none"}addEventListenerWithTracking(t,e,n){t&&(t.addEventListener(e,n),this.eventListeners.push({element:t,event:e,handler:n}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:n})=>{t&&t.removeEventListener&&t.removeEventListener(e,n)}),this.eventListeners=[]}bindEvents(){const{elements:t}=this;this.addEventListenerWithTracking(t.closeBtn,"click",()=>this.hide()),this.addEventListenerWithTracking(t.refreshBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.retryBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.overlay,"click",e=>{e.target===t.overlay&&this.hide()}),this.addEventListenerWithTracking(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:this.handleStart.bind(this),move:this.handleMove.bind(this),end:this.handleEnd.bind(this)};this.addEventListenerWithTracking(t.btn,"mousedown",e.start),this.addEventListenerWithTracking(t.btn,"touchstart",e.start),this.addEventListenerWithTracking(t.sliderImg,"mousedown",e.start),this.addEventListenerWithTracking(t.sliderImg,"touchstart",e.start),this.addEventListenerWithTracking(document,"mousemove",e.move),this.addEventListenerWithTracking(document,"touchmove",e.move),this.addEventListenerWithTracking(document,"mouseup",e.end),this.addEventListenerWithTracking(document,"touchend",e.end)}getPosition(){const{elements:t,options:e}=this,n=t.track.offsetWidth-t.btn.offsetWidth,i=this.state.currentX/n;return Math.round(i*(e.width-e.sliderSize))}handleStart(t){if(!this.captchaData||this.state.isDragging)return;this.startTime||(this.startTime=Date.now()),this.times=[{time:Date.now(),position:this.getPosition()}],t.preventDefault(),t.stopPropagation(),this.state.isDragging=!0;const e=this.getClientX(t);this.state.startX=e-this.state.currentX,this.setTransition(!1),this.updateUIState("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,n=this.elements.track.offsetWidth-this.elements.btn.offsetWidth;this.state.currentX=Math.max(0,Math.min(e,n)),this.times.push({time:Date.now(),position:this.getPosition()}),this.updateSliderPosition()}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.verify())}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e}updateUIState(t){const{elements:e}=this;switch(t){case"dragging":e.hint.style.opacity="0",e.fingerAnimation.style.display="none";break;case"success":e.btn.style.background="#67c23a",e.icon.innerHTML="✓",e.icon.style.color="white",e.fingerAnimation.style.display="none",this.updateHintText("验证成功","#67c23a");break;case"fail":e.btn.style.background="#f56c6c",e.icon.innerHTML="✗",e.icon.style.color="white";break;case"reset":e.btn.style.background="white",e.icon.innerHTML="→",e.icon.style.color="#666",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","#999")}}updateHintText(t,e){const{elements:n}=this;if(n.hint.textContent=t,n.hint.style.color=e,n.hint.style.opacity="1","验证成功"===t){const t=parseInt(n.btn.style.left)||0,e=n.track.offsetWidth;let i="50%";t>.6*e?i="25%":t>.3*e&&(i="75%"),n.hint.style.left=i}else n.hint.style.left="50%"}updateSliderPosition(){const{elements:t,options:e,state:n}=this,i=t.track.offsetWidth-t.btn.offsetWidth,s=n.currentX/i*(e.width-e.sliderSize),a=n.currentX/i;t.btn.style.left=n.currentX+"px",t.sliderImg.style.left=s+"px",t.fingerAnimation.style.opacity=a>=.8?"0":"0.6"}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",this.elements.overlay.offsetHeight,requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),setTimeout(()=>{this.elements.overlay.style.display="none",this.reset(),this.options.onClose&&this.options.onClose()},300)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now();const t={place:2,timestamp:Date.now()},e=await this.fetchWithTimeout(this.options.apiUrl,{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!e.ok)throw new Error(`HTTP ${e.status}: 获取验证码失败`);const n=await e.json();if(!(n.data&&n.data.canvasSrc&&n.data.blockSrc))throw new Error(n.message||n.msg||"验证码数据格式错误");this.captchaData=n.data,await this.renderCaptcha()}catch(t){console.error("加载验证码失败:",t),this.showError("加载验证码失败: "+t.message)}}async fetchWithTimeout(t,e){const n=new AbortController,i=setTimeout(()=>n.abort(),this.options.timeout);try{const s=await fetch(t,{...e,signal:n.signal});return clearTimeout(i),s}catch(t){throw clearTimeout(i),t}}async renderCaptcha(){return new Promise((t,e)=>{let n=0;const i=()=>{n++,2===n&&(this.hideLoading(),this.showCaptcha(),t())},s=()=>e(new Error("图片加载失败"));this.loadImage(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight},i,s),this.loadImage(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY},i,s)})}loadImage(t,e,n,i,s){t.onload=i,t.onerror=s,t.src=e,Object.entries(n).forEach(([e,n])=>{t.style[e]="number"==typeof n?n+"px":n})}showLoading(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.retryBtn.style.display="none"}hideLoading(){this.elements.loadingText.style.display="none"}showCaptcha(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="none",t.track.style.display="block",t.error.style.display="none",t.retryBtn.style.display="none"}showError(t){this.hideLoading(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.retryBtn.style.display="inline-block"}async verify(){if(this.captchaData)try{const t={loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times]},e=await this.fetchWithTimeout(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const n=await e.json();"0"===n.code||!0===n.success?this.onVerifySuccess():this.onVerifyFail(n.message||n.msg||"验证失败")}catch(t){console.error("验证请求失败:",t),this.onVerifyFail("网络错误,请检查连接后重试")}else this.onVerifyFail("验证码数据丢失,请刷新重试")}onVerifySuccess(){const t=Date.now()-this.startTime,e=`${(t/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showTimeDisplay(e),setTimeout(()=>{this.options.onSuccess({captchaId:this.captchaData.captchaId,timestamp:Date.now(),duration:t}),this.hide()},2e3)}showTimeDisplay(t){const{elements:e}=this;e.timeDisplay.textContent=`验证成功!耗时:${t}`,e.timeDisplay.style.display="block",e.timeDisplay.style.opacity="0",setTimeout(()=>{e.timeDisplay.style.transition="opacity 0.3s ease",e.timeDisplay.style.opacity="1"},100)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.updateHintText(t,"#f56c6c"),setTimeout(()=>{this.reset(),this.state.retryCount>=this.options.maxRetries?(this.showError("验证失败次数过多,请刷新重试"),this.options.onFail({reason:"max_retries_exceeded",retryCount:this.state.retryCount})):this.refresh()},1500)}reset(){this.state.currentX=0,this.setTransition(!0),this.updateUIState("reset"),this.updateSliderPosition(),this.elements.timeDisplay.style.display="none",this.startTime=null}refresh(){this.reset(),this.captchaData=null,this.state.retryCount=0,this.loadCaptcha()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.removeAllEventListeners(),this.elements.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay),this.elements=null,this.captchaData=null,this.times=null,this.eventListeners=null,this.state=null,this.options=null,this.startTime=null}static create(e){return new t(e)}static show(e){const n=new t(e);return n.show(),n}}"undefined"!=typeof module&&module.exports?(module.exports=t,module.exports.default=t,module.exports.PopupSliderCaptcha=t):"function"==typeof define&&define.amd?define([],()=>t):"undefined"!=typeof window&&(window.PopupSliderCaptcha=t,window.SliderCaptcha=t),"undefined"!=typeof window&&(window.SliderCaptcha=t,window.PopupSliderCaptcha=t);export{t as PopupSliderCaptcha,t as default};
|
|
1
|
+
class PopupSliderCaptcha{static DEFAULTS={width:350,height:200,sliderSize:42,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify"};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",finger:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,author:"Your Name",license:"MIT"}}constructor(t={}){this.options={...PopupSliderCaptcha.DEFAULTS,...t},this.elements={},this.state={isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0},this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.init()}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const t=document.createElement("style");t.id="slider-captcha-styles",t.textContent=`\n .${PopupSliderCaptcha.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${PopupSliderCaptcha.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(t)}createElements(){const{elements:t}=this,{CSS_CLASSES:e}=PopupSliderCaptcha;t.overlay=this.createElement("div",e.overlay),t.modal=this.createElement("div",e.modal),t.header=this.createElement("div",e.header),t.title=this.createElement("h3","slider-captcha-title","滑动验证"),t.closeBtn=this.createElement("button","slider-captcha-close","×"),t.refreshBtn=this.createElement("button","slider-captcha-refresh","⟳"),t.container=this.createElement("div",e.container),t.backgroundImg=this.createElement("img","slider-captcha-bg"),t.sliderImg=this.createElement("img","slider-captcha-piece"),t.loadingText=this.createElement("div",e.loading),t.timeDisplay=this.createElement("div","slider-captcha-time"),t.track=this.createElement("div",e.track),t.fingerAnimation=this.createElement("div",e.finger,"👉"),t.btn=this.createElement("div",e.btn),t.icon=this.createElement("div","","→"),t.hint=this.createElement("div",e.hint,"向右滑动完成验证"),t.error=this.createElement("div",e.error),t.retryBtn=this.createElement("button","slider-captcha-retry","重新获取"),t.loadingText.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,e="",n=""){const i=document.createElement(t);return e&&(i.className=e),n&&(i.textContent=n),i}assembleDOM(){const{elements:t}=this;t.btn.appendChild(t.icon),t.track.appendChild(t.fingerAnimation),t.track.appendChild(t.btn),t.track.appendChild(t.hint);const e=this.createElement("div","slider-captcha-header-buttons");e.appendChild(t.refreshBtn),e.appendChild(t.closeBtn),t.header.appendChild(t.title),t.header.appendChild(e),t.container.appendChild(t.backgroundImg),t.container.appendChild(t.sliderImg),t.container.appendChild(t.loadingText),t.modal.appendChild(t.header),t.modal.appendChild(t.container),t.modal.appendChild(t.track),t.modal.appendChild(t.timeDisplay),t.modal.appendChild(t.error),t.modal.appendChild(t.retryBtn),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){const{elements:t}=this;t.container.style.display="none",t.track.style.display="none"}addEventListenerWithTracking(t,e,n){t&&(t.addEventListener(e,n),this.eventListeners.push({element:t,event:e,handler:n}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:n})=>{t&&t.removeEventListener&&t.removeEventListener(e,n)}),this.eventListeners=[]}bindEvents(){const{elements:t}=this;this.addEventListenerWithTracking(t.closeBtn,"click",()=>this.hide()),this.addEventListenerWithTracking(t.refreshBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.retryBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.overlay,"click",e=>{e.target===t.overlay&&this.hide()}),this.addEventListenerWithTracking(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:this.handleStart.bind(this),move:this.handleMove.bind(this),end:this.handleEnd.bind(this)};this.addEventListenerWithTracking(t.btn,"mousedown",e.start),this.addEventListenerWithTracking(t.btn,"touchstart",e.start),this.addEventListenerWithTracking(t.sliderImg,"mousedown",e.start),this.addEventListenerWithTracking(t.sliderImg,"touchstart",e.start),this.addEventListenerWithTracking(document,"mousemove",e.move),this.addEventListenerWithTracking(document,"touchmove",e.move),this.addEventListenerWithTracking(document,"mouseup",e.end),this.addEventListenerWithTracking(document,"touchend",e.end)}getPosition(){const{elements:t,options:e}=this,n=t.track.offsetWidth-t.btn.offsetWidth,i=this.state.currentX/n;return Math.round(i*(e.width-e.sliderSize))}handleStart(t){if(!this.captchaData||this.state.isDragging)return;this.startTime||(this.startTime=Date.now()),this.times=[{time:Date.now(),position:this.getPosition()}],t.preventDefault(),t.stopPropagation(),this.state.isDragging=!0;const e=this.getClientX(t);this.state.startX=e-this.state.currentX,this.setTransition(!1),this.updateUIState("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,n=this.elements.track.offsetWidth-this.elements.btn.offsetWidth;this.state.currentX=Math.max(0,Math.min(e,n)),this.times.push({time:Date.now(),position:this.getPosition()}),this.updateSliderPosition()}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.verify())}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e}updateUIState(t){const{elements:e}=this;switch(t){case"dragging":e.hint.style.opacity="0",e.fingerAnimation.style.display="none";break;case"success":e.btn.style.background="#67c23a",e.icon.innerHTML="✓",e.icon.style.color="white",e.fingerAnimation.style.display="none",this.updateHintText("验证成功","#67c23a");break;case"fail":e.btn.style.background="#f56c6c",e.icon.innerHTML="✗",e.icon.style.color="white";break;case"reset":e.btn.style.background="white",e.icon.innerHTML="→",e.icon.style.color="#666",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","#999")}}updateHintText(t,e){const{elements:n}=this;if(n.hint.textContent=t,n.hint.style.color=e,n.hint.style.opacity="1","验证成功"===t){const t=parseInt(n.btn.style.left)||0,e=n.track.offsetWidth;let i="50%";t>.6*e?i="25%":t>.3*e&&(i="75%"),n.hint.style.left=i}else n.hint.style.left="50%"}updateSliderPosition(){const{elements:t,options:e,state:n}=this,i=t.track.offsetWidth-t.btn.offsetWidth,s=n.currentX/i*(e.width-e.sliderSize),a=n.currentX/i;t.btn.style.left=n.currentX+"px",t.sliderImg.style.left=s+"px",t.fingerAnimation.style.opacity=a>=.8?"0":"0.6"}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",this.elements.overlay.offsetHeight,requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),setTimeout(()=>{this.elements.overlay.style.display="none",this.reset(),this.options.onClose&&this.options.onClose()},300)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now();const t={place:2,timestamp:Date.now()},e=await this.fetchWithTimeout(this.options.apiUrl,{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!e.ok)throw new Error(`HTTP ${e.status}: 获取验证码失败`);const n=await e.json();if(!(n.data&&n.data.canvasSrc&&n.data.blockSrc))throw new Error(n.message||n.msg||"验证码数据格式错误");this.captchaData=n.data,await this.renderCaptcha()}catch(t){this.showError("加载验证码失败: "+t.message)}}async fetchWithTimeout(t,e){const n=new AbortController,i=setTimeout(()=>n.abort(),this.options.timeout);try{const s=await fetch(t,{...e,signal:n.signal});return clearTimeout(i),s}catch(t){throw clearTimeout(i),t}}async renderCaptcha(){return new Promise((t,e)=>{let n=0;const i=()=>{n++,2===n&&(this.hideLoading(),this.showCaptcha(),t())},s=()=>e(new Error("图片加载失败"));this.loadImage(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight},i,s),this.loadImage(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY},i,s)})}loadImage(t,e,n,i,s){t.onload=i,t.onerror=s,t.src=e,Object.entries(n).forEach(([e,n])=>{t.style[e]="number"==typeof n?n+"px":n})}showLoading(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.retryBtn.style.display="none"}hideLoading(){this.elements.loadingText.style.display="none"}showCaptcha(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="none",t.track.style.display="block",t.error.style.display="none",t.retryBtn.style.display="none"}showError(t){this.hideLoading(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.retryBtn.style.display="inline-block"}async verify(){if(this.captchaData)try{const t={loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times]},e=await this.fetchWithTimeout(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const n=await e.json();"0"===n.code||!0===n.success?this.onVerifySuccess():this.onVerifyFail(n.message||n.msg||"验证失败")}catch(t){this.onVerifyFail("网络错误,请检查连接后重试")}else this.onVerifyFail("验证码数据丢失,请刷新重试")}onVerifySuccess(){const t=Date.now()-this.startTime,e=`${(t/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showTimeDisplay(e),setTimeout(()=>{this.options.onSuccess({captchaId:this.captchaData.captchaId,timestamp:Date.now(),duration:t}),this.hide()},2e3)}showTimeDisplay(t){const{elements:e}=this;e.timeDisplay.textContent=`验证成功!耗时:${t}`,e.timeDisplay.style.display="block",e.timeDisplay.style.opacity="0",setTimeout(()=>{e.timeDisplay.style.transition="opacity 0.3s ease",e.timeDisplay.style.opacity="1"},100)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.updateHintText(t,"#f56c6c"),setTimeout(()=>{this.reset(),this.state.retryCount>=this.options.maxRetries?(this.showError("验证失败次数过多,请刷新重试"),this.options.onFail({reason:"max_retries_exceeded",retryCount:this.state.retryCount})):this.refresh()},1500)}reset(){this.state.currentX=0,this.setTransition(!0),this.updateUIState("reset"),this.updateSliderPosition(),this.elements.timeDisplay.style.display="none",this.startTime=null}refresh(){this.reset(),this.captchaData=null,this.state.retryCount=0,this.loadCaptcha()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.removeAllEventListeners(),this.elements.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay),this.elements=null,this.captchaData=null,this.times=null,this.eventListeners=null,this.state=null,this.options=null,this.startTime=null}static create(t){return new PopupSliderCaptcha(t)}static show(t){const e=new PopupSliderCaptcha(t);return e.show(),e}}"undefined"!=typeof module&&module.exports?(module.exports=PopupSliderCaptcha,module.exports.default=PopupSliderCaptcha,module.exports.PopupSliderCaptcha=PopupSliderCaptcha):"function"==typeof define&&define.amd?define([],()=>PopupSliderCaptcha):"undefined"!=typeof window&&(window.PopupSliderCaptcha=PopupSliderCaptcha,window.SliderCaptcha=PopupSliderCaptcha),"undefined"!=typeof window&&(window.SliderCaptcha=PopupSliderCaptcha,window.PopupSliderCaptcha=PopupSliderCaptcha);export{PopupSliderCaptcha,PopupSliderCaptcha as default};
|
package/dist/slider-captcha.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).SliderCaptcha={})}(this,function(t){"use strict";class e{static DEFAULTS={width:350,height:200,sliderSize:42,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify"};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",finger:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,author:"Your Name",license:"MIT"}}constructor(t={}){this.options={...e.DEFAULTS,...t},this.elements={},this.state={isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0},this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.init()}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const t=document.createElement("style");t.id="slider-captcha-styles",t.textContent=`\n .${e.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${e.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${e.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${e.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${e.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${e.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${e.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${e.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${e.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${e.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${e.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${e.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${e.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(t)}createElements(){const{elements:t}=this,{CSS_CLASSES:n}=e;t.overlay=this.createElement("div",n.overlay),t.modal=this.createElement("div",n.modal),t.header=this.createElement("div",n.header),t.title=this.createElement("h3","slider-captcha-title","滑动验证"),t.closeBtn=this.createElement("button","slider-captcha-close","×"),t.refreshBtn=this.createElement("button","slider-captcha-refresh","⟳"),t.container=this.createElement("div",n.container),t.backgroundImg=this.createElement("img","slider-captcha-bg"),t.sliderImg=this.createElement("img","slider-captcha-piece"),t.loadingText=this.createElement("div",n.loading),t.timeDisplay=this.createElement("div","slider-captcha-time"),t.track=this.createElement("div",n.track),t.fingerAnimation=this.createElement("div",n.finger,"👉"),t.btn=this.createElement("div",n.btn),t.icon=this.createElement("div","","→"),t.hint=this.createElement("div",n.hint,"向右滑动完成验证"),t.error=this.createElement("div",n.error),t.retryBtn=this.createElement("button","slider-captcha-retry","重新获取"),t.loadingText.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,e="",n=""){const i=document.createElement(t);return e&&(i.className=e),n&&(i.textContent=n),i}assembleDOM(){const{elements:t}=this;t.btn.appendChild(t.icon),t.track.appendChild(t.fingerAnimation),t.track.appendChild(t.btn),t.track.appendChild(t.hint);const e=this.createElement("div","slider-captcha-header-buttons");e.appendChild(t.refreshBtn),e.appendChild(t.closeBtn),t.header.appendChild(t.title),t.header.appendChild(e),t.container.appendChild(t.backgroundImg),t.container.appendChild(t.sliderImg),t.container.appendChild(t.loadingText),t.modal.appendChild(t.header),t.modal.appendChild(t.container),t.modal.appendChild(t.track),t.modal.appendChild(t.timeDisplay),t.modal.appendChild(t.error),t.modal.appendChild(t.retryBtn),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){const{elements:t}=this;t.container.style.display="none",t.track.style.display="none"}addEventListenerWithTracking(t,e,n){t&&(t.addEventListener(e,n),this.eventListeners.push({element:t,event:e,handler:n}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:n})=>{t&&t.removeEventListener&&t.removeEventListener(e,n)}),this.eventListeners=[]}bindEvents(){const{elements:t}=this;this.addEventListenerWithTracking(t.closeBtn,"click",()=>this.hide()),this.addEventListenerWithTracking(t.refreshBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.retryBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.overlay,"click",e=>{e.target===t.overlay&&this.hide()}),this.addEventListenerWithTracking(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:this.handleStart.bind(this),move:this.handleMove.bind(this),end:this.handleEnd.bind(this)};this.addEventListenerWithTracking(t.btn,"mousedown",e.start),this.addEventListenerWithTracking(t.btn,"touchstart",e.start),this.addEventListenerWithTracking(t.sliderImg,"mousedown",e.start),this.addEventListenerWithTracking(t.sliderImg,"touchstart",e.start),this.addEventListenerWithTracking(document,"mousemove",e.move),this.addEventListenerWithTracking(document,"touchmove",e.move),this.addEventListenerWithTracking(document,"mouseup",e.end),this.addEventListenerWithTracking(document,"touchend",e.end)}getPosition(){const{elements:t,options:e}=this,n=t.track.offsetWidth-t.btn.offsetWidth,i=this.state.currentX/n;return Math.round(i*(e.width-e.sliderSize))}handleStart(t){if(!this.captchaData||this.state.isDragging)return;this.startTime||(this.startTime=Date.now()),this.times=[{time:Date.now(),position:this.getPosition()}],t.preventDefault(),t.stopPropagation(),this.state.isDragging=!0;const e=this.getClientX(t);this.state.startX=e-this.state.currentX,this.setTransition(!1),this.updateUIState("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,n=this.elements.track.offsetWidth-this.elements.btn.offsetWidth;this.state.currentX=Math.max(0,Math.min(e,n)),this.times.push({time:Date.now(),position:this.getPosition()}),this.updateSliderPosition()}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.verify())}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e}updateUIState(t){const{elements:e}=this;switch(t){case"dragging":e.hint.style.opacity="0",e.fingerAnimation.style.display="none";break;case"success":e.btn.style.background="#67c23a",e.icon.innerHTML="✓",e.icon.style.color="white",e.fingerAnimation.style.display="none",this.updateHintText("验证成功","#67c23a");break;case"fail":e.btn.style.background="#f56c6c",e.icon.innerHTML="✗",e.icon.style.color="white";break;case"reset":e.btn.style.background="white",e.icon.innerHTML="→",e.icon.style.color="#666",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","#999")}}updateHintText(t,e){const{elements:n}=this;if(n.hint.textContent=t,n.hint.style.color=e,n.hint.style.opacity="1","验证成功"===t){const t=parseInt(n.btn.style.left)||0,e=n.track.offsetWidth;let i="50%";t>.6*e?i="25%":t>.3*e&&(i="75%"),n.hint.style.left=i}else n.hint.style.left="50%"}updateSliderPosition(){const{elements:t,options:e,state:n}=this,i=t.track.offsetWidth-t.btn.offsetWidth,s=n.currentX/i*(e.width-e.sliderSize),a=n.currentX/i;t.btn.style.left=n.currentX+"px",t.sliderImg.style.left=s+"px",t.fingerAnimation.style.opacity=a>=.8?"0":"0.6"}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",this.elements.overlay.offsetHeight,requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),setTimeout(()=>{this.elements.overlay.style.display="none",this.reset(),this.options.onClose&&this.options.onClose()},300)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now();const t={place:2,timestamp:Date.now()},e=await this.fetchWithTimeout(this.options.apiUrl,{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!e.ok)throw new Error(`HTTP ${e.status}: 获取验证码失败`);const n=await e.json();if(!(n.data&&n.data.canvasSrc&&n.data.blockSrc))throw new Error(n.message||n.msg||"验证码数据格式错误");this.captchaData=n.data,await this.renderCaptcha()}catch(t){console.error("加载验证码失败:",t),this.showError("加载验证码失败: "+t.message)}}async fetchWithTimeout(t,e){const n=new AbortController,i=setTimeout(()=>n.abort(),this.options.timeout);try{const s=await fetch(t,{...e,signal:n.signal});return clearTimeout(i),s}catch(t){throw clearTimeout(i),t}}async renderCaptcha(){return new Promise((t,e)=>{let n=0;const i=()=>{n++,2===n&&(this.hideLoading(),this.showCaptcha(),t())},s=()=>e(new Error("图片加载失败"));this.loadImage(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight},i,s),this.loadImage(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY},i,s)})}loadImage(t,e,n,i,s){t.onload=i,t.onerror=s,t.src=e,Object.entries(n).forEach(([e,n])=>{t.style[e]="number"==typeof n?n+"px":n})}showLoading(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.retryBtn.style.display="none"}hideLoading(){this.elements.loadingText.style.display="none"}showCaptcha(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="none",t.track.style.display="block",t.error.style.display="none",t.retryBtn.style.display="none"}showError(t){this.hideLoading(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.retryBtn.style.display="inline-block"}async verify(){if(this.captchaData)try{const t={loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times]},e=await this.fetchWithTimeout(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const n=await e.json();"0"===n.code||!0===n.success?this.onVerifySuccess():this.onVerifyFail(n.message||n.msg||"验证失败")}catch(t){console.error("验证请求失败:",t),this.onVerifyFail("网络错误,请检查连接后重试")}else this.onVerifyFail("验证码数据丢失,请刷新重试")}onVerifySuccess(){const t=Date.now()-this.startTime,e=`${(t/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showTimeDisplay(e),setTimeout(()=>{this.options.onSuccess({captchaId:this.captchaData.captchaId,timestamp:Date.now(),duration:t}),this.hide()},2e3)}showTimeDisplay(t){const{elements:e}=this;e.timeDisplay.textContent=`验证成功!耗时:${t}`,e.timeDisplay.style.display="block",e.timeDisplay.style.opacity="0",setTimeout(()=>{e.timeDisplay.style.transition="opacity 0.3s ease",e.timeDisplay.style.opacity="1"},100)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.updateHintText(t,"#f56c6c"),setTimeout(()=>{this.reset(),this.state.retryCount>=this.options.maxRetries?(this.showError("验证失败次数过多,请刷新重试"),this.options.onFail({reason:"max_retries_exceeded",retryCount:this.state.retryCount})):this.refresh()},1500)}reset(){this.state.currentX=0,this.setTransition(!0),this.updateUIState("reset"),this.updateSliderPosition(),this.elements.timeDisplay.style.display="none",this.startTime=null}refresh(){this.reset(),this.captchaData=null,this.state.retryCount=0,this.loadCaptcha()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.removeAllEventListeners(),this.elements.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay),this.elements=null,this.captchaData=null,this.times=null,this.eventListeners=null,this.state=null,this.options=null,this.startTime=null}static create(t){return new e(t)}static show(t){const n=new e(t);return n.show(),n}}"undefined"!=typeof module&&module.exports?(module.exports=e,module.exports.default=e,module.exports.PopupSliderCaptcha=e):"function"==typeof define&&define.amd?define([],()=>e):"undefined"!=typeof window&&(window.PopupSliderCaptcha=e,window.SliderCaptcha=e),"undefined"!=typeof window&&(window.SliderCaptcha=e,window.PopupSliderCaptcha=e),t.PopupSliderCaptcha=e,t.default=e,Object.defineProperty(t,"__esModule",{value:!0})});
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).SliderCaptcha={})}(this,function(t){"use strict";class PopupSliderCaptcha{static DEFAULTS={width:350,height:200,sliderSize:42,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify"};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",finger:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,author:"Your Name",license:"MIT"}}constructor(t={}){this.options={...PopupSliderCaptcha.DEFAULTS,...t},this.elements={},this.state={isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0},this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.init()}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const t=document.createElement("style");t.id="slider-captcha-styles",t.textContent=`\n .${PopupSliderCaptcha.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${PopupSliderCaptcha.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(t)}createElements(){const{elements:t}=this,{CSS_CLASSES:e}=PopupSliderCaptcha;t.overlay=this.createElement("div",e.overlay),t.modal=this.createElement("div",e.modal),t.header=this.createElement("div",e.header),t.title=this.createElement("h3","slider-captcha-title","滑动验证"),t.closeBtn=this.createElement("button","slider-captcha-close","×"),t.refreshBtn=this.createElement("button","slider-captcha-refresh","⟳"),t.container=this.createElement("div",e.container),t.backgroundImg=this.createElement("img","slider-captcha-bg"),t.sliderImg=this.createElement("img","slider-captcha-piece"),t.loadingText=this.createElement("div",e.loading),t.timeDisplay=this.createElement("div","slider-captcha-time"),t.track=this.createElement("div",e.track),t.fingerAnimation=this.createElement("div",e.finger,"👉"),t.btn=this.createElement("div",e.btn),t.icon=this.createElement("div","","→"),t.hint=this.createElement("div",e.hint,"向右滑动完成验证"),t.error=this.createElement("div",e.error),t.retryBtn=this.createElement("button","slider-captcha-retry","重新获取"),t.loadingText.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,e="",n=""){const i=document.createElement(t);return e&&(i.className=e),n&&(i.textContent=n),i}assembleDOM(){const{elements:t}=this;t.btn.appendChild(t.icon),t.track.appendChild(t.fingerAnimation),t.track.appendChild(t.btn),t.track.appendChild(t.hint);const e=this.createElement("div","slider-captcha-header-buttons");e.appendChild(t.refreshBtn),e.appendChild(t.closeBtn),t.header.appendChild(t.title),t.header.appendChild(e),t.container.appendChild(t.backgroundImg),t.container.appendChild(t.sliderImg),t.container.appendChild(t.loadingText),t.modal.appendChild(t.header),t.modal.appendChild(t.container),t.modal.appendChild(t.track),t.modal.appendChild(t.timeDisplay),t.modal.appendChild(t.error),t.modal.appendChild(t.retryBtn),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){const{elements:t}=this;t.container.style.display="none",t.track.style.display="none"}addEventListenerWithTracking(t,e,n){t&&(t.addEventListener(e,n),this.eventListeners.push({element:t,event:e,handler:n}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:n})=>{t&&t.removeEventListener&&t.removeEventListener(e,n)}),this.eventListeners=[]}bindEvents(){const{elements:t}=this;this.addEventListenerWithTracking(t.closeBtn,"click",()=>this.hide()),this.addEventListenerWithTracking(t.refreshBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.retryBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.overlay,"click",e=>{e.target===t.overlay&&this.hide()}),this.addEventListenerWithTracking(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:this.handleStart.bind(this),move:this.handleMove.bind(this),end:this.handleEnd.bind(this)};this.addEventListenerWithTracking(t.btn,"mousedown",e.start),this.addEventListenerWithTracking(t.btn,"touchstart",e.start),this.addEventListenerWithTracking(t.sliderImg,"mousedown",e.start),this.addEventListenerWithTracking(t.sliderImg,"touchstart",e.start),this.addEventListenerWithTracking(document,"mousemove",e.move),this.addEventListenerWithTracking(document,"touchmove",e.move),this.addEventListenerWithTracking(document,"mouseup",e.end),this.addEventListenerWithTracking(document,"touchend",e.end)}getPosition(){const{elements:t,options:e}=this,n=t.track.offsetWidth-t.btn.offsetWidth,i=this.state.currentX/n;return Math.round(i*(e.width-e.sliderSize))}handleStart(t){if(!this.captchaData||this.state.isDragging)return;this.startTime||(this.startTime=Date.now()),this.times=[{time:Date.now(),position:this.getPosition()}],t.preventDefault(),t.stopPropagation(),this.state.isDragging=!0;const e=this.getClientX(t);this.state.startX=e-this.state.currentX,this.setTransition(!1),this.updateUIState("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,n=this.elements.track.offsetWidth-this.elements.btn.offsetWidth;this.state.currentX=Math.max(0,Math.min(e,n)),this.times.push({time:Date.now(),position:this.getPosition()}),this.updateSliderPosition()}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.verify())}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e}updateUIState(t){const{elements:e}=this;switch(t){case"dragging":e.hint.style.opacity="0",e.fingerAnimation.style.display="none";break;case"success":e.btn.style.background="#67c23a",e.icon.innerHTML="✓",e.icon.style.color="white",e.fingerAnimation.style.display="none",this.updateHintText("验证成功","#67c23a");break;case"fail":e.btn.style.background="#f56c6c",e.icon.innerHTML="✗",e.icon.style.color="white";break;case"reset":e.btn.style.background="white",e.icon.innerHTML="→",e.icon.style.color="#666",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","#999")}}updateHintText(t,e){const{elements:n}=this;if(n.hint.textContent=t,n.hint.style.color=e,n.hint.style.opacity="1","验证成功"===t){const t=parseInt(n.btn.style.left)||0,e=n.track.offsetWidth;let i="50%";t>.6*e?i="25%":t>.3*e&&(i="75%"),n.hint.style.left=i}else n.hint.style.left="50%"}updateSliderPosition(){const{elements:t,options:e,state:n}=this,i=t.track.offsetWidth-t.btn.offsetWidth,s=n.currentX/i*(e.width-e.sliderSize),a=n.currentX/i;t.btn.style.left=n.currentX+"px",t.sliderImg.style.left=s+"px",t.fingerAnimation.style.opacity=a>=.8?"0":"0.6"}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",this.elements.overlay.offsetHeight,requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),setTimeout(()=>{this.elements.overlay.style.display="none",this.reset(),this.options.onClose&&this.options.onClose()},300)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now();const t={place:2,timestamp:Date.now()},e=await this.fetchWithTimeout(this.options.apiUrl,{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!e.ok)throw new Error(`HTTP ${e.status}: 获取验证码失败`);const n=await e.json();if(!(n.data&&n.data.canvasSrc&&n.data.blockSrc))throw new Error(n.message||n.msg||"验证码数据格式错误");this.captchaData=n.data,await this.renderCaptcha()}catch(t){this.showError("加载验证码失败: "+t.message)}}async fetchWithTimeout(t,e){const n=new AbortController,i=setTimeout(()=>n.abort(),this.options.timeout);try{const s=await fetch(t,{...e,signal:n.signal});return clearTimeout(i),s}catch(t){throw clearTimeout(i),t}}async renderCaptcha(){return new Promise((t,e)=>{let n=0;const i=()=>{n++,2===n&&(this.hideLoading(),this.showCaptcha(),t())},s=()=>e(new Error("图片加载失败"));this.loadImage(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight},i,s),this.loadImage(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY},i,s)})}loadImage(t,e,n,i,s){t.onload=i,t.onerror=s,t.src=e,Object.entries(n).forEach(([e,n])=>{t.style[e]="number"==typeof n?n+"px":n})}showLoading(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.retryBtn.style.display="none"}hideLoading(){this.elements.loadingText.style.display="none"}showCaptcha(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="none",t.track.style.display="block",t.error.style.display="none",t.retryBtn.style.display="none"}showError(t){this.hideLoading(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.retryBtn.style.display="inline-block"}async verify(){if(this.captchaData)try{const t={loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times]},e=await this.fetchWithTimeout(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const n=await e.json();"0"===n.code||!0===n.success?this.onVerifySuccess():this.onVerifyFail(n.message||n.msg||"验证失败")}catch(t){this.onVerifyFail("网络错误,请检查连接后重试")}else this.onVerifyFail("验证码数据丢失,请刷新重试")}onVerifySuccess(){const t=Date.now()-this.startTime,e=`${(t/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showTimeDisplay(e),setTimeout(()=>{this.options.onSuccess({captchaId:this.captchaData.captchaId,timestamp:Date.now(),duration:t}),this.hide()},2e3)}showTimeDisplay(t){const{elements:e}=this;e.timeDisplay.textContent=`验证成功!耗时:${t}`,e.timeDisplay.style.display="block",e.timeDisplay.style.opacity="0",setTimeout(()=>{e.timeDisplay.style.transition="opacity 0.3s ease",e.timeDisplay.style.opacity="1"},100)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.updateHintText(t,"#f56c6c"),setTimeout(()=>{this.reset(),this.state.retryCount>=this.options.maxRetries?(this.showError("验证失败次数过多,请刷新重试"),this.options.onFail({reason:"max_retries_exceeded",retryCount:this.state.retryCount})):this.refresh()},1500)}reset(){this.state.currentX=0,this.setTransition(!0),this.updateUIState("reset"),this.updateSliderPosition(),this.elements.timeDisplay.style.display="none",this.startTime=null}refresh(){this.reset(),this.captchaData=null,this.state.retryCount=0,this.loadCaptcha()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.removeAllEventListeners(),this.elements.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay),this.elements=null,this.captchaData=null,this.times=null,this.eventListeners=null,this.state=null,this.options=null,this.startTime=null}static create(t){return new PopupSliderCaptcha(t)}static show(t){const e=new PopupSliderCaptcha(t);return e.show(),e}}"undefined"!=typeof module&&module.exports?(module.exports=PopupSliderCaptcha,module.exports.default=PopupSliderCaptcha,module.exports.PopupSliderCaptcha=PopupSliderCaptcha):"function"==typeof define&&define.amd?define([],()=>PopupSliderCaptcha):"undefined"!=typeof window&&(window.PopupSliderCaptcha=PopupSliderCaptcha,window.SliderCaptcha=PopupSliderCaptcha),"undefined"!=typeof window&&(window.SliderCaptcha=PopupSliderCaptcha,window.PopupSliderCaptcha=PopupSliderCaptcha),t.PopupSliderCaptcha=PopupSliderCaptcha,t.default=PopupSliderCaptcha,Object.defineProperty(t,"__esModule",{value:!0})});
|
|
@@ -1,2 +1,51 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).SliderCaptcha={})}(this,function(t){"use strict";class PopupSliderCaptcha{static DEFAULTS={width:350,height:200,sliderSize:42,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify"};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",finger:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,author:"Your Name",license:"MIT"}}constructor(t={}){this.options={...PopupSliderCaptcha.DEFAULTS,...t},this.elements={},this.state={isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0},this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.init()}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const t=document.createElement("style");t.id="slider-captcha-styles",t.textContent=`\n .${PopupSliderCaptcha.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${PopupSliderCaptcha.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(t)}createElements(){const{elements:t}=this,{CSS_CLASSES:e}=PopupSliderCaptcha;t.overlay=this.createElement("div",e.overlay),t.modal=this.createElement("div",e.modal),t.header=this.createElement("div",e.header),t.title=this.createElement("h3","slider-captcha-title","滑动验证"),t.closeBtn=this.createElement("button","slider-captcha-close","×"),t.refreshBtn=this.createElement("button","slider-captcha-refresh","⟳"),t.container=this.createElement("div",e.container),t.backgroundImg=this.createElement("img","slider-captcha-bg"),t.sliderImg=this.createElement("img","slider-captcha-piece"),t.loadingText=this.createElement("div",e.loading),t.timeDisplay=this.createElement("div","slider-captcha-time"),t.track=this.createElement("div",e.track),t.fingerAnimation=this.createElement("div",e.finger,"👉"),t.btn=this.createElement("div",e.btn),t.icon=this.createElement("div","","→"),t.hint=this.createElement("div",e.hint,"向右滑动完成验证"),t.error=this.createElement("div",e.error),t.retryBtn=this.createElement("button","slider-captcha-retry","重新获取"),t.loadingText.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,e="",n=""){const i=document.createElement(t);return e&&(i.className=e),n&&(i.textContent=n),i}assembleDOM(){const{elements:t}=this;t.btn.appendChild(t.icon),t.track.appendChild(t.fingerAnimation),t.track.appendChild(t.btn),t.track.appendChild(t.hint);const e=this.createElement("div","slider-captcha-header-buttons");e.appendChild(t.refreshBtn),e.appendChild(t.closeBtn),t.header.appendChild(t.title),t.header.appendChild(e),t.container.appendChild(t.backgroundImg),t.container.appendChild(t.sliderImg),t.container.appendChild(t.loadingText),t.modal.appendChild(t.header),t.modal.appendChild(t.container),t.modal.appendChild(t.track),t.modal.appendChild(t.timeDisplay),t.modal.appendChild(t.error),t.modal.appendChild(t.retryBtn),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){const{elements:t}=this;t.container.style.display="none",t.track.style.display="none"}addEventListenerWithTracking(t,e,n){t&&(t.addEventListener(e,n),this.eventListeners.push({element:t,event:e,handler:n}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:n})=>{t&&t.removeEventListener&&t.removeEventListener(e,n)}),this.eventListeners=[]}bindEvents(){const{elements:t}=this;this.addEventListenerWithTracking(t.closeBtn,"click",()=>this.hide()),this.addEventListenerWithTracking(t.refreshBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.retryBtn,"click",()=>this.refresh()),this.addEventListenerWithTracking(t.overlay,"click",e=>{e.target===t.overlay&&this.hide()}),this.addEventListenerWithTracking(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:this.handleStart.bind(this),move:this.handleMove.bind(this),end:this.handleEnd.bind(this)};this.addEventListenerWithTracking(t.btn,"mousedown",e.start),this.addEventListenerWithTracking(t.btn,"touchstart",e.start),this.addEventListenerWithTracking(t.sliderImg,"mousedown",e.start),this.addEventListenerWithTracking(t.sliderImg,"touchstart",e.start),this.addEventListenerWithTracking(document,"mousemove",e.move),this.addEventListenerWithTracking(document,"touchmove",e.move),this.addEventListenerWithTracking(document,"mouseup",e.end),this.addEventListenerWithTracking(document,"touchend",e.end)}getPosition(){const{elements:t,options:e}=this,n=t.track.offsetWidth-t.btn.offsetWidth,i=this.state.currentX/n;return Math.round(i*(e.width-e.sliderSize))}handleStart(t){if(!this.captchaData||this.state.isDragging)return;this.startTime||(this.startTime=Date.now()),this.times=[{time:Date.now(),position:this.getPosition()}],t.preventDefault(),t.stopPropagation(),this.state.isDragging=!0;const e=this.getClientX(t);this.state.startX=e-this.state.currentX,this.setTransition(!1),this.updateUIState("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,n=this.elements.track.offsetWidth-this.elements.btn.offsetWidth;this.state.currentX=Math.max(0,Math.min(e,n)),this.times.push({time:Date.now(),position:this.getPosition()}),this.updateSliderPosition()}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.verify())}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e}updateUIState(t){const{elements:e}=this;switch(t){case"dragging":e.hint.style.opacity="0",e.fingerAnimation.style.display="none";break;case"success":e.btn.style.background="#67c23a",e.icon.innerHTML="✓",e.icon.style.color="white",e.fingerAnimation.style.display="none",this.updateHintText("验证成功","#67c23a");break;case"fail":e.btn.style.background="#f56c6c",e.icon.innerHTML="✗",e.icon.style.color="white";break;case"reset":e.btn.style.background="white",e.icon.innerHTML="→",e.icon.style.color="#666",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","#999")}}updateHintText(t,e){const{elements:n}=this;if(n.hint.textContent=t,n.hint.style.color=e,n.hint.style.opacity="1","验证成功"===t){const t=parseInt(n.btn.style.left)||0,e=n.track.offsetWidth;let i="50%";t>.6*e?i="25%":t>.3*e&&(i="75%"),n.hint.style.left=i}else n.hint.style.left="50%"}updateSliderPosition(){const{elements:t,options:e,state:n}=this,i=t.track.offsetWidth-t.btn.offsetWidth,s=n.currentX/i*(e.width-e.sliderSize),a=n.currentX/i;t.btn.style.left=n.currentX+"px",t.sliderImg.style.left=s+"px",t.fingerAnimation.style.opacity=a>=.8?"0":"0.6"}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",this.elements.overlay.offsetHeight,requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),setTimeout(()=>{this.elements.overlay.style.display="none",this.reset(),this.options.onClose&&this.options.onClose()},300)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now();const t={place:2,timestamp:Date.now()},e=await this.fetchWithTimeout(this.options.apiUrl,{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Accept:"application/json"}});if(!e.ok)throw new Error(`HTTP ${e.status}: 获取验证码失败`);const n=await e.json();if(!(n.data&&n.data.canvasSrc&&n.data.blockSrc))throw new Error(n.message||n.msg||"验证码数据格式错误");this.captchaData=n.data,await this.renderCaptcha()}catch(t){this.showError("加载验证码失败: "+t.message)}}async fetchWithTimeout(t,e){const n=new AbortController,i=setTimeout(()=>n.abort(),this.options.timeout);try{const s=await fetch(t,{...e,signal:n.signal});return clearTimeout(i),s}catch(t){throw clearTimeout(i),t}}async renderCaptcha(){return new Promise((t,e)=>{let n=0;const i=()=>{n++,2===n&&(this.hideLoading(),this.showCaptcha(),t())},s=()=>e(new Error("图片加载失败"));this.loadImage(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight},i,s),this.loadImage(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY},i,s)})}loadImage(t,e,n,i,s){t.onload=i,t.onerror=s,t.src=e,Object.entries(n).forEach(([e,n])=>{t.style[e]="number"==typeof n?n+"px":n})}showLoading(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.retryBtn.style.display="none"}hideLoading(){this.elements.loadingText.style.display="none"}showCaptcha(){const{elements:t}=this;t.container.style.display="block",t.loadingText.style.display="none",t.track.style.display="block",t.error.style.display="none",t.retryBtn.style.display="none"}showError(t){this.hideLoading(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.retryBtn.style.display="inline-block"}async verify(){if(this.captchaData)try{const t={loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times]},e=await this.fetchWithTimeout(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);const n=await e.json();"0"===n.code||!0===n.success?this.onVerifySuccess():this.onVerifyFail(n.message||n.msg||"验证失败")}catch(t){this.onVerifyFail("网络错误,请检查连接后重试")}else this.onVerifyFail("验证码数据丢失,请刷新重试")}onVerifySuccess(){const t=Date.now()-this.startTime,e=`${(t/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showTimeDisplay(e),setTimeout(()=>{this.options.onSuccess({captchaId:this.captchaData.captchaId,timestamp:Date.now(),duration:t}),this.hide()},2e3)}showTimeDisplay(t){const{elements:e}=this;e.timeDisplay.textContent=`验证成功!耗时:${t}`,e.timeDisplay.style.display="block",e.timeDisplay.style.opacity="0",setTimeout(()=>{e.timeDisplay.style.transition="opacity 0.3s ease",e.timeDisplay.style.opacity="1"},100)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.updateHintText(t,"#f56c6c"),setTimeout(()=>{this.reset(),this.state.retryCount>=this.options.maxRetries?(this.showError("验证失败次数过多,请刷新重试"),this.options.onFail({reason:"max_retries_exceeded",retryCount:this.state.retryCount})):this.refresh()},1500)}reset(){this.state.currentX=0,this.setTransition(!0),this.updateUIState("reset"),this.updateSliderPosition(),this.elements.timeDisplay.style.display="none",this.startTime=null}refresh(){this.reset(),this.captchaData=null,this.state.retryCount=0,this.loadCaptcha()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.removeAllEventListeners(),this.elements.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay),this.elements=null,this.captchaData=null,this.times=null,this.eventListeners=null,this.state=null,this.options=null,this.startTime=null}static create(t){return new PopupSliderCaptcha(t)}static show(t){const e=new PopupSliderCaptcha(t);return e.show(),e}}"undefined"!=typeof module&&module.exports?(module.exports=PopupSliderCaptcha,module.exports.default=PopupSliderCaptcha,module.exports.PopupSliderCaptcha=PopupSliderCaptcha):"function"==typeof define&&define.amd?define([],()=>PopupSliderCaptcha):"undefined"!=typeof window&&(window.PopupSliderCaptcha=PopupSliderCaptcha,window.SliderCaptcha=PopupSliderCaptcha),"undefined"!=typeof window&&(window.SliderCaptcha=PopupSliderCaptcha,window.PopupSliderCaptcha=PopupSliderCaptcha),t.PopupSliderCaptcha=PopupSliderCaptcha,t.default=PopupSliderCaptcha,Object.defineProperty(t,"__esModule",{value:!0})});
|
|
1
|
+
(function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).SliderCaptcha={})})(this,(function(t){"use strict"
|
|
2
|
+
class PopupSliderCaptcha{static DEFAULTS={width:350,height:200,sliderSize:42,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify"}
|
|
3
|
+
static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",finger:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"}
|
|
4
|
+
static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,author:"Your Name",license:"MIT"}}constructor(t={}){this.options={...PopupSliderCaptcha.DEFAULTS,...t},this.elements={},this.state={isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0},this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.init()}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return
|
|
5
|
+
const t=document.createElement("style")
|
|
6
|
+
t.id="slider-captcha-styles",t.textContent=`\n .${PopupSliderCaptcha.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${PopupSliderCaptcha.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(t)}createElements(){const{elements:t}=this,{CSS_CLASSES:n}=PopupSliderCaptcha
|
|
7
|
+
t.overlay=this.createElement("div",n.overlay),t.modal=this.createElement("div",n.modal),t.header=this.createElement("div",n.header),t.title=this.createElement("h3","slider-captcha-title","滑动验证"),t.closeBtn=this.createElement("button","slider-captcha-close","×"),t.refreshBtn=this.createElement("button","slider-captcha-refresh","⟳"),t.container=this.createElement("div",n.container),t.backgroundImg=this.createElement("img","slider-captcha-bg"),t.sliderImg=this.createElement("img","slider-captcha-piece"),t.loadingText=this.createElement("div",n.loading),t.timeDisplay=this.createElement("div","slider-captcha-time"),t.track=this.createElement("div",n.track),t.fingerAnimation=this.createElement("div",n.finger,"👉"),t.btn=this.createElement("div",n.btn),t.icon=this.createElement("div","","→"),t.hint=this.createElement("div",n.hint,"向右滑动完成验证"),t.error=this.createElement("div",n.error),t.retryBtn=this.createElement("button","slider-captcha-retry","重新获取"),t.loadingText.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,n="",i=""){const e=document.createElement(t)
|
|
8
|
+
return n&&(e.className=n),i&&(e.textContent=i),e}assembleDOM(){const{elements:t}=this
|
|
9
|
+
t.btn.appendChild(t.icon),t.track.appendChild(t.fingerAnimation),t.track.appendChild(t.btn),t.track.appendChild(t.hint)
|
|
10
|
+
const n=this.createElement("div","slider-captcha-header-buttons")
|
|
11
|
+
n.appendChild(t.refreshBtn),n.appendChild(t.closeBtn),t.header.appendChild(t.title),t.header.appendChild(n),t.container.appendChild(t.backgroundImg),t.container.appendChild(t.sliderImg),t.container.appendChild(t.loadingText),t.modal.appendChild(t.header),t.modal.appendChild(t.container),t.modal.appendChild(t.track),t.modal.appendChild(t.timeDisplay),t.modal.appendChild(t.error),t.modal.appendChild(t.retryBtn),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){const{elements:t}=this
|
|
12
|
+
t.container.style.display="none",t.track.style.display="none"}addEventListenerWithTracking(t,n,i){t&&(t.addEventListener(n,i),this.eventListeners.push({element:t,event:n,handler:i}))}removeAllEventListeners(){this.eventListeners.forEach((({element:t,event:n,handler:i})=>{t&&t.removeEventListener&&t.removeEventListener(n,i)})),this.eventListeners=[]}bindEvents(){const{elements:t}=this
|
|
13
|
+
this.addEventListenerWithTracking(t.closeBtn,"click",(()=>this.hide())),this.addEventListenerWithTracking(t.refreshBtn,"click",(()=>this.refresh())),this.addEventListenerWithTracking(t.retryBtn,"click",(()=>this.refresh())),this.addEventListenerWithTracking(t.overlay,"click",(n=>{n.target===t.overlay&&this.hide()})),this.addEventListenerWithTracking(document,"keydown",(t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()})),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,n={start:this.handleStart.bind(this),move:this.handleMove.bind(this),end:this.handleEnd.bind(this)}
|
|
14
|
+
this.addEventListenerWithTracking(t.btn,"mousedown",n.start),this.addEventListenerWithTracking(t.btn,"touchstart",n.start),this.addEventListenerWithTracking(t.sliderImg,"mousedown",n.start),this.addEventListenerWithTracking(t.sliderImg,"touchstart",n.start),this.addEventListenerWithTracking(document,"mousemove",n.move),this.addEventListenerWithTracking(document,"touchmove",n.move),this.addEventListenerWithTracking(document,"mouseup",n.end),this.addEventListenerWithTracking(document,"touchend",n.end)}getPosition(){const{elements:t,options:n}=this,i=t.track.offsetWidth-t.btn.offsetWidth,e=this.state.currentX/i
|
|
15
|
+
return Math.round(e*(n.width-n.sliderSize))}handleStart(t){if(!this.captchaData||this.state.isDragging)return
|
|
16
|
+
this.startTime||(this.startTime=Date.now()),this.times=[{time:Date.now(),position:this.getPosition()}],t.preventDefault(),t.stopPropagation(),this.state.isDragging=!0
|
|
17
|
+
const n=this.getClientX(t)
|
|
18
|
+
this.state.startX=n-this.state.currentX,this.setTransition(!1),this.updateUIState("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}handleMove(t){if(!this.state.isDragging)return
|
|
19
|
+
t.preventDefault()
|
|
20
|
+
const n=this.getClientX(t)-this.state.startX,i=this.elements.track.offsetWidth-this.elements.btn.offsetWidth
|
|
21
|
+
this.state.currentX=Math.max(0,Math.min(n,i)),this.times.push({time:Date.now(),position:this.getPosition()}),this.updateSliderPosition()}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.verify())}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const n=t?"all 0.3s ease":"none"
|
|
22
|
+
this.elements.btn.style.transition=n,this.elements.sliderImg.style.transition=n}updateUIState(t){const{elements:n}=this
|
|
23
|
+
switch(t){case"dragging":n.hint.style.opacity="0",n.fingerAnimation.style.display="none"
|
|
24
|
+
break
|
|
25
|
+
case"success":n.btn.style.background="#67c23a",n.icon.innerHTML="✓",n.icon.style.color="white",n.fingerAnimation.style.display="none",this.updateHintText("验证成功","#67c23a")
|
|
26
|
+
break
|
|
27
|
+
case"fail":n.btn.style.background="#f56c6c",n.icon.innerHTML="✗",n.icon.style.color="white"
|
|
28
|
+
break
|
|
29
|
+
case"reset":n.btn.style.background="white",n.icon.innerHTML="→",n.icon.style.color="#666",n.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","#999")}}updateHintText(t,n){const{elements:i}=this
|
|
30
|
+
if(i.hint.textContent=t,i.hint.style.color=n,i.hint.style.opacity="1","验证成功"===t){const t=parseInt(i.btn.style.left)||0,n=i.track.offsetWidth
|
|
31
|
+
let e="50%"
|
|
32
|
+
t>.6*n?e="25%":t>.3*n&&(e="75%"),i.hint.style.left=e}else i.hint.style.left="50%"}updateSliderPosition(){const{elements:t,options:n,state:i}=this,e=t.track.offsetWidth-t.btn.offsetWidth,s=i.currentX/e*(n.width-n.sliderSize),o=i.currentX/e
|
|
33
|
+
t.btn.style.left=i.currentX+"px",t.sliderImg.style.left=s+"px",t.fingerAnimation.style.opacity=.8>o?"0.6":"0"}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",this.elements.overlay.offsetHeight,requestAnimationFrame((()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")})),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),setTimeout((()=>{this.elements.overlay.style.display="none",this.reset(),this.options.onClose&&this.options.onClose()}),300)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now()
|
|
34
|
+
const t={place:2,timestamp:Date.now()},n=await this.fetchWithTimeout(this.options.apiUrl,{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json",Accept:"application/json"}})
|
|
35
|
+
if(!n.ok)throw Error(`HTTP ${n.status}: 获取验证码失败`)
|
|
36
|
+
const i=await n.json()
|
|
37
|
+
if(!(i.data&&i.data.canvasSrc&&i.data.blockSrc))throw Error(i.message||i.msg||"验证码数据格式错误")
|
|
38
|
+
this.captchaData=i.data,await this.renderCaptcha()}catch(t){this.showError("加载验证码失败: "+t.message)}}async fetchWithTimeout(t,n){const i=new AbortController,e=setTimeout((()=>i.abort()),this.options.timeout)
|
|
39
|
+
try{const s=await fetch(t,{...n,signal:i.signal})
|
|
40
|
+
return clearTimeout(e),s}catch(t){throw clearTimeout(e),t}}async renderCaptcha(){return new Promise(((t,n)=>{let i=0
|
|
41
|
+
const e=()=>{i++,2===i&&(this.hideLoading(),this.showCaptcha(),t())},s=()=>n(Error("图片加载失败"))
|
|
42
|
+
this.loadImage(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight},e,s),this.loadImage(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY},e,s)}))}loadImage(t,n,i,e,s){t.onload=e,t.onerror=s,t.src=n,Object.entries(i).forEach((([n,i])=>{t.style[n]="number"==typeof i?i+"px":i}))}showLoading(){const{elements:t}=this
|
|
43
|
+
t.container.style.display="block",t.loadingText.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.retryBtn.style.display="none"}hideLoading(){this.elements.loadingText.style.display="none"}showCaptcha(){const{elements:t}=this
|
|
44
|
+
t.container.style.display="block",t.loadingText.style.display="none",t.track.style.display="block",t.error.style.display="none",t.retryBtn.style.display="none"}showError(t){this.hideLoading(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.retryBtn.style.display="inline-block"}async verify(){if(this.captchaData)try{const t={loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times]},n=await this.fetchWithTimeout(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(t)})
|
|
45
|
+
if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`)
|
|
46
|
+
const i=await n.json()
|
|
47
|
+
"0"===i.code||!0===i.success?this.onVerifySuccess():this.onVerifyFail(i.message||i.msg||"验证失败")}catch(t){this.onVerifyFail("网络错误,请检查连接后重试")}else this.onVerifyFail("验证码数据丢失,请刷新重试")}onVerifySuccess(){const t=Date.now()-this.startTime,n=(t/1e3).toFixed(2)+"s"
|
|
48
|
+
this.updateUIState("success"),this.showTimeDisplay(n),setTimeout((()=>{this.options.onSuccess({captchaId:this.captchaData.captchaId,timestamp:Date.now(),duration:t}),this.hide()}),2e3)}showTimeDisplay(t){const{elements:n}=this
|
|
49
|
+
n.timeDisplay.textContent="验证成功!耗时:"+t,n.timeDisplay.style.display="block",n.timeDisplay.style.opacity="0",setTimeout((()=>{n.timeDisplay.style.transition="opacity 0.3s ease",n.timeDisplay.style.opacity="1"}),100)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.updateHintText(t,"#f56c6c"),setTimeout((()=>{this.reset(),this.state.retryCount<this.options.maxRetries?this.refresh():(this.showError("验证失败次数过多,请刷新重试"),this.options.onFail({reason:"max_retries_exceeded",retryCount:this.state.retryCount}))}),1500)}reset(){this.state.currentX=0,this.setTransition(!0),this.updateUIState("reset"),this.updateSliderPosition(),this.elements.timeDisplay.style.display="none",this.startTime=null}refresh(){this.reset(),this.captchaData=null,this.state.retryCount=0,this.loadCaptcha()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.removeAllEventListeners(),this.elements.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay),this.elements=null,this.captchaData=null,this.times=null,this.eventListeners=null,this.state=null,this.options=null,this.startTime=null}static create(t){return new PopupSliderCaptcha(t)}static show(t){const n=new PopupSliderCaptcha(t)
|
|
50
|
+
return n.show(),n}}"undefined"!=typeof module&&module.exports?(module.exports=PopupSliderCaptcha,module.exports.default=PopupSliderCaptcha,module.exports.PopupSliderCaptcha=PopupSliderCaptcha):"function"==typeof define&&define.amd?define([],(()=>PopupSliderCaptcha)):"undefined"!=typeof window&&(window.PopupSliderCaptcha=PopupSliderCaptcha,window.SliderCaptcha=PopupSliderCaptcha),"undefined"!=typeof window&&(window.SliderCaptcha=PopupSliderCaptcha,window.PopupSliderCaptcha=PopupSliderCaptcha),t.PopupSliderCaptcha=PopupSliderCaptcha,t.default=PopupSliderCaptcha,Object.defineProperty(t,"t",{value:!0})}))
|
|
2
51
|
//# sourceMappingURL=slider-captcha.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slider-captcha.min.js","sources":["../src/slider-captcha.js","../src/index.js"],"sourcesContent":["/**\n * 纯JavaScript弹窗滑块验证码组件\n * @class PopupSliderCaptcha\n */\nclass PopupSliderCaptcha {\n // ==================== 静态配置 ====================\n static DEFAULTS = {\n width: 350,\n height: 200,\n sliderSize: 42,\n maxRetries: 3,\n timeout: 30000,\n apiUrl: \"/api/captcha\",\n verifyUrl: \"/api/captcha/verify\"\n }\n\n static CSS_CLASSES = {\n overlay: \"slider-captcha-overlay\",\n modal: \"slider-captcha-modal\",\n header: \"slider-captcha-header\",\n container: \"slider-captcha-container\",\n track: \"slider-captcha-track\",\n btn: \"slider-captcha-btn\",\n finger: \"slider-captcha-finger\",\n hint: \"slider-captcha-hint\",\n loading: \"slider-captcha-loading\",\n error: \"slider-captcha-error\"\n }\n\n static get version() {\n return '1.0.0'\n }\n\n // 添加SDK信息\n static get info() {\n return {\n name: 'Slider Captcha SDK',\n version: this.version,\n author: 'Your Name',\n license: 'MIT'\n }\n }\n // ==================== 构造函数和初始化 ====================\n constructor(options = {}) {\n this.options = { ...PopupSliderCaptcha.DEFAULTS, ...options }\n this.elements = {}\n this.state = {\n isVisible: false,\n isDragging: false,\n currentX: 0,\n startX: 0,\n retryCount: 0\n }\n this.captchaData = null\n this.times = []\n this.startTime = null\n this.eventListeners = []\n\n this.init()\n }\n\n init() {\n this.injectStyles()\n this.createElements()\n this.bindEvents()\n }\n\n // ==================== 样式注入 ====================\n injectStyles() {\n if (document.querySelector(\"#slider-captcha-styles\")) return\n\n const style = document.createElement(\"style\")\n style.id = \"slider-captcha-styles\"\n style.textContent = `\n .${PopupSliderCaptcha.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${PopupSliderCaptcha.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `\n document.head.appendChild(style)\n }\n\n // ==================== DOM 创建和管理 ====================\n createElements() {\n const { elements } = this\n const { CSS_CLASSES } = PopupSliderCaptcha\n\n // 创建主要元素\n elements.overlay = this.createElement(\"div\", CSS_CLASSES.overlay)\n elements.modal = this.createElement(\"div\", CSS_CLASSES.modal)\n elements.header = this.createElement(\"div\", CSS_CLASSES.header)\n elements.title = this.createElement(\"h3\", \"slider-captcha-title\", \"滑动验证\")\n elements.closeBtn = this.createElement(\"button\", \"slider-captcha-close\", \"×\")\n elements.refreshBtn = this.createElement(\"button\", \"slider-captcha-refresh\", \"⟳\") // 添加刷新按钮\n\n // 验证码相关元素\n elements.container = this.createElement(\"div\", CSS_CLASSES.container)\n elements.backgroundImg = this.createElement(\"img\", \"slider-captcha-bg\")\n elements.sliderImg = this.createElement(\"img\", \"slider-captcha-piece\")\n elements.loadingText = this.createElement(\"div\", CSS_CLASSES.loading)\n elements.timeDisplay = this.createElement(\"div\", \"slider-captcha-time\") // 添加耗时显示\n\n // 滑块轨道相关元素\n elements.track = this.createElement(\"div\", CSS_CLASSES.track)\n elements.fingerAnimation = this.createElement(\"div\", CSS_CLASSES.finger, \"👉\")\n elements.btn = this.createElement(\"div\", CSS_CLASSES.btn)\n elements.icon = this.createElement(\"div\", \"\", \"→\")\n elements.hint = this.createElement(\"div\", CSS_CLASSES.hint, \"向右滑动完成验证\")\n\n // 错误和重试元素\n elements.error = this.createElement(\"div\", CSS_CLASSES.error)\n elements.retryBtn = this.createElement(\"button\", \"slider-captcha-retry\", \"重新获取\")\n\n // 设置加载动画内容\n elements.loadingText.innerHTML = `\n <span class=\"loading-dots\">\n <span class=\"dot\">.</span>\n <span class=\"dot\">.</span>\n <span class=\"dot\">.</span>\n </span>\n `\n\n // 组装DOM结构\n this.assembleDOM()\n\n // 初始状态设置\n this.setInitialState()\n }\n\n // 辅助方法:创建元素\n createElement(tag, className = \"\", textContent = \"\") {\n const element = document.createElement(tag)\n if (className) element.className = className\n if (textContent) element.textContent = textContent\n return element\n }\n\n // 组装DOM结构\n assembleDOM() {\n const { elements } = this\n\n // 组装滑块按钮\n elements.btn.appendChild(elements.icon)\n\n // 组装滑块轨道(移除耗时显示)\n elements.track.appendChild(elements.fingerAnimation)\n elements.track.appendChild(elements.btn)\n elements.track.appendChild(elements.hint)\n\n // 创建头部按钮容器\n const headerButtons = this.createElement(\"div\", \"slider-captcha-header-buttons\")\n headerButtons.appendChild(elements.refreshBtn)\n headerButtons.appendChild(elements.closeBtn)\n\n // 组装头部\n elements.header.appendChild(elements.title)\n elements.header.appendChild(headerButtons)\n\n // 组装验证码容器\n elements.container.appendChild(elements.backgroundImg)\n elements.container.appendChild(elements.sliderImg)\n elements.container.appendChild(elements.loadingText)\n\n // 组装模态框(将耗时显示放在轨道后面)\n elements.modal.appendChild(elements.header)\n elements.modal.appendChild(elements.container)\n elements.modal.appendChild(elements.track)\n elements.modal.appendChild(elements.timeDisplay) // 移动到这里,在轨道下方\n elements.modal.appendChild(elements.error)\n elements.modal.appendChild(elements.retryBtn)\n\n // 组装遮罩层\n elements.overlay.appendChild(elements.modal)\n\n // 添加到页面\n document.body.appendChild(elements.overlay)\n }\n\n // 设置初始状态\n setInitialState() {\n const { elements } = this\n elements.container.style.display = \"none\"\n elements.track.style.display = \"none\"\n }\n\n // ==================== 事件管理 ====================\n addEventListenerWithTracking(element, event, handler) {\n if (!element) return\n\n element.addEventListener(event, handler)\n\n // 跟踪事件监听器以便后续移除\n this.eventListeners.push({\n element,\n event,\n handler\n })\n }\n\n // 移除所有跟踪的事件监听器\n removeAllEventListeners() {\n this.eventListeners.forEach(({ element, event, handler }) => {\n if (element && element.removeEventListener) {\n element.removeEventListener(event, handler)\n }\n })\n this.eventListeners = []\n }\n\n // 绑定事件\n bindEvents() {\n const { elements } = this\n\n // 使用箭头函数避免this绑定问题,并跟踪事件监听器\n this.addEventListenerWithTracking(elements.closeBtn, \"click\", () => this.hide())\n this.addEventListenerWithTracking(elements.refreshBtn, \"click\", () => this.refresh()) // 添加刷新按钮事件\n this.addEventListenerWithTracking(elements.retryBtn, \"click\", () => this.refresh())\n\n // 遮罩层点击关闭\n this.addEventListenerWithTracking(elements.overlay, \"click\", (e) => {\n if (e.target === elements.overlay) this.hide()\n })\n\n // 键盘事件\n this.addEventListenerWithTracking(document, \"keydown\", (e) => {\n if (e.key === \"Escape\" && this.state.isVisible) this.hide()\n })\n\n // 滑块拖拽事件\n this.bindSliderEvents()\n }\n\n bindSliderEvents() {\n const { elements } = this\n\n // 统一的事件处理器\n const eventHandlers = {\n start: this.handleStart.bind(this),\n move: this.handleMove.bind(this),\n end: this.handleEnd.bind(this)\n }\n\n // 滑块和按钮的开始事件\n this.addEventListenerWithTracking(elements.btn, \"mousedown\", eventHandlers.start)\n this.addEventListenerWithTracking(elements.btn, \"touchstart\", eventHandlers.start)\n this.addEventListenerWithTracking(elements.sliderImg, \"mousedown\", eventHandlers.start)\n this.addEventListenerWithTracking(elements.sliderImg, \"touchstart\", eventHandlers.start)\n\n // 文档级别的移动和结束事件\n this.addEventListenerWithTracking(document, \"mousemove\", eventHandlers.move)\n this.addEventListenerWithTracking(document, \"touchmove\", eventHandlers.move)\n this.addEventListenerWithTracking(document, \"mouseup\", eventHandlers.end)\n this.addEventListenerWithTracking(document, \"touchend\", eventHandlers.end)\n }\n\n // ==================== 拖拽处理 ====================\n getPosition() {\n const { elements, options } = this\n const maxX = elements.track.offsetWidth - elements.btn.offsetWidth\n const percentage = this.state.currentX / maxX\n return Math.round(percentage * (options.width - options.sliderSize))\n }\n\n handleStart(e) {\n if (!this.captchaData || this.state.isDragging) return\n\n // 记录开始时间(如果还没有记录)\n if (!this.startTime) {\n this.startTime = Date.now()\n }\n\n // 初始化拖拽轨迹\n this.times = [{ time: Date.now(), position: this.getPosition() }]\n\n e.preventDefault()\n e.stopPropagation()\n\n this.state.isDragging = true\n const clientX = this.getClientX(e)\n this.state.startX = clientX - this.state.currentX\n\n // 移除过渡效果并更新UI状态\n this.setTransition(false)\n this.updateUIState(\"dragging\")\n\n // 添加拖拽时的视觉反馈\n document.body.style.userSelect = \"none\"\n document.body.style.cursor = \"grabbing\"\n }\n\n handleMove(e) {\n if (!this.state.isDragging) return\n\n e.preventDefault()\n const clientX = this.getClientX(e)\n const deltaX = clientX - this.state.startX\n const maxX = this.elements.track.offsetWidth - this.elements.btn.offsetWidth\n\n this.state.currentX = Math.max(0, Math.min(deltaX, maxX))\n this.times.push({ time: Date.now(), position: this.getPosition() })\n\n this.updateSliderPosition()\n }\n\n handleEnd() {\n if (!this.state.isDragging) return\n\n this.times.push({ time: Date.now(), position: this.getPosition() })\n this.state.isDragging = false\n this.verify()\n }\n\n // 辅助方法:获取客户端X坐标\n getClientX(e) {\n return e.type.includes(\"touch\") ? e.touches[0].clientX : e.clientX\n }\n\n // ==================== UI 状态更新 ====================\n setTransition(enabled) {\n const transition = enabled ? \"all 0.3s ease\" : \"none\"\n this.elements.btn.style.transition = transition\n this.elements.sliderImg.style.transition = transition\n }\n\n // 更新UI状态\n updateUIState(state) {\n const { elements } = this\n\n switch (state) {\n case \"dragging\":\n elements.hint.style.opacity = \"0\"\n elements.fingerAnimation.style.display = \"none\"\n break\n case \"success\":\n elements.btn.style.background = \"#67c23a\"\n elements.icon.innerHTML = \"✓\"\n elements.icon.style.color = \"white\"\n elements.fingerAnimation.style.display = \"none\"\n this.updateHintText(\"验证成功\", \"#67c23a\")\n break\n case \"fail\":\n elements.btn.style.background = \"#f56c6c\"\n elements.icon.innerHTML = \"✗\"\n elements.icon.style.color = \"white\"\n break\n case \"reset\":\n elements.btn.style.background = \"white\"\n elements.icon.innerHTML = \"→\"\n elements.icon.style.color = \"#666\"\n elements.fingerAnimation.style.display = \"block\"\n this.updateHintText(\"向右滑动完成验证\", \"#999\")\n break\n }\n }\n\n // 更新提示文字\n updateHintText(text, color) {\n const { elements } = this\n elements.hint.textContent = text\n elements.hint.style.color = color\n elements.hint.style.opacity = \"1\"\n\n // 根据滑块位置调整文字位置\n if (text === \"验证成功\") {\n const sliderPosition = parseInt(elements.btn.style.left) || 0\n const trackWidth = elements.track.offsetWidth\n let textLeft = \"50%\"\n\n if (sliderPosition > trackWidth * 0.6) {\n textLeft = \"25%\"\n } else if (sliderPosition > trackWidth * 0.3) {\n textLeft = \"75%\"\n }\n\n elements.hint.style.left = textLeft\n } else {\n elements.hint.style.left = \"50%\"\n }\n }\n\n // 更新滑块位置\n updateSliderPosition() {\n const { elements, options, state } = this\n const maxX = elements.track.offsetWidth - elements.btn.offsetWidth\n const pieceX = (state.currentX / maxX) * (options.width - options.sliderSize)\n\n // 计算滑动进度百分比\n const progress = state.currentX / maxX\n\n elements.btn.style.left = state.currentX + \"px\"\n elements.sliderImg.style.left = pieceX + \"px\"\n\n // 当滑动到80%时,finger元素透明度变为0\n if (progress >= 0.8) {\n elements.fingerAnimation.style.opacity = \"0\"\n } else {\n elements.fingerAnimation.style.opacity = \"0.6\"\n }\n }\n\n // ==================== 显示和隐藏 ====================\n show() {\n this.state.isVisible = true\n this.elements.overlay.style.display = \"flex\"\n\n // 强制重绘以确保初始状态生效\n this.elements.overlay.offsetHeight\n\n // 添加动画类\n requestAnimationFrame(() => {\n this.elements.overlay.classList.add(\"show\")\n this.elements.modal.classList.add(\"show\")\n })\n\n this.loadCaptcha()\n }\n\n hide() {\n this.state.isVisible = false\n\n // 移除动画类\n this.elements.overlay.classList.remove(\"show\")\n this.elements.modal.classList.remove(\"show\")\n\n // 等待动画完成后隐藏\n setTimeout(() => {\n this.elements.overlay.style.display = \"none\"\n this.reset()\n if (this.options.onClose) {\n this.options.onClose()\n }\n }, 300)\n }\n\n // ==================== 验证码加载和渲染 ====================\n async loadCaptcha() {\n try {\n this.showLoading()\n this.startTime = Date.now() // 记录开始时间\n\n const requestData = {\n place: 2,\n timestamp: Date.now()\n }\n\n const response = await this.fetchWithTimeout(this.options.apiUrl, {\n method: \"POST\",\n body: JSON.stringify(requestData),\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\"\n }\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: 获取验证码失败`)\n }\n\n const data = await response.json()\n\n if (data.data && data.data.canvasSrc && data.data.blockSrc) {\n this.captchaData = data.data\n await this.renderCaptcha()\n } else {\n throw new Error(data.message || data.msg || \"验证码数据格式错误\")\n }\n } catch (error) {\n console.error(\"加载验证码失败:\", error)\n this.showError(\"加载验证码失败: \" + error.message)\n }\n }\n\n // 带超时的fetch\n async fetchWithTimeout(url, options) {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.options.timeout)\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal\n })\n clearTimeout(timeoutId)\n return response\n } catch (error) {\n clearTimeout(timeoutId)\n throw error\n }\n }\n\n // 渲染验证码\n async renderCaptcha() {\n return new Promise((resolve, reject) => {\n let loadedCount = 0\n const totalImages = 2\n\n const onImageLoad = () => {\n loadedCount++\n if (loadedCount === totalImages) {\n this.hideLoading()\n this.showCaptcha()\n resolve()\n }\n }\n\n const onImageError = () => reject(new Error(\"图片加载失败\"))\n\n // 加载背景图片\n this.loadImage(\n this.elements.backgroundImg,\n this.captchaData.canvasSrc,\n {\n width: this.captchaData.canvasWidth,\n height: this.captchaData.canvasHeight\n },\n onImageLoad,\n onImageError\n )\n\n // 加载滑块图片\n this.loadImage(\n this.elements.sliderImg,\n this.captchaData.blockSrc,\n {\n width: this.captchaData.blockWidth,\n height: this.captchaData.blockHeight,\n top: this.captchaData.blockY\n },\n onImageLoad,\n onImageError\n )\n })\n }\n\n // 加载图片\n loadImage(imgElement, src, styles, onLoad, onError) {\n imgElement.onload = onLoad\n imgElement.onerror = onError\n imgElement.src = src\n\n // 应用样式\n Object.entries(styles).forEach(([key, value]) => {\n imgElement.style[key] = typeof value === \"number\" ? value + \"px\" : value\n })\n }\n\n // ==================== 加载状态管理 ====================\n showLoading() {\n const { elements } = this\n elements.container.style.display = \"block\"\n elements.loadingText.style.display = \"flex\"\n elements.track.style.display = \"none\"\n elements.error.style.display = \"none\"\n elements.retryBtn.style.display = \"none\"\n }\n\n // 隐藏加载状态\n hideLoading() {\n this.elements.loadingText.style.display = \"none\"\n }\n\n // 显示验证码\n showCaptcha() {\n const { elements } = this\n elements.container.style.display = \"block\"\n elements.loadingText.style.display = \"none\"\n elements.track.style.display = \"block\"\n elements.error.style.display = \"none\"\n elements.retryBtn.style.display = \"none\"\n }\n\n // 显示错误\n showError(message) {\n this.hideLoading()\n this.elements.error.textContent = message\n this.elements.error.style.display = \"block\"\n this.elements.retryBtn.style.display = \"inline-block\"\n }\n\n // ==================== 验证逻辑 ====================\n async verify() {\n if (!this.captchaData) {\n this.onVerifyFail(\"验证码数据丢失,请刷新重试\")\n return\n }\n\n try {\n const requestData = {\n loginVo: {\n nonceStr: this.captchaData.nonceStr,\n value: this.getPosition()\n },\n dragEventList: [...this.times]\n }\n\n const response = await this.fetchWithTimeout(this.options.verifyUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\"\n },\n body: JSON.stringify(requestData)\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const data = await response.json()\n\n if (data.code === \"0\" || data.success === true) {\n this.onVerifySuccess()\n } else {\n this.onVerifyFail(data.message || data.msg || \"验证失败\")\n }\n } catch (error) {\n console.error(\"验证请求失败:\", error)\n this.onVerifyFail(\"网络错误,请检查连接后重试\")\n }\n }\n\n // 验证成功\n onVerifySuccess() {\n const endTime = Date.now()\n const duration = endTime - this.startTime\n const durationText = `${(duration / 1000).toFixed(2)}s`\n\n this.updateUIState(\"success\")\n this.showTimeDisplay(durationText) // 显示耗时\n\n setTimeout(() => {\n this.options.onSuccess({\n captchaId: this.captchaData.captchaId,\n timestamp: Date.now(),\n duration: duration\n })\n this.hide()\n }, 2000) // 延长显示时间以便用户看到耗时\n }\n\n // 添加显示耗时的方法\n showTimeDisplay(timeText) {\n const { elements } = this\n elements.timeDisplay.textContent = `验证成功!耗时:${timeText}`\n elements.timeDisplay.style.display = \"block\"\n\n // 添加淡入动画\n elements.timeDisplay.style.opacity = \"0\"\n setTimeout(() => {\n elements.timeDisplay.style.transition = \"opacity 0.3s ease\"\n elements.timeDisplay.style.opacity = \"1\"\n }, 100)\n }\n\n // 验证失败\n onVerifyFail(message) {\n this.state.retryCount++\n this.updateUIState(\"fail\")\n this.updateHintText(message, \"#f56c6c\")\n\n setTimeout(() => {\n this.reset()\n\n if (this.state.retryCount >= this.options.maxRetries) {\n this.showError(\"验证失败次数过多,请刷新重试\")\n this.options.onFail({\n reason: \"max_retries_exceeded\",\n retryCount: this.state.retryCount\n })\n } else {\n this.refresh()\n }\n }, 1500)\n }\n\n // ==================== 重置和刷新 ====================\n reset() {\n this.state.currentX = 0\n this.setTransition(true)\n this.updateUIState(\"reset\")\n this.updateSliderPosition()\n\n // 隐藏耗时显示\n this.elements.timeDisplay.style.display = \"none\"\n this.startTime = null\n }\n\n // 刷新\n refresh() {\n this.reset()\n this.captchaData = null\n this.state.retryCount = 0\n this.loadCaptcha()\n }\n\n // ==================== 销毁和清理 ====================\n destroy() {\n // 恢复页面状态\n document.body.style.userSelect = \"\"\n document.body.style.cursor = \"\"\n\n // 移除所有事件监听器\n this.removeAllEventListeners()\n\n // 移除DOM元素\n if (this.elements.overlay?.parentNode) {\n this.elements.overlay.parentNode.removeChild(this.elements.overlay)\n }\n\n // 清理所有引用\n this.elements = null\n this.captchaData = null\n this.times = null\n this.eventListeners = null\n this.state = null\n this.options = null\n this.startTime = null\n }\n\n // ==================== 静态工厂方法 ====================\n static create(options) {\n return new PopupSliderCaptcha(options)\n }\n\n static show(options) {\n const instance = new PopupSliderCaptcha(options)\n instance.show()\n return instance\n }\n}\n\n// ==================== 模块导出 ====================\n// ES Module export (for modern bundlers)\nexport default PopupSliderCaptcha\nexport { PopupSliderCaptcha }\n\n// CommonJS export (for Node.js compatibility)\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = PopupSliderCaptcha\n module.exports.default = PopupSliderCaptcha\n module.exports.PopupSliderCaptcha = PopupSliderCaptcha\n} else if (typeof define === \"function\" && define.amd) {\n define([], () => PopupSliderCaptcha)\n} else if (typeof window !== \"undefined\") {\n window.PopupSliderCaptcha = PopupSliderCaptcha\n window.SliderCaptcha = PopupSliderCaptcha\n}\n","// 导入滑块验证码组件\nimport PopupSliderCaptcha from './slider-captcha.js'\n\n// 导出主要类\nexport { PopupSliderCaptcha }\n\n// 默认导出\nexport default PopupSliderCaptcha\n\n// 全局注册(用于UMD构建)\nif (typeof window !== 'undefined') {\n window.SliderCaptcha = PopupSliderCaptcha\n window.PopupSliderCaptcha = PopupSliderCaptcha\n}\n"],"names":["PopupSliderCaptcha","static","width","height","sliderSize","maxRetries","timeout","apiUrl","verifyUrl","overlay","modal","header","container","track","btn","finger","hint","loading","error","version","info","name","this","author","license","constructor","options","DEFAULTS","elements","state","isVisible","isDragging","currentX","startX","retryCount","captchaData","times","startTime","eventListeners","init","injectStyles","createElements","bindEvents","document","querySelector","style","createElement","id","textContent","CSS_CLASSES","head","appendChild","title","closeBtn","refreshBtn","backgroundImg","sliderImg","loadingText","timeDisplay","fingerAnimation","icon","retryBtn","innerHTML","assembleDOM","setInitialState","tag","className","element","headerButtons","body","display","addEventListenerWithTracking","event","handler","addEventListener","push","removeAllEventListeners","forEach","removeEventListener","hide","refresh","e","target","key","bindSliderEvents","eventHandlers","start","handleStart","bind","move","handleMove","end","handleEnd","getPosition","maxX","offsetWidth","percentage","Math","round","Date","now","time","position","preventDefault","stopPropagation","clientX","getClientX","setTransition","updateUIState","userSelect","cursor","deltaX","max","min","updateSliderPosition","verify","type","includes","touches","enabled","transition","opacity","background","color","updateHintText","text","sliderPosition","parseInt","left","trackWidth","textLeft","pieceX","progress","show","offsetHeight","requestAnimationFrame","classList","add","loadCaptcha","remove","setTimeout","reset","onClose","showLoading","requestData","place","timestamp","response","fetchWithTimeout","method","JSON","stringify","headers","Accept","ok","Error","status","data","json","canvasSrc","blockSrc","message","msg","renderCaptcha","showError","url","controller","AbortController","timeoutId","abort","fetch","signal","clearTimeout","Promise","resolve","reject","loadedCount","onImageLoad","hideLoading","showCaptcha","onImageError","loadImage","canvasWidth","canvasHeight","blockWidth","blockHeight","top","blockY","imgElement","src","styles","onLoad","onError","onload","onerror","Object","entries","value","loginVo","nonceStr","dragEventList","statusText","code","success","onVerifySuccess","onVerifyFail","duration","durationText","toFixed","showTimeDisplay","onSuccess","captchaId","timeText","onFail","reason","destroy","parentNode","removeChild","create","instance","module","exports","default","define","amd","window","SliderCaptcha"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slider-captcha.min.js","sources":["../src/slider-captcha.js","../src/index.js"],"sourcesContent":["/**\n * 纯JavaScript弹窗滑块验证码组件\n * @class PopupSliderCaptcha\n */\nclass PopupSliderCaptcha {\n // ==================== 静态配置 ====================\n static DEFAULTS = {\n width: 350,\n height: 200,\n sliderSize: 42,\n maxRetries: 3,\n timeout: 30000,\n apiUrl: \"/api/captcha\",\n verifyUrl: \"/api/captcha/verify\"\n }\n\n static CSS_CLASSES = {\n overlay: \"slider-captcha-overlay\",\n modal: \"slider-captcha-modal\",\n header: \"slider-captcha-header\",\n container: \"slider-captcha-container\",\n track: \"slider-captcha-track\",\n btn: \"slider-captcha-btn\",\n finger: \"slider-captcha-finger\",\n hint: \"slider-captcha-hint\",\n loading: \"slider-captcha-loading\",\n error: \"slider-captcha-error\"\n }\n\n static get version() {\n return '1.0.0'\n }\n\n // 添加SDK信息\n static get info() {\n return {\n name: 'Slider Captcha SDK',\n version: this.version,\n author: 'Your Name',\n license: 'MIT'\n }\n }\n // ==================== 构造函数和初始化 ====================\n constructor(options = {}) {\n this.options = { ...PopupSliderCaptcha.DEFAULTS, ...options }\n this.elements = {}\n this.state = {\n isVisible: false,\n isDragging: false,\n currentX: 0,\n startX: 0,\n retryCount: 0\n }\n this.captchaData = null\n this.times = []\n this.startTime = null\n this.eventListeners = []\n\n this.init()\n }\n\n init() {\n this.injectStyles()\n this.createElements()\n this.bindEvents()\n }\n\n // ==================== 样式注入 ====================\n injectStyles() {\n if (document.querySelector(\"#slider-captcha-styles\")) return\n\n const style = document.createElement(\"style\")\n style.id = \"slider-captcha-styles\"\n style.textContent = `\n .${PopupSliderCaptcha.CSS_CLASSES.overlay} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.overlay}.show {\n opacity: 1;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${PopupSliderCaptcha.CSS_CLASSES.header} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.btn} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.finger} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${PopupSliderCaptcha.CSS_CLASSES.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${PopupSliderCaptcha.CSS_CLASSES.modal}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `\n document.head.appendChild(style)\n }\n\n // ==================== DOM 创建和管理 ====================\n createElements() {\n const { elements } = this\n const { CSS_CLASSES } = PopupSliderCaptcha\n\n // 创建主要元素\n elements.overlay = this.createElement(\"div\", CSS_CLASSES.overlay)\n elements.modal = this.createElement(\"div\", CSS_CLASSES.modal)\n elements.header = this.createElement(\"div\", CSS_CLASSES.header)\n elements.title = this.createElement(\"h3\", \"slider-captcha-title\", \"滑动验证\")\n elements.closeBtn = this.createElement(\"button\", \"slider-captcha-close\", \"×\")\n elements.refreshBtn = this.createElement(\"button\", \"slider-captcha-refresh\", \"⟳\") // 添加刷新按钮\n\n // 验证码相关元素\n elements.container = this.createElement(\"div\", CSS_CLASSES.container)\n elements.backgroundImg = this.createElement(\"img\", \"slider-captcha-bg\")\n elements.sliderImg = this.createElement(\"img\", \"slider-captcha-piece\")\n elements.loadingText = this.createElement(\"div\", CSS_CLASSES.loading)\n elements.timeDisplay = this.createElement(\"div\", \"slider-captcha-time\") // 添加耗时显示\n\n // 滑块轨道相关元素\n elements.track = this.createElement(\"div\", CSS_CLASSES.track)\n elements.fingerAnimation = this.createElement(\"div\", CSS_CLASSES.finger, \"👉\")\n elements.btn = this.createElement(\"div\", CSS_CLASSES.btn)\n elements.icon = this.createElement(\"div\", \"\", \"→\")\n elements.hint = this.createElement(\"div\", CSS_CLASSES.hint, \"向右滑动完成验证\")\n\n // 错误和重试元素\n elements.error = this.createElement(\"div\", CSS_CLASSES.error)\n elements.retryBtn = this.createElement(\"button\", \"slider-captcha-retry\", \"重新获取\")\n\n // 设置加载动画内容\n elements.loadingText.innerHTML = `\n <span class=\"loading-dots\">\n <span class=\"dot\">.</span>\n <span class=\"dot\">.</span>\n <span class=\"dot\">.</span>\n </span>\n `\n\n // 组装DOM结构\n this.assembleDOM()\n\n // 初始状态设置\n this.setInitialState()\n }\n\n // 辅助方法:创建元素\n createElement(tag, className = \"\", textContent = \"\") {\n const element = document.createElement(tag)\n if (className) element.className = className\n if (textContent) element.textContent = textContent\n return element\n }\n\n // 组装DOM结构\n assembleDOM() {\n const { elements } = this\n\n // 组装滑块按钮\n elements.btn.appendChild(elements.icon)\n\n // 组装滑块轨道(移除耗时显示)\n elements.track.appendChild(elements.fingerAnimation)\n elements.track.appendChild(elements.btn)\n elements.track.appendChild(elements.hint)\n\n // 创建头部按钮容器\n const headerButtons = this.createElement(\"div\", \"slider-captcha-header-buttons\")\n headerButtons.appendChild(elements.refreshBtn)\n headerButtons.appendChild(elements.closeBtn)\n\n // 组装头部\n elements.header.appendChild(elements.title)\n elements.header.appendChild(headerButtons)\n\n // 组装验证码容器\n elements.container.appendChild(elements.backgroundImg)\n elements.container.appendChild(elements.sliderImg)\n elements.container.appendChild(elements.loadingText)\n\n // 组装模态框(将耗时显示放在轨道后面)\n elements.modal.appendChild(elements.header)\n elements.modal.appendChild(elements.container)\n elements.modal.appendChild(elements.track)\n elements.modal.appendChild(elements.timeDisplay) // 移动到这里,在轨道下方\n elements.modal.appendChild(elements.error)\n elements.modal.appendChild(elements.retryBtn)\n\n // 组装遮罩层\n elements.overlay.appendChild(elements.modal)\n\n // 添加到页面\n document.body.appendChild(elements.overlay)\n }\n\n // 设置初始状态\n setInitialState() {\n const { elements } = this\n elements.container.style.display = \"none\"\n elements.track.style.display = \"none\"\n }\n\n // ==================== 事件管理 ====================\n addEventListenerWithTracking(element, event, handler) {\n if (!element) return\n\n element.addEventListener(event, handler)\n\n // 跟踪事件监听器以便后续移除\n this.eventListeners.push({\n element,\n event,\n handler\n })\n }\n\n // 移除所有跟踪的事件监听器\n removeAllEventListeners() {\n this.eventListeners.forEach(({ element, event, handler }) => {\n if (element && element.removeEventListener) {\n element.removeEventListener(event, handler)\n }\n })\n this.eventListeners = []\n }\n\n // 绑定事件\n bindEvents() {\n const { elements } = this\n\n // 使用箭头函数避免this绑定问题,并跟踪事件监听器\n this.addEventListenerWithTracking(elements.closeBtn, \"click\", () => this.hide())\n this.addEventListenerWithTracking(elements.refreshBtn, \"click\", () => this.refresh()) // 添加刷新按钮事件\n this.addEventListenerWithTracking(elements.retryBtn, \"click\", () => this.refresh())\n\n // 遮罩层点击关闭\n this.addEventListenerWithTracking(elements.overlay, \"click\", (e) => {\n if (e.target === elements.overlay) this.hide()\n })\n\n // 键盘事件\n this.addEventListenerWithTracking(document, \"keydown\", (e) => {\n if (e.key === \"Escape\" && this.state.isVisible) this.hide()\n })\n\n // 滑块拖拽事件\n this.bindSliderEvents()\n }\n\n bindSliderEvents() {\n const { elements } = this\n\n // 统一的事件处理器\n const eventHandlers = {\n start: this.handleStart.bind(this),\n move: this.handleMove.bind(this),\n end: this.handleEnd.bind(this)\n }\n\n // 滑块和按钮的开始事件\n this.addEventListenerWithTracking(elements.btn, \"mousedown\", eventHandlers.start)\n this.addEventListenerWithTracking(elements.btn, \"touchstart\", eventHandlers.start)\n this.addEventListenerWithTracking(elements.sliderImg, \"mousedown\", eventHandlers.start)\n this.addEventListenerWithTracking(elements.sliderImg, \"touchstart\", eventHandlers.start)\n\n // 文档级别的移动和结束事件\n this.addEventListenerWithTracking(document, \"mousemove\", eventHandlers.move)\n this.addEventListenerWithTracking(document, \"touchmove\", eventHandlers.move)\n this.addEventListenerWithTracking(document, \"mouseup\", eventHandlers.end)\n this.addEventListenerWithTracking(document, \"touchend\", eventHandlers.end)\n }\n\n // ==================== 拖拽处理 ====================\n getPosition() {\n const { elements, options } = this\n const maxX = elements.track.offsetWidth - elements.btn.offsetWidth\n const percentage = this.state.currentX / maxX\n return Math.round(percentage * (options.width - options.sliderSize))\n }\n\n handleStart(e) {\n if (!this.captchaData || this.state.isDragging) return\n\n // 记录开始时间(如果还没有记录)\n if (!this.startTime) {\n this.startTime = Date.now()\n }\n\n // 初始化拖拽轨迹\n this.times = [{ time: Date.now(), position: this.getPosition() }]\n\n e.preventDefault()\n e.stopPropagation()\n\n this.state.isDragging = true\n const clientX = this.getClientX(e)\n this.state.startX = clientX - this.state.currentX\n\n // 移除过渡效果并更新UI状态\n this.setTransition(false)\n this.updateUIState(\"dragging\")\n\n // 添加拖拽时的视觉反馈\n document.body.style.userSelect = \"none\"\n document.body.style.cursor = \"grabbing\"\n }\n\n handleMove(e) {\n if (!this.state.isDragging) return\n\n e.preventDefault()\n const clientX = this.getClientX(e)\n const deltaX = clientX - this.state.startX\n const maxX = this.elements.track.offsetWidth - this.elements.btn.offsetWidth\n\n this.state.currentX = Math.max(0, Math.min(deltaX, maxX))\n this.times.push({ time: Date.now(), position: this.getPosition() })\n\n this.updateSliderPosition()\n }\n\n handleEnd() {\n if (!this.state.isDragging) return\n\n this.times.push({ time: Date.now(), position: this.getPosition() })\n this.state.isDragging = false\n this.verify()\n }\n\n // 辅助方法:获取客户端X坐标\n getClientX(e) {\n return e.type.includes(\"touch\") ? e.touches[0].clientX : e.clientX\n }\n\n // ==================== UI 状态更新 ====================\n setTransition(enabled) {\n const transition = enabled ? \"all 0.3s ease\" : \"none\"\n this.elements.btn.style.transition = transition\n this.elements.sliderImg.style.transition = transition\n }\n\n // 更新UI状态\n updateUIState(state) {\n const { elements } = this\n\n switch (state) {\n case \"dragging\":\n elements.hint.style.opacity = \"0\"\n elements.fingerAnimation.style.display = \"none\"\n break\n case \"success\":\n elements.btn.style.background = \"#67c23a\"\n elements.icon.innerHTML = \"✓\"\n elements.icon.style.color = \"white\"\n elements.fingerAnimation.style.display = \"none\"\n this.updateHintText(\"验证成功\", \"#67c23a\")\n break\n case \"fail\":\n elements.btn.style.background = \"#f56c6c\"\n elements.icon.innerHTML = \"✗\"\n elements.icon.style.color = \"white\"\n break\n case \"reset\":\n elements.btn.style.background = \"white\"\n elements.icon.innerHTML = \"→\"\n elements.icon.style.color = \"#666\"\n elements.fingerAnimation.style.display = \"block\"\n this.updateHintText(\"向右滑动完成验证\", \"#999\")\n break\n }\n }\n\n // 更新提示文字\n updateHintText(text, color) {\n const { elements } = this\n elements.hint.textContent = text\n elements.hint.style.color = color\n elements.hint.style.opacity = \"1\"\n\n // 根据滑块位置调整文字位置\n if (text === \"验证成功\") {\n const sliderPosition = parseInt(elements.btn.style.left) || 0\n const trackWidth = elements.track.offsetWidth\n let textLeft = \"50%\"\n\n if (sliderPosition > trackWidth * 0.6) {\n textLeft = \"25%\"\n } else if (sliderPosition > trackWidth * 0.3) {\n textLeft = \"75%\"\n }\n\n elements.hint.style.left = textLeft\n } else {\n elements.hint.style.left = \"50%\"\n }\n }\n\n // 更新滑块位置\n updateSliderPosition() {\n const { elements, options, state } = this\n const maxX = elements.track.offsetWidth - elements.btn.offsetWidth\n const pieceX = (state.currentX / maxX) * (options.width - options.sliderSize)\n\n // 计算滑动进度百分比\n const progress = state.currentX / maxX\n\n elements.btn.style.left = state.currentX + \"px\"\n elements.sliderImg.style.left = pieceX + \"px\"\n\n // 当滑动到80%时,finger元素透明度变为0\n if (progress >= 0.8) {\n elements.fingerAnimation.style.opacity = \"0\"\n } else {\n elements.fingerAnimation.style.opacity = \"0.6\"\n }\n }\n\n // ==================== 显示和隐藏 ====================\n show() {\n this.state.isVisible = true\n this.elements.overlay.style.display = \"flex\"\n\n // 强制重绘以确保初始状态生效\n this.elements.overlay.offsetHeight\n\n // 添加动画类\n requestAnimationFrame(() => {\n this.elements.overlay.classList.add(\"show\")\n this.elements.modal.classList.add(\"show\")\n })\n\n this.loadCaptcha()\n }\n\n hide() {\n this.state.isVisible = false\n\n // 移除动画类\n this.elements.overlay.classList.remove(\"show\")\n this.elements.modal.classList.remove(\"show\")\n\n // 等待动画完成后隐藏\n setTimeout(() => {\n this.elements.overlay.style.display = \"none\"\n this.reset()\n if (this.options.onClose) {\n this.options.onClose()\n }\n }, 300)\n }\n\n // ==================== 验证码加载和渲染 ====================\n async loadCaptcha() {\n try {\n this.showLoading()\n this.startTime = Date.now() // 记录开始时间\n\n const requestData = {\n place: 2,\n timestamp: Date.now()\n }\n\n const response = await this.fetchWithTimeout(this.options.apiUrl, {\n method: \"POST\",\n body: JSON.stringify(requestData),\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\"\n }\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: 获取验证码失败`)\n }\n\n const data = await response.json()\n\n if (data.data && data.data.canvasSrc && data.data.blockSrc) {\n this.captchaData = data.data\n await this.renderCaptcha()\n } else {\n throw new Error(data.message || data.msg || \"验证码数据格式错误\")\n }\n } catch (error) {\n console.error(\"加载验证码失败:\", error)\n this.showError(\"加载验证码失败: \" + error.message)\n }\n }\n\n // 带超时的fetch\n async fetchWithTimeout(url, options) {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.options.timeout)\n\n try {\n const response = await fetch(url, {\n ...options,\n signal: controller.signal\n })\n clearTimeout(timeoutId)\n return response\n } catch (error) {\n clearTimeout(timeoutId)\n throw error\n }\n }\n\n // 渲染验证码\n async renderCaptcha() {\n return new Promise((resolve, reject) => {\n let loadedCount = 0\n const totalImages = 2\n\n const onImageLoad = () => {\n loadedCount++\n if (loadedCount === totalImages) {\n this.hideLoading()\n this.showCaptcha()\n resolve()\n }\n }\n\n const onImageError = () => reject(new Error(\"图片加载失败\"))\n\n // 加载背景图片\n this.loadImage(\n this.elements.backgroundImg,\n this.captchaData.canvasSrc,\n {\n width: this.captchaData.canvasWidth,\n height: this.captchaData.canvasHeight\n },\n onImageLoad,\n onImageError\n )\n\n // 加载滑块图片\n this.loadImage(\n this.elements.sliderImg,\n this.captchaData.blockSrc,\n {\n width: this.captchaData.blockWidth,\n height: this.captchaData.blockHeight,\n top: this.captchaData.blockY\n },\n onImageLoad,\n onImageError\n )\n })\n }\n\n // 加载图片\n loadImage(imgElement, src, styles, onLoad, onError) {\n imgElement.onload = onLoad\n imgElement.onerror = onError\n imgElement.src = src\n\n // 应用样式\n Object.entries(styles).forEach(([key, value]) => {\n imgElement.style[key] = typeof value === \"number\" ? value + \"px\" : value\n })\n }\n\n // ==================== 加载状态管理 ====================\n showLoading() {\n const { elements } = this\n elements.container.style.display = \"block\"\n elements.loadingText.style.display = \"flex\"\n elements.track.style.display = \"none\"\n elements.error.style.display = \"none\"\n elements.retryBtn.style.display = \"none\"\n }\n\n // 隐藏加载状态\n hideLoading() {\n this.elements.loadingText.style.display = \"none\"\n }\n\n // 显示验证码\n showCaptcha() {\n const { elements } = this\n elements.container.style.display = \"block\"\n elements.loadingText.style.display = \"none\"\n elements.track.style.display = \"block\"\n elements.error.style.display = \"none\"\n elements.retryBtn.style.display = \"none\"\n }\n\n // 显示错误\n showError(message) {\n this.hideLoading()\n this.elements.error.textContent = message\n this.elements.error.style.display = \"block\"\n this.elements.retryBtn.style.display = \"inline-block\"\n }\n\n // ==================== 验证逻辑 ====================\n async verify() {\n if (!this.captchaData) {\n this.onVerifyFail(\"验证码数据丢失,请刷新重试\")\n return\n }\n\n try {\n const requestData = {\n loginVo: {\n nonceStr: this.captchaData.nonceStr,\n value: this.getPosition()\n },\n dragEventList: [...this.times]\n }\n\n const response = await this.fetchWithTimeout(this.options.verifyUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\"\n },\n body: JSON.stringify(requestData)\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n const data = await response.json()\n\n if (data.code === \"0\" || data.success === true) {\n this.onVerifySuccess()\n } else {\n this.onVerifyFail(data.message || data.msg || \"验证失败\")\n }\n } catch (error) {\n console.error(\"验证请求失败:\", error)\n this.onVerifyFail(\"网络错误,请检查连接后重试\")\n }\n }\n\n // 验证成功\n onVerifySuccess() {\n const endTime = Date.now()\n const duration = endTime - this.startTime\n const durationText = `${(duration / 1000).toFixed(2)}s`\n\n this.updateUIState(\"success\")\n this.showTimeDisplay(durationText) // 显示耗时\n\n setTimeout(() => {\n this.options.onSuccess({\n captchaId: this.captchaData.captchaId,\n timestamp: Date.now(),\n duration: duration\n })\n this.hide()\n }, 2000) // 延长显示时间以便用户看到耗时\n }\n\n // 添加显示耗时的方法\n showTimeDisplay(timeText) {\n const { elements } = this\n elements.timeDisplay.textContent = `验证成功!耗时:${timeText}`\n elements.timeDisplay.style.display = \"block\"\n\n // 添加淡入动画\n elements.timeDisplay.style.opacity = \"0\"\n setTimeout(() => {\n elements.timeDisplay.style.transition = \"opacity 0.3s ease\"\n elements.timeDisplay.style.opacity = \"1\"\n }, 100)\n }\n\n // 验证失败\n onVerifyFail(message) {\n this.state.retryCount++\n this.updateUIState(\"fail\")\n this.updateHintText(message, \"#f56c6c\")\n\n setTimeout(() => {\n this.reset()\n\n if (this.state.retryCount >= this.options.maxRetries) {\n this.showError(\"验证失败次数过多,请刷新重试\")\n this.options.onFail({\n reason: \"max_retries_exceeded\",\n retryCount: this.state.retryCount\n })\n } else {\n this.refresh()\n }\n }, 1500)\n }\n\n // ==================== 重置和刷新 ====================\n reset() {\n this.state.currentX = 0\n this.setTransition(true)\n this.updateUIState(\"reset\")\n this.updateSliderPosition()\n\n // 隐藏耗时显示\n this.elements.timeDisplay.style.display = \"none\"\n this.startTime = null\n }\n\n // 刷新\n refresh() {\n this.reset()\n this.captchaData = null\n this.state.retryCount = 0\n this.loadCaptcha()\n }\n\n // ==================== 销毁和清理 ====================\n destroy() {\n // 恢复页面状态\n document.body.style.userSelect = \"\"\n document.body.style.cursor = \"\"\n\n // 移除所有事件监听器\n this.removeAllEventListeners()\n\n // 移除DOM元素\n if (this.elements.overlay?.parentNode) {\n this.elements.overlay.parentNode.removeChild(this.elements.overlay)\n }\n\n // 清理所有引用\n this.elements = null\n this.captchaData = null\n this.times = null\n this.eventListeners = null\n this.state = null\n this.options = null\n this.startTime = null\n }\n\n // ==================== 静态工厂方法 ====================\n static create(options) {\n return new PopupSliderCaptcha(options)\n }\n\n static show(options) {\n const instance = new PopupSliderCaptcha(options)\n instance.show()\n return instance\n }\n}\n\n// ==================== 模块导出 ====================\n// ES Module export (for modern bundlers)\nexport default PopupSliderCaptcha\nexport { PopupSliderCaptcha }\n\n// CommonJS export (for Node.js compatibility)\nif (typeof module !== \"undefined\" && module.exports) {\n module.exports = PopupSliderCaptcha\n module.exports.default = PopupSliderCaptcha\n module.exports.PopupSliderCaptcha = PopupSliderCaptcha\n} else if (typeof define === \"function\" && define.amd) {\n define([], () => PopupSliderCaptcha)\n} else if (typeof window !== \"undefined\") {\n window.PopupSliderCaptcha = PopupSliderCaptcha\n window.SliderCaptcha = PopupSliderCaptcha\n}\n","// 导入滑块验证码组件\nimport PopupSliderCaptcha from './slider-captcha.js'\n\n// 导出主要类\nexport { PopupSliderCaptcha }\n\n// 默认导出\nexport default PopupSliderCaptcha\n\n// 全局注册(用于UMD构建)\nif (typeof window !== 'undefined') {\n window.SliderCaptcha = PopupSliderCaptcha\n window.PopupSliderCaptcha = PopupSliderCaptcha\n}\n"],"names":["PopupSliderCaptcha","static","width","height","sliderSize","maxRetries","timeout","apiUrl","verifyUrl","overlay","modal","header","container","track","btn","finger","hint","loading","error","version","info","name","this","author","license","constructor","options","DEFAULTS","elements","state","isVisible","isDragging","currentX","startX","retryCount","captchaData","times","startTime","eventListeners","init","injectStyles","createElements","bindEvents","document","querySelector","style","createElement","id","textContent","CSS_CLASSES","head","appendChild","title","closeBtn","refreshBtn","backgroundImg","sliderImg","loadingText","timeDisplay","fingerAnimation","icon","retryBtn","innerHTML","assembleDOM","setInitialState","tag","className","element","headerButtons","body","display","addEventListenerWithTracking","event","handler","addEventListener","push","removeAllEventListeners","forEach","removeEventListener","hide","refresh","e","target","key","bindSliderEvents","eventHandlers","start","handleStart","bind","move","handleMove","end","handleEnd","getPosition","maxX","offsetWidth","percentage","Math","round","Date","now","time","position","preventDefault","stopPropagation","clientX","getClientX","setTransition","updateUIState","userSelect","cursor","deltaX","max","min","updateSliderPosition","verify","type","includes","touches","enabled","transition","opacity","background","color","updateHintText","text","sliderPosition","parseInt","left","trackWidth","textLeft","pieceX","progress","show","offsetHeight","requestAnimationFrame","classList","add","loadCaptcha","remove","setTimeout","reset","onClose","showLoading","requestData","place","timestamp","response","fetchWithTimeout","method","JSON","stringify","headers","Accept","ok","Error","status","data","json","canvasSrc","blockSrc","message","msg","renderCaptcha","showError","url","controller","AbortController","timeoutId","abort","fetch","signal","clearTimeout","Promise","resolve","reject","loadedCount","onImageLoad","hideLoading","showCaptcha","onImageError","loadImage","canvasWidth","canvasHeight","blockWidth","blockHeight","top","blockY","imgElement","src","styles","onLoad","onError","onload","onerror","Object","entries","value","loginVo","nonceStr","dragEventList","statusText","code","success","onVerifySuccess","onVerifyFail","duration","durationText","toFixed","showTimeDisplay","onSuccess","captchaId","timeText","onFail","reason","destroy","parentNode","removeChild","create","instance","module","exports","default","define","amd","window","SliderCaptcha"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
(function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).SliderCaptcha={})})(this,(function(t){"use strict"
|
|
2
|
+
class PopupSliderCaptcha{static t={width:350,height:200,i:42,o:3,timeout:3e4,h:"/api/captcha",l:"/api/captcha/verify"}
|
|
3
|
+
static p={u:"slider-captcha-overlay",m:"slider-captcha-modal",v:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",k:"slider-captcha-btn",S:"slider-captcha-finger",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"}
|
|
4
|
+
static get version(){return"1.0.0"}static get info(){return{name:"Slider Captcha SDK",version:this.version,T:"Your Name",$:"MIT"}}constructor(t={}){this.options={...PopupSliderCaptcha.t,...t},this.elements={},this.state={isVisible:!1,j:!1,C:0,D:0,A:0},this.L=null,this.M=[],this.startTime=null,this.O=[],this.init()}init(){this.P(),this.I(),this._()}P(){if(document.querySelector("#slider-captcha-styles"))return
|
|
5
|
+
const t=document.createElement("style")
|
|
6
|
+
t.id="slider-captcha-styles",t.textContent=`\n .${PopupSliderCaptcha.p.u} {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.p.u}.show {\n opacity: 1;\n }\n\n .${PopupSliderCaptcha.p.m} {\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n position: relative;\n max-width: 90vw;\n max-height: 90vh;\n transform: scale(0.8) translateY(-20px);\n opacity: 0;\n transition: all 0.3s ease-in-out;\n }\n\n .${PopupSliderCaptcha.p.m}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n\n .${PopupSliderCaptcha.p.v} {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid #eee;\n }\n\n .${PopupSliderCaptcha.p.container} {\n display: flex;\n align-items: center;\n position: relative;\n width: ${this.options.width}px;\n height: ${this.options.height}px;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 15px;\n background: #837a7a;\n justify-content: center;\n }\n\n .${PopupSliderCaptcha.p.track} {\n width: 100%;\n height: 40px;\n background: #f7f9fa;\n border: 1px solid #e4e7eb;\n border-radius: 20px;\n position: relative;\n margin-bottom: 15px;\n overflow: hidden;\n }\n\n .${PopupSliderCaptcha.p.k} {\n width: 38px;\n height: 38px;\n background: white;\n border: 1px solid #ccc;\n border-radius: 50%;\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n user-select: none;\n z-index: 1\n }\n\n .${PopupSliderCaptcha.p.S} {\n position: absolute;\n top: 50%;\n left: 10px;\n transform: translateY(-50%);\n font-size: 20px;\n animation: fingerSlide 2s ease-in-out infinite;\n pointer-events: none;\n z-index: 1;\n opacity: 0.6;\n }\n\n .${PopupSliderCaptcha.p.hint} {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #999;\n font-size: 14px;\n pointer-events: none;\n z-index: 1;\n transition: all 0.3s ease;\n }\n\n .${PopupSliderCaptcha.p.loading} {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(255, 255, 255, 0.9);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n color: #666;\n font-size: 14px;\n z-index: 10;\n border-radius: 4px;\n }\n\n .loading-dots {\n display: inline-block;\n margin-left: 4px;\n }\n\n .loading-dots .dot {\n display: inline-block;\n animation: loading-dot 1.8s infinite both;\n font-size: 25px;\n font-weight: bold;\n }\n\n .loading-dots .dot:nth-child(1) { animation-delay: 0s; }\n .loading-dots .dot:nth-child(2) { animation-delay: 0.6s; }\n .loading-dots .dot:nth-child(3) { animation-delay: 1.2s; }\n\n @keyframes loading-dot {\n 0%, 80%, 100% {\n opacity: 0.3;\n transform: scale(0.8);\n }\n 40% {\n opacity: 1;\n transform: scale(1.2);\n }\n }\n\n @keyframes fingerSlide {\n 0% {\n left: 10px;\n opacity: 0.6;\n }\n 50% {\n opacity: 1;\n }\n 100% {\n left: calc(50% - 10px);\n opacity: 0.6;\n }\n }\n\n .slider-captcha-bg {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n }\n\n .slider-captcha-piece {\n position: absolute;\n top: 0;\n left: 0;\n cursor: pointer;\n transition: none;\n z-index: 2;\n }\n\n .${PopupSliderCaptcha.p.error} {\n color: #f56c6c;\n font-size: 12px;\n text-align: center;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-retry {\n background: #409eff;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n margin-top: 10px;\n display: none;\n }\n\n .slider-captcha-title {\n margin: 0;\n font-size: 16px;\n color: #333;\n }\n\n .slider-captcha-close {\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .slider-captcha-refresh {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n padding: 0;\n width: 30px;\n height: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n border-radius: 4px;\n transition: all 0.3s ease;\n }\n\n .slider-captcha-refresh:hover {\n background: #f5f5f5;\n color: #409eff;\n }\n\n .slider-captcha-time {\n position: relative;\n top: 0;\n left: 0;\n transform: none;\n color: #67c23a;\n font-size: 14px;\n font-weight: bold;\n display: none;\n background: rgba(255, 255, 255, 0.9);\n padding: 8px 12px;\n border-radius: 4px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n text-align: center;\n margin-top: 10px;\n width: 100%;\n box-sizing: border-box;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n\n /* 移除重复的样式定义 */\n .${PopupSliderCaptcha.p.m}.show {\n transform: scale(1) translateY(0);\n opacity: 1;\n }\n\n .slider-captcha-header-buttons {\n display: flex;\n align-items: center;\n }\n `,document.head.appendChild(t)}I(){const{elements:t}=this,{p:n}=PopupSliderCaptcha
|
|
7
|
+
t.u=this.createElement("div",n.u),t.m=this.createElement("div",n.m),t.v=this.createElement("div",n.v),t.title=this.createElement("h3","slider-captcha-title","滑动验证"),t.Y=this.createElement("button","slider-captcha-close","×"),t.U=this.createElement("button","slider-captcha-refresh","⟳"),t.container=this.createElement("div",n.container),t.V=this.createElement("img","slider-captcha-bg"),t.F=this.createElement("img","slider-captcha-piece"),t.H=this.createElement("div",n.loading),t.N=this.createElement("div","slider-captcha-time"),t.track=this.createElement("div",n.track),t.X=this.createElement("div",n.S,"👉"),t.k=this.createElement("div",n.k),t.icon=this.createElement("div","","→"),t.hint=this.createElement("div",n.hint,"向右滑动完成验证"),t.error=this.createElement("div",n.error),t.J=this.createElement("button","slider-captcha-retry","重新获取"),t.H.innerHTML='\n <span class="loading-dots">\n <span class="dot">.</span>\n <span class="dot">.</span>\n <span class="dot">.</span>\n </span>\n ',this.W(),this.q()}createElement(t,n="",i=""){const e=document.createElement(t)
|
|
8
|
+
return n&&(e.className=n),i&&(e.textContent=i),e}W(){const{elements:t}=this
|
|
9
|
+
t.k.appendChild(t.icon),t.track.appendChild(t.X),t.track.appendChild(t.k),t.track.appendChild(t.hint)
|
|
10
|
+
const n=this.createElement("div","slider-captcha-header-buttons")
|
|
11
|
+
n.appendChild(t.U),n.appendChild(t.Y),t.v.appendChild(t.title),t.v.appendChild(n),t.container.appendChild(t.V),t.container.appendChild(t.F),t.container.appendChild(t.H),t.m.appendChild(t.v),t.m.appendChild(t.container),t.m.appendChild(t.track),t.m.appendChild(t.N),t.m.appendChild(t.error),t.m.appendChild(t.J),t.u.appendChild(t.m),document.body.appendChild(t.u)}q(){const{elements:t}=this
|
|
12
|
+
t.container.style.display="none",t.track.style.display="none"}K(t,n,i){t&&(t.addEventListener(n,i),this.O.push({element:t,event:n,R:i}))}B(){this.O.forEach((({element:t,event:n,R:i})=>{t&&t.removeEventListener&&t.removeEventListener(n,i)})),this.O=[]}_(){const{elements:t}=this
|
|
13
|
+
this.K(t.Y,"click",(()=>this.hide())),this.K(t.U,"click",(()=>this.refresh())),this.K(t.J,"click",(()=>this.refresh())),this.K(t.u,"click",(n=>{n.target===t.u&&this.hide()})),this.K(document,"keydown",(t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()})),this.G()}G(){const{elements:t}=this,n={start:this.Z.bind(this),move:this.tt.bind(this),end:this.nt.bind(this)}
|
|
14
|
+
this.K(t.k,"mousedown",n.start),this.K(t.k,"touchstart",n.start),this.K(t.F,"mousedown",n.start),this.K(t.F,"touchstart",n.start),this.K(document,"mousemove",n.move),this.K(document,"touchmove",n.move),this.K(document,"mouseup",n.end),this.K(document,"touchend",n.end)}it(){const{elements:t,options:n}=this,i=t.track.offsetWidth-t.k.offsetWidth,e=this.state.C/i
|
|
15
|
+
return Math.round(e*(n.width-n.i))}Z(t){if(!this.L||this.state.j)return
|
|
16
|
+
this.startTime||(this.startTime=Date.now()),this.M=[{time:Date.now(),position:this.it()}],t.preventDefault(),t.stopPropagation(),this.state.j=!0
|
|
17
|
+
const n=this.et(t)
|
|
18
|
+
this.state.D=n-this.state.C,this.st(!1),this.ot("dragging"),document.body.style.userSelect="none",document.body.style.cursor="grabbing"}tt(t){if(!this.state.j)return
|
|
19
|
+
t.preventDefault()
|
|
20
|
+
const n=this.et(t)-this.state.D,i=this.elements.track.offsetWidth-this.elements.k.offsetWidth
|
|
21
|
+
this.state.C=Math.max(0,Math.min(n,i)),this.M.push({time:Date.now(),position:this.it()}),this.ht()}nt(){this.state.j&&(this.M.push({time:Date.now(),position:this.it()}),this.state.j=!1,this.verify())}et(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}st(t){const n=t?"all 0.3s ease":"none"
|
|
22
|
+
this.elements.k.style.transition=n,this.elements.F.style.transition=n}ot(t){const{elements:n}=this
|
|
23
|
+
switch(t){case"dragging":n.hint.style.opacity="0",n.X.style.display="none"
|
|
24
|
+
break
|
|
25
|
+
case"success":n.k.style.background="#67c23a",n.icon.innerHTML="✓",n.icon.style.color="white",n.X.style.display="none",this.rt("验证成功","#67c23a")
|
|
26
|
+
break
|
|
27
|
+
case"fail":n.k.style.background="#f56c6c",n.icon.innerHTML="✗",n.icon.style.color="white"
|
|
28
|
+
break
|
|
29
|
+
case"reset":n.k.style.background="white",n.icon.innerHTML="→",n.icon.style.color="#666",n.X.style.display="block",this.rt("向右滑动完成验证","#999")}}rt(t,n){const{elements:i}=this
|
|
30
|
+
if(i.hint.textContent=t,i.hint.style.color=n,i.hint.style.opacity="1","验证成功"===t){const t=parseInt(i.k.style.left)||0,n=i.track.offsetWidth
|
|
31
|
+
let e="50%"
|
|
32
|
+
t>.6*n?e="25%":t>.3*n&&(e="75%"),i.hint.style.left=e}else i.hint.style.left="50%"}ht(){const{elements:t,options:n,state:i}=this,e=t.track.offsetWidth-t.k.offsetWidth,s=i.C/e*(n.width-n.i),o=i.C/e
|
|
33
|
+
t.k.style.left=i.C+"px",t.F.style.left=s+"px",t.X.style.opacity=.8>o?"0.6":"0"}show(){this.state.isVisible=!0,this.elements.u.style.display="flex",this.elements.u.offsetHeight,requestAnimationFrame((()=>{this.elements.u.classList.add("show"),this.elements.m.classList.add("show")})),this.ct()}hide(){this.state.isVisible=!1,this.elements.u.classList.remove("show"),this.elements.m.classList.remove("show"),setTimeout((()=>{this.elements.u.style.display="none",this.reset(),this.options.lt&&this.options.lt()}),300)}async ct(){try{this.dt(),this.startTime=Date.now()
|
|
34
|
+
const t={ut:2,timestamp:Date.now()},n=await this.ft(this.options.h,{method:"POST",body:JSON.stringify(t),headers:{gt:"application/json",bt:"application/json"}})
|
|
35
|
+
if(!n.ok)throw Error(`HTTP ${n.status}: 获取验证码失败`)
|
|
36
|
+
const i=await n.json()
|
|
37
|
+
if(!(i.data&&i.data.xt&&i.data.yt))throw Error(i.message||i.wt||"验证码数据格式错误")
|
|
38
|
+
this.L=i.data,await this.vt()}catch(t){this.kt("加载验证码失败: "+t.message)}}async ft(t,n){const i=new AbortController,e=setTimeout((()=>i.abort()),this.options.timeout)
|
|
39
|
+
try{const s=await fetch(t,{...n,signal:i.signal})
|
|
40
|
+
return clearTimeout(e),s}catch(t){throw clearTimeout(e),t}}async vt(){return new Promise(((t,n)=>{let i=0
|
|
41
|
+
const e=()=>{i++,2===i&&(this.St(),this.Tt(),t())},s=()=>n(Error("图片加载失败"))
|
|
42
|
+
this.zt(this.elements.V,this.L.xt,{width:this.L.$t,height:this.L.Et},e,s),this.zt(this.elements.F,this.L.yt,{width:this.L.jt,height:this.L.Ct,top:this.L.Dt},e,s)}))}zt(t,n,i,e,s){t.onload=e,t.onerror=s,t.src=n,Object.entries(i).forEach((([n,i])=>{t.style[n]="number"==typeof i?i+"px":i}))}dt(){const{elements:t}=this
|
|
43
|
+
t.container.style.display="block",t.H.style.display="flex",t.track.style.display="none",t.error.style.display="none",t.J.style.display="none"}St(){this.elements.H.style.display="none"}Tt(){const{elements:t}=this
|
|
44
|
+
t.container.style.display="block",t.H.style.display="none",t.track.style.display="block",t.error.style.display="none",t.J.style.display="none"}kt(t){this.St(),this.elements.error.textContent=t,this.elements.error.style.display="block",this.elements.J.style.display="inline-block"}async verify(){if(this.L)try{const t={At:{Lt:this.L.Lt,value:this.it()},Mt:[...this.M]},n=await this.ft(this.options.l,{method:"POST",headers:{gt:"application/json",bt:"application/json"},body:JSON.stringify(t)})
|
|
45
|
+
if(!n.ok)throw Error(`HTTP ${n.status}: ${n.statusText}`)
|
|
46
|
+
const i=await n.json()
|
|
47
|
+
"0"===i.code||!0===i.Ot?this.Pt():this.It(i.message||i.wt||"验证失败")}catch(t){this.It("网络错误,请检查连接后重试")}else this.It("验证码数据丢失,请刷新重试")}Pt(){const t=Date.now()-this.startTime,n=(t/1e3).toFixed(2)+"s"
|
|
48
|
+
this.ot("success"),this._t(n),setTimeout((()=>{this.options.Yt({Ut:this.L.Ut,timestamp:Date.now(),duration:t}),this.hide()}),2e3)}_t(t){const{elements:n}=this
|
|
49
|
+
n.N.textContent="验证成功!耗时:"+t,n.N.style.display="block",n.N.style.opacity="0",setTimeout((()=>{n.N.style.transition="opacity 0.3s ease",n.N.style.opacity="1"}),100)}It(t){this.state.A++,this.ot("fail"),this.rt(t,"#f56c6c"),setTimeout((()=>{this.reset(),this.state.A<this.options.o?this.refresh():(this.kt("验证失败次数过多,请刷新重试"),this.options.Vt({reason:"max_retries_exceeded",A:this.state.A}))}),1500)}reset(){this.state.C=0,this.st(!0),this.ot("reset"),this.ht(),this.elements.N.style.display="none",this.startTime=null}refresh(){this.reset(),this.L=null,this.state.A=0,this.ct()}destroy(){document.body.style.userSelect="",document.body.style.cursor="",this.B(),this.elements.u?.parentNode&&this.elements.u.parentNode.removeChild(this.elements.u),this.elements=null,this.L=null,this.M=null,this.O=null,this.state=null,this.options=null,this.startTime=null}static create(t){return new PopupSliderCaptcha(t)}static show(t){const n=new PopupSliderCaptcha(t)
|
|
50
|
+
return n.show(),n}}"undefined"!=typeof module&&module.exports?(module.exports=PopupSliderCaptcha,module.exports.default=PopupSliderCaptcha,module.exports.PopupSliderCaptcha=PopupSliderCaptcha):"function"==typeof define&&define.amd?define([],(()=>PopupSliderCaptcha)):"undefined"!=typeof window&&(window.PopupSliderCaptcha=PopupSliderCaptcha,window.SliderCaptcha=PopupSliderCaptcha),"undefined"!=typeof window&&(window.SliderCaptcha=PopupSliderCaptcha,window.PopupSliderCaptcha=PopupSliderCaptcha),t.PopupSliderCaptcha=PopupSliderCaptcha,t.default=PopupSliderCaptcha,Object.defineProperty(t,"Ft",{value:!0})}))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slider-captcha-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "纯JavaScript滑块验证码SDK,无依赖,支持多种模块格式",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/slider-captcha.cjs.js",
|
|
@@ -43,8 +43,7 @@
|
|
|
43
43
|
],
|
|
44
44
|
"author": {
|
|
45
45
|
"name": "wql",
|
|
46
|
-
"email": "weiqinlin627@gmail.com"
|
|
47
|
-
"url": "https://github.com/yourusername"
|
|
46
|
+
"email": "weiqinlin627@gmail.com"
|
|
48
47
|
},
|
|
49
48
|
"license": "MIT",
|
|
50
49
|
"engines": {
|
|
@@ -58,7 +57,7 @@
|
|
|
58
57
|
},
|
|
59
58
|
"repository": {
|
|
60
59
|
"type": "git",
|
|
61
|
-
"url": "
|
|
60
|
+
"url": "http://106.55.220.25:8080/weiqinlin/slider-captcha-sdk.git"
|
|
62
61
|
},
|
|
63
62
|
"publishConfig": {
|
|
64
63
|
"registry": "https://registry.npmjs.org/"
|