Files
wordpress-theme-aksrt/assets/js/routes/user-center.js
T
kate522 5fd7c212ed fix(runtime): 按参考主题硬切换为 loader/main/auto_fun 单一 DOM 生命周期
- 删除 AksrtFrontend registry/rehydrate 双路架构及 build/public.js 拼包入口,
  前台唯一初始路径为 PHP 本地栈(jQuery 1.9.1 → Bootstrap 3.4.1)
  → loader_js(_win/aksrtData 唯一注入)→ tbquire(['main']) → runtime/main.min.js
- window.auto_fun = debounce(z_auto_fun, 100) 为唯一重扫入口;所有模块改为
  执行一次 + off/on('auto_fun.aksrt-*') 命名空间重扫,data/class 标记幂等
- 局部更新协议统一:HTML 注入 → post_ajax.ed → auto_fun();
  远程 modal → loaded.bs.modal → auto_fun();JSON 成功 → zib_ajax.success
- 灯箱恢复 Miniimgbox/imgbox 唯一 owner(imgbox-container、a[data-imgbox]、
  imgbox_data),删除 AksrtLightbox/data-aksrt-lightbox 第二实现及 CSS/overlay,
  _win.imgbox* 重新接上 post-display 设置
- 死代码清理:runtime 下 11 组被自有实现替代的旧模块副本、无消费 vendored 库、
  顶层 grade.min.js、revenue.js、无调用方的 aksrt_get_win_runtime_config(),
  以及 loader 中 16 条死 mapping
- PHP 入队改为本地栈 + loader_js,routed/widget/submit 依赖统一换绑;
  用户卡片发布按钮恢复 nav_user_newposts_btn 设置门控
- 测试对齐:新增 frontend-lifecycle-contract,重写 navigation/lazyload/toast/
  auth-source/comment/page-display/post-visual 等断言为目标架构契约

验证:node --test widget-system+navigation 223 通过 0 失败;
构建脚本重建 main/imgbox min 与 CSS bundles;改动 PHP php -l、JS node --check
全部通过;git diff --check 干净;credits 重入 harness 通过。
2026-09-03 12:44:31 +08:00

1150 lines
47 KiB
JavaScript

/**
* 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() {
var tabContainer = document.querySelector('.uc-mobile-tabs');
if (!tabContainer) return;
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.jQuery) jQuery(document).trigger('post_ajax.ed', [target]);
if (typeof window.auto_fun === 'function') window.auto_fun();
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.jQuery) jQuery(document).trigger('post_ajax.ed', [target]);
if (typeof window.auto_fun === 'function') window.auto_fun();
});
}).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() {
var root = document.querySelector('[data-identity-root]');
if (root && window.AKSRTUserCenterAuth && typeof window.AKSRTUserCenterAuth.init === 'function') {
window.AKSRTUserCenterAuth.init(root);
}
}
/* ==================== 列表内取消收藏 ==================== */
function initUnfavorite() {
var list = document.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 = '<div class="profile-empty__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="30" height="30"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></div><p class="profile-empty__title">暂无收藏</p><p class="profile-empty__desc">你还没有收藏任何文章。</p>';
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() {
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() {
var list = document.getElementById('aksrt-notification-list');
var markAllBtn = document.getElementById('aksrt-mark-all-notifications-read');
var markSelectedBtn = document.getElementById('aksrt-mark-selected-notifications-read');
var deleteSelectedBtn = document.getElementById('aksrt-delete-selected-notifications');
if (!list) return;
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);
}
}
jQuery(document).off('auto_fun.aksrt-user-center').on('auto_fun.aksrt-user-center', function () {
initDashboardNavigation();
initProfileForm();
initAuthForm();
initUnfavorite();
initPostDeletion();
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';
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);
});
});
}
})();