Revert "feat(题目): 样例试运行放出「终端会话」,把输入是怎么喂进去的画给学生看"
Some checks failed
Deploy / deploy (push) Has been cancelled

撤掉 8cd4459。功能本身跑通了(C / C++ / Python3 三条都验过),但界面上不成立:
为了解释「输入是喂进去的」「提示语也算输出」这两件事,每跑一次样例就压两大段
灰色小字,把一个干净的小面板变成了说明书 —— 需要靠一段说明才能看懂的配色,
本身就说明这个设计没做对。

连带撤掉同一批的「结果不自动复位 + 通过/不通过 tag」,回到 bd84599 的样子。

「学生感觉不到输入这一步」这个问题还在,只是不该用这个办法解。教程 04 那边讲
「交到判题狗上要把提示语删掉」的一节保留(那部分和界面无关,本来就成立),
其中引用本功能的那句话已经删掉并重新发布。
This commit is contained in:
2026-09-02 01:25:19 -06:00
parent 8cd4459964
commit 9675dfbd42
2 changed files with 42 additions and 296 deletions

View File

@@ -5,7 +5,6 @@ import { storeToRefs } from "pinia"
import { useCodeStore } from "oj/store/code"
import { useProblemStore } from "oj/store/problem"
import { createTestSubmission } from "utils/judge"
import type { TranscriptSegment } from "utils/judge"
import { DIFFICULTY } from "utils/constants"
import type { Problem, ProblemStatus } from "utils/types"
import Copy from "shared/components/Copy.vue"
@@ -18,19 +17,10 @@ import SQLDataTable from "./SQLDataTable.vue"
type Sample = Problem["samples"][number] & {
id: number
msg: string
// 终端会话不支持回显的语言Java / Go / JS拿不到退回只显示 msg
segments: TranscriptSegment[] | null
status: ProblemStatus
loading: boolean
}
/** 会话里的一段在界面上怎么显示 */
type ShownSegment = {
text: string
// out = 程序真正的输出in = 喂进去的输入prompt = 学生自己打的提示语
kind: "out" | "in" | "prompt"
}
const theme = useThemeVars()
const style = computed(() => "color: " + theme.value.primaryColor)
const isDark = useDark()
@@ -102,7 +92,6 @@ const samples = ref<Sample[]>(
...sample,
id: index,
msg: "",
segments: null,
status: "not_test",
loading: false,
})),
@@ -138,7 +127,6 @@ async function test(sample: Sample, index: number) {
return {
...sample,
msg: res.output,
segments: res.segments,
status: status,
loading: false,
}
@@ -146,79 +134,39 @@ async function test(sample: Sample, index: number) {
return sample
}
})
const id = setTimeout(() => {
clearTimeout(id)
samples.value = samples.value.map((sample) => {
if (sample.id === index) {
return {
...sample,
msg: res.output,
status: "not_test",
loading: false,
}
} else {
return sample
}
})
}, 2000)
}
// 运行结果不再自动复位后,按钮固定叫「测试」——
// 一个绿色的「通过」按钮既当状态又当「再跑一次」的入口,容易误读,
// 通过 / 不通过挪到旁边的 tag 上
const STATUS_TAG = {
not_test: null,
failed: { label: "通过", type: "error" },
passed: { label: "通过", type: "success" },
} as const satisfies Record<
ProblemStatus,
{ label: string; type: string } | null
>
/**
* 一段输出后面紧跟着一段输入,说明它是「请输入半径:」这类提示语。学生在自己电脑上
* 跑,这句是打给自己看的;到了判题狗这里它一样算进 stdout是最常见的 WA 原因,
* 所以单独标出来。
*/
function shownSegments(sample: Sample): ShownSegment[] {
const segments = sample.segments
if (!segments) return [{ text: sample.msg, kind: "out" }]
return segments.map((seg, index) => ({
// 样例的输入大多不带结尾换行(库里就是 `"700"`),照原样贴出来,下一行输出会
// 和它挤在一起变成 `700` `7` → `7007`。学生真在终端里敲的话这里有个回车,
// 补上它才是他电脑上看到的样子 —— 只影响显示,判定用的是 msg。
text:
seg.kind === "input" && !seg.text.endsWith("\n")
? seg.text + "\n"
: seg.text,
kind:
seg.kind === "input"
? "in"
: segments[index + 1]?.kind === "input"
? "prompt"
: "out",
}))
function label(status: ProblemStatus, loading: boolean) {
if (loading) return "测试中"
return {
not_test: "测试",
failed: "不通过",
passed: "通过",
}[status]
}
function hasFedInput(sample: Sample) {
return !!sample.segments?.some((seg) => seg.kind === "input")
}
/**
* 把提示语抠掉之后正好等于期望输出 —— 这时候能把话说死:答案是对的,删掉就通过。
* 抠掉还是对不上,就只说提示语也算输出,不误导。
*/
function hint(sample: Sample) {
if (sample.status !== "failed") return ""
const shown = shownSegments(sample)
if (!shown.some((seg) => seg.kind === "prompt")) return ""
const withoutPrompt = shown
.filter((seg) => seg.kind === "out")
.map((seg) => seg.text)
.join("")
if (withoutPrompt.trim() === sample.output.trim()) {
return "答案本身是对的,只是输出里多了带下划线的提示语 —— 判题狗只对比程序打印的结果,把 input() / printf() 里的提示语删掉就通过了。"
}
return "带下划线的是你自己打印的提示语,判题狗也会把它算进你的输出里。"
}
function segStyle(kind: ShownSegment["kind"]) {
if (kind === "in") {
return { color: theme.value.infoColor, fontWeight: 600 }
}
if (kind === "prompt") {
return {
color: theme.value.textColor3,
textDecoration: "underline dotted",
textUnderlineOffset: "3px",
}
}
return {}
function type(status: ProblemStatus) {
return {
not_test: "",
failed: "error",
passed: "success",
}[status] as "warning" | "error" | "success"
}
</script>
@@ -363,9 +311,7 @@ function segStyle(kind: ShownSegment["kind"]) {
</p>
<n-list bordered style="margin-bottom: 8px">
<n-list-item v-for="(rule, i) in rules" :key="i">
<n-tag :type="KIND_TAG_TYPE[rule.kind]">
{{ rule.description }}
</n-tag>
<n-tag :type="KIND_TAG_TYPE[rule.kind]">{{ rule.description }}</n-tag>
</n-list-item>
</n-list>
</div>
@@ -382,18 +328,11 @@ function segStyle(kind: ShownSegment["kind"]) {
</p>
<n-button
size="small"
:loading="sample.loading"
:type="type(sample.status)"
@click="test(sample, index)"
>
测试
{{ label(sample.status, sample.loading) }}
</n-button>
<n-tag
v-if="STATUS_TAG[sample.status]"
size="small"
:type="STATUS_TAG[sample.status]!.type"
>
{{ STATUS_TAG[sample.status]!.label }}
</n-tag>
</n-flex>
<n-descriptions
bordered
@@ -418,23 +357,8 @@ function segStyle(kind: ShownSegment["kind"]) {
</template>
<div class="testcase">{{ sample.output }}</div>
</n-descriptions-item>
<n-descriptions-item
label="运行过程"
v-if="sample.msg || sample.segments?.length"
>
<div class="terminal">
<span
v-for="(seg, i) of shownSegments(sample)"
:key="i"
:style="segStyle(seg.kind)"
>{{ seg.text }}</span
>
</div>
<p v-if="hasFedInput(sample)" class="terminalNote">
蓝色那几段是判题狗提前准备好自动喂给程序的输入
所以这里不用你敲键盘程序也不会停下来等
</p>
<p v-if="hint(sample)" class="terminalNote">{{ hint(sample) }}</p>
<n-descriptions-item label="运行结果" v-if="sample.msg">
<div class="testcase">{{ sample.msg }}</div>
</n-descriptions-item>
</n-descriptions>
</div>
@@ -518,20 +442,6 @@ function segStyle(kind: ShownSegment["kind"]) {
font-family: "Monaco";
}
.terminal {
font-size: 14px;
white-space: pre-wrap;
word-break: break-all;
line-height: 1.7;
font-family: Monaco, Consolas, monospace;
}
.terminalNote {
font-size: 13px;
opacity: 0.75;
margin: 8px 0 0;
}
.status-alert {
margin-bottom: 16px;
}