/** * AKSRT User Center — 本人中心专属脚本 * * 从 page-profile.php 内联 JS 迁移而来。 * 依赖全局 aksrtUserCenter 配置对象(由模板注入)。 * * @package AKSRT */ /* global aksrtUserCenter, notyf */ (function() { 'use strict'; var cfg = window.aksrtUserCenter || {}; var ajaxUrl = cfg.ajaxUrl || ''; var publicNonce = cfg.publicNonce || ''; var settingsNonce = cfg.settingsNonce || ''; var submitNonce = cfg.submitNonce || ''; var userId = cfg.userId || 0; var i18n = cfg.i18n || {}; var tabAliases = cfg.tabAliases || {}; var tabUrls = cfg.tabUrls || {}; function captchaAvailable(scene, scope) { var captcha = window.AKSRTCaptcha; if (!captcha) return false; var element = scope && scope.nodeType ? scope : null; if (element && element.matches && element.matches('[data-aksrt-captcha-config][data-scene="' + scene + '"]')) return true; if (element && element.querySelector && element.querySelector('[data-aksrt-captcha-config][data-scene="' + scene + '"]')) return true; var mount = element && element.closest ? element.closest('[data-aksrt-captcha-config][data-scene="' + scene + '"]') : null; return !!mount; } function runCaptcha(scene, scope) { return window.AKSRTCaptcha ? window.AKSRTCaptcha.run(scene, scope) : Promise.resolve({}); } function resetCaptcha(scene, scope) { if (window.AKSRTCaptcha) window.AKSRTCaptcha.reset(scene, scope); } /* ==================== Dashboard 导航 ==================== */ function initDashboardNavigation() { if (document.documentElement.dataset.aksrtUserCenterNavigationInitialized === 'true') return; var tabContainer = document.querySelector('.uc-mobile-tabs'); if (!tabContainer) return; document.documentElement.dataset.aksrtUserCenterNavigationInitialized = 'true'; var tabs = tabContainer.querySelectorAll('[data-tab]'); var sidebarBtns = document.querySelectorAll('.uc-sidebar [data-tab]'); var panels = {}; var defaultTab = cfg.defaultTab || 'profile'; var drawerTrigger = document.getElementById('uc-drawer-trigger'); var drawerOverlay = document.getElementById('uc-drawer-overlay'); var sidebar = document.querySelector('.uc-sidebar'); tabs.forEach(function(tab) { var name = tab.getAttribute('data-tab'); var panel = document.getElementById('panel-' + name); if (panel) { panels[name] = { tab: tab, panel: panel }; } }); function openDrawer() { if (sidebar) sidebar.classList.add('is-open'); if (drawerOverlay) drawerOverlay.classList.add('is-visible'); document.body.style.overflow = 'hidden'; } function closeDrawer() { if (sidebar) sidebar.classList.remove('is-open'); if (drawerOverlay) drawerOverlay.classList.remove('is-visible'); document.body.style.overflow = ''; } function switchTo(name) { if (!panels[name]) name = defaultTab; Object.keys(panels).forEach(function(key) { var active = key === name; panels[key].tab.classList.toggle('is-active', active); panels[key].tab.setAttribute('aria-selected', active ? 'true' : 'false'); panels[key].panel.classList.toggle('is-active', active); panels[key].panel.hidden = !active; }); sidebarBtns.forEach(function(btn) { btn.classList.toggle('is-active', btn.getAttribute('data-tab') === name); }); closeDrawer(); } function loadPanel(name, forceLoad, queryParams) { if (!panels[name]) return Promise.reject(new Error('panel-not-found')); var target = panels[name].panel; if (!forceLoad && target.dataset.panelLoaded === 'true') return Promise.resolve(); if (target._aksrtPanelRequest) return target._aksrtPanelRequest; var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || ''; if (!restUrl) return Promise.reject(new Error('rest-unavailable')); if (name === 'orders' && (!window.AKSRTOrderCenter || typeof window.AKSRTOrderCenter.init !== 'function')) { return Promise.reject(new Error('orders-initializer-unavailable')); } target.classList.add('is-loading'); var url = restUrl.replace(/\/$/, '') + '/user-center/panel/' + encodeURIComponent(name); if (name === 'creator' || name === 'revenue' || name === 'posts' || name === 'follow') { var params = queryParams || new URLSearchParams(window.location.search); var allowedParams = []; if (name === 'creator') allowedParams = ['income_page']; if (name === 'revenue') allowedParams = ['reward_tab', 'commission_page', 'user_page']; if (name === 'posts') allowedParams = ['post_status', 'posts_page']; if (name === 'follow') allowedParams = ['follow_list', 'follow_page']; allowedParams.forEach(function(key) { if (params.get(key)) url += (url.indexOf('?') === -1 ? '?' : '&') + key + '=' + encodeURIComponent(params.get(key)); }); } target._aksrtPanelRequest = fetch(url, { credentials: 'same-origin', headers: { 'X-WP-Nonce': (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || '' } }).then(function(response) { return response.json().then(function(payload) { if (!response.ok || !payload || !payload.success || !payload.data || typeof payload.data.html !== 'string') throw new Error('panel-load-failed'); if (name === 'orders' && window.AKSRTOrderCenter && typeof window.AKSRTOrderCenter.destroy === 'function') { window.AKSRTOrderCenter.destroy(target); } target.innerHTML = payload.data.html; if (window.AksrtFrontend && typeof window.AksrtFrontend.rehydrate === 'function') { window.AksrtFrontend.rehydrate(target, 'ajax'); } target.dataset.panelLoaded = 'true'; }); }).finally(function() { target._aksrtPanelRequest = null; target.classList.remove('is-loading'); }); return target._aksrtPanelRequest; } /* Revenue tabs are injected with the panel HTML. Handle them in the * capture phase so they never fall through to a browser navigation. */ function loadRevenueTab(queryParams) { var target = document.getElementById('panel-revenue'); var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || ''; if (!target || !restUrl) return Promise.reject(new Error('revenue-panel-unavailable')); var params = queryParams || new URLSearchParams(); var url = restUrl.replace(/\/$/, '') + '/user-center/panel/revenue'; params.forEach(function(value, key) { if (key === 'reward_tab' || key === 'commission_page' || key === 'user_page') { url += (url.indexOf('?') === -1 ? '?' : '&') + key + '=' + encodeURIComponent(value); } }); target.classList.add('is-loading'); return fetch(url, { credentials: 'same-origin', headers: { 'X-WP-Nonce': (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || '' } }).then(function(response) { return response.json().then(function(payload) { if (!response.ok || !payload || !payload.success || !payload.data || typeof payload.data.html !== 'string') { throw new Error('revenue-tab-load-failed'); } target.innerHTML = payload.data.html; target.dataset.panelLoaded = 'true'; if (window.AksrtFrontend && typeof window.AksrtFrontend.rehydrate === 'function') { window.AksrtFrontend.rehydrate(target, 'ajax'); } }); }).finally(function() { target.classList.remove('is-loading'); }); } function activate(name, updateHistory, forceLoad, queryParams) { if (!panels[name]) name = defaultTab; // VIP membership may change outside this page, so never reuse stale HTML. if (name === 'vip' && !forceLoad) forceLoad = true; return loadPanel(name, forceLoad, queryParams).then(function() { switchTo(name); if (updateHistory) updateUrl(name); if (name === 'revenue' && typeof window.AKSRTRevenueCenterInit === 'function') window.AKSRTRevenueCenterInit(panels[name].panel); if (name === 'revenue' && typeof window.AKSRTAffiliateRevenueInit === 'function') window.AKSRTAffiliateRevenueInit(); if (name === 'revenue' && typeof window.AKSRTWithdrawInit === 'function') window.AKSRTWithdrawInit(); }).catch(function() { notyf(i18n.networkError || '加载失败,请重试', 'danger'); }); } function tabFromUrl() { var pathParts = window.location.pathname.replace(/\/+$/, '').split('/'); var pathTab = pathParts.length > 2 && pathParts[pathParts.length - 2] === 'user' ? pathParts[pathParts.length - 1] : ''; var name = pathTab || (cfg.activeTab || defaultTab); if (name === 'balance') name = 'credits'; return tabAliases[name] || name; } function updateUrl(name, sourceUrl) { if (!window.history.replaceState) return; var pathTab = name === 'credits' ? 'balance' : name; var u = new URL('/user/' + pathTab + '/', window.location.origin); if (sourceUrl && name !== 'revenue' && name !== 'orders') { var source = new URL(sourceUrl, window.location.href); source.searchParams.forEach(function(value, key) { u.searchParams.set(key, value); }); } window.history.pushState({}, '', u); } // Delegate every internal user-center link. This also covers markup injected // after a REST panel refresh, so panel navigation never falls back to a full load. document.addEventListener('click', function(e) { var withdrawOpen = e.target && e.target.closest ? e.target.closest('[data-aksrt-withdraw-open]') : null; if (withdrawOpen) { var withdrawModal = document.querySelector('[data-aksrt-withdraw-modal]'); if (withdrawModal) { e.preventDefault(); e.stopPropagation(); withdrawModal.hidden = false; withdrawModal.classList.add('is-open'); document.body.classList.add('uc-modal-open'); } return; } var withdrawClose = e.target && e.target.closest ? e.target.closest('[data-aksrt-withdraw-close]') : null; if (withdrawClose) { var openWithdrawModal = withdrawClose.closest('[data-aksrt-withdraw-modal]'); if (openWithdrawModal) { e.preventDefault(); e.stopPropagation(); openWithdrawModal.hidden = true; openWithdrawModal.classList.remove('is-open'); if (!document.querySelector('[data-aksrt-withdraw-modal].is-open')) document.body.classList.remove('uc-modal-open'); } return; } var link = e.target && e.target.closest ? e.target.closest('a[href]') : null; if (!link || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || link.target === '_blank') return; var url; try { url = new URL(link.href, window.location.href); } catch (_) { return; } if (url.origin !== window.location.origin) return; if (link.closest('.uc-reward-tabs, .uc-reward-pagination')) { e.preventDefault(); e.stopPropagation(); loadRevenueTab(url.searchParams).then(function() { if (window.history.replaceState) window.history.replaceState({}, '', '/user/revenue/'); if (typeof window.AKSRTAffiliateRevenueInit === 'function') window.AKSRTAffiliateRevenueInit(); if (typeof window.AKSRTWithdrawInit === 'function') window.AKSRTWithdrawInit(); }).catch(function() { notyf(i18n.networkError || '加载失败,请重试', 'danger'); }); return; } var parts = url.pathname.replace(/\/+$/, '').split('/'); var pathTab = parts.length > 2 && parts[parts.length - 2] === 'user' ? parts[parts.length - 1] : ''; var name = link.getAttribute('data-tab') || pathTab; if (name === 'balance') name = 'credits'; name = tabAliases[name] || name; if (!name || !panels[name]) return; e.preventDefault(); var hasQuery = url.search !== ''; activate(name, false, hasQuery, url.searchParams).then(function() { updateUrl(name, url.href); }); }, true); var creatorPanel = panels.creator ? panels.creator.panel : null; if (creatorPanel) { creatorPanel.addEventListener('click', function(e) { var link = e.target && e.target.closest ? e.target.closest('.creator-income__pagination a') : null; if (!link || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; e.preventDefault(); var pageUrl = new URL(link.href, window.location.href); loadPanel('creator', true, pageUrl.searchParams).then(function() { switchTo('creator'); if (window.history.pushState) window.history.pushState({}, '', pageUrl.toString()); if (typeof window.AKSRTRevenueCenterInit === 'function') window.AKSRTRevenueCenterInit(creatorPanel); }).catch(function() { notyf(i18n.networkError || '加载失败,请重试', 'danger'); }); }); } window.addEventListener('popstate', function() { activate(tabFromUrl(), false, window.location.search !== '', new URLSearchParams(window.location.search)); }); if (drawerTrigger) drawerTrigger.addEventListener('click', openDrawer); if (drawerOverlay) drawerOverlay.addEventListener('click', closeDrawer); var initialTab = tabFromUrl(); if (initialTab === 'orders' && window.location.search && window.history.replaceState) { window.history.replaceState({}, '', window.location.pathname + window.location.hash); } activate(initialTab, false); } /* ==================== 个人信息保存 ==================== */ function initProfileForm(root) { root = root || document; var form = root.id === 'profile-form' ? root : root.querySelector('#profile-form'); if (!form) return; if (form.dataset.aksrtProfileInitialized === 'true') return; form.dataset.aksrtProfileInitialized = 'true'; form.addEventListener('submit', function(e) { e.preventDefault(); var btn = form.querySelector('#profile-save-btn'); if (!btn || btn.disabled) return; btn.disabled = true; btn.textContent = i18n.saving || '保存中…'; notyf(i18n.saving || '正在保存资料…', 'load', '', 'aksrt-profile-save-toast'); var data = new FormData(form); data.set('user_id', String(userId)); fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: data }) .then(function(r) { return r.json(); }) .then(function(res) { btn.disabled = false; btn.textContent = i18n.saveBtn || '保存修改'; if (res.success) { notyf((res.data && res.data.message) || i18n.saved || '保存成功!', 'success', 5, 'aksrt-profile-save-toast'); } else { notyf((res.data && res.data.message) || i18n.saveFailed || '保存失败', 'danger', 5, 'aksrt-profile-save-toast'); } }) .catch(function() { btn.disabled = false; btn.textContent = i18n.saveBtn || '保存修改'; notyf(i18n.networkError || '网络错误,请重试', 'danger', 5, 'aksrt-profile-save-toast'); }); }); } function initAuthForm(root) { root = root || document; var identityRoot = root.matches && root.matches('[data-identity-root]') ? root : root.querySelector('[data-identity-root]'); if (identityRoot && window.AKSRTUserCenterAuth && typeof window.AKSRTUserCenterAuth.init === 'function') { window.AKSRTUserCenterAuth.init(identityRoot); } } /* ==================== 列表内取消收藏 ==================== */ function initUnfavorite(root) { root = root || document; var list = root.matches && root.matches('.profile-list[data-list="favorites"]') ? root : root.querySelector('.profile-list[data-list="favorites"]'); if (!list) return; list.addEventListener('click', function(e) { var btn = e.target.closest('[data-action="unfavorite"]'); if (!btn) return; e.preventDefault(); var postId = btn.getAttribute('data-post-id'); if (!postId || btn.disabled) return; if (!confirm(i18n.confirmUnfavorite || '确定取消收藏这篇文章?')) return; btn.disabled = true; btn.classList.add('is-loading'); var body = new URLSearchParams(); body.set('action', 'aksrt_toggle_favorite'); body.set('postId', postId); body.set('nonce', publicNonce); body.set('active', '0'); fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, credentials: 'same-origin', body: body.toString() }) .then(function(r) { return r.json(); }) .then(function(res) { if (res && res.success) { var item = btn.closest('.profile-list-item'); if (item) { item.classList.add('is-removing'); setTimeout(function() { if (item.parentNode) item.parentNode.removeChild(item); if (!list.querySelector('.profile-list-item')) { // Show empty state instead of reloading var empty = document.createElement('div'); empty.className = 'profile-empty'; empty.innerHTML = '
暂无收藏
你还没有收藏任何文章。
'; list.parentNode.replaceChild(empty, list); } }, 220); } notyf(i18n.unfavorited || '已取消收藏'); } else { btn.disabled = false; btn.classList.remove('is-loading'); notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }) .catch(function() { btn.disabled = false; btn.classList.remove('is-loading'); notyf(i18n.networkError || '网络错误', 'danger'); }); }); } function initPostDeletion(root) { if (document.documentElement.dataset.aksrtPostDeletionBound === 'true') return; document.documentElement.dataset.aksrtPostDeletionBound = 'true'; document.addEventListener('click', function(e) { var btn = e.target && e.target.closest ? e.target.closest('[data-action="delete-post"]') : null; if (!btn || btn.disabled || !confirm('确定删除这篇文章?')) return; e.preventDefault(); btn.disabled = true; var body = new URLSearchParams(); body.set('action', 'aksrt_delete_post'); body.set('post_id', btn.getAttribute('data-post-id') || ''); body.set('nonce', btn.getAttribute('data-nonce') || submitNonce); fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, credentials: 'same-origin', body: body.toString() }) .then(function(response) { return response.json(); }) .then(function(response) { if (!response || !response.success) throw new Error('delete-failed'); var item = btn.closest('.profile-list-item'); if (item) item.remove(); notyf('文章已删除'); }) .catch(function() { btn.disabled = false; notyf(i18n.operationFailed || '操作失败', 'danger'); }); }); } /* ==================== 通知中心 ==================== */ function initNotifications(root) { root = root || document; var list = root.matches && root.matches('#aksrt-notification-list') ? root : root.querySelector('#aksrt-notification-list'); var markAllBtn = list && list.ownerDocument.getElementById('aksrt-mark-all-notifications-read'); var markSelectedBtn = list && list.ownerDocument.getElementById('aksrt-mark-selected-notifications-read'); var deleteSelectedBtn = list && list.ownerDocument.getElementById('aksrt-delete-selected-notifications'); if (!list || list.dataset.aksrtNotificationsInitialized === 'true') return; list.dataset.aksrtNotificationsInitialized = 'true'; function markRead(notificationId, markAll) { var body = new URLSearchParams(); body.set('action', 'aksrt_mark_notification_read'); body.set('nonce', publicNonce); if (markAll) { body.set('mark_all', '1'); } else { body.set('notification_id', notificationId); } return fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, credentials: 'same-origin', body: body.toString() }).then(function(r) { return r.json(); }); } // 单条标记已读 / 删除 list.addEventListener('click', function(e) { var btn = e.target.closest('.aksrt-notification-read-btn'); if (btn) { var id = btn.getAttribute('data-notification-id'); if (!id || btn.disabled) return; btn.disabled = true; markRead(id, false).then(function(res) { if (res && res.success) { var row = btn.closest('[data-notification-id]'); if (row) row.classList.add('is-read'); btn.remove(); notyf(i18n.markedRead || '已标记为已读'); } else { btn.disabled = false; notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }).catch(function() { btn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); return; } var delBtn = e.target.closest('.aksrt-notification-delete-btn'); if (!delBtn) return; var delId = delBtn.getAttribute('data-notification-id'); if (!delId || delBtn.disabled) return; if (!confirm(i18n.confirmDeleteNotification || '确定删除这条通知?')) return; delBtn.disabled = true; var body = new URLSearchParams(); body.set('action', 'aksrt_delete_notification'); body.set('nonce', publicNonce); body.set('notification_id', delId); fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, credentials: 'same-origin', body: body.toString() }).then(function(r) { return r.json(); }).then(function(res) { if (res && res.success) { var delRow = delBtn.closest('[data-notification-id]'); if (delRow) delRow.remove(); notyf(i18n.notificationDeleted || '已删除通知'); } else { delBtn.disabled = false; notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }).catch(function() { delBtn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); }); // 全部已读 if (markAllBtn) { markAllBtn.addEventListener('click', function() { if (markAllBtn.disabled) return; markAllBtn.disabled = true; markRead('', true).then(function(res) { if (res && res.success) { list.querySelectorAll('[data-notification-id]').forEach(function(row) { row.classList.add('is-read'); }); list.querySelectorAll('.aksrt-notification-read-btn').forEach(function(b) { b.remove(); }); notyf(i18n.allMarkedRead || '已全部标记为已读'); } else { markAllBtn.disabled = false; notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }).catch(function() { markAllBtn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); }); } // 批量已读 if (markSelectedBtn) { markSelectedBtn.addEventListener('click', function() { var checked = Array.prototype.slice.call(list.querySelectorAll('.aksrt-notification-check:checked')).map(function(input) { return input.value; }); if (!checked.length) { notyf(i18n.selectNotifications || '请先选择要标记的通知', 'warning'); return; } markSelectedBtn.disabled = true; var chain = Promise.resolve(); checked.forEach(function(id) { chain = chain.then(function() { return markRead(id, false); }); }); chain.then(function() { checked.forEach(function(id) { var row = list.querySelector('[data-notification-id="' + id + '"]'); if (row) row.classList.add('is-read'); var b = row ? row.querySelector('.aksrt-notification-read-btn') : null; if (b) b.remove(); }); notyf(i18n.selectedMarkedRead || '已标记所选通知为已读'); markSelectedBtn.disabled = false; }).catch(function() { markSelectedBtn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); }); } // 批量删除 if (deleteSelectedBtn) { deleteSelectedBtn.addEventListener('click', function() { var checked = Array.prototype.slice.call(list.querySelectorAll('.aksrt-notification-check:checked')).map(function(input) { return input.value; }); if (!checked.length) { notyf(i18n.selectNotifications || '请先选择要删除的通知', 'warning'); return; } if (!confirm(i18n.confirmBatchDelete || '确定批量删除所选通知?')) return; deleteSelectedBtn.disabled = true; var body = new FormData(); body.append('action', 'aksrt_delete_notifications'); body.append('nonce', publicNonce); checked.forEach(function(id) { body.append('notification_ids[]', id); }); fetch(ajaxUrl, { method: 'POST', body: body, credentials: 'same-origin' }) .then(function(r) { return r.json(); }) .then(function(res) { if (res && res.success) { checked.forEach(function(id) { var row = list.querySelector('[data-notification-id="' + id + '"]'); if (row) row.remove(); }); notyf(i18n.batchDeleted || '已删除所选通知'); } else { deleteSelectedBtn.disabled = false; notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }) .catch(function() { deleteSelectedBtn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); }); } } /* ==================== 消息通知偏好 ==================== */ function initNotificationPreferences(root) { root = root || document; var form = root.matches && root.matches('[data-aksrt-notification-preferences]') ? root : root.querySelector('[data-aksrt-notification-preferences]'); if (!form || form.dataset.aksrtInitialized === 'true') return; form.dataset.aksrtInitialized = 'true'; form.addEventListener('submit', function(e) { e.preventDefault(); var button = form.querySelector('[type="submit"]'); if (!button || button.disabled) return; var toastId = 'aksrt-notification-preferences-toast'; var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || ''; var nonce = (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || cfg.restNonce || ''; var originalText = button.textContent; var preferences = {}; ['posts', 'like', 'system'].forEach(function(key) { var input = form.querySelector('[name="' + key + '"]'); preferences[key] = Boolean(input && input.checked); }); button.disabled = true; button.textContent = i18n.saving || '保存中…'; notyf(i18n.saving || '正在保存设置…', 'load', '', toastId); fetch(restUrl.replace(/\/$/, '') + '/user/notification-preferences', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, body: JSON.stringify({ preferences: preferences }) }).then(function(response) { return response.json().then(function(payload) { if (!response.ok || !payload || !payload.success) { throw new Error(payload && payload.message ? payload.message : (i18n.saveFailed || '保存失败')); } return payload; }); }).then(function(payload) { notyf(payload.message || i18n.saved || '设置已保存', 'success', 5, toastId); }).catch(function(error) { notyf(error.message || i18n.networkError || '网络错误,请重试', 'danger', 5, toastId); }).finally(function() { button.disabled = false; button.textContent = originalText; }); }); } /* ==================== 账号设置:弹窗 / 密码 / 邮箱 / 绑定 ==================== */ 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])'); if (firstInput) setTimeout(function() { firstInput.focus(); }, 200); } function closeModal(m) { if (typeof m === 'string') m = document.getElementById(m); if (!m) return; m.classList.remove('is-open'); m.hidden = true; document.body.style.overflow = ''; var f = m.querySelector('form'); var sceneMap = { 'pwd-modal': 'change_password', 'email-modal': 'change_email', 'phone-modal': 'bind_phone' }; var scene = m.id && sceneMap[m.id] ? sceneMap[m.id] : ''; if (f) { f.reset(); var bar = document.getElementById('pwd-strength-bar'); var txt = document.getElementById('pwd-strength-text'); if (bar && bar.firstChild) { bar.style.width = '0'; bar.firstChild.className = 'pwd-strength__fill'; } if (txt) { txt.textContent = ''; txt.className = 'pwd-strength__text'; } } if (scene) resetCaptcha(scene, f || m); } 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')); }); }); 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')); }); }); 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); } }); } // 密码显隐切换 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; inp.type = (inp.type === 'password') ? 'text' : 'password'; this.classList.toggle('is-visible'); }); }); // 密码强度 var pInput = document.getElementById('pwd-new'); var sBar = document.getElementById('pwd-strength-bar'); var sTxt = document.getElementById('pwd-strength-text'); 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'); if (!v) { f.className = 'pwd-strength__fill'; sTxt.className = 'pwd-strength__text'; sTxt.textContent = ''; return; } var s = 0; if (v.length >= 6) s++; if (v.length >= 10) s++; if (/[a-z]/.test(v) && /[A-Z]/.test(v)) s++; if (/\d/.test(v)) s++; if (/[^a-zA-Z0-9]/.test(v)) s++; var lv = '', tx = ''; if (s <= 1) { lv = 'weak'; tx = '弱'; } else if (s <= 3) { lv = 'medium'; tx = '中'; } else { lv = 'strong'; tx = '强'; } f.className = 'pwd-strength__fill level-' + lv; sTxt.className = 'pwd-strength__text level-' + lv; sTxt.textContent = '密码强度: ' + tx; }); } // 验证码发送 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) { 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'; runCaptcha(scene, form).then(function(captchaPayload) { if (captchaPayload === null) 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); var sec = 60; self.classList.add('is-counting'); self.textContent = sec + 's 后重发'; var t = setInterval(function() { sec--; if (sec <= 0) { clearInterval(t); self.disabled = false; self.classList.remove('is-counting'); self.textContent = getSendBtnIdleText(btnId); } else { self.textContent = sec + 's 后重发'; } }, 1000); } 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); }); }); } bindSend('pwd-send-code', 'pwd-form', 'change_password'); bindSend('email-send-code', 'email-form', 'change_email'); // 表单提交 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; } } if (fid === 'pwd-form') { var pwdNew = document.getElementById('pwd-new'); var pwdConfirm = document.getElementById('pwd-new-confirm'); if (pwdNew && pwdConfirm && pwdNew.value !== pwdConfirm.value) { pwdConfirm.focus(); notyf(i18n.passwordMismatch || '两次输入的新密码不一致', 'warning'); return; } } if (btn) { btn.disabled = true; btn.textContent = 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; btn.textContent = orig; } 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; btn.textContent = orig; } if (r.success) { notyf((r.data && r.data.message) || '操作成功', 'success', 5, toastId); closeModal(fm.closest('.aksrt-modal')); 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; btn.textContent = orig; } notyf(i18n.networkError || '网络错误', 'danger', 5, toastId); }) .finally(function() { resetCaptcha(scene, fm); }); }); } submitForm('pwd-form', 'aksrt_change_password'); submitForm('email-form', 'aksrt_change_email'); root.querySelectorAll('.binding-item--linkable').forEach(function(link) { if (link.dataset.aksrtBindCaptchaInitialized === 'true') return; link.dataset.aksrtBindCaptchaInitialized = 'true'; link.addEventListener('click', function(event) { if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; var startUrl = link.getAttribute('data-bind-start-url') || link.getAttribute('href') || ''; var provider = link.getAttribute('data-provider') || link.getAttribute('data-bind-provider') || ''; var scope = link.closest('[data-aksrt-captcha-scope]') || root; if (!startUrl || !captchaAvailable('bind', scope)) return; event.preventDefault(); var restUrl = String(cfg.restUrl || (window.aksrtData && (aksrtData.restUrl || aksrtData.apiUrl)) || '').replace(/\/$/, ''); var nonce = cfg.restNonce || (window.aksrtData && (aksrtData.restNonce || aksrtData.nonce)) || ''; var redirectTo = (cfg.tabUrls && cfg.tabUrls.settings) || window.location.href; if (!restUrl) { window.location.assign(startUrl); return; } link.classList.add('is-loading'); runCaptcha('bind', scope).then(function(captchaPayload) { if (captchaPayload === null) return null; return fetch(restUrl + '/oauth/bind-pass', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, body: JSON.stringify(Object.assign({ provider: provider, redirect_to: redirectTo }, captchaPayload || {})) }); }).then(function(response) { if (!response) return null; return response.json().catch(function() { return {}; }).then(function(payload) { if (!response.ok || !payload || payload.success === false) throw new Error(payload.message || '绑定验证失败'); return payload.data || payload; }); }).then(function(payload) { if (payload && payload.url) window.location.assign(payload.url); }).catch(function(error) { notyf(error.message || '绑定验证失败,请重试', 'danger'); }).finally(function() { link.classList.remove('is-loading'); resetCaptcha('bind', scope); }); }); }); // 解绑 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; if (!confirm(i18n.confirmUnbind || '确定解除绑定?')) return; 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'); data.set('nonce', unbindNonce ? unbindNonce.value : ''); data.set('provider', p); 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(response) { return response.json().catch(function() { return {}; }).then(function(payload) { return { ok: response.ok, status: response.status, payload: payload || {} }; }); }) .then(function(result) { var d = result.payload; if (result.ok && d.success) { notyf((d.data && d.data.message) || d.message || i18n.unbindSuccess || '解除成功', 'success', 5, toastId); setTimeout(function() { window.location.href = tabUrls.settings || new URL('/user/settings/', window.location.origin).toString(); }, 1000); } else { var message = result.status === 409 ? (d.message || (d.data && d.data.message) || i18n.unbindFailed || '解除失败') : ((d.data && d.data.message) || d.message || i18n.unbindFailed || '解除失败'); notyf(message, 'danger', 5, toastId); self.disabled = false; self.textContent = i18n.unbind || '解除'; } }) .catch(function() { notyf(i18n.networkError || '网络错误', 'danger', 5, toastId); self.disabled = false; self.textContent = i18n.unbind || '解除'; }); }); }); } function initSettingsCallbackToast() { if (document.documentElement.dataset.aksrtSettingsCallbackInitialized === 'true') return; var currentUrl = new URL(window.location.href); var action = currentUrl.searchParams.get('oauth_action'); if (['login', 'bind', 'error'].indexOf(action) === -1) return; if (action !== 'bind' && document.documentElement.dataset.aksrtOauthResultHandled === 'true') return; document.documentElement.dataset.aksrtSettingsCallbackInitialized = 'true'; var provider = currentUrl.searchParams.get('oauth_provider') || ''; var providerLabels = { github: 'GitHub', gitee: 'Gitee', qq: 'QQ', google: 'Google', weibo: '微博', weixin: '微信', weixingzh: '微信公众号' }; var errorMessages = { access_denied: '已取消第三方授权', cancelled: '已取消第三方授权', canceled: '已取消第三方授权', invalid_state: '授权状态已失效,请重新尝试', state_mismatch: '授权状态已失效,请重新尝试', provider_unavailable: '该登录方式暂时不可用', provider_error: '第三方授权失败,请稍后重试', callback_failed: '第三方授权失败,请稍后重试', login_failed: '第三方登录失败,请重试', bind_failed: '第三方账号绑定失败,请重试', bind_verification_required: '请先完成人机验证后再绑定', account_conflict: '该第三方账号无法绑定到当前账号', already_bound: '该第三方账号已绑定其他账号', timeout: '第三方授权已超时,请重试' }; var providerLabel = providerLabels[provider] || ''; var message = ''; var type = 'success'; if (action === 'login') message = providerLabel ? providerLabel + '登录成功' : '登录成功'; if (action === 'bind') message = providerLabel ? providerLabel + '绑定成功' : (i18n.bindSuccess || '绑定成功'); if (action === 'error') { type = 'danger'; message = errorMessages[currentUrl.searchParams.get('oauth_error') || ''] || '第三方授权失败,请重试'; } notyf(message, type); if (window.history.replaceState) { ['oauth_action', 'oauth_provider', 'oauth_error', 'oauth_bound'].forEach(function(key) { currentUrl.searchParams.delete(key); }); window.history.replaceState({}, '', currentUrl.pathname + currentUrl.search + currentUrl.hash); } } function initNavigationOnce() { initDashboardNavigation(); initPostDeletion(); } if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') { window.AksrtFrontend.register('aksrt-user-center', function(root) { initNavigationOnce(); initProfileForm(root); initAuthForm(root); initUnfavorite(root); initNotifications(root); initNotificationPreferences(root); initSettings(root); initBindPhone(root); 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'; var sendButton = form.querySelector('[data-aksrt-sms-trigger]'); if (sendButton) sendButton.addEventListener('click', function () { var phone = String((form.querySelector('[name="phone"]') || {}).value || '').trim(); if (!/^1[3-9]\d{9}$/.test(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; } 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 left = Number((res.json.data && res.json.data.countdown) || 60); var timer = setInterval(function () { left -= 1; sendButton.textContent = left + 's'; if (left <= 0) { clearInterval(timer); sendButton.disabled = false; sendButton.textContent = '获取验证码'; } }, 1000); }).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 (!/^1[3-9]\d{9}$/.test(phone)) { notyf('请输入正确的手机号', 'warning'); return; } if (!code) { notyf('请输入短信验证码', 'warning'); return; } var submitBtn = form.querySelector('[type="submit"]'); if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = '绑定中...'; } 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); setTimeout(function () { window.location.reload(); }, 600); return; } notyf((res.json && (res.json.message || res.json.error)) || '绑定失败', 'danger', 5, toastId); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '确认绑定'; } }) .catch(function () { notyf('网络错误', 'danger', 5, toastId); if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '确认绑定'; } }); } runCaptcha('bind_phone', form).then(function(captchaPayload) { if (captchaPayload === null) { if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '确认绑定'; } return null; } return doBind(captchaPayload); }).finally(function() { resetCaptcha('bind_phone', form); }); }); } })();