slider-captcha-sdk 1.0.18 → 1.0.20
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/dist/index.esm.js +183 -218
- package/dist/index.min.js +1 -1
- package/dist/password-validator.d.ts +13 -17
- package/dist/password-validator.esm.js +22 -73
- package/dist/password-validator.min.js +1 -1
- package/dist/slider-captcha.esm.js +161 -145
- package/dist/slider-captcha.min.js +1 -1
- package/package.json +12 -9
|
@@ -8,22 +8,22 @@ class PopupSliderCaptcha {
|
|
|
8
8
|
sliderSize: 38,
|
|
9
9
|
maxRetries: 3,
|
|
10
10
|
timeout: 30000,
|
|
11
|
-
apiUrl:
|
|
12
|
-
verifyUrl:
|
|
11
|
+
apiUrl: '/api/captcha',
|
|
12
|
+
verifyUrl: '/api/captcha/verify',
|
|
13
13
|
throttleDelay: 16,
|
|
14
14
|
clickMaskClose: false
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
static CSS_CLASSES = {
|
|
18
|
-
overlay:
|
|
19
|
-
modal:
|
|
20
|
-
header:
|
|
21
|
-
container:
|
|
22
|
-
track:
|
|
23
|
-
btn:
|
|
24
|
-
hint:
|
|
25
|
-
loading:
|
|
26
|
-
error:
|
|
18
|
+
overlay: 'slider-captcha-overlay',
|
|
19
|
+
modal: 'slider-captcha-modal',
|
|
20
|
+
header: 'slider-captcha-header',
|
|
21
|
+
container: 'slider-captcha-container',
|
|
22
|
+
track: 'slider-captcha-track',
|
|
23
|
+
btn: 'slider-captcha-btn',
|
|
24
|
+
hint: 'slider-captcha-hint',
|
|
25
|
+
loading: 'slider-captcha-loading',
|
|
26
|
+
error: 'slider-captcha-error'
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// 优化:提取CSS样式为独立方法,减少主体代码长度
|
|
@@ -40,7 +40,6 @@ class PopupSliderCaptcha {
|
|
|
40
40
|
THROTTLE_DELAY: 16
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// 优化:添加错误类型枚举
|
|
44
43
|
static ERROR_TYPES = {
|
|
45
44
|
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
46
45
|
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
|
|
@@ -50,7 +49,6 @@ class PopupSliderCaptcha {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
constructor(options = {}) {
|
|
53
|
-
// 优化:合并默认配置,使用常量
|
|
54
52
|
this.options = {
|
|
55
53
|
...PopupSliderCaptcha.DEFAULTS,
|
|
56
54
|
...options,
|
|
@@ -59,7 +57,6 @@ class PopupSliderCaptcha {
|
|
|
59
57
|
throttleDelay: options.throttleDelay || PopupSliderCaptcha.CONSTANTS.THROTTLE_DELAY
|
|
60
58
|
};
|
|
61
59
|
|
|
62
|
-
// 优化:初始化所有属性
|
|
63
60
|
this.elements = {};
|
|
64
61
|
this.state = this.createInitialState();
|
|
65
62
|
this.captchaData = null;
|
|
@@ -72,10 +69,8 @@ class PopupSliderCaptcha {
|
|
|
72
69
|
this.imageCache = new Map();
|
|
73
70
|
this.abortController = null;
|
|
74
71
|
|
|
75
|
-
// 优化:使用箭头函数绑定this,避免重复bind调用
|
|
76
72
|
this.throttledHandleMove = this.throttle((e) => this.handleMove(e), this.options.throttleDelay);
|
|
77
73
|
|
|
78
|
-
// 优化:添加错误处理
|
|
79
74
|
try {
|
|
80
75
|
this.init();
|
|
81
76
|
} catch (error) {
|
|
@@ -84,7 +79,6 @@ class PopupSliderCaptcha {
|
|
|
84
79
|
}
|
|
85
80
|
}
|
|
86
81
|
|
|
87
|
-
// 优化:提取初始状态创建为独立方法
|
|
88
82
|
createInitialState() {
|
|
89
83
|
return {
|
|
90
84
|
isVisible: false,
|
|
@@ -103,15 +97,14 @@ class PopupSliderCaptcha {
|
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
injectStyles() {
|
|
106
|
-
if (document.querySelector(
|
|
100
|
+
if (document.querySelector('#slider-captcha-styles')) { return }
|
|
107
101
|
|
|
108
|
-
const style = document.createElement(
|
|
109
|
-
style.id =
|
|
102
|
+
const style = document.createElement('style');
|
|
103
|
+
style.id = 'slider-captcha-styles';
|
|
110
104
|
style.textContent = PopupSliderCaptcha.getStyles();
|
|
111
105
|
document.head.appendChild(style);
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
// 优化:简化元素创建逻辑
|
|
115
108
|
createElements() {
|
|
116
109
|
const { elements, options } = this;
|
|
117
110
|
|
|
@@ -151,19 +144,18 @@ class PopupSliderCaptcha {
|
|
|
151
144
|
this.setInitialState();
|
|
152
145
|
}
|
|
153
146
|
|
|
154
|
-
createElement(tag, className =
|
|
147
|
+
createElement(tag, className = '', textContent = '') {
|
|
155
148
|
const element = document.createElement(tag);
|
|
156
|
-
if (className) element.className = className;
|
|
157
|
-
if (textContent) element.textContent = textContent;
|
|
149
|
+
if (className) { element.className = className; }
|
|
150
|
+
if (textContent) { element.textContent = textContent; }
|
|
158
151
|
return element
|
|
159
152
|
}
|
|
160
153
|
|
|
161
|
-
// 优化:简化DOM组装逻辑
|
|
162
154
|
assembleDOM() {
|
|
163
155
|
const { elements } = this;
|
|
164
156
|
|
|
165
157
|
// 组装头部
|
|
166
|
-
const headerButtons = this.createElement(
|
|
158
|
+
const headerButtons = this.createElement('div', 'slider-captcha-header-buttons');
|
|
167
159
|
headerButtons.append(elements.refreshBtn, elements.closeBtn);
|
|
168
160
|
elements.header.append(elements.title, headerButtons);
|
|
169
161
|
|
|
@@ -177,19 +169,10 @@ class PopupSliderCaptcha {
|
|
|
177
169
|
|
|
178
170
|
// 组装滑块轨道
|
|
179
171
|
elements.btn.appendChild(elements.icon);
|
|
180
|
-
elements.track.append(
|
|
181
|
-
elements.fingerAnimation,
|
|
182
|
-
elements.btn,
|
|
183
|
-
elements.hint
|
|
184
|
-
);
|
|
172
|
+
elements.track.append(elements.fingerAnimation, elements.btn, elements.hint);
|
|
185
173
|
|
|
186
174
|
// 组装模态框
|
|
187
|
-
elements.modal.append(
|
|
188
|
-
elements.header,
|
|
189
|
-
elements.container,
|
|
190
|
-
elements.track,
|
|
191
|
-
elements.error
|
|
192
|
-
);
|
|
175
|
+
elements.modal.append(elements.header, elements.container, elements.track, elements.error);
|
|
193
176
|
|
|
194
177
|
// 组装到覆盖层
|
|
195
178
|
elements.overlay.appendChild(elements.modal);
|
|
@@ -198,24 +181,23 @@ class PopupSliderCaptcha {
|
|
|
198
181
|
|
|
199
182
|
setInitialState() {
|
|
200
183
|
// 批量设置初始状态
|
|
201
|
-
Object.assign(this.elements.container.style, { display:
|
|
202
|
-
Object.assign(this.elements.track.style, { display:
|
|
184
|
+
Object.assign(this.elements.container.style, { display: 'none' });
|
|
185
|
+
Object.assign(this.elements.track.style, { display: 'none' });
|
|
203
186
|
}
|
|
204
187
|
|
|
205
|
-
// 优化:简化事件绑定
|
|
206
188
|
bindEvents() {
|
|
207
189
|
const { elements } = this;
|
|
208
190
|
|
|
209
191
|
// 基础事件
|
|
210
|
-
this.addEventListener(elements.closeBtn,
|
|
211
|
-
this.addEventListener(elements.refreshBtn,
|
|
212
|
-
this.addEventListener(elements.overlay,
|
|
213
|
-
if (e.target === elements.overlay && this.options.clickMaskClose) this.hide();
|
|
192
|
+
this.addEventListener(elements.closeBtn, 'click', () => this.hide());
|
|
193
|
+
this.addEventListener(elements.refreshBtn, 'click', () => this.refresh());
|
|
194
|
+
this.addEventListener(elements.overlay, 'click', (e) => {
|
|
195
|
+
if (e.target === elements.overlay && this.options.clickMaskClose) { this.hide(); }
|
|
214
196
|
});
|
|
215
|
-
this.addEventListener(document,
|
|
216
|
-
if (e.key ===
|
|
197
|
+
this.addEventListener(document, 'keydown', (e) => {
|
|
198
|
+
if (e.key === 'Escape' && this.state.isVisible) { this.hide(); }
|
|
217
199
|
});
|
|
218
|
-
this.addEventListener(document,
|
|
200
|
+
this.addEventListener(document, 'visibilitychange', () => this.handleVisibilityChange());
|
|
219
201
|
|
|
220
202
|
this.bindSliderEvents();
|
|
221
203
|
}
|
|
@@ -229,19 +211,18 @@ class PopupSliderCaptcha {
|
|
|
229
211
|
};
|
|
230
212
|
|
|
231
213
|
// 滑块事件
|
|
232
|
-
this.addEventListener(elements.btn,
|
|
233
|
-
this.addEventListener(elements.btn,
|
|
234
|
-
this.addEventListener(elements.sliderImg,
|
|
235
|
-
this.addEventListener(elements.sliderImg,
|
|
236
|
-
this.addEventListener(document,
|
|
237
|
-
this.addEventListener(document,
|
|
238
|
-
this.addEventListener(document,
|
|
239
|
-
this.addEventListener(document,
|
|
214
|
+
this.addEventListener(elements.btn, 'mousedown', handlers.start);
|
|
215
|
+
this.addEventListener(elements.btn, 'touchstart', handlers.start);
|
|
216
|
+
this.addEventListener(elements.sliderImg, 'mousedown', handlers.start);
|
|
217
|
+
this.addEventListener(elements.sliderImg, 'touchstart', handlers.start);
|
|
218
|
+
this.addEventListener(document, 'mousemove', handlers.move, { passive: false });
|
|
219
|
+
this.addEventListener(document, 'touchmove', handlers.move, { passive: false });
|
|
220
|
+
this.addEventListener(document, 'mouseup', handlers.end);
|
|
221
|
+
this.addEventListener(document, 'touchend', handlers.end);
|
|
240
222
|
}
|
|
241
223
|
|
|
242
|
-
// 优化:改进事件管理
|
|
243
224
|
addEventListener(element, event, handler, options = {}) {
|
|
244
|
-
if (!element || typeof handler !== 'function') return
|
|
225
|
+
if (!element || typeof handler !== 'function') { return }
|
|
245
226
|
|
|
246
227
|
element.addEventListener(event, handler, options);
|
|
247
228
|
this.eventListeners.push({ element, event, handler, options });
|
|
@@ -258,7 +239,6 @@ class PopupSliderCaptcha {
|
|
|
258
239
|
this.eventListeners.length = 0;
|
|
259
240
|
}
|
|
260
241
|
|
|
261
|
-
// 优化:缓存尺寸计算
|
|
262
242
|
getDimensions() {
|
|
263
243
|
if (!this.cachedDimensions) {
|
|
264
244
|
const trackWidth = this.elements.track.offsetWidth;
|
|
@@ -278,9 +258,8 @@ class PopupSliderCaptcha {
|
|
|
278
258
|
return Math.round(percentage * (this.options.width - this.options.sliderSize))
|
|
279
259
|
}
|
|
280
260
|
|
|
281
|
-
// 优化:简化拖拽处理
|
|
282
261
|
handleStart(e) {
|
|
283
|
-
if (!this.captchaData || this.state.isDragging || this.state.isLoading) return
|
|
262
|
+
if (!this.captchaData || this.state.isDragging || this.state.isLoading) { return }
|
|
284
263
|
|
|
285
264
|
e.preventDefault();
|
|
286
265
|
this.state.isDragging = true;
|
|
@@ -289,12 +268,12 @@ class PopupSliderCaptcha {
|
|
|
289
268
|
this.times = [{ time: Date.now(), position: this.getPosition() }];
|
|
290
269
|
|
|
291
270
|
this.setTransition(false);
|
|
292
|
-
this.updateUIState(
|
|
271
|
+
this.updateUIState('dragging');
|
|
293
272
|
this.cachedDimensions = null; // 清除缓存
|
|
294
273
|
}
|
|
295
274
|
|
|
296
275
|
handleMove(e) {
|
|
297
|
-
if (!this.state.isDragging) return
|
|
276
|
+
if (!this.state.isDragging) { return }
|
|
298
277
|
e.preventDefault();
|
|
299
278
|
|
|
300
279
|
const clientX = this.getClientX(e);
|
|
@@ -305,12 +284,14 @@ class PopupSliderCaptcha {
|
|
|
305
284
|
this.times.push({ time: Date.now(), position: this.getPosition() });
|
|
306
285
|
|
|
307
286
|
// 优化:使用RAF批量更新
|
|
308
|
-
|
|
287
|
+
if (this.rafId) {
|
|
288
|
+
cancelAnimationFrame(this.rafId);
|
|
289
|
+
}
|
|
309
290
|
this.rafId = requestAnimationFrame(() => this.updateSliderPosition());
|
|
310
291
|
}
|
|
311
292
|
|
|
312
293
|
handleEnd() {
|
|
313
|
-
if (!this.state.isDragging) return
|
|
294
|
+
if (!this.state.isDragging) { return }
|
|
314
295
|
|
|
315
296
|
this.times.push({ time: Date.now(), position: this.getPosition() });
|
|
316
297
|
this.state.isDragging = false;
|
|
@@ -331,11 +312,11 @@ class PopupSliderCaptcha {
|
|
|
331
312
|
}
|
|
332
313
|
|
|
333
314
|
getClientX(e) {
|
|
334
|
-
return e.type.includes(
|
|
315
|
+
return e.type.includes('touch') ? e.touches[0].clientX : e.clientX
|
|
335
316
|
}
|
|
336
317
|
|
|
337
318
|
setTransition(enabled) {
|
|
338
|
-
const transition = enabled ?
|
|
319
|
+
const transition = enabled ? 'all 0.3s ease' : 'none';
|
|
339
320
|
requestAnimationFrame(() => {
|
|
340
321
|
this.elements.btn.style.transition = transition;
|
|
341
322
|
this.elements.sliderImg.style.transition = transition;
|
|
@@ -347,38 +328,38 @@ class PopupSliderCaptcha {
|
|
|
347
328
|
const { elements } = this;
|
|
348
329
|
const updates = {
|
|
349
330
|
dragging: () => {
|
|
350
|
-
elements.hint.style.opacity =
|
|
351
|
-
elements.fingerAnimation.style.display =
|
|
331
|
+
elements.hint.style.opacity = '0';
|
|
332
|
+
elements.fingerAnimation.style.display = 'none';
|
|
352
333
|
},
|
|
353
334
|
success: () => {
|
|
354
|
-
Object.assign(elements.btn.style, { background:
|
|
355
|
-
Object.assign(elements.icon.style, { innerHTML:
|
|
356
|
-
elements.icon.innerHTML =
|
|
335
|
+
Object.assign(elements.btn.style, { background: 'var(--sc-success)' });
|
|
336
|
+
Object.assign(elements.icon.style, { innerHTML: '✓', color: 'white' });
|
|
337
|
+
elements.icon.innerHTML = '✓';
|
|
357
338
|
},
|
|
358
339
|
fail: () => {
|
|
359
|
-
Object.assign(elements.btn.style, { background:
|
|
360
|
-
Object.assign(elements.icon.style, { innerHTML:
|
|
361
|
-
elements.icon.innerHTML =
|
|
340
|
+
Object.assign(elements.btn.style, { background: 'var(--sc-danger)' });
|
|
341
|
+
Object.assign(elements.icon.style, { innerHTML: '✗', color: 'white' });
|
|
342
|
+
elements.icon.innerHTML = '✗';
|
|
362
343
|
},
|
|
363
344
|
reset: () => {
|
|
364
|
-
Object.assign(elements.btn.style, { background:
|
|
365
|
-
Object.assign(elements.icon.style, { color:
|
|
366
|
-
elements.icon.innerHTML =
|
|
367
|
-
elements.fingerAnimation.style.display =
|
|
368
|
-
this.updateHintText(
|
|
345
|
+
Object.assign(elements.btn.style, { background: 'white' });
|
|
346
|
+
Object.assign(elements.icon.style, { color: '#666' });
|
|
347
|
+
elements.icon.innerHTML = '→';
|
|
348
|
+
elements.fingerAnimation.style.display = 'block';
|
|
349
|
+
this.updateHintText('向右滑动完成验证', 'var(--sc-text-light)');
|
|
369
350
|
},
|
|
370
351
|
loading: () => {
|
|
371
|
-
elements.hint.style.opacity =
|
|
372
|
-
elements.fingerAnimation.style.display =
|
|
373
|
-
Object.assign(elements.track.style, { pointerEvents:
|
|
352
|
+
elements.hint.style.opacity = '0';
|
|
353
|
+
elements.fingerAnimation.style.display = 'none';
|
|
354
|
+
Object.assign(elements.track.style, { pointerEvents: 'none', opacity: '0.6' });
|
|
374
355
|
}
|
|
375
356
|
};
|
|
376
357
|
|
|
377
358
|
if (updates[state]) {
|
|
378
359
|
requestAnimationFrame(() => {
|
|
379
360
|
updates[state]();
|
|
380
|
-
if (state !==
|
|
381
|
-
Object.assign(elements.track.style, { pointerEvents:
|
|
361
|
+
if (state !== 'loading') {
|
|
362
|
+
Object.assign(elements.track.style, { pointerEvents: 'auto', opacity: '1' });
|
|
382
363
|
}
|
|
383
364
|
});
|
|
384
365
|
}
|
|
@@ -387,7 +368,7 @@ class PopupSliderCaptcha {
|
|
|
387
368
|
updateHintText(text, color) {
|
|
388
369
|
requestAnimationFrame(() => {
|
|
389
370
|
Object.assign(this.elements.hint, { textContent: text });
|
|
390
|
-
Object.assign(this.elements.hint.style, { color, opacity:
|
|
371
|
+
Object.assign(this.elements.hint.style, { color, opacity: '1' });
|
|
391
372
|
});
|
|
392
373
|
}
|
|
393
374
|
|
|
@@ -400,18 +381,18 @@ class PopupSliderCaptcha {
|
|
|
400
381
|
requestAnimationFrame(() => {
|
|
401
382
|
elements.btn.style.transform = `translateX(${state.currentX}px)`;
|
|
402
383
|
elements.sliderImg.style.transform = `translateX(${pieceX}px)`;
|
|
403
|
-
elements.fingerAnimation.style.opacity = progress >= 0.8 ?
|
|
384
|
+
elements.fingerAnimation.style.opacity = progress >= 0.8 ? '0' : '0.6';
|
|
404
385
|
});
|
|
405
386
|
}
|
|
406
387
|
|
|
407
388
|
// 优化:简化显示/隐藏逻辑
|
|
408
389
|
show() {
|
|
409
390
|
this.state.isVisible = true;
|
|
410
|
-
this.elements.overlay.style.display =
|
|
391
|
+
this.elements.overlay.style.display = 'flex';
|
|
411
392
|
|
|
412
393
|
requestAnimationFrame(() => {
|
|
413
|
-
this.elements.overlay.classList.add(
|
|
414
|
-
this.elements.modal.classList.add(
|
|
394
|
+
this.elements.overlay.classList.add('show');
|
|
395
|
+
this.elements.modal.classList.add('show');
|
|
415
396
|
});
|
|
416
397
|
|
|
417
398
|
this.loadCaptcha();
|
|
@@ -419,11 +400,11 @@ class PopupSliderCaptcha {
|
|
|
419
400
|
|
|
420
401
|
hide() {
|
|
421
402
|
this.state.isVisible = false;
|
|
422
|
-
this.elements.overlay.classList.remove(
|
|
423
|
-
this.elements.modal.classList.remove(
|
|
403
|
+
this.elements.overlay.classList.remove('show');
|
|
404
|
+
this.elements.modal.classList.remove('show');
|
|
424
405
|
|
|
425
406
|
this.safeSetTimeout(() => {
|
|
426
|
-
this.elements.overlay.style.display =
|
|
407
|
+
this.elements.overlay.style.display = 'none';
|
|
427
408
|
document.body.removeChild(this.elements.overlay);
|
|
428
409
|
this.reset();
|
|
429
410
|
this.options.onClose?.();
|
|
@@ -469,9 +450,9 @@ class PopupSliderCaptcha {
|
|
|
469
450
|
this.abortController = new AbortController();
|
|
470
451
|
|
|
471
452
|
const response = await fetch(this.options.apiUrl, {
|
|
472
|
-
method:
|
|
453
|
+
method: 'POST',
|
|
473
454
|
headers: {
|
|
474
|
-
|
|
455
|
+
'Content-Type': 'application/json',
|
|
475
456
|
...this.options.headers
|
|
476
457
|
},
|
|
477
458
|
body: JSON.stringify({
|
|
@@ -489,7 +470,7 @@ class PopupSliderCaptcha {
|
|
|
489
470
|
|
|
490
471
|
// 优化:更严格的数据验证
|
|
491
472
|
if (!this.validateCaptchaData(data)) {
|
|
492
|
-
throw new Error(
|
|
473
|
+
throw new Error('验证码数据格式错误')
|
|
493
474
|
}
|
|
494
475
|
|
|
495
476
|
this.captchaData = data.data;
|
|
@@ -498,7 +479,10 @@ class PopupSliderCaptcha {
|
|
|
498
479
|
} catch (error) {
|
|
499
480
|
if (error.name === 'AbortError') {
|
|
500
481
|
this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR, '请求被取消');
|
|
501
|
-
} else if (
|
|
482
|
+
} else if (
|
|
483
|
+
error.message.includes('Failed to fetch') ||
|
|
484
|
+
error.message.includes('NetworkError')
|
|
485
|
+
) {
|
|
502
486
|
this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR, '网络连接失败');
|
|
503
487
|
} else {
|
|
504
488
|
this.handleError(PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR, error.message, error);
|
|
@@ -508,12 +492,21 @@ class PopupSliderCaptcha {
|
|
|
508
492
|
|
|
509
493
|
// 优化:添加验证码数据验证方法
|
|
510
494
|
validateCaptchaData(data) {
|
|
511
|
-
if (!data || typeof data !== 'object') return false
|
|
512
|
-
|
|
513
|
-
const requiredFields = [
|
|
495
|
+
if (!data || typeof data !== 'object') { return false }
|
|
496
|
+
|
|
497
|
+
const requiredFields = [
|
|
498
|
+
'canvasSrc',
|
|
499
|
+
'blockSrc',
|
|
500
|
+
'canvasWidth',
|
|
501
|
+
'canvasHeight',
|
|
502
|
+
'blockWidth',
|
|
503
|
+
'blockHeight',
|
|
504
|
+
'blockY',
|
|
505
|
+
'nonceStr'
|
|
506
|
+
];
|
|
514
507
|
const dataObj = data.data || data;
|
|
515
508
|
|
|
516
|
-
return requiredFields.every(field => {
|
|
509
|
+
return requiredFields.every((field) => {
|
|
517
510
|
const value = dataObj[field];
|
|
518
511
|
return value !== null && value !== undefined && value !== ''
|
|
519
512
|
})
|
|
@@ -523,6 +516,22 @@ class PopupSliderCaptcha {
|
|
|
523
516
|
return new Promise((resolve, reject) => {
|
|
524
517
|
let hasError = false;
|
|
525
518
|
|
|
519
|
+
// const _onLoad = () => {
|
|
520
|
+
// if (hasError) { return }
|
|
521
|
+
// loadedCount++
|
|
522
|
+
// if (loadedCount === 2) {
|
|
523
|
+
// this.hideLoading()
|
|
524
|
+
// resolve()
|
|
525
|
+
// }
|
|
526
|
+
// }
|
|
527
|
+
//
|
|
528
|
+
// const _onError = (error) => {
|
|
529
|
+
// if (hasError) { return }
|
|
530
|
+
// hasError = true
|
|
531
|
+
// this.handleError(PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR, '图片加载失败', error)
|
|
532
|
+
// reject(new Error('图片加载失败'))
|
|
533
|
+
// }
|
|
534
|
+
|
|
526
535
|
// 优化:并行加载图片,提高性能
|
|
527
536
|
const loadPromises = [
|
|
528
537
|
this.loadImageAsync(this.elements.backgroundImg, this.captchaData.canvasSrc, {
|
|
@@ -569,16 +578,16 @@ class PopupSliderCaptcha {
|
|
|
569
578
|
reject(new Error('图片加载超时'));
|
|
570
579
|
}, 10000); // 10秒超时
|
|
571
580
|
|
|
572
|
-
imgElement.onload = ()
|
|
581
|
+
imgElement.onload = function onImageLoad() {
|
|
573
582
|
this.safeClearTimeout(timeoutId);
|
|
574
583
|
this.imageCache.set(src, imgElement.cloneNode());
|
|
575
584
|
resolve();
|
|
576
|
-
};
|
|
585
|
+
}.bind(this);
|
|
577
586
|
|
|
578
|
-
imgElement.onerror = (error)
|
|
587
|
+
imgElement.onerror = function onImageError(error) {
|
|
579
588
|
this.safeClearTimeout(timeoutId);
|
|
580
589
|
reject(error);
|
|
581
|
-
};
|
|
590
|
+
}.bind(this);
|
|
582
591
|
|
|
583
592
|
imgElement.src = src;
|
|
584
593
|
this.applyStyles(imgElement, styles);
|
|
@@ -588,7 +597,7 @@ class PopupSliderCaptcha {
|
|
|
588
597
|
// 优化:提取样式应用逻辑
|
|
589
598
|
applyStyles(element, styles) {
|
|
590
599
|
Object.entries(styles).forEach(([key, value]) => {
|
|
591
|
-
element.style[key] = typeof value ===
|
|
600
|
+
element.style[key] = typeof value === 'number' ? `${value}px` : value;
|
|
592
601
|
});
|
|
593
602
|
}
|
|
594
603
|
|
|
@@ -596,31 +605,31 @@ class PopupSliderCaptcha {
|
|
|
596
605
|
showLoading() {
|
|
597
606
|
this.state.isLoading = true;
|
|
598
607
|
this.batchUpdateStyles({
|
|
599
|
-
container: { display:
|
|
600
|
-
loadingText: { display:
|
|
601
|
-
error: { display:
|
|
608
|
+
container: { display: 'block' },
|
|
609
|
+
loadingText: { display: 'flex' },
|
|
610
|
+
error: { display: 'none' }
|
|
602
611
|
});
|
|
603
|
-
this.updateUIState(
|
|
612
|
+
this.updateUIState('loading');
|
|
604
613
|
}
|
|
605
614
|
|
|
606
615
|
hideLoading() {
|
|
607
616
|
this.state.isLoading = false;
|
|
608
|
-
this.batchUpdateStyles({ loadingText: { display:
|
|
609
|
-
this.updateUIState(
|
|
617
|
+
this.batchUpdateStyles({ loadingText: { display: 'none' } });
|
|
618
|
+
this.updateUIState('reset');
|
|
610
619
|
}
|
|
611
620
|
|
|
612
621
|
showCaptcha() {
|
|
613
622
|
this.batchUpdateStyles({
|
|
614
|
-
container: { display:
|
|
615
|
-
track: { display:
|
|
616
|
-
error: { display:
|
|
623
|
+
container: { display: 'block' },
|
|
624
|
+
track: { display: 'block' },
|
|
625
|
+
error: { display: 'none' }
|
|
617
626
|
});
|
|
618
627
|
}
|
|
619
628
|
|
|
620
629
|
showError(message) {
|
|
621
630
|
this.hideLoading();
|
|
622
631
|
this.batchUpdateStyles({
|
|
623
|
-
error: { display:
|
|
632
|
+
error: { display: 'block', textContent: message }
|
|
624
633
|
});
|
|
625
634
|
}
|
|
626
635
|
|
|
@@ -644,7 +653,7 @@ class PopupSliderCaptcha {
|
|
|
644
653
|
|
|
645
654
|
async verify() {
|
|
646
655
|
if (!this.captchaData) {
|
|
647
|
-
this.onVerifyFail(
|
|
656
|
+
this.onVerifyFail('验证码数据丢失');
|
|
648
657
|
return
|
|
649
658
|
}
|
|
650
659
|
|
|
@@ -656,9 +665,9 @@ class PopupSliderCaptcha {
|
|
|
656
665
|
this.abortController = new AbortController();
|
|
657
666
|
|
|
658
667
|
const response = await fetch(this.options.verifyUrl, {
|
|
659
|
-
method:
|
|
668
|
+
method: 'POST',
|
|
660
669
|
headers: {
|
|
661
|
-
|
|
670
|
+
'Content-Type': 'application/json',
|
|
662
671
|
...this.options.headers
|
|
663
672
|
},
|
|
664
673
|
body: JSON.stringify({
|
|
@@ -682,12 +691,15 @@ class PopupSliderCaptcha {
|
|
|
682
691
|
if (this.isVerifySuccess(data)) {
|
|
683
692
|
this.onVerifySuccess(data.data || data.result);
|
|
684
693
|
} else {
|
|
685
|
-
this.onVerifyFail(data.message || data.msg ||
|
|
694
|
+
this.onVerifyFail(data.message || data.msg || '验证失败,请重试!');
|
|
686
695
|
}
|
|
687
696
|
} catch (error) {
|
|
688
697
|
if (error.name === 'AbortError') {
|
|
689
698
|
this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR, '验证请求被取消');
|
|
690
|
-
} else if (
|
|
699
|
+
} else if (
|
|
700
|
+
error.message.includes('Failed to fetch') ||
|
|
701
|
+
error.message.includes('NetworkError')
|
|
702
|
+
) {
|
|
691
703
|
this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR, '网络连接失败');
|
|
692
704
|
} else {
|
|
693
705
|
this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR, error.message, error);
|
|
@@ -697,30 +709,32 @@ class PopupSliderCaptcha {
|
|
|
697
709
|
|
|
698
710
|
// 优化:添加验证成功判断方法
|
|
699
711
|
isVerifySuccess(data) {
|
|
700
|
-
if (!data || typeof data !== 'object') return false
|
|
712
|
+
if (!data || typeof data !== 'object') { return false }
|
|
701
713
|
|
|
702
714
|
// 支持多种成功标识
|
|
703
715
|
const successIndicators = [
|
|
704
|
-
data.code ===
|
|
716
|
+
data.code === '0',
|
|
705
717
|
data.code === 0,
|
|
706
718
|
data.success === true,
|
|
707
719
|
data.status === 'success',
|
|
708
720
|
data.result === true
|
|
709
721
|
];
|
|
710
722
|
|
|
711
|
-
return successIndicators.some(indicator => indicator === true)
|
|
723
|
+
return successIndicators.some((indicator) => indicator === true)
|
|
712
724
|
}
|
|
713
725
|
|
|
714
726
|
onVerifySuccess(ticket) {
|
|
715
|
-
const duration = this.dragStartTime ?
|
|
727
|
+
const duration = this.dragStartTime ?
|
|
728
|
+
Date.now() - this.dragStartTime :
|
|
729
|
+
Date.now() - this.startTime;
|
|
716
730
|
const durationText = `验证成功!耗时:${(duration / 1000).toFixed(2)}s`;
|
|
717
731
|
|
|
718
|
-
this.updateUIState(
|
|
719
|
-
this.showFloatingTime(durationText,
|
|
732
|
+
this.updateUIState('success');
|
|
733
|
+
this.showFloatingTime(durationText, 'success');
|
|
720
734
|
|
|
721
735
|
this.safeSetTimeout(() => {
|
|
722
736
|
this.options.onSuccess?.({
|
|
723
|
-
ticket
|
|
737
|
+
ticket,
|
|
724
738
|
timestamp: Date.now(),
|
|
725
739
|
duration
|
|
726
740
|
});
|
|
@@ -728,21 +742,21 @@ class PopupSliderCaptcha {
|
|
|
728
742
|
}, 2000);
|
|
729
743
|
}
|
|
730
744
|
|
|
731
|
-
showFloatingTime(text, type =
|
|
745
|
+
showFloatingTime(text, type = 'success') {
|
|
732
746
|
const { elements } = this;
|
|
733
747
|
elements.floatingTime.textContent = text;
|
|
734
748
|
elements.floatingTime.className = `slider-captcha-floating-time ${type}`;
|
|
735
749
|
|
|
736
|
-
this.safeSetTimeout(() => elements.floatingTime.classList.add(
|
|
750
|
+
this.safeSetTimeout(() => elements.floatingTime.classList.add('show'), 100);
|
|
737
751
|
this.safeSetTimeout(() => {
|
|
738
|
-
elements.floatingTime.className =
|
|
752
|
+
elements.floatingTime.className = 'slider-captcha-floating-time';
|
|
739
753
|
}, 2500); // 优化:延长显示时间,避免被reset清除
|
|
740
754
|
}
|
|
741
755
|
|
|
742
756
|
onVerifyFail(message) {
|
|
743
757
|
this.state.retryCount++;
|
|
744
|
-
this.updateUIState(
|
|
745
|
-
this.showFloatingTime(message,
|
|
758
|
+
this.updateUIState('fail');
|
|
759
|
+
this.showFloatingTime(message, 'fail');
|
|
746
760
|
|
|
747
761
|
this.safeSetTimeout(() => {
|
|
748
762
|
if (this.state.retryCount >= this.options.maxRetries) {
|
|
@@ -772,10 +786,10 @@ class PopupSliderCaptcha {
|
|
|
772
786
|
// 重置UI
|
|
773
787
|
requestAnimationFrame(() => {
|
|
774
788
|
this.setTransition(true);
|
|
775
|
-
this.elements.btn.style.transform =
|
|
776
|
-
this.elements.sliderImg.style.transform =
|
|
777
|
-
this.updateUIState(
|
|
778
|
-
this.elements.error.style.display =
|
|
789
|
+
this.elements.btn.style.transform = 'translateX(0px)';
|
|
790
|
+
this.elements.sliderImg.style.transform = 'translateX(0px)';
|
|
791
|
+
this.updateUIState('reset');
|
|
792
|
+
this.elements.error.style.display = 'none';
|
|
779
793
|
});
|
|
780
794
|
}
|
|
781
795
|
|
|
@@ -803,7 +817,7 @@ class PopupSliderCaptcha {
|
|
|
803
817
|
}
|
|
804
818
|
|
|
805
819
|
clearAllTimers() {
|
|
806
|
-
this.timers.forEach(timer => {
|
|
820
|
+
this.timers.forEach((timer) => {
|
|
807
821
|
clearTimeout(timer);
|
|
808
822
|
clearInterval(timer);
|
|
809
823
|
});
|
|
@@ -833,13 +847,15 @@ class PopupSliderCaptcha {
|
|
|
833
847
|
// 工具函数:节流
|
|
834
848
|
throttle(func, delay) {
|
|
835
849
|
let lastCall = 0;
|
|
836
|
-
|
|
850
|
+
const throttledFunction = (...args) => {
|
|
837
851
|
const now = Date.now();
|
|
838
852
|
if (now - lastCall >= delay) {
|
|
839
853
|
lastCall = now;
|
|
840
854
|
return func.apply(this, args)
|
|
841
855
|
}
|
|
842
|
-
|
|
856
|
+
return undefined
|
|
857
|
+
};
|
|
858
|
+
return throttledFunction
|
|
843
859
|
}
|
|
844
860
|
|
|
845
861
|
destroy() {
|
|
@@ -852,8 +868,8 @@ class PopupSliderCaptcha {
|
|
|
852
868
|
|
|
853
869
|
// 恢复body样式
|
|
854
870
|
if (document.body) {
|
|
855
|
-
document.body.style.userSelect =
|
|
856
|
-
document.body.style.cursor =
|
|
871
|
+
document.body.style.userSelect = '';
|
|
872
|
+
document.body.style.cursor = '';
|
|
857
873
|
}
|
|
858
874
|
|
|
859
875
|
// 清理所有定时器
|
|
@@ -881,7 +897,7 @@ class PopupSliderCaptcha {
|
|
|
881
897
|
this.cachedDimensions = null;
|
|
882
898
|
|
|
883
899
|
// 清空所有属性
|
|
884
|
-
Object.keys(this).forEach(key => {
|
|
900
|
+
Object.keys(this).forEach((key) => {
|
|
885
901
|
if (key !== 'constructor') {
|
|
886
902
|
this[key] = null;
|
|
887
903
|
}
|
|
@@ -908,12 +924,12 @@ class PopupSliderCaptcha {
|
|
|
908
924
|
}
|
|
909
925
|
|
|
910
926
|
// 模块导出
|
|
911
|
-
if (typeof module !==
|
|
927
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
912
928
|
module.exports = PopupSliderCaptcha;
|
|
913
929
|
module.exports.default = PopupSliderCaptcha;
|
|
914
|
-
} else if (typeof define ===
|
|
930
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
915
931
|
define([], () => PopupSliderCaptcha);
|
|
916
|
-
} else if (typeof window !==
|
|
932
|
+
} else if (typeof window !== 'undefined') {
|
|
917
933
|
window.PopupSliderCaptcha = PopupSliderCaptcha;
|
|
918
934
|
window.SliderCaptcha = PopupSliderCaptcha;
|
|
919
935
|
}
|