- assets/vendor 全量迁移: fontawesome5→assets/fontawesome5, css→assets/css, js→assets/js/runtime(避开 14 个同名自有文件), img 合并去重→assets/img, fonts→assets/fonts; 删除 assets/vendor - libs 五个与 widgets/third-party 字节相同的库不重复入库, loader paths 改指向 - 运行时切换: _win.uri=assets 根, loader baseUrl=/js/runtime, enlighter 注入 路径同步; PHP 7 个引用文件切换并改名 $zibll_*→$runtime_* - 去品牌: Qinver 头注释/品牌 console.log/演示链接清除, 默认文案中性化 - 功能标识符改名: zib__→aksrt__, zib_ajax→aksrt_ajax, window.zib→ window.aksrtLegacy, zibllblock/*→aksrtblock/*, .zib-widget→.aksrt-panel, .zib-slider→.aksrt-slider, zib_menu_options meta 键等 - 自有 CSS 改名: legacy-utilities/compat-vars/footer shell - admin SPA 重建; 契约测试同步(路径/断言/文件更名) - 例外保留: tests/visual 对照环境与 fixtures 前缀(外部 Zibll 产品引用) 验证: php -l 133 个改动文件通过; runtime 86 个 JS node --check 通过; 契约测试 68/70(2 个失败经 stash 对照确认为存量问题)
428 lines
14 KiB
JavaScript
428 lines
14 KiB
JavaScript
/**
|
||
* 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,
|
||
}
|
||
})()
|