- 工具栏「运行例子」:一键用全部例子试跑(Judge0,不算提交),结果进「结果 › 运行例子」, 每个例子 ✓ / ✕,没对上的沿用答案错误那套中文说明;比赛里只摆输出、不给提示句 (后端在比赛里故意不做例子对比);SQL 题不给 - 「结果」页签分成提交结果 / 运行例子 / 自己输入三段;「自己输入」顶替顶栏「自测」模式 那个单独的编辑器 - 试跑出错(编译、运行、超时)给中文说明,Judge0 的英文原文折叠起来 - 例子比对两边都去掉末尾空白:原来只 trim 了运行输出,样例末尾带空格的题永远「不通过」 - 去掉题面里每个例子旁的「测试」、顶栏的「双栏 / 自测 / 题目」切换、EditorForTest 和 screenMode store Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f4db7dc81c
commit
45505e8384
16 files changed
+562
-374
No files matched your search
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { useCodeStore } from "oj/store/code"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { DIFFICULTY } from "utils/constants"
|
||||
import { getTagColor } from "utils/functions"
|
||||
import { createTestSubmission } from "utils/judge"
|
||||
import type { ProblemDetail, ProblemRow, ProblemStatus } from "utils/types"
|
||||
import { useSubmissionStore } from "oj/store/submission"
|
||||
import type { ProblemRow } from "utils/types"
|
||||
import Copy from "shared/components/Copy.vue"
|
||||
import { useDark } from "@vueuse/core"
|
||||
import { MdPreview } from "md-editor-v3"
|
||||
@@ -14,16 +13,8 @@ import "md-editor-v3/lib/preview.css"
|
||||
import { getSimilarProblems } from "oj/api"
|
||||
import SQLDataTable from "./SQLDataTable.vue"
|
||||
|
||||
type Sample = ProblemDetail["samples"][number] & {
|
||||
id: number
|
||||
msg: string
|
||||
status: ProblemStatus
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const isDark = useDark()
|
||||
const route = useRoute()
|
||||
const codeStore = useCodeStore()
|
||||
const problemStore = useProblemStore()
|
||||
const { problem } = storeToRefs(problemStore)
|
||||
|
||||
@@ -88,26 +79,10 @@ const hasTriedButNotPassed = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
function freshSamples(): Sample[] {
|
||||
return (problem.value?.samples ?? []).map((sample, index) => ({
|
||||
...sample,
|
||||
id: index,
|
||||
msg: "",
|
||||
status: "not_test",
|
||||
loading: false,
|
||||
}))
|
||||
}
|
||||
|
||||
const samples = ref<Sample[]>(freshSamples())
|
||||
|
||||
// 题目页换题是同一个组件复用(顶栏题号直达、「下一题」、相似题都是只换路由参数),
|
||||
// 不跟着重建的话,新题下面摆的还是上一道题的例子,「测试」也拿旧例子去比
|
||||
watch(
|
||||
() => problem.value?._id,
|
||||
() => {
|
||||
samples.value = freshSamples()
|
||||
},
|
||||
)
|
||||
// 例子只是摆出来。原来每个例子旁有个「测试」按钮(结果只给通过 / 不通过、2 秒后复位),
|
||||
// 现在试跑统一走工具栏的「运行例子」和结果页签的「自己输入」
|
||||
const samples = computed(() => problem.value?.samples ?? [])
|
||||
const submissionStore = useSubmissionStore()
|
||||
|
||||
// 文案和配色分类都由后端生成 —— 原来这里有一份 NODE_TARGET_LABELS +
|
||||
// ruleDescription + ruleTagType,和判题机那份几乎一模一样,见契约
|
||||
@@ -119,66 +94,6 @@ const KIND_TAG_TYPE = {
|
||||
} as const
|
||||
|
||||
const astRequirements = computed(() => Object.entries(problem.value?.astRequirements ?? {}))
|
||||
|
||||
async function test(sample: Sample, index: number) {
|
||||
samples.value = samples.value.map((sample) => {
|
||||
if (sample.id === index) {
|
||||
sample.loading = true
|
||||
}
|
||||
return sample
|
||||
})
|
||||
const problemId = problem.value?._id
|
||||
const res = await createTestSubmission(codeStore.code, sample.input)
|
||||
// 跑的这一会儿换了题:结果是上一道题的例子的,按下标写进去就串到新题上了
|
||||
if (problem.value?._id !== problemId) return
|
||||
samples.value = samples.value.map((sample) => {
|
||||
if (sample.id === index) {
|
||||
const status = res.status === 3 && res.output.trim() === sample.output ? "passed" : "failed"
|
||||
return {
|
||||
...sample,
|
||||
msg: res.output,
|
||||
status: status,
|
||||
loading: false,
|
||||
}
|
||||
} else {
|
||||
return sample
|
||||
}
|
||||
})
|
||||
|
||||
const id = setTimeout(() => {
|
||||
clearTimeout(id)
|
||||
if (problem.value?._id !== problemId) return
|
||||
samples.value = samples.value.map((sample) => {
|
||||
if (sample.id === index) {
|
||||
return {
|
||||
...sample,
|
||||
msg: res.output,
|
||||
status: "not_test",
|
||||
loading: false,
|
||||
}
|
||||
} else {
|
||||
return sample
|
||||
}
|
||||
})
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
function label(status: ProblemStatus, loading: boolean) {
|
||||
if (loading) return "测试中"
|
||||
return {
|
||||
not_test: "测试",
|
||||
failed: "不通过",
|
||||
passed: "通过",
|
||||
}[status]
|
||||
}
|
||||
|
||||
function type(status: ProblemStatus) {
|
||||
return {
|
||||
not_test: "",
|
||||
failed: "error",
|
||||
passed: "success",
|
||||
}[status] as "warning" | "error" | "success"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -280,14 +195,15 @@ function type(status: ProblemStatus) {
|
||||
<Icon icon="streamline-emojis:microscope"></Icon>
|
||||
例子 {{ index + 1 }}
|
||||
</h3>
|
||||
<!-- 只在第一个例子旁放一个入口:每个都放就成了一排一样的链接 -->
|
||||
<n-button
|
||||
v-if="index === 0"
|
||||
text
|
||||
type="primary"
|
||||
size="small"
|
||||
secondary
|
||||
:type="type(sample.status) || 'default'"
|
||||
:loading="sample.loading"
|
||||
@click="test(sample, index)"
|
||||
@click="submissionStore.revealResult('custom')"
|
||||
>
|
||||
{{ label(sample.status, sample.loading) }}
|
||||
自己输入数据试试 ›
|
||||
</n-button>
|
||||
</n-flex>
|
||||
<div class="sample-grid">
|
||||
@@ -306,10 +222,6 @@ function type(status: ProblemStatus) {
|
||||
<pre class="testcase">{{ sample.output }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sample.msg" class="sample-box sample-result">
|
||||
<div class="sample-label">运行结果</div>
|
||||
<pre class="testcase">{{ sample.msg }}</pre>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -521,10 +433,6 @@ function type(status: ProblemStatus) {
|
||||
padding: 6px 10px 10px;
|
||||
}
|
||||
|
||||
.sample-result {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.sample-label {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
|
||||
Reference in new issue
Block a user