feat: add taro student learning flow

This commit is contained in:
Codex
2026-06-29 12:04:59 +08:00
parent c090008f52
commit b79c4259c0
28 changed files with 1567 additions and 12 deletions

View File

@@ -7,6 +7,8 @@ export interface ContentEntry {
route?: string;
icon?: string;
sortOrder?: number;
description?: string;
regionId?: string | null;
}
export interface DashboardSnapshot {
@@ -30,3 +32,169 @@ export async function loadStudentDashboard(regionId?: string): Promise<Dashboard
profile: profile.item || null,
};
}
export interface RegionItem {
id: string;
name: string;
code?: string;
shortName?: string;
isHot?: boolean;
}
export interface ContentNode {
id: string;
entryId: string;
parentId?: string | null;
name: string;
nodeType?: string;
markerType?: string | null;
depth?: number;
isLeaf?: boolean;
isSelectable?: boolean;
metadata?: Record<string, unknown>;
}
export interface QuestionCollection {
id: string;
entryId?: string | null;
nodeId?: string | null;
name: string;
collectionType?: string;
questionCount?: number;
durationMinutes?: number | null;
}
export interface PracticeBlueprint {
id: string;
entryId?: string | null;
nodeId?: string | null;
collectionId?: string | null;
name: string;
mode: string;
questionLimit?: number | null;
durationMinutes?: number | null;
}
export interface VocabularyUnit {
id: string;
name: string;
description?: string | null;
wordCount?: number;
regionId?: string | null;
}
export interface HandbookSubject {
id: string;
name: string;
type?: string | null;
description?: string | null;
}
export interface HandbookChapter {
id: string;
subjectId: string;
name: string;
description?: string | null;
}
export interface HandbookEntry {
id: string;
chapterId: string;
title: string;
summary?: string | null;
content?: string | null;
}
export interface ScorelineRecord {
id: string;
year: number;
schoolName?: string | null;
majorName?: string | null;
fieldValues?: Record<string, unknown>;
}
export interface ContentAsset {
id: string;
title?: string | null;
assetType?: string;
categoryLabel?: string | null;
description?: string | null;
fileName?: string | null;
mimeType?: string | null;
visibility?: string;
previewStatus?: string;
}
export async function loadRegions() {
return apiRequest<{ items?: RegionItem[] }>('/api/catalog/regions');
}
export async function loadContentEntries(regionId?: string, entryType?: string) {
return apiRequest<{ items?: ContentEntry[] }>('/api/catalog/content-entries', { query: { regionId, entryType } });
}
export async function loadContentNodes(entryId: string, parentId: string | null = 'root', mode: 'children' | 'flat' = 'children') {
return apiRequest<{ items?: ContentNode[] }>('/api/catalog/content-nodes', { query: { entryId, parentId, mode } });
}
export async function loadQuestionCollections(input: { entryId?: string; nodeId?: string; limit?: number }) {
return apiRequest<{ items?: QuestionCollection[] }>('/api/catalog/question-collections', {
query: { entryId: input.entryId, nodeId: input.nodeId, limit: input.limit || 100 },
});
}
export async function loadCollectionQuestions(collectionId: string, limit = 200) {
return apiRequest<{ items?: import('./learning').QuestionItem[] }>('/api/catalog/question-collections/questions', {
query: { collectionId, limit },
});
}
export async function loadQuestions(query: { entryId?: string; contentNodeId?: string; collectionId?: string; limit?: number }) {
return apiRequest<{ items?: import('./learning').QuestionItem[] }>('/api/catalog/questions', {
query: { ...query, limit: query.limit || 200 },
});
}
export async function loadPracticeBlueprints(input: { entryId?: string; nodeId?: string; collectionId?: string; mode?: string }) {
return apiRequest<{ items?: PracticeBlueprint[] }>('/api/catalog/practice-blueprints', {
query: input,
});
}
export async function loadVocabularyUnits(regionId?: string) {
return apiRequest<{ items?: VocabularyUnit[] }>('/api/catalog/vocabulary-units', { query: { regionId } });
}
export async function loadVocabularyWords(unitId: string) {
return apiRequest<{ items?: import('./learning').VocabularyWord[] }>('/api/catalog/vocabulary-words', { query: { unitId } });
}
export async function loadHandbookSubjects(regionId?: string) {
return apiRequest<{ items?: HandbookSubject[] }>('/api/catalog/handbook-subjects', { query: { regionId } });
}
export async function loadHandbookChapters(subjectId: string) {
return apiRequest<{ items?: HandbookChapter[] }>('/api/catalog/handbook-chapters', { query: { subjectId } });
}
export async function loadHandbookEntries(chapterId: string, includeContent = true) {
return apiRequest<{ items?: HandbookEntry[] }>('/api/catalog/handbook-entries', { query: { chapterId, includeContent } });
}
export async function loadScorelineRecords(query: { regionId?: string; schoolId?: string; majorId?: string; year?: number; pageSize?: number } = {}) {
return apiRequest<{ items?: ScorelineRecord[]; total?: number }>('/api/scoreline/records', {
query: { ...query, pageSize: query.pageSize || 20 },
});
}
export async function loadContentAssets(query: { entryId?: string; contentNodeId?: string; assetType?: string; includeLocked?: boolean } = {}) {
return apiRequest<{ items?: ContentAsset[] }>('/api/catalog/assets', { query: { ...query, includeLocked: query.includeLocked ?? true } });
}
export async function signAssetPreview(assetId: string) {
return apiRequest<{ preview?: { url?: string }; item?: ContentAsset }>('/api/catalog/assets/preview', { query: { assetId } });
}
export async function signAssetDownload(assetId: string) {
return apiRequest<{ download?: { url?: string }; item?: ContentAsset }>('/api/catalog/assets/download', { query: { assetId } });
}

