fix(后台): 测试用例生成器允许无输入的测试点
Some checks failed
Deploy / deploy (push) Has been cancelled

「这行算不算数」原来判的是 `f.in.trim()`,把「这题没有输入」和「这行我还没
填」撞成了同一种状态。打印类题目(输出星号矩形、只靠 print 的格式化输出题)
输入留空 → 点「先运行」整行被删 → 一行不剩 → 「上传」永远是灰的,老师只能
往输入框里塞个「无」才存得下去,那两个字节就真的进了 stdin。8019 / 8020 /
8021 三道题的 1.in 就是这么变成 3 字节「无」的。

拆成两个判据:

- hasOutput —— 有输出且没报错,这一行才打进 zip,输入空不空无所谓;
- isFilled  —— 输入或输出任一非空,只用来在「先运行」里筛掉面板上的空占位行,
  全没填时留一行当无输入题跑。

canUpload 保留原来那条守卫:填了输入却没跑出输出的行仍然挡住上传。「先运行」
不再要求先填输入。

后端本来就收空的 .in(processTestCaseZip 无非空校验),已上线的 8007、P003
的 1.in 就是 0 字节,卡住的一直只有这个面板。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdMvixPo27sWFrUmk6ZStZ
This commit is contained in:
2026-09-07 06:29:23 -06:00
parent ca29fcb554
commit d1d071ff7b

View File

@@ -83,13 +83,32 @@ watch(
const isRunning = ref(false)
const isUploading = ref(false)
const hasAnyInput = computed(() => files.value.some((f) => f.in.trim()))
/**
* 一个测试点必须有输出,但不一定有输入 —— 打印类题目(输出星号矩形、只靠
* print 的格式化输出题…)的 `N.in` 本来就该是空文件。
*
* 原来「这行算不算数」判的是 `f.in.trim()`,把「这题没有输入」和「这行我还没填」
* 撞成了同一种状态:输入留空 → 点「先运行」整行被删 → 一行不剩 → 「上传」
* 永远是灰的。老师只能往输入框里塞个「无」才存得下去,那两个字节就真的进了 stdin。
*/
function hasOutput(file: FileEntry) {
return !!file.out.trim() && !file.error
}
/** 老师碰过这一行没有。面板一开就铺 5 个空行,`+1` / `+5` 还能再加,空行是占位不是测试点 */
function isFilled(file: FileEntry) {
return !!(file.in.trim() || file.out.trim())
}
/** 真正会被打进 zip 的行:有输出就算数,输入空不空无所谓 */
const uploadable = computed(() => files.value.filter(hasOutput))
const canUpload = computed(
() =>
!isRunning.value &&
hasAnyInput.value &&
files.value.filter((f) => f.in.trim()).every((f) => f.out && !f.error),
uploadable.value.length > 0 &&
// 填了输入却没跑出输出:要么还没运行,要么运行炸了,这种状态不许传
files.value.every((f) => !f.in.trim() || hasOutput(f)),
)
function reset() {
@@ -122,14 +141,16 @@ async function run() {
)
if (!answer?.code.trim()) return
// 过滤行,去重(按输入内容)
// 过滤没填过的行,去重(按输入内容)。全没填就留一行,当成无输入题跑一次 ——
// 无输入题只可能有一个测试点,去重那条规则本来也会把多行空输入并成一行。
const seen = new Set<string>()
files.value = files.value.filter((f) => {
if (!f.in.trim()) return false
const kept = files.value.filter((f) => {
if (!isFilled(f)) return false
if (seen.has(f.in)) return false
seen.add(f.in)
return true
})
files.value = kept.length > 0 ? kept : files.value.slice(0, 1)
// 清空旧输出
files.value = files.value.map((f) => ({ ...f, out: "", error: false }))
@@ -158,8 +179,7 @@ async function run() {
async function upload() {
isUploading.value = true
try {
const data = files.value
.filter((f) => f.in.trim() && f.out && !f.error)
const data = uploadable.value
.flatMap((f, i) => [
{ name: `${i + 1}.in`, content: f.in },
{ name: `${i + 1}.out`, content: f.out },
@@ -208,20 +228,20 @@ async function upload() {
<n-button :disabled="isRunning" @click="reset">清空</n-button>
<n-button :disabled="isRunning" @click="add(1)">+1</n-button>
<n-button :disabled="isRunning" @click="add(5)">+5</n-button>
<n-tooltip :disabled="hasAnswerCode && hasAnyInput">
<n-tooltip :disabled="hasAnswerCode">
<template #trigger>
<span>
<n-button
type="success"
:loading="isRunning"
:disabled="!hasAnswerCode || !hasAnyInput"
:disabled="!hasAnswerCode"
@click="run"
>
先运行
</n-button>
</span>
</template>
{{ !hasAnswerCode ? "请先在题目中填写答案代码" : "请先填写输入" }}
请先在题目中填写答案代码
</n-tooltip>
<n-button
type="primary"