/** * AKSRT modal auth interactions. * * @package AKSRT */ (function () { 'use strict'; var API_ROOT = (window.aksrtData && window.aksrtData.apiUrl) || '/wp-json/aksrt/v1'; var NONCE = (window.aksrtData && window.aksrtData.restNonce) || ''; var modal = null; var currentTab = 'login'; function init() { modal = document.getElementById('aksrt-modal-auth'); if (!modal) { return; } var closeBtn = modal.querySelector('.aksrt-modal-auth__close'); var overlay = modal.querySelector('.aksrt-modal-auth__overlay'); if (closeBtn) { closeBtn.addEventListener('click', close); } if (overlay) { overlay.addEventListener('click', close); } document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && modal.classList.contains('is-open')) { close(); } }); modal.querySelectorAll('.aksrt-modal-auth__tab').forEach(function (tab) { tab.addEventListener('click', function () { switchTab(this.dataset.tab || 'login'); }); }); modal.querySelectorAll('.aksrt-modal-auth__switch').forEach(function (button) { button.addEventListener('click', function () { switchTab(this.dataset.switch || 'login'); }); }); modal.querySelectorAll('.aksrt-modal-auth__pass-toggle').forEach(function (button) { button.addEventListener('click', function () { var input = this.parentElement.querySelector('input'); var icon = this.querySelector('i'); if (!input || !icon) { return; } if (input.type === 'password') { input.type = 'text'; icon.className = 'far fa-eye-slash'; } else { input.type = 'password'; icon.className = 'far fa-eye'; } }); }); var loginForm = document.getElementById('aksrt-modal-login-form'); if (loginForm) { loginForm.addEventListener('submit', function (event) { event.preventDefault(); handleLogin(loginForm); }); } var registerForm = document.getElementById('aksrt-modal-register-form'); if (registerForm) { registerForm.addEventListener('submit', function (event) { event.preventDefault(); handleRegister(registerForm); }); } document.addEventListener('click', function (event) { var trigger = event.target.closest('[data-aksrt-auth-required], .aksrt-login-trigger, #nav-login-btn, .signin-loader, .signup-loader, .qrcode-signin'); if (!trigger) { return; } event.preventDefault(); if (trigger.classList.contains('signup-loader')) { open(trigger.dataset.authRedirect || ''); switchTab('register'); return; } if (trigger.classList.contains('qrcode-signin')) { open(trigger.dataset.authRedirect || ''); switchTab('qrcode'); return; } open(trigger.dataset.authRedirect || ''); }); initQrLogin(); initAgreementShake(); initSocialPriority(); } function open(redirectUrl) { if (!modal) { return; } modal.classList.add('is-open'); modal.setAttribute('aria-hidden', 'false'); document.body.style.overflow = 'hidden'; if (redirectUrl) { modal.dataset.redirect = redirectUrl; } setTimeout(function () { var firstInput = modal.querySelector('.aksrt-modal-auth__panel.is-active input:not([type="hidden"])'); if (firstInput) { firstInput.focus(); } }, 100); } var qrLoginTimer = null; function close() { if (!modal) { return; } modal.classList.remove('is-open'); modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; clearMessages(); stopQrPolling(); } function switchTab(tab) { currentTab = tab; modal.querySelectorAll('.aksrt-modal-auth__tab').forEach(function (button) { var isActive = button.dataset.tab === tab; button.classList.toggle('is-active', isActive); button.setAttribute('aria-selected', isActive ? 'true' : 'false'); }); modal.querySelectorAll('.aksrt-modal-auth__panel').forEach(function (panel) { panel.classList.toggle('is-active', panel.dataset.panel === tab); }); modal.querySelectorAll('.aksrt-modal-auth__footer-text').forEach(function (row) { row.hidden = row.dataset.for !== tab; }); modal.setAttribute('aria-label', tab === 'register' ? '注册' : '登录'); clearMessages(); } function withCaptcha(scene, form, next) { var store = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes ? window.AKSRTCaptchaState.scenes : null; var instance = store && store[scene] ? store[scene] : null; if (instance && typeof instance.verify === 'function') { instance.verify(function () { next(serializeForm(form)); }); return; } next(serializeForm(form)); } function resetCaptcha(scene) { try { var store = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes ? window.AKSRTCaptchaState.scenes : null; if (store && store[scene] && typeof store[scene].reset === 'function') { store[scene].reset(); } } catch (e) { /* ignore */ } } function handleLogin(form) { if (!form.querySelector('[name="log"]') || !form.querySelector('[name="pwd"]')) { showMessage('login', '请输入用户名和密码', 'error'); return; } setLoading(form, true); clearMessages(); withCaptcha('login', form, function (payload) { if (!payload.log || !payload.pwd) { setLoading(form, false); showMessage('login', '请输入用户名和密码', 'error'); return; } request('/auth/modal-login', payload) .then(function (result) { if (result.ok && result.data.success) { onAuthSuccess(result.data); return; } resetCaptcha('login'); var message = result.data.message || '登录失败'; if (result.data.verify_url) { message += ' 前往验证'; } showMessage('login', message, 'error'); }) .catch(function () { resetCaptcha('login'); showMessage('login', '网络错误,请稍后重试', 'error'); }) .finally(function () { setLoading(form, false); }); }); } function handleRegister(form) { setLoading(form, true); clearMessages(); withCaptcha('register', form, function (payload) { if (!payload.user_login || !payload.user_email || !payload.user_pass || !payload.user_pass_confirm) { setLoading(form, false); showMessage('register', '请完整填写注册信息', 'error'); return; } if (payload.user_pass !== payload.user_pass_confirm) { setLoading(form, false); showMessage('register', '两次输入的密码不一致', 'error'); return; } request('/auth/modal-register', payload) .then(function (result) { if (!result.ok || !result.data.success) { resetCaptcha('register'); showMessage('register', result.data.message || '注册失败', 'error'); return; } if (result.data.user) { onAuthSuccess(result.data); return; } if (result.data.verification_required) { var verifyMessage = result.data.message || '注册成功,请完成邮箱验证'; if (result.data.verify_url) { verifyMessage += ' 前往验证'; } showMessage('register', verifyMessage, 'success'); form.reset(); resetCaptcha('register'); return; } var loginMessage = result.data.message || '注册成功,请登录'; switchTab('login'); prefillLogin(result.data.user_login || result.data.user_email || ''); showMessage('login', loginMessage, 'success'); form.reset(); resetCaptcha('register'); }) .catch(function () { resetCaptcha('register'); showMessage('register', '网络错误,请稍后重试', 'error'); }) .finally(function () { setLoading(form, false); }); }); } function serializeForm(form) { var payload = {}; var formData = new FormData(form); formData.forEach(function (value, key) { if (payload[key] !== undefined) { if (!Array.isArray(payload[key])) { payload[key] = [payload[key]]; } payload[key].push(value); } else { payload[key] = value; } }); return payload; } function request(path, payload) { var headers = { 'Content-Type': 'application/json' }; if (NONCE) { headers['X-WP-Nonce'] = NONCE; } return fetch(API_ROOT + path, { method: 'POST', headers: headers, body: JSON.stringify(payload), }).then(function (response) { return response.json().then(function (data) { return { ok: response.ok, data: data }; }); }); } function prefillLogin(value) { var loginInput = document.getElementById('modal-login-user'); if (loginInput && value) { loginInput.value = value; } } function onAuthSuccess(data) { close(); if (data.user) { updateNavUser(data.user); } if (window.aksrt && aksrt.notyf) { aksrt.notyf(data.message || '登录成功', 'success'); } else if (typeof window.aksrtToast === 'function') { window.aksrtToast(data.message || '登录成功', 'success'); } var redirect = modal.dataset.redirect || data.redirect; if (redirect && redirect !== window.location.href) { setTimeout(function () { window.location.href = redirect; }, 500); return; } setTimeout(function () { window.location.reload(); }, 500); } function updateNavUser(user) { document.querySelectorAll('.nav-user__avatar img, .nav-user__toggle img').forEach(function (image) { if (user.avatar_url) { image.src = user.avatar_url; } }); document.querySelectorAll('.nav-user__name').forEach(function (el) { if (user.display_name) { el.textContent = user.display_name; } }); } function showMessage(panel, message, type) { var box = document.getElementById('aksrt-modal-' + panel + '-error'); if (!box) { return; } box.innerHTML = message; box.hidden = false; box.classList.toggle('is-success', type === 'success'); } function clearMessages() { if (!modal) { return; } modal.querySelectorAll('.aksrt-modal-auth__error').forEach(function (box) { box.hidden = true; box.innerHTML = ''; box.classList.remove('is-success'); }); } function setLoading(form, loading) { var submit = form.querySelector('.aksrt-modal-auth__submit'); if (!submit) { return; } var label = submit.querySelector('span'); var spinner = submit.querySelector('i'); submit.disabled = loading; if (label) { label.hidden = loading; } if (spinner) { spinner.hidden = !loading; } } function escapeAttribute(value) { return String(value || '') .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); } // ── QR Code Login (§10.7) ── function bindWeixingzhCodeForm(container) { if (!container) return; var form = container.querySelector('[data-aksrt-weixingzh-code]'); if (!form || form._bound) return; form._bound = true; form.addEventListener('submit', function (e) { e.preventDefault(); var input = form.querySelector('[name="code"]'); var code = input ? String(input.value || '').trim() : ''; if (!code) return; var btn = form.querySelector('button[type="submit"]'); if (btn) btn.disabled = true; request('/auth/weixingzh-code', { code: code }) .then(function (result) { var data = (result && result.data) || result || {}; if ((result && result.ok) || (result && result.success) || data.logged_in) { onAuthSuccess(data.data || data); return; } throw new Error((result && (result.message || result.msg)) || '验证码错误'); }) .catch(function (err) { alert(err.message || '登录失败'); }) .finally(function () { if (btn) btn.disabled = false; }); }); } function initQrLogin() { var qrPanel = modal.querySelector('[data-panel="qrcode"]'); if (!qrPanel) return; var qrContainer = qrPanel.querySelector('.aksrt-modal-auth__qr-code'); if (!qrContainer) return; // When qrcode tab is activated, fetch QR and start polling var observer = new MutationObserver(function () { if (qrPanel.classList.contains('is-active')) { fetchQrCode(qrContainer); } else { stopQrPolling(); } }); observer.observe(qrPanel, { attributes: true, attributeFilter: ['class'] }); } function fetchQrCode(container) { container.innerHTML = '
'; request('/auth/qrcode-login', {}) .then(function (result) { var payload = (result && result.data) || result || {}; var qrHtml = payload.qr_html || (payload.data && payload.data.qr_html) || ''; var checkUrl = payload.check_url || (payload.data && payload.data.check_url) || '/auth/check-qrcode-login'; var token = payload.token || (payload.data && payload.data.token) || ''; if (qrHtml) { container.innerHTML = qrHtml; bindWeixingzhCodeForm(container); if (checkUrl && token) { startQrPolling(checkUrl, token); } } else { container.innerHTML = '