View File

@@ -0,0 +1,63 @@
import { apiRequest } from './api';
export interface SvipPlan {
id: string;
name: string;
price?: number;
priceCents?: number;
originalPrice?: number | null;
days?: number;
desc?: string | null;
badge?: string | null;
recommended?: boolean;
}
export interface OrderItem {
id: string;
orderNo: string;
status: string;
productName?: string | null;
amountCents?: number;
paidAt?: string | null;
createdAt?: string;
}
export async function loadSvipPlans(regionId?: string) {
return apiRequest<{ items?: SvipPlan[] }>('/api/catalog/svip-plans', { query: { regionId } });
}
export async function createOrder(body: {
planId: string;
regionId?: string;
payMethod?: string;
payProvider?: string;
quantity?: number;
couponCode?: string;
}) {
return apiRequest<{ item?: Record<string, unknown> }>('/api/commerce/orders', {
method: 'POST',
body,
});
}
export async function loadOrders(limit = 20) {
return apiRequest<{ items?: OrderItem[] }>('/api/commerce/orders', { query: { limit } });
}
export async function loadEntitlements() {
return apiRequest<{ items?: Record<string, unknown>[]; summary?: Record<string, unknown> }>('/api/commerce/entitlements');
}
export async function checkActivationCode(code: string, regionId?: string) {
return apiRequest<{ valid?: boolean; item?: Record<string, unknown>; reasonCode?: string; message?: string }>(
'/api/commerce/activation-codes/check',
{ method: 'POST', body: { code, regionId } },
);
}
export async function redeemActivationCode(code: string, regionId?: string) {
return apiRequest<{ item?: Record<string, unknown>; entitlement?: unknown }>('/api/commerce/activation-codes/redeem', {
method: 'POST',
body: { code, regionId },
});
}

View File

@@ -0,0 +1,129 @@
import { apiRequest } from './api';
export interface QuestionOption {
text?: string;
label?: string;
value?: string | number;
content?: string;
[key: string]: unknown;
}
export interface QuestionItem {
id: string;
content?: string;
type?: string;
typeLabel?: string;
options?: QuestionOption[] | string[] | null;
answerText?: string | null;
correctOptionIndex?: number | null;
correctOptionIndices?: number[] | null;
explanation?: string | null;
hasVideoExplanation?: boolean;
}
export interface PracticeSession {
id: string;
mode: string;
questionIds: string[];
questionCount: number;
durationMinutes?: number | null;
accessMode?: string;
consumedFreeQuota?: number;
expiresAt?: string | null;
}
export interface AnswerResult {
id: string;
questionId: string;
isCorrect: boolean | 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;
}
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 submitAnswer(body: {
practiceSessionId: string;
questionId: string;
selectedOptions?: string[];
answerText?: string;
}) {
return apiRequest<{ item: AnswerResult }>('/api/learning/answers', {
method: 'POST',
body,
});
}
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 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 loadLeaderboard(metric: 'questions' | 'score' | 'vocabulary' | 'mock_exam' = 'questions') {
return apiRequest<{ items?: Record<string, unknown>[]; currentUserRank?: unknown }>('/api/learning/leaderboard', {
query: { metric, period: '7d', scope: 'tenant' },
});
}
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 loadVocabularyStats(unitId?: string) {
return apiRequest<{ item?: Record<string, unknown> }>('/api/learning/vocabulary/stats', { query: { unitId } });
}

View File

@@ -0,0 +1,35 @@
import { apiRequest } from './api';
export interface StudentProfile {
userId?: string;
name?: string | null;
phone?: string | null;
score?: number;
target?: {
regionName?: string | null;
schoolName?: string | null;
majorName?: string | null;
};
membership?: {
isSvip?: boolean;
entitlement?: unknown;
};
stats?: Record<string, unknown>;
recentPractices?: unknown[];
}
export async function loadProfile() {
return apiRequest<{ item?: StudentProfile }>('/api/profile/me');
}
export async function checkIn() {
return apiRequest<{ item?: Record<string, unknown> }>('/api/profile/check-in', { method: 'POST' });
}
export async function loadBadges() {
return apiRequest<{ items?: Record<string, unknown>[] }>('/api/profile/badges');
}
export async function loadExamCountdowns() {
return apiRequest<{ items?: Record<string, unknown>[] }>('/api/profile/exam-countdowns');
}