From 95553086ddebb5d30eb94eda173432136aa6709f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 18 Aug 2026 03:48:50 +0800 Subject: [PATCH] (fix):update user setting toast --- assets/js/routes/user-center.js | 132 +++++++++++++----- assets/js/sign-register.js | 27 +++- inc/helpers/aksrt-user-center.php | 5 +- .../profile-settings.php | 1 + templates/pages/page-profile.php | 1 - .../parts/user-center/account-modals.php | 5 - tests/widget-system/toast-contract.test.cjs | 37 +++++ 7 files changed, 156 insertions(+), 52 deletions(-) delete mode 100644 templates/parts/user-center/account-modals.php diff --git a/assets/js/routes/user-center.js b/assets/js/routes/user-center.js index d7554048..51e12e7e 100644 --- a/assets/js/routes/user-center.js +++ b/assets/js/routes/user-center.js @@ -572,12 +572,14 @@ } /* ==================== 账号设置:弹窗 / 密码 / 邮箱 / 绑定 ==================== */ - function initSettings() { + function initSettings(root) { if (!ajaxUrl || !settingsNonce) return; + root = root || document; function openModal(id) { var m = document.getElementById(id); if (!m) return; + m.hidden = false; m.classList.add('is-open'); document.body.style.overflow = 'hidden'; var firstInput = m.querySelector('input:not([type=hidden])'); @@ -603,23 +605,33 @@ if (scene && store && store[scene] && typeof store[scene].reset === 'function') { store[scene].reset(); } + if (m.id === 'phone-modal') m.hidden = true; } - document.querySelectorAll('.security-trigger').forEach(function(btn) { + root.querySelectorAll('.security-trigger').forEach(function(btn) { + if (btn.dataset.aksrtSettingsInitialized === 'true') return; + btn.dataset.aksrtSettingsInitialized = 'true'; btn.addEventListener('click', function() { openModal(this.getAttribute('data-modal')); }); }); - document.querySelectorAll('[data-close-modal]').forEach(function(el) { + root.querySelectorAll('[data-close-modal], [data-modal-close]').forEach(function(el) { + if (el.dataset.aksrtSettingsInitialized === 'true') return; + el.dataset.aksrtSettingsInitialized = 'true'; el.addEventListener('click', function() { closeModal(this.closest('.aksrt-modal')); }); }); - document.addEventListener('keydown', function(e) { - if (e.key === 'Escape') { - var openM = document.querySelector('.aksrt-modal.is-open'); - if (openM) closeModal(openM); - } - }); + if (document.documentElement.dataset.aksrtSettingsKeyboardInitialized !== 'true') { + document.documentElement.dataset.aksrtSettingsKeyboardInitialized = 'true'; + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + var openM = document.querySelector('.aksrt-modal.is-open'); + if (openM) closeModal(openM); + } + }); + } // 密码显隐切换 - document.querySelectorAll('#pwd-modal .toggle-pwd, #email-modal .toggle-pwd').forEach(function(btn) { + root.querySelectorAll('#pwd-modal .toggle-pwd, #email-modal .toggle-pwd').forEach(function(btn) { + if (btn.dataset.aksrtSettingsInitialized === 'true') return; + btn.dataset.aksrtSettingsInitialized = 'true'; btn.addEventListener('click', function() { var inp = document.getElementById(this.getAttribute('data-target')); if (!inp) return; @@ -632,7 +644,8 @@ var pInput = document.getElementById('pwd-new'); var sBar = document.getElementById('pwd-strength-bar'); var sTxt = document.getElementById('pwd-strength-text'); - if (pInput && sBar && sTxt) { + if (pInput && sBar && sTxt && pInput.dataset.aksrtSettingsInitialized !== 'true') { + pInput.dataset.aksrtSettingsInitialized = 'true'; pInput.addEventListener('input', function() { var v = this.value; var f = sBar.querySelector('.pwd-strength__fill'); @@ -673,13 +686,19 @@ function bindSend(btnId, formId, scene) { var b = 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 = document.getElementById(formId); + var toastId = scene === 'change_password' + ? 'aksrt-settings-password-code-toast' + : 'aksrt-settings-email-code-toast'; triggerSceneCaptcha(scene, function() { 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); @@ -690,10 +709,10 @@ data.set(key, value); }); } - fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString() }) + 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.message || (i18n.codeSent || '验证码已发送')); + notyf((res.data && res.data.message) || i18n.codeSent || '验证码已发送', 'success', 5, toastId); var sec = 60; self.classList.add('is-counting'); self.textContent = sec + 's 后重发'; @@ -709,13 +728,13 @@ } }, 1000); } else { - notyf(res.data.message || (i18n.sendFailed || '发送失败'), 'danger'); + notyf((res.data && res.data.message) || i18n.sendFailed || '发送失败', 'danger', 5, toastId); self.disabled = false; self.textContent = getSendBtnIdleText(btnId); } }) .catch(function() { - notyf(i18n.networkError || '网络错误', 'danger'); + notyf(i18n.networkError || '网络错误', 'danger', 5, toastId); self.disabled = false; self.textContent = getSendBtnIdleText(btnId); }); @@ -730,10 +749,15 @@ function submitForm(fid, act) { var fm = 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 orig = btn ? btn.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; } @@ -748,24 +772,25 @@ } } if (btn) { btn.disabled = true; btn.textContent = i18n.submitting || '提交中...'; } + notyf(i18n.submitting || '正在提交…', 'load', '', toastId); var d = new FormData(fm); - d.append('action', act); - d.append('_settings_nonce', settingsNonce); - fetch(ajaxUrl, { method: 'POST', body: d }) + d.set('action', act); + d.set('_settings_nonce', settingsNonce); + fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: d }) .then(function(r) { return r.json(); }) .then(function(r) { if (btn) { btn.disabled = false; btn.textContent = orig; } if (r.success) { - notyf(r.data.message || ''); + notyf((r.data && r.data.message) || '操作成功', 'success', 5, toastId); closeModal(fm.closest('.aksrt-modal')); - if (r.data.redirect_to) setTimeout(function() { window.location.href = r.data.redirect_to; }, 2500); + if (r.data && r.data.redirect_to) setTimeout(function() { window.location.href = r.data.redirect_to; }, 2500); } else { - notyf(r.data.message || (i18n.operationFailed || '操作失败'), 'danger'); + notyf((r.data && r.data.message) || i18n.operationFailed || '操作失败', 'danger', 5, toastId); } }) .catch(function() { if (btn) { btn.disabled = false; btn.textContent = orig; } - notyf(i18n.networkError || '网络错误', 'danger'); + notyf(i18n.networkError || '网络错误', 'danger', 5, toastId); }); }); } @@ -774,7 +799,9 @@ submitForm('email-form', 'aksrt_change_email'); // 解绑 - document.querySelectorAll('.binding-unbind-btn').forEach(function(b) { + root.querySelectorAll('.binding-unbind-btn').forEach(function(b) { + if (b.dataset.aksrtSettingsInitialized === 'true') return; + b.dataset.aksrtSettingsInitialized = 'true'; b.addEventListener('click', function() { var p = this.getAttribute('data-provider'); if (!p) return; @@ -782,6 +809,8 @@ var self = b; self.disabled = true; self.textContent = i18n.unbinding || '解除中...'; + var toastId = 'aksrt-settings-oauth-unbind-toast'; + notyf(i18n.unbinding || '正在解除绑定…', 'load', '', toastId); var unbindNonce = document.getElementById('aksrt-oauth-unbind-nonce'); var data = new URLSearchParams(); data.set('action', 'aksrt_oauth_unbind'); @@ -790,27 +819,25 @@ data.set('userId', String(userId)); 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(d) { if (d.success) { - notyf(i18n.unbindSuccess || '解除成功'); + notyf((d.data && d.data.message) || i18n.unbindSuccess || '解除成功', 'success', 5, toastId); setTimeout(function() { - var u = new URL(window.location); - u.searchParams.set('tab', 'settings'); - u.searchParams.set('oauth_unbound', p); - window.location.href = u.toString(); + window.location.href = tabUrls.settings || new URL('/user/settings/', window.location.origin).toString(); }, 1000); } else { - notyf(i18n.unbindFailed || '解除失败', 'danger'); + notyf((d.data && d.data.message) || i18n.unbindFailed || '解除失败', 'danger', 5, toastId); self.disabled = false; self.textContent = i18n.unbind || '解除'; } }) .catch(function() { - notyf(i18n.networkError || '网络错误', 'danger'); + notyf(i18n.networkError || '网络错误', 'danger', 5, toastId); self.disabled = false; self.textContent = i18n.unbind || '解除'; }); @@ -818,6 +845,30 @@ }); } + function initSettingsCallbackToast() { + if (document.documentElement.dataset.aksrtSettingsCallbackInitialized === 'true') return; + document.documentElement.dataset.aksrtSettingsCallbackInitialized = 'true'; + var currentUrl = new URL(window.location.href); + var provider = currentUrl.searchParams.get('oauth_bound'); + var isBindingCallback = currentUrl.searchParams.get('oauth_action') === 'bind' + || currentUrl.searchParams.get('tab') === 'settings' + || /\/user\/settings\/?$/.test(currentUrl.pathname); + if (!provider || !isBindingCallback) return; + + notyf(i18n.bindSuccess || '绑定成功', 'success'); + if (window.history.replaceState) { + var cleanUrl = new URL(tabUrls.settings || '/user/settings/', window.location.origin); + window.history.replaceState({}, '', cleanUrl.pathname + cleanUrl.search + cleanUrl.hash); + } + } + + if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') { + window.AksrtFrontend.register('aksrt-settings', function(root) { + initSettings(root); + initBindPhone(root); + }); + } + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { @@ -828,6 +879,8 @@ initPostDeletion(); initNotifications(); initSettings(); + initBindPhone(); + initSettingsCallbackToast(); }); } else { initDashboardNavigation(); @@ -837,12 +890,17 @@ initPostDeletion(); initNotifications(); initSettings(); + initBindPhone(); + initSettingsCallbackToast(); } // 绑定手机 - (function initBindPhone() { - var form = document.getElementById('aksrt-bind-phone-form'); + 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'; form.addEventListener('submit', function (e) { e.preventDefault(); var phoneEl = form.querySelector('[name="phone"]'); @@ -883,6 +941,8 @@ } catch (err) {} } function doBind() { + var toastId = 'aksrt-settings-phone-bind-toast'; + notyf('正在绑定手机…', 'load', '', toastId); var captcha = collectCaptcha(form); var restRoot = (window.aksrtData && aksrtData.apiUrl) || '/wp-json/aksrt/v1'; var nonce = (window.aksrtData && aksrtData.restNonce) || ''; @@ -898,12 +958,12 @@ .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) || '绑定成功'); + notyf(res.json.message || '绑定成功', 'success', 5, toastId); setTimeout(function () { window.location.reload(); }, 600); return; } resetCaptcha(); - notyf((res.json && (res.json.message || res.json.error)) || '绑定失败', 'danger'); + notyf((res.json && (res.json.message || res.json.error)) || '绑定失败', 'danger', 5, toastId); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '确认绑定'; @@ -911,7 +971,7 @@ }) .catch(function () { resetCaptcha(); - notyf('网络错误', 'danger'); + notyf('网络错误', 'danger', 5, toastId); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '确认绑定'; @@ -925,6 +985,6 @@ doBind(); } }); - })(); + } })(); diff --git a/assets/js/sign-register.js b/assets/js/sign-register.js index 46ac6b82..fdf2daa4 100644 --- a/assets/js/sign-register.js +++ b/assets/js/sign-register.js @@ -30,8 +30,11 @@ * SMS 验证码发送 + 倒计时 * ================================================================= */ - function initSmsButtons() { - document.querySelectorAll('[data-aksrt-sms-trigger]').forEach(btn => { + 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) // 恢复页面刷新后的倒计时 @@ -78,26 +81,32 @@ 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) - const scene = btn.dataset.aksrtSmsScene || 'register' 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') + 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') + notyf(msg, 'danger', 5, toastId) try { const captchaState = window.AKSRTCaptchaState if (captchaState && captchaState.scenes && captchaState.scenes[scene]) { @@ -115,7 +124,7 @@ } }) .catch(() => { - notyf('网络错误,请重试', 'danger') + notyf('网络错误,请重试', 'danger', 5, toastId) btn.disabled = false btn.textContent = original }) @@ -397,6 +406,12 @@ } // 初始化 + 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 { diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php index 71408b3d..9f791ab2 100644 --- a/inc/helpers/aksrt-user-center.php +++ b/inc/helpers/aksrt-user-center.php @@ -181,7 +181,6 @@ if (!function_exists('aksrt_capture_user_center_view')) { 'settings' => 'settings', 'header' => 'header', 'sidebar' => 'sidebar', - 'account-modals' => 'account-modals', ]; $view = sanitize_file_name($view); $renderer = $map[$view] ?? $view; @@ -262,9 +261,6 @@ if (!function_exists('aksrt_build_user_center_render_context')) { ]; } - $context['account_modals_html'] = 'settings' === $active_tab - ? aksrt_capture_user_center_view('account-modals', $context) - : ''; return $context; } } @@ -371,6 +367,7 @@ if (!function_exists('aksrt_user_center_runtime_config')) { 'unbindSuccess' => __('解除成功', 'aksrt'), 'unbindFailed' => __('解除失败', 'aksrt'), 'unbind' => __('解除', 'aksrt'), + 'bindSuccess' => __('绑定成功', 'aksrt'), ], ]; } diff --git a/inc/helpers/user-center-renderers/profile-settings.php b/inc/helpers/user-center-renderers/profile-settings.php index bff7ded6..209fca0e 100644 --- a/inc/helpers/user-center-renderers/profile-settings.php +++ b/inc/helpers/user-center-renderers/profile-settings.php @@ -14,3 +14,4 @@ $dir = __DIR__; require $dir . '/settings-bindings.php'; require $dir . '/settings-security.php'; require $dir . '/settings-runtime-fields.php'; +require $dir . '/profile-account-modals.php'; diff --git a/templates/pages/page-profile.php b/templates/pages/page-profile.php index cc6cc111..2d296b7f 100644 --- a/templates/pages/page-profile.php +++ b/templates/pages/page-profile.php @@ -37,7 +37,6 @@ get_header(); $panel]); ?> - $render_context]); ?> diff --git a/templates/parts/user-center/account-modals.php b/templates/parts/user-center/account-modals.php deleted file mode 100644 index 25331fde..00000000 --- a/templates/parts/user-center/account-modals.php +++ /dev/null @@ -1,5 +0,0 @@ - { + const userCenter = read('assets/js/routes/user-center.js'); + const signRegister = read('assets/js/sign-register.js'); + const settings = read('inc/helpers/user-center-renderers/profile-settings.php'); + const context = read('inc/helpers/aksrt-user-center.php'); + const page = read('templates/pages/page-profile.php'); + + [ + 'aksrt-settings-password-code-toast', + 'aksrt-settings-email-code-toast', + 'aksrt-settings-password-toast', + 'aksrt-settings-email-toast', + 'aksrt-settings-oauth-unbind-toast', + 'aksrt-settings-phone-bind-toast', + ].forEach((id) => assert.match(userCenter, new RegExp("['\"]" + id + "['\"]"))); + + assert.match(userCenter, /notyf\([^;]+?'load'[^;]+?toastId\);/); + assert.match(userCenter, /notyf\([^;]+?'success'[^;]+?toastId\);/); + assert.match(userCenter, /notyf\([^;]+?'danger'[^;]+?toastId\);/); + assert.match(userCenter, /register\('aksrt-settings',[\s\S]*?initSettings\(root\);[\s\S]*?initBindPhone\(root\);/); + assert.match(userCenter, /aksrtSettingsInitialized/); + assert.match(userCenter, /oauth_bound[\s\S]*?notyf\([^;]+?'success'\)[\s\S]*?history\.replaceState/); + assert.doesNotMatch(userCenter, /oauth_unbound/); + + assert.match(signRegister, /scene === 'bind_phone'[\s\S]*?'aksrt-settings-phone-code-toast'/); + assert.match(signRegister, /notyf\('正在发送验证码…', 'load', '', toastId\)/); + assert.match(signRegister, /notyf\([^;]+?'success', 5, toastId\)/); + assert.match(signRegister, /notyf\([^;]+?'danger', 5, toastId\)/); + assert.match(signRegister, /register\('aksrt-sms-buttons',[\s\S]*?initSmsButtons\(root\)/); + assert.match(signRegister, /aksrtSmsInitialized/); + + assert.match(settings, /require \$dir \. '\/profile-account-modals\.php';/); + assert.doesNotMatch(context, /account_modals_html|'account-modals'/); + assert.doesNotMatch(page, /user-center\/account-modals/); + assert.equal(fs.existsSync(path.join(root, 'templates/parts/user-center/account-modals.php')), false); +}); + test('legacy frontend toast APIs and selectors are fully removed', () => { const files = [ 'assets/js',