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>
这个提交包含在:
@@ -362,6 +362,10 @@ CREATE TABLE IF NOT EXISTS wrong_book (
|
||||
problem_id INTEGER NOT NULL,
|
||||
last_submission_id INTEGER,
|
||||
note TEXT NOT NULL DEFAULT "",
|
||||
note_score INTEGER NOT NULL DEFAULT 0,
|
||||
note_rating INTEGER NOT NULL DEFAULT 0,
|
||||
note_feedback_md TEXT NOT NULL DEFAULT "",
|
||||
note_scored_at INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY(user_id, problem_id),
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
@@ -577,6 +581,11 @@ CREATE TABLE IF NOT EXISTS daily_task_logs (
|
||||
EnsureColumn(db, "problem_drafts", "stdin", "stdin TEXT NOT NULL DEFAULT ''");
|
||||
EnsureColumn(db, "problem_solution_jobs", "max_solutions",
|
||||
"max_solutions INTEGER NOT NULL DEFAULT 3");
|
||||
EnsureColumn(db, "wrong_book", "note_score", "note_score INTEGER NOT NULL DEFAULT 0");
|
||||
EnsureColumn(db, "wrong_book", "note_rating", "note_rating INTEGER NOT NULL DEFAULT 0");
|
||||
EnsureColumn(db, "wrong_book", "note_feedback_md", "note_feedback_md TEXT NOT NULL DEFAULT ''");
|
||||
EnsureColumn(db, "wrong_book", "note_scored_at", "note_scored_at INTEGER NOT NULL DEFAULT 0");
|
||||
EnsureColumn(db, "wrong_book", "note_images_json", "note_images_json TEXT NOT NULL DEFAULT '[]'");
|
||||
EnsureColumn(db, "problem_solutions", "variant", "variant INTEGER NOT NULL DEFAULT 1");
|
||||
EnsureColumn(db, "problem_solutions", "idea_md", "idea_md TEXT NOT NULL DEFAULT ''");
|
||||
EnsureColumn(db, "problem_solutions", "explanation_md",
|
||||
@@ -727,6 +736,154 @@ void SeedDemoData(SqliteDb& db) {
|
||||
0,
|
||||
now);
|
||||
}
|
||||
|
||||
// Always seed C++基础课程任务(幂等:按 slug 检测存在)。
|
||||
{
|
||||
const auto existing = QueryOneId(raw, "SELECT id FROM problems WHERE slug='cpp-basic-01-hello' LIMIT 1");
|
||||
if (!existing.has_value()) {
|
||||
const int64_t created = now;
|
||||
struct CourseItem {
|
||||
const char* slug;
|
||||
const char* title;
|
||||
int diff;
|
||||
const char* source;
|
||||
const char* md;
|
||||
const char* tags[6];
|
||||
};
|
||||
const CourseItem items[] = {
|
||||
{
|
||||
"cpp-basic-01-hello",
|
||||
"C++基础01:环境配置与Hello World(VSCode)",
|
||||
1,
|
||||
"course:cpp-basic:01",
|
||||
R"MD(# C++基础01:环境配置与Hello World(VSCode)
|
||||
|
||||
## 学习目标
|
||||
- 安装并打开 VSCode,创建并运行第一个 C++14 程序
|
||||
- 学会新建文件、保存、运行、查看输出
|
||||
- 了解 `main()`、`#include <iostream>`、`cout`
|
||||
|
||||
## 推荐视频(观看后写笔记)
|
||||
- 保姆级:VSCode + mingw64 配置 C/C++(BV1tg411N7Fq)
|
||||
- https://www.bilibili.com/video/BV1tg411N7Fq/
|
||||
- 每天五分钟学C++:01 开发工具(BV1dK4y137bk,系列入口)
|
||||
- https://www.bilibili.com/video/BV1dK4y137bk/
|
||||
|
||||
## 参考图文
|
||||
- LoongBa 极简配置:GCC/VSCode/HelloWorld
|
||||
- https://github.com/LoongBa/Cpp_Beginner_Guide
|
||||
|
||||
## 练习(完成至少 2 题)
|
||||
- B2002 Hello, World! https://www.luogu.com.cn/problem/B2002
|
||||
- P1000 超级玛丽游戏 https://www.luogu.com.cn/problem/P1000
|
||||
|
||||
## 提交要求
|
||||
- 在本题页面下方“学习笔记”区域写下:
|
||||
1) 你安装了什么、遇到什么坑、怎么解决
|
||||
2) 你的 HelloWorld 代码
|
||||
3) 你学到的 3 个关键词
|
||||
)MD",
|
||||
{"cpp-basic", "vscode", "io", "", "", ""}
|
||||
},
|
||||
{
|
||||
"cpp-basic-02-io",
|
||||
"C++基础02:输入输出与变量",
|
||||
1,
|
||||
"course:cpp-basic:02",
|
||||
R"MD(# C++基础02:输入输出与变量
|
||||
|
||||
## 学习目标
|
||||
- 会用 `cin` 读入、`cout` 输出
|
||||
- 理解变量:`int / long long / double / char / string`
|
||||
|
||||
## 推荐视频
|
||||
- 每天五分钟学C++:02 输出、03 变量(系列入口见上)
|
||||
- https://www.bilibili.com/video/BV1dK4y137bk/
|
||||
|
||||
## 练习
|
||||
- P1001 A+B Problem https://www.luogu.com.cn/problem/P1001
|
||||
- B2008 计算 (a+b)×c 的值 https://www.luogu.com.cn/problem/B2008
|
||||
- P5704 字母转换 https://www.luogu.com.cn/problem/P5704
|
||||
|
||||
## 提交要求
|
||||
- 上传/填写学习笔记:写出 `cin/cout` 模板、常见错误(空格/换行)
|
||||
)MD",
|
||||
{"cpp-basic", "io", "types", "", "", ""}
|
||||
},
|
||||
{
|
||||
"cpp-basic-03-branch",
|
||||
"C++基础03:分支结构(if / switch)",
|
||||
2,
|
||||
"course:cpp-basic:03",
|
||||
R"MD(# C++基础03:分支结构(if / switch)
|
||||
|
||||
## 学习目标
|
||||
- 会写 `if / else if / else` 与基本逻辑运算
|
||||
- 能处理边界与分类讨论
|
||||
|
||||
## 练习
|
||||
- B2035 判断数正负 https://www.luogu.com.cn/problem/B2035
|
||||
- P5711 闰年判断 https://www.luogu.com.cn/problem/P5711
|
||||
- P1909 买铅笔 https://www.luogu.com.cn/problem/P1909
|
||||
|
||||
## 提交要求
|
||||
- 笔记里写清:你如何找“边界”(例如 0、最小/最大、等于条件)
|
||||
)MD",
|
||||
{"cpp-basic", "branch", "logic", "", "", ""}
|
||||
},
|
||||
{
|
||||
"cpp-basic-04-loop",
|
||||
"C++基础04:循环结构(for / while)",
|
||||
2,
|
||||
"course:cpp-basic:04",
|
||||
R"MD(# C++基础04:循环结构(for / while)
|
||||
|
||||
## 学习目标
|
||||
- 会用循环做:计数、累加、打印图形
|
||||
|
||||
## 练习
|
||||
- B2083 画矩形 https://www.luogu.com.cn/problem/B2083
|
||||
- P1421 小玉买文具 https://www.luogu.com.cn/problem/P1421
|
||||
|
||||
## 提交要求
|
||||
- 笔记里写:循环三要素(初始化/条件/更新)+ 你调试的方法
|
||||
)MD",
|
||||
{"cpp-basic", "loop", "debug", "", "", ""}
|
||||
},
|
||||
{
|
||||
"cpp-basic-05-array",
|
||||
"C++基础05:数组入门(一维)",
|
||||
3,
|
||||
"course:cpp-basic:05",
|
||||
R"MD(# C++基础05:数组入门(一维)
|
||||
|
||||
## 学习目标
|
||||
- 会定义数组、遍历、统计
|
||||
|
||||
## 练习
|
||||
- P1427 小鱼的数字游戏 https://www.luogu.com.cn/problem/P1427
|
||||
- P1428 小鱼比可爱 https://www.luogu.com.cn/problem/P1428
|
||||
|
||||
## 提交要求
|
||||
- 笔记里写:数组下标从 0/1 的选择;如何避免越界
|
||||
)MD",
|
||||
{"cpp-basic", "array", "", "", "", ""}
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& it : items) {
|
||||
InsertProblem(raw, it.slug, it.title, it.md, it.diff, it.source, "", "", created);
|
||||
const auto pid = QueryOneId(raw, std::string("SELECT id FROM problems WHERE slug='") + it.slug + "' LIMIT 1");
|
||||
if (pid.has_value()) {
|
||||
for (const char* tag : it.tags) {
|
||||
if (!tag || !*tag) continue;
|
||||
InsertProblemTag(raw, *pid, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace csp::db
|
||||
|
||||
在新工单中引用
屏蔽一个用户