From 02fcde3c84934db6fd909b36763b3352150eb9e2 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 6 Aug 2026 10:42:32 +0800 Subject: [PATCH] fix(user-center): load revenue tabs without refresh Co-Authored-By: Claude Fable 5 --- assets/css/routes/user-center/responsive.css | 11 +-- assets/js/revenue-invite.js | 46 ++++++++++-- assets/js/routes/user-center.js | 66 +++++++++++------ inc/helpers/aksrt-user-center.php | 4 +- .../user-center-renderers/profile-revenue.php | 7 +- src/Payment/Rebate.php | 2 +- src/User/Center.php | 74 +++++++++++++++++++ 7 files changed, 169 insertions(+), 41 deletions(-) diff --git a/assets/css/routes/user-center/responsive.css b/assets/css/routes/user-center/responsive.css index d096c1fa..8c4eb49e 100644 --- a/assets/css/routes/user-center/responsive.css +++ b/assets/css/routes/user-center/responsive.css @@ -25,7 +25,7 @@ display: none; } - .uc-mobile-tabs button { + .uc-mobile-tabs a { flex-shrink: 0; white-space: nowrap; padding: 8px 14px; @@ -35,16 +35,13 @@ color: var(--color-ink-3); font-size: 13px; font-weight: 500; + text-decoration: none; cursor: pointer; transition: all 0.2s ease; } - .uc-mobile-tabs button:hover { - background: var(--surface-2); - color: var(--color-ink); - } - - .uc-mobile-tabs button.is-active { + .uc-mobile-tabs a:hover, + .uc-mobile-tabs a.is-active { background: var(--color-primary-soft); color: var(--color-primary); font-weight: 600; diff --git a/assets/js/revenue-invite.js b/assets/js/revenue-invite.js index 862302f6..0201d551 100644 --- a/assets/js/revenue-invite.js +++ b/assets/js/revenue-invite.js @@ -4,11 +4,11 @@ (function () { 'use strict'; - var root = document.querySelector('[data-aksrt-revenue-page]'); - if (!root) return; + function init(root) { + var data = window.aksrtData || {}; + if (!root || !data) return; - var data = window.aksrtData || {}; - var rest = data.restUrl || data.apiUrl || ((data.siteUrl || '').replace(/\/$/, '') + '/wp-json/aksrt/v1'); + var rest = data.restUrl || data.apiUrl || ((data.siteUrl || '').replace(/\/$/, '') + '/wp-json/aksrt/v1'); if (!rest) return; rest = rest.replace(/\/?$/, ''); @@ -218,8 +218,44 @@ }).then(function (result) { if (!result.ok || !result.json || !result.json.success) { setWithdrawBusy(false); setWithdrawTip((result.json && result.json.message) || '提现申请失败', true); return; } setWithdrawTip('提现申请已提交', false); - window.setTimeout(function () { window.location.reload(); }, 700); + if (result.json.data && typeof result.json.data.wallet_balance !== 'undefined') { + var balanceEl = root.querySelector('[data-aksrt-revenue-balance]'); + if (balanceEl) balanceEl.textContent = Number(result.json.data.wallet_balance).toFixed(2); + } + withdrawForm.reset(); + setWithdrawBusy(false); }).catch(function () { setWithdrawBusy(false); setWithdrawTip('网络错误,请稍后重试', true); }); }); + var rewardTabs = root.querySelectorAll('.uc-reward-tabs a, .uc-reward-pagination a'); + rewardTabs.forEach(function (link) { + link.addEventListener('click', function (event) { + event.preventDefault(); + var targetUrl = new URL(link.href, window.location.href); + var panelUrl = rest + '/user-center/panel/revenue?' + targetUrl.searchParams.toString(); + fetch(panelUrl, { + credentials: 'same-origin', + headers: { 'X-WP-Nonce': data.restNonce || data.nonce || '' } + }).then(function (response) { + return response.json().then(function (payload) { + if (!response.ok || !payload.success || !payload.data || !payload.data.html) throw new Error('revenue-load-failed'); + var current = document.querySelector('[data-aksrt-revenue-page]'); + if (!current) return; + var wrapper = document.createElement('div'); + wrapper.innerHTML = payload.data.html; + var next = wrapper.firstElementChild; + if (!next) throw new Error('revenue-html-empty'); + current.replaceWith(next); + window.history.pushState({}, '', targetUrl); + init(next); + }); + }).catch(function () { + if (typeof showToast === 'function') showToast('加载失败,请重试'); + }); + }); + }); + } } + + window.AKSRTRevenueCenterInit = init; + init(document.querySelector('[data-aksrt-revenue-page]')); })(); diff --git a/assets/js/routes/user-center.js b/assets/js/routes/user-center.js index bc0f34eb..3005c28d 100644 --- a/assets/js/routes/user-center.js +++ b/assets/js/routes/user-center.js @@ -56,11 +56,6 @@ function switchTo(name) { if (!panels[name]) name = defaultTab; - if (panels[name].panel.dataset.panelLoaded === 'false') { - window.location.assign(tabUrls[name] || panels[name].panel.dataset.tabUrl || ('?tab=' + encodeURIComponent(name))); - return; - } - Object.keys(panels).forEach(function(key) { var active = key === name; panels[key].tab.classList.toggle('is-active', active); @@ -68,14 +63,48 @@ 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) { + if (!panels[name]) return Promise.reject(new Error('panel-not-found')); + var target = panels[name].panel; + if (target.dataset.panelLoaded === 'true') return Promise.resolve(); + if (target._aksrtPanelRequest) return target._aksrtPanelRequest; + var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || ''; + if (!restUrl) return Promise.reject(new Error('rest-unavailable')); + target.classList.add('is-loading'); + var url = restUrl.replace(/\/$/, '') + '/user-center/panel/' + encodeURIComponent(name); + 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'); + target.innerHTML = payload.data.html; + target.dataset.panelLoaded = 'true'; + }); + }).finally(function() { + target._aksrtPanelRequest = null; + target.classList.remove('is-loading'); + }); + return target._aksrtPanelRequest; + } + + function activate(name, updateHistory) { + if (!panels[name]) name = defaultTab; + return loadPanel(name).then(function() { + switchTo(name); + if (updateHistory) updateUrl(name); + if (typeof window.AKSRTRevenueCenterInit === 'function') window.AKSRTRevenueCenterInit(panels[name].panel); + }).catch(function() { + if (typeof showToast === 'function') showToast(i18n.networkError || '加载失败,请重试'); + }); + } + function tabFromUrl() { var m = window.location.search.match(/[?&]tab=([^&]+)/); var name = m ? decodeURIComponent(m[1]) : (cfg.activeTab || defaultTab); @@ -85,38 +114,27 @@ function updateUrl(name) { if (!window.history.replaceState) return; var u = new URL(window.location); - if (name === defaultTab) { - u.searchParams.delete('tab'); - } else { - u.searchParams.set('tab', name); - } + if (name === defaultTab) u.searchParams.delete('tab'); + else u.searchParams.set('tab', name); window.history.replaceState({}, '', u); } tabs.forEach(function(tab) { tab.addEventListener('click', function(e) { - var name = this.getAttribute('data-tab'); - if (!panels[name] || panels[name].panel.dataset.panelLoaded === 'false') return; e.preventDefault(); - switchTo(name); - updateUrl(name); + activate(this.getAttribute('data-tab'), true); }); }); - sidebarBtns.forEach(function(btn) { btn.addEventListener('click', function(e) { - var name = this.getAttribute('data-tab'); - if (!panels[name] || panels[name].panel.dataset.panelLoaded === 'false') return; e.preventDefault(); - switchTo(name); - updateUrl(name); + activate(this.getAttribute('data-tab'), true); }); }); - + window.addEventListener('popstate', function() { activate(tabFromUrl(), false); }); if (drawerTrigger) drawerTrigger.addEventListener('click', openDrawer); if (drawerOverlay) drawerOverlay.addEventListener('click', closeDrawer); - - switchTo(tabFromUrl()); + activate(tabFromUrl(), false); } /* ==================== 个人信息保存 ==================== */ diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php index b91c50e5..5fabd13a 100644 --- a/inc/helpers/aksrt-user-center.php +++ b/inc/helpers/aksrt-user-center.php @@ -342,7 +342,9 @@ if (!function_exists('aksrt_user_center_runtime_config')) { 'publicNonce' => wp_create_nonce('aksrt_public_nonce'), 'settingsNonce' => wp_create_nonce('aksrt_account_settings'), 'userId' => $user_id, - 'defaultTab' => $default_tab, + 'restUrl' => rest_url('aksrt/v1'), + 'restNonce' => wp_create_nonce('wp_rest'), + 'defaultTab' => $default_tab, 'activeTab' => $active_tab, 'tabAliases' => [ 'signin' => 'credits', diff --git a/inc/helpers/user-center-renderers/profile-revenue.php b/inc/helpers/user-center-renderers/profile-revenue.php index 4d426035..071e3c27 100644 --- a/inc/helpers/user-center-renderers/profile-revenue.php +++ b/inc/helpers/user-center-renderers/profile-revenue.php @@ -51,7 +51,8 @@ if ($details_text === '') { $details_text = trim((string) ($affiliate['note'] ?? '')) ?: __('好友通过推广链接完成符合条件的订单后,系统会按当前佣金比例记录返佣,结算后进入可提现余额。', 'aksrt'); } -$active_reward_tab = sanitize_key((string) ($_GET['reward_tab'] ?? 'detail')); +$revenue_request = is_array($context['revenue_request'] ?? null) ? $context['revenue_request'] : []; +$active_reward_tab = sanitize_key((string) ($revenue_request['reward_tab'] ?? ($_GET['reward_tab'] ?? 'detail'))); if (!in_array($active_reward_tab, ['detail', 'orders', 'users'], true)) { $active_reward_tab = 'detail'; } @@ -65,7 +66,7 @@ $format_amount = static function ($amount): string { return number_format_i18n((float) $amount, 2); }; -$commission_page = max(1, (int) ($_GET['commission_page'] ?? 1)); +$commission_page = max(1, (int) ($revenue_request['commission_page'] ?? ($_GET['commission_page'] ?? 1))); $commission_view = class_exists('\AKSRT\Payment\Rebate') ? \AKSRT\Payment\Rebate::get_instance()->get_list([ 'user_id' => $user_id, @@ -107,7 +108,7 @@ $status_labels = [
-

+
diff --git a/src/Payment/Rebate.php b/src/Payment/Rebate.php index 6c4ebea3..956eaced 100644 --- a/src/Payment/Rebate.php +++ b/src/Payment/Rebate.php @@ -199,7 +199,7 @@ class Rebate { SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending, SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) as pending_amount, SUM(CASE WHEN status = 'settled' THEN 1 ELSE 0 END) as settled, - SUM(amount) as total_amount, + SUM(CASE WHEN status IN ('pending', 'settled') THEN amount ELSE 0 END) as total_amount, SUM(CASE WHEN status = 'settled' THEN amount ELSE 0 END) as settled_amount FROM {$table} {$where}"; $row = $params diff --git a/src/User/Center.php b/src/User/Center.php index 4bc29c54..ef4920ce 100644 --- a/src/User/Center.php +++ b/src/User/Center.php @@ -25,6 +25,80 @@ class Center { public function init(): void { $this->register_default_panels(); + add_action('rest_api_init', [$this, 'register_rest_routes']); + } + + /** + * Register user-center fragment routes. + */ + public function register_rest_routes(): void { + register_rest_route('aksrt/v1', '/user-center/panel/(?P[a-z0-9_-]+)', [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [$this, 'rest_panel'], + 'permission_callback' => static function (): bool { + return is_user_logged_in(); + }, + 'args' => [ + 'tab' => [ + 'required' => true, + 'sanitize_callback' => 'sanitize_key', + 'validate_callback' => static function ($value): bool { + return preg_match('/^[a-z0-9_-]+$/', (string) $value) === 1; + }, + ], + ], + ]); + } + + /** + * Render a user-center panel or a revenue reward fragment. + */ + public function rest_panel(\WP_REST_Request $request) { + $tab = sanitize_key((string) $request->get_param('tab')); + $tabs = $this->get_dashboard_tabs(); + if (!isset($tabs[$tab])) { + return new \WP_Error('aksrt_user_center_panel', __('用户中心面板不存在', 'aksrt'), ['status' => 404]); + } + + $user = wp_get_current_user(); + $context = function_exists('aksrt_get_user_center_render_context') + ? aksrt_get_user_center_render_context($user) + : []; + if (!is_array($context) || !$context) { + return new \WP_Error('aksrt_user_center_context', __('用户中心数据加载失败', 'aksrt'), ['status' => 500]); + } + + $revenue_request = []; + if ($tab === 'revenue') { + $reward_tab = sanitize_key((string) $request->get_param('reward_tab')); + if (!in_array($reward_tab, ['detail', 'orders', 'users'], true)) { + $reward_tab = 'detail'; + } + $revenue_request = [ + 'reward_tab' => $reward_tab, + 'commission_page' => max(1, min(100, (int) $request->get_param('commission_page'))), + 'invite_page' => max(1, min(100, (int) $request->get_param('invite_page'))), + ]; + $context['revenue_request'] = $revenue_request; + } + + $html = function_exists('aksrt_capture_user_center_panel') + ? aksrt_capture_user_center_panel($tab, $tabs[$tab], $context) + : ''; + if ($html === '') { + return new \WP_Error('aksrt_user_center_panel_empty', __('用户中心面板暂不可用', 'aksrt'), ['status' => 500]); + } + + return rest_ensure_response([ + 'success' => true, + 'data' => [ + 'tab' => $tab, + 'html' => $html, + 'reward_tab' => $revenue_request['reward_tab'] ?? '', + 'commission_page' => $revenue_request['commission_page'] ?? 1, + 'invite_page' => $revenue_request['invite_page'] ?? 1, + ], + ]); } /**