扫码登录暂不可用

'; } }) .catch(function () { container.innerHTML = '

网络错误

'; }); } function startQrPolling(checkUrl, token) { stopQrPolling(); qrLoginTimer = setInterval(function () { request(checkUrl, { token: token }) .then(function (result) { var data = (result && result.data) || result || {}; var body = data.data || data; if ((result && result.ok) || data.success || body.logged_in) { if (body.logged_in || (data.data && data.data.logged_in)) { stopQrPolling(); onAuthSuccess(body.data || body); } } }) .catch(function () { /* keep polling */ }); }, 2000); } function stopQrPolling() { if (qrLoginTimer) { clearInterval(qrLoginTimer); qrLoginTimer = null; } } // ── Social Login Priority (§10.7) ── function initSocialPriority() { var wxPriority = window.aksrtData && aksrtData.signinWxPriority; if (wxPriority && modal) { var wxBtn = modal.querySelector('.social-login-item.weixingzh, .social-login-item[data-provider="wechat"]'); if (wxBtn) { setTimeout(function () { wxBtn.click(); }, 100); } } } // ── Agreement Check: shake instead of disable (§10.7) ── function initAgreementShake() { if (!modal) return; modal.querySelectorAll('.aksrt-modal-auth__submit').forEach(function (btn) { btn.addEventListener('click', function (e) { var form = btn.closest('form'); if (!form) return; var agree = form.querySelector('[name="agree_terms"], [name="user_agreement"]'); if (agree && !agree.checked) { e.preventDefault(); e.stopImmediatePropagation(); agree.closest('label, .aksrt-modal-auth__agreement').classList.add('ani', 'shake'); if (window.aksrt && aksrt.notyf) aksrt.notyf('请先同意用户协议', 'warning'); setTimeout(function () { agree.closest('label, .aksrt-modal-auth__agreement').classList.remove('ani', 'shake'); }, 400); } }); }); } // ── Password Visibility: unified (§10.7) ── // Already handled above; added unification note. window.AksrtAuth = { open: open, close: close, }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();