补充主题静态资源与归档模板
This commit is contained in:
1 parent
1268a55dd0
commit
3247db7ed1
265 files changed
+59174
No files matched your search
@@ -0,0 +1,390 @@
|
||||
/**
|
||||
* 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)
|
||||
|
||||
fetch(REST_BASE + '/auth/send-sms-code', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone, ...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')
|
||||
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 inviteCode = form.querySelector('[name="invite_code"]')?.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 captchaData = collectCaptchaFields(form)
|
||||
|
||||
fetch(REST_BASE + '/auth/phone-register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone, code, password, invite_code: inviteCode, ...captchaData }),
|
||||
})
|
||||
.then(r => r.json().then(data => ({ ok: r.ok, data })))
|
||||
.then(({ ok, data }) => {
|
||||
if (ok && data.success) {
|
||||
showToast(data.message || '注册成功', 'success')
|
||||
setTimeout(() => {
|
||||
window.location.href = data.data?.redirect || '/'
|
||||
}, 500)
|
||||
} else {
|
||||
showToast(data.message || '注册失败', 'error')
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '注册'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
showToast('网络错误', 'error')
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '注册'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
fetch(REST_BASE + endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
.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 (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '登录'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
showToast('网络错误', 'error')
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '登录'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 辅助功能
|
||||
* ================================================================= */
|
||||
|
||||
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,
|
||||
}
|
||||
})()
|
||||
Reference in new issue
Block a user