diff --git a/assets/css/routes/user-center/settings.css b/assets/css/routes/user-center/settings.css index 879d8667..37f9c402 100644 --- a/assets/css/routes/user-center/settings.css +++ b/assets/css/routes/user-center/settings.css @@ -624,6 +624,8 @@ background: var(--main-bg-color, var(--surface-1, #fff)); } +.aksrt-settings-modal [hidden] { display: none !important; } + .aksrt-settings-modal .modal-colorful-header { position: absolute; top: 0; @@ -643,54 +645,50 @@ cursor: pointer; } -.aksrt-settings-modal form { +.aksrt-settings-modal > form { position: relative; z-index: 0; margin-top: 120px; padding: 18px 15px; } -.aksrt-settings-modal__fields { padding: 0 2px; } -.aksrt-settings-modal .modal-field { position: relative; margin-bottom: 14px; } -.aksrt-settings-modal .modal-field label { display: block; margin-bottom: 6px; color: var(--muted-2-color, #777); font-size: 12px; font-weight: 400; } -.aksrt-settings-modal .modal-field input, -.aksrt-settings-modal .readonly-display { - display: block; - width: 100%; - min-height: 38px; - box-sizing: border-box; - padding: 8px 38px 8px 11px; - border: 1px solid var(--main-border-color, var(--color-line, rgba(50, 50, 50, .12))); - border-radius: var(--mini-radius, 4px); - background: var(--main-bg-color, var(--surface-1, #fff)); - color: var(--color-ink, #333); - font-size: 13px; - outline: 0; +.aksrt-settings-modal__pane { + position: relative; + z-index: 0; + margin-top: 120px; + padding: 18px 15px; } -.aksrt-settings-modal .readonly-display { padding-right: 11px; color: var(--muted-2-color, #777); word-break: break-all; } -.aksrt-settings-modal .modal-field input:focus { border-color: var(--focus-color, var(--color-primary, #2997f7)); box-shadow: 0 0 0 2px var(--focus-color-opacity1, rgba(41, 151, 247, .12)); } + +.aksrt-settings-modal__pane form, +.aksrt-settings-modal .account-step form { + margin: 0; + padding: 0; +} + +.aksrt-settings-modal .account-step-list { + position: relative; + z-index: 0; + margin-top: 120px; + padding: 18px 15px 0; +} + +.aksrt-settings-modal .account-step-panels { position: relative; z-index: 0; } +.aksrt-settings-modal .account-step { display: none; padding: 0 15px 18px; } +.aksrt-settings-modal .account-step.is-active { display: block; } +.aksrt-settings-modal .account-step > p { margin: 0 0 12px; } +.aksrt-settings-modal .account-recovery-toggle { border: 0; background: transparent; cursor: pointer; } + +.aksrt-settings-modal__fields { padding: 0 2px; } .aksrt-settings-modal .toggle-pwd { position: absolute; right: 7px; bottom: 4px; width: 30px; height: 30px; padding: 0; border: 0; background: transparent; color: var(--muted-2-color, #999); cursor: pointer; } .aksrt-settings-modal .toggle-pwd:hover { color: var(--color-ink, #333); } -.aksrt-settings-modal .code-input-group { display: flex; align-items: stretch; gap: 7px; } -.aksrt-settings-modal .code-input-group input { min-width: 0; padding-right: 11px; letter-spacing: 2px; } .aksrt-settings-modal .code-send-btn { min-width: 112px; padding: 6px 10px; white-space: nowrap; } .aksrt-settings-modal .code-send-btn.is-counting { pointer-events: none; opacity: .6; } -.aksrt-settings-modal .pwd-strength { height: 3px; margin-top: 6px; border-radius: 2px; background: var(--muted-border-color, #eee); overflow: hidden; } -.aksrt-settings-modal .pwd-strength__fill { width: 0; height: 100%; border-radius: inherit; transition: width .2s ease, background-color .2s ease; } -.aksrt-settings-modal .pwd-strength__fill.level-weak { width: 33%; background: var(--color-error, #ef4444); } -.aksrt-settings-modal .pwd-strength__fill.level-medium { width: 66%; background: var(--color-warning, #f59e0b); } -.aksrt-settings-modal .pwd-strength__fill.level-strong { width: 100%; background: var(--color-success, #10b981); } -.aksrt-settings-modal .pwd-strength__text { display: block; min-height: 16px; margin-top: 3px; color: var(--muted-2-color, #999); font-size: 11px; } -.aksrt-settings-modal .pwd-strength__text.level-weak { color: var(--color-error, #ef4444); } -.aksrt-settings-modal .pwd-strength__text.level-medium { color: var(--color-warning, #f59e0b); } -.aksrt-settings-modal .pwd-strength__text.level-strong { color: var(--color-success, #10b981); } .aksrt-settings-modal .aksrt-captcha-wrap { margin: 12px 0 8px; } @media (max-width: 768px) { .aksrt-modal-runtime.aksrt-user-settings-modal.aksrt-modal-runtime--bottom .aksrt-modal-runtime__dialog { max-height: calc(100dvh - env(safe-area-inset-bottom)); } - .aksrt-settings-modal .code-input-group { gap: 6px; } .aksrt-settings-modal .code-send-btn { min-width: 104px; } } diff --git a/assets/js/routes/user-center-auth.js b/assets/js/routes/user-center-auth.js index 27d7c1d9..bf712644 100644 --- a/assets/js/routes/user-center-auth.js +++ b/assets/js/routes/user-center-auth.js @@ -167,7 +167,7 @@ var submitLabel = button ? button.innerHTML : ''; if (button) { button.disabled = true; - button.innerHTML = '正在提交…'; + button.innerHTML = '正在提交…'; } toast('正在提交申请…', 'load', '', 'aksrt_identity_submit'); window.AksrtRequest.request(config.restBase, { diff --git a/assets/js/routes/user-center.js b/assets/js/routes/user-center.js index 2c1e8ab3..13a69a9f 100644 --- a/assets/js/routes/user-center.js +++ b/assets/js/routes/user-center.js @@ -349,7 +349,7 @@ } /* ==================== 个人信息保存 ==================== */ - + function initProfileForm(root) { root = root || document; @@ -718,7 +718,6 @@ var runtime = window.AksrtModalRuntime; var template = document.querySelector('template[data-aksrt-settings-modal-template="' + kind + '"]'); if (!runtime || typeof runtime.open !== 'function' || !template) return; - var title = kind === 'password' ? '修改密码' : (kind === 'email' ? '更改邮箱' : '绑定手机'); runtime.open({ html: template.innerHTML, kind: 'account-settings', @@ -727,252 +726,349 @@ onContent: function (_, session) { var shell = session && session.shell; if (!shell) return; - shell.setAttribute('aria-label', title); + var dialog = shell.querySelector('[data-account-dialog]'); + var title = dialog && dialog.querySelector('[data-account-password-title], .modal-colorful-header .text-center > div:last-child'); + if (title && title.textContent.trim()) shell.setAttribute('aria-label', title.textContent.trim()); var firstInput = shell.querySelector('input:not([type="hidden"]), textarea, select'); if (firstInput) firstInput.focus(); - bindModalFields(shell); - initBindPhone(shell); + bindDialog(shell); }, }); } - function bindModalFields(scope) { - if (!scope) return; - scope.querySelectorAll('.toggle-pwd').forEach(function(btn) { - if (btn.dataset.aksrtSettingsInitialized === 'true') return; - btn.dataset.aksrtSettingsInitialized = 'true'; - btn.addEventListener('click', function() { - var inp = scope.querySelector('#' + this.getAttribute('data-target')); - if (!inp) return; - inp.type = inp.type === 'password' ? 'text' : 'password'; - this.classList.toggle('is-visible'); - var icon = this.querySelector('i'); - if (icon) { - icon.classList.toggle('fa-eye', inp.type === 'password'); - icon.classList.toggle('fa-eye-slash', inp.type === 'text'); - } + function setStep(dialog, key) { + dialog.querySelectorAll('[data-account-step]').forEach(function (panel) { + panel.classList.toggle('is-active', panel.getAttribute('data-account-step') === key); + }); + var panels = Array.prototype.slice.call(dialog.querySelectorAll('[data-account-step]')); + var index = Math.max(0, panels.findIndex(function (panel) { return panel.getAttribute('data-account-step') === key; })); + dialog.querySelectorAll('.account-step-list li').forEach(function (item, itemIndex) { + item.classList.toggle('active', itemIndex === index); + item.classList.toggle('done', itemIndex < index); + }); + } + + function resetCountdown(button) { + if (!button) return; + if (button.__aksrtCountdownTimer) window.clearInterval(button.__aksrtCountdownTimer); + button.__aksrtCountdownTimer = null; + button.disabled = false; + button.classList.remove('is-counting'); + button.textContent = button.getAttribute('data-idle-label') || '发送验证码'; + } + + function startCountdown(button, seconds) { + var idleText = button.textContent; + var left = Number(seconds || 60); + if (button.__aksrtCountdownTimer) window.clearInterval(button.__aksrtCountdownTimer); + button.disabled = true; + button.classList.add('is-counting'); + button.setAttribute('data-idle-label', idleText); + button.textContent = left + 's'; + button.__aksrtCountdownTimer = window.setInterval(function () { + left -= 1; + if (left <= 0) { resetCountdown(button); return; } + button.textContent = left + 's'; + }, 1000); + } + + function settingsRestRoot() { + return String((window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || '/wp-json/aksrt/v1').replace(/\/$/, ''); + } + + function settingsRestNonce() { + return (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || cfg.restNonce || ''; + } + + function formHasRequiredValues(form) { + var required = form.querySelectorAll('[required]'); + for (var i = 0; i < required.length; i++) { + if (required[i].type === 'checkbox' && !required[i].checked) { + required[i].focus(); + notyf(i18n.agreeTerms || '请先同意相关协议', 'warning'); + return false; + } + if (required[i].type !== 'checkbox' && !String(required[i].value || '').trim()) { + required[i].focus(); + notyf(i18n.fillRequired || '请填写必填项', 'warning'); + return false; + } + } + return true; + } + + function sendEmailCode(button, form, scene) { + if (!form || button.disabled) return; + if (scene === 'change_email' && !String((form.querySelector('[name="new_email"]') || {}).value || '').trim()) { + notyf('请输入邮箱', 'warning'); + return; + } + runCaptcha('change_email', form).then(function (captchaPayload) { + if (captchaPayload === null) return null; + var data = new URLSearchParams(); + data.set('action', 'aksrt_send_account_email_code'); + data.set('_settings_nonce', settingsNonce); + data.set('scene', scene); + new FormData(form).forEach(function (value, key) { if (value !== '' && !data.has(key)) data.set(key, value); }); + Object.keys(captchaPayload || {}).forEach(function (key) { data.set(key, captchaPayload[key]); }); + button.disabled = true; + return fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString() }); + }).then(function (response) { return response ? response.json() : null; }).then(function (payload) { + if (!payload) return; + if (!payload.success) throw new Error((payload.data && payload.data.message) || '发送失败'); + notyf((payload.data && payload.data.message) || '验证码已发送', 'success'); + startCountdown(button, payload.data && payload.data.countdown); + }).catch(function (error) { + resetCountdown(button); + notyf(error.message || '发送失败', 'danger'); + }).finally(function () { resetCaptcha('change_email', form); }); + } + + function sendPhoneCode(button, form, stage) { + if (!form || button.disabled) return; + var phone = String((form.querySelector('[name="phone"]') || {}).value || '').trim(); + if (!phone) { notyf('请输入手机号', 'warning'); return; } + runCaptcha('bind_phone', form).then(function (captchaPayload) { + if (captchaPayload === null) return null; + var restRoot = settingsRestRoot(); + var nonce = settingsRestNonce(); + var request = fetch(restRoot + '/user/phone-code', { + method: 'POST', credentials: 'same-origin', + headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, + body: JSON.stringify(Object.assign({ phone: phone, stage: stage }, captchaPayload || {})) + }); + button.disabled = true; + return request; + }).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) { + if (!result) return; + if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '发送失败'); + notyf(result.payload.message || '验证码已发送', 'success'); + startCountdown(button, result.payload.data && result.payload.data.countdown); + }).catch(function (error) { + resetCountdown(button); + notyf(error.message || '发送失败', 'danger'); + }).finally(function () { resetCaptcha('bind_phone', form); }); + } + + function postAjaxForm(form, action, scene, success) { + if (!formHasRequiredValues(form)) return; + var button = form.querySelector('[type="submit"]'); + var label = button && button.querySelector('[data-aksrt-settings-submit-label]'); + var original = label ? label.textContent : ''; + if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); } + runCaptcha(scene, form).then(function (captchaPayload) { + if (captchaPayload === null) { + if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } + return null; + } + var data = new FormData(form); + data.set('action', action); + data.set('_settings_nonce', settingsNonce); + Object.keys(captchaPayload || {}).forEach(function (key) { data.set(key, captchaPayload[key]); }); + return fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: data }); + }).then(function (response) { return response ? response.json() : null; }).then(function (payload) { + if (!payload) return; + if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } + if (!payload.success) throw new Error((payload.data && payload.data.message) || '操作失败'); + notyf((payload.data && payload.data.message) || '操作成功', 'success'); + if (typeof success === 'function') success(payload); + }).catch(function (error) { + if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } + notyf(error.message || '操作失败', 'danger'); + }).finally(function () { resetCaptcha(scene, form); }); + } + + function postPhoneForm(form, endpoint, success) { + if (!formHasRequiredValues(form)) return; + var button = form.querySelector('[type="submit"]'); + var label = button && button.querySelector('[data-aksrt-settings-submit-label]'); + var original = label ? label.textContent : ''; + runCaptcha('bind_phone', form).then(function (captchaPayload) { + if (captchaPayload === null) return null; + var restRoot = settingsRestRoot(); + var nonce = settingsRestNonce(); + if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); } + var payload = {}; + new FormData(form).forEach(function (value, key) { payload[key] = value; }); + Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; }); + return fetch(restRoot + endpoint, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, body: JSON.stringify(payload) }); + }).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) { + if (!result) return; + if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '操作失败'); + if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } + notyf(result.payload.message || '操作成功', 'success'); + if (typeof success === 'function') success(result.payload); + }).catch(function (error) { + if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } + notyf(error.message || '操作失败', 'danger'); + }).finally(function () { resetCaptcha('bind_phone', form); }); + } + + function sendRecoveryCode(button, form) { + if (!form || button.disabled) return; + var target = String((form.querySelector('[name="target"]') || {}).value || '').trim(); + if (!target) { notyf('请输入邮箱或手机号', 'warning'); return; } + var dialog = form.closest('[data-account-dialog="password"]'); + button.disabled = true; + runCaptcha('lostpassword', form).then(function (captchaPayload) { + if (captchaPayload === null) { notyf('请先完成人机验证', 'warning'); resetCountdown(button); return null; } + var payload = { scene: 'resetpassword', target: target, channel: dialog ? (dialog.getAttribute('data-recovery-channel') || '') : '' }; + Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; }); + return fetch(settingsRestRoot() + '/auth/code', { + method: 'POST', credentials: 'same-origin', + headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() }, + body: JSON.stringify(payload) + }); + }).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) { + if (!result) return; + if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '发送失败'); + notyf((result.payload.data && result.payload.data.message) || result.payload.message || '验证码已发送', 'success'); + startCountdown(button, (result.payload.data && result.payload.data.countdown) || result.payload.countdown); + }).catch(function (error) { + resetCountdown(button); + notyf(error.message || '发送失败', 'danger'); + }).finally(function () { resetCaptcha('lostpassword', form); }); + } + + function postRecoveryForm(form) { + if (!formHasRequiredValues(form)) return; + var password = form.querySelector('[name="password"]'); + var confirm = form.querySelector('[name="password_confirm"]'); + if (password && confirm && password.value !== confirm.value) { notyf('两次密码输入不一致', 'warning'); confirm.focus(); return; } + var button = form.querySelector('[type="submit"]'); + var label = button && button.querySelector('[data-aksrt-settings-submit-label]'); + var original = label ? label.textContent : ''; + var dialog = form.closest('[data-account-dialog="password"]'); + if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); } + runCaptcha('lostpassword', form).then(function (captchaPayload) { + if (captchaPayload === null) { + if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } + return null; + } + var payload = {}; + new FormData(form).forEach(function (value, key) { payload[key] = value; }); + payload.channel = dialog ? (dialog.getAttribute('data-recovery-channel') || '') : ''; + Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; }); + return fetch(settingsRestRoot() + '/auth/reset-password', { + method: 'POST', credentials: 'same-origin', + headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() }, + body: JSON.stringify(payload) + }); + }).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) { + if (!result) return; + if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '重设密码失败'); + if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } + notyf(result.payload.message || (result.payload.data && result.payload.data.message) || '密码已重置', 'success'); + window.setTimeout(function () { + var redirect = result.payload.redirect || (result.payload.data && result.payload.data.redirect); + if (redirect) window.location.assign(redirect); + else window.location.reload(); + }, 700); + }).catch(function (error) { + if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } + notyf(error.message || '重设密码失败', 'danger'); + }).finally(function () { resetCaptcha('lostpassword', form); }); + } + + function bindDialog(shell) { + var dialog = shell.querySelector('[data-account-dialog]'); + if (!dialog || dialog.dataset.aksrtBound === 'true') return; + dialog.dataset.aksrtBound = 'true'; + dialog.querySelectorAll('.toggle-pwd').forEach(function (button) { + button.addEventListener('click', function () { + var input = dialog.querySelector('#' + button.getAttribute('data-target')); + if (!input) return; + input.type = input.type === 'password' ? 'text' : 'password'; + var icon = button.querySelector('i'); + if (icon) { icon.classList.toggle('fa-eye', input.type === 'password'); icon.classList.toggle('fa-eye-slash', input.type === 'text'); } }); }); - - var pInput = scope.querySelector('#pwd-new'); - var sBar = scope.querySelector('#pwd-strength-bar'); - var sTxt = scope.querySelector('#pwd-strength-text'); - if (pInput && sBar && sTxt) { - pInput.addEventListener('input', function() { - var v = this.value; - var fill = sBar.querySelector('.pwd-strength__fill'); - if (!v) { fill.className = 'pwd-strength__fill'; sTxt.className = 'pwd-strength__text'; sTxt.textContent = ''; return; } - var score = 0; - if (v.length >= 6) score++; - if (v.length >= 10) score++; - if (/[a-z]/.test(v) && /[A-Z]/.test(v)) score++; - if (/\d/.test(v)) score++; - if (/[^a-zA-Z0-9]/.test(v)) score++; - var level = score <= 1 ? 'weak' : (score <= 3 ? 'medium' : 'strong'); - var label = score <= 1 ? '弱' : (score <= 3 ? '中' : '强'); - fill.className = 'pwd-strength__fill level-' + level; - sTxt.className = 'pwd-strength__text level-' + level; - sTxt.textContent = '密码强度: ' + label; + var type = dialog.getAttribute('data-account-dialog'); + if (type === 'password') { + var recoveryTarget = dialog.querySelector('#pwd-recovery-target'); + if (recoveryTarget && dialog.dataset.recoveryTarget) recoveryTarget.value = dialog.dataset.recoveryTarget; + dialog.querySelectorAll('[data-account-recovery-open],[data-account-recovery-back]').forEach(function (button) { + button.addEventListener('click', function () { + var showRecovery = button.hasAttribute('data-account-recovery-open'); + dialog.querySelectorAll('[data-account-password-pane]').forEach(function (pane) { + pane.hidden = pane.getAttribute('data-account-password-pane') === 'recovery' ? !showRecovery : showRecovery; + }); + var title = dialog.querySelector('[data-account-password-title]'); + if (title) { + title.textContent = title.getAttribute(showRecovery ? 'data-recovery-label' : 'data-change-label') || title.textContent; + var shell = dialog.closest('.aksrt-user-settings-modal'); + if (shell) shell.setAttribute('aria-label', title.textContent); + } + var input = dialog.querySelector(showRecovery ? '#pwd-recovery-target' : '#pwd-old'); + if (input) input.focus(); + }); + }); + var passwordForm = dialog.querySelector('#pwd-form'); + if (passwordForm) passwordForm.addEventListener('submit', function (event) { + event.preventDefault(); + var password = passwordForm.querySelector('[name="password"]'); + var confirm = passwordForm.querySelector('[name="password2"]'); + if (password && confirm && password.value !== confirm.value) { notyf('两次密码输入不一致', 'warning'); confirm.focus(); return; } + postAjaxForm(passwordForm, 'aksrt_change_password', 'change_password', function () { window.setTimeout(function () { window.location.reload(); }, 700); }); + }); + var recoveryForm = dialog.querySelector('#pwd-recovery-form'); + var recoveryCodeButton = dialog.querySelector('[data-account-recovery-send]'); + if (recoveryCodeButton && recoveryForm) recoveryCodeButton.addEventListener('click', function () { sendRecoveryCode(recoveryCodeButton, recoveryForm); }); + if (recoveryForm) recoveryForm.addEventListener('submit', function (event) { event.preventDefault(); postRecoveryForm(recoveryForm); }); + } else if (type === 'email') { + dialog.querySelectorAll('.account-code-send').forEach(function (button) { + button.addEventListener('click', function () { sendEmailCode(button, button.closest('form'), button.getAttribute('data-account-code-scene')); }); + }); + var oldForm = dialog.querySelector('#email-old-verify-form'); + var newForm = dialog.querySelector('#email-new-form'); + if (oldForm) oldForm.addEventListener('submit', function (event) { + event.preventDefault(); + postAjaxForm(oldForm, 'aksrt_verify_account_email', 'change_email', function () { setStep(dialog, 'email-new'); }); + }); + if (newForm) newForm.addEventListener('submit', function (event) { + event.preventDefault(); + postAjaxForm(newForm, 'aksrt_change_email', 'change_email', function () { + if (dialog.querySelector('[data-account-step="email-success"]')) setStep(dialog, 'email-success'); + window.setTimeout(function () { window.location.reload(); }, 700); + }); + }); + } else if (type === 'phone') { + dialog.querySelectorAll('[data-aksrt-sms-trigger]').forEach(function (button) { + button.addEventListener('click', function () { sendPhoneCode(button, button.closest('form'), button.getAttribute('data-aksrt-sms-scene')); }); + }); + var phoneOldForm = dialog.querySelector('#phone-old-verify-form'); + var phoneNewForm = dialog.querySelector('#aksrt-bind-phone-form'); + if (phoneOldForm) phoneOldForm.addEventListener('submit', function (event) { + event.preventDefault(); + postPhoneForm(phoneOldForm, '/user/phone-verify', function () { setStep(dialog, 'phone-new'); }); + }); + if (phoneNewForm) phoneNewForm.addEventListener('submit', function (event) { + event.preventDefault(); + postPhoneForm(phoneNewForm, '/user/phone', function () { + if (dialog.querySelector('[data-account-step="phone-success"]')) setStep(dialog, 'phone-success'); + window.setTimeout(function () { window.location.reload(); }, 700); + }); }); } - bindSend('pwd-send-code', 'pwd-form', 'change_password', scope); - bindSend('email-send-code', 'email-form', 'change_email', scope); - submitForm('pwd-form', 'aksrt_change_password', scope); - submitForm('email-form', 'aksrt_change_email', scope); } function resetSettingsModal(shell) { if (!shell || !shell.classList.contains('aksrt-user-settings-modal')) return; - shell.dataset.aksrtSettingsModalClosed = 'true'; - var forms = shell.querySelectorAll('form'); - var sceneMap = { 'pwd-form': 'change_password', 'email-form': 'change_email', 'aksrt-bind-phone-form': 'bind_phone' }; - forms.forEach(function(form) { - form.reset(); - var scene = sceneMap[form.id]; - if (scene) resetCaptcha(scene, form); - form.querySelectorAll('[data-aksrt-countdown]').forEach(function(button) { - if (button.__aksrtCountdownTimer) window.clearInterval(button.__aksrtCountdownTimer); - button.__aksrtCountdownTimer = null; - button.disabled = false; - button.classList.remove('is-counting'); - button.textContent = button.getAttribute('data-idle-label') || button.textContent; - }); - }); - var bar = shell.querySelector('#pwd-strength-bar'); - var fill = bar && bar.querySelector('.pwd-strength__fill'); - var text = shell.querySelector('#pwd-strength-text'); - if (fill) fill.className = 'pwd-strength__fill'; - if (text) { text.textContent = ''; text.className = 'pwd-strength__text'; } + shell.querySelectorAll('.account-code-send,[data-aksrt-sms-trigger],[data-account-recovery-send]').forEach(resetCountdown); + shell.querySelectorAll('form').forEach(function (form) { form.reset(); }); } if (document.documentElement.dataset.aksrtSettingsModalCleanupInitialized !== 'true') { document.documentElement.dataset.aksrtSettingsModalCleanupInitialized = 'true'; - document.addEventListener('aksrt:modal:close', function(event) { - var shell = event.detail && event.detail.shell; - resetSettingsModal(shell); - }); + document.addEventListener('aksrt:modal:close', function (event) { resetSettingsModal(event.detail && event.detail.shell); }); } - root.querySelectorAll('[data-aksrt-settings-modal]').forEach(function(btn) { - if (btn.dataset.aksrtSettingsInitialized === 'true') return; - btn.dataset.aksrtSettingsInitialized = 'true'; - btn.addEventListener('click', function() { - openSettingsModal(this.getAttribute('data-aksrt-settings-modal')); - }); + root.querySelectorAll('[data-aksrt-settings-modal]').forEach(function (button) { + if (button.dataset.aksrtSettingsInitialized === 'true') return; + button.dataset.aksrtSettingsInitialized = 'true'; + button.addEventListener('click', function () { openSettingsModal(button.getAttribute('data-aksrt-settings-modal')); }); }); - - // 验证码发送 - function getSendBtnIdleText(btnId) { - if (btnId === 'pwd-send-code') return i18n.sendToCurrentEmail || '发送到当前邮箱'; - if (btnId === 'email-send-code') return i18n.sendToNewEmail || '发送到新邮箱'; - return i18n.sendCode || '发送验证码'; - } - - function bindSend(btnId, formId, scene, scope) { - var container = scope || document; - var b = container.querySelector ? container.querySelector('#' + btnId) : document.getElementById(btnId); - if (!b) return; - if (b.dataset.aksrtSettingsInitialized === 'true') return; - b.dataset.aksrtSettingsInitialized = 'true'; - b.addEventListener('click', function() { - if (this.classList.contains('is-counting')) return; - var self = this; - var form = container.querySelector ? container.querySelector('#' + formId) : document.getElementById(formId); - var toastId = scene === 'change_password' - ? 'aksrt-settings-password-code-toast' - : 'aksrt-settings-email-code-toast'; - runCaptcha(scene, form).then(function(captchaPayload) { - if (captchaPayload === null) return; - if (container.dataset && container.dataset.aksrtSettingsModalClosed === 'true') return; - self.disabled = true; - self.textContent = i18n.sending || '发送中...'; - notyf(i18n.sending || '正在发送验证码…', 'load', '', toastId); - var data = new URLSearchParams(); - data.set('action', 'aksrt_send_email_code'); - data.set('_settings_nonce', settingsNonce); - data.set('scene', scene || ''); - if (form) { - new FormData(form).forEach(function(value, key) { - if (data.has(key) || value === '' || value == null) return; - data.set(key, value); - }); - } - Object.keys(captchaPayload || {}).forEach(function(key) { data.set(key, captchaPayload[key]); }); - fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString() }) - .then(function(r) { return r.json(); }).then(function(res) { - if (res.success) { - notyf((res.data && res.data.message) || i18n.codeSent || '验证码已发送', 'success', 5, toastId); - if (container.dataset && container.dataset.aksrtSettingsModalClosed === 'true') return; - var sec = 60; - self.classList.add('is-counting'); - self.textContent = sec + 's 后重发'; - self.setAttribute('data-aksrt-countdown', ''); - self.setAttribute('data-idle-label', getSendBtnIdleText(btnId)); - var t = setInterval(function() { - sec--; - if (sec <= 0) { - clearInterval(t); - self.__aksrtCountdownTimer = null; - self.disabled = false; - self.classList.remove('is-counting'); - self.textContent = getSendBtnIdleText(btnId); - } else { - self.textContent = sec + 's 后重发'; - } - }, 1000); - self.__aksrtCountdownTimer = t; - } else { - notyf((res.data && res.data.message) || i18n.sendFailed || '发送失败', 'danger', 5, toastId); - self.disabled = false; - self.textContent = getSendBtnIdleText(btnId); - } - }) - .catch(function() { - notyf(i18n.networkError || '网络错误', 'danger', 5, toastId); - self.disabled = false; - self.textContent = getSendBtnIdleText(btnId); - }) - .finally(function() { - resetCaptcha(scene, form); - }); - }).catch(function() { - resetCaptcha(scene, form); - notyf('人机验证失败,请重试', 'danger', 5, toastId); - }); - }); - } - - // 表单提交 - function submitForm(fid, act, scope) { - var container = scope || document; - var fm = container.querySelector ? container.querySelector('#' + fid) : document.getElementById(fid); - if (!fm) return; - if (fm.dataset.aksrtSettingsInitialized === 'true') return; - fm.dataset.aksrtSettingsInitialized = 'true'; - fm.addEventListener('submit', function(e) { - e.preventDefault(); - var btn = fm.querySelector('[type=submit]'); - var submitLabel = btn && btn.querySelector('[data-aksrt-settings-submit-label]'); - var orig = submitLabel ? submitLabel.textContent : ''; - var toastId = fid === 'pwd-form' - ? 'aksrt-settings-password-toast' - : 'aksrt-settings-email-toast'; - var reqs = fm.querySelectorAll('[required]'); - for (var i = 0; i < reqs.length; i++) { - if (!reqs[i].value.trim()) { reqs[i].focus(); notyf(i18n.fillRequired || '请填写必填项', 'warning'); return; } - } - if (fid === 'pwd-form') { - var pwdNew = fm.querySelector('#pwd-new'); - var pwdConfirm = fm.querySelector('#pwd-new-confirm'); - if (pwdNew && pwdConfirm && pwdNew.value !== pwdConfirm.value) { - pwdConfirm.focus(); - notyf(i18n.passwordMismatch || '两次输入的新密码不一致', 'warning'); - return; - } - } - if (btn) { btn.disabled = true; setSettingsSubmitLabel(btn, i18n.submitting || '提交中...'); } - notyf(i18n.submitting || '正在提交…', 'load', '', toastId); - var scene = fid === 'pwd-form' ? 'change_password' : 'change_email'; - runCaptcha(scene, fm).then(function(captchaPayload) { - if (captchaPayload === null) { - if (btn) { btn.disabled = false; setSettingsSubmitLabel(btn, orig); } - return null; - } - if (container.dataset && container.dataset.aksrtSettingsModalClosed === 'true') return null; - var modalShell = fm.closest('.aksrt-user-settings-modal'); - if (modalShell && modalShell.dataset.aksrtSettingsModalClosed === 'true') return null; - var d = new FormData(fm); - d.set('action', act); - d.set('_settings_nonce', settingsNonce); - Object.keys(captchaPayload || {}).forEach(function(key) { d.set(key, captchaPayload[key]); }); - return fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: d }); - }) - .then(function(r) { return r ? r.json() : null; }) - .then(function(r) { - if (!r) return; - if (btn) { btn.disabled = false; setSettingsSubmitLabel(btn, orig); } - if (r.success) { - notyf((r.data && r.data.message) || '操作成功', 'success', 5, toastId); - var activeModal = window.AksrtModalRuntime && window.AksrtModalRuntime.getActive(); - if (activeModal && activeModal.classList.contains('aksrt-user-settings-modal') && activeModal.contains(fm)) { - window.AksrtModalRuntime.close('settings-submit'); - } - if (r.data && r.data.redirect_to) setTimeout(function() { window.location.href = r.data.redirect_to; }, 2500); - } else { - notyf((r.data && r.data.message) || i18n.operationFailed || '操作失败', 'danger', 5, toastId); - } - }) - .catch(function() { - if (btn) { btn.disabled = false; setSettingsSubmitLabel(btn, orig); } - notyf(i18n.networkError || '网络错误', 'danger', 5, toastId); - }) - .finally(function() { - resetCaptcha(scene, fm); - }); - }); - } - - root.querySelectorAll('.binding-item--linkable').forEach(function(link) { + root.querySelectorAll('.binding-item--linkable').forEach(function(link) { if (link.dataset.aksrtBindCaptchaInitialized === 'true') return; link.dataset.aksrtBindCaptchaInitialized = 'true'; link.addEventListener('click', function(event) { @@ -1134,145 +1230,9 @@ initNotifications(); initNotificationPreferences(); initSettings(); - initBindPhone(); initSettingsCallbackToast(); }); - // 绑定手机 - function initBindPhone(root) { - root = root || document; - var form = root.id === 'aksrt-bind-phone-form' ? root : root.querySelector('#aksrt-bind-phone-form'); - if (!form) return; - if (form.dataset.aksrtSettingsInitialized === 'true') return; - form.dataset.aksrtSettingsInitialized = 'true'; - function isValidSmsPhone(phone) { - if (!window.aksrtData || aksrtData.smsNumberLimitCh !== false) { - return /^1[3-9]\d{9}$/.test(phone); - } - return /^(?:\d{5,}|\+\d{6,}|\+?\d{2,}[-\s]\d{2,}[0-9\-\s]{0,15})$/.test(phone); - } - var sendButton = form.querySelector('[data-aksrt-sms-trigger]'); - if (sendButton) sendButton.addEventListener('click', function () { - var phone = String((form.querySelector('[name="phone"]') || {}).value || '').trim(); - if (!isValidSmsPhone(phone)) { notyf('请输入正确的手机号', 'warning'); return; } - var toastId = 'aksrt-settings-phone-code-toast'; - var restRoot = (window.aksrtData && aksrtData.apiUrl) || '/wp-json/aksrt/v1'; - var nonce = (window.aksrtData && aksrtData.restNonce) || ''; - sendButton.disabled = true; - notyf('正在发送验证码…', 'load', '', toastId); - runCaptcha('bind_phone', form).then(function(captchaPayload) { - if (captchaPayload === null) { - sendButton.disabled = false; - return null; - } - var modalShell = form.closest('.aksrt-user-settings-modal'); - if (modalShell && modalShell.dataset.aksrtSettingsModalClosed === 'true') return null; - return fetch(String(restRoot).replace(/\/$/, '') + '/user/phone-code', { - method: 'POST', credentials: 'same-origin', - headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, - body: JSON.stringify(Object.assign({ phone: phone }, captchaPayload || {})) - }); - }).then(function (r) { return r ? r.json().then(function (j) { return { ok: r.ok, json: j }; }) : null; }).then(function (res) { - if (!res) return; - if (!res.ok || !res.json.success) throw new Error(res.json.message || '发送失败'); - notyf(res.json.message || '验证码已发送', 'success', 5, toastId); - var modalShell = form.closest('.aksrt-user-settings-modal'); - if (modalShell && modalShell.dataset.aksrtSettingsModalClosed === 'true') return; - var left = Number((res.json.data && res.json.data.countdown) || 60); - sendButton.setAttribute('data-aksrt-countdown', ''); - sendButton.setAttribute('data-idle-label', '获取验证码'); - sendButton.classList.add('is-counting'); - var timer = setInterval(function () { - left -= 1; - sendButton.textContent = left + 's'; - if (left <= 0) { - clearInterval(timer); - sendButton.__aksrtCountdownTimer = null; - sendButton.disabled = false; - sendButton.classList.remove('is-counting'); - sendButton.textContent = sendButton.getAttribute('data-idle-label'); - } - }, 1000); - sendButton.__aksrtCountdownTimer = timer; - }).catch(function (error) { sendButton.disabled = false; notyf(error.message || '发送失败', 'danger', 5, toastId); }) - .finally(function () { resetCaptcha('bind_phone', form); }); - }); - form.addEventListener('submit', function (e) { - e.preventDefault(); - var phoneEl = form.querySelector('[name="phone"]'); - var codeEl = form.querySelector('[name="code"]'); - var phone = phoneEl ? String(phoneEl.value || '').trim() : ''; - var code = codeEl ? String(codeEl.value || '').trim() : ''; - if (!isValidSmsPhone(phone)) { - notyf('请输入正确的手机号', 'warning'); - return; - } - if (!/^\d{6}$/.test(code)) { - notyf('请输入短信验证码', 'warning'); - return; - } - var submitBtn = form.querySelector('[type="submit"]'); - var submitLabel = submitBtn && submitBtn.querySelector('[data-aksrt-settings-submit-label]'); - var submitIdleText = submitLabel ? submitLabel.textContent : ''; - if (submitBtn) { - submitBtn.disabled = true; - setSettingsSubmitLabel(submitBtn, '绑定中...'); - } - function doBind(captcha) { - var toastId = 'aksrt-settings-phone-bind-toast'; - notyf('正在绑定手机…', 'load', '', toastId); - var restRoot = (window.aksrtData && aksrtData.apiUrl) || '/wp-json/aksrt/v1'; - var nonce = (window.aksrtData && aksrtData.restNonce) || ''; - return fetch(String(restRoot).replace(/\/$/, '') + '/user/phone', { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - 'X-WP-Nonce': nonce - }, - body: JSON.stringify(Object.assign({ phone: phone, code: code }, captcha)) - }) - .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); }) - .then(function (res) { - if (res.ok && res.json && res.json.success) { - notyf(res.json.message || '绑定成功', 'success', 5, toastId); - var activeModal = window.AksrtModalRuntime && window.AksrtModalRuntime.getActive(); - if (activeModal && activeModal.classList.contains('aksrt-user-settings-modal') && activeModal.contains(form)) { - window.AksrtModalRuntime.close('settings-submit'); - } - setTimeout(function () { window.location.reload(); }, 600); - return; - } - notyf((res.json && (res.json.message || res.json.error)) || '绑定失败', 'danger', 5, toastId); - if (submitBtn) { - submitBtn.disabled = false; - setSettingsSubmitLabel(submitBtn, submitIdleText); - } - }) - .catch(function () { - notyf('网络错误', 'danger', 5, toastId); - if (submitBtn) { - submitBtn.disabled = false; - setSettingsSubmitLabel(submitBtn, submitIdleText); - } - }); - } - runCaptcha('bind_phone', form).then(function(captchaPayload) { - if (captchaPayload === null) { - if (submitBtn) { - submitBtn.disabled = false; - setSettingsSubmitLabel(submitBtn, submitIdleText); - } - return null; - } - var modalShell = form.closest('.aksrt-user-settings-modal'); - if (modalShell && modalShell.dataset.aksrtSettingsModalClosed === 'true') return null; - return doBind(captchaPayload); - }).finally(function() { - resetCaptcha('bind_phone', form); - }); - }); - } })(); diff --git a/inc/helpers/user-center-renderers/profile-auth.php b/inc/helpers/user-center-renderers/profile-auth.php index 44188e3a..129ebfbd 100644 --- a/inc/helpers/user-center-renderers/profile-auth.php +++ b/inc/helpers/user-center-renderers/profile-auth.php @@ -81,7 +81,7 @@ $config = [
diff --git a/inc/helpers/user-center-renderers/settings-account.php b/inc/helpers/user-center-renderers/settings-account.php index 1bf58e22..2be64f02 100644 --- a/inc/helpers/user-center-renderers/settings-account.php +++ b/inc/helpers/user-center-renderers/settings-account.php @@ -1,25 +1,29 @@ ID : (int) ($user_id ?? 0); $account_email = $account_user instanceof WP_User ? (string) $account_user->user_email : ''; $account_phone = $account_user_id > 0 ? (string) get_user_meta($account_user_id, 'phone_number', true) : ''; -$user_settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : []; -$sms_settings = class_exists('\\AKSRT\\Sms\\SMS') ? \AKSRT\Sms\SMS::get_instance()->get_provider_config() : []; +$account_oauth_new = $account_user_id > 0 && get_user_meta($account_user_id, 'oauth_new', true); +$user_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : []; +$sms_settings = class_exists('\AKSRT\Sms\SMS') ? \AKSRT\Sms\SMS::get_instance()->get_provider_config() : []; $phone_available = !empty($user_settings['bind_phone']) && (($sms_settings['sms_sdk'] ?? 'null') !== 'null'); -$phone_visible = $account_phone !== '' || $phone_available; -$phone_label = $account_phone !== '' ? $account_phone : __('暂未绑定', 'aksrt'); -$email_action = $account_email !== '' ? __('修改', 'aksrt') : __('绑定', 'aksrt'); -$phone_action = $account_phone !== '' ? __('修改', 'aksrt') : __('绑定', 'aksrt'); +$agreement = function_exists('aksrt_get_auth_agreement_view') ? aksrt_get_auth_agreement_view() : ['enabled' => false, 'required' => false, 'label_html' => '']; +$email_verify_required = $account_email !== '' && !empty($user_settings['email_set_captch']); +$reset_channel = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::reset_password_verification_type() : 'email'; +$sign_in_available = !class_exists('\\AKSRT\\Settings\\Settings') || (!\AKSRT\Settings\Settings::close_sign() && !\AKSRT\Settings\Settings::close_signin()); +$can_recover_password = $sign_in_available && ($reset_channel === 'email_phone' + ? ($account_email !== '' || $account_phone !== '') + : ($reset_channel === 'phone' ? $account_phone !== '' : $account_email !== '')); +$recovery_target = $reset_channel === 'phone' ? $account_phone : ($account_email !== '' ? $account_email : $account_phone); +$recovery_channel = $reset_channel === 'email_phone' ? '' : $reset_channel; $render_row = static function (string $icon, string $title, string $value, string $action, string $modal, string $icon_tone, string $action_tone, bool $interactive = true): void { ?> @@ -28,116 +32,122 @@ $render_row = static function (string $icon, string $title, string $value, strin - - - + - - - - + + + + + +