forked from wangziqi/gongxue-base
385 lines
10 KiB
TypeScript
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 } });
|
|
}
|