From d1d071ff7b1b3af468122c4f776c52d1ab1edac3 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 7 Sep 2026 06:29:23 -0600 Subject: [PATCH] =?UTF-8?q?fix(=E5=90=8E=E5=8F=B0):=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E7=94=9F=E6=88=90=E5=99=A8=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E6=97=A0=E8=BE=93=E5=85=A5=E7=9A=84=E6=B5=8B=E8=AF=95=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「这行算不算数」原来判的是 `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 Claude-Session: https://claude.ai/code/session_01AdMvixPo27sWFrUmk6ZStZ --- .../problem/components/TestcaseGenerator.vue | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/apps/web/src/admin/problem/components/TestcaseGenerator.vue b/apps/web/src/admin/problem/components/TestcaseGenerator.vue index 2755033..6e69939 100644 --- a/apps/web/src/admin/problem/components/TestcaseGenerator.vue +++ b/apps/web/src/admin/problem/components/TestcaseGenerator.vue @@ -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() - 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() { 清空 +1 +5 - + - {{ !hasAnswerCode ? "请先在题目中填写答案代码" : "请先填写输入" }} + 请先在题目中填写答案代码