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>
这个提交包含在:
@@ -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()}
|
||||
|
||||
在新工单中引用
屏蔽一个用户