- 答案错误里「格式和内容都不一样」那一类(样例上错的 44%):原来只说「对照一下,找找 从哪里开始不一样」,现在是「你的程序输出了「E」,正确的是「D」。拿例子 2 的输入「60」, 一行一行走一遍你的程序,看它走进了哪个分支」;多行的说第几行开始不一样 - 不在这节课里的题交对之后,结果页签里给「再练几道相似的」(原来只在题面最底下) - 提交前自动排版改了代码的,结果上面提一句「编辑器里的代码也跟着变了,按 Ctrl+Z 能撤回」 - 交对之后「提交」降成描边按钮,「下一题」是唯一的主按钮 - 「重置代码」先确认(原来点一下就把代码和本地草稿一起清掉了) - AI 提示的档位写「第 1 档 · 反问」,不写「L0 反问」 - 成就弹窗从右下角挪到右上角,别和结果、点评弹窗叠在一起 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9110c678fc
commit
972dac3776
10 files changed
+201
-88
No files matched your search
@@ -214,12 +214,22 @@ const copy = async () => {
|
|||||||
message[success ? "success" : "error"](`代码复制${success ? "成功" : "失败"}`)
|
message[success ? "success" : "error"](`代码复制${success ? "成功" : "失败"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置会把编辑器里的代码换成模板、连本地草稿一起删掉,点错一下写了半节课的代码就没了,先问一句
|
||||||
|
const dialog = useDialog()
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
|
dialog.warning({
|
||||||
|
title: "重置代码",
|
||||||
|
content: "编辑器里的代码会换回题目给的模板,存着的草稿也会删掉。确定吗?",
|
||||||
|
positiveText: "重置",
|
||||||
|
negativeText: "再想想",
|
||||||
|
onPositiveClick: () => {
|
||||||
codeStore.setCode(
|
codeStore.setCode(
|
||||||
problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
||||||
)
|
)
|
||||||
storage.remove(storageKey)
|
storage.remove(storageKey)
|
||||||
message.success("代码重置成功")
|
message.success("已换回模板,按 Ctrl+Z 可以撤回")
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeLanguage = (v: LANGUAGE) => {
|
const changeLanguage = (v: LANGUAGE) => {
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import { useProblemStore } from "oj/store/problem"
|
|||||||
import { DIFFICULTY } from "utils/constants"
|
import { DIFFICULTY } from "utils/constants"
|
||||||
import { getTagColor } from "utils/functions"
|
import { getTagColor } from "utils/functions"
|
||||||
import { useSubmissionStore } from "oj/store/submission"
|
import { useSubmissionStore } from "oj/store/submission"
|
||||||
import type { ProblemRow } from "utils/types"
|
|
||||||
import Copy from "shared/components/Copy.vue"
|
import Copy from "shared/components/Copy.vue"
|
||||||
import { useDark } from "@vueuse/core"
|
import { useDark } from "@vueuse/core"
|
||||||
import { MdPreview } from "md-editor-v3"
|
import { MdPreview } from "md-editor-v3"
|
||||||
import "md-editor-v3/lib/preview.css"
|
import "md-editor-v3/lib/preview.css"
|
||||||
import { getSimilarProblems } from "oj/api"
|
|
||||||
import SQLDataTable from "./SQLDataTable.vue"
|
import SQLDataTable from "./SQLDataTable.vue"
|
||||||
|
import SimilarProblems from "./SimilarProblems.vue"
|
||||||
import { useFlowchartStore } from "oj/store/flowchart"
|
import { useFlowchartStore } from "oj/store/flowchart"
|
||||||
import { useMyFlowchartStore } from "shared/store/myFlowchart"
|
import { useMyFlowchartStore } from "shared/store/myFlowchart"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
@@ -37,42 +36,9 @@ const sqlChangedTables = computed(() => {
|
|||||||
return exp && "changed_tables" in exp ? exp.changed_tables : []
|
return exp && "changed_tables" in exp ? exp.changed_tables : []
|
||||||
})
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
// 相似题推荐:做对了、或者错了 3 次以上才给(比赛、题单里不给,见 problemPageContext)
|
||||||
|
const showSimilar = computed(
|
||||||
// 相似题目推荐
|
() => ctx.value.similar && (problem.value?.myStatus === 0 || problemStore.failCount >= 3),
|
||||||
const similarProblems = ref<ProblemRow[]>([])
|
|
||||||
const similarLoaded = ref(false)
|
|
||||||
|
|
||||||
async function loadSimilarProblems() {
|
|
||||||
if (similarLoaded.value || !problem.value) return
|
|
||||||
// 比赛、题单里不推荐(理由见 problemPageContext 那张表)
|
|
||||||
if (!ctx.value.similar) return
|
|
||||||
try {
|
|
||||||
similarProblems.value = await getSimilarProblems(problem.value._id)
|
|
||||||
} catch {
|
|
||||||
similarProblems.value = []
|
|
||||||
}
|
|
||||||
similarLoaded.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换题目时重置相似推荐状态
|
|
||||||
watch(
|
|
||||||
() => problem.value?._id,
|
|
||||||
() => {
|
|
||||||
similarProblems.value = []
|
|
||||||
similarLoaded.value = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// AC 或失败次数 >= 3 时加载推荐
|
|
||||||
watch(
|
|
||||||
() => [problem.value?._id, problem.value?.myStatus, problemStore.failCount],
|
|
||||||
([, status, failCount]) => {
|
|
||||||
if (status === 0 || (failCount as number) >= 3) {
|
|
||||||
loadSimilarProblems()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -352,49 +318,7 @@ const astRequirements = computed(() => Object.entries(problem.value?.astRequirem
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 相似题目推荐 -->
|
<SimilarProblems v-if="showSimilar" title="相似题目推荐" />
|
||||||
<div v-if="ctx.similar && similarProblems.length > 0">
|
|
||||||
<n-divider />
|
|
||||||
<h3 class="title">
|
|
||||||
<Icon icon="streamline-ultimate-color:like"></Icon>
|
|
||||||
相似题目推荐
|
|
||||||
</h3>
|
|
||||||
<n-list bordered>
|
|
||||||
<n-list-item v-for="sp in similarProblems" :key="sp._id">
|
|
||||||
<n-flex align="center" justify="space-between">
|
|
||||||
<n-flex align="center">
|
|
||||||
<n-tag size="small">{{ sp._id }}</n-tag>
|
|
||||||
<n-button
|
|
||||||
text
|
|
||||||
type="info"
|
|
||||||
@click="
|
|
||||||
router.push({
|
|
||||||
name: 'problem',
|
|
||||||
params: { problemID: sp._id },
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ sp.title }}
|
|
||||||
</n-button>
|
|
||||||
</n-flex>
|
|
||||||
<!-- getSimilarProblems 已经过 toProblemRow,难度是中文,不是 Low/Mid/High -->
|
|
||||||
<n-tag
|
|
||||||
v-if="sp.difficulty"
|
|
||||||
size="small"
|
|
||||||
:type="
|
|
||||||
sp.difficulty === '简单'
|
|
||||||
? 'success'
|
|
||||||
: sp.difficulty === '困难'
|
|
||||||
? 'error'
|
|
||||||
: 'warning'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ sp.difficulty }}
|
|
||||||
</n-tag>
|
|
||||||
</n-flex>
|
|
||||||
</n-list-item>
|
|
||||||
</n-list>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ const FlowchartResult = defineAsyncComponent(() => import("./FlowchartResult.vue
|
|||||||
const PeerLatest = defineAsyncComponent(() => import("./PeerLatest.vue"))
|
const PeerLatest = defineAsyncComponent(() => import("./PeerLatest.vue"))
|
||||||
|
|
||||||
const submissionStore = useSubmissionStore()
|
const submissionStore = useSubmissionStore()
|
||||||
const { submission, syntaxErrorInfo, resultSegment } = storeToRefs(submissionStore)
|
const { submission, syntaxErrorInfo, resultSegment, formattedBeforeSubmit } =
|
||||||
|
storeToRefs(submissionStore)
|
||||||
const { problem } = storeToRefs(useProblemStore())
|
const { problem } = storeToRefs(useProblemStore())
|
||||||
const codeStore = useCodeStore()
|
const codeStore = useCodeStore()
|
||||||
|
|
||||||
@@ -55,7 +56,12 @@ const canTrial = computed(() => {
|
|||||||
<n-alert type="warning" title="代码有语法错误,还没有提交" />
|
<n-alert type="warning" title="代码有语法错误,还没有提交" />
|
||||||
<PythonErrorExplain :err-info="syntaxErrorInfo" />
|
<PythonErrorExplain :err-info="syntaxErrorInfo" />
|
||||||
</n-flex>
|
</n-flex>
|
||||||
<SubmissionResult v-else-if="submission" :submission="submission" />
|
<template v-else-if="submission">
|
||||||
|
<p v-if="formattedBeforeSubmit" class="formatted">
|
||||||
|
提交之前自动整理了代码格式(缩进、空格),编辑器里的代码也跟着变了,按 Ctrl+Z 能撤回
|
||||||
|
</p>
|
||||||
|
<SubmissionResult :submission="submission" />
|
||||||
|
</template>
|
||||||
<PeerLatest v-else-if="teacherCollab" />
|
<PeerLatest v-else-if="teacherCollab" />
|
||||||
<n-empty
|
<n-empty
|
||||||
v-else
|
v-else
|
||||||
@@ -79,6 +85,12 @@ const canTrial = computed(() => {
|
|||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.formatted {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
margin-top: 48px;
|
margin-top: 48px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ProblemRow } from "utils/types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同一道题两处(题面末尾、结果页签)都挂着时只拉一次。放在模块级:写在 setup 里就成了
|
||||||
|
* 每个实例各一份
|
||||||
|
*/
|
||||||
|
const cache = new Map<string, Promise<ProblemRow[]>>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { storeToRefs } from "pinia"
|
||||||
|
import { getSimilarProblems } from "oj/api"
|
||||||
|
import { useProblemStore } from "oj/store/problem"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相似题推荐。题面末尾(做对了、或者错了 3 次以上)和结果页签(不在课堂里的题做对之后)
|
||||||
|
* 两处用:原来只在题面最底下,学生交完不会再滚回去看。什么时候出现由调用方的 v-if 决定。
|
||||||
|
*/
|
||||||
|
const props = defineProps<{ title: string }>()
|
||||||
|
|
||||||
|
const { problem } = storeToRefs(useProblemStore())
|
||||||
|
const list = ref<ProblemRow[]>([])
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => problem.value?._id,
|
||||||
|
async (id) => {
|
||||||
|
list.value = []
|
||||||
|
if (!id) return
|
||||||
|
if (!cache.has(id))
|
||||||
|
cache.set(
|
||||||
|
id,
|
||||||
|
getSimilarProblems(id).catch(() => []),
|
||||||
|
)
|
||||||
|
const rows = await cache.get(id)!
|
||||||
|
if (problem.value?._id === id) list.value = rows
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// getSimilarProblems 已经过 toProblemRow,难度是中文,不是 Low/Mid/High
|
||||||
|
function difficultyType(difficulty: string) {
|
||||||
|
if (difficulty === "简单") return "success"
|
||||||
|
if (difficulty === "困难") return "error"
|
||||||
|
return "warning"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section v-if="list.length" class="similar">
|
||||||
|
<h3 class="title">{{ props.title }}</h3>
|
||||||
|
<n-list bordered>
|
||||||
|
<n-list-item v-for="sp in list" :key="sp._id">
|
||||||
|
<n-flex align="center" justify="space-between" :wrap="false">
|
||||||
|
<router-link :to="{ name: 'problem', params: { problemID: sp._id } }" class="link">
|
||||||
|
<n-tag size="small" :bordered="false">{{ sp._id }}</n-tag>
|
||||||
|
<span class="name">{{ sp.title }}</span>
|
||||||
|
</router-link>
|
||||||
|
<n-tag v-if="sp.difficulty" size="small" :type="difficultyType(sp.difficulty)">
|
||||||
|
{{ sp.difficulty }}
|
||||||
|
</n-tag>
|
||||||
|
</n-flex>
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.similar {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover .name {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -19,6 +19,8 @@ import PythonErrorExplain from "./PythonErrorExplain.vue"
|
|||||||
import RuntimeErrorExplain from "./RuntimeErrorExplain.vue"
|
import RuntimeErrorExplain from "./RuntimeErrorExplain.vue"
|
||||||
import WrongAnswerExplain from "./WrongAnswerExplain.vue"
|
import WrongAnswerExplain from "./WrongAnswerExplain.vue"
|
||||||
import LessonNext from "./LessonNext.vue"
|
import LessonNext from "./LessonNext.vue"
|
||||||
|
import SimilarProblems from "./SimilarProblems.vue"
|
||||||
|
import { useLessonStore } from "oj/store/lesson"
|
||||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||||
import { MdPreview } from "md-editor-v3"
|
import { MdPreview } from "md-editor-v3"
|
||||||
import "md-editor-v3/lib/preview.css"
|
import "md-editor-v3/lib/preview.css"
|
||||||
@@ -98,6 +100,20 @@ const showLessonNext = computed(
|
|||||||
!props.peer && props.submission?.result === SubmissionStatus.accepted && ctx.value.lessonNext,
|
!props.peer && props.submission?.result === SubmissionStatus.accepted && ctx.value.lessonNext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过了、但这道题不在这节课里(没有「下一题」可给):给几道相似题接着练。
|
||||||
|
* 原来相似题只在题面最底下,交完不会有人再滚回去看
|
||||||
|
*/
|
||||||
|
const lessonStore = useLessonStore()
|
||||||
|
const showSimilar = computed(
|
||||||
|
() =>
|
||||||
|
!props.peer &&
|
||||||
|
props.submission?.result === SubmissionStatus.accepted &&
|
||||||
|
ctx.value.similar &&
|
||||||
|
!!problemStore.problem &&
|
||||||
|
!lessonStore.includes(problemStore.problem._id),
|
||||||
|
)
|
||||||
|
|
||||||
const msg = computed(() => {
|
const msg = computed(() => {
|
||||||
if (!props.submission) return ""
|
if (!props.submission) return ""
|
||||||
// 走 PythonErrorExplain / RuntimeErrorExplain 那两张中文卡片
|
// 走 PythonErrorExplain / RuntimeErrorExplain 那两张中文卡片
|
||||||
@@ -216,6 +232,7 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
|||||||
v-if="showLessonNext && problemStore.problem"
|
v-if="showLessonNext && problemStore.problem"
|
||||||
:problem-display-id="problemStore.problem._id"
|
:problem-display-id="problemStore.problem._id"
|
||||||
/>
|
/>
|
||||||
|
<SimilarProblems v-if="showSimilar" title="再练几道相似的" />
|
||||||
<n-flex
|
<n-flex
|
||||||
vertical
|
vertical
|
||||||
v-if="
|
v-if="
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useSubmissionStore } from "oj/store/submission"
|
|||||||
import { getSubmitButtonState } from "./submitButtonState"
|
import { getSubmitButtonState } from "./submitButtonState"
|
||||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
|
import { SubmissionStatus } from "utils/constants"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提交按钮。只把点击交给 store:提交的状态在 submission store,结果显示在左栏的
|
* 提交按钮。只把点击交给 store:提交的状态在 submission store,结果显示在左栏的
|
||||||
@@ -33,6 +34,14 @@ const buttonState = computed(() =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这道题刚交对了:「提交」降成描边按钮,让结果页签里的「下一题」是唯一的主按钮
|
||||||
|
* (设计文档 5.4)。改了代码想再交一次照样能交
|
||||||
|
*/
|
||||||
|
const justAccepted = computed(
|
||||||
|
() => submissionStore.submission?.result === SubmissionStatus.accepted,
|
||||||
|
)
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
if (buttonState.value.disabled) return
|
if (buttonState.value.disabled) return
|
||||||
submissionStore.submit({
|
submissionStore.submit({
|
||||||
@@ -46,7 +55,7 @@ function submit() {
|
|||||||
<n-button
|
<n-button
|
||||||
:size="props.size ?? (isDesktop ? 'medium' : 'small')"
|
:size="props.size ?? (isDesktop ? 'medium' : 'small')"
|
||||||
:block="props.block"
|
:block="props.block"
|
||||||
type="primary"
|
:type="justAccepted ? 'default' : 'primary'"
|
||||||
:disabled="buttonState.disabled"
|
:disabled="buttonState.disabled"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -147,10 +147,40 @@ export function explainDiff(
|
|||||||
case "values":
|
case "values":
|
||||||
return "输出的格式对了,但算出来的数不对。拿这个例子的输入,自己一步一步算一遍,看看程序在哪一步和你想的不一样。"
|
return "输出的格式对了,但算出来的数不对。拿这个例子的输入,自己一步一步算一遍,看看程序在哪一步和你想的不一样。"
|
||||||
case "other":
|
case "other":
|
||||||
return "对照一下你的输出和正确输出,找找从哪里开始不一样。"
|
return otherHint(check)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 放进一句话里的值:太长的截断,免得一句提示撑满一屏 */
|
||||||
|
function quote(text: string, max = 24) {
|
||||||
|
const flat = text.trim()
|
||||||
|
return `「${flat.length > max ? `${flat.slice(0, max)}…` : flat}」`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「其他」:格式和内容都不一样(占样例错误的 44%)。原来只说「对照一下,找找从哪里开始
|
||||||
|
* 不一样」,对 D / E 这种整个值不同的情况没用 —— 学生看得出不一样,看不出为什么。
|
||||||
|
* 改成把具体的值说出来,再告诉他拿这个例子的输入去走一遍程序(设计文档第 6 节)。
|
||||||
|
*/
|
||||||
|
function otherHint(check: SampleCheck) {
|
||||||
|
const expected = (check.expected ?? "").replace(/\s+$/, "")
|
||||||
|
const output = (check.output ?? "").replace(/\s+$/, "")
|
||||||
|
const no = (check.index ?? 0) + 1
|
||||||
|
const input = (check.input ?? "").trim()
|
||||||
|
const walk =
|
||||||
|
input && !input.includes("\n") && input.length <= 24
|
||||||
|
? `拿例子 ${no} 的输入${quote(input)},一行一行走一遍你的程序,看它走进了哪个分支、算出了什么。`
|
||||||
|
: `拿例子 ${no} 的输入,一行一行走一遍你的程序,看它走进了哪个分支、算出了什么。`
|
||||||
|
if (!expected.includes("\n") && !output.includes("\n")) {
|
||||||
|
return `你的程序输出了${quote(output)},正确的是${quote(expected)}。${walk}`
|
||||||
|
}
|
||||||
|
const line = firstDifferentLine(expected, output)
|
||||||
|
const want = expected.split("\n")[line] ?? ""
|
||||||
|
const got = output.split("\n")[line]
|
||||||
|
const gotText = got === undefined ? "什么都没有" : quote(got)
|
||||||
|
return `从第 ${line + 1} 行开始不一样:正确的是${quote(want)},你输出的是${gotText}。${walk}`
|
||||||
|
}
|
||||||
|
|
||||||
/** 第一处不一样的行号,0 起;完全一样(只差结尾空白)时为 -1 */
|
/** 第一处不一样的行号,0 起;完全一样(只差结尾空白)时为 -1 */
|
||||||
export function firstDifferentLine(expected: string, output: string) {
|
export function firstDifferentLine(expected: string, output: string) {
|
||||||
const e = expected.replace(/\s+$/, "").split("\n")
|
const e = expected.replace(/\s+$/, "").split("\n")
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ export const useSubmissionStore = defineStore("submission", () => {
|
|||||||
*/
|
*/
|
||||||
const syntaxErrorInfo = ref("")
|
const syntaxErrorInfo = ref("")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这次提交之前自动排版改了代码。排版会直接改掉编辑器里的代码(缩进、空格、换行),
|
||||||
|
* 学生交完发现代码「变了样」却不知道为什么 —— 结果页签里提一句
|
||||||
|
*/
|
||||||
|
const formattedBeforeSubmit = ref(false)
|
||||||
|
|
||||||
const { start: startCooldown, isPending: isCooldown } = useTimeout(5000, {
|
const { start: startCooldown, isPending: isCooldown } = useTimeout(5000, {
|
||||||
controls: true,
|
controls: true,
|
||||||
immediate: false,
|
immediate: false,
|
||||||
@@ -84,6 +90,7 @@ export const useSubmissionStore = defineStore("submission", () => {
|
|||||||
const problem = problemStore.problem
|
const problem = problemStore.problem
|
||||||
if (!problem) return
|
if (!problem) return
|
||||||
syntaxErrorInfo.value = ""
|
syntaxErrorInfo.value = ""
|
||||||
|
formattedBeforeSubmit.value = false
|
||||||
|
|
||||||
// 0. 提交前自动格式化(Python 用 ruff,C/C++ 用 clang-format,SQL 用 sqlparse)。
|
// 0. 提交前自动格式化(Python 用 ruff,C/C++ 用 clang-format,SQL 用 sqlparse)。
|
||||||
// Python 在格式化之前先由服务端的 CPython 查一遍语法,有错就不提交
|
// Python 在格式化之前先由服务端的 CPython 查一遍语法,有错就不提交
|
||||||
@@ -95,7 +102,10 @@ export const useSubmissionStore = defineStore("submission", () => {
|
|||||||
code: codeStore.code.value,
|
code: codeStore.code.value,
|
||||||
language: formatLang,
|
language: formatLang,
|
||||||
})
|
})
|
||||||
|
if (res.code !== codeStore.code.value) {
|
||||||
codeStore.setCode(res.code)
|
codeStore.setCode(res.code)
|
||||||
|
formattedBeforeSubmit.value = true
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (errorCode(e) === "syntax-error") {
|
if (errorCode(e) === "syntax-error") {
|
||||||
// 仅 Python 会出现:message 是 CPython 的报错原文,交给 PythonErrorExplain 翻译
|
// 仅 Python 会出现:message 是 CPython 的报错原文,交给 PythonErrorExplain 翻译
|
||||||
@@ -151,6 +161,7 @@ export const useSubmissionStore = defineStore("submission", () => {
|
|||||||
*/
|
*/
|
||||||
function reset() {
|
function reset() {
|
||||||
syntaxErrorInfo.value = ""
|
syntaxErrorInfo.value = ""
|
||||||
|
formattedBeforeSubmit.value = false
|
||||||
resultSegment.value = "submit"
|
resultSegment.value = "submit"
|
||||||
monitor.reset()
|
monitor.reset()
|
||||||
hint.clearHint()
|
hint.clearHint()
|
||||||
@@ -183,6 +194,7 @@ export const useSubmissionStore = defineStore("submission", () => {
|
|||||||
isFormatting,
|
isFormatting,
|
||||||
isSubmittingRequest,
|
isSubmittingRequest,
|
||||||
syntaxErrorInfo,
|
syntaxErrorInfo,
|
||||||
|
formattedBeforeSubmit,
|
||||||
isCooldown,
|
isCooldown,
|
||||||
submit,
|
submit,
|
||||||
reset,
|
reset,
|
||||||
|
|||||||
@@ -59,8 +59,9 @@ onUnmounted(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.toast {
|
.toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
/* 右上角、顶栏下面。原来在右下角,题目页上正好和结果、点评弹窗叠在一起 */
|
||||||
right: 24px;
|
right: 24px;
|
||||||
bottom: 24px;
|
top: 72px;
|
||||||
z-index: 3000;
|
z-index: 3000;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
@@ -162,7 +162,8 @@ export const HINT_LEVEL_COMPILE = -1
|
|||||||
export function hintLevelLabel(level: number) {
|
export function hintLevelLabel(level: number) {
|
||||||
if (level === HINT_LEVEL_COMPILE) return "编译错误"
|
if (level === HINT_LEVEL_COMPILE) return "编译错误"
|
||||||
const item = HINT_LEVELS.find((entry) => entry.level === level)
|
const item = HINT_LEVELS.find((entry) => entry.level === level)
|
||||||
return item ? `L${item.level} ${item.name}` : `L${level}`
|
// 给学生看的,从 1 数起、不写「L0」:学生不知道 L 是什么,也不会从 0 数
|
||||||
|
return item ? `第 ${item.level + 1} 档 · ${item.name}` : `第 ${level + 1} 档`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user