feat: problems local stats, user status, admin panel enhancements, rating text

- Problems page: replace Luogu pass rate with local submission stats
  (local_submit_count, local_ac_count)
- Problems page: add user AC/fail status column (user_ac, user_fail_count)
- Admin users: add total_submissions and total_ac columns
- Admin users: add detail panel with submissions/rating/redeem tabs
- Admin: new endpoint GET /api/v1/admin/users/{id}/rating-history
- Rating history: note field includes problem title via JOIN
- Me page: translate task codes to friendly labels with icons
- Me page: problem links in rating history are clickable
- Wrong book service, learning note scoring, note image controller
- Backend SQL uses batch queries for performance

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
这个提交包含在:
cryptocommuniums-afk
2026-02-16 17:35:22 +08:00
父节点 7860414ae5
当前提交 cfbe9a0363
修改 22 个文件,包含 1366 行新增26 行删除

查看文件

@@ -1,6 +1,6 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import {
ArrowRightLeft,
Calendar,
@@ -135,6 +135,33 @@ export default function MePage() {
return name;
};
const formatRatingNote = (note: string, type: string): React.ReactNode => {
// Daily task codes
const taskLabels: Record<string, [string, string]> = {
login_checkin: ["每日签到 🎯", "Daily Sign-in 🎯"],
daily_submit: ["每日提交 📝", "Daily Submission 📝"],
first_ac: ["首次通过 ⭐", "First AC ⭐"],
code_quality: ["代码质量 🛠️", "Code Quality 🛠️"],
};
if (type === "daily_task" && taskLabels[note]) {
return isZh ? taskLabels[note][0] : taskLabels[note][1];
}
// Solution view: "Problem 1234:Title"
const m = note.match(/^Problem (\d+):(.*)$/);
if (m) {
const pid = m[1];
const title = m[2].trim();
return (
<a href={`/problems/${pid}`} className="hover:underline text-[color:var(--mc-diamond)]">
{isZh ? `查看题解 P${pid}` : `View Solution P${pid}`}
{title ? ` · ${title}` : ""}
</a>
);
}
// Redeem items keep original text
return note;
};
const loadAll = async () => {
setLoading(true);
setError("");
@@ -388,7 +415,7 @@ export default function MePage() {
{item.change > 0 ? <TrendingUp size={14} /> : <TrendingDown size={14} />}
{item.change > 0 ? `+${item.change}` : item.change}
</span>
<span className="ml-2">{item.note}</span>
<span className="ml-2">{formatRatingNote(item.note, item.type)}</span>
</span>
<span className="text-[color:var(--mc-stone-dark)]">
{new Date(item.created_at * 1000).toLocaleString()}