feat(题目): 左栏顶上标出「这节课做完几道」、题单进度或比赛倒计时,统计 / 点评 / 我的提交收进抽屉

- 上下文条(按入口三选一):课堂 = 「这节课 做完 3/5」+ 每道题号 + 上一题 / 下一题;
  题单 = 题单名 + 进度 +「第 N 题」+ 上一题 / 下一题;比赛 = 比赛名 + 每道题号 +「还剩 38:12」+ 排名。
  题号只放号码,状态用 ✓ / ! 字符区分,不只靠颜色;多于 5 道横向滚动,当前题保持可见。
  通过之后当场 +1,不用刷新
- 左栏只剩「题目 / 结果」两个页签;统计 / 点评 / 我的提交变成页签行右侧的按钮,点开是抽屉,
  只盖题面、编辑器不动,交完判出结果时自动收起。手机上收进页签行的「⋯」
- 流程图并进「题目」页签:参考流程图默认折叠;自己画到 A/S 的那张缩略显示、可放大,
  一进题就能看到(原来要先切到「流程图」这门「语言」),也不再强行切页签
- 「本题已经被你解决啦」两条整宽提示改成标题下的「✓ 已解决 / ! 尝试过」标签;
  时间内存限制只写「限时 1 秒」
- 入口差异收成一张表(problemPageContext):题单里不给抽屉、状态标签、相似题;
  比赛里没有点评和求助。工具栏的「本题提交」挪到「我的提交」抽屉底部
- 顺手修:没有例子的题点「去自测猫」会抛错;「我的提交」没交过时一片空白

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-09-28 16:02:28 +08:00
1 parent 45505e8384
commit 116b0a2c95
16 files changed
+1057 -251

No files matched your search

@@ -0,0 +1,92 @@
import type { RouteLocationNormalizedLoaded } from "vue-router"
/**
* 题目页的三种入口:题库(含课堂)、题单、比赛。同一个 detail.vue 挂在三条路由上。
*
* 入口之间的差异**只在这张表里判断**。原来散在各个组件里各查各的路由:同一件事
* 在题单里是「禁用」、在比赛里是「隐藏」,加一处功能就得把每个组件翻一遍
* (见 docs/specs/2026-09-28-problem-page-redesign-design.md 第 8 节)。
*/
export type ProblemEntry = "problem" | "problemset" | "contest"
export type ProblemDrawer = "info" | "reaction" | "submission"
export const DRAWER_TITLE: Record<ProblemDrawer, string> = {
info: "统计",
reaction: "点评",
submission: "我的提交",
}
interface EntryRules {
/** 页签行右侧的抽屉,按显示顺序 */
drawers: readonly ProblemDrawer[]
/** 标题下的「已解决 / 尝试过」 */
statusTag: boolean
/** 题面末尾的相似题推荐 */
similar: boolean
/** 通过之后给「这节课的下一题」 */
lessonNext: boolean
/** 通过之后 1.5 秒弹强制点评 */
reviewAfterAccepted: boolean
/** 通过之后 1.5 秒回题单 */
backToProblemSet: boolean
/** 举手求助 */
help: boolean
}
const RULES: Record<ProblemEntry, EntryRules> = {
problem: {
drawers: ["info", "reaction", "submission"],
statusTag: true,
similar: true,
lessonNext: true,
reviewAfterAccepted: true,
backToProblemSet: false,
help: true,
},
// 题单里什么「以前的」都不给看:加入题单之前的提交、统计、点评都能拿来抄答案
problemset: {
drawers: [],
statusTag: false,
// 点进相似题就离开了题单
similar: false,
lessonNext: false,
// 1.5 秒后要跳回题单页,弹了也会被冲掉
reviewAfterAccepted: false,
backToProblemSet: true,
help: true,
},
contest: {
drawers: ["info", "submission"],
statusTag: true,
// 相似题接口按 displayId 在公开题库里找,比赛题的编号默认是 1/2/3,
// 撞上同号公开题时反而会在比赛中把题库列给学生
similar: false,
lessonNext: false,
reviewAfterAccepted: false,
backToProblemSet: false,
help: false,
},
}
export function problemEntryOf(route: RouteLocationNormalizedLoaded): ProblemEntry {
if (route.name === "contest problem") return "contest"
if (route.name === "problemset problem") return "problemset"
return "problem"
}
/**
* 当前入口和它的规则。跟着路由算、不在 setup 时取一次:三条路由之间是复用同一个页面的。
*/
export function useProblemPageContext() {
const route = useRoute()
return computed(() => {
const entry = problemEntryOf(route)
return {
entry,
contestId: entry === "contest" ? String(route.params.contestID ?? "") : "",
problemSetId: entry === "problemset" ? String(route.params.problemSetId ?? "") : "",
...RULES[entry],
}
})
}