Files
gongxue-base/apps/taro/src/services/learning.ts
2026-06-30 15:56:01 +08:00

385 lines
10 KiB
TypeScript

import { apiRequest } from './api';
export interface QuestionOption {
text?: string;
label?: string;
value?: string | number;
content?: string;
[key: string]: unknown;
}
export interface QuestionItem {
id: string;
questionId?: string;
content?: string;
type?: string;
typeLabel?: string;
mediaUrl?: string | null;
options?: QuestionOption[] | string[] | null;
answerText?: string | null;
correctOptionIndex?: number | null;
correctOptionIndices?: number[] | null;
explanation?: string | null;
subQuestions?: Record<string, unknown>[] | null;
hasVideoExplanation?: boolean;
wrongCount?: number;
lastWrongAt?: string | null;
resolvedAt?: string | null;
createdAt?: string;
}
export interface SubAnswerInput {
subQuestionId: string;
selectedOptions?: string[];
answerText?: string;
selfJudgedCorrect?: boolean;
}
export interface SubAnswerResult {
subQuestionId: string;
order?: number;
type?: string;
typeLabel?: string | null;
content?: string | null;
selectedOptions?: string[];
answerText?: string | null;
isCorrect?: boolean | null;
selfJudged?: boolean;
correctOptionIndex?: number | null;
correctOptionIndices?: number[];
correctAnswerText?: string | null;
explanation?: string | null;
answered?: boolean;
score?: number;
totalScore?: number;
}
export interface AnswerPayload {
mode?: string;
subAnswers?: SubAnswerInput[];
subResults?: SubAnswerResult[];
summary?: Record<string, unknown>;
}
export interface PracticeSession {
id: string;
mode: string;
questionIds: string[];
questionCount: number;
durationMinutes?: number | null;
accessMode?: string;
consumedFreeQuota?: number;
expiresAt?: string | null;
}
export interface PracticeSessionDetail extends PracticeSession {
status?: string;
startedAt?: string;
finishedAt?: string | null;
entryId?: string | null;
contentNodeId?: string | null;
collectionId?: string | null;
blueprintId?: string | null;
questions?: QuestionItem[];
answersByQuestion?: Record<string, AnswerResult>;
}
export interface AnswerResult {
id: string;
questionId: string;
isCorrect: boolean | null;
selectedOptions?: string[];
answerText?: string | null;
answerPayload?: AnswerPayload;
answeredAt?: string;
selfJudged?: boolean;
subResults?: SubAnswerResult[];
compositeSummary?: Record<string, unknown>;
}
export interface PracticeReport {
id: string;
practiceSessionId: string;
mode: string;
totalQuestions: number;
answeredCount: number;
correctCount: number;
wrongCount: number;
unansweredCount: number;
score: number;
totalScore: number;
accuracy: number;
durationSeconds?: number;
sectionStats?: Record<string, unknown>[];
questionResults?: Record<string, unknown>[];
wrongQuestionIds?: string[];
submittedAt?: string;
}
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 {
questionId: string;
wrongCount?: number;
lastWrongAt?: string | null;
resolvedAt?: string | null;
type?: string;
typeLabel?: string | null;
subjectName?: string | null;
categoryName?: string | null;
content?: string | null;
answerAttempts?: number;
reviewStatus?: string;
suggestedReviewAt?: string | null;
}
export interface VocabularyWord {
id?: string;
wordId?: string;
unitId?: string;
word: string;
phonetic?: string | null;
meaning?: string | null;
example?: string | null;
exampleTranslation?: string | null;
difficulty?: string | null;
status?: string;
dueLevel?: string;
favoritedAt?: string | null;
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;
avatarPreset?: 'male' | 'female';
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;
entryId?: string;
contentNodeId?: string;
mode?: string;
questionLimit?: number;
}) {
return apiRequest<{ item: PracticeSession }>('/api/learning/practice-sessions', {
method: 'POST',
body,
});
}
export async function loadPracticeSessionDetail(practiceSessionId: string) {
return apiRequest<{ item: PracticeSessionDetail }>('/api/learning/practice-sessions/detail', {
query: { practiceSessionId },
});
}
export async function submitAnswer(body: {
practiceSessionId: string;
questionId: string;
selectedOptions?: string[];
answerText?: string;
selfJudgedCorrect?: boolean;
subAnswers?: SubAnswerInput[];
}) {
return apiRequest<{ item: AnswerResult }>('/api/learning/answers', {
method: 'POST',
body,
});
}
export async function submitPracticeSession(practiceSessionId: string) {
return apiRequest<{ item?: PracticeReport }>('/api/learning/practice-sessions/submit', {
method: 'POST',
body: { practiceSessionId },
});
}
export async function loadPracticeSessionReport(practiceSessionId: string) {
return apiRequest<{ item?: PracticeReport }>('/api/learning/practice-sessions/report', {
query: { practiceSessionId },
});
}
export async function loadPracticeReports(query: { mode?: string; blueprintId?: string; limit?: number } = {}) {
return apiRequest<{ items?: PracticeReport[] }>('/api/learning/practice-reports', {
query: { ...query, limit: query.limit || 20 },
});
}
export async function loadPracticeHistory(query: { mode?: string; status?: string; limit?: number } = {}) {
return apiRequest<{ items?: PracticeHistoryItem[] }>('/api/learning/practice-sessions/history', {
query: { ...query, limit: query.limit || 20 },
});
}
export async function toggleQuestionFavorite(questionId: string, favorite: boolean) {
return apiRequest<{ ok: boolean; favorite: boolean }>('/api/learning/favorites/questions', {
method: 'POST',
body: { questionId, favorite },
});
}
export async function loadWrongQuestions(limit = 30) {
return apiRequest<{ items?: QuestionItem[] }>('/api/learning/wrong-questions', { query: { limit } });
}
export async function loadWrongQuestionReviewPlan(limit = 20) {
return apiRequest<{ items?: WrongQuestionReviewItem[]; nextAction?: Record<string, unknown> }>('/api/learning/wrong-questions/review-plan', {
query: { limit },
});
}
export async function resolveWrongQuestion(questionId: string) {
return apiRequest<{ ok: boolean }>('/api/learning/wrong-questions/resolve', {
method: 'POST',
body: { questionId },
});
}
export async function loadFavoriteQuestions(limit = 30) {
return apiRequest<{ items?: QuestionItem[] }>('/api/learning/favorites/questions', { query: { limit } });
}
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<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',
{ query: { unitId, reviewLimit: 20, newLimit: 10 } },
);
}
export async function reviewVocabularyWord(wordId: string, result: 'known' | 'unknown') {
return apiRequest<{ item?: Record<string, unknown> }>('/api/learning/vocabulary/review', {
method: 'POST',
body: { wordId, result },
});
}
export async function toggleWordFavorite(wordId: string, favorite: boolean) {
return apiRequest<{ ok: boolean; favorite: boolean }>('/api/learning/vocabulary/favorites', {
method: 'POST',
body: { wordId, favorite },
});
}
export async function loadFavoriteWords(unitId?: string, limit = 500) {
return apiRequest<{ items?: VocabularyWord[] }>('/api/learning/vocabulary/favorites', {
query: { unitId, limit },
});
}
export async function loadVocabularyStats(unitId?: string) {
return apiRequest<{ item?: Record<string, unknown> }>('/api/learning/vocabulary/stats', { query: { unitId } });
}