/** * 学生相关API接口 */ import { post, get } from '../utils/request' import type { ApiResponse } from '../utils/request' // ==================== 类型定义 ==================== // 学生信息类型 export type StudentInfo = { id: string name: string account: string avatar: string grade: string className: string schoolName: string } // 首页当前学生基础信息 export type StudentHomeProfile = { id: string name: string account: string grade: string school: string className: string avatar: string comprehensiveLevel: 'excellent' | 'good' | 'qualified' | 'needs_improvement' } // 家长已绑定学生的选择列表项 export type BoundStudentSummary = { id: string name: string account: string grade: string school: string className: string avatar: string } // 首页今日学习概览,所有数值均归属于 studentId 对应的学生。 export type StudentHomeMetrics = { studentId: string onlineToday: boolean checkedInToday: boolean learningMinutesToday: number accuracyToday: number | null learningDaysThisWeek: number shouldStudyDaysThisWeek: number pendingHomeworkCount: number updatedAt: string } export type TodayLearningCourse = { id: string time: string subject: string content: string checkedIn: boolean } export type TodayLearningScore = { accuracy: number yesterdayAccuracy: number change: number } export type TodayLearningPoint = { id: string label: string points: number } export type StudyReportSubject = { id: string name: string } export type StudyReportTrendPoint = { label: string value: number average: number valueHeight: number averageHeight: number } export type StudyReportTrend = { subjectId: string points: StudyReportTrendPoint[] } export type StudyReportActivityPoint = { label: string studyMinutes: number plannedMinutes: number newMasteredCount: number studyHeight: number plannedHeight: number newMasteredHeight: number } export type StudyReportActivity = { summary: string activeDays: number totalDurationText: string points: StudyReportActivityPoint[] } export type StudyReportSubjectMastery = { subjectName: string masteredCount: number totalCount: number masteredPercent: number } export type StudyReportPeriod = { rangeText: string statusTitle: string statusDescription: string newMasteredPoints: number consecutiveDays: number averageAccuracy: number scaleLabels: string[] trends: StudyReportTrend[] activity: StudyReportActivity subjectMasteries: StudyReportSubjectMastery[] } export type StudentStudyReport = { studentId: string studentName: string studentGrade: string studentClass: string updatedAtText: string subjects: StudyReportSubject[] week: StudyReportPeriod month: StudyReportPeriod } export type TodayLearningDetail = { studentId: string studentName: string dateText: string checkedIn: boolean learningMinutes: number answerMinutes: number answerCount: number completedPackages: number totalPackages: number masteredPoints: number courses: TodayLearningCourse[] score: TodayLearningScore pointsTotal: number pointDetails: TodayLearningPoint[] } export type VocabularyBook = { id: string name: string cover: string subtitle: string totalWords: number learnedWords: number status: 'learning' | 'unfinished' | 'completed' | 'not_started' statusText: string levelText: string color: 'green' | 'yellow' | 'blue' | 'gray' } export type StudentVocabulary = { studentId: string studentName: string studentGrade: string studentClass: string bookCount: number books: VocabularyBook[] } export type GrowthPointRecord = { id: string title: string timeText: string points: number icon: 'calendar' | 'assignment' | 'streak' | 'accuracy' | 'mastery' | 'note' } export type StudentGrowth = { studentId: string studentName: string studentGrade: string studentClass: string pointsBalance: number level: number rank: number streakDays: number totalPoints: number records: GrowthPointRecord[] } export type LearningTaskItem = { id: string title: string status: 'not_started' | 'in_progress' | 'success' | 'failed' statusText: string accuracyText: string answerText: string deadlineText: string } export type LearningTaskGroup = { id: string subject: string unit: string lesson: string startText: string endText: string knowledgeCount: number note: string tasks: LearningTaskItem[] } export type StudentLearningTasks = { studentId: string studentName: string studentGrade: string studentClass: string pendingCount: number completedCount: number failedCount: number groups: LearningTaskGroup[] } export type ParentStudyPackStatus = 0 | 1 | 2 | 3 export type StudentAlertLevel = 'critical' | 'high' | 'medium' | 'low' export type StudentAlert = { id: string level: StudentAlertLevel title: string content: string occurredAt: string isRead: boolean } export type StudentAlertList = { list: StudentAlert[] pagination: { page: number pageSize: number total: number hasMore: boolean } } export type StudentLearningWarningItem = { type: number title: string description: string isTriggered: boolean statusName: string } export type StudentLearningWarningResult = { userId: string date: string totalCount: number triggeredCount: number items: StudentLearningWarningItem[] } // 验证学生二维码请求参数 export type VerifyQRCodeParams = { qrCode: string account: string name: string } // 验证学生账号请求参数 export type VerifyAccountParams = { account: string name: string } // ==================== API方法 ==================== /** * 验证学生二维码 */ export function apiVerifyStudentQRCode(params: VerifyQRCodeParams): Promise | null> { return apiVerifyStudentAccount({ account: params.account, name: params.name }) } /** * 验证学生账号 */ export function apiVerifyStudentAccount(params: VerifyAccountParams): Promise | null> { return get('/api/v1/Parents/VerifyStudent', { RealName: params.name, Account: params.account }).then((res) => { if (!res || !res.data) return null return { code: res.code, message: res.message, data: { id: String(res.data.userId), name: res.data.name, account: res.data.account, avatar: res.data.avatar || '', grade: res.data.gradeLevel || '', className: res.data.className || '', schoolName: res.data.schoolName || '' } } }) } /** * 检查手机号是否已注册 */ export function apiCheckPhone(phone: string): Promise | null> { return get<{ exists: boolean }>(`/api/parent/check-phone?phone=${phone}`) } /** * 获取当前家长默认绑定学生的首页基础信息。 * 服务端根据 Authorization Token 识别家长并校验学生绑定关系。 */ export function apiGetStudentHomeProfile(studentId?: string, showLoading: boolean = true): Promise | null> { const url = studentId ? `/api/parent/home-profile?studentId=${studentId}` : '/api/parent/home-profile' return get(url, undefined, { showLoading }) } /** * 获取当前家长绑定的全部学生,用于首页切换学生。 */ export function apiGetBoundStudents(): Promise | null> { return get('/api/v1/Parents/GetBoundStudents').then((res) => { if (!res || !res.data) return null const items = Array.isArray(res.data) ? res.data : res.data.students if (!Array.isArray(items)) return null return { code: res.code, message: res.message, data: { students: items.map((item: any) => ({ id: String(item.userId), name: item.name || '', account: item.account || '', grade: item.gradeLevel || '', school: item.schoolName || '', className: item.className || '', avatar: item.avatar || '' })), currentStudentId: null } } }) } /** * 获取指定学生的首页今日学习概览。 */ export function apiGetStudentHomeMetrics(studentId: string, showLoading: boolean = true): Promise | null> { return get(`/api/v1/ParentStudentToday/GetTodayInfo?userId=${studentId}`, undefined, { showLoading }).then((res) => { if (!res || !res.data) return null return { code: res.code, message: res.message, data: { studentId: String(res.data.userId ?? studentId), onlineToday: Boolean(res.data.todayOnline), checkedInToday: Boolean(res.data.todayCheckIn), learningMinutesToday: Math.floor((Number(res.data.todayStudyDuration) || 0) / 60), accuracyToday: Number(res.data.todayAccuracyRate) || 0, learningDaysThisWeek: Number(res.data.weekStudyDays) || 0, shouldStudyDaysThisWeek: Number(res.data.weekShouldStudyDays) || 0, pendingHomeworkCount: Number(res.data.pendingPackCount) || 0, updatedAt: res.data.date || '' } } }) } /** * 获取指定学生今日学习预警,仅返回已触发的预警规则。 */ export function apiGetStudentLearningWarnings(studentId: string, showLoading: boolean = true): Promise | null> { return get(`/api/v1/ParentLearningWarning/GetTodayList?userId=${studentId}`, undefined, { showLoading }).then((res) => { if (!res || !res.data || !Array.isArray(res.data.items)) return null const list: StudentAlert[] = res.data.items .filter((item: any) => Boolean(item.isTriggered)) .map((item: any, index: number) => ({ id: `${studentId}-${item.type ?? index}`, level: 'high', title: item.title || '学习预警', content: item.description || item.statusName || '', occurredAt: res.data.date || '', isRead: false })) return { code: res.code, message: res.message, data: { list, pagination: { page: 1, pageSize: list.length, total: list.length, hasMore: false } } } }) } /** * 获取学生今日学习详情及课程打卡记录。 */ export function apiGetTodayLearningDetail(studentId: string, showLoading: boolean = true): Promise | null> { return get(`/api/parent/today-learning?studentId=${studentId}`, undefined, { showLoading }) } /** * 获取学生学情报告。接口一次返回周报、月报和全部科目趋势数据。 */ export function apiGetStudentStudyReport(studentId: string, showLoading: boolean = true): Promise | null> { return get(`/api/parent/study-report?studentId=${studentId}`, undefined, { showLoading }) } export function apiGetStudentVocabulary(studentId: string, showLoading: boolean = true): Promise | null> { return get(`/api/v1/Parents/GetAllLexiconBooks/${studentId}`, undefined, { showLoading }).then((res) => { if (!res || !Array.isArray(res.data)) return null const books: VocabularyBook[] = res.data.map((item: any) => { const learnedWords = Number(item.lexiconCount) || 0 const totalWords = Number(item.totalCount) || learnedWords const status: VocabularyBook['status'] = totalWords > 0 && learnedWords >= totalWords ? 'completed' : item.isStudying ? 'learning' : learnedWords > 0 ? 'unfinished' : 'not_started' return { id: String(item.id ?? item.title ?? ''), name: item.title || '', cover: item.cover || '', subtitle: `总词量 ${totalWords} 词`, totalWords, learnedWords, status, statusText: status === 'completed' ? '已完成' : status === 'learning' ? '学习中' : status === 'unfinished' ? '未学完' : '未开始', levelText: item.slogan || '', color: status === 'learning' ? 'green' : status === 'unfinished' ? 'yellow' : status === 'completed' ? 'blue' : 'gray' } }) return { code: res.code, message: res.message, data: { studentId, studentName: '', studentGrade: '', studentClass: '', bookCount: books.length, books } } }) } export function apiGetStudentGrowth(studentId: string, showLoading: boolean = true): Promise | null> { return get(`/api/parent/growth?studentId=${studentId}`, undefined, { showLoading }) } export function apiGetStudentLearningTasks(studentId: string, showLoading: boolean = true): Promise | null> { return apiGetParentStudyPacks(studentId, 0, showLoading) } /** * 获取学生当天学习包任务。Status: 0 全部,1 待完成,2 已完成,3 未通过。 */ export function apiGetParentStudyPacks(studentId: string, status: ParentStudyPackStatus = 0, showLoading: boolean = true): Promise | null> { return get(`/api/v1/ParentStudyPack/GetTodayList?UserId=${studentId}&Status=${status}`, undefined, { showLoading }).then((res) => { if (!res || !res.data) return null const statusMap: { [key: number]: LearningTaskItem['status'] } = { 0: 'not_started', 1: 'in_progress', 2: 'success', 3: 'failed' } const statusTextMap: { [key: string]: string } = { not_started: '待完成', in_progress: '进行中', success: '已完成', failed: '未通过' } const courses = Array.isArray(res.data.courses) ? res.data.courses : [] const groups: LearningTaskGroup[] = courses.map((course: any) => { const tasks: LearningTaskItem[] = (Array.isArray(course.packs) ? course.packs : []).map((pack: any) => { const taskStatus = statusMap[Number(pack.status)] || 'not_started' const accuracy = pack.latestAccuracyRate === null || pack.latestAccuracyRate === undefined ? '--' : `${Number(pack.latestAccuracyRate) || 0}%` return { id: String(pack.packId ?? pack.knowledgePointId ?? ''), title: pack.name || '未命名学习任务', status: taskStatus, statusText: pack.statusName || statusTextMap[taskStatus], accuracyText: accuracy, answerText: pack.completedAttemptCount ? `已完成 ${pack.completedAttemptCount} 次` : '尚未完成', deadlineText: pack.deadlineTime || '' } }) return { id: String(course.courseId ?? course.sectionCount ?? ''), subject: course.baseSubjectName || '', unit: course.studyStageName || '', lesson: course.courseName || course.sectionName || '', startText: formatTaskTime(course.planStartTime), endText: formatTaskTime(course.planEndTime), knowledgeCount: Number(course.knowledgePointCount) || tasks.length, note: course.deadlineTime ? `截止时间:${formatTaskTime(course.deadlineTime)}` : '', tasks } }) return { code: res.code, message: res.message, data: { studentId: String(res.data.userId ?? studentId), studentName: res.data.userName || '', studentGrade: '', studentClass: '', pendingCount: Number(res.data.pendingCount) || 0, completedCount: Number(res.data.completedCount) || 0, failedCount: Number(res.data.failedCount) || 0, groups } } }) } function formatTaskTime(value: any): string { if (!value) return '--' const text = String(value) return text.length >= 16 ? text.substring(11, 16) : text } /** * 获取当前学生的预警信息。首页调用时 limit 固定为 3。 */ export function apiGetStudentAlerts(studentId: string, page: number = 1, pageSize: number = 20, limit?: number, showLoading: boolean = true): Promise | null> { const suffix = limit ? `limit=${limit}` : `page=${page}&pageSize=${pageSize}` return get(`/api/parent/alerts?studentId=${studentId}&${suffix}`, undefined, { showLoading }) }