From 49c12b761f2e489b09b7c79acbb1aae9c729bb2a Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 3 Aug 2026 15:38:17 +0800 Subject: [PATCH] feat(users): merge promotion into revenue center --- .../src/schema/sections/users/features.js | 4 +- assets/css/routes/user-center.css | 2 + assets/css/routes/user-center/revenue.css | 57 +++++ assets/css/routes/user-center/shell.css | 2 +- assets/js/revenue.js | 204 +++++++++--------- inc/helpers/aksrt-transactions.php | 4 +- inc/helpers/aksrt-user-center.php | 30 +-- .../user-center-renderers/profile-invite.php | 24 +++ .../profile-promotion.php | 92 -------- .../user-center-renderers/profile-revenue.php | 60 ++++++ .../user-center-renderers/sidebar-creator.php | 4 +- .../user-center-renderers/sidebar-nav.php | 8 - inc/modules/user-parts/urls.php | 15 +- src/Admin/Schema/SchemaUsers.php | 2 +- src/Core/ScriptsPart1b.php | 21 +- src/Credits/Balance.php | 1 + src/Payment/Revenue.php | 53 ++++- src/Settings/UserRuntime.php | 4 +- src/User/Center.php | 20 +- templates/pages/page-revenue.php | 175 +-------------- 20 files changed, 369 insertions(+), 413 deletions(-) create mode 100644 assets/css/routes/user-center/revenue.css create mode 100644 inc/helpers/user-center-renderers/profile-invite.php delete mode 100644 inc/helpers/user-center-renderers/profile-promotion.php create mode 100644 inc/helpers/user-center-renderers/profile-revenue.php diff --git a/assets/admin/src/schema/sections/users/features.js b/assets/admin/src/schema/sections/users/features.js index c3d4616d..f1d94f0d 100644 --- a/assets/admin/src/schema/sections/users/features.js +++ b/assets/admin/src/schema/sections/users/features.js @@ -4,7 +4,7 @@ export const featuresSchema = { icon: 'fas fa-user-cog', description: '配置用户中心和个人主页的功能模块。', defaults: { - user_center_tabs: 'profile,level,posts,comments,favorites,vip,credits,promotion,auth', + user_center_tabs: 'profile,level,posts,comments,favorites,vip,credits,revenue,auth', user_cover_upload: true, user_social_links: true, user_desc_std: '这家伙很懒…', @@ -24,7 +24,7 @@ export const featuresSchema = { title: '用户中心', icon: 'fas fa-user-shield', fields: [ - { key: 'user_center_tabs', label: '用户中心 Tab', type: 'input', default: 'profile,level,posts,comments,favorites,vip,credits,promotion,auth', help: '逗号分隔。level=我的等级;promotion=推广中心;auth=官方认证;等级、VIP、订单、推广中心和认证会按功能开关自动注入' }, + { key: 'user_center_tabs', label: '用户中心 Tab', type: 'input', default: 'profile,level,posts,comments,favorites,vip,credits,revenue,auth', help: '逗号分隔。level=我的等级;revenue=收益中心;auth=官方认证;等级、VIP、订单、收益中心和认证会按功能开关自动注入' }, { key: 'user_cover_upload', label: '封面上传', type: 'switch', default: true }, { key: 'user_social_links', label: '社交链接', type: 'switch', default: true }, { key: 'user_desc_std', label: '默认签名', type: 'input', default: '这家伙很懒…', help: '用户未设置个人简介时显示的默认文案' }, diff --git a/assets/css/routes/user-center.css b/assets/css/routes/user-center.css index 95bc6463..62e4b3ea 100644 --- a/assets/css/routes/user-center.css +++ b/assets/css/routes/user-center.css @@ -2,4 +2,6 @@ @import url("user-center/credits.css"); @import url("user-center/level.css"); @import url("user-center/settings.css"); +@import url("revenue.css"); +@import url("user-center/revenue.css"); @import url("user-center/responsive.css"); diff --git a/assets/css/routes/user-center/revenue.css b/assets/css/routes/user-center/revenue.css new file mode 100644 index 00000000..f996c584 --- /dev/null +++ b/assets/css/routes/user-center/revenue.css @@ -0,0 +1,57 @@ +/* User-center revenue and invitation additions. */ +.uc-content .uc-revenue-panel { padding: 4px 0; } +.uc-content .uc-revenue-panel__header { margin-bottom: 20px; } +.uc-content .uc-revenue-panel__header p { margin: 0 0 4px; color: var(--color-primary); font-size: 12px; font-weight: 700; } +.uc-content .uc-revenue-panel__header h2 { margin: 0; color: var(--color-ink); font-size: 22px; } +.uc-content .uc-revenue-panel__header > span { display: block; margin-top: 7px; color: var(--color-ink-4); font-size: 13px; } +.uc-content .aksrt-revenue-page__layout { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-bottom: 20px; } +.uc-content .aksrt-revenue-page__panel, +.uc-content .aksrt-revenue-page__section { padding: 20px; border: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); border-radius: 14px; background: var(--surface-1); } +.uc-content .aksrt-revenue-page__panel h3, +.uc-content .aksrt-revenue-page__section h3 { margin: 0 0 14px; font-size: 16px; } +.uc-content .aksrt-revenue-page__withdraw-form { display: grid; gap: 12px; } +.uc-content .aksrt-revenue-page__withdraw-form label { display: grid; gap: 6px; color: var(--color-ink-3); font-size: 13px; } +.uc-content .aksrt-revenue-page__withdraw-form input, +.uc-content .aksrt-revenue-page__withdraw-form select, +.uc-content .aksrt-revenue-page__withdraw-form textarea { width: 100%; padding: 9px 11px; border: 1px solid var(--color-line-soft, rgba(0,0,0,.1)); border-radius: 8px; color: var(--color-ink); background: var(--surface-2); } +.uc-content .aksrt-revenue-page__form-msg.is-error { color: var(--color-error, #dc2626); } +.uc-content .uc-promotion-panel { display: grid; gap: 16px; margin-top: 24px; padding: 20px; border: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); border-radius: 14px; background: var(--surface-1); } +.uc-content .uc-promotion-panel__header { display: flex; align-items: center; gap: 12px; } +.uc-content .uc-promotion-panel__icon { display: grid; width: 42px; height: 42px; place-items: center; border-radius: 12px; color: #fff; background: var(--color-primary); } +.uc-content .uc-promotion-panel__eyebrow { margin: 0 0 2px; color: var(--color-primary); font-size: 12px; font-weight: 700; } +.uc-content .uc-promotion-panel__header h3 { margin: 0; font-size: 18px; } +.uc-content .uc-promotion-panel__intro, +.uc-content .uc-promotion-section__header p { margin: 0; color: var(--color-ink-4); font-size: 13px; line-height: 1.65; } +.uc-content .uc-promotion-panel__status { margin: 0; padding: 10px 12px; border-radius: 8px; color: var(--color-ink-3); background: var(--surface-2); font-size: 13px; } +.uc-content .uc-promotion-panel__status.is-error, +.uc-content .uc-promotion-copy-feedback.is-error { color: var(--color-error, #dc2626); } +.uc-content .uc-promotion-section { padding-top: 4px; } +.uc-content .uc-promotion-section h4 { margin: 0 0 4px; font-size: 15px; } +.uc-content .uc-promotion-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 12px; border: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); border-radius: 10px; overflow: hidden; } +.uc-content .uc-promotion-stat { display: grid; gap: 4px; padding: 14px; border-right: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); } +.uc-content .uc-promotion-stat:last-child { border-right: 0; } +.uc-content .uc-promotion-stat span, +.uc-content .uc-promotion-stat small { color: var(--color-ink-4); font-size: 12px; } +.uc-content .uc-promotion-stat strong { color: var(--color-primary); font-size: 26px; font-variant-numeric: tabular-nums; } +.uc-content .uc-promotion-share { display: grid; gap: 10px; margin-top: 12px; } +.uc-content .uc-promotion-code { display: flex; align-items: baseline; gap: 10px; color: var(--color-ink-4); font-size: 13px; } +.uc-content .uc-promotion-code strong { color: var(--color-ink); font-size: 19px; letter-spacing: .08em; } +.uc-content .uc-promotion-link-row { display: flex; gap: 10px; } +.uc-content .uc-promotion-link { flex: 1; min-width: 0; height: 42px; padding: 0 11px; border: 1px solid var(--color-line-soft, rgba(0,0,0,.1)); border-radius: 8px; background: var(--surface-2); } +.uc-content .uc-promotion-copy { min-width: 108px; } +.uc-content .uc-promotion-copy-feedback { min-height: 18px; margin: 0; color: var(--color-ink-4); font-size: 12px; } +.uc-content .uc-promotion-records__header { display: flex; justify-content: space-between; gap: 12px; } +.uc-content .uc-promotion-record-list { display: grid; margin-top: 12px; } +.uc-content .uc-promotion-record { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-top: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); } +.uc-content .uc-promotion-record__avatar { display: grid; width: 40px; height: 40px; flex: 0 0 40px; place-items: center; overflow: hidden; border-radius: 50%; background: var(--surface-2); } +.uc-content .uc-promotion-record__avatar img { width: 100%; height: 100%; object-fit: cover; } +.uc-content .uc-promotion-record__main { display: grid; flex: 1; min-width: 0; gap: 3px; } +.uc-content .uc-promotion-record__main span, +.uc-content .uc-promotion-record__status small { color: var(--color-ink-4); font-size: 12px; } +.uc-content .uc-promotion-record__status { display: grid; justify-items: end; gap: 4px; } +.uc-content .uc-promotion-badge { padding: 3px 8px; border-radius: 999px; font-size: 12px; font-weight: 700; } +.uc-content .uc-promotion-badge.is-registered { color: #2563eb; background: rgba(37,99,235,.1); } +.uc-content .uc-promotion-badge.is-verified { color: #7c3aed; background: rgba(124,58,237,.1); } +.uc-content .uc-promotion-badge.is-rewarded { color: #16a34a; background: rgba(22,163,74,.1); } +.uc-content .uc-promotion-record-empty { padding: 22px; border-top: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); color: var(--color-ink-4); font-size: 13px; text-align: center; } +@media (max-width: 640px) { .uc-content .aksrt-revenue-page__layout { grid-template-columns: 1fr; } .uc-content .uc-promotion-stats { grid-template-columns: 1fr; } .uc-content .uc-promotion-stat { border-right: 0; border-bottom: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); } .uc-content .uc-promotion-stat:last-child { border-bottom: 0; } .uc-content .uc-promotion-link-row { flex-direction: column; } .uc-content .uc-promotion-copy { width: 100%; } } diff --git a/assets/css/routes/user-center/shell.css b/assets/css/routes/user-center/shell.css index f6494df8..792ce2dc 100644 --- a/assets/css/routes/user-center/shell.css +++ b/assets/css/routes/user-center/shell.css @@ -546,7 +546,7 @@ body.aksrt-user-center .uc-content, .uc-center-card small { margin-top: 3px; color: var(--color-ink-4); font-size: 10px; } .uc-center-card > i { color: var(--color-ink-4); font-size: 11px; } .uc-center-card--creation .uc-center-card__icon { color: #6650c7; background: rgba(124, 92, 220, 0.12); } -.uc-center-card--promotion .uc-center-card__icon { color: #ce6b35; background: rgba(234, 112, 55, 0.12); } +.uc-center-card--revenue .uc-center-card__icon { color: #ce6b35; background: rgba(234, 112, 55, 0.12); } .uc-service-grid { display: grid; diff --git a/assets/js/revenue.js b/assets/js/revenue.js index e687f0a9..1795d62b 100644 --- a/assets/js/revenue.js +++ b/assets/js/revenue.js @@ -1,114 +1,116 @@ /** - * Creator revenue / withdraw form. + * AKSRT Revenue Center Frontend * * @package AKSRT */ (function () { - "use strict"; + 'use strict'; - var page = document.querySelector("[data-aksrt-revenue-page]"); - if (!page || !window.aksrtData) return; + var page = document.querySelector('[data-aksrt-revenue-page]'); + var data = window.aksrtData || {}; + if (!page || !data) return; - function restBase() { - return ((aksrtData.siteUrl || "").replace(/\/$/, "")) + "/wp-json/aksrt/v1"; - } + var rest = data.restUrl || data.apiUrl || ((data.siteUrl || '').replace(/\/$/, '') + '/wp-json/aksrt/v1'); + if (!rest) return; + if (window.location.protocol === 'https:' && rest.substring(0, 5) === 'http:') { + rest = 'https:' + rest.substring(5); + } + rest = rest.replace(/\/?$/, ''); - function rest(path, method, body) { - var opts = { - method: method || "GET", - credentials: "same-origin", - headers: { "X-WP-Nonce": aksrtData.restNonce || "" }, - }; - if (body !== undefined) { - opts.headers["Content-Type"] = "application/json"; - opts.body = JSON.stringify(body); - } - return fetch(restBase() + path, opts).then(function (r) { - return r.json(); - }); - } + var form = page.querySelector('[data-aksrt-withdraw-form]'); + var message = page.querySelector('[data-aksrt-withdraw-msg]'); + var balance = page.querySelector('[data-aksrt-revenue-balance]'); + var list = page.querySelector('[data-aksrt-withdraw-list]'); + if (!form) return; - var form = page.querySelector("[data-aksrt-withdraw-form]"); - var msg = page.querySelector("[data-aksrt-withdraw-msg]"); - var balanceEl = page.querySelector("[data-aksrt-revenue-balance]"); - var list = page.querySelector("[data-aksrt-withdraw-list]"); + function setMessage(text, error) { + if (!message) return; + message.hidden = false; + message.textContent = text; + message.classList.toggle('is-error', !!error); + } - if (!form) return; + function request(payload) { + return fetch(rest + '/pay/balance/withdraw', { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': data.restNonce || data.nonce || '' + }, + body: JSON.stringify(payload) + }).then(function (response) { + return response.json().catch(function () { + return { message: '服务器返回了非 JSON 响应' }; + }).then(function (json) { + return { ok: response.ok, json: json }; + }); + }); + } - form.addEventListener("submit", function (e) { - e.preventDefault(); - var fd = new FormData(form); - var amount = Number(fd.get("amount") || 0); - var method = String(fd.get("method") || "alipay"); - var account = String(fd.get("account_info") || "").trim(); - var min = Number(page.getAttribute("data-min-amount") || 50); - if (amount < min) { - if (msg) { - msg.hidden = false; - msg.textContent = "最低提现金额为 " + min; - } - return; - } - if (!account) { - if (msg) { - msg.hidden = false; - msg.textContent = "请填写收款账户信息"; - } - return; - } + form.addEventListener('submit', function (event) { + event.preventDefault(); + var values = new FormData(form); + var amount = Number(values.get('amount') || 0); + var method = String(values.get('method') || 'alipay'); + var account = String(values.get('account_info') || '').trim(); + var minimum = Number(page.getAttribute('data-min-amount') || 50); + if (amount < minimum) { + setMessage('最低提现金额为 ' + minimum, true); + return; + } + if (!account) { + setMessage('请填写收款账户信息', true); + return; + } - var btn = form.querySelector('button[type="submit"]'); - if (btn) { - btn.disabled = true; - btn.textContent = "提交中..."; - } - - rest("/shop/frontend/revenue/withdraw", "POST", { - amount: amount, - method: method, - account_info: account, - }) - .then(function (result) { - if (btn) { - btn.disabled = false; - btn.textContent = "提交提现申请"; - } - if (!result || !result.success) { - if (msg) { - msg.hidden = false; - msg.textContent = (result && result.message) || "提现失败"; - } - return; - } - if (msg) { - msg.hidden = false; - msg.textContent = result.message || "提现申请已提交"; - } - if (balanceEl && result.data && typeof result.data.balance !== "undefined") { - balanceEl.textContent = "¥" + Number(result.data.balance).toFixed(2); - } - if (list && result.data) { - var item = document.createElement("div"); - item.className = "aksrt-revenue-page__item"; - item.innerHTML = - "
¥" + - Number(result.data.amount || amount).toFixed(2) + - "" + - method + - "
pending
"; - list.insertBefore(item, list.firstChild); - } - form.reset(); - }) - .catch(function () { - if (btn) { - btn.disabled = false; - btn.textContent = "提交提现申请"; - } - if (msg) { - msg.hidden = false; - msg.textContent = "网络错误"; - } - }); - }); + var button = form.querySelector('button[type="submit"]'); + if (button) { + button.disabled = true; + button.textContent = '提交中…'; + } + request({ amount: amount, method: method, account_info: account }).then(function (result) { + if (button) { + button.disabled = false; + button.textContent = '提交提现申请'; + } + if (!result.ok || !result.json || !result.json.success) { + setMessage((result.json && result.json.message) || '提现失败,请稍后重试。', true); + return; + } + var response = result.json.data || {}; + setMessage(response.message || '提现申请已提交。', false); + if (balance && typeof response.wallet_balance !== 'undefined') { + balance.textContent = '¥' + Number(response.wallet_balance).toFixed(2); + } + if (list) { + var item = document.createElement('div'); + item.className = 'aksrt-revenue-page__item'; + var main = document.createElement('div'); + var amountEl = document.createElement('strong'); + amountEl.textContent = '¥' + Number(response.amount || amount).toFixed(2); + var methodEl = document.createElement('span'); + methodEl.textContent = method; + main.appendChild(amountEl); + main.appendChild(methodEl); + var state = document.createElement('div'); + var pending = document.createElement('span'); + pending.textContent = 'pending'; + var time = document.createElement('time'); + time.textContent = 'just now'; + state.appendChild(pending); + state.appendChild(time); + item.appendChild(main); + item.appendChild(state); + list.insertBefore(item, list.firstChild); + } + form.reset(); + }).catch(function () { + if (button) { + button.disabled = false; + button.textContent = '提交提现申请'; + } + setMessage('网络错误,请稍后重试。', true); + }); + }); })(); diff --git a/inc/helpers/aksrt-transactions.php b/inc/helpers/aksrt-transactions.php index dc67ef56..c681a085 100644 --- a/inc/helpers/aksrt-transactions.php +++ b/inc/helpers/aksrt-transactions.php @@ -55,8 +55,8 @@ function aksrt_get_revenue_page_view(int $user_id): array { $total_withdrawn = 0.0; $records = []; - if (class_exists('\AKSRT\Credits\Credits')) { - $balance = (float) \AKSRT\Credits\Credits::balance($user_id); + if (class_exists('\AKSRT\Credits\Balance')) { + $balance = (float) \AKSRT\Credits\Balance::balance($user_id); } if (class_exists('\AKSRT\Payment\Revenue')) { diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php index ff20d13e..bc8e7898 100644 --- a/inc/helpers/aksrt-user-center.php +++ b/inc/helpers/aksrt-user-center.php @@ -103,8 +103,8 @@ if (!function_exists('aksrt_get_user_center_tab_url')) { */ function aksrt_get_user_center_tab_url(string $tab): string { $tab = sanitize_key($tab); - if ('invite' === $tab) { - $tab = 'promotion'; + if ('invite' === $tab || 'promotion' === $tab) { + $tab = 'revenue'; } $url = remove_query_arg([ 'tab', 'posts_page', 'comments_page', 'favorites_page', 'credits_page', @@ -154,7 +154,7 @@ if (!function_exists('aksrt_capture_user_center_view')) { 'vip' => 'vip', 'orders' => 'orders', 'auth' => 'auth', - 'promotion' => 'promotion', + 'revenue' => 'revenue', 'settings' => 'settings', 'header' => 'header', 'sidebar' => 'sidebar', @@ -179,7 +179,7 @@ if (!function_exists('aksrt_capture_user_center_panel')) { * Capture a built-in or registered user-center panel body. */ function aksrt_capture_user_center_panel(string $tab_key, array $tab, array $context): string { - $built_in_tabs = ['profile', 'level', 'posts', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'promotion', 'settings']; + $built_in_tabs = ['profile', 'level', 'posts', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'revenue', 'settings']; if (in_array($tab_key, $built_in_tabs, true)) { return aksrt_capture_user_center_view($tab_key, $context); } @@ -284,8 +284,8 @@ if (!function_exists('aksrt_user_center_resolve_tabs')) { $active = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : $default; if ('signin' === $active || 'assets' === $active) { $active = 'credits'; - } elseif ('invite' === $active) { - $active = 'promotion'; + } elseif ('invite' === $active || 'promotion' === $active) { + $active = 'revenue'; } elseif ('vip' === $active) { // Prefer dedicated vip tab when registered; else credits (VIP shop). $active = in_array('vip', $keys, true) ? 'vip' : 'credits'; @@ -344,7 +344,8 @@ if (!function_exists('aksrt_user_center_runtime_config')) { 'activeTab' => $active_tab, 'tabAliases' => [ 'signin' => 'credits', - 'invite' => 'promotion', + 'invite' => 'revenue', + 'promotion' => 'revenue', 'assets' => 'credits', // vip → vip when panel exists; SPA may still map missing keys via server resolve. 'vip' => 'vip', @@ -491,9 +492,14 @@ if (!function_exists('aksrt_get_user_center_render_context')) { $runtime_config = aksrt_user_center_runtime_config($user_id, $tabs['default'], $active_tab, $tabs['urls']); $runtime_config['avatarUpload'] = aksrt_user_center_avatar_upload_config($allow_avatar_upload); $revenue_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_revenue_settings() : []; + $show_invite_section = $show_credit_tabs + && class_exists('\AKSRT\Settings\Settings') + && \AKSRT\Settings\Settings::invite_enabled() + && \AKSRT\Settings\Settings::invite_registration_enabled(); + $show_revenue_section = !empty($revenue_settings['enabled']) || !empty($affiliate_settings['enabled']); $new_post_url = function_exists('aksrt_get_new_post_url') ? aksrt_get_new_post_url() : ''; - $revenue_url = (!empty($revenue_settings['enabled']) || !empty($affiliate_settings['enabled'])) && function_exists('aksrt_get_revenue_page_url') - ? aksrt_get_revenue_page_url() + $revenue_url = isset($tabs['tabs']['revenue']) + ? aksrt_get_user_center_tab_url('revenue') : ''; $render_context = [ @@ -534,16 +540,14 @@ if (!function_exists('aksrt_get_user_center_render_context')) { 'show_vip' => !empty($vip_settings['enabled']), 'show_credit_tabs' => $show_credit_tabs, 'affiliate_enabled' => $show_credit_tabs && !empty($affiliate_settings['enabled']), + 'show_revenue_section' => $show_revenue_section, 'currency_name' => $currency_name, 'credit_balance' => (float) ($dashboard['credit_balance'] ?? 0.0), 'credits_enabled' => $show_credit_tabs, 'balance_enabled' => $balance_enabled, 'wallet_balance' => $wallet_balance, 'vip_settings' => $vip_settings, - 'show_invite_section' => $show_credit_tabs - && class_exists('\AKSRT\Settings\Settings') - && \AKSRT\Settings\Settings::invite_enabled() - && \AKSRT\Settings\Settings::invite_registration_enabled(), + 'show_invite_section' => $show_invite_section, 'user_email' => (string) $current_user->user_email, 'runtime_config' => $runtime_config, 'cover_upload_config' => aksrt_user_center_cover_upload_config($allow_bg_upload), diff --git a/inc/helpers/user-center-renderers/profile-invite.php b/inc/helpers/user-center-renderers/profile-invite.php new file mode 100644 index 00000000..beef040c --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-invite.php @@ -0,0 +1,24 @@ + +
+
+ +
+

+

+
+
+

+

+

—
—
—
+ +

+

+
diff --git a/inc/helpers/user-center-renderers/profile-promotion.php b/inc/helpers/user-center-renderers/profile-promotion.php deleted file mode 100644 index e4755602..00000000 --- a/inc/helpers/user-center-renderers/profile-promotion.php +++ /dev/null @@ -1,92 +0,0 @@ - -
-
- -
-

-

-
-
-

- -
- -
- -
-
-

-

-
-
-
- - — - -
-
- - — - -
-
- - — - -
-
-
- - - -
-
-

-
-

-
- -
-
-
-

-

-
- -
-
-
-
-
-
diff --git a/inc/helpers/user-center-renderers/profile-revenue.php b/inc/helpers/user-center-renderers/profile-revenue.php new file mode 100644 index 00000000..dae74ec1 --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-revenue.php @@ -0,0 +1,60 @@ +' . esc_html__('请先登录', 'aksrt') . ''; + return; +} + +$show_revenue_section = !empty($context['show_revenue_section']); +$show_invite_section = !empty($context['show_invite_section']); +$revenue_view = function_exists('aksrt_get_revenue_page_view') + ? aksrt_get_revenue_page_view($user_id) + : ['balance' => 0, 'total_earned' => 0, 'total_withdrawn' => 0, 'records' => []]; +$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_withdrawal_settings() : []; +$min_amount = (float) ($settings['min_amount'] ?? 50); +$fee_rate = (float) ($settings['service_charge_rate'] ?? 0); +$withdraw_text = (string) ($settings['text'] ?? ''); +$withdrawals = class_exists('\AKSRT\Payment\Withdrawal') + ? \AKSRT\Payment\Withdrawal::get_instance()->get_user_withdrawals($user_id, ['limit' => 20]) + : []; +$records = (array) ($revenue_view['records'] ?? []); +$page = max(1, isset($_GET['rev_page']) ? (int) $_GET['rev_page'] : 1); +$per_page = 20; +$total_pages = max(1, (int) ceil(max(1, count($records)) / $per_page)); +$paged_records = array_slice($records, ($page - 1) * $per_page, $per_page); +$revenue_url = static function (int $target_page): string { + $url = function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('revenue') : home_url('/profile/?tab=revenue'); + return $target_page > 1 ? add_query_arg('rev_page', $target_page, $url) : $url; +}; +?> +
+
+

+

+ +
+ + +
+
¥
+
¥
+
¥
+
+
+

+

¥

+
+

¥
1) : ?>

+ + + +
diff --git a/inc/helpers/user-center-renderers/sidebar-creator.php b/inc/helpers/user-center-renderers/sidebar-creator.php index 9303b3d4..d7fc7e98 100644 --- a/inc/helpers/user-center-renderers/sidebar-creator.php +++ b/inc/helpers/user-center-renderers/sidebar-creator.php @@ -26,9 +26,9 @@ if ($creation_url === '' && $revenue_url === '') { - + - + diff --git a/inc/helpers/user-center-renderers/sidebar-nav.php b/inc/helpers/user-center-renderers/sidebar-nav.php index b0f27387..ec9e682e 100644 --- a/inc/helpers/user-center-renderers/sidebar-nav.php +++ b/inc/helpers/user-center-renderers/sidebar-nav.php @@ -11,7 +11,6 @@ $level_url = isset($dashboard_tabs['level']) ? aksrt_get_user_center_tab_url('level') : ''; $auth_url = isset($dashboard_tabs['auth']) ? aksrt_get_user_center_tab_url('auth') : ''; -$promotion_url = isset($dashboard_tabs['promotion']) ? aksrt_get_user_center_tab_url('promotion') : ''; $orders_available = isset($dashboard_tabs['orders']); $orders_url = $orders_available ? (string) ($orders_url ?? '') : ''; @@ -29,13 +28,6 @@ $services = array_values(array_filter([ 'url' => $auth_url, 'tab' => 'auth', ], - [ - 'label' => __('推广中心', 'aksrt'), - 'icon' => 'user-plus', - 'tone' => 'promotion', - 'url' => $promotion_url, - 'tab' => 'promotion', - ], [ 'label' => __('我的订单', 'aksrt'), 'icon' => 'receipt', diff --git a/inc/modules/user-parts/urls.php b/inc/modules/user-parts/urls.php index ad51d024..c6395dca 100644 --- a/inc/modules/user-parts/urls.php +++ b/inc/modules/user-parts/urls.php @@ -32,7 +32,8 @@ function aksrt_get_user_center_url(string $tab = ''): string { 'assets' => 'credits', 'vip' => 'vip', // dedicated vip tab when registered; resolver falls back if missing 'signin' => 'credits', - 'invite' => 'promotion', + 'invite' => 'revenue', + 'promotion' => 'revenue', ]; if (isset($aliases[$tab])) { $tab = $aliases[$tab]; @@ -86,12 +87,16 @@ endif; if (!function_exists('aksrt_get_revenue_page_url')) : /** - * 收益中心页面 URL(模板 templates/pages/page-revenue.php)。 + * 收益中心 URL — 嵌在用户中心 tab=revenue。 */ function aksrt_get_revenue_page_url(): string { - return function_exists('aksrt_get_template_url') - ? aksrt_get_template_url('revenue') - : home_url('/revenue/'); + if (function_exists('aksrt_get_user_center_tab_url')) { + return aksrt_get_user_center_tab_url('revenue'); + } + if (function_exists('aksrt_get_user_center_url')) { + return aksrt_get_user_center_url('revenue'); + } + return home_url('/profile/?tab=revenue'); } endif; diff --git a/src/Admin/Schema/SchemaUsers.php b/src/Admin/Schema/SchemaUsers.php index 6680e212..a1b1ddf4 100644 --- a/src/Admin/Schema/SchemaUsers.php +++ b/src/Admin/Schema/SchemaUsers.php @@ -526,7 +526,7 @@ class SchemaUsers { 'option_key' => 'aksrt_settings', 'groups' => [ ['key' => 'center', 'title' => '用户中心', 'fields' => [ - ['key' => 'user_center_tabs', 'label' => '用户中心 Tab', 'type' => 'input', 'default' => 'profile,level,posts,comments,favorites,vip,credits,promotion,auth', 'help' => '逗号分隔。level=我的等级;promotion=推广中心;auth=官方认证;等级、VIP、订单、推广中心和认证会按功能开关自动注入'], + ['key' => 'user_center_tabs', 'label' => '用户中心 Tab', 'type' => 'input', 'default' => 'profile,level,posts,comments,favorites,vip,credits,revenue,auth', 'help' => '逗号分隔。level=我的等级;revenue=收益中心;auth=官方认证;等级、VIP、订单、收益中心和认证会按功能开关自动注入'], ['key' => 'user_cover_upload', 'label' => '封面上传', 'type' => 'switch', 'default' => true], ['key' => 'user_social_links', 'label' => '社交链接', 'type' => 'switch', 'default' => true], ['key' => 'user_desc_std', 'label' => '默认签名', 'type' => 'input', 'default' => '这家伙很懒…', 'desc' => '用户未设置个人简介时显示的默认文案'], diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php index 92591163..f1f93c48 100644 --- a/src/Core/ScriptsPart1b.php +++ b/src/Core/ScriptsPart1b.php @@ -679,13 +679,22 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js'; ); } - // 收益中心页 - if (is_page_template('templates/pages/page-revenue.php') || (bool) get_query_var('aksrt_revenue')) { - wp_enqueue_style( + // 收益中心:旧路由重定向页 / 用户中心 tab=revenue。 + $is_revenue_surface = is_page_template('templates/pages/page-revenue.php') + || (bool) get_query_var('aksrt_revenue') + || ( + (function_exists('aksrt_is_user_center_page') && aksrt_is_user_center_page()) + && isset($_GET['tab']) + && in_array(sanitize_key((string) wp_unslash($_GET['tab'])), ['revenue', 'invite', 'promotion'], true) + ); + if ($is_revenue_surface) { + $revenue_js = get_template_directory() . '/assets/js/revenue.js'; + wp_enqueue_script( 'aksrt-revenue', - $base_uri . '/assets/css/routes/revenue.css', - ['aksrt-tokens'], - $version + $base_uri . '/assets/js/revenue.js', + ['aksrt-script'], + file_exists($revenue_js) ? (string) filemtime($revenue_js) : $version, + true ); } diff --git a/src/Credits/Balance.php b/src/Credits/Balance.php index ece322ea..f22dd78b 100644 --- a/src/Credits/Balance.php +++ b/src/Credits/Balance.php @@ -367,6 +367,7 @@ class Balance { 'tip_received' => __('收到打赏', 'aksrt'), 'admin' => __('管理员调整', 'aksrt'), 'exchange_from_credits' => __('积分兑换', 'aksrt'), + 'creator_income' => __('创作收益结算', 'aksrt'), 'withdraw_hold' => __('提现冻结', 'aksrt'), 'withdraw_refund' => __('提现退回', 'aksrt'), ]; diff --git a/src/Payment/Revenue.php b/src/Payment/Revenue.php index faf7f729..49287f33 100644 --- a/src/Payment/Revenue.php +++ b/src/Payment/Revenue.php @@ -54,13 +54,52 @@ class Revenue { public function settle(int $revenue_id): bool { global $wpdb; - return $wpdb->update( - $this->table(), - ['status' => 'settled', 'settled_at' => current_time('mysql')], - ['id' => $revenue_id, 'status' => 'pending'], - ['%s', '%s'], - ['%d', '%s'] - ) !== false; + if (!class_exists('\AKSRT\Credits\Balance')) { + return false; + } + + $wpdb->query('START TRANSACTION'); + try { + $revenue = $wpdb->get_row($wpdb->prepare( + "SELECT id, user_id, amount, order_number FROM {$this->table()} WHERE id = %d AND status = %s FOR UPDATE", + $revenue_id, + 'pending' + ), ARRAY_A); + if (!is_array($revenue)) { + $wpdb->query('ROLLBACK'); + return false; + } + + $credited = \AKSRT\Credits\Balance::add( + (int) $revenue['user_id'], + (float) $revenue['amount'], + 'creator_income', + ['type' => 'revenue', 'id' => $revenue_id], + sprintf(__('创作收益结算(订单 %s)', 'aksrt'), (string) $revenue['order_number']) + ); + if (is_wp_error($credited)) { + $wpdb->query('ROLLBACK'); + return false; + } + + $settled = $wpdb->update( + $this->table(), + ['status' => 'settled', 'settled_at' => current_time('mysql')], + ['id' => $revenue_id, 'status' => 'pending'], + ['%s', '%s'], + ['%d', '%s'] + ); + if ($settled !== 1) { + $wpdb->query('ROLLBACK'); + return false; + } + + $wpdb->query('COMMIT'); + return true; + } catch (\Throwable $e) { + $wpdb->query('ROLLBACK'); + return false; + } } /** diff --git a/src/Settings/UserRuntime.php b/src/Settings/UserRuntime.php index 8197aea9..6afd8363 100644 --- a/src/Settings/UserRuntime.php +++ b/src/Settings/UserRuntime.php @@ -83,14 +83,14 @@ class UserRuntime { */ public static function get_user_center_settings(): array { $settings = Settings::get_all(); - $raw_tabs = $settings['user_center_tabs'] ?? 'profile,level,posts,comments,credits,promotion,favorites,auth'; + $raw_tabs = $settings['user_center_tabs'] ?? 'profile,level,posts,comments,credits,revenue,favorites,auth'; $tabs = is_array($raw_tabs) ? $raw_tabs : preg_split('/[\s,]+/', (string) $raw_tabs, -1, PREG_SPLIT_NO_EMPTY); // Built-in switchable panels. VIP is always auto-injected when enabled // by AKSRT\User\Center — keep it allowed for explicit config too. - $allowed_tabs = ['profile', 'level', 'posts', 'comments', 'favorites', 'credits', 'promotion', 'vip', 'orders', 'auth', 'settings']; + $allowed_tabs = ['profile', 'level', 'posts', 'comments', 'favorites', 'credits', 'revenue', 'vip', 'orders', 'auth', 'settings']; $normalized_tabs = ['profile']; foreach ((array) $tabs as $tab) { diff --git a/src/User/Center.php b/src/User/Center.php index 3eb47dbb..df4ca286 100644 --- a/src/User/Center.php +++ b/src/User/Center.php @@ -91,9 +91,9 @@ class Center { 'priority' => 50, 'type' => 'panel', ], - 'promotion' => [ - 'label' => __('推广中心', 'aksrt'), - 'icon' => 'user-plus', + 'revenue' => [ + 'label' => __('收益中心', 'aksrt'), + 'icon' => 'chart-line', 'priority' => 55, 'type' => 'panel', ], @@ -127,10 +127,16 @@ class Center { && !empty(\AKSRT\User\Level::settings()['enabled']); $credits_on = class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::credits_enabled(); $balance_on = class_exists('\AKSRT\Credits\Balance') && \AKSRT\Credits\Balance::enabled(); - $promotion_on = $credits_on + $invite_on = $credits_on && class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::invite_enabled() && \AKSRT\Settings\Settings::invite_registration_enabled(); + $revenue_on = class_exists('\AKSRT\Settings\Settings') + && ( + !empty(\AKSRT\Settings\Settings::get_payment_revenue_settings()['enabled']) + || !empty(\AKSRT\Settings\Settings::get_payment_affiliate_settings()['enabled']) + ); + $revenue_tab_on = $revenue_on || $invite_on; $vip_on = class_exists('\AKSRT\Settings\Settings') && !empty(\AKSRT\Settings\Settings::get_payment_vip_settings()['enabled']); @@ -177,7 +183,7 @@ class Center { if (($credits_on || $balance_on) && !in_array('credits', $order, true)) { $order[] = 'credits'; } - if ($promotion_on && !in_array('promotion', $order, true)) { + if ($revenue_tab_on && !in_array('revenue', $order, true)) { $insert_at = count($order); foreach (['credits', 'favorites', 'posts'] as $anchor) { $idx = array_search($anchor, $order, true); @@ -186,7 +192,7 @@ class Center { break; } } - array_splice($order, $insert_at, 0, ['promotion']); + array_splice($order, $insert_at, 0, ['revenue']); } $auth_on = class_exists('\AKSRT\User\AuthApply') && !empty(\AKSRT\User\AuthApply::settings()['enabled']); @@ -207,7 +213,7 @@ class Center { if ($key === 'credits' && !$credits_on && !$balance_on) { continue; } - if ($key === 'promotion' && !$promotion_on) { + if ($key === 'revenue' && !$revenue_tab_on) { continue; } if ($key === 'vip' && !$vip_on) { diff --git a/templates/pages/page-revenue.php b/templates/pages/page-revenue.php index c54876ab..d68e755b 100644 --- a/templates/pages/page-revenue.php +++ b/templates/pages/page-revenue.php @@ -3,178 +3,25 @@ * Template Name: AKSRT - Revenue * Template Post Type: page * - * Creator revenue center: overview, ledger, withdraw request. + * Legacy revenue route canonicalizes to the private user-center tab. * * @package AKSRT */ defined('ABSPATH') || exit; -get_header(); +$target = function_exists('aksrt_get_user_center_tab_url') + ? aksrt_get_user_center_tab_url('revenue') + : home_url('/profile/?tab=revenue'); +$rev_page = isset($_GET['rev_page']) ? max(1, (int) $_GET['rev_page']) : 1; +if ($rev_page > 1) { + $target = add_query_arg('rev_page', $rev_page, $target); +} if (!is_user_logged_in()) { - $login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink()); - wp_safe_redirect($login_url); + wp_safe_redirect(aksrt_get_login_url($target)); exit; } -$user_id = get_current_user_id(); -$revenue_view = function_exists('aksrt_get_revenue_page_view') - ? aksrt_get_revenue_page_view($user_id) - : ['balance' => 0, 'total_earned' => 0, 'total_withdrawn' => 0, 'records' => []]; - -$balance = (float) ($revenue_view['balance'] ?? 0); -$total_earned = (float) ($revenue_view['total_earned'] ?? 0); -$total_withdrawn = (float) ($revenue_view['total_withdrawn'] ?? 0); -$records = $revenue_view['records'] ?? []; - -$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_withdrawal_settings() : []; -$min_amount = (float) ($settings['min_amount'] ?? 50); -$fee_rate = (float) ($settings['service_charge_rate'] ?? 0); -$withdraw_text = (string) ($settings['text'] ?? ''); - -$withdrawals = []; -if (class_exists('\AKSRT\Payment\Withdrawal')) { - $withdrawals = \AKSRT\Payment\Withdrawal::get_instance()->get_user_withdrawals($user_id, ['limit' => 20]); -} - -$per_page = 20; -$paged = max(1, (int) ($_GET['rev_page'] ?? 1)); -$total_records = count($records); -$total_pages = max(1, (int) ceil(max(1, $total_records) / $per_page)); -if ($total_records === 0) { - $total_pages = 1; -} -$paged_records = array_slice($records, ($paged - 1) * $per_page, $per_page); -?> - -
- - -
- __('创作收益', 'aksrt'), - 'subtitle' => __('查看分成明细并发起提现。', 'aksrt'), - 'active' => 'revenue', - ]); - } else { - echo '

' . esc_html__('创作收益', 'aksrt') . '

'; - echo '

' . esc_html__('查看分成明细并发起提现。', 'aksrt') . '

'; - } - ?> - -
-
-
-
- ¥ - -
-
-
-
-
- ¥ - -
-
-
-
-
- ¥ - -
-
-
- -
-
-

- -
- -
- - - -

- - -
-
- -
-

- -
- -
-
- ¥ - -
-
- - -
-
- -
- -

- -
-
- -
-

- -
- -
-
- - -
- - ¥ - -
- -
- 1 && $total_records > 0) : ?> - - - -

- -
-
-
- -