feat(题目): 结果标题说清楚错在哪——「在例子 1 上就错了」「所有测试点都对了」,答错时附上题目对输出的原话

- 在公开例子上就错了的:标题写「答案错误 · 在例子 N 上就错了」,不再挂「通过 x/y 个测试点」
  和进度条 —— 那时候那个数字没意义,先把例子跑对
- 做对了:「答案正确 · 所有测试点都对了」
- 答案错误的说明末尾摆出题目「输出」那一节的原话:格式不对、漏了一行这类错,对着原话一看就明白
- 例子都对、错在隐藏测试点的,给「去自己输入试试」(SQL 题不给,跑不了)
- 提交详情页的标题不变

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-09-28 19:03:13 +08:00
1 parent b4afcbd47f
commit 8f0d39bb5a
2 files changed
+110 -10

No files matched your search

@@ -145,6 +145,41 @@ const msg = computed(() => {
// 这是学生这边唯一能看出「比上次多过了几个点」的地方。
const partialCases = computed(() => submissionPartialCases(props.submission))
/** 在公开的例子上就错了:这时候「通过 x/y 个测试点」没意义,先说是哪个例子 */
const failedOnSample = computed(() =>
sampleCheck.value && !sampleCheck.value.passed ? sampleCheck.value : null,
)
/**
* 结果标题(设计文档第 6 节)。比「答案错误 · 通过 1/3 个测试点」多走一步:
* 例子上就错了的说「在例子 N 上就错了」,例子都对的才报测试点;做对了说全对了。
* 提交详情页还用通用的 submissionResultTitle
*/
const title = computed(() => {
const submission = props.submission
if (!submission) return ""
if (submission.result === SubmissionStatus.accepted) return "答案正确 · 所有测试点都对了"
if (failedOnSample.value) {
return `答案错误 · 在例子 ${(failedOnSample.value.index ?? 0) + 1} 上就错了`
}
const cases = hiddenCases.value
if (cases) return `答案错误 · 通过 ${cases.passed}/${cases.total} 个测试点`
return submissionResultTitle(submission)
})
/**
* 例子都对了、错在隐藏的测试点上:一个都没过也要报「通过 0/5」—— 那正说明例子之外的全错了。
* 其余情况沿用 submissionPartialCases(一个都没过就不报)
*/
const hiddenCases = computed(() => {
const summary = props.submission?.caseSummary
if (!sampleCheck.value?.passed || !summary || summary.passed >= summary.total) return null
return summary
})
const progressCases = computed(
() => hiddenCases.value ?? (failedOnSample.value ? null : partialCases.value),
)
// 是否显示AI提示区域。
// 阈值和后端 POST /ai/hint 共用契约里的 HINT_MIN_FAILURES,别在这里写死数字;
// 编译失败不数次数,和后端同口径(理由见 HINT_MIN_FAILURES 的注释);
@@ -214,16 +249,12 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
<template>
<div v-if="submission">
<n-alert
:type="JUDGE_STATUS[submission.result]['type']"
:title="submissionResultTitle(submission)"
class="mb-3"
>
<template v-if="partialCases" #default>
<n-alert :type="JUDGE_STATUS[submission.result]['type']" :title="title" class="mb-3">
<template v-if="progressCases" #default>
<n-progress
type="line"
status="success"
:percentage="(partialCases.passed / partialCases.total) * 100"
:percentage="(progressCases.passed / progressCases.total) * 100"
:show-indicator="false"
/>
</template>
@@ -248,6 +279,7 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
<RuntimeErrorExplain v-if="runtimeError" :info="runtimeError" :code="submission.code" />
<WrongAnswerExplain
v-if="sampleCheck"
full
:check="sampleCheck"
:code="submission.code"
:language="submission.language"
@@ -1,4 +1,10 @@
<script setup lang="ts">
import { storeToRefs } from "pinia"
import { useDark } from "@vueuse/core"
import { MdPreview } from "md-editor-v3"
import "md-editor-v3/lib/preview.css"
import { useProblemStore } from "oj/store/problem"
import { useSubmissionStore } from "oj/store/submission"
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
import {
classifyDiff,
@@ -21,8 +27,24 @@ const props = defineProps<{
* 前端试跑也不能绕过去
*/
plain?: boolean
/**
* 提交结果里用:末尾摆出题目对输出的原话,例子都对时再给「去自己输入试试」。
* 格式错、漏了一行这类错误,对着题目原话一看就明白;运行例子那边不需要
*/
full?: boolean
}>()
const isDark = useDark()
const { problem } = storeToRefs(useProblemStore())
const submissionStore = useSubmissionStore()
/** 题目要求的输出(题面「输出」那一节的原文,Markdown)。SQL 题没有这一节 */
const requirement = computed(() =>
props.full && !problem.value?.sqlConfig ? (problem.value?.outputDescription ?? "").trim() : "",
)
/** 自己输入走 Judge0,SQL 跑不了 */
const canCustomRun = computed(() => props.full && !problem.value?.sqlConfig)
/** 后端每段截到 2000 字,到了上限就是被截过 */
const TEXT_LIMIT = 2000
@@ -62,9 +84,20 @@ function lines(text: string) {
<template>
<n-card embedded class="explain-card">
<div v-if="check.passed" class="explain">
题目里的例子都对了,没通过的是隐藏的测试点。想想特殊情况:最大的数、最小的数、0、负数,或者题目里专门提到的情况。
</div>
<n-flex v-if="check.passed" vertical :size="10">
<div class="explain">
题目里的例子都对了,没通过的是隐藏的测试点。想想特殊情况:最大的数、最小的数、0、负数,或者题目里专门提到的情况。
</div>
<n-button
v-if="canCustomRun"
text
type="primary"
class="custom"
@click="submissionStore.revealResult('custom')"
>
去自己输入试试 ›
</n-button>
</n-flex>
<n-flex v-else vertical :size="12">
<div class="explain">
<b>例子 {{ (check.index ?? 0) + 1 }} 没有通过{{ plain ? "。" : ":" }}</b
@@ -112,6 +145,14 @@ function lines(text: string) {
</div>
</div>
</n-flex>
<div v-if="requirement" class="requirement">
<div class="label">题目要求的输出</div>
<MdPreview
preview-theme="vuepress"
:model-value="requirement"
:theme="isDark ? 'dark' : 'light'"
/>
</div>
</n-card>
</template>
@@ -120,6 +161,33 @@ function lines(text: string) {
max-width: 560px;
}
.custom {
align-self: flex-start;
}
.requirement {
margin-top: 14px;
padding-top: 10px;
border-top: 1px dashed rgba(128, 128, 128, 0.3);
}
/* md-editor 的预览自带白底、一圈 padding 和段落外边距,放在灰卡片里像一块补丁 */
.requirement :deep(.md-editor) {
background: transparent;
}
.requirement :deep(.md-editor-preview-wrapper) {
padding: 0;
}
.requirement :deep(.md-editor-preview > :first-child) {
margin-top: 0;
}
.requirement :deep(.md-editor-preview > :last-child) {
margin-bottom: 0;
}
.explain {
font-size: 16px;
line-height: 1.7;