fix(题目): 修掉题目页重构审查查出的问题——提交途中换题会把结果算到新题上、运行例子把对的菱形判错、点评完「下一题」不跳等

题目页重构之后按「功能对照 / 状态 / 结果页 / 布局与后端」四路审查了一遍,查出的问题一起修:

提交与换题
- 提交、画流程图途中换题:A 题格式化好的代码会盖掉 B 的草稿,A 判对了把 B 标成已解决、
  弹 B 的点评、题单里跳回题单。现在每个请求回来先核对题目,轮询和推送回来的也核对提交编号
- 画着流程图换到不能画的题,语言退到题目的第一门、还被记成偏好;现在回到学生习惯的那门
- 强制点评评完点「继续 / 下一题」,学生之前在看「题目」页签的话会停在原题
- 离开题目页之后判题、流程图的 WebSocket 一直连着,现在闲 15 分钟断开

运行例子
- 输出开头的空格被削掉,打印菱形这类题对的代码也显示「没对上」
- C 的编译警告拼进了「你的输出」(后台生成测试点也会写进 .out)
- Python 的语法错误(中文标点)被说成「下标有没有越界、除数是不是 0」
- 比赛里仍然把空格画成「·」、把不同的地方标红

入口与协作
- 比赛里不给「课堂统计」、不绑协作:房间和统计都按题号找,比赛题号和公开题撞号
- 「看他交过什么」按整名匹配,不再混进学号相近的同学
- 协作中点上下文条、相似题、下一题开新标签,协作不断
- 题单入口给管理员的「⋯」里留「本题提交」

结果与题面
- 「去自己输入拿 51 这些数试试」取到了题面 HTML 里的颜色值;「题目原话」露出 "
- 流程图 A 级的学生标题下还写着「还没交过」
- 「我的提交」上方补回「答案错误 × 3」这样的计数
- 流程图历次评分能点开看那一版的图;读分数失败不再说「还没交过流程图」;手机上能看流程图点评
- 「结果」页签有新结果没看时加粗上色,看过就恢复
- 空画布拖进第一个节点画布会跳一下;画布底下空着一条
- 比赛没开始写成「已结束」、倒计时超过一个月算错;分隔条拖到头工具栏被挤坏;题单里的题目页有备案页脚
- 流程图历次分数只读需要的几列(一个学生一道题 23 次提交:96 kB → 2 kB)
- 比赛密码、比赛没开始等几条学生看得到的英文报错换成中文

设计文档照实际做法更新了第 5.5、5.6、6、8、9、10、11 节。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-09-29 00:07:16 +08:00
1 parent ee66cef367
commit e7b95d211d
34 files changed
+579 -108

No files matched your search

@@ -4,6 +4,8 @@ import { useProblemStore } from "oj/store/problem"
import { useSubmissionStore } from "oj/store/submission"
import { useCodeStore } from "oj/store/code"
import { useTeacherCollab } from "../composables/teacherCollab"
import { useProblemPageContext } from "../composables/problemPageContext"
import { useBreakpoints } from "shared/composables/breakpoints"
import { useThemeVars } from "naive-ui"
import { parseTime } from "utils/functions"
import ResultHeader from "./ResultHeader.vue"
@@ -30,6 +32,8 @@ const { submission, syntaxErrorInfo, resultSegment, formattedBeforeSubmit } =
storeToRefs(submissionStore)
const { problem } = storeToRefs(useProblemStore())
const codeStore = useCodeStore()
const ctx = useProblemPageContext()
const { isDesktop } = useBreakpoints()
/** 协作中的老师自己还没交过:先摆学生最近一次交的 */
const teacherCollab = useTeacherCollab()
@@ -37,8 +41,25 @@ const teacherCollab = useTeacherCollab()
const theme = useThemeVars()
const { samplesRunAt } = submissionStore.trial
type Segment = "submit" | "samples" | "custom"
const segments = computed<{ value: Segment; label: string; time: string }[]>(() => [
/**
* 手机上流程图题只能看点评、不能画(设计文档 5.6)。画不了,语言也就落不到 Flowchart 上,
* 下面的 drawing 永远是假 —— 原来学生在电脑上画过、AI 评过的,手机上一条都看不到。
* 这时多给一段「流程图点评」,只读。
*/
const flowchartReadable = computed(
() => !isDesktop.value && !!problem.value?.allowFlowchart && ctx.value.entry !== "contest",
)
/** 在看「流程图点评」那一段。只在这一页记着:点运行例子、提交时照常切回对应的分段 */
const viewingFlowchart = ref(false)
watch(
() => [submissionStore.resultSeq, problem.value?.id, flowchartReadable.value],
() => {
viewingFlowchart.value = false
},
)
type Segment = "submit" | "samples" | "custom" | "flowchart"
const codeSegments = computed<{ value: Segment; label: string; time: string }[]>(() => [
{
value: "submit",
// 语法没过就没交上去:那一段不叫「提交结果」
@@ -64,32 +85,55 @@ const canTrial = computed(() => {
const languages = problem.value?.languages ?? []
return !languages.includes("SQL")
})
const segments = computed(() => {
const list = canTrial.value ? codeSegments.value : codeSegments.value.slice(0, 1)
return flowchartReadable.value
? [...list, { value: "flowchart" as const, label: "流程图点评", time: "" }]
: list
})
/** 只有一段(SQL 题、又不是手机上的流程图题)就不摆分段了 */
const showSegments = computed(() => segments.value.length > 1)
function isActive(segment: Segment) {
if (viewingFlowchart.value) return segment === "flowchart"
return resultSegment.value === segment
}
function pick(segment: Segment) {
if (segment === "flowchart") {
viewingFlowchart.value = true
return
}
viewingFlowchart.value = false
resultSegment.value = segment
}
</script>
<template>
<FlowchartResult v-if="drawing" />
<div v-show="!drawing" class="result-pane">
<!-- 设计稿:三个胶囊,选中的深色实心,带上那次结果的时间 -->
<div v-if="canTrial" class="segments" role="tablist" aria-label="结果">
<div v-if="showSegments" class="segments" role="tablist" aria-label="结果">
<button
v-for="segment in segments"
:key="segment.value"
type="button"
role="tab"
class="segment"
:class="{ active: resultSegment === segment.value }"
:aria-selected="resultSegment === segment.value"
@click="resultSegment = segment.value"
:class="{ active: isActive(segment.value) }"
:aria-selected="isActive(segment.value)"
@click="pick(segment.value)"
>
{{ segment.label }}<template v-if="segment.time"> · {{ segment.time }}</template>
</button>
<span v-if="resultSegment === 'custom'" class="segment-note"
<span v-if="!viewingFlowchart && resultSegment === 'custom'" class="segment-note"
>自己编数据试试,这里不判对错</span
>
</div>
<FlowchartResult v-if="viewingFlowchart && flowchartReadable" view-only />
<!-- 提交结果用 v-show:切去看运行例子时不卸载,挂着的错误说明在编辑器里标着红 -->
<div v-show="resultSegment === 'submit' || !canTrial">
<div v-show="!viewingFlowchart && (resultSegment === 'submit' || !canTrial)">
<template v-if="syntaxErrorInfo">
<ResultHeader kind="warning" title="代码有语法错误,还没有交上去" />
<PythonErrorExplain :err-info="syntaxErrorInfo" />
@@ -107,7 +151,7 @@ const canTrial = computed(() => {
description="还没有提交过。写完代码按「提交」,结果会出现在这里"
/>
</div>
<template v-if="canTrial">
<template v-if="canTrial && !viewingFlowchart">
<SampleRunResult v-if="resultSegment === 'samples'" />
<CustomRun v-else-if="resultSegment === 'custom'" />
</template>