/** * AKSRT Sign/Register — 统一认证交互脚本 * * 对齐 legacy 的 sign-register.js: * - 短信验证码倒计时(60 秒 + 服务端 remaining_time) * - 手机号注册/登录表单交互 * - 人机验证联动 * - 密码可见性切换 * - 协议勾选校验 */ ;(() => { 'use strict' const COUNTDOWN_KEY = 'aksrt_sms_cooldown' const DEFAULT_COOLDOWN = 60 const REST_BASE = '/wp-json/aksrt/v1' /** * 初始化所有认证页面 */ function initAuthPages() { initSmsButtons() initPasswordToggles() initAgreementChecks() initFormValidation() initPhoneAuthTabs() } /* ================================================================= * SMS 验证码发送 + 倒计时 * ================================================================= */ function initSmsButtons(root) { root = root || document root.querySelectorAll('[data-aksrt-sms-trigger]').forEach(btn => { if (btn.dataset.aksrtSmsInitialized === 'true') return btn.dataset.aksrtSmsInitialized = 'true' const cooldown = parseInt(btn.dataset.cooldown || DEFAULT_COOLDOWN, 10) // 恢复页面刷新后的倒计时 const stored = sessionStorage.getItem(COUNTDOWN_KEY) if (stored) { const remaining = Math.ceil((parseInt(stored, 10) - Date.now()) / 1000) if (remaining > 0) { startCountdown(btn, remaining) } } btn.addEventListener('click', e => { e.preventDefault() if (btn.disabled) return sendSmsCode(btn, cooldown) }) }) } function sendSmsCode(btn, cooldown) { const phoneInput = document.querySelector(btn.dataset.aksrtSmsPhone || '[data-aksrt-phone-input]') const phone = phoneInput ? phoneInput.value.trim() : '' if (!/^1[3-9]\d{9}$/.test(phone)) { notyf('请输入正确的手机号', 'warning') if (phoneInput) phoneInput.focus() return } // 人机验证联动 const captchaState = window.AKSRTCaptchaState const scene = btn.dataset.aksrtSmsScene || 'register' if (captchaState && captchaState.scenes && captchaState.scenes[scene]) { captchaState.scenes[scene].verify(() => { doSendSms(btn, phone, cooldown) }) } else { doSendSms(btn, phone, cooldown) } } function doSendSms(btn, phone, cooldown) { btn.disabled = true const original = btn.textContent btn.textContent = '发送中...' const scene = btn.dataset.aksrtSmsScene || 'register' const toastId = scene === 'bind_phone' ? 'aksrt-settings-phone-code-toast' : 'aksrt-auth-sms-code-toast' notyf('正在发送验证码…', 'load', '', toastId) // 收集人机验证字段 const form = btn.closest('form') const captchaData = collectCaptchaFields(form) fetch(REST_BASE + '/auth/send-sms-code', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone, scene, ...captchaData }), }) .then(r => r.json().then(data => ({ ok: r.ok, data }))) .then(({ ok, data }) => { if (ok && data.success) { notyf(data.message || '验证码已发送', 'success', 5, toastId) const seconds = (data.data && data.data.remaining_time) || cooldown sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + seconds * 1000)) startCountdown(btn, seconds) } else { const msg = (data.data && data.data.message) || data.message || '发送失败' notyf(msg, 'danger', 5, toastId) try { const captchaState = window.AKSRTCaptchaState if (captchaState && captchaState.scenes && captchaState.scenes[scene]) { captchaState.scenes[scene].reset() } } catch (e) { /* ignore */ } const remaining = data.data && data.data.remaining_time if (remaining && remaining > 0) { sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + remaining * 1000)) startCountdown(btn, remaining) } else { btn.disabled = false btn.textContent = original } } }) .catch(() => { notyf('网络错误,请重试', 'danger', 5, toastId) btn.disabled = false btn.textContent = original }) } function startCountdown(btn, seconds) { let remaining = Math.max(1, Math.floor(seconds)) btn.disabled = true btn.textContent = remaining + 's' const timer = setInterval(() => { remaining-- if (remaining <= 0) { clearInterval(timer) btn.disabled = false btn.textContent = '获取验证码' sessionStorage.removeItem(COUNTDOWN_KEY) } else { btn.textContent = remaining + 's' } }, 1000) } /* ================================================================= * 手机号注册/登录表单 * ================================================================= */ function initPhoneAuthTabs() { document.querySelectorAll('[data-aksrt-auth-tab]').forEach(tab => { tab.addEventListener('click', e => { e.preventDefault() const target = tab.dataset.aksrtAuthTab switchAuthTab(target) }) }) // 手机号注册表单 const phoneRegForm = document.getElementById('aksrt-phone-register-form') if (phoneRegForm) { phoneRegForm.addEventListener('submit', e => { e.preventDefault() handlePhoneRegister(phoneRegForm) }) } // 手机号登录表单 const phoneLoginForm = document.getElementById('aksrt-phone-login-form') if (phoneLoginForm) { phoneLoginForm.addEventListener('submit', e => { e.preventDefault() handlePhoneLogin(phoneLoginForm) }) } } function switchAuthTab(tabName) { document.querySelectorAll('[data-auth-panel]').forEach(panel => { panel.style.display = panel.dataset.authPanel === tabName ? '' : 'none' }) document.querySelectorAll('[data-aksrt-auth-tab]').forEach(tab => { tab.classList.toggle('is-active', tab.dataset.aksrtAuthTab === tabName) }) } function handlePhoneRegister(form) { const phone = form.querySelector('[name="phone"]')?.value?.trim() const code = form.querySelector('[name="code"]')?.value?.trim() const password = form.querySelector('[name="password"]')?.value const confirm = form.querySelector('[name="password_confirm"]')?.value const ref = form.querySelector('[name="ref"]')?.value?.trim() if (!phone || !code || !password) { notyf('请填写完整信息', 'warning') return } if (password !== confirm) { notyf('两次密码不一致', 'warning') return } if (password.length < 6) { notyf('密码至少 6 位', 'warning') return } const submitBtn = form.querySelector('[type="submit"]') if (submitBtn) { submitBtn.disabled = true submitBtn.textContent = '注册中...' } const captchaState = window.AKSRTCaptchaState const doRegister = () => { const captchaData = collectCaptchaFields(form) fetch(REST_BASE + '/auth/phone-register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone, code, password, ref, ...captchaData }), }) .then(r => r.json().then(data => ({ ok: r.ok, data }))) .then(({ ok, data }) => { if (ok && data.success) { notyf(data.message || '注册成功', 'success') if (data.notice) notyf(data.notice, 'warning') setTimeout(() => { window.location.href = data.redirect || '/' }, 500) } else { notyf(data.message || '注册失败', 'danger') try { if (captchaState && captchaState.scenes && captchaState.scenes.register) { captchaState.scenes.register.reset() } } catch (e) { /* ignore */ } if (submitBtn) { submitBtn.disabled = false submitBtn.textContent = '注册' } } }) .catch(() => { notyf('网络错误', 'danger') try { if (captchaState && captchaState.scenes && captchaState.scenes.register) { captchaState.scenes.register.reset() } } catch (e) { /* ignore */ } if (submitBtn) { submitBtn.disabled = false submitBtn.textContent = '注册' } }) } if (captchaState && captchaState.scenes && captchaState.scenes.register) { captchaState.scenes.register.verify(doRegister) } else { doRegister() } } function handlePhoneLogin(form) { const phone = form.querySelector('[name="phone"]')?.value?.trim() const password = form.querySelector('[name="password"]')?.value const loginMode = form.querySelector('[name="login_mode"]')?.value || 'password' if (!phone) { notyf('请输入手机号', 'warning') return } const submitBtn = form.querySelector('[type="submit"]') if (submitBtn) { submitBtn.disabled = true submitBtn.textContent = '登录中...' } let endpoint, body if (loginMode === 'code') { const code = form.querySelector('[name="code"]')?.value?.trim() if (!code) { notyf('请输入验证码', 'warning') if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '登录' } return } endpoint = '/auth/phone-code-login' body = { phone, code } } else { if (!password) { notyf('请输入密码', 'warning') if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '登录' } return } endpoint = '/auth/phone-login' body = { phone, password } } const captchaState = window.AKSRTCaptchaState const doLogin = () => { const captchaData = collectCaptchaFields(form) fetch(REST_BASE + endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...body, ...captchaData }), }) .then(r => r.json().then(data => ({ ok: r.ok, data }))) .then(({ ok, data }) => { if (ok && data.success) { notyf('登录成功', 'success') setTimeout(() => { window.location.href = data.data?.redirect || '/' }, 500) } else { notyf(data.message || '登录失败', 'danger') if (captchaState && captchaState.scenes && captchaState.scenes.login) { captchaState.scenes.login.reset() } if (submitBtn) { submitBtn.disabled = false submitBtn.textContent = '登录' } } }) .catch(() => { notyf('网络错误', 'danger') if (submitBtn) { submitBtn.disabled = false submitBtn.textContent = '登录' } }) } if (captchaState && captchaState.scenes && captchaState.scenes.login) { captchaState.scenes.login.verify(doLogin) } else { doLogin() } } /* ================================================================= * 辅助功能 * ================================================================= */ function collectCaptchaFields(form) { if (!form) return {} const fields = {} const names = [ 'captcha_verified', 'tencent_ticket', 'tencent_randstr', 'tencent_sid', 'cf_turnstile_response', 'geetest_lot_number', 'geetest_captcha_output', 'geetest_pass_token', 'geetest_gen_time', 'aliyun_captcha_verify_param' ] names.forEach(name => { const el = form.querySelector('[name="' + name + '"]') if (el && el.value) fields[name] = el.value }) return fields } function initPasswordToggles() { document.querySelectorAll('[data-aksrt-password-toggle]').forEach(btn => { btn.addEventListener('click', () => { const input = btn.closest('.input-wrap')?.querySelector('input') if (!input) return const isPassword = input.type === 'password' input.type = isPassword ? 'text' : 'password' const icon = btn.querySelector('i, svg') if (icon) { if (icon.tagName === 'I') { icon.className = isPassword ? 'fa fas fa-eye-slash' : 'fa fas fa-eye' } } }) }) } function initAgreementChecks() { document.querySelectorAll('form[data-aksrt-auth-form]').forEach(form => { const checkbox = form.querySelector('[data-aksrt-agreement]') const submitBtn = form.querySelector('[type="submit"]') if (checkbox && submitBtn) { const update = () => { submitBtn.disabled = !checkbox.checked } checkbox.addEventListener('change', update) update() } }) } function initFormValidation() { document.querySelectorAll('form[data-aksrt-auth-form]').forEach(form => { form.addEventListener('submit', e => { const password = form.querySelector('[name="password"]') const confirm = form.querySelector('[name="password_confirm"]') if (password && confirm && password.value !== confirm.value) { e.preventDefault() notyf('两次密码不一致', 'warning') confirm.focus() return } }) }) } // 初始化 if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') { window.AksrtFrontend.register('aksrt-sms-buttons', function(root) { initSmsButtons(root) }) } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAuthPages) } else { initAuthPages() } // 暴露给外部 window.AKSRTSignRegister = { init: initAuthPages, sendSmsCode, switchAuthTab, } })()