Files
ojnext/src/oj/problem/detail.vue
yuetsh 38500671af 新增 SQL 题型前端支持
- LANGUAGE 类型与六张语言映射表补 SQL;编辑器 SQLite 方言高亮
- 出题表单:SQL 语言选项、题型/行序配置卡片、数据脚本压缩包上传、标准答案必填校验
- 修复做题页语言回退硬编码 Python3 的问题(SQL 题会提交被拒)
- SQL 题隐藏自测猫/自测屏/复制到自测猫(外部运行器不支持 SQL)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 17:23:23 -06:00

285 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { getProblem } from "oj/api"
import { useBreakpoints } from "shared/composables/breakpoints"
import { storeToRefs } from "pinia"
import { useProblemStore } from "oj/store/problem"
import { useScreenModeStore } from "shared/store/screenMode"
import { useMyFlowchartStore } from "shared/store/myFlowchart"
// 抽成具名 loader便于进页面时与接口并行预取编辑器 chunk
const loadProblemEditor = () => import("./components/ProblemEditor.vue")
const loadContestEditor = () => import("./components/ContestEditor.vue")
const ProblemEditor = defineAsyncComponent(loadProblemEditor)
const ContestEditor = defineAsyncComponent(loadContestEditor)
const EditorForTest = defineAsyncComponent(
() => import("./components/EditorForTest.vue"),
)
const ProblemContent = defineAsyncComponent(
() => import("./components/ProblemContent.vue"),
)
const ProblemInfo = defineAsyncComponent(
() => import("./components/ProblemInfo.vue"),
)
const ProblemSubmission = defineAsyncComponent(
() => import("./components/ProblemSubmission.vue"),
)
const ProblemComment = defineAsyncComponent(
() => import("./components/ProblemComment.vue"),
)
const ProblemFlowchart = defineAsyncComponent(
() => import("./components/ProblemFlowchart.vue"),
)
const MyFlowchartTab = defineAsyncComponent(
() => import("./components/MyFlowchartTab.vue"),
)
interface Props {
problemID: string
contestID?: string
problemSetId?: string
}
const { problemID, contestID = "", problemSetId = "" } = defineProps<Props>()
const errMsg = ref("无数据")
const route = useRoute()
const router = useRouter()
const problemStore = useProblemStore()
const screenModeStore = useScreenModeStore()
const myFlowchartStore = useMyFlowchartStore()
const { problem } = storeToRefs(problemStore)
const { shouldShowProblem } = storeToRefs(screenModeStore)
const { isMobile, isDesktop } = useBreakpoints()
const tabOptions = computed(() => {
const options: string[] = ["content"]
if (problem.value?.show_flowchart) {
options.push("flowchart")
}
if (isMobile.value) {
options.push("editor")
}
options.push("info")
if (!contestID) {
options.push("comment")
}
if (myFlowchartStore.showing) {
options.push("my-flowchart")
}
options.push("submission")
return options
})
const currentTab = ref("content")
const inProblem = computed(() => route.name === "problem")
watch(
[() => route.query.tab, () => tabOptions.value],
([rawTab]) => {
const tabs = tabOptions.value
const fallback = tabs[0] ?? "content"
currentTab.value = tabs.includes(rawTab as string)
? (rawTab as string)
: fallback
},
{ immediate: true },
)
watch(currentTab, (tab) => {
if (!tabOptions.value.includes(tab) || route.query.tab === tab) return
router.replace({
query: { ...route.query, tab },
})
})
watch(
() => myFlowchartStore.showing,
(showing) => {
if (showing) currentTab.value = "my-flowchart"
},
)
async function init() {
screenModeStore.resetScreenMode()
// 并行预取右侧编辑器 chunkCodeMirror ~370K+
// 避免等 getProblem 返回后才串行下载,编辑器才迟迟出现
;(inProblem.value ? loadProblemEditor : loadContestEditor)()
try {
const res = await getProblem(problemID, contestID)
problem.value = res.data
} catch (err: any) {
problem.value = null
if (err.data === "Contest has not started yet.") {
errMsg.value = "比赛还没有开始"
}
}
}
onMounted(init)
watch(() => problemID, init)
onBeforeUnmount(() => {
problem.value = null
errMsg.value = "无数据"
screenModeStore.resetScreenMode()
myFlowchartStore.hide()
})
watch(isMobile, (value) => {
if (value) screenModeStore.resetScreenMode()
})
// SQL 题不支持"自测"模式(外部代码运行器无法执行 SQL切到该屏时自动跳到下一模式
watch(
() => screenModeStore.isCodeOnlyMode,
(codeOnly) => {
if (codeOnly && problem.value?.languages.includes("SQL")) {
screenModeStore.switchScreenMode()
}
},
)
</script>
<template>
<template v-if="problem">
<n-split
v-if="isDesktop && screenModeStore.isBothMode"
direction="horizontal"
:default-size="0.43"
:min="0.2"
:max="0.8"
style="height: calc(100vh - 92px)"
>
<template #1>
<n-scrollbar style="height: 100%">
<n-tabs v-model:value="currentTab" type="segment">
<n-tab-pane name="content" tab="题目描述">
<ProblemContent />
</n-tab-pane>
<n-tab-pane
v-if="problem.show_flowchart && problem.mermaid_code"
name="flowchart"
tab="流程图表"
>
<ProblemFlowchart />
</n-tab-pane>
<n-tab-pane name="info" tab="题目统计" :disabled="!!problemSetId">
<ProblemInfo />
</n-tab-pane>
<n-tab-pane
v-if="!contestID"
name="comment"
tab="题目点评"
:disabled="!!problemSetId"
>
<ProblemComment />
</n-tab-pane>
<n-tab-pane
v-if="myFlowchartStore.showing"
name="my-flowchart"
tab="我的流程图"
>
<MyFlowchartTab />
</n-tab-pane>
<n-tab-pane
name="submission"
tab="我的提交"
:disabled="!!problemSetId"
>
<ProblemSubmission />
</n-tab-pane>
</n-tabs>
</n-scrollbar>
</template>
<template #2>
<component :is="inProblem ? ProblemEditor : ContestEditor" />
</template>
</n-split>
<!-- Desktop: code only mode -->
<template v-else-if="isDesktop && screenModeStore.isCodeOnlyMode">
<EditorForTest />
</template>
<!-- Desktop: problem only mode -->
<template v-else-if="isDesktop && shouldShowProblem">
<n-scrollbar style="max-height: calc(100vh - 92px)">
<n-tabs v-model:value="currentTab" type="segment">
<n-tab-pane name="content" tab="题目描述">
<ProblemContent />
</n-tab-pane>
<n-tab-pane
v-if="problem.show_flowchart && problem.mermaid_code"
name="flowchart"
tab="流程图表"
>
<ProblemFlowchart />
</n-tab-pane>
<n-tab-pane name="info" tab="题目统计" :disabled="!!problemSetId">
<ProblemInfo />
</n-tab-pane>
<n-tab-pane
v-if="!contestID"
name="comment"
tab="题目点评"
:disabled="!!problemSetId"
>
<ProblemComment />
</n-tab-pane>
<n-tab-pane
v-if="myFlowchartStore.showing"
name="my-flowchart"
tab="我的流程图"
>
<MyFlowchartTab />
</n-tab-pane>
<n-tab-pane
name="submission"
tab="我的提交"
:disabled="!!problemSetId"
>
<ProblemSubmission />
</n-tab-pane>
</n-tabs>
</n-scrollbar>
</template>
<!-- Mobile -->
<n-tabs v-else v-model:value="currentTab" type="segment">
<n-tab-pane name="content" tab="描述">
<ProblemContent />
</n-tab-pane>
<n-tab-pane v-if="problem.show_flowchart" name="flowchart" tab="流程">
<ProblemFlowchart />
</n-tab-pane>
<n-tab-pane name="editor" tab="代码">
<component :is="inProblem ? ProblemEditor : ContestEditor" />
</n-tab-pane>
<n-tab-pane name="info" tab="统计" :disabled="!!problemSetId">
<ProblemInfo />
</n-tab-pane>
<n-tab-pane
v-if="!contestID"
name="comment"
tab="点评"
:disabled="!!problemSetId"
>
<ProblemComment />
</n-tab-pane>
<n-tab-pane
v-if="myFlowchartStore.showing"
name="my-flowchart"
tab="我的流程图"
>
<MyFlowchartTab />
</n-tab-pane>
<n-tab-pane name="submission" tab="提交" :disabled="!!problemSetId">
<ProblemSubmission />
</n-tab-pane>
</n-tabs>
</template>
<n-empty v-else :description="errMsg"></n-empty>
</template>