feat(后台): SQL 题有了自己的出题页——左边写,右边就是学生看到的样子,改答案、改数据马上重跑

- 「新建题目」先选编程题还是 SQL 题,选了就不能改;编辑时按题目本身进对应的页
- SQL 出题页(设计稿「SQL 出题页」方案 B):测试数据一组一行,改完自动重跑,
  跑不通的用中文说原因;保存时才打包上传测试点,不用再手动「预览」「上传」
- 新建时存草稿,误关页面不丢
- 编程题出题页去掉 SQL 那一堆分支(以前勾上 SQL 会静默清掉语法要求和流程图)
- 题目列表能按题型筛,SQL 题标题后面挂标签

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-09 18:59:15 +08:00
1 parent 25d3296410
commit 2cb39fd96b
13 files changed
+1414 -185

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
<script setup lang="ts">
import { isSqlProblem } from "@oj2/contract"
import type { AdminProblem } from "utils/types"
import { getProblem } from "../api"
import ProblemDetail from "./detail.vue"
import SqlDetail from "./SqlDetail.vue"
/**
* 出题 / 改题的入口:编程题和 SQL 题是两张不同的页。
* 新建看地址上的 `?type=sql`(后台题目列表「新建题目」下拉里选的),
* 编辑看题目本身 —— 题型建好之后不能改,所以没有「切换题型」这回事。
*/
const props = defineProps<{
problemID?: string
contestID?: string
}>()
const route = useRoute()
const router = useRouter()
const message = useMessage()
const loaded = shallowRef<AdminProblem | null>(null)
const kind = ref<"code" | "sql" | null>(null)
async function resolve() {
kind.value = null
loaded.value = null
if (!props.problemID) {
kind.value = route.query.type === "sql" ? "sql" : "code"
return
}
try {
loaded.value = await getProblem(props.problemID)
kind.value = isSqlProblem(loaded.value) ? "sql" : "code"
} catch {
message.error("获取题目失败")
router.push(
props.contestID
? { name: "admin contest problem list", params: { contestID: props.contestID } }
: { name: "admin problem list" },
)
}
}
watch(() => [props.problemID, route.query.type], resolve, { immediate: true })
</script>
<template>
<SqlDetail
v-if="kind === 'sql'"
:key="`sql-${problemID ?? 'new'}`"
:problem-i-d="problemID"
:contest-i-d="contestID"
:initial="loaded"
/>
<ProblemDetail
v-else-if="kind === 'code'"
:key="`code-${problemID ?? 'new'}`"
:problem-i-d="problemID"
:contest-i-d="contestID"
:initial="loaded"
/>
</template>