diff --git a/frontend/components/ops/__tests__/opsUsersLeaderboard.test.ts b/frontend/components/ops/__tests__/opsUsersLeaderboard.test.ts
new file mode 100644
index 00000000..ed347380
--- /dev/null
+++ b/frontend/components/ops/__tests__/opsUsersLeaderboard.test.ts
@@ -0,0 +1,27 @@
+import fs from "node:fs";
+import path from "node:path";
+
+function assert(condition: unknown, message: string): asserts condition {
+ if (!condition) throw new Error(message);
+}
+
+export function runTests() {
+ const projectRoot = process.cwd();
+ const usersPageSource = fs.readFileSync(
+ path.join(projectRoot, "components", "ops", "users", "UsersPageClient.tsx"),
+ "utf8",
+ );
+
+ assert(
+ usersPageSource.includes("leaderboardName") &&
+ usersPageSource.includes("String(entry.username || \"\").trim()") &&
+ usersPageSource.includes("TG${entry.telegram_id}"),
+ "ops users leaderboard must fall back when username is blank",
+ );
+ assert(
+ usersPageSource.includes("本周暂无积分记录") &&
+ usersPageSource.includes("本周积分") &&
+ !usersPageSource.includes("entry.username ?? `TG${entry.telegram_id}`"),
+ "ops users leaderboard must show weekly points clearly and avoid zero-point fake rankings",
+ );
+}
diff --git a/frontend/components/ops/users/UsersPageClient.tsx b/frontend/components/ops/users/UsersPageClient.tsx
index 32665636..28a73b00 100644
--- a/frontend/components/ops/users/UsersPageClient.tsx
+++ b/frontend/components/ops/users/UsersPageClient.tsx
@@ -7,6 +7,13 @@ import { Button } from "@/components/ui/button";
import { opsApi } from "@/lib/ops-api";
import type { OpsUser, LeaderboardEntry } from "@/types/ops";
+function leaderboardName(entry: LeaderboardEntry) {
+ const username = String(entry.username || "").trim();
+ if (username) return username;
+ if (entry.telegram_id != null) return `TG${entry.telegram_id}`;
+ return "未知用户";
+}
+
export function UsersPageClient() {
const [query, setQuery] = useState("");
const [searching, setSearching] = useState(false);
@@ -130,16 +137,16 @@ export function UsersPageClient() {
本周排行榜 Top {leaderboard.length}
{leaderboard.length === 0 ? (
- 无数据
+ 本周暂无积分记录
) : (
{leaderboard.map((entry, i) => (
-
#{entry.rank ?? i + 1}
- {entry.username ?? `TG${entry.telegram_id}`}
+ {leaderboardName(entry)}
- {entry.weekly_points ?? 0} 分
+ 本周积分 {entry.weekly_points ?? 0} 分
))}
diff --git a/src/database/db_manager.py b/src/database/db_manager.py
index 9e0e4508..9a87f190 100644
--- a/src/database/db_manager.py
+++ b/src/database/db_manager.py
@@ -2916,22 +2916,26 @@ class DBManager:
conn.row_factory = sqlite3.Row
cursor = conn.execute(
"""
- SELECT
- username,
- u.points AS points,
- u.message_count AS message_count,
- u.telegram_id AS telegram_id,
- COALESCE(a.points,
- CASE
- WHEN u.weekly_points_week = ? THEN COALESCE(u.weekly_points, 0)
- ELSE 0
- END
- ) AS weekly_points
- FROM users u
- LEFT JOIN weekly_points_archive a
- ON a.telegram_id = u.telegram_id
- AND a.week_key = ?
- ORDER BY weekly_points DESC, points DESC, message_count DESC
+ SELECT *
+ FROM (
+ SELECT
+ username,
+ u.points AS points,
+ u.message_count AS message_count,
+ u.telegram_id AS telegram_id,
+ COALESCE(a.points,
+ CASE
+ WHEN u.weekly_points_week = ? THEN COALESCE(u.weekly_points, 0)
+ ELSE 0
+ END
+ ) AS weekly_points
+ FROM users u
+ LEFT JOIN weekly_points_archive a
+ ON a.telegram_id = u.telegram_id
+ AND a.week_key = ?
+ ) ranked
+ WHERE weekly_points > 0
+ ORDER BY weekly_points DESC, points DESC, message_count DESC, telegram_id ASC
LIMIT ?
""",
(week_key, week_key, limit),
diff --git a/tests/test_weekly_points_display.py b/tests/test_weekly_points_display.py
index 5e700a21..88ac8c4e 100644
--- a/tests/test_weekly_points_display.py
+++ b/tests/test_weekly_points_display.py
@@ -51,3 +51,32 @@ def test_points_rank_display_uses_invite_points_copy(tmp_path):
assert "积分获取: 邀请付费用户" in text
assert "本周发言积分" not in text
assert "今日发言积分" not in text
+
+
+def test_weekly_leaderboard_excludes_zero_weekly_points_and_orders_active_users(tmp_path):
+ db = DBManager(str(tmp_path / "weekly-leaderboard.db"))
+ now = datetime.now()
+ week_key = f"{now.isocalendar()[0]}-W{now.isocalendar()[1]:02d}"
+ users = [
+ (1001, "old_high_total", 10000, 0, week_key),
+ (1002, "", 100, 8, week_key),
+ (1003, "active_winner", 200, 12, week_key),
+ (1004, "last_week_only", 900, 50, "2025-W01"),
+ ]
+ for telegram_id, username, points, weekly_points, weekly_week in users:
+ db.upsert_user(telegram_id, username)
+ with db._get_connection() as conn: # noqa: SLF001
+ conn.execute(
+ """
+ UPDATE users
+ SET points = ?, message_count = ?, weekly_points = ?, weekly_points_week = ?
+ WHERE telegram_id = ?
+ """,
+ (points, 10, weekly_points, weekly_week, telegram_id),
+ )
+ conn.commit()
+
+ rows = db.get_weekly_leaderboard(limit=10)
+
+ assert [row["telegram_id"] for row in rows] == [1003, 1002]
+ assert [row["weekly_points"] for row in rows] == [12, 8]