diff --git a/assets/admin/src/schema/sections/forum/credits.js b/assets/admin/src/schema/sections/forum/credits.js index 154fabdb..ee38c282 100644 --- a/assets/admin/src/schema/sections/forum/credits.js +++ b/assets/admin/src/schema/sections/forum/credits.js @@ -11,6 +11,7 @@ export const creditsSchema = { bbs_credits_score_extra: 3, bbs_credits_essence: 10, bbs_credits_posts_hot: 5, + bbs_hot_recommend_threshold: 500, bbs_credits_adopted: 20, bbs_credits_daily_max: 50, }, @@ -27,6 +28,7 @@ export const creditsSchema = { { key: 'bbs_credits_score_extra', label: '被加分', type: 'number', default: 3, min: 0 }, { key: 'bbs_credits_essence', label: '被设精华', type: 'number', default: 10, min: 0 }, { key: 'bbs_credits_posts_hot', label: '成为热帖', type: 'number', default: 5, min: 0 }, + { key: 'bbs_hot_recommend_threshold', label: '热帖推荐分阈值', type: 'number', default: 500, min: 1, help: '推荐分达到该值触发热帖奖励与勋章累计' }, { key: 'bbs_credits_adopted', label: '被采纳', type: 'number', default: 20, min: 0 }, { key: 'bbs_credits_daily_max', label: '每日积分上限', type: 'number', default: 50, min: 0, help: '0 = 不限' }, ], diff --git a/assets/css/routes/user-center/credits.css b/assets/css/routes/user-center/credits.css index 94a19592..e9fb457f 100644 --- a/assets/css/routes/user-center/credits.css +++ b/assets/css/routes/user-center/credits.css @@ -993,3 +993,49 @@ body.uc-modal-open { .uc-badges-item .medal-icon { width: 28px; height: 28px; object-fit: contain; } .uc-badges-wear-tag { margin-left: 6px; } .uc-badges-empty { padding: 16px 0; text-align: center; } + + +/* Level / integral card */ +.uc-level-card { + margin: 16px 0; + padding: 16px; + border-radius: 12px; + background: linear-gradient(135deg, rgba(240,68,148,.12), rgba(99,102,241,.10)); + position: relative; + overflow: hidden; +} +.uc-level-card__badge { margin-top: 12px; } +.uc-level-card .progress, +.integral-progress.progress { + height: 8px; + border-radius: 999px; + background: rgba(127,127,127,.18); + overflow: hidden; +} +.uc-level-card .progress-bar { + height: 100%; + border-radius: 999px; + background: linear-gradient(90deg, #f04494, #6366f1); +} +.uc-level-growth__row { + display: grid; + grid-template-columns: 56px 1fr auto; + gap: 8px; + padding: 6px 0; + border-bottom: 1px solid rgba(127,127,127,.12); + font-size: 12px; +} +.uc-level-growth__row.is-done { opacity: .7; } +.uc-level-growth__row.is-next { font-weight: 700; color: var(--focus-color, #f04494); } +.uc-points-tasks__row.is-done { opacity: .72; } +.uc-points-tasks__progress-bar { + height: 6px; + border-radius: 999px; + background: rgba(127,127,127,.16); + overflow: hidden; +} +.uc-points-tasks__progress-bar > span { + display: block; + height: 100%; + background: linear-gradient(90deg, #22c55e, #16a34a); +} diff --git a/inc/classes/admin/schema/class-aksrt-admin-schema-forum-credits.php b/inc/classes/admin/schema/class-aksrt-admin-schema-forum-credits.php index 6458b47c..008c80a9 100644 --- a/inc/classes/admin/schema/class-aksrt-admin-schema-forum-credits.php +++ b/inc/classes/admin/schema/class-aksrt-admin-schema-forum-credits.php @@ -24,6 +24,7 @@ class AKSRT_Admin_Schema_Forum_Credits { ['key' => 'bbs_credits_score_extra', 'label' => '被加分', 'type' => 'number', 'default' => 3, 'min' => 0], ['key' => 'bbs_credits_essence', 'label' => '被设精华', 'type' => 'number', 'default' => 10, 'min' => 0], ['key' => 'bbs_credits_posts_hot', 'label' => '成为热帖', 'type' => 'number', 'default' => 5, 'min' => 0], + ['key' => 'bbs_hot_recommend_threshold', 'label' => '热帖推荐分阈值', 'type' => 'number', 'default' => 500, 'min' => 1, 'help' => '推荐分达到该值触发热帖奖励与勋章累计'], ['key' => 'bbs_credits_adopted', 'label' => '被采纳', 'type' => 'number', 'default' => 20, 'min' => 0], ['key' => 'bbs_credits_daily_max', 'label' => '每日积分上限', 'type' => 'number', 'default' => 50, 'min' => 0, 'help' => '0 = 不限'], ]], diff --git a/inc/classes/class-aksrt-points-free.php b/inc/classes/class-aksrt-points-free.php index b20984eb..a73d1747 100644 --- a/inc/classes/class-aksrt-points-free.php +++ b/inc/classes/class-aksrt-points-free.php @@ -13,6 +13,8 @@ class AKSRT_Points_Free { public const META_DETAIL = 'aksrt_free_points_detail'; public const META_LOGIN = 'aksrt_free_points_login_date'; + public const META_SIGNIN = 'aksrt_free_points_signin_date'; + public const META_TASKS = 'aksrt_free_points_task_dates'; /** * Task catalog: key => [label, short_desc, default_points] @@ -162,6 +164,7 @@ class AKSRT_Points_Free { } self::bump_detail($user_id, $points); + self::mark_task_done_today($user_id, $task_key); return $points; } @@ -182,13 +185,22 @@ class AKSRT_Points_Free { public static function task_list_for_user(int $user_id = 0): array { $cfg = self::settings(); $list = []; + $today = self::today_key(); $login_done = false; + $signin_done = false; + $task_dates = []; if ($user_id > 0) { - $login_done = (string) get_user_meta($user_id, self::META_LOGIN, true) === self::today_key(); + $login_done = (string) get_user_meta($user_id, self::META_LOGIN, true) === $today; + $signin_done = (string) get_user_meta($user_id, self::META_SIGNIN, true) === $today; + $raw = get_user_meta($user_id, self::META_TASKS, true); + $task_dates = is_array($raw) ? $raw : []; } + + $passive = ['post_like', 'post_favorite', 'followed', 'comment_like', 'bbs_essence', 'bbs_adopt', 'bbs_posts_hot', 'bbs_comment_hot', 'bbs_score_extra']; + $active_once = ['post_new', 'comment_new', 'bbs_posts_new', 'bbs_reply']; + foreach (self::catalog() as $key => $meta) { $pts = (int) ($cfg['tasks'][$key] ?? 0); - // Hide sign_up from daily task list (one-time); still grant on register. if ($key === 'sign_up') { continue; } @@ -196,10 +208,27 @@ class AKSRT_Points_Free { continue; } $status = 'todo'; - if (in_array($key, ['login', 'sign_in'], true) && $login_done) { + $progress = null; + $target = null; + if ($key === 'login' && $login_done) { $status = 'done_today'; - } elseif (in_array($key, ['post_like', 'post_favorite', 'followed', 'comment_like', 'bbs_essence', 'bbs_adopt', 'bbs_posts_hot', 'bbs_comment_hot', 'bbs_score_extra'], true)) { + $progress = 1; + $target = 1; + } elseif ($key === 'sign_in' && $signin_done) { + $status = 'done_today'; + $progress = 1; + $target = 1; + } elseif (in_array($key, $passive, true)) { $status = 'passive'; + } elseif (in_array($key, $active_once, true)) { + $target = 1; + $done_day = is_array($task_dates[$key] ?? null) ? (string) ($task_dates[$key]['day'] ?? '') : (string) ($task_dates[$key] ?? ''); + if ($done_day === $today) { + $status = 'done_today'; + $progress = 1; + } else { + $progress = 0; + } } $list[] = [ 'key' => $key, @@ -208,13 +237,39 @@ class AKSRT_Points_Free { 'points' => $pts, 'enabled' => true, 'status' => $status, - 'progress' => null, - 'target' => null, + 'progress' => $progress, + 'target' => $target, ]; } return $list; } + /** + * Mark a free-task as completed today (for progress DTO). + */ + public static function mark_task_done_today(int $user_id, string $task_key): void { + if ($user_id <= 0) { + return; + } + $task_key = sanitize_key($task_key); + $today = self::today_key(); + if ($task_key === 'login') { + update_user_meta($user_id, self::META_LOGIN, $today); + return; + } + if ($task_key === 'sign_in') { + update_user_meta($user_id, self::META_SIGNIN, $today); + return; + } + $raw = get_user_meta($user_id, self::META_TASKS, true); + $map = is_array($raw) ? $raw : []; + $map[$task_key] = ['day' => $today, 'ts' => time()]; + if (count($map) > 40) { + $map = array_slice($map, -40, null, true); + } + update_user_meta($user_id, self::META_TASKS, $map); + } + private static function today_key(): string { return (new DateTimeImmutable('today', wp_timezone()))->format('Y-m-d'); } diff --git a/inc/classes/forum/class-aksrt-forum-score-service.php b/inc/classes/forum/class-aksrt-forum-score-service.php index 3d47142a..b55c768d 100644 --- a/inc/classes/forum/class-aksrt-forum-score-service.php +++ b/inc/classes/forum/class-aksrt-forum-score-service.php @@ -222,7 +222,14 @@ class AKSRT_Forum_Score_Service { } private function maybe_reward_hot_topic(int $topic_id, float $score): void { - $hot_threshold = 500.0; + $config = $this->forum->get_config(); + $hot_threshold = (float) ($config['post_page']['hot_recommend_threshold'] + ?? $config['credits']['hot_recommend_threshold'] + ?? $config['other']['hot_topic_threshold'] + ?? 500); + if ($hot_threshold <= 0) { + $hot_threshold = 500.0; + } if ($score < $hot_threshold) { return; } @@ -239,7 +246,6 @@ class AKSRT_Forum_Score_Service { } do_action('aksrt_forum_topic_hot', $topic_id, (int) $author_id); update_post_meta($topic_id, '_aksrt_hot_rewarded', '1'); - $config = $this->forum->get_config(); $amount = (int) ($config['credits']['posts_hot'] ?? 5); if ($amount > 0) { if (class_exists('AKSRT_Points_Free')) { diff --git a/inc/classes/settings/class-aksrt-settings-forum-shop.php b/inc/classes/settings/class-aksrt-settings-forum-shop.php index aef038f1..8b8a2f3f 100644 --- a/inc/classes/settings/class-aksrt-settings-forum-shop.php +++ b/inc/classes/settings/class-aksrt-settings-forum-shop.php @@ -263,6 +263,7 @@ class AKSRT_Settings_Forum_Shop { 'breadcrumbs_bbs_home_name' => $str('bbs_breadcrumbs_bbs_home_name', '论坛'), 'breadcrumbs_plate_cat' => $bool('bbs_breadcrumbs_plate_cat', true), 'score_extra_max' => $int('bbs_score_extra_max', 5), + 'hot_recommend_threshold' => $int('bbs_hot_recommend_threshold', 500), 'score_deduct_max' => $int('bbs_score_deduct_max', 3), 'maxheight_enabled' => $bool('bbs_posts_maxheight_s'), 'maxheight' => $int('bbs_posts_maxheight', 800), @@ -337,6 +338,7 @@ class AKSRT_Settings_Forum_Shop { 'score_extra' => $int('bbs_credits_score_extra', 3), 'essence' => $int('bbs_credits_essence', 10), 'posts_hot' => $int('bbs_credits_posts_hot', 5), + 'hot_recommend_threshold' => $int('bbs_hot_recommend_threshold', 500), 'adopted' => $int('bbs_credits_adopted', 20), 'daily_max' => $int('bbs_credits_daily_max', 50), ], diff --git a/inc/helpers/user-center-renderers/credits-level.php b/inc/helpers/user-center-renderers/credits-level.php new file mode 100644 index 00000000..4aac7f47 --- /dev/null +++ b/inc/helpers/user-center-renderers/credits-level.php @@ -0,0 +1,107 @@ += $max; +if (!$is_max) { + $next_need = (int) ($opt['upgrade_integral_' . ($level + 1)] ?? 0); + if ($next_need <= $integral) { + // Resolve again in case meta lag. + $level = AKSRT_User_Level::resolve_level($integral, $cfg); + $is_max = $level >= $max; + $next_need = $is_max ? $integral : (int) ($opt['upgrade_integral_' . ($level + 1)] ?? $integral); + $name = (string) ($opt['name_' . $level] ?? ($prefix . $level)); + } +} + +if ($is_max) { + $proportion = 100; + $need_html = esc_html__('已为最高等级', 'aksrt'); + $frac_html = esc_html((string) $integral) . '/' . esc_html((string) $integral); +} else { + $den = max(1, $next_need); + $proportion = (int) min(100, max(0, ($integral / $den) * 100)); + $need_left = max(0, $next_need - $integral); + $need_html = sprintf( + /* translators: %s: remaining XP */ + esc_html__('升级还需%s经验', 'aksrt'), + '' . esc_html((string) $need_left) . '' + ); + $frac_html = esc_html((string) $integral) . '/' . esc_html((string) $next_need); +} + +$avatar = function_exists('aksrt_get_avatar_html') + ? aksrt_get_avatar_html($user_id, 42, ['class' => 'avatar avatar-mini']) + : get_avatar($user_id, 42); + +// Growth rows (next few levels). +$growth = ''; +$from = max(1, $level); +$to = min($max, $level + 5); +for ($i = $from; $i <= $to; $i++) { + $need_i = (int) ($opt['upgrade_integral_' . $i] ?? 0); + $nm = (string) ($opt['name_' . $i] ?? ($prefix . $i)); + $state = $integral >= $need_i ? 'is-done' : ($i === $level + 1 ? 'is-next' : ''); + $growth .= '
'; + $growth .= '' . esc_html($prefix . $i) . ''; + $growth .= '' . esc_html($nm) . ''; + $growth .= '' . esc_html((string) $need_i) . ''; + $growth .= '
'; +} +?> +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+ +
diff --git a/inc/helpers/user-center-renderers/credits-points-tasks.php b/inc/helpers/user-center-renderers/credits-points-tasks.php index fd4eaacf..f7b1d52a 100644 --- a/inc/helpers/user-center-renderers/credits-points-tasks.php +++ b/inc/helpers/user-center-renderers/credits-points-tasks.php @@ -201,6 +201,19 @@ $pane = isset($points_tasks_pane) ? sanitize_key((string) $points_tasks_pane)
+ 0) : + $pv = max(0, (int) $progress); + $tv = max(1, (int) $target); + $pct = (int) min(100, round(($pv / $tv) * 100)); + ?> +
+
+
+
+
diff --git a/inc/helpers/user-center-renderers/profile-credits.php b/inc/helpers/user-center-renderers/profile-credits.php index 2b9d59a6..21e49794 100644 --- a/inc/helpers/user-center-renderers/profile-credits.php +++ b/inc/helpers/user-center-renderers/profile-credits.php @@ -44,6 +44,9 @@ if (!empty($show_vip)) { if ($show_credits) { require $dir . '/credits-signin.php'; } +if (class_exists('AKSRT_User_Level')) { + require $dir . '/credits-level.php'; +} // Tabbed ledgers (Zibll record tabs). if ($show_wallet || $show_credits) :