feat(提交): 列表标出来自题单的提交
Some checks failed
Deploy / deploy (push) Has been cancelled

submission 新增 problemset_id,学生从 /problemset/:id/problem/:pid 入口提交时
前端带上、后端落库,提交列表在题目后面挂一个「题单 xxx」的标签,点了进题单。

只是来源标记:题单进度和奖章仍由判完之后的 recordSolvedProblem 按「已加入且含
这道题的所有题单」记账,和从哪个入口进来无关,所以这个字段带错顶多标签不准,
不影响成绩。校验只确认这道题在那个题单里 —— 不查 visible / status、也不查有没有
加入,藏起来的题单里还困着已加入的学生;对不上就当没带,提交照收。

外键 ON DELETE SET NULL:删题单不该带走提交,清掉标记就行。索引建成部分索引
(WHERE problemset_id IS NOT NULL),绝大多数提交不来自题单,全列索引是给 12 万行
白建一遍;谓词能被 problemset_id = $1 蕴含,删题单时的外键检查也用得上它。

列表按页单独查一次题单标题,没有把 problemset join 进那条调过的深翻页查询。
比赛提交恒为 null:题单只收非比赛题。

历史回填从 problemset_submission 取「当年首次 AC 那条」,其余老提交无从判断入口,
一律留空。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7AsqceaUfC81k7UCDcSk2
This commit is contained in:
2026-09-02 22:06:29 -06:00
parent cf1eea0810
commit e624c62502
8 changed files with 4100 additions and 11 deletions

View File

@@ -137,6 +137,11 @@ async function submit() {
if (contestID) {
data.contestId = parseInt(contestID)
}
// 从题单入口进来的,把来源题单一起报上去:提交列表要据此标出「来自题单」。
// 只是来源标记,题单进度仍由后端判完之后自己记账(见上面那段注释)
if (problemSetId) {
data.problemSetId = parseInt(problemSetId)
}
// 2. 提交代码到后端
isSubmittingRequest.value = true
try {

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { NButton, NTag, NText } from "naive-ui"
import { NButton, NFlex, NTag, NText } from "naive-ui"
import { useRouteQuery } from "@vueuse/router"
import {
adminRejudge,
@@ -322,9 +322,9 @@ const columns = computed(() => {
{
title: renderTableTitle("题目", "streamline-emojis:blossom"),
key: "problem",
minWidth: 300,
render: (row) =>
h(
minWidth: 360,
render: (row) => {
const problem = h(
ButtonWithSearch,
{
type: "题目",
@@ -332,7 +332,28 @@ const columns = computed(() => {
onSearch: () => (query.problem = row.problem),
},
() => `${row.problem} ${row.problemTitle}`,
),
)
// 从题单入口做出来的提交才有这个标记(后端只在提交时记来源),
// 同一道题从普通题库刷的不会带。老提交里只有当年首次 AC 那条有
const problemSet = row.problemSet
if (!problemSet) return problem
return h(NFlex, { align: "center", size: 8, wrap: false }, () => [
problem,
h(
NTag,
{
size: "small",
round: true,
type: "info",
bordered: false,
style: { cursor: "pointer", flexShrink: 0 },
onClick: () =>
window.open("/problemset/" + problemSet.id, "_blank"),
},
() => "题单 " + problemSet.title,
),
])
},
},
{
title: renderTableTitle("语言", "streamline-ultimate-color:earth-pin-2"),