From c32738267ff1f4ee9763f4e6bde92515cb857195 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 18 Jul 2026 21:28:32 +0800 Subject: [PATCH] feat(header): user-box density 1:1 (check-in, wallet, VIP, signout) Add payvip/check-in/wallet/badges strips, guest OAuth strip, confirm signout modal + REST check-in wiring for zibll header parity. --- assets/css/frontend/nav/header-userbox.css | 96 ++++++++++++ assets/js/header-userbox.js | 113 +++++++++++++++ header.php | 35 ++++- inc/bootstrap.php | 1 + inc/helpers/aksrt-header-userbox.php | 161 +++++++++++++++++++++ src/AKSRT/Core/ScriptsPart1b.php | 21 +++ 6 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 assets/css/frontend/nav/header-userbox.css create mode 100644 assets/js/header-userbox.js create mode 100644 inc/helpers/aksrt-header-userbox.php diff --git a/assets/css/frontend/nav/header-userbox.css b/assets/css/frontend/nav/header-userbox.css new file mode 100644 index 00000000..9db422c2 --- /dev/null +++ b/assets/css/frontend/nav/header-userbox.css @@ -0,0 +1,96 @@ +/** + * Header user-box density (check-in / wallet / payvip / signout modal). + * + * @package AKSRT + */ + +.aksrt-header-wallet { + display: flex; + gap: 10px; + justify-content: space-between; + padding: 8px 10px; + border-radius: 8px; + background: var(--surface-2, rgba(0, 0, 0, 0.04)); + color: inherit; + text-decoration: none; + font-size: 12px; +} + +.aksrt-header-wallet__item b { + margin-left: 2px; +} + +.aksrt-header-checkin { + width: 100%; + border: 0; + border-radius: 6px; + padding: 8px 10px; + cursor: pointer; + background: rgba(255, 193, 7, 0.18); + color: #b78103; +} + +.aksrt-header-checkin.is-done { + opacity: 0.75; + cursor: default; +} + +.aksrt-header-payvip .payvip-icon, +.aksrt-header-payvip .but { + display: block; + text-align: center; + text-decoration: none; + padding: 8px 10px; + border-radius: 6px; + background: linear-gradient(90deg, #ff6b6b, #f04494); + color: #fff; +} + +.aksrt-header-badges { + flex-wrap: wrap; + gap: 4px; +} + +#modal_signout.modal { + position: fixed; + inset: 0; + z-index: 1100; + display: none; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.45); + padding: 16px; +} + +#modal_signout.modal.is-open, +#modal_signout.modal.show { + display: flex; +} + +#modal_signout .modal-content { + background: var(--main-bg-color, #fff); + border-radius: 10px; + min-width: 280px; + max-width: 92vw; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18); +} + +#modal_signout .but.c-red { + background: #e74c3c; + color: #fff; + text-decoration: none; + padding: 8px 14px; + border-radius: 6px; +} + +#modal_signout .but { + border: 0; + background: var(--surface-2, #f0f0f0); + padding: 8px 14px; + border-radius: 6px; + cursor: pointer; +} + +.user-dropdown .social_loginbar { + padding: 0 10px 12px; +} diff --git a/assets/js/header-userbox.js b/assets/js/header-userbox.js new file mode 100644 index 00000000..12f20529 --- /dev/null +++ b/assets/js/header-userbox.js @@ -0,0 +1,113 @@ +/** + * Header user-box: check-in REST + signout modal. + * + * @package AKSRT + */ +(function () { + 'use strict'; + + function restBase() { + if (window.aksrtData && window.aksrtData.restUrl) return window.aksrtData.restUrl.replace(/\/?$/, '/'); + if (window.aksrtData && window.aksrtData.siteUrl) return String(window.aksrtData.siteUrl).replace(/\/?$/, '/') + 'wp-json/'; + return '/wp-json/'; + } + + function restNonce() { + return (window.aksrtData && window.aksrtData.restNonce) || ''; + } + + function toast(msg, type) { + if (window.aksrt && typeof window.aksrt.toast === 'function') { + window.aksrt.toast(msg, type || 'info'); + return; + } + // eslint-disable-next-line no-alert + window.alert(msg); + } + + function openModal(id) { + var modal = document.getElementById(id); + if (!modal) return; + modal.classList.add('is-open', 'show'); + modal.setAttribute('aria-hidden', 'false'); + } + + function closeModal(id) { + var modal = document.getElementById(id); + if (!modal) return; + modal.classList.remove('is-open', 'show'); + modal.setAttribute('aria-hidden', 'true'); + } + + function initSignoutModal() { + document.addEventListener('click', function (e) { + var openBtn = e.target.closest('[data-target="#modal_signout"], [href="#modal_signout"]'); + if (openBtn) { + e.preventDefault(); + openModal('modal_signout'); + return; + } + var dismiss = e.target.closest('#modal_signout [data-dismiss="modal"]'); + if (dismiss) { + e.preventDefault(); + closeModal('modal_signout'); + return; + } + if (e.target && e.target.id === 'modal_signout') { + closeModal('modal_signout'); + } + }); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeModal('modal_signout'); + }); + } + + function initCheckin() { + document.addEventListener('click', function (e) { + var btn = e.target.closest('[data-aksrt-checkin="1"]'); + if (!btn || btn.disabled) return; + e.preventDefault(); + btn.disabled = true; + fetch(restBase() + 'aksrt/v1/signin', { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'X-WP-Nonce': restNonce(), + }, + body: '{}', + }) + .then(function (r) { + return r.json().then(function (data) { + return { ok: r.ok, data: data }; + }); + }) + .then(function (res) { + if (!res.ok) { + var msg = (res.data && (res.data.message || res.data.msg)) || '签到失败'; + toast(msg, 'danger'); + btn.disabled = false; + return; + } + toast((res.data && res.data.message) || '签到成功', 'success'); + btn.classList.add('is-done'); + btn.innerHTML = ' 今日已签到'; + }) + .catch(function () { + toast('网络错误', 'danger'); + btn.disabled = false; + }); + }); + } + + function init() { + initSignoutModal(); + initCheckin(); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/header.php b/header.php index 115573c1..628799c9 100644 --- a/header.php +++ b/header.php @@ -303,11 +303,32 @@ if (!empty($aksrt_bg_styles)) { + ' . $checkin . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } + if (function_exists('aksrt_header_user_wallet_mini')) { + echo '
' . aksrt_header_user_wallet_mini(get_current_user_id()) . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } else { + ?>
+ +
@@ -326,7 +347,7 @@ if (!empty($aksrt_bg_styles)) { - + @@ -370,6 +391,16 @@ if (!empty($aksrt_bg_styles)) { + + render_social_buttons(__('快速登录', 'aksrt')); + if ($oauth_html !== '') { + echo '
' . $oauth_html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } + ?> + @@ -385,6 +416,8 @@ if (!empty($aksrt_bg_styles)) { + + diff --git a/inc/bootstrap.php b/inc/bootstrap.php index 11b1766a..2996a21e 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -44,6 +44,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-credits.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-null.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-ajax-list.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-links.php'; +require_once AKSRT_DIR . '/inc/helpers/aksrt-header-userbox.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php'; require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php'; diff --git a/inc/helpers/aksrt-header-userbox.php b/inc/helpers/aksrt-header-userbox.php new file mode 100644 index 00000000..442ec454 --- /dev/null +++ b/inc/helpers/aksrt-header-userbox.php @@ -0,0 +1,161 @@ + 0 ? $user_id : get_current_user_id(); + if ($user_id <= 0 || !class_exists('\AKSRT\Credits\Signin')) { + return ''; + } + $cfg = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::get_signin_reward_settings() + : ['enabled' => false]; + if (empty($cfg['enabled'])) { + return ''; + } + + // Lightweight today status via private API if available. + $signed = false; + try { + $signin = \AKSRT\Credits\Signin::get_instance(); + // Prefer public method if present. + if (method_exists($signin, 'is_signed_today')) { + $signed = (bool) $signin->is_signed_today($user_id); + } else { + // Fallback: direct meta/table probe via rest_today logic is heavy; use option day key. + $day = wp_date('Y-m-d'); + $signed = (bool) get_user_meta($user_id, 'aksrt_signin_' . $day, true); + } + } catch (\Throwable $e) { + $signed = false; + } + + if ($signed) { + return ''; + } + + return ''; + } +endif; + +if (!function_exists('aksrt_header_user_wallet_mini')) : + /** + * Mini wallet strip: credits + cash balance. + */ + function aksrt_header_user_wallet_mini(int $user_id = 0): string { + $user_id = $user_id > 0 ? $user_id : get_current_user_id(); + if ($user_id <= 0) { + return ''; + } + $credits = function_exists('aksrt_user_credits') ? (int) aksrt_user_credits($user_id) : 0; + $currency = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::get_currency_name() + : __('积分', 'aksrt'); + $wallet = 0.0; + if (class_exists('\AKSRT\Credits\Balance')) { + $wallet = (float) \AKSRT\Credits\Balance::balance($user_id); + } + $center = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('assets') : '#'; + + $html = ''; + $html .= ' ' + . esc_html($currency) . ' ' . esc_html(number_format_i18n($credits)) . ''; + $html .= ' ' + . esc_html__('余额', 'aksrt') . ' ' . esc_html(number_format_i18n($wallet, 2)) . ''; + $html .= ''; + return $html; + } +endif; + +if (!function_exists('aksrt_header_user_badges_row')) : + /** + * Identity / medal chips under profile. + */ + function aksrt_header_user_badges_row(int $user_id = 0): string { + $user_id = $user_id > 0 ? $user_id : get_current_user_id(); + if ($user_id <= 0 || !function_exists('aksrt_render_user_identity_chips')) { + return ''; + } + $chips = aksrt_render_user_identity_chips($user_id, 'card'); + if ($chips === '') { + return ''; + } + return '
' . $chips . '
'; + } +endif; + +if (!function_exists('aksrt_header_payvip_bar')) : + /** + * VIP status / open CTA bar. + */ + function aksrt_header_payvip_bar(int $user_id = 0): string { + $user_id = $user_id > 0 ? $user_id : get_current_user_id(); + if ($user_id <= 0) { + return ''; + } + $badge = function_exists('aksrt_render_vip_badge') ? aksrt_render_vip_badge($user_id, 'md') : ''; + if ($badge !== '') { + return '
' . $badge . '
'; + } + // Guest-style open VIP for non-members if VIP feature on. + $vip_url = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('vip') : '#'; + $enabled = true; + if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_vip_settings')) { + $vs = \AKSRT\Settings\Settings::get_vip_settings(); + $enabled = !empty($vs['enabled']) || !empty($vs['vip1_enabled']) || !empty($vs['vip2_enabled']); + } + if (!$enabled) { + return ''; + } + return '
' + . '' + . '' + . esc_html__('开通会员 尊享会员权益', 'aksrt') + . '
'; + } +endif; + +if (!function_exists('aksrt_render_signout_modal')) : + /** + * Confirm logout modal markup (once per page). + */ + function aksrt_render_signout_modal(): void { + static $done = false; + if ($done || !is_user_logged_in()) { + return; + } + $done = true; + $logout = wp_logout_url(home_url('/')); + ?> + +