feat(frontend): enable problem-scoped Python and Java submissions

This commit is contained in:
2026-10-09 22:32:38 +08:00
parent 507b15b64a
commit 9d960c2a9b
11 changed files with 289 additions and 24 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
// No source is stored here. Fingerprints bind an immutable request and owner to a UUID.
import type { components } from './generated/schema'
export type SubmissionInput = {
problem_version_id: string
language: 'c17' | 'cpp20'
language: components['schemas']['kf_oj__contracts__judging_v2__Language']
source: string
}
type Intent = { fingerprint: string; key: string; submissionId?: string }
+1
View File
@@ -10,6 +10,7 @@ import 'monaco-editor/features/linesOperations/register'
import 'monaco-editor/features/wordOperations/register'
import 'monaco-editor/languages/definitions/cpp/register'
import 'monaco-editor/languages/definitions/python/register'
import 'monaco-editor/languages/definitions/java/register'
import 'monaco-editor/languages/definitions/rust/register'
import EditorWorker from 'monaco-editor/editor/editor.worker?worker'
const props = defineProps<{
+7 -1
View File
@@ -1,9 +1,11 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { components } from '../api/generated/schema'
import { preparingStatus, type WireLanguage } from '../composables/judgingLanguage'
const props = defineProps<{
state: components['schemas']['SubmissionSummary']['state']
verdict: components['schemas']['SubmissionSummary']['verdict']
language?: WireLanguage
}>()
const grades: Record<string, string> = {
accepted: 'AC',
@@ -27,7 +29,11 @@ const states: Record<string, string> = {
finished: '评测完成',
}
const label = computed(() =>
props.verdict ? (grades[props.verdict] ?? props.verdict) : states[props.state],
props.verdict
? (grades[props.verdict] ?? props.verdict)
: props.state === 'compiling'
? preparingStatus(props.language)
: states[props.state],
)
</script>
<template>
@@ -0,0 +1,52 @@
import type { components } from '../api/generated/schema'
import type { Language } from './useCodeDraft'
export type WireLanguage = components['schemas']['SubmissionSummary']['language']
type ProblemCapability = Pick<
components['schemas']['ProblemDetail'],
'languages' | 'language_configurations' | 'run_limits'
>
const wire: Record<Language, WireLanguage | null> = {
c: 'c17',
cpp: 'cpp20',
python: 'python313',
java: 'java21',
rust: null,
}
export function languageCapability(problem: ProblemCapability | null, language: Language) {
const id = wire[language]
if (!problem || !id || !problem.languages.includes(id)) return null
const configuration = problem.language_configurations.find((c) => c.language === id)
if (configuration) {
const validEntry =
id === 'python313'
? configuration.preparation_kind === 'syntax_check' &&
configuration.source_name === 'main.py' &&
configuration.entrypoint === 'main.py'
: id === 'java21'
? configuration.preparation_kind === 'compile' &&
configuration.source_name === 'Main.java' &&
configuration.entrypoint === 'Main'
: configuration.preparation_kind === 'compile'
if (!validEntry || !configuration.runtime_version) return null
return { wire: id, configuration, runLimits: configuration.run_limits }
}
// Only legacy C/C++ versions expose one shared budget. A managed language
// without its server-provided configuration must not inherit that budget.
if (
(id === 'c17' || id === 'cpp20') &&
problem.language_configurations.length === 0 &&
problem.run_limits
)
return { wire: id, configuration: null, runLimits: problem.run_limits }
return null
}
export function preparationName(language?: WireLanguage) {
return language === 'python313' ? '语法检查' : '编译'
}
export function preparingStatus(language?: WireLanguage) {
return language === 'python313' ? '正在检查语法' : '正在编译'
}
+6 -4
View File
@@ -1,15 +1,17 @@
import { computed, onScopeDispose, ref, watch } from 'vue'
export type Language = 'c' | 'cpp' | 'python' | 'rust'
export type Language = 'c' | 'cpp' | 'python' | 'java' | 'rust'
export const languageLabels: Record<Language, string> = {
c: 'C17',
cpp: 'C++20',
python: 'Python',
python: 'Python 3.13',
java: 'Java 21',
rust: 'Rust',
}
export const codeTemplates: Record<Language, string> = {
c: '#include <stdio.h>\n\nint main(void) {\n return 0;\n}\n',
cpp: '#include <iostream>\nusing namespace std;\n\nint main() {\n // 在这里编写你的解法\n return 0;\n}\n',
python: '# 在这里编写你的解法\n\n',
java: 'public class Main {\n public static void main(String[] args) throws Exception {\n // 在这里编写你的解法\n }\n}\n',
rust: 'use std::io::{self, Read};\n\nfn main() {\n let mut input = String::new();\n io::stdin().read_to_string(&mut input).unwrap();\n // 在这里编写你的解法\n}\n',
}
export function useCodeDraft(problemId: string) {
@@ -32,11 +34,11 @@ export function useCodeDraft(problemId: string) {
typeof saved.drafts === 'object'
) {
const storedDrafts = saved.drafts as Record<string, unknown>
for (const lang of ['c', 'cpp', 'python', 'rust'] as const) {
for (const lang of Object.keys(codeTemplates) as Language[]) {
const value = storedDrafts[lang]
if (typeof value === 'string') drafts.value[lang] = value
}
if ('language' in saved && ['c', 'cpp', 'python', 'rust'].includes(String(saved.language)))
if ('language' in saved && Object.hasOwn(codeTemplates, String(saved.language)))
language.value = saved.language as Language
state.value = 'saved'
}
+25 -13
View File
@@ -8,6 +8,7 @@ import { submissionIntent, type SubmissionInput } from '../api/submissionIntent'
import type { components } from '../api/generated/schema'
import { useSession } from '../composables/useSession'
import { useCodeDraft, codeTemplates, languageLabels } from '../composables/useCodeDraft'
import { languageCapability, preparationName } from '../composables/judgingLanguage'
import CodeEditor from '../components/CodeEditor.vue'
import EmptyState from '../components/EmptyState.vue'
import UiIcon from '../components/UiIcon.vue'
@@ -31,17 +32,10 @@ const workspace = ref<HTMLDivElement>()
const request = new AbortController()
let alive = true
const intents = new Map<string, ReturnType<typeof submissionIntent>>()
const wireLanguage = computed(() =>
language.value === 'c' ? 'c17' : language.value === 'cpp' ? 'cpp20' : null,
)
const supported = computed(
() => wireLanguage.value !== null && !!problem.value?.languages.includes(wireLanguage.value),
)
const runLimits = computed(
() =>
problem.value?.language_configurations?.find((c) => c.language === wireLanguage.value)
?.run_limits ?? problem.value?.run_limits,
)
const capability = computed(() => languageCapability(problem.value, language.value))
const wireLanguage = computed(() => capability.value?.wire ?? null)
const supported = computed(() => capability.value !== null)
const runLimits = computed(() => capability.value?.runLimits)
const draftText = computed(
() =>
({
@@ -102,7 +96,11 @@ function downloadCode() {
const link = document.createElement('a')
link.href = url
link.download =
'solution-' + id + '.' + { c: 'c', cpp: 'cpp', python: 'py', rust: 'rs' }[language.value]
language.value === 'java'
? 'Main.java'
: language.value === 'python'
? 'main.py'
: 'solution-' + id + '.' + { c: 'c', cpp: 'cpp', rust: 'rs' }[language.value]
link.click()
setTimeout(() => URL.revokeObjectURL(url), 1000)
}
@@ -289,7 +287,21 @@ onBeforeUnmount(() => {
</div>
<div class="editor-testing">
<p v-if="!supported" class="notice-strip">
当前语言暂不支持提交,草稿仍会保存。可切换到题目支持的 C17 / C++20。
当前题目版本不支持此语言,草稿仍会保存。请选择题目公布的可用语言。
</p>
<p
v-else-if="capability?.configuration"
class="notice-strip"
data-testid="language-capability"
>
{{ languageLabels[language] }} · {{ capability.configuration.runtime_version }} ·
{{ preparationName(capability.wire) }}后逐点运行。
<template v-if="capability.wire === 'java21'">
默认包 Main.java,入口为 public static void main(String[])。
</template>
<template v-else-if="capability.wire === 'python313'">
单文件 main.py,仅标准库。
</template>
</p>
<p v-if="submitError" role="alert">
{{ submitError }}
+12 -3
View File
@@ -9,6 +9,7 @@ import type { components } from '../api/generated/schema'
import PageHeading from '../components/PageHeading.vue'
import PanelCard from '../components/PanelCard.vue'
import JudgementStatus from '../components/JudgementStatus.vue'
import { preparationName } from '../composables/judgingLanguage'
const route = useRoute()
const id = String(route.params.id)
const submission = ref<components['schemas']['SubmissionDetail'] | null>(null)
@@ -120,7 +121,11 @@ onBeforeUnmount(() => {
</script>
<template>
<PageHeading :title="'提交 ' + id" description="个人提交的持久化评测状态"
><JudgementStatus v-if="submission" :state="submission.state" :verdict="submission.verdict"
><JudgementStatus
v-if="submission"
:state="submission.state"
:verdict="submission.verdict"
:language="submission.language"
/></PageHeading>
<p v-if="error" role="alert">
{{ error }}
@@ -169,13 +174,17 @@ onBeforeUnmount(() => {
<PanelCard title="评测详情" icon="submissions">
<p v-if="submission.failure_code" role="alert">系统原因:{{ submission.failure_code }}</p>
<template v-if="submission.result">
<p>编译状态:{{ submission.result.compilation_status }}</p>
<p>
{{ preparationName(submission.language) }}状态:{{ submission.result.compilation_status }}
</p>
<pre
v-if="submission.result.diagnostics"
class="sample-block"
data-testid="compiler-diagnostics"
>{{ submission.result.diagnostics }}</pre>
<p v-if="submission.result.diagnostics_truncated">编译诊断已截断。</p>
<p v-if="submission.result.diagnostics_truncated">
{{ preparationName(submission.language) }}诊断已截断。
</p>
<p v-if="!submission.result.cleanup_complete" role="alert">
资源清理未确认,服务保留该任务并隔离容量。
</p>
+4 -1
View File
@@ -91,7 +91,10 @@ onBeforeUnmount(() => {
label="状态"
min-width="130"
><template #default="{ row }"
><JudgementStatus :state="row.state" :verdict="row.verdict" /></template></el-table-column
><JudgementStatus
:state="row.state"
:verdict="row.verdict"
:language="row.language" /></template></el-table-column
><el-table-column prop="created_at" label="提交时间" min-width="180" />
</el-table>
<div class="pagination-row">