diff --git a/assets/css/routes/user-center/shell.css b/assets/css/routes/user-center/shell.css index c17db312..aa45bd2c 100644 --- a/assets/css/routes/user-center/shell.css +++ b/assets/css/routes/user-center/shell.css @@ -124,16 +124,30 @@ body.aksrt-user-center .uc-content, .uc-header__actions { position: relative; + display: flex; + align-items: center; + gap: 6px; +} + +.uc-header__signin-btn.is-done { + color: var(--color-success, #10b981); +} + +.uc-header__message-btn { + position: relative; } .uc-header__action-badge { + position: absolute; + top: -5px; + right: -5px; display: inline-flex; align-items: center; justify-content: center; - min-width: 18px; - height: 18px; - margin-left: 6px; - padding: 0 5px; + min-width: 16px; + height: 16px; + margin: 0; + padding: 0 4px; border-radius: 999px; background: var(--color-error, #ef4444); color: #fff; diff --git a/assets/js/credits.js b/assets/js/credits.js index 223db5cf..493b91b4 100644 --- a/assets/js/credits.js +++ b/assets/js/credits.js @@ -15,6 +15,7 @@ return; } var activePoll = null; + var signinBusy = false; function api(path, options) { return window.AksrtRequest.request(path, options || {}).then(function (response) { @@ -36,15 +37,42 @@ // ===== 签到 ===== function initSignin() { - var root = document.querySelector('[data-aksrt-signin]'); - if (!root || root.dataset.aksrtSigninInitialized === '1') return; - root.dataset.aksrtSigninInitialized = '1'; - var status = root.querySelector('[data-signin-status]'); - var btn = root.querySelector('[data-signin-btn]'); - if (!btn) return; + var buttons = Array.prototype.slice.call(document.querySelectorAll('[data-signin-btn]')); + var freshButtons = buttons.filter(function (button) { + return button.dataset.aksrtSigninInitialized !== '1'; + }); + if (!freshButtons.length) return; + freshButtons.forEach(function (button) { + button.dataset.aksrtSigninInitialized = '1'; + }); - function setStatus(text) { if (status) status.textContent = text; } - function setStatusHtml(html) { if (status) status.innerHTML = html; } + function statuses() { + return document.querySelectorAll('[data-signin-status]'); + } + + function currentButtons() { + return document.querySelectorAll('[data-signin-btn]'); + } + + function setStatus(text) { + statuses().forEach(function (status) { status.textContent = text; }); + } + + function setStatusHtml(html) { + statuses().forEach(function (status) { status.innerHTML = html; }); + } + + function setButtonState(state) { + currentButtons().forEach(function (button) { + var label = button.querySelector('[data-signin-label]'); + var text = state === 'done' + ? button.getAttribute('data-signin-done-label') || '已签到' + : (state === 'busy' ? '…' : button.getAttribute('data-signin-idle-label') || '签到'); + if (label) label.textContent = text; + button.disabled = state !== 'idle'; + button.classList.toggle('is-done', state === 'done'); + }); + } function refresh() { api('/signin/today').then(function (r) { @@ -57,16 +85,15 @@ var j = r.json; if (!j.enabled) { setStatus('签到功能未启用'); - btn.disabled = true; + setButtonState('done'); return; } if (j.signed) { setStatusHtml('已签到 ' + j.streak + ' 天 +' + j.reward_today); - btn.disabled = true; - btn.innerHTML = '已签到'; + setButtonState('done'); } else { setStatusHtml('连续 ' + j.streak + ' 天,+' + j.reward_if_signin); - btn.disabled = false; + setButtonState('idle'); } }).catch(function (err) { if (window.console) console.error('[AKSRT Credits] /signin/today 请求失败:', err); @@ -124,45 +151,49 @@ onSuccess(); } - btn.addEventListener('click', function () { - btn.disabled = true; - btn.innerHTML = '…'; + freshButtons.forEach(function (button) { + button.addEventListener('click', function () { + if (signinBusy) return; + signinBusy = true; + setButtonState('busy'); - triggerCaptcha(function () { - doSignin(); - }, function (msg) { - resetCaptcha(); - if (msg) notyf(msg, 'danger'); - btn.disabled = false; - btn.innerHTML = '签到'; - }); - - function doSignin() { - notyf('正在签到…', 'load', '', 'aksrt-balance-signin-toast'); - api('/signin', { - method: 'POST', - body: JSON.stringify({ captcha: collectCaptcha() }) - }).then(function (r) { - if (!r.ok) { - resetCaptcha(); - notyf((r.json && r.json.message) || '签到失败', 'danger', 5, 'aksrt-balance-signin-toast'); - btn.disabled = false; - btn.innerHTML = '签到'; - return; - } - notyf('签到成功,获得 ' + r.json.reward + ' 积分(连续 ' + r.json.streak + ' 天)', 'success', 5, 'aksrt-balance-signin-toast'); - var balanceEls = document.querySelectorAll('[data-credits-value]'); - balanceEls.forEach(function (el) { el.textContent = r.json.balance.toLocaleString(); }); - refresh(); - }).catch(function () { + triggerCaptcha(function () { + doSignin(); + }, function (msg) { + signinBusy = false; resetCaptcha(); - notyf('网络错误,请重试', 'danger', 5, 'aksrt-balance-signin-toast'); - btn.disabled = false; - btn.innerHTML = '签到'; + if (msg) notyf(msg, 'danger'); + setButtonState('idle'); }); - } + }); }); + function doSignin() { + notyf('正在签到…', 'load', '', 'aksrt-balance-signin-toast'); + api('/signin', { + method: 'POST', + body: JSON.stringify({ captcha: collectCaptcha() }) + }).then(function (r) { + signinBusy = false; + if (!r.ok) { + resetCaptcha(); + notyf((r.json && r.json.message) || '签到失败', 'danger', 5, 'aksrt-balance-signin-toast'); + setButtonState('idle'); + return; + } + notyf(r.json.message || '签到成功', 'success', 5, 'aksrt-balance-signin-toast'); + var balanceEls = document.querySelectorAll('[data-credits-value]'); + balanceEls.forEach(function (el) { el.textContent = r.json.balance.toLocaleString(); }); + setButtonState('done'); + refresh(); + }).catch(function () { + signinBusy = false; + resetCaptcha(); + notyf('网络错误,请重试', 'danger', 5, 'aksrt-balance-signin-toast'); + setButtonState('idle'); + }); + } + refresh(); } diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php index 9f791ab2..9df9bac5 100644 --- a/inc/helpers/aksrt-user-center.php +++ b/inc/helpers/aksrt-user-center.php @@ -496,6 +496,13 @@ if (!function_exists('aksrt_get_user_center_render_context')) { $currency_name = (string) ($dashboard['currency_name'] ?? __('积分', 'aksrt')); $balance_enabled = (bool) ($dashboard['balance_enabled'] ?? false); $wallet_balance = (float) ($dashboard['wallet_balance'] ?? 0.0); + $signin_settings = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::get_signin_reward_settings() + : ['enabled' => false]; + $signin_enabled = !empty($signin_settings['enabled']) && class_exists('\AKSRT\Credits\Signin'); + $signin_signed_today = $signin_enabled + ? \AKSRT\Credits\Signin::get_instance()->is_signed_today($user_id) + : false; $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); @@ -539,6 +546,8 @@ if (!function_exists('aksrt_get_user_center_render_context')) { 'currency_name' => $currency_name, 'credit_balance' => (float) ($dashboard['credit_balance'] ?? 0.0), 'credits_enabled' => $show_credit_tabs, + 'signin_enabled' => $signin_enabled, + 'signin_signed_today' => $signin_signed_today, 'balance_enabled' => $balance_enabled, 'wallet_balance' => $wallet_balance, 'vip_settings' => $vip_settings, diff --git a/inc/helpers/user-center-renderers/credits-balance.php b/inc/helpers/user-center-renderers/credits-balance.php index 1b7e4b95..f38e3e28 100644 --- a/inc/helpers/user-center-renderers/credits-balance.php +++ b/inc/helpers/user-center-renderers/credits-balance.php @@ -34,12 +34,14 @@ $currency = (string) ($currency_name ?? __('积分', 'aksrt'));
+ - +
diff --git a/inc/helpers/user-center-renderers/credits-signin.php b/inc/helpers/user-center-renderers/credits-signin.php index 886e8b06..4e66f0de 100644 --- a/inc/helpers/user-center-renderers/credits-signin.php +++ b/inc/helpers/user-center-renderers/credits-signin.php @@ -14,14 +14,6 @@ if (!$credits_enabled) { return; } -if (function_exists('aksrt_is_captcha_enabled_for') && aksrt_is_captcha_enabled_for('signin')) : - ?> - - get_recent_signins($user_id, 30); diff --git a/inc/helpers/user-center-renderers/profile-header.php b/inc/helpers/user-center-renderers/profile-header.php index e72005d1..58c881ee 100644 --- a/inc/helpers/user-center-renderers/profile-header.php +++ b/inc/helpers/user-center-renderers/profile-header.php @@ -69,9 +69,24 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
+ + + + + - - + + 0) : ?> @@ -79,4 +94,9 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
+ +
+ +
+ diff --git a/tests/widget-system/toast-contract.test.cjs b/tests/widget-system/toast-contract.test.cjs index 4ff9dd58..6d1e786d 100644 --- a/tests/widget-system/toast-contract.test.cjs +++ b/tests/widget-system/toast-contract.test.cjs @@ -197,6 +197,10 @@ test('balance center actions use direct fixed-id notyf flows', () => { const credits = read('assets/js/credits.js'); const scripts = read('src/Core/ScriptsPart1b.php'); const css = read('assets/css/routes/user-center/credits.css'); + const shell = read('assets/css/routes/user-center/shell.css'); + const header = read('inc/helpers/user-center-renderers/profile-header.php'); + const signin = read('inc/helpers/user-center-renderers/credits-signin.php'); + const context = read('inc/helpers/aksrt-user-center.php'); const renderers = [ 'inc/helpers/user-center-renderers/credits-balance.php', 'inc/helpers/user-center-renderers/credits-wallet.php', @@ -228,10 +232,21 @@ test('balance center actions use direct fixed-id notyf flows', () => { }); assert.match(credits, /notyf\('等待支付结果…', 'load', '', 'aksrt-balance-charge-payment-toast'\)/); + assert.match(credits, /querySelectorAll\('\[data-signin-btn\]'\)/); + assert.match(credits, /freshButtons\.forEach[\s\S]*?addEventListener\('click'/); assert.match(credits, /onTimeout:[\s\S]*?notyf\([^;]+?'danger'[^;]+?'aksrt-balance-charge-payment-toast'\)/); assert.match(credits, /register\('credits',[\s\S]*?initSignin\(\);[\s\S]*?initBalanceCharge\(\);[\s\S]*?initTransfer\(\);[\s\S]*?initPointsExchange\(\);[\s\S]*?initWithdraw\(\);/); assert.match(credits, /aksrtSigninInitialized[\s\S]*?aksrtPointsExchangeInitialized[\s\S]*?aksrtWithdrawInitialized/); assert.match(scripts, /'aksrt-credits',[\s\S]*?\/assets\/js\/credits\.js',[\s\S]*?\[[^\]]*'aksrt-notyf'[^\]]*\]/); + assert.ok(header.indexOf('data-signin-btn') < header.indexOf('uc-header__message-btn')); + assert.match(header, /uc-header__message-btn[^>]+aria-label="<\?php esc_attr_e\('消息通知'/); + assert.doesNotMatch(header, /]*><\/i>\s*<\?php esc_html_e\('消息通知'/); + assert.match(header, /data-signin-captcha[\s\S]*?aksrt_render_captcha\('signin'\)/); + assert.doesNotMatch(signin, /data-signin-captcha|aksrt_render_captcha/); + assert.match(context, /'signin_enabled'\s*=>\s*\$signin_enabled/); + assert.match(context, /'signin_signed_today'\s*=>\s*\$signin_signed_today/); + assert.match(shell, /\.uc-header__actions\s*\{[^}]*display:\s*flex;[^}]*gap:\s*6px;/s); + assert.match(shell, /\.uc-header__message-btn\s*\{[^}]*position:\s*relative;/s); assert.doesNotMatch(renderers, /data-(?:signin|aksrt-(?:charge|transfer|points-exchange|withdraw))-tip/); assert.doesNotMatch(css, /\.uc-(?:modal|balance-card)__tip/); });