refactor(前端): shared / utils 不再 import oj 和 admin 下的任何东西
依赖方向原来是反的:顶栏、登录框、举手列表这些 shared 组件从 oj/ 里拿东西。 - 跨端的接口挪进 shared/api.ts(按 apps/web/CLAUDE.md 的分法):学生端题目列表、 出题人列表、这节课的题、待弹的成就 - 这节课的 store → shared/store/lesson.ts;头像 RankAvatar → shared/components - 纯函数挪进 utils/:举手列表的错法归类 errorKind、它依赖的答案错误分类 wrongAnswer 和 Python 报错翻译 pythonError、题目列表行的 toProblemRow - 只有后台在用的出题人下拉 AuthorSelect 挪回 admin/components 纯搬家。首页、题目列表、题目页、排名、课堂看板、后台题目列表打开无报错, 顶栏题号框敲 1001 回车直达题目页。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d4aadc7722
commit
e8a20d12f9
38 files changed
+78
-78
No files matched your search
@@ -0,0 +1,92 @@
|
||||
import { defineStore } from "pinia"
|
||||
import { getClassActivity } from "shared/api"
|
||||
import { LANGUAGE_SHOW_VALUE } from "utils/constants"
|
||||
import type { ClassActivity, ClassActivityProblem } from "utils/types"
|
||||
|
||||
/** 同一节课里来回换题不用每次都重拉;过了这么久再进题目页就重拉一次 */
|
||||
const STALE_MS = 2 * 60 * 1000
|
||||
|
||||
/**
|
||||
* 「这节课的题」:首页「班里在做」那几道(老师在课堂看板布置的,或者从同班提交推断的)。
|
||||
*
|
||||
* 题目页有两处要用:顶部的课堂条(做完几道、每道的状态),和通过之后的「下一题」。
|
||||
* 原来「下一题」每次通过都自己拉一遍,现在两处读同一份,通过之后由判题那边
|
||||
* (SubmissionEffects)先把这道就地标成做完、再重拉一次。
|
||||
*/
|
||||
export const useLessonStore = defineStore("lesson", () => {
|
||||
const activity = ref<ClassActivity | null>(null)
|
||||
let loadedAt = 0
|
||||
let loading: Promise<void> | null = null
|
||||
/**
|
||||
* 只认最后发出去的那次。进页面那次还在路上时学生就交对了:先就地标成做完、再强制重拉 ——
|
||||
* 先发的那次要是后到,会把「做完」盖回去,「下一题」又指回刚做完的这道
|
||||
*/
|
||||
let sequence = 0
|
||||
|
||||
/** 没拉过或者旧了才拉;force 用在刚通过一道题之后 */
|
||||
function load(force = false) {
|
||||
if (!force && activity.value && Date.now() - loadedAt < STALE_MS) return loading
|
||||
if (!force && loading) return loading
|
||||
const mine = ++sequence
|
||||
loading = getClassActivity()
|
||||
.then((res) => {
|
||||
if (mine !== sequence) return
|
||||
activity.value = res
|
||||
loadedAt = Date.now()
|
||||
})
|
||||
// 拉不到就没有课堂条,不影响做题
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
if (mine === sequence) loading = null
|
||||
})
|
||||
return loading
|
||||
}
|
||||
|
||||
/** 刚通过:不等重拉,先把这道标成做完,课堂条上的「做完 N/M」马上加一 */
|
||||
function markAccepted(problemDisplayId: string) {
|
||||
const item = find(problemDisplayId)
|
||||
if (item) item.myStatus = "accepted"
|
||||
}
|
||||
|
||||
function find(problemDisplayId: string): ClassActivityProblem | undefined {
|
||||
const id = problemDisplayId.toLowerCase()
|
||||
return activity.value?.problems.find((item) => item.problemDisplayId.toLowerCase() === id)
|
||||
}
|
||||
|
||||
/** 这道题在不在这节课里;不在(学生在做别的)就没有课堂条、也没有「下一题」 */
|
||||
function includes(problemDisplayId: string) {
|
||||
return !!find(problemDisplayId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过这道之后该去哪一道:按列表顺序排在这道**后面**、还没通过的第一道,后面都做完了
|
||||
* 再从头找 —— 学生常常跳着做,先做完了第 3 道再回头做第 1 道。当前这道本身不看它的
|
||||
* myStatus:重拉回来之前,它还是通过之前那一刻的状态。
|
||||
* 这道不在这节课里返回 null;在,但别的都做完了,next 为 null。
|
||||
*/
|
||||
function nextAfter(problemDisplayId: string) {
|
||||
const list = activity.value?.problems ?? []
|
||||
const id = problemDisplayId.toLowerCase()
|
||||
const index = list.findIndex((item) => item.problemDisplayId.toLowerCase() === id)
|
||||
if (index < 0) return null
|
||||
const rest = [...list.slice(index + 1), ...list.slice(0, index)].filter(
|
||||
(item) => item.myStatus !== "accepted",
|
||||
)
|
||||
return { total: list.length, next: rest[0] ?? null, remaining: rest.length }
|
||||
}
|
||||
|
||||
/** 老师布置时选的语言,给界面写「C语言」;没选(推断出来的课、以前布置的)为空 */
|
||||
const languageText = computed(() => {
|
||||
const language = activity.value?.language
|
||||
return language ? LANGUAGE_SHOW_VALUE[language] : ""
|
||||
})
|
||||
|
||||
/** 老师布置的叫「老师布置的题」(选了语言带上语言),推断出来的叫「这节课的题」 */
|
||||
const label = computed(() =>
|
||||
activity.value?.source === "teacher"
|
||||
? `老师布置的题${languageText.value ? ` · ${languageText.value}` : ""}`
|
||||
: "这节课的题",
|
||||
)
|
||||
|
||||
return { activity, label, languageText, load, markAccepted, includes, nextAfter }
|
||||
})
|
||||
Reference in new issue
Block a user