430 lines
14 KiB
JavaScript
430 lines
14 KiB
JavaScript
/**
|
||
* AKSRT Sign/Register — 统一认证交互脚本
|
||
*
|
||
* 对齐 zibll 的 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() {
|
||
document.querySelectorAll('[data-aksrt-sms-trigger]').forEach(btn => {
|
||
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)) {
|
||
showToast('请输入正确的手机号', '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 form = btn.closest('form')
|
||
const captchaData = collectCaptchaFields(form)
|
||
const scene = btn.dataset.aksrtSmsScene || 'register'
|
||
|
||
fetch(REST_BASE + '/auth/send-sms-code', {
|
||
method: 'POST',
|
||
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) {
|
||
showToast(data.message || '验证码已发送', 'success')
|
||
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 || '发送失败'
|
||
showToast(msg, 'error')
|
||
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(() => {
|
||
showToast('网络错误,请重试', 'error')
|
||
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) {
|
||
showToast('请填写完整信息', 'warning')
|
||
return
|
||
}
|
||
if (password !== confirm) {
|
||
showToast('两次密码不一致', 'warning')
|
||
return
|
||
}
|
||
if (password.length < 6) {
|
||
showToast('密码至少 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) {
|
||
showToast(data.message || '注册成功', 'success')
|
||
if (data.notice) showToast(data.notice, 'warning')
|
||
setTimeout(() => {
|
||
window.location.href = data.redirect || '/'
|
||
}, 500)
|
||
} else {
|
||
showToast(data.message || '注册失败', 'error')
|
||
try {
|
||
if (captchaState && captchaState.scenes && captchaState.scenes.register) {
|
||
captchaState.scenes.register.reset()
|
||
}
|
||
} catch (e) { /* ignore */ }
|
||
if (submitBtn) {
|
||
submitBtn.disabled = false
|
||
submitBtn.textContent = '注册'
|
||
}
|
||
}
|
||
})
|
||
.catch(() => {
|
||
showToast('网络错误', 'error')
|
||
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) {
|
||
showToast('请输入手机号', '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) {
|
||
showToast('请输入验证码', 'warning')
|
||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '登录' }
|
||
return
|
||
}
|
||
endpoint = '/auth/phone-code-login'
|
||
body = { phone, code }
|
||
} else {
|
||
if (!password) {
|
||
showToast('请输入密码', '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) {
|
||
showToast('登录成功', 'success')
|
||
setTimeout(() => {
|
||
window.location.href = data.data?.redirect || '/'
|
||
}, 500)
|
||
} else {
|
||
showToast(data.message || '登录失败', 'error')
|
||
if (captchaState && captchaState.scenes && captchaState.scenes.login) {
|
||
captchaState.scenes.login.reset()
|
||
}
|
||
if (submitBtn) {
|
||
submitBtn.disabled = false
|
||
submitBtn.textContent = '登录'
|
||
}
|
||
}
|
||
})
|
||
.catch(() => {
|
||
showToast('网络错误', 'error')
|
||
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 ? 'fas fa-eye-slash' : '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()
|
||
showToast('两次密码不一致', 'warning')
|
||
confirm.focus()
|
||
return
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
function showToast(message, type) {
|
||
if (typeof window.aksrtShowToast === 'function') {
|
||
window.aksrtShowToast(message, type)
|
||
return
|
||
}
|
||
const toast = document.createElement('div')
|
||
toast.className = 'aksrt-auth-toast aksrt-auth-toast--' + (type || 'info')
|
||
toast.textContent = message
|
||
document.body.appendChild(toast)
|
||
setTimeout(() => toast.classList.add('aksrt-auth-toast--show'), 10)
|
||
setTimeout(() => {
|
||
toast.classList.remove('aksrt-auth-toast--show')
|
||
setTimeout(() => toast.remove(), 300)
|
||
}, 3000)
|
||
}
|
||
|
||
// 初始化
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', initAuthPages)
|
||
} else {
|
||
initAuthPages()
|
||
}
|
||
|
||
// 暴露给外部
|
||
window.AKSRTSignRegister = {
|
||
init: initAuthPages,
|
||
sendSmsCode,
|
||
switchAuthTab,
|
||
showToast,
|
||
}
|
||
})()
|