feat: add student learning report dashboard

This commit is contained in:
Codex
2026-06-30 00:30:38 +08:00
parent 3905e3cb77
commit 4a13715f29
8 changed files with 440 additions and 19 deletions

View File

@@ -119,19 +119,24 @@ export interface PracticeReport {
export interface PracticeHistoryItem {
id: string;
mode: string;
blueprintName?: string | null;
collectionName?: string | null;
entryName?: string | null;
contentNodeName?: string | null;
questionCount?: number;
durationMinutes?: number | null;
accessMode?: string | null;
answeredCount?: number;
correctCount?: number;
wrongCount?: number;
status?: string;
reportId?: string | null;
score?: number | null;
reportTotalScore?: number | null;
accuracy?: number | null;
startedAt?: string;
finishedAt?: string | null;
submittedAt?: string | null;
}
export interface WrongQuestionReviewItem {
@@ -165,6 +170,86 @@ export interface VocabularyWord {
note?: string | null;
}
export interface LearningStats {
windowDays: number;
answers: {
totalAnswered: number;
correctCount: number;
wrongCount: number;
todayAnswered: number;
accuracy: number;
latestAnsweredAt?: string | null;
};
sessions: {
totalSessions: number;
activeSessions: number;
finishedSessions: number;
latestStartedAt?: string | null;
};
reports: {
reportCount: number;
avgAccuracy: number;
bestScore: number;
latestSubmittedAt?: string | null;
};
wrongBook: {
unresolvedWrong: number;
resolvedWrong: number;
totalWrongBook: number;
};
favorites: {
favoriteQuestions: number;
};
questionTypes: LearningQuestionTypeStats[];
}
export interface LearningQuestionTypeStats {
questionType: string;
typeLabel?: string | null;
answeredCount: number;
correctCount: number;
wrongCount: number;
accuracy: number;
}
export interface LearningTrendItem {
date: string;
answeredCount: number;
correctCount: number;
wrongCount: number;
accuracy: number;
sessionCount: number;
reportCount: number;
score: number;
totalScore: number;
}
export interface LeaderboardItem {
rank: number;
userId: string;
displayName: string;
avatarUrl?: string | null;
primaryRole?: string;
regionId?: string | null;
regionName?: string | null;
classIds?: string[];
classNames?: string[];
value: number;
secondaryValue?: number | null;
latestAt?: string | null;
isCurrentUser?: boolean;
}
export interface LeaderboardPayload {
metric: 'questions' | 'score' | 'vocabulary' | 'mock_exam';
label: string;
unit: string;
period: 'all' | '7d' | '30d';
items?: LeaderboardItem[];
currentUser?: LeaderboardItem | null;
generatedAt?: string;
}
export async function createPracticeSession(body: {
blueprintId?: string;
collectionId?: string;
@@ -252,16 +337,20 @@ export async function loadFavoriteQuestions(limit = 30) {
return apiRequest<{ items?: QuestionItem[] }>('/api/learning/favorites/questions', { query: { limit } });
}
export async function loadLearningStats() {
return apiRequest<{ item?: Record<string, unknown> }>('/api/learning/stats');
export async function loadLearningStats(days = 30) {
return apiRequest<{ item?: LearningStats }>('/api/learning/stats', { query: { days } });
}
export async function loadLeaderboard(metric: 'questions' | 'score' | 'vocabulary' | 'mock_exam' = 'questions') {
return apiRequest<{ items?: Record<string, unknown>[]; currentUserRank?: unknown }>('/api/learning/leaderboard', {
return apiRequest<LeaderboardPayload>('/api/learning/leaderboard', {
query: { metric, period: '7d', scope: 'tenant' },
});
}
export async function loadLearningTrend(days = 14) {
return apiRequest<{ items?: LearningTrendItem[] }>('/api/learning/trend', { query: { days } });
}
export async function loadVocabularyReviewPlan(unitId?: string) {
return apiRequest<{ item?: { words?: VocabularyWord[]; dueCount?: number; newCount?: number; totalPlanned?: number } }>(
'/api/learning/vocabulary/review-plan',