From af734530352c139195a73ef60f538531bf368a1e Mon Sep 17 00:00:00 2001 From: tanxin Date: Thu, 5 Nov 2026 11:01:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=85=A5=E5=AD=A6=E7=94=9F=E5=AD=A6?= =?UTF-8?q?=E4=B9=A0=E6=95=B0=E6=8D=AE=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/auth.ts | 3 +- api/student.ts | 202 ++++++++++++++++++++++++++++- pages/growth/index.uvue | 4 +- pages/index/index.uvue | 63 ++++++--- pages/login/index.uvue | 19 +++ pages/profile/index.uvue | 9 +- pages/register/input-student.uvue | 2 +- pages/register/parent-account.uvue | 3 +- pages/register/scan-student.uvue | 2 +- pages/study/index.uvue | 2 +- pages/tasks/index.uvue | 21 ++- pages/today-learning/index.uvue | 2 +- pages/vocabulary/index.uvue | 16 ++- store/home.ts | 32 ++++- utils/request.ts | 4 +- 15 files changed, 336 insertions(+), 48 deletions(-) diff --git a/api/auth.ts b/api/auth.ts index c4f0dc4..604fb95 100644 --- a/api/auth.ts +++ b/api/auth.ts @@ -4,6 +4,7 @@ import { post } from '../utils/request' import type { ApiResponse } from '../utils/request' +import { md5 } from '../utils/md5' // ==================== 类型定义 ==================== @@ -85,7 +86,7 @@ export type RefreshTokenResponse = { * 密码登录 */ export function apiLogin(params: LoginParams): Promise | null> { - return post('/api/v1/Parents/Login', { phone: params.phone, password: params.password, loginType: 0 }).then((res) => { + return post('/api/v1/Parents/Login', { phone: params.phone, password: md5(params.password), loginType: 0 }).then((res) => { if (!res || !res.data) return null return { code: res.code, message: res.message, data: { userId: String(res.data.parentId), phone: res.data.phone, nickname: res.data.name, avatar: '', token: res.data.token, refreshToken: '' } } }) diff --git a/api/student.ts b/api/student.ts index f2ad53d..c720006 100644 --- a/api/student.ts +++ b/api/student.ts @@ -14,7 +14,8 @@ export type StudentInfo = { account: string avatar: string grade: string - class: string + className: string + schoolName: string } // 首页当前学生基础信息 @@ -24,6 +25,7 @@ export type StudentHomeProfile = { account: string grade: string school: string + className: string avatar: string comprehensiveLevel: 'excellent' | 'good' | 'qualified' | 'needs_improvement' } @@ -35,6 +37,7 @@ export type BoundStudentSummary = { account: string grade: string school: string + className: string avatar: string } @@ -46,6 +49,7 @@ export type StudentHomeMetrics = { learningMinutesToday: number accuracyToday: number | null learningDaysThisWeek: number + shouldStudyDaysThisWeek: number pendingHomeworkCount: number updatedAt: string } @@ -156,6 +160,7 @@ export type TodayLearningDetail = { export type VocabularyBook = { id: string name: string + cover: string subtitle: string totalWords: number learnedWords: number @@ -228,6 +233,8 @@ export type StudentLearningTasks = { groups: LearningTaskGroup[] } +export type ParentStudyPackStatus = 0 | 1 | 2 | 3 + export type StudentAlertLevel = 'critical' | 'high' | 'medium' | 'low' export type StudentAlert = { @@ -249,6 +256,22 @@ export type StudentAlertList = { } } +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 @@ -277,7 +300,7 @@ export function apiVerifyStudentQRCode(params: VerifyQRCodeParams): 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 || '', class: res.data.className || '' } } + 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 || '' } } }) } @@ -301,14 +324,83 @@ export function apiGetStudentHomeProfile(studentId?: string, showLoading: boolea * 获取当前家长绑定的全部学生,用于首页切换学生。 */ export function apiGetBoundStudents(): Promise | null> { - return get<{ students: BoundStudentSummary[], currentStudentId: string | null }>('/api/parent/students') + 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/parent/home-metrics?studentId=${studentId}`, undefined, { showLoading }) + 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 + } + } + } + }) } /** @@ -326,7 +418,37 @@ export function apiGetStudentStudyReport(studentId: string, showLoading: boolean } export function apiGetStudentVocabulary(studentId: string, showLoading: boolean = true): Promise | null> { - return get(`/api/parent/vocabulary?studentId=${studentId}`, undefined, { showLoading }) + 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> { @@ -334,7 +456,75 @@ export function apiGetStudentGrowth(studentId: string, showLoading: boolean = tr } export function apiGetStudentLearningTasks(studentId: string, showLoading: boolean = true): Promise | null> { - return get(`/api/parent/learning-tasks?studentId=${studentId}`, undefined, { showLoading }) + 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 } /** diff --git a/pages/growth/index.uvue b/pages/growth/index.uvue index 8fa808f..11521b7 100644 --- a/pages/growth/index.uvue +++ b/pages/growth/index.uvue @@ -1,6 +1,6 @@ @@ -8,7 +8,7 @@