diff --git a/apps/api/src/db/schema.ts b/apps/api/src/db/schema.ts index d930519..919aa8f 100644 --- a/apps/api/src/db/schema.ts +++ b/apps/api/src/db/schema.ts @@ -1195,7 +1195,7 @@ export const problemsetBadge = pgTable( * 冲突目标就是 (user_id, tutorial_id),再挂个 id 序列没有任何用处。 * * **只记「读到哪一课 + 停留多久」。** 练一练的作答另有一张表(下面的 `exerciseAttempt`), - * 一个学生一道练习一行;「学完」由两张表合起来判(契约的 `isLessonDone`)。 + * 一个学生一道练习一行;「学完」由两张表合起来判(契约的 `isTutorialDone`)。 * * 外键这里**用了库级 CASCADE**,和 Django 建的那些 NO ACTION 外键不同:删教程、删用户 * 都不必再记得回来手工清一遍子表(后台删教程的事务里就没清它,靠的就是这里)。 diff --git a/apps/api/src/routes/admin/learn.ts b/apps/api/src/routes/admin/learn.ts index 2ca2c3d..3fd4b81 100644 --- a/apps/api/src/routes/admin/learn.ts +++ b/apps/api/src/routes/admin/learn.ts @@ -1,7 +1,7 @@ import { FLOWCHART_PASS_GRADES, STUDENT_ROLES, - isLessonDone, + isTutorialDone, type LearnBoard, type LearnBoardStudent, type LearnExerciseAttemptList, @@ -198,7 +198,7 @@ adminLearnRoutes.get('/learn-analytics/board', requireTeacher, async (c) => { students: students.map((student) => { const opened: number[] = [] const done: number[] = [] - const lessons: LearnBoardStudent['lessons'] = [] + const attempts: LearnBoardStudent['tutorials'] = [] let exerciseSolved = 0 let totalSeconds = 0 let lastViewedAt: string | null = null @@ -210,26 +210,26 @@ adminLearnRoutes.get('/learn-analytics/board', requireTeacher, async (c) => { exerciseSolved += solvedHere if (exercises) { const { tried, solved, wrongAttempts } = exercises - lessons.push({ tutorialId: tutorial.id, tried, solved, wrongAttempts }) + attempts.push({ tutorialId: tutorial.id, tried, solved, wrongAttempts }) } if (row) { opened.push(tutorial.id) totalSeconds += row.totalSeconds if (!lastViewedAt || row.lastViewedAt > lastViewedAt) lastViewedAt = row.lastViewedAt } - const lesson = { + const here = { viewCount: row?.viewCount ?? 0, exerciseTotal: tutorial.exerciseTotal, exerciseSolved: solvedHere, } - if (isLessonDone(lesson)) done.push(tutorial.id) + if (isTutorialDone(here)) done.push(tutorial.id) } return { ...student, opened, done, exerciseSolved, - lessons, + tutorials: attempts, problems: problemsOf.get(student.userId) ?? [], totalSeconds, lastViewedAt, @@ -394,27 +394,27 @@ adminLearnRoutes.get('/learn-analytics/students/:id', requireTeacher, async (c) ).map((row) => [row.problemId, row]), ) - const byLesson = new Map() + const byTutorial = new Map() for (const { tutorialId, ...row } of exercises) { - const list = byLesson.get(tutorialId) ?? [] + const list = byTutorial.get(tutorialId) ?? [] list.push({ ...row, attempts: row.attempts ?? 0, solved: row.solved ?? false, }) - byLesson.set(tutorialId, list) + byTutorial.set(tutorialId, list) } return success(c, { ...student, - lessons: tutorials + tutorials: tutorials .map((row) => ({ tutorialId: row.tutorialId, title: row.title, viewCount: row.viewCount ?? 0, totalSeconds: row.totalSeconds ?? 0, lastViewedAt: row.lastViewedAt, - exercises: byLesson.get(row.tutorialId) ?? [], + exercises: byTutorial.get(row.tutorialId) ?? [], problems: related .filter((problem) => problem.tutorialId === row.tutorialId) .map(({ id: problemId, displayId, title }) => { diff --git a/apps/web/src/admin/problem/components/StuckDetail.vue b/apps/web/src/admin/analytics/components/StuckDetail.vue similarity index 99% rename from apps/web/src/admin/problem/components/StuckDetail.vue rename to apps/web/src/admin/analytics/components/StuckDetail.vue index 0941c07..c71ff90 100644 --- a/apps/web/src/admin/problem/components/StuckDetail.vue +++ b/apps/web/src/admin/analytics/components/StuckDetail.vue @@ -8,7 +8,7 @@ import { avgTries, rate } from './stuckMath' /** * 卡点分析右栏:一道题的详情(做过几人、一次就对、错在哪、各班、交了几次才对、历年、 - * 还没做对的人)。从 Stuck.vue 拆出来,取数和三栏的联动还在那边。 + * 还没做对的人)。从 stuck.vue 拆出来,取数和三栏的联动还在那边。 */ const props = defineProps<{ detail: StuckProblemDetail diff --git a/apps/web/src/admin/problem/components/stuckMath.ts b/apps/web/src/admin/analytics/components/stuckMath.ts similarity index 86% rename from apps/web/src/admin/problem/components/stuckMath.ts rename to apps/web/src/admin/analytics/components/stuckMath.ts index 1091be9..e77d56a 100644 --- a/apps/web/src/admin/problem/components/stuckMath.ts +++ b/apps/web/src/admin/analytics/components/stuckMath.ts @@ -1,4 +1,4 @@ -/** 卡点分析(Stuck.vue)和它的单题详情(StuckDetail.vue)共用的两个算法 */ +/** 卡点分析(stuck.vue)和它的单题详情(StuckDetail.vue)共用的两个算法 */ /** 百分比,取整;分母是 0 时给 0 */ export function rate(part: number, whole: number) { diff --git a/apps/web/src/admin/problem/Stuck.vue b/apps/web/src/admin/analytics/stuck.vue similarity index 100% rename from apps/web/src/admin/problem/Stuck.vue rename to apps/web/src/admin/analytics/stuck.vue diff --git a/apps/web/src/admin/problem/Trend.vue b/apps/web/src/admin/analytics/trend.vue similarity index 100% rename from apps/web/src/admin/problem/Trend.vue rename to apps/web/src/admin/analytics/trend.vue diff --git a/apps/web/src/admin/setting/config.vue b/apps/web/src/admin/config/index.vue similarity index 100% rename from apps/web/src/admin/setting/config.vue rename to apps/web/src/admin/config/index.vue diff --git a/apps/web/src/admin/setting/home.vue b/apps/web/src/admin/home/index.vue similarity index 100% rename from apps/web/src/admin/setting/home.vue rename to apps/web/src/admin/home/index.vue diff --git a/apps/web/src/admin/learn/StudentDrawer.vue b/apps/web/src/admin/learn/StudentDrawer.vue index 65fd9e3..53b6ba8 100644 --- a/apps/web/src/admin/learn/StudentDrawer.vue +++ b/apps/web/src/admin/learn/StudentDrawer.vue @@ -7,7 +7,7 @@ import type { LearnStudentDetail } from 'utils/types' import UserName from 'shared/components/UserName.vue' import { useBackClose } from 'shared/composables/backClose' import { useBreakpoints } from 'shared/composables/breakpoints' -import { splitTitle, type TutorialType } from 'oj/learn/lesson' +import { splitTitle, type TutorialType } from 'oj/learn/utils' import { EXERCISE_KIND } from './kinds' /** @@ -30,31 +30,31 @@ const theme = useThemeVars() const { isDesktop } = useBreakpoints() useBackClose(show, () => !isDesktop.value) -type Lesson = LearnStudentDetail['lessons'][number] -type Exercise = Lesson['exercises'][number] -type Problem = Lesson['problems'][number] +type Tutorial = LearnStudentDetail['tutorials'][number] +type Exercise = Tutorial['exercises'][number] +type Problem = Tutorial['problems'][number] const detail = ref(null) const loading = ref(false) const expanded = ref(new Set()) const summary = computed(() => { - const lessons = detail.value?.lessons ?? [] - const exercises = lessons.flatMap((lesson) => lesson.exercises) + const tutorials = detail.value?.tutorials ?? [] + const exercises = tutorials.flatMap((tutorial) => tutorial.exercises) // 一道题挂在两课只算一次 - const problems = new Map(lessons.flatMap((lesson) => lesson.problems).map((p) => [p.id, p])) + const problems = new Map(tutorials.flatMap((tutorial) => tutorial.problems).map((p) => [p.id, p])) return { problemSolved: [...problems.values()].filter((p) => p.solved).length, problemTotal: problems.size, - opened: lessons.filter((lesson) => lesson.viewCount > 0).length, - seconds: lessons.reduce((n, lesson) => n + lesson.totalSeconds, 0), + opened: tutorials.filter((tutorial) => tutorial.viewCount > 0).length, + seconds: tutorials.reduce((n, tutorial) => n + tutorial.totalSeconds, 0), solved: exercises.filter((ex) => ex.solved).length, total: exercises.length, } }) -function solvedOf(lesson: Lesson) { - return lesson.exercises.filter((ex) => ex.solved).length +function solvedOf(tutorial: Tutorial) { + return tutorial.exercises.filter((ex) => ex.solved).length } function stateOf(ex: Exercise) { @@ -92,13 +92,13 @@ watch( const data = await getLearnStudent(userId, { type }) detail.value = data expanded.value = new Set( - data.lessons + data.tutorials .filter( - (lesson) => - (lesson.exercises.some((ex) => ex.attempts > 0) && lesson.exercises.some((ex) => !ex.solved)) || - lesson.problems.some((p) => p.submissions > 0 && !p.solved), + (tutorial) => + (tutorial.exercises.some((ex) => ex.attempts > 0) && tutorial.exercises.some((ex) => !ex.solved)) || + tutorial.problems.some((p) => p.submissions > 0 && !p.solved), ) - .map((lesson) => lesson.tutorialId), + .map((tutorial) => tutorial.tutorialId), ) } catch { detail.value = null @@ -133,40 +133,42 @@ watch( '--bad': theme.errorColor, }" > -
还没打开过
-
-