feat: 家长端任务、教师端统计、数据库迁移等

- 家长端阅读任务:从 task_target 获取任务(学生+班级),支持家长关联孩子查看
- 家长端提交任务:修复照片上传,使用 uploadFile(file, poster) 符合 API 规范
- 教师端任务列表:TaskResponse 新增 targetCount、completionCount,填充目标人数与完成人数
- 数据库迁移:V45 添加 task_completion.photos,V46 添加 submitted_at、reviewed_at
- 其他:班级学生、家长孩子、学校班级统计等修复

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-20 18:43:47 +08:00
parent c93d325cee
commit 5cb903d7ed
127 changed files with 6153 additions and 1395 deletions

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
*/ */
import type { import type {
BasicSettingsUpdateRequest, BasicSettingsUpdateRequest,
BatchStudentRecordsRequest,
BindStudentParams, BindStudentParams,
ChangePasswordParams, ChangePasswordParams,
CheckConflictParams, CheckConflictParams,
@ -29,8 +30,10 @@ import type {
GenerateReadOnlyTokenParams, GenerateReadOnlyTokenParams,
GetActiveTeachersParams, GetActiveTeachersParams,
GetActiveTenantsParams, GetActiveTenantsParams,
GetAllCoursesParams,
GetAllStudentsParams, GetAllStudentsParams,
GetCalendarViewDataParams, GetCalendarViewDataParams,
GetChildLessonsParams,
GetClassPageParams, GetClassPageParams,
GetClassStudents1Params, GetClassStudents1Params,
GetClassStudentsParams, GetClassStudentsParams,
@ -57,8 +60,11 @@ import type {
GetSchedules1Params, GetSchedules1Params,
GetSchedulesParams, GetSchedulesParams,
GetSchoolCoursesParams, GetSchoolCoursesParams,
GetStatisticsParams,
GetStudentPageParams, GetStudentPageParams,
GetTaskPage1Params, GetTaskCompletions1Params,
GetTaskCompletionsParams,
GetTaskListParams,
GetTaskPageParams, GetTaskPageParams,
GetTasksByStudentParams, GetTasksByStudentParams,
GetTeacherPageParams, GetTeacherPageParams,
@ -82,7 +88,6 @@ import type {
RefreshTokenRequest, RefreshTokenRequest,
RenewRequest, RenewRequest,
ResetPassword1Params, ResetPassword1Params,
ResetPasswordParams,
ResourceItemCreateRequest, ResourceItemCreateRequest,
ResourceItemUpdateRequest, ResourceItemUpdateRequest,
ResourceLibraryCreateRequest, ResourceLibraryCreateRequest,
@ -95,7 +100,10 @@ import type {
StudentCreateRequest, StudentCreateRequest,
StudentRecordRequest, StudentRecordRequest,
StudentUpdateRequest, StudentUpdateRequest,
TaskCompleteRequest,
TaskCreateRequest, TaskCreateRequest,
TaskFeedbackRequest,
TaskSubmitRequest,
TaskTemplateCreateRequest, TaskTemplateCreateRequest,
TaskUpdateRequest, TaskUpdateRequest,
TeacherCreateRequest, TeacherCreateRequest,
@ -162,6 +170,38 @@ const deleteTask = (
); );
} }
/**
* @summary
*/
const updateFeedback = (
completionId: number,
taskFeedbackRequest: TaskFeedbackRequest,
) => {
return customMutator<Blob>(
{url: `/v1/teacher/tasks/completions/${completionId}/feedback`, method: 'PUT',
headers: {'Content-Type': 'application/json', },
data: taskFeedbackRequest,
responseType: 'blob'
},
);
}
/**
* @summary
*/
const submitFeedback = (
completionId: number,
taskFeedbackRequest: TaskFeedbackRequest,
) => {
return customMutator<Blob>(
{url: `/v1/teacher/tasks/completions/${completionId}/feedback`, method: 'POST',
headers: {'Content-Type': 'application/json', },
data: taskFeedbackRequest,
responseType: 'blob'
},
);
}
/** /**
* @summary * @summary
*/ */
@ -388,48 +428,6 @@ const deleteTeacher = (
); );
} }
/**
* @summary Get task by ID
*/
const getTask1 = (
id: number,
) => {
return customMutator<Blob>(
{url: `/v1/school/tasks/${id}`, method: 'GET',
responseType: 'blob'
},
);
}
/**
* @summary Update task
*/
const updateTask1 = (
id: number,
taskUpdateRequest: TaskUpdateRequest,
) => {
return customMutator<Blob>(
{url: `/v1/school/tasks/${id}`, method: 'PUT',
headers: {'Content-Type': 'application/json', },
data: taskUpdateRequest,
responseType: 'blob'
},
);
}
/**
* @summary Delete task
*/
const deleteTask1 = (
id: number,
) => {
return customMutator<Blob>(
{url: `/v1/school/tasks/${id}`, method: 'DELETE',
responseType: 'blob'
},
);
}
/** /**
* @summary * @summary
*/ */
@ -826,42 +824,32 @@ const removeClassTeacher = (
} }
/** /**
* @summary Get growth record by ID * @summary
*/ */
const getGrowthRecord2 = ( const updateSubmission = (
id: number, taskId: number,
taskSubmitRequest: TaskSubmitRequest,
) => { ) => {
return customMutator<Blob>( return customMutator<Blob>(
{url: `/v1/parent/growth-records/${id}`, method: 'GET', {url: `/v1/parent/tasks/${taskId}/submit`, method: 'PUT',
responseType: 'blob'
},
);
}
/**
* @summary Update growth record
*/
const updateGrowthRecord2 = (
id: number,
growthRecordUpdateRequest: GrowthRecordUpdateRequest,
) => {
return customMutator<Blob>(
{url: `/v1/parent/growth-records/${id}`, method: 'PUT',
headers: {'Content-Type': 'application/json', }, headers: {'Content-Type': 'application/json', },
data: growthRecordUpdateRequest, data: taskSubmitRequest,
responseType: 'blob' responseType: 'blob'
}, },
); );
} }
/** /**
* @summary Delete growth record * @summary
*/ */
const deleteGrowthRecord2 = ( const submitTask = (
id: number, taskId: number,
taskSubmitRequest: TaskSubmitRequest,
) => { ) => {
return customMutator<Blob>( return customMutator<Blob>(
{url: `/v1/parent/growth-records/${id}`, method: 'DELETE', {url: `/v1/parent/tasks/${taskId}/submit`, method: 'POST',
headers: {'Content-Type': 'application/json', },
data: taskSubmitRequest,
responseType: 'blob' responseType: 'blob'
}, },
); );
@ -1610,12 +1598,12 @@ const saveStudentRecord = (
*/ */
const batchSaveStudentRecords = ( const batchSaveStudentRecords = (
id: number, id: number,
studentRecordRequest: StudentRecordRequest[], batchStudentRecordsRequest: BatchStudentRecordsRequest,
) => { ) => {
return customMutator<Blob>( return customMutator<Blob>(
{url: `/v1/teacher/lessons/${id}/students/batch-records`, method: 'POST', {url: `/v1/teacher/lessons/${id}/students/batch-records`, method: 'POST',
headers: {'Content-Type': 'application/json', }, headers: {'Content-Type': 'application/json', },
data: studentRecordRequest, data: batchStudentRecordsRequest,
responseType: 'blob' responseType: 'blob'
}, },
); );
@ -1650,7 +1638,7 @@ const getLessonFeedback = (
/** /**
* @summary * @summary
*/ */
const submitFeedback = ( const submitFeedback1 = (
id: number, id: number,
lessonFeedbackRequest: LessonFeedbackRequest, lessonFeedbackRequest: LessonFeedbackRequest,
) => { ) => {
@ -1778,40 +1766,9 @@ const createTeacher = (
*/ */
const resetPassword = ( const resetPassword = (
id: number, id: number,
params: ResetPasswordParams,
) => { ) => {
return customMutator<Blob>( return customMutator<Blob>(
{url: `/v1/school/teachers/${id}/reset-password`, method: 'POST', {url: `/v1/school/teachers/${id}/reset-password`, method: 'POST',
params,
responseType: 'blob'
},
);
}
/**
* @summary Get task page
*/
const getTaskPage1 = (
params?: GetTaskPage1Params,
) => {
return customMutator<Blob>(
{url: `/v1/school/tasks`, method: 'GET',
params,
responseType: 'blob'
},
);
}
/**
* @summary Create task
*/
const createTask1 = (
taskCreateRequest: TaskCreateRequest,
) => {
return customMutator<Blob>(
{url: `/v1/school/tasks`, method: 'POST',
headers: {'Content-Type': 'application/json', },
data: taskCreateRequest,
responseType: 'blob' responseType: 'blob'
}, },
); );
@ -2157,14 +2114,17 @@ const assignStudents = (
} }
/** /**
* @summary Complete task * @summary 使 JSON body
*/ */
const completeTask = ( const completeTask = (
id: number, id: number,
params: CompleteTaskParams, taskCompleteRequest: TaskCompleteRequest,
params?: CompleteTaskParams,
) => { ) => {
return customMutator<Blob>( return customMutator<Blob>(
{url: `/v1/parent/tasks/${id}/complete`, method: 'POST', {url: `/v1/parent/tasks/${id}/complete`, method: 'POST',
headers: {'Content-Type': 'application/json', },
data: taskCompleteRequest,
params, params,
responseType: 'blob' responseType: 'blob'
}, },
@ -2197,21 +2157,6 @@ const markAllAsRead1 = (
); );
} }
/**
* @summary Create growth record
*/
const createGrowthRecord2 = (
growthRecordCreateRequest: GrowthRecordCreateRequest,
) => {
return customMutator<Blob>(
{url: `/v1/parent/growth-records`, method: 'POST',
headers: {'Content-Type': 'application/json', },
data: growthRecordCreateRequest,
responseType: 'blob'
},
);
}
/** /**
* Token * Token
* @summary WebOffice Token * @summary WebOffice Token
@ -2723,6 +2668,34 @@ const getTodayLessons = (
); );
} }
/**
* @summary
*/
const getTaskCompletions = (
taskId: number,
params?: GetTaskCompletionsParams,
) => {
return customMutator<Blob>(
{url: `/v1/teacher/tasks/${taskId}/completions`, method: 'GET',
params,
responseType: 'blob'
},
);
}
/**
* @summary
*/
const getCompletionDetail = (
completionId: number,
) => {
return customMutator<Blob>(
{url: `/v1/teacher/tasks/completions/${completionId}`, method: 'GET',
responseType: 'blob'
},
);
}
/** /**
* @summary * @summary
*/ */
@ -2941,10 +2914,11 @@ const getCourse = (
* @summary * @summary
*/ */
const getAllCourses = ( const getAllCourses = (
params?: GetAllCoursesParams,
) => { ) => {
return customMutator<Blob>( return customMutator<Blob>(
{url: `/v1/teacher/courses/all`, method: 'GET', {url: `/v1/teacher/courses/all`, method: 'GET',
params,
responseType: 'blob' responseType: 'blob'
}, },
); );
@ -3191,6 +3165,75 @@ const getCourseReports = (
); );
} }
/**
* @summary
*/
const getTaskList = (
params?: GetTaskListParams,
) => {
return customMutator<Blob>(
{url: `/v1/school/reading-tasks`, method: 'GET',
params,
responseType: 'blob'
},
);
}
/**
* @summary
*/
const getTaskDetail = (
taskId: number,
) => {
return customMutator<Blob>(
{url: `/v1/school/reading-tasks/${taskId}`, method: 'GET',
responseType: 'blob'
},
);
}
/**
* @summary
*/
const getTaskCompletions1 = (
taskId: number,
params?: GetTaskCompletions1Params,
) => {
return customMutator<Blob>(
{url: `/v1/school/reading-tasks/${taskId}/completions`, method: 'GET',
params,
responseType: 'blob'
},
);
}
/**
* @summary
*/
const getStatistics = (
params?: GetStatisticsParams,
) => {
return customMutator<Blob>(
{url: `/v1/school/reading-tasks/statistics`, method: 'GET',
params,
responseType: 'blob'
},
);
}
/**
* @summary
*/
const getCompletionDetail1 = (
completionId: number,
) => {
return customMutator<Blob>(
{url: `/v1/school/reading-tasks/completions/${completionId}`, method: 'GET',
responseType: 'blob'
},
);
}
/** /**
* @summary Get children of parent * @summary Get children of parent
*/ */
@ -3418,7 +3461,7 @@ const getSchoolCourse = (
} }
/** /**
* @summary Get my tasks * @summary
*/ */
const getMyTasks = ( const getMyTasks = (
params?: GetMyTasksParams, params?: GetMyTasksParams,
@ -3432,9 +3475,9 @@ const getMyTasks = (
} }
/** /**
* @summary Get task by ID * @summary
*/ */
const getTask2 = ( const getTask1 = (
id: number, id: number,
) => { ) => {
return customMutator<Blob>( return customMutator<Blob>(
@ -3445,7 +3488,7 @@ const getTask2 = (
} }
/** /**
* @summary Get tasks by student ID * @summary
*/ */
const getTasksByStudent = ( const getTasksByStudent = (
studentId: number, studentId: number,
@ -3459,6 +3502,32 @@ const getTasksByStudent = (
); );
} }
/**
* @summary
*/
const getCompletionDetail2 = (
completionId: number,
) => {
return customMutator<Blob>(
{url: `/v1/parent/tasks/completions/${completionId}`, method: 'GET',
responseType: 'blob'
},
);
}
/**
* @summary
*/
const getFeedback = (
completionId: number,
) => {
return customMutator<Blob>(
{url: `/v1/parent/tasks/completions/${completionId}/feedback`, method: 'GET',
responseType: 'blob'
},
);
}
/** /**
* @summary Get my notifications * @summary Get my notifications
*/ */
@ -3499,6 +3568,19 @@ const getUnreadCount1 = (
); );
} }
/**
* @summary Get growth record by ID
*/
const getGrowthRecord2 = (
id: number,
) => {
return customMutator<Blob>(
{url: `/v1/parent/growth-records/${id}`, method: 'GET',
responseType: 'blob'
},
);
}
/** /**
* @summary Get growth records by student ID * @summary Get growth records by student ID
*/ */
@ -3555,6 +3637,21 @@ const getChild = (
); );
} }
/**
* @summary Get child lesson records (reading history)
*/
const getChildLessons = (
id: number,
params?: GetChildLessonsParams,
) => {
return customMutator<Blob>(
{url: `/v1/parent/children/${id}/lessons`, method: 'GET',
params,
responseType: 'blob'
},
);
}
/** /**
* @summary Get child growth records * @summary Get child growth records
*/ */
@ -3800,10 +3897,12 @@ const deleteFile = (
); );
} }
return {getTask,updateTask,deleteTask,getTemplate,updateTemplate,deleteTemplate,getSchedule,updateSchedule,cancelSchedule,getLesson,updateLesson,getLessonProgress,saveLessonProgress,getGrowthRecord,updateGrowthRecord,deleteGrowthRecord,getTeacher,updateTeacher,deleteTeacher,getTask1,updateTask1,deleteTask1,getTemplate1,updateTemplate1,deleteTemplate1,getStudent,updateStudent,deleteStudent,getSettings,updateSettings,getSecuritySettings,updateSecuritySettings,getNotificationSettings,updateNotificationSettings,getBasicSettings,updateBasicSettings,getSchedule1,updateSchedule1,cancelSchedule1,getParent,updateParent,deleteParent,getGrowthRecord1,updateGrowthRecord1,deleteGrowthRecord1,getClass,updateClass,deleteClass,updateClassTeacher,removeClassTeacher,getGrowthRecord2,updateGrowthRecord2,deleteGrowthRecord2,findOne,update,_delete,reorder,getTenant,updateTenant,deleteTenant,updateTenantStatus,updateTenantQuota,getAllSettings,updateSettings1,getStorageSettings,updateStorageSettings,getSecuritySettings1,updateSecuritySettings1,getNotificationSettings1,updateNotificationSettings1,getBasicSettings1,updateBasicSettings1,findLibrary,updateLibrary,deleteLibrary,findItem,updateItem,deleteItem,getCourse1,updateCourse,deleteCourse,reorderSteps,findOne1,update1,delete1,updateStep,removeStep,reorder1,findOne2,update2,delete2,setPackages,getTaskPage,createTask,getTemplates,createTemplate,createFromTemplate,getSchedules,createSchedule,markAsRead,markAllAsRead,getMyLessons,createLesson,saveStudentRecord,batchSaveStudentRecords,startLesson,getLessonFeedback,submitFeedback,completeLesson,cancelLesson,createLessonFromSchedule,startLessonFromSchedule,getGrowthRecordPage,createGrowthRecord,getTeacherPage,createTeacher,resetPassword,getTaskPage1,createTask1,getTemplates1,createTemplate1,getStudentPage,createStudent,getSchedules1,createSchedule1,checkConflict,batchCreateSchedules,createSchedulesByClasses,getParentPage,createParent,bindStudent,unbindStudent,resetPassword1,renewCollection,getGrowthRecordPage1,createGrowthRecord1,getClassPage,createClass,getClassTeachers1,assignTeachers,getClassStudents1,assignStudents,completeTask,markAsRead1,markAllAsRead1,createGrowthRecord2,refreshToken,uploadFile,refreshToken1,logout,login,changePassword,findAll,create,getTenantPage,createTenant,resetTenantPassword,findAllLibraries,createLibrary,findAllItems,createItem,batchDeleteItems,getCoursePage1,createCourse,submitCourse,rejectCourse,publishCourse,archiveCourse,findAll1,create1,findSteps,createStep,page,create2,withdraw,submit,republish,reject,publish,archive,getWeeklyStats,getTodayLessons,getDefaultTemplate,getAllStudents,getTodaySchedules,getTimetable,getRecommendedCourses,getMyNotifications,getNotification,getUnreadCount,getStudentRecords,getTodayLessons1,getLessonTrend,getFeedbacks,getFeedbackStats,getDashboard,getCoursePage,getCourse,getAllCourses,getCourseUsage,getClasses,getClassTeachers,getClassStudents,getDefaultTemplate1,getSchoolStats,getActiveTeachers,getLessonTrend1,getCourseUsageStats,getCourseDistribution,getRecentActivities,getTimetable1,getCoursePackageLessonTypes,getCalendarViewData,getTeacherReports,getStudentReports,getOverview,getCourseReports,getParentChildren,findTenantCollections,getPackagesByCollection,getPackageCourses,getPackageInfo,getPackageUsage,getLogList,getLogDetail,getLogStats,getFeedbacks1,getFeedbackStats1,exportTeacherStats,exportStudentStats,exportLessons,exportGrowthRecords,getSchoolCourses,getSchoolCourse,getMyTasks,getTask2,getTasksByStudent,getMyNotifications1,getNotification1,getUnreadCount1,getGrowthRecordsByStudent,getRecentGrowthRecords,getMyChildren,getChild,getChildGrowth,generateEditToken,generateReadOnlyToken,getOssToken,getCurrentUser,getTenantStats,getAllActiveTenants,getStats,getTrendData,getActiveTenants,getPopularCourses,getRecentActivities1,getTenantDefaults,getStats1,getAllPublishedCourses,findByType,getAllPublishedCollections,deleteFile}}; return {getTask,updateTask,deleteTask,updateFeedback,submitFeedback,getTemplate,updateTemplate,deleteTemplate,getSchedule,updateSchedule,cancelSchedule,getLesson,updateLesson,getLessonProgress,saveLessonProgress,getGrowthRecord,updateGrowthRecord,deleteGrowthRecord,getTeacher,updateTeacher,deleteTeacher,getTemplate1,updateTemplate1,deleteTemplate1,getStudent,updateStudent,deleteStudent,getSettings,updateSettings,getSecuritySettings,updateSecuritySettings,getNotificationSettings,updateNotificationSettings,getBasicSettings,updateBasicSettings,getSchedule1,updateSchedule1,cancelSchedule1,getParent,updateParent,deleteParent,getGrowthRecord1,updateGrowthRecord1,deleteGrowthRecord1,getClass,updateClass,deleteClass,updateClassTeacher,removeClassTeacher,updateSubmission,submitTask,findOne,update,_delete,reorder,getTenant,updateTenant,deleteTenant,updateTenantStatus,updateTenantQuota,getAllSettings,updateSettings1,getStorageSettings,updateStorageSettings,getSecuritySettings1,updateSecuritySettings1,getNotificationSettings1,updateNotificationSettings1,getBasicSettings1,updateBasicSettings1,findLibrary,updateLibrary,deleteLibrary,findItem,updateItem,deleteItem,getCourse1,updateCourse,deleteCourse,reorderSteps,findOne1,update1,delete1,updateStep,removeStep,reorder1,findOne2,update2,delete2,setPackages,getTaskPage,createTask,getTemplates,createTemplate,createFromTemplate,getSchedules,createSchedule,markAsRead,markAllAsRead,getMyLessons,createLesson,saveStudentRecord,batchSaveStudentRecords,startLesson,getLessonFeedback,submitFeedback1,completeLesson,cancelLesson,createLessonFromSchedule,startLessonFromSchedule,getGrowthRecordPage,createGrowthRecord,getTeacherPage,createTeacher,resetPassword,getTemplates1,createTemplate1,getStudentPage,createStudent,getSchedules1,createSchedule1,checkConflict,batchCreateSchedules,createSchedulesByClasses,getParentPage,createParent,bindStudent,unbindStudent,resetPassword1,renewCollection,getGrowthRecordPage1,createGrowthRecord1,getClassPage,createClass,getClassTeachers1,assignTeachers,getClassStudents1,assignStudents,completeTask,markAsRead1,markAllAsRead1,refreshToken,uploadFile,refreshToken1,logout,login,changePassword,findAll,create,getTenantPage,createTenant,resetTenantPassword,findAllLibraries,createLibrary,findAllItems,createItem,batchDeleteItems,getCoursePage1,createCourse,submitCourse,rejectCourse,publishCourse,archiveCourse,findAll1,create1,findSteps,createStep,page,create2,withdraw,submit,republish,reject,publish,archive,getWeeklyStats,getTodayLessons,getTaskCompletions,getCompletionDetail,getDefaultTemplate,getAllStudents,getTodaySchedules,getTimetable,getRecommendedCourses,getMyNotifications,getNotification,getUnreadCount,getStudentRecords,getTodayLessons1,getLessonTrend,getFeedbacks,getFeedbackStats,getDashboard,getCoursePage,getCourse,getAllCourses,getCourseUsage,getClasses,getClassTeachers,getClassStudents,getDefaultTemplate1,getSchoolStats,getActiveTeachers,getLessonTrend1,getCourseUsageStats,getCourseDistribution,getRecentActivities,getTimetable1,getCoursePackageLessonTypes,getCalendarViewData,getTeacherReports,getStudentReports,getOverview,getCourseReports,getTaskList,getTaskDetail,getTaskCompletions1,getStatistics,getCompletionDetail1,getParentChildren,findTenantCollections,getPackagesByCollection,getPackageCourses,getPackageInfo,getPackageUsage,getLogList,getLogDetail,getLogStats,getFeedbacks1,getFeedbackStats1,exportTeacherStats,exportStudentStats,exportLessons,exportGrowthRecords,getSchoolCourses,getSchoolCourse,getMyTasks,getTask1,getTasksByStudent,getCompletionDetail2,getFeedback,getMyNotifications1,getNotification1,getUnreadCount1,getGrowthRecord2,getGrowthRecordsByStudent,getRecentGrowthRecords,getMyChildren,getChild,getChildLessons,getChildGrowth,generateEditToken,generateReadOnlyToken,getOssToken,getCurrentUser,getTenantStats,getAllActiveTenants,getStats,getTrendData,getActiveTenants,getPopularCourses,getRecentActivities1,getTenantDefaults,getStats1,getAllPublishedCourses,findByType,getAllPublishedCollections,deleteFile}};
export type GetTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask']>>> export type GetTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask']>>>
export type UpdateTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTask']>>> export type UpdateTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTask']>>>
export type DeleteTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTask']>>> export type DeleteTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTask']>>>
export type UpdateFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateFeedback']>>>
export type SubmitFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitFeedback']>>>
export type GetTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplate']>>> export type GetTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplate']>>>
export type UpdateTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTemplate']>>> export type UpdateTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTemplate']>>>
export type DeleteTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTemplate']>>> export type DeleteTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTemplate']>>>
@ -3820,9 +3919,6 @@ export type DeleteGrowthRecordResult = NonNullable<Awaited<ReturnType<ReturnType
export type GetTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTeacher']>>> export type GetTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTeacher']>>>
export type UpdateTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTeacher']>>> export type UpdateTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTeacher']>>>
export type DeleteTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTeacher']>>> export type DeleteTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTeacher']>>>
export type GetTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask1']>>>
export type UpdateTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTask1']>>>
export type DeleteTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTask1']>>>
export type GetTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplate1']>>> export type GetTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplate1']>>>
export type UpdateTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTemplate1']>>> export type UpdateTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTemplate1']>>>
export type DeleteTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTemplate1']>>> export type DeleteTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteTemplate1']>>>
@ -3851,9 +3947,8 @@ export type UpdateClassResult = NonNullable<Awaited<ReturnType<ReturnType<typeof
export type DeleteClassResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteClass']>>> export type DeleteClassResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteClass']>>>
export type UpdateClassTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateClassTeacher']>>> export type UpdateClassTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateClassTeacher']>>>
export type RemoveClassTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['removeClassTeacher']>>> export type RemoveClassTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['removeClassTeacher']>>>
export type GetGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getGrowthRecord2']>>> export type UpdateSubmissionResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateSubmission']>>>
export type UpdateGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateGrowthRecord2']>>> export type SubmitTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitTask']>>>
export type DeleteGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteGrowthRecord2']>>>
export type FindOneResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['findOne']>>> export type FindOneResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['findOne']>>>
export type UpdateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['update']>>> export type UpdateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['update']>>>
export type _DeleteResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['_delete']>>> export type _DeleteResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['_delete']>>>
@ -3908,7 +4003,7 @@ export type SaveStudentRecordResult = NonNullable<Awaited<ReturnType<ReturnType<
export type BatchSaveStudentRecordsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['batchSaveStudentRecords']>>> export type BatchSaveStudentRecordsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['batchSaveStudentRecords']>>>
export type StartLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['startLesson']>>> export type StartLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['startLesson']>>>
export type GetLessonFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getLessonFeedback']>>> export type GetLessonFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getLessonFeedback']>>>
export type SubmitFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitFeedback']>>> export type SubmitFeedback1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitFeedback1']>>>
export type CompleteLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['completeLesson']>>> export type CompleteLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['completeLesson']>>>
export type CancelLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['cancelLesson']>>> export type CancelLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['cancelLesson']>>>
export type CreateLessonFromScheduleResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createLessonFromSchedule']>>> export type CreateLessonFromScheduleResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createLessonFromSchedule']>>>
@ -3918,8 +4013,6 @@ export type CreateGrowthRecordResult = NonNullable<Awaited<ReturnType<ReturnType
export type GetTeacherPageResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTeacherPage']>>> export type GetTeacherPageResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTeacherPage']>>>
export type CreateTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTeacher']>>> export type CreateTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTeacher']>>>
export type ResetPasswordResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['resetPassword']>>> export type ResetPasswordResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['resetPassword']>>>
export type GetTaskPage1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskPage1']>>>
export type CreateTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTask1']>>>
export type GetTemplates1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplates1']>>> export type GetTemplates1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTemplates1']>>>
export type CreateTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTemplate1']>>> export type CreateTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTemplate1']>>>
export type GetStudentPageResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStudentPage']>>> export type GetStudentPageResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStudentPage']>>>
@ -3946,7 +4039,6 @@ export type AssignStudentsResult = NonNullable<Awaited<ReturnType<ReturnType<typ
export type CompleteTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['completeTask']>>> export type CompleteTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['completeTask']>>>
export type MarkAsRead1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['markAsRead1']>>> export type MarkAsRead1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['markAsRead1']>>>
export type MarkAllAsRead1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['markAllAsRead1']>>> export type MarkAllAsRead1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['markAllAsRead1']>>>
export type CreateGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createGrowthRecord2']>>>
export type RefreshTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['refreshToken']>>> export type RefreshTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['refreshToken']>>>
export type UploadFileResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['uploadFile']>>> export type UploadFileResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['uploadFile']>>>
export type RefreshToken1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['refreshToken1']>>> export type RefreshToken1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['refreshToken1']>>>
@ -3983,6 +4075,8 @@ export type PublishResult = NonNullable<Awaited<ReturnType<ReturnType<typeof get
export type ArchiveResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['archive']>>> export type ArchiveResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['archive']>>>
export type GetWeeklyStatsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getWeeklyStats']>>> export type GetWeeklyStatsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getWeeklyStats']>>>
export type GetTodayLessonsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTodayLessons']>>> export type GetTodayLessonsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTodayLessons']>>>
export type GetTaskCompletionsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskCompletions']>>>
export type GetCompletionDetailResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCompletionDetail']>>>
export type GetDefaultTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getDefaultTemplate']>>> export type GetDefaultTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getDefaultTemplate']>>>
export type GetAllStudentsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getAllStudents']>>> export type GetAllStudentsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getAllStudents']>>>
export type GetTodaySchedulesResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTodaySchedules']>>> export type GetTodaySchedulesResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTodaySchedules']>>>
@ -4018,6 +4112,11 @@ export type GetTeacherReportsResult = NonNullable<Awaited<ReturnType<ReturnType<
export type GetStudentReportsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStudentReports']>>> export type GetStudentReportsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStudentReports']>>>
export type GetOverviewResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getOverview']>>> export type GetOverviewResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getOverview']>>>
export type GetCourseReportsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCourseReports']>>> export type GetCourseReportsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCourseReports']>>>
export type GetTaskListResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskList']>>>
export type GetTaskDetailResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskDetail']>>>
export type GetTaskCompletions1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTaskCompletions1']>>>
export type GetStatisticsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getStatistics']>>>
export type GetCompletionDetail1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCompletionDetail1']>>>
export type GetParentChildrenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getParentChildren']>>> export type GetParentChildrenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getParentChildren']>>>
export type FindTenantCollectionsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['findTenantCollections']>>> export type FindTenantCollectionsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['findTenantCollections']>>>
export type GetPackagesByCollectionResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getPackagesByCollection']>>> export type GetPackagesByCollectionResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getPackagesByCollection']>>>
@ -4036,15 +4135,19 @@ export type ExportGrowthRecordsResult = NonNullable<Awaited<ReturnType<ReturnTyp
export type GetSchoolCoursesResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getSchoolCourses']>>> export type GetSchoolCoursesResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getSchoolCourses']>>>
export type GetSchoolCourseResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getSchoolCourse']>>> export type GetSchoolCourseResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getSchoolCourse']>>>
export type GetMyTasksResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyTasks']>>> export type GetMyTasksResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyTasks']>>>
export type GetTask2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask2']>>> export type GetTask1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTask1']>>>
export type GetTasksByStudentResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTasksByStudent']>>> export type GetTasksByStudentResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getTasksByStudent']>>>
export type GetCompletionDetail2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getCompletionDetail2']>>>
export type GetFeedbackResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getFeedback']>>>
export type GetMyNotifications1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyNotifications1']>>> export type GetMyNotifications1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyNotifications1']>>>
export type GetNotification1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getNotification1']>>> export type GetNotification1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getNotification1']>>>
export type GetUnreadCount1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getUnreadCount1']>>> export type GetUnreadCount1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getUnreadCount1']>>>
export type GetGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getGrowthRecord2']>>>
export type GetGrowthRecordsByStudentResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getGrowthRecordsByStudent']>>> export type GetGrowthRecordsByStudentResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getGrowthRecordsByStudent']>>>
export type GetRecentGrowthRecordsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getRecentGrowthRecords']>>> export type GetRecentGrowthRecordsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getRecentGrowthRecords']>>>
export type GetMyChildrenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyChildren']>>> export type GetMyChildrenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyChildren']>>>
export type GetChildResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getChild']>>> export type GetChildResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getChild']>>>
export type GetChildLessonsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getChildLessons']>>>
export type GetChildGrowthResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getChildGrowth']>>> export type GetChildGrowthResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getChildGrowth']>>>
export type GenerateEditTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['generateEditToken']>>> export type GenerateEditTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['generateEditToken']>>>
export type GenerateReadOnlyTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['generateReadOnlyToken']>>> export type GenerateReadOnlyTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['generateReadOnlyToken']>>>

View File

@ -0,0 +1,16 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { StudentRecordRequest } from './studentRecordRequest';
/**
*
*/
export interface BatchStudentRecordsRequest {
/** 记录列表 */
records: StudentRecordRequest[];
}

View File

@ -0,0 +1,29 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { ClassInfo } from './classInfo';
/**
*
*/
export interface ChildInfoResponse {
/** 学生ID */
id?: number;
/** 姓名 */
name?: string;
/** 性别 */
gender?: string;
/** 出生日期 */
birthDate?: string;
/** 与家长关系FATHER/MOTHER/GRANDFATHER/GRANDMOTHER/OTHER */
relationship?: string;
/** 阅读次数student_record 数量) */
readingCount?: number;
/** 上课次数lesson 记录数) */
lessonCount?: number;
class?: ClassInfo;
}

View File

@ -0,0 +1,31 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { ClassInfo } from './classInfo';
import type { StatsInfo } from './statsInfo';
/**
*
*/
export interface ChildProfileResponse {
/** 学生ID */
id?: number;
/** 姓名 */
name?: string;
/** 性别 */
gender?: string;
/** 出生日期 */
birthDate?: string;
/** 与家长关系 */
relationship?: string;
/** 阅读次数 */
readingCount?: number;
/** 上课次数 */
lessonCount?: number;
stats?: StatsInfo;
class?: ClassInfo;
}

View File

@ -0,0 +1,19 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface ClassInfo {
/** 班级ID */
id?: number;
/** 班级名称 */
name?: string;
/** 年级 */
grade?: string;
}

View File

@ -7,7 +7,7 @@
*/ */
export type CompleteTaskParams = { export type CompleteTaskParams = {
studentId: number; studentId?: number;
content?: string; content?: string;
attachments?: string; attachments?: string;
}; };

View File

@ -16,4 +16,6 @@ export interface CourseCollectionPageQueryRequest {
pageSize?: number; pageSize?: number;
/** 状态 */ /** 状态 */
status?: string; status?: string;
/** 年级(支持多个,逗号分隔) */
gradeLevels?: string;
} }

View File

@ -0,0 +1,17 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface CourseInfo {
/** 课程ID */
id?: number;
/** 课程名称 */
name?: string;
}

View File

@ -20,6 +20,8 @@ export interface CoursePageQueryRequest {
category?: string; category?: string;
/** 状态 */ /** 状态 */
status?: string; status?: string;
/** 年级(支持多个,逗号分隔) */
gradeTags?: string;
/** 是否仅查询待审核 */ /** 是否仅查询待审核 */
reviewOnly?: boolean; reviewOnly?: boolean;
} }

View File

@ -6,6 +6,7 @@
* OpenAPI spec version: 1.0.0 * OpenAPI spec version: 1.0.0
*/ */
import type { CourseLessonResponse } from './courseLessonResponse'; import type { CourseLessonResponse } from './courseLessonResponse';
import type { LessonTagResponse } from './lessonTagResponse';
/** /**
* *
@ -89,10 +90,10 @@ export interface CourseResponse {
activitiesData?: string; activitiesData?: string;
/** 评估数据 */ /** 评估数据 */
assessmentData?: string; assessmentData?: string;
/** 年级标签 */ /** 年级标签(规范为数组,与套餐管理适用年级对齐) */
gradeTags?: string; gradeTags?: string[];
/** 领域标签 */ /** 领域标签(规范为数组) */
domainTags?: string; domainTags?: string[];
/** 是否有集体课 */ /** 是否有集体课 */
hasCollectiveLesson?: number; hasCollectiveLesson?: number;
/** 版本号 */ /** 版本号 */
@ -129,4 +130,6 @@ export interface CourseResponse {
updatedAt?: string; updatedAt?: string;
/** 关联的课程环节 */ /** 关联的课程环节 */
courseLessons?: CourseLessonResponse[]; courseLessons?: CourseLessonResponse[];
/** 课程环节标签(列表展示用,仅 name 和 lessonType */
lessonTags?: LessonTagResponse[];
} }

View File

@ -16,6 +16,8 @@ export interface DayScheduleItem {
className?: string; className?: string;
/** 课程包名称 */ /** 课程包名称 */
coursePackageName?: string; coursePackageName?: string;
/** 课程类型代码 (如 DOMAIN_HEALTH) */
lessonType?: string;
/** 课程类型名称 */ /** 课程类型名称 */
lessonTypeName?: string; lessonTypeName?: string;
/** 教师名称 */ /** 教师名称 */

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
export type GetAllCoursesParams = {
keyword?: string;
grade?: string;
domain?: string;
};

View File

@ -0,0 +1,12 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
export type GetChildLessonsParams = {
pageNum?: number;
pageSize?: number;
};

View File

@ -10,5 +10,7 @@ export type GetCoursePageParams = {
pageNum?: number; pageNum?: number;
pageSize?: number; pageSize?: number;
keyword?: string; keyword?: string;
category?: string; grade?: string;
domain?: string;
lessonType?: string;
}; };

View File

@ -10,4 +10,5 @@ export type GetMyNotifications1Params = {
pageNum?: number; pageNum?: number;
pageSize?: number; pageSize?: number;
isRead?: number; isRead?: number;
notificationType?: string;
}; };

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
export type GetStatisticsParams = {
dateType?: string;
startDate?: string;
endDate?: string;
};

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
export type GetTaskCompletions1Params = {
pageNum?: number;
pageSize?: number;
status?: string;
};

View File

@ -0,0 +1,13 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
export type GetTaskCompletionsParams = {
pageNum?: number;
pageSize?: number;
status?: string;
};

View File

@ -0,0 +1,23 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
export type GetTaskListParams = {
pageNum?: number;
pageSize?: number;
keyword?: string;
type?: string;
status?: string;
classIds?: number[];
teacherIds?: number[];
dateType?: string;
startDate?: string;
endDate?: string;
completionRate?: string;
sortBy?: string;
sortOrder?: string;
};

View File

@ -11,5 +11,6 @@ pageNum?: number;
pageSize?: number; pageSize?: number;
keyword?: string; keyword?: string;
type?: string; type?: string;
taskType?: string;
status?: string; status?: string;
}; };

View File

@ -10,4 +10,6 @@ export type GetTemplates1Params = {
pageNum?: number; pageNum?: number;
pageSize?: number; pageSize?: number;
type?: string; type?: string;
taskType?: string;
keyword?: string;
}; };

View File

@ -10,4 +10,5 @@ export type GetTemplatesParams = {
pageNum?: number; pageNum?: number;
pageSize?: number; pageSize?: number;
type?: string; type?: string;
keyword?: string;
}; };

View File

@ -0,0 +1,29 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { ClassInfo } from './classInfo';
/**
*
*/
export interface GrowthRecordForParentResponse {
/** ID */
id?: number;
/** 标题 */
title?: string;
/** 内容 */
content?: string;
/** 图片URL列表 */
images?: string[];
/** 记录日期 */
recordDate?: string;
/** 记录类型 */
recordType?: string;
/** 创建时间 */
createdAt?: string;
class?: ClassInfo;
}

View File

@ -20,12 +20,16 @@ export * from './basicSettingsUpdateRequest';
export * from './batchCreateSchedulesBody'; export * from './batchCreateSchedulesBody';
export * from './batchStudentRecordsDto'; export * from './batchStudentRecordsDto';
export * from './batchStudentRecordsDtoRecordsItem'; export * from './batchStudentRecordsDtoRecordsItem';
export * from './batchStudentRecordsRequest';
export * from './bindStudentParams'; export * from './bindStudentParams';
export * from './calendarViewResponse'; export * from './calendarViewResponse';
export * from './calendarViewResponseSchedules'; export * from './calendarViewResponseSchedules';
export * from './changePasswordParams'; export * from './changePasswordParams';
export * from './checkConflictParams'; export * from './checkConflictParams';
export * from './childInfoResponse';
export * from './childProfileResponse';
export * from './classCreateRequest'; export * from './classCreateRequest';
export * from './classInfo';
export * from './classResponse'; export * from './classResponse';
export * from './classTeacherResponse'; export * from './classTeacherResponse';
export * from './classUpdateRequest'; export * from './classUpdateRequest';
@ -40,6 +44,7 @@ export * from './courseCollectionResponse';
export * from './courseControllerFindAllParams'; export * from './courseControllerFindAllParams';
export * from './courseControllerGetReviewListParams'; export * from './courseControllerGetReviewListParams';
export * from './courseCreateRequest'; export * from './courseCreateRequest';
export * from './courseInfo';
export * from './courseLesson'; export * from './courseLesson';
export * from './courseLessonCreateRequest'; export * from './courseLessonCreateRequest';
export * from './courseLessonResponse'; export * from './courseLessonResponse';
@ -95,8 +100,10 @@ export * from './getActiveTeachersParams';
export * from './getActiveTenants200'; export * from './getActiveTenants200';
export * from './getActiveTenants200DataItem'; export * from './getActiveTenants200DataItem';
export * from './getActiveTenantsParams'; export * from './getActiveTenantsParams';
export * from './getAllCoursesParams';
export * from './getAllStudentsParams'; export * from './getAllStudentsParams';
export * from './getCalendarViewDataParams'; export * from './getCalendarViewDataParams';
export * from './getChildLessonsParams';
export * from './getClassPageParams'; export * from './getClassPageParams';
export * from './getClassStudents1Params'; export * from './getClassStudents1Params';
export * from './getClassStudentsParams'; export * from './getClassStudentsParams';
@ -127,7 +134,11 @@ export * from './getRecentGrowthRecordsParams';
export * from './getSchedules1Params'; export * from './getSchedules1Params';
export * from './getSchedulesParams'; export * from './getSchedulesParams';
export * from './getSchoolCoursesParams'; export * from './getSchoolCoursesParams';
export * from './getStatisticsParams';
export * from './getStudentPageParams'; export * from './getStudentPageParams';
export * from './getTaskCompletions1Params';
export * from './getTaskCompletionsParams';
export * from './getTaskListParams';
export * from './getTaskPage1Params'; export * from './getTaskPage1Params';
export * from './getTaskPageParams'; export * from './getTaskPageParams';
export * from './getTasksByStudentParams'; export * from './getTasksByStudentParams';
@ -141,6 +152,7 @@ export * from './grantCollectionRequest';
export * from './grantRequest'; export * from './grantRequest';
export * from './growthRecord'; export * from './growthRecord';
export * from './growthRecordCreateRequest'; export * from './growthRecordCreateRequest';
export * from './growthRecordForParentResponse';
export * from './growthRecordResponse'; export * from './growthRecordResponse';
export * from './growthRecordUpdateRequest'; export * from './growthRecordUpdateRequest';
export * from './immTokenVo'; export * from './immTokenVo';
@ -156,14 +168,17 @@ export * from './lessonFeedbackDtoActivitiesDone';
export * from './lessonFeedbackDtoStepFeedbacks'; export * from './lessonFeedbackDtoStepFeedbacks';
export * from './lessonFeedbackRequest'; export * from './lessonFeedbackRequest';
export * from './lessonFeedbackResponse'; export * from './lessonFeedbackResponse';
export * from './lessonInfo';
export * from './lessonProgressDto'; export * from './lessonProgressDto';
export * from './lessonProgressDtoProgressData'; export * from './lessonProgressDtoProgressData';
export * from './lessonProgressRequest'; export * from './lessonProgressRequest';
export * from './lessonProgressRequestProgressData'; export * from './lessonProgressRequestProgressData';
export * from './lessonRecordResponse';
export * from './lessonResponse'; export * from './lessonResponse';
export * from './lessonStep'; export * from './lessonStep';
export * from './lessonStepCreateRequest'; export * from './lessonStepCreateRequest';
export * from './lessonStepResponse'; export * from './lessonStepResponse';
export * from './lessonTagResponse';
export * from './lessonTypeInfo'; export * from './lessonTypeInfo';
export * from './lessonUpdateRequest'; export * from './lessonUpdateRequest';
export * from './libraryCreateRequest'; export * from './libraryCreateRequest';
@ -174,6 +189,7 @@ export * from './loginDto';
export * from './loginRequest'; export * from './loginRequest';
export * from './loginResponse'; export * from './loginResponse';
export * from './notification'; export * from './notification';
export * from './notificationForParentResponse';
export * from './notificationResponse'; export * from './notificationResponse';
export * from './notificationSettingsResponse'; export * from './notificationSettingsResponse';
export * from './notificationSettingsUpdateRequest'; export * from './notificationSettingsUpdateRequest';
@ -197,11 +213,14 @@ export * from './pageResultCourseCollectionResponse';
export * from './pageResultCoursePackageResponse'; export * from './pageResultCoursePackageResponse';
export * from './pageResultCourseResponse'; export * from './pageResultCourseResponse';
export * from './pageResultGrowthRecord'; export * from './pageResultGrowthRecord';
export * from './pageResultGrowthRecordForParentResponse';
export * from './pageResultGrowthRecordResponse'; export * from './pageResultGrowthRecordResponse';
export * from './pageResultLesson'; export * from './pageResultLesson';
export * from './pageResultLessonFeedbackResponse'; export * from './pageResultLessonFeedbackResponse';
export * from './pageResultLessonRecordResponse';
export * from './pageResultLessonResponse'; export * from './pageResultLessonResponse';
export * from './pageResultNotification'; export * from './pageResultNotification';
export * from './pageResultNotificationForParentResponse';
export * from './pageResultNotificationResponse'; export * from './pageResultNotificationResponse';
export * from './pageResultOperationLogResponse'; export * from './pageResultOperationLogResponse';
export * from './pageResultParent'; export * from './pageResultParent';
@ -211,11 +230,14 @@ export * from './pageResultResourceItemResponse';
export * from './pageResultResourceLibrary'; export * from './pageResultResourceLibrary';
export * from './pageResultResourceLibraryResponse'; export * from './pageResultResourceLibraryResponse';
export * from './pageResultSchedulePlanResponse'; export * from './pageResultSchedulePlanResponse';
export * from './pageResultSchoolCourseResponse';
export * from './pageResultStudent'; export * from './pageResultStudent';
export * from './pageResultStudentResponse'; export * from './pageResultStudentResponse';
export * from './pageResultTask'; export * from './pageResultTask';
export * from './pageResultTaskCompletionDetailResponse';
export * from './pageResultTaskResponse'; export * from './pageResultTaskResponse';
export * from './pageResultTaskTemplateResponse'; export * from './pageResultTaskTemplateResponse';
export * from './pageResultTaskWithCompletionResponse';
export * from './pageResultTeacher'; export * from './pageResultTeacher';
export * from './pageResultTeacherResponse'; export * from './pageResultTeacherResponse';
export * from './pageResultTenant'; export * from './pageResultTenant';
@ -247,6 +269,7 @@ export * from './resourceLibraryResponse';
export * from './resourceLibraryUpdateRequest'; export * from './resourceLibraryUpdateRequest';
export * from './resultBasicSettingsResponse'; export * from './resultBasicSettingsResponse';
export * from './resultCalendarViewResponse'; export * from './resultCalendarViewResponse';
export * from './resultChildProfileResponse';
export * from './resultClassResponse'; export * from './resultClassResponse';
export * from './resultClazz'; export * from './resultClazz';
export * from './resultConflictCheckResult'; export * from './resultConflictCheckResult';
@ -260,6 +283,7 @@ export * from './resultCourseResponse';
export * from './resultDto'; export * from './resultDto';
export * from './resultDtoData'; export * from './resultDtoData';
export * from './resultGrowthRecord'; export * from './resultGrowthRecord';
export * from './resultGrowthRecordForParentResponse';
export * from './resultGrowthRecordResponse'; export * from './resultGrowthRecordResponse';
export * from './resultImmTokenVo'; export * from './resultImmTokenVo';
export * from './resultLesson'; export * from './resultLesson';
@ -269,6 +293,7 @@ export * from './resultLessonResponse';
export * from './resultLessonStep'; export * from './resultLessonStep';
export * from './resultLessonStepResponse'; export * from './resultLessonStepResponse';
export * from './resultListActiveTenantItemResponse'; export * from './resultListActiveTenantItemResponse';
export * from './resultListChildInfoResponse';
export * from './resultListClassResponse'; export * from './resultListClassResponse';
export * from './resultListClassTeacherResponse'; export * from './resultListClassTeacherResponse';
export * from './resultListClazz'; export * from './resultListClazz';
@ -281,6 +306,7 @@ export * from './resultListCoursePackageResponse';
export * from './resultListCourseReportResponse'; export * from './resultListCourseReportResponse';
export * from './resultListCourseResponse'; export * from './resultListCourseResponse';
export * from './resultListGrowthRecord'; export * from './resultListGrowthRecord';
export * from './resultListGrowthRecordForParentResponse';
export * from './resultListGrowthRecordResponse'; export * from './resultListGrowthRecordResponse';
export * from './resultListLesson'; export * from './resultListLesson';
export * from './resultListLessonResponse'; export * from './resultListLessonResponse';
@ -308,7 +334,10 @@ export * from './resultLoginResponse';
export * from './resultLong'; export * from './resultLong';
export * from './resultMapStringObject'; export * from './resultMapStringObject';
export * from './resultMapStringObjectData'; export * from './resultMapStringObjectData';
export * from './resultMapStringString';
export * from './resultMapStringStringData';
export * from './resultNotification'; export * from './resultNotification';
export * from './resultNotificationForParentResponse';
export * from './resultNotificationResponse'; export * from './resultNotificationResponse';
export * from './resultNotificationSettingsResponse'; export * from './resultNotificationSettingsResponse';
export * from './resultObject'; export * from './resultObject';
@ -327,11 +356,14 @@ export * from './resultPageResultCourseCollectionResponse';
export * from './resultPageResultCoursePackageResponse'; export * from './resultPageResultCoursePackageResponse';
export * from './resultPageResultCourseResponse'; export * from './resultPageResultCourseResponse';
export * from './resultPageResultGrowthRecord'; export * from './resultPageResultGrowthRecord';
export * from './resultPageResultGrowthRecordForParentResponse';
export * from './resultPageResultGrowthRecordResponse'; export * from './resultPageResultGrowthRecordResponse';
export * from './resultPageResultLesson'; export * from './resultPageResultLesson';
export * from './resultPageResultLessonFeedbackResponse'; export * from './resultPageResultLessonFeedbackResponse';
export * from './resultPageResultLessonRecordResponse';
export * from './resultPageResultLessonResponse'; export * from './resultPageResultLessonResponse';
export * from './resultPageResultNotification'; export * from './resultPageResultNotification';
export * from './resultPageResultNotificationForParentResponse';
export * from './resultPageResultNotificationResponse'; export * from './resultPageResultNotificationResponse';
export * from './resultPageResultOperationLogResponse'; export * from './resultPageResultOperationLogResponse';
export * from './resultPageResultParent'; export * from './resultPageResultParent';
@ -341,11 +373,14 @@ export * from './resultPageResultResourceItemResponse';
export * from './resultPageResultResourceLibrary'; export * from './resultPageResultResourceLibrary';
export * from './resultPageResultResourceLibraryResponse'; export * from './resultPageResultResourceLibraryResponse';
export * from './resultPageResultSchedulePlanResponse'; export * from './resultPageResultSchedulePlanResponse';
export * from './resultPageResultSchoolCourseResponse';
export * from './resultPageResultStudent'; export * from './resultPageResultStudent';
export * from './resultPageResultStudentResponse'; export * from './resultPageResultStudentResponse';
export * from './resultPageResultTask'; export * from './resultPageResultTask';
export * from './resultPageResultTaskCompletionDetailResponse';
export * from './resultPageResultTaskResponse'; export * from './resultPageResultTaskResponse';
export * from './resultPageResultTaskTemplateResponse'; export * from './resultPageResultTaskTemplateResponse';
export * from './resultPageResultTaskWithCompletionResponse';
export * from './resultPageResultTeacher'; export * from './resultPageResultTeacher';
export * from './resultPageResultTeacherResponse'; export * from './resultPageResultTeacherResponse';
export * from './resultPageResultTenant'; export * from './resultPageResultTenant';
@ -358,6 +393,7 @@ export * from './resultResourceItemResponse';
export * from './resultResourceLibrary'; export * from './resultResourceLibrary';
export * from './resultResourceLibraryResponse'; export * from './resultResourceLibraryResponse';
export * from './resultSchedulePlanResponse'; export * from './resultSchedulePlanResponse';
export * from './resultSchoolCourseResponse';
export * from './resultSchoolSettingsResponse'; export * from './resultSchoolSettingsResponse';
export * from './resultSecuritySettingsResponse'; export * from './resultSecuritySettingsResponse';
export * from './resultStatsResponse'; export * from './resultStatsResponse';
@ -365,8 +401,11 @@ export * from './resultStatsTrendResponse';
export * from './resultString'; export * from './resultString';
export * from './resultStudent'; export * from './resultStudent';
export * from './resultStudentRecordResponse'; export * from './resultStudentRecordResponse';
export * from './resultStudentRecordsResponse';
export * from './resultStudentResponse'; export * from './resultStudentResponse';
export * from './resultTask'; export * from './resultTask';
export * from './resultTaskCompletionDetailResponse';
export * from './resultTaskFeedbackResponse';
export * from './resultTaskResponse'; export * from './resultTaskResponse';
export * from './resultTaskTemplateResponse'; export * from './resultTaskTemplateResponse';
export * from './resultTeacher'; export * from './resultTeacher';
@ -387,6 +426,7 @@ export * from './schedulePlanCreateRequest';
export * from './schedulePlanResponse'; export * from './schedulePlanResponse';
export * from './schedulePlanUpdateRequest'; export * from './schedulePlanUpdateRequest';
export * from './schoolControllerImportStudentsParams'; export * from './schoolControllerImportStudentsParams';
export * from './schoolCourseResponse';
export * from './schoolFeedbackControllerFindAllParams'; export * from './schoolFeedbackControllerFindAllParams';
export * from './schoolSettingsResponse'; export * from './schoolSettingsResponse';
export * from './schoolSettingsUpdateRequest'; export * from './schoolSettingsUpdateRequest';
@ -396,24 +436,35 @@ export * from './securitySettingsUpdateRequest';
export * from './statsControllerGetActiveTeachersParams'; export * from './statsControllerGetActiveTeachersParams';
export * from './statsControllerGetLessonTrendParams'; export * from './statsControllerGetLessonTrendParams';
export * from './statsControllerGetRecentActivitiesParams'; export * from './statsControllerGetRecentActivitiesParams';
export * from './statsInfo';
export * from './statsResponse'; export * from './statsResponse';
export * from './statsTrendResponse'; export * from './statsTrendResponse';
export * from './stepCreateRequest'; export * from './stepCreateRequest';
export * from './student'; export * from './student';
export * from './studentCreateRequest'; export * from './studentCreateRequest';
export * from './studentInfo';
export * from './studentRecordDto'; export * from './studentRecordDto';
export * from './studentRecordRequest'; export * from './studentRecordRequest';
export * from './studentRecordResponse'; export * from './studentRecordResponse';
export * from './studentRecordsResponse';
export * from './studentReportResponse'; export * from './studentReportResponse';
export * from './studentResponse'; export * from './studentResponse';
export * from './studentUpdateRequest'; export * from './studentUpdateRequest';
export * from './studentWithRecordResponse';
export * from './submitCourseDto'; export * from './submitCourseDto';
export * from './task'; export * from './task';
export * from './taskCompleteRequest';
export * from './taskCompletionDetailResponse';
export * from './taskCreateRequest'; export * from './taskCreateRequest';
export * from './taskFeedbackRequest';
export * from './taskFeedbackResponse';
export * from './taskInfo';
export * from './taskResponse'; export * from './taskResponse';
export * from './taskSubmitRequest';
export * from './taskTemplateCreateRequest'; export * from './taskTemplateCreateRequest';
export * from './taskTemplateResponse'; export * from './taskTemplateResponse';
export * from './taskUpdateRequest'; export * from './taskUpdateRequest';
export * from './taskWithCompletionResponse';
export * from './teacher'; export * from './teacher';
export * from './teacherCourseControllerFindAllParams'; export * from './teacherCourseControllerFindAllParams';
export * from './teacherCourseControllerGetAllStudentsParams'; export * from './teacherCourseControllerGetAllStudentsParams';
@ -425,6 +476,7 @@ export * from './teacherCreateRequest';
export * from './teacherFeedbackControllerFindAllParams'; export * from './teacherFeedbackControllerFindAllParams';
export * from './teacherReportResponse'; export * from './teacherReportResponse';
export * from './teacherResponse'; export * from './teacherResponse';
export * from './teacherResponseClassNames';
export * from './teacherTaskControllerGetMonthlyStatsParams'; export * from './teacherTaskControllerGetMonthlyStatsParams';
export * from './teacherUpdateRequest'; export * from './teacherUpdateRequest';
export * from './tenant'; export * from './tenant';

View File

@ -15,7 +15,7 @@ import type { LessonResponse } from './lessonResponse';
export interface LessonDetailResponse { export interface LessonDetailResponse {
lesson?: LessonResponse; lesson?: LessonResponse;
course?: CourseResponse; course?: CourseResponse;
class?: ClassResponse;
/** 排课选择的课程类型(子课程模式时用于直接进入对应子课程) */ /** 排课选择的课程类型(子课程模式时用于直接进入对应子课程) */
lessonType?: string; lessonType?: string;
class?: ClassResponse;
} }

View File

@ -0,0 +1,19 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface LessonInfo {
/** 课时 ID */
id?: number;
/** 状态 */
status?: string;
/** 班级名称 */
className?: string;
}

View File

@ -0,0 +1,29 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { LessonInfo } from './lessonInfo';
/**
*
*/
export interface LessonRecordResponse {
/** 记录IDstudent_record.id */
id?: number;
lesson?: LessonInfo;
/** 专注度评分 */
focus?: number;
/** 参与度评分 */
participation?: number;
/** 兴趣度评分 */
interest?: number;
/** 理解度评分 */
understanding?: number;
/** 备注 */
notes?: string;
/** 创建时间 */
createdAt?: string;
}

View File

@ -0,0 +1,17 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface LessonTagResponse {
/** 环节名称 */
name?: string;
/** 环节类型INTRODUCTION、COLLECTIVE、LANGUAGE、HEALTH、SCIENCE、SOCIAL、ART */
lessonType?: string;
}

View File

@ -0,0 +1,27 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface NotificationForParentResponse {
/** ID */
id?: number;
/** 标题 */
title?: string;
/** 内容 */
content?: string;
/** 是否已读 */
isRead?: boolean;
/** 阅读时间 */
readAt?: string;
/** 创建时间 */
createdAt?: string;
/** 通知类型 */
notificationType?: string;
}

View File

@ -0,0 +1,16 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { GrowthRecordForParentResponse } from './growthRecordForParentResponse';
export interface PageResultGrowthRecordForParentResponse {
list?: GrowthRecordForParentResponse[];
total?: number;
pageNum?: number;
pageSize?: number;
pages?: number;
}

View File

@ -0,0 +1,16 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { LessonRecordResponse } from './lessonRecordResponse';
export interface PageResultLessonRecordResponse {
list?: LessonRecordResponse[];
total?: number;
pageNum?: number;
pageSize?: number;
pages?: number;
}

View File

@ -0,0 +1,16 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { NotificationForParentResponse } from './notificationForParentResponse';
export interface PageResultNotificationForParentResponse {
list?: NotificationForParentResponse[];
total?: number;
pageNum?: number;
pageSize?: number;
pages?: number;
}

View File

@ -0,0 +1,16 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { SchoolCourseResponse } from './schoolCourseResponse';
export interface PageResultSchoolCourseResponse {
list?: SchoolCourseResponse[];
total?: number;
pageNum?: number;
pageSize?: number;
pages?: number;
}

View File

@ -0,0 +1,16 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { TaskCompletionDetailResponse } from './taskCompletionDetailResponse';
export interface PageResultTaskCompletionDetailResponse {
list?: TaskCompletionDetailResponse[];
total?: number;
pageNum?: number;
pageSize?: number;
pages?: number;
}

View File

@ -0,0 +1,16 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { TaskWithCompletionResponse } from './taskWithCompletionResponse';
export interface PageResultTaskWithCompletionResponse {
list?: TaskWithCompletionResponse[];
total?: number;
pageNum?: number;
pageSize?: number;
pages?: number;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { ChildProfileResponse } from './childProfileResponse';
export interface ResultChildProfileResponse {
code?: number;
message?: string;
data?: ChildProfileResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { GrowthRecordForParentResponse } from './growthRecordForParentResponse';
export interface ResultGrowthRecordForParentResponse {
code?: number;
message?: string;
data?: GrowthRecordForParentResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { ChildInfoResponse } from './childInfoResponse';
export interface ResultListChildInfoResponse {
code?: number;
message?: string;
data?: ChildInfoResponse[];
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { GrowthRecordForParentResponse } from './growthRecordForParentResponse';
export interface ResultListGrowthRecordForParentResponse {
code?: number;
message?: string;
data?: GrowthRecordForParentResponse[];
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { ResultMapStringStringData } from './resultMapStringStringData';
export interface ResultMapStringString {
code?: number;
message?: string;
data?: ResultMapStringStringData;
}

View File

@ -0,0 +1,9 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
export type ResultMapStringStringData = {[key: string]: string};

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { NotificationForParentResponse } from './notificationForParentResponse';
export interface ResultNotificationForParentResponse {
code?: number;
message?: string;
data?: NotificationForParentResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { PageResultGrowthRecordForParentResponse } from './pageResultGrowthRecordForParentResponse';
export interface ResultPageResultGrowthRecordForParentResponse {
code?: number;
message?: string;
data?: PageResultGrowthRecordForParentResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { PageResultLessonRecordResponse } from './pageResultLessonRecordResponse';
export interface ResultPageResultLessonRecordResponse {
code?: number;
message?: string;
data?: PageResultLessonRecordResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { PageResultNotificationForParentResponse } from './pageResultNotificationForParentResponse';
export interface ResultPageResultNotificationForParentResponse {
code?: number;
message?: string;
data?: PageResultNotificationForParentResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { PageResultSchoolCourseResponse } from './pageResultSchoolCourseResponse';
export interface ResultPageResultSchoolCourseResponse {
code?: number;
message?: string;
data?: PageResultSchoolCourseResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { PageResultTaskCompletionDetailResponse } from './pageResultTaskCompletionDetailResponse';
export interface ResultPageResultTaskCompletionDetailResponse {
code?: number;
message?: string;
data?: PageResultTaskCompletionDetailResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { PageResultTaskWithCompletionResponse } from './pageResultTaskWithCompletionResponse';
export interface ResultPageResultTaskWithCompletionResponse {
code?: number;
message?: string;
data?: PageResultTaskWithCompletionResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { SchoolCourseResponse } from './schoolCourseResponse';
export interface ResultSchoolCourseResponse {
code?: number;
message?: string;
data?: SchoolCourseResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { StudentRecordsResponse } from './studentRecordsResponse';
export interface ResultStudentRecordsResponse {
code?: number;
message?: string;
data?: StudentRecordsResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { TaskCompletionDetailResponse } from './taskCompletionDetailResponse';
export interface ResultTaskCompletionDetailResponse {
code?: number;
message?: string;
data?: TaskCompletionDetailResponse;
}

View File

@ -0,0 +1,14 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { TaskFeedbackResponse } from './taskFeedbackResponse';
export interface ResultTaskFeedbackResponse {
code?: number;
message?: string;
data?: TaskFeedbackResponse;
}

View File

@ -0,0 +1,50 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { LessonTagResponse } from './lessonTagResponse';
/**
*
*/
export interface SchoolCourseResponse {
/** ID */
id?: number;
/** 租户 ID */
tenantId?: number;
/** 课程名称 */
name?: string;
/** 课程编码 */
code?: string;
/** 描述 */
description?: string;
/** 绘本名称 */
pictureBookName?: string;
/** 封面图片路径 */
coverImagePath?: string;
/** 封面 URL */
coverUrl?: string;
/** 年级标签(规范为数组) */
gradeTags?: string[];
/** 领域标签(规范为数组) */
domainTags?: string[];
/** 课程时长(分钟) */
duration?: number;
/** 使用次数 */
usageCount?: number;
/** 教师数量 */
teacherCount?: number;
/** 平均评分 */
avgRating?: number;
/** 状态 */
status?: string;
/** 创建时间 */
createdAt?: string;
/** 更新时间 */
updatedAt?: string;
/** 课程环节标签(列表展示用,仅 name 和 lessonType */
lessonTags?: LessonTagResponse[];
}

View File

@ -0,0 +1,19 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface StatsInfo {
/** 阅读记录数 */
lessonRecords?: number;
/** 成长档案数 */
growthRecords?: number;
/** 任务完成数 */
taskCompletions?: number;
}

View File

@ -0,0 +1,23 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { ClassInfo } from './classInfo';
/**
*
*/
export interface StudentInfo {
/** 学生ID */
id?: number;
/** 学生姓名 */
name?: string;
/** 学生头像 */
avatar?: string;
/** 性别MALE/FEMALE */
gender?: string;
classInfo?: ClassInfo;
}

View File

@ -0,0 +1,18 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { LessonInfo } from './lessonInfo';
import type { StudentWithRecordResponse } from './studentWithRecordResponse';
/**
*
*/
export interface StudentRecordsResponse {
lesson?: LessonInfo;
/** 学生列表(含记录) */
students?: StudentWithRecordResponse[];
}

View File

@ -0,0 +1,21 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { StudentRecordResponse } from './studentRecordResponse';
/**
*
*/
export interface StudentWithRecordResponse {
/** 学生 ID */
id?: number;
/** 学生姓名 */
name?: string;
/** 性别 */
gender?: string;
record?: StudentRecordResponse;
}

View File

@ -28,6 +28,8 @@ export interface Task {
description?: string; description?: string;
/** 任务类型 */ /** 任务类型 */
type?: string; type?: string;
/** 关联绘本名称 */
relatedBookName?: string;
/** 课程 ID */ /** 课程 ID */
courseId?: number; courseId?: number;
/** 创建人 ID */ /** 创建人 ID */

View File

@ -0,0 +1,19 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface TaskCompleteRequest {
/** 学生ID */
studentId: number;
/** 完成内容/反馈 */
content?: string;
/** 附件 */
attachments?: string;
}

View File

@ -0,0 +1,39 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { StudentInfo } from './studentInfo';
import type { TaskFeedbackResponse } from './taskFeedbackResponse';
/**
*
*/
export interface TaskCompletionDetailResponse {
/** 完成记录ID */
id?: number;
/** 任务ID */
taskId?: number;
/** 任务标题 */
taskTitle?: string;
student?: StudentInfo;
/** 状态PENDING/SUBMITTED/REVIEWED */
status?: string;
/** 状态文本:待完成/已提交/已评价 */
statusText?: string;
/** 照片URL数组 */
photos?: string[];
/** 视频URL */
videoUrl?: string;
/** 语音URL */
audioUrl?: string;
/** 完成内容/阅读心得 */
content?: string;
/** 提交时间 */
submittedAt?: string;
/** 评价时间 */
reviewedAt?: string;
feedback?: TaskFeedbackResponse;
}

View File

@ -16,12 +16,20 @@ export interface TaskCreateRequest {
description?: string; description?: string;
/** 任务类型reading-阅读homework-作业activity-活动 */ /** 任务类型reading-阅读homework-作业activity-活动 */
type?: string; type?: string;
/** 任务类型(前端兼容 taskType */
taskType?: string;
/** 关联绘本名称(手动填写) */
relatedBookName?: string;
/** 课程 ID */ /** 课程 ID */
courseId?: number; courseId?: number;
/** 课程 ID前端兼容 relatedCourseId */
relatedCourseId?: number;
/** 开始日期 */ /** 开始日期 */
startDate?: string; startDate?: string;
/** 截止日期 */ /** 截止日期 */
dueDate?: string; dueDate?: string;
/** 截止日期(前端兼容 endDate */
endDate?: string;
/** 附件JSON 数组) */ /** 附件JSON 数组) */
attachments?: string; attachments?: string;
/** 目标类型class-班级student-学生 */ /** 目标类型class-班级student-学生 */

View File

@ -0,0 +1,27 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface TaskFeedbackRequest {
/** 评价结果EXCELLENT-优秀/PASSED-通过/NEEDS_WORK-需改进 */
result: string;
/**
* 1-5
* @minimum 1
* @maximum 5
*/
rating?: number;
/**
*
* @minLength 0
* @maxLength 500
*/
comment?: string;
}

View File

@ -0,0 +1,33 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface TaskFeedbackResponse {
/** 评价ID */
id?: number;
/** 完成记录ID */
completionId?: number;
/** 评价结果EXCELLENT/PASSED/NEEDS_WORK */
result?: string;
/** 评价结果文本:优秀/通过/需改进 */
resultText?: string;
/** 评分 1-5 */
rating?: number;
/** 评语 */
comment?: string;
/** 教师ID */
teacherId?: number;
/** 教师姓名 */
teacherName?: string;
/** 教师头像 */
teacherAvatar?: string;
/** 评价时间 */
createdAt?: string;
}

View File

@ -0,0 +1,29 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { CourseInfo } from './courseInfo';
/**
*
*/
export interface TaskInfo {
/** 任务ID */
id?: number;
/** 任务标题 */
title?: string;
/** 任务描述 */
description?: string;
/** 任务类型 */
taskType?: string;
/** 开始日期 */
startDate?: string;
/** 截止日期 */
endDate?: string;
/** 关联绘本名称 */
relatedBookName?: string;
course?: CourseInfo;
}

View File

@ -20,6 +20,8 @@ export interface TaskResponse {
description?: string; description?: string;
/** 任务类型 */ /** 任务类型 */
type?: string; type?: string;
/** 关联绘本名称 */
relatedBookName?: string;
/** 课程 ID */ /** 课程 ID */
courseId?: number; courseId?: number;
/** 创建人 ID */ /** 创建人 ID */
@ -34,6 +36,10 @@ export interface TaskResponse {
status?: string; status?: string;
/** 附件 */ /** 附件 */
attachments?: string; attachments?: string;
/** 目标类型CLASS-班级STUDENT-学生 */
targetType?: string;
/** 目标 IDs班级或学生 */
targetIds?: number[];
/** 创建时间 */ /** 创建时间 */
createdAt?: string; createdAt?: string;
/** 更新时间 */ /** 更新时间 */

View File

@ -0,0 +1,31 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export interface TaskSubmitRequest {
/** 学生ID */
studentId?: number;
/**
* URL数组9
* @minItems 0
* @maxItems 9
*/
photos?: string[];
/** 视频URL */
videoUrl?: string;
/** 语音URL */
audioUrl?: string;
/**
* /
* @minLength 0
* @maxLength 1000
*/
content?: string;
}

View File

@ -23,9 +23,9 @@ export interface TaskTemplateCreateRequest {
/** 默认持续时间 (天) */ /** 默认持续时间 (天) */
defaultDuration?: number; defaultDuration?: number;
/** 是否默认模板 */ /** 是否默认模板 */
isDefault?: number; isDefault?: boolean;
/** 模板内容 */ /** 模板内容 */
content?: string; content?: string;
/** 是否公开 */ /** 是否公开 */
isPublic?: number; isPublic?: boolean;
} }

View File

@ -16,6 +16,14 @@ export interface TaskUpdateRequest {
description?: string; description?: string;
/** 任务类型 */ /** 任务类型 */
type?: string; type?: string;
/** 任务类型(前端兼容 taskType */
taskType?: string;
/** 关联绘本名称 */
relatedBookName?: string;
/** 课程 ID */
courseId?: number;
/** 课程 ID前端兼容 relatedCourseId */
relatedCourseId?: number;
/** 开始日期 */ /** 开始日期 */
startDate?: string; startDate?: string;
/** 截止日期 */ /** 截止日期 */
@ -24,4 +32,8 @@ export interface TaskUpdateRequest {
status?: string; status?: string;
/** 附件JSON 数组) */ /** 附件JSON 数组) */
attachments?: string; attachments?: string;
/** 目标类型CLASS-班级STUDENT-学生 */
targetType?: string;
/** 目标 IDs */
targetIds?: number[];
} }

View File

@ -0,0 +1,33 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
import type { TaskFeedbackResponse } from './taskFeedbackResponse';
import type { TaskInfo } from './taskInfo';
/**
*
*/
export interface TaskWithCompletionResponse {
/** 任务ID */
id?: number;
/** 完成状态PENDING-待提交/SUBMITTED-已提交/REVIEWED-已评价 */
status?: string;
/** 提交时间 */
submittedAt?: string;
/** 评价时间 */
reviewedAt?: string;
/** 照片URL列表 */
photos?: string[];
/** 视频URL */
videoUrl?: string;
/** 语音URL */
audioUrl?: string;
/** 阅读心得/完成内容 */
content?: string;
teacherFeedback?: TaskFeedbackResponse;
task?: TaskInfo;
}

View File

@ -10,18 +10,20 @@
* *
*/ */
export interface TeacherCreateRequest { export interface TeacherCreateRequest {
/** 用户名 */ /** 用户名/登录账号 */
username: string; username: string;
/** 密码 */ /** 密码 */
password: string; password: string;
/** 姓名 */ /** 姓名 */
name: string; name: string;
/** 电话 */ /** 电话 */
phone?: string; phone: string;
/** 邮箱 */ /** 邮箱 */
email?: string; email?: string;
/** 性别 */ /** 性别 */
gender?: string; gender?: string;
/** 简介 */ /** 简介 */
bio?: string; bio?: string;
/** 负责班级ID列表 */
classIds?: number[];
} }

View File

@ -5,6 +5,7 @@
* Reading Platform Backend Service API Documentation * Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0 * OpenAPI spec version: 1.0.0
*/ */
import type { TeacherResponseClassNames } from './teacherResponseClassNames';
/** /**
* *
@ -14,8 +15,6 @@ export interface TeacherResponse {
id?: number; id?: number;
/** 租户 ID */ /** 租户 ID */
tenantId?: number; tenantId?: number;
/** 用户名 */
username?: string;
/** 姓名 */ /** 姓名 */
name?: string; name?: string;
/** 电话 */ /** 电话 */
@ -30,10 +29,18 @@ export interface TeacherResponse {
bio?: string; bio?: string;
/** 状态 */ /** 状态 */
status?: string; status?: string;
/** 负责班级ID列表 */
classIds?: number[];
/** 负责班级名称 */
classNames?: TeacherResponseClassNames;
/** 授课次数 */
lessonCount?: number;
/** 最后登录时间 */ /** 最后登录时间 */
lastLoginAt?: string; lastLoginAt?: string;
/** 创建时间 */ /** 创建时间 */
createdAt?: string; createdAt?: string;
/** 更新时间 */ /** 更新时间 */
updatedAt?: string; updatedAt?: string;
/** 登录账号 */
loginAccount?: string;
} }

View File

@ -0,0 +1,12 @@
/**
* Generated by orval v8.5.3 🍺
* Do not edit manually.
* Reading Platform API
* Reading Platform Backend Service API Documentation
* OpenAPI spec version: 1.0.0
*/
/**
*
*/
export type TeacherResponseClassNames = { [key: string]: unknown };

View File

@ -24,4 +24,6 @@ export interface TeacherUpdateRequest {
bio?: string; bio?: string;
/** 状态 */ /** 状态 */
status?: string; status?: string;
/** 负责班级ID列表 */
classIds?: number[];
} }

View File

@ -26,8 +26,6 @@ export interface TenantCreateRequest {
address?: string; address?: string;
/** Logo URL */ /** Logo URL */
logoUrl?: string; logoUrl?: string;
/** 套餐类型 */
packageType?: string;
/** 教师配额 */ /** 教师配额 */
teacherQuota?: number; teacherQuota?: number;
/** 学生配额 */ /** 学生配额 */
@ -36,6 +34,6 @@ export interface TenantCreateRequest {
startDate?: string; startDate?: string;
/** 结束日期 */ /** 结束日期 */
expireDate?: string; expireDate?: string;
/** 课程套餐 ID(可选) */ /** 课程套餐 ID 列表(可选,支持多选) */
collectionId?: number; collectionIds?: number[];
} }

View File

@ -36,8 +36,8 @@ export interface TenantResponse {
maxStudents?: number; maxStudents?: number;
/** 最大教师数 */ /** 最大教师数 */
maxTeachers?: number; maxTeachers?: number;
/** 套餐类型 */ /** 套餐名称列表 */
packageType?: string; packageNames?: string[];
/** 教师配额 */ /** 教师配额 */
teacherQuota?: number; teacherQuota?: number;
/** 学生配额 */ /** 学生配额 */

View File

@ -24,10 +24,8 @@ export interface TenantUpdateRequest {
logoUrl?: string; logoUrl?: string;
/** 状态 */ /** 状态 */
status?: string; status?: string;
/** 套餐类型 */
packageType?: string;
/** 课程套餐ID用于三层架构 */ /** 课程套餐ID用于三层架构 */
collectionId?: number; collectionIds?: number[];
/** 教师配额 */ /** 教师配额 */
teacherQuota?: number; teacherQuota?: number;
/** 学生配额 */ /** 学生配额 */

View File

@ -92,6 +92,7 @@ export interface TaskSubmitRequest {
// 带完成信息的任务(兼容旧接口) // 带完成信息的任务(兼容旧接口)
export interface TaskWithCompletion { export interface TaskWithCompletion {
id: number; id: number;
studentId?: number; // 学生ID多孩子聚合时用于标识任务归属
status: TaskCompletionStatus; status: TaskCompletionStatus;
completedAt?: string; completedAt?: string;
feedback?: string; feedback?: string;
@ -154,23 +155,41 @@ export const getChildProfile = (childId: number): Promise<ChildProfile> =>
export const getChildLessons = ( export const getChildLessons = (
childId: number, childId: number,
params?: { pageNum?: number; pageSize?: number } params?: { pageNum?: number; pageSize?: number; page?: number }
): Promise<{ items: LessonRecord[]; total: number; page: number; pageSize: number }> => ): Promise<{ items: LessonRecord[]; total: number; page: number; pageSize: number }> =>
http.get(`/v1/parent/children/${childId}/lessons`, { params }); http.get<{ list: LessonRecord[]; total: number; pageNum: number; pageSize: number }>(`/v1/parent/children/${childId}/lessons`, {
params: { pageNum: params?.pageNum ?? params?.page ?? 1, pageSize: params?.pageSize ?? 10 },
}).then(res => ({
items: res.list || [],
total: res.total || 0,
page: res.pageNum || 1,
pageSize: res.pageSize || 10,
}));
// ==================== 任务 API ==================== // ==================== 任务 API ====================
/** 获取我的任务列表(聚合多孩子任务,无 childId 时使用) */
export const getMyTasks = (
params?: { pageNum?: number; pageSize?: number; status?: string }
): Promise<{ items: TaskWithCompletion[]; total: number; page: number; pageSize: number }> =>
http.get<{ list: TaskWithCompletion[]; total: number; pageNum: number; pageSize: number }>('/v1/parent/tasks', { params }).then(res => ({
items: res.list || [],
total: res.total || 0,
page: res.pageNum || 1,
pageSize: res.pageSize || 10,
}));
/** 获取指定孩子的任务列表 */
export const getChildTasks = ( export const getChildTasks = (
childId: number, childId: number,
params?: { pageNum?: number; pageSize?: number; status?: string } params?: { pageNum?: number; pageSize?: number; status?: string }
): Promise<{ items: TaskWithCompletion[]; total: number; page: number; pageSize: number }> => ): Promise<{ items: TaskWithCompletion[]; total: number; page: number; pageSize: number }> =>
http.get<{ list: TaskWithCompletion[]; total: number; pageNum: number; pageSize: number }>(`/v1/parent/tasks/student/${childId}`, { params }) http.get<{ list: TaskWithCompletion[]; total: number; pageNum: number; pageSize: number }>(`/v1/parent/tasks/student/${childId}`, { params }).then(res => ({
.then(res => ({ items: res.list || [],
items: res.list || [], total: res.total || 0,
total: res.total || 0, page: res.pageNum || 1,
page: res.pageNum || 1, pageSize: res.pageSize || 10,
pageSize: res.pageSize || 10, }));
}));
// 提交任务完成(新版) // 提交任务完成(新版)
export const submitTaskCompletion = ( export const submitTaskCompletion = (
@ -233,8 +252,18 @@ export const getNotifications = (
unreadCount: number; unreadCount: number;
page: number; page: number;
pageSize: number; pageSize: number;
}> => }> => {
http.get<{ list: Notification[]; total: number; pageNum: number; pageSize: number }>('/v1/parent/notifications', { params }) const queryParams: Record<string, any> = {
pageNum: params?.pageNum ?? 1,
pageSize: params?.pageSize ?? 10,
};
if (params?.isRead !== undefined) {
queryParams.isRead = params.isRead ? 1 : 0;
}
if (params?.notificationType) {
queryParams.notificationType = params.notificationType;
}
return http.get<{ list: Notification[]; total: number; pageNum: number; pageSize: number }>('/v1/parent/notifications', { params: queryParams })
.then(res => ({ .then(res => ({
items: res.list || [], items: res.list || [],
total: res.total || 0, total: res.total || 0,
@ -242,6 +271,7 @@ export const getNotifications = (
page: res.pageNum || 1, page: res.pageNum || 1,
pageSize: res.pageSize || 10, pageSize: res.pageSize || 10,
})); }));
};
export const getUnreadCount = (): Promise<number> => export const getUnreadCount = (): Promise<number> =>
http.get<number>('/v1/parent/notifications/unread-count').then(res => res || 0); http.get<number>('/v1/parent/notifications/unread-count').then(res => res || 0);

View File

@ -1260,8 +1260,13 @@ export const getParents = (params?: ParentQueryParams) =>
export const getParent = (id: number) => export const getParent = (id: number) =>
http.get<Parent>(`/v1/school/parents/${id}`); http.get<Parent>(`/v1/school/parents/${id}`);
export const createParent = (data: CreateParentDto) => export const createParent = (data: CreateParentDto) => {
http.post<Parent>('/v1/school/parents', data); const { loginAccount, ...rest } = data;
return http.post<Parent>('/v1/school/parents', {
...rest,
username: loginAccount,
});
};
export const updateParent = (id: number, data: UpdateParentDto) => export const updateParent = (id: number, data: UpdateParentDto) =>
http.put<Parent>(`/v1/school/parents/${id}`, data); http.put<Parent>(`/v1/school/parents/${id}`, data);
@ -1273,8 +1278,8 @@ export const resetParentPassword = (id: number) =>
http.post<{ tempPassword: string }>(`/v1/school/parents/${id}/reset-password`); http.post<{ tempPassword: string }>(`/v1/school/parents/${id}/reset-password`);
export const getParentChildren = async (parentId: number): Promise<ParentChild[]> => { export const getParentChildren = async (parentId: number): Promise<ParentChild[]> => {
const parent = await http.get<Parent & { children: ParentChild[] }>(`/v1/school/parents/${parentId}`); const list = await http.get<ParentChild[]>(`/v1/school/parents/${parentId}/children`);
return parent.children || []; return Array.isArray(list) ? list : [];
}; };
export const addChildToParent = (parentId: number, data: AddChildDto) => export const addChildToParent = (parentId: number, data: AddChildDto) =>

View File

@ -122,6 +122,7 @@ export function getTeacherStudents(params?: { pageNum?: number; pageSize?: numbe
} }
// 获取班级学生列表 // 获取班级学生列表
// 后端返回 { list, total, pageNum, pageSize, classInfo },前端统一为 { items, total, page, pageSize, class }
export function getTeacherClassStudents(classId: number, params?: { pageNum?: number; pageSize?: number; keyword?: string }): Promise<{ export function getTeacherClassStudents(classId: number, params?: { pageNum?: number; pageSize?: number; keyword?: string }): Promise<{
items: Array<{ items: Array<{
id: number; id: number;
@ -151,7 +152,13 @@ export function getTeacherClassStudents(classId: number, params?: { pageNum?: nu
pageSize: params?.pageSize, pageSize: params?.pageSize,
keyword: params?.keyword, keyword: params?.keyword,
}, },
}) as any; }).then((res: any) => ({
items: res?.list ?? res?.items ?? [],
total: res?.total ?? 0,
page: res?.pageNum ?? res?.page ?? 1,
pageSize: res?.pageSize ?? 10,
class: res?.classInfo ?? res?.class,
}));
} }
// 获取班级教师列表 // 获取班级教师列表

View File

@ -11,9 +11,15 @@
<p>关注孩子的阅读成长陪伴每一步进步</p> <p>关注孩子的阅读成长陪伴每一步进步</p>
</div> </div>
<div class="banner-decorations"> <div class="banner-decorations">
<span class="decoration"><BookOutlined /></span> <span class="decoration">
<span class="decoration"><StarOutlined /></span> <BookOutlined />
<span class="decoration"><HeartOutlined /></span> </span>
<span class="decoration">
<StarOutlined />
</span>
<span class="decoration">
<HeartOutlined />
</span>
</div> </div>
</div> </div>
</div> </div>
@ -22,15 +28,12 @@
<!-- 我的孩子 --> <!-- 我的孩子 -->
<div class="section-card"> <div class="section-card">
<div class="section-header"> <div class="section-header">
<h3><TeamOutlined /> 我的孩子</h3> <h3>
<TeamOutlined /> 我的孩子
</h3>
</div> </div>
<div class="children-grid" v-if="children.length > 0"> <div class="children-grid" v-if="children.length > 0">
<div <div v-for="child in children" :key="child.id" class="child-card" @click="goToChildDetail(child.id)">
v-for="child in children"
:key="child.id"
class="child-card"
@click="goToChildDetail(child.id)"
>
<div class="child-avatar"> <div class="child-avatar">
<a-avatar :size="isMobile ? 56 : 64" :style="{ backgroundColor: getAvatarColor(child.id) }"> <a-avatar :size="isMobile ? 56 : 64" :style="{ backgroundColor: getAvatarColor(child.id) }">
{{ child.name.charAt(0) }} {{ child.name.charAt(0) }}
@ -43,8 +46,12 @@
</div> </div>
<div class="child-class">{{ child.class?.name || '未分班' }}</div> <div class="child-class">{{ child.class?.name || '未分班' }}</div>
<div class="child-stats"> <div class="child-stats">
<span><BookOutlined /> {{ child.readingCount }} 次阅读</span> <span>
<span><ReadOutlined /> {{ child.lessonCount }} 节课</span> <BookOutlined /> {{ child.readingCount }} 次阅读
</span>
<span>
<ReadOutlined /> {{ child.lessonCount }} 节课
</span>
</div> </div>
</div> </div>
<div class="card-arrow"> <div class="card-arrow">
@ -62,11 +69,13 @@
<!-- 最近任务 --> <!-- 最近任务 -->
<div class="section-card"> <div class="section-card">
<div class="section-header"> <div class="section-header">
<h3><CheckSquareOutlined /> 最近任务</h3> <h3>
<CheckSquareOutlined /> 最近任务
</h3>
<a-button type="link" @click="goToTasks" class="view-all-btn">查看全部</a-button> <a-button type="link" @click="goToTasks" class="view-all-btn">查看全部</a-button>
</div> </div>
<div class="task-list" v-if="recentTasks.length > 0"> <div class="task-list" v-if="recentTasks.length > 0">
<div v-for="task in recentTasks" :key="task.id" class="task-item"> <div v-for="task in recentTasks" :key="`${task.id}-${task.studentId ?? ''}`" class="task-item">
<div class="task-status" :class="getStatusClass(task.status)"> <div class="task-status" :class="getStatusClass(task.status)">
{{ getStatusText(task.status) }} {{ getStatusText(task.status) }}
</div> </div>
@ -87,7 +96,9 @@
<!-- 成长档案 --> <!-- 成长档案 -->
<div class="section-card"> <div class="section-card">
<div class="section-header"> <div class="section-header">
<h3><FileImageOutlined /> 成长档案</h3> <h3>
<FileImageOutlined /> 成长档案
</h3>
<a-button type="link" @click="goToGrowth" class="view-all-btn">查看全部</a-button> <a-button type="link" @click="goToGrowth" class="view-all-btn">查看全部</a-button>
</div> </div>
<div class="growth-list" v-if="recentGrowth.length > 0"> <div class="growth-list" v-if="recentGrowth.length > 0">
@ -126,7 +137,7 @@ import {
RightOutlined, RightOutlined,
InboxOutlined, InboxOutlined,
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { getChildren, type ChildInfo } from '@/api/parent'; import { getChildren, getMyTasks, getChildGrowthRecords, type ChildInfo, type TaskWithCompletion } from '@/api/parent';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const router = useRouter(); const router = useRouter();
@ -153,8 +164,8 @@ const getRelationshipText = (relationship: string) => {
const statusMap: Record<string, { text: string; class: string }> = { const statusMap: Record<string, { text: string; class: string }> = {
PENDING: { text: '待完成', class: 'status-pending' }, PENDING: { text: '待完成', class: 'status-pending' },
IN_PROGRESS: { text: '进行中', class: 'status-progress' }, SUBMITTED: { text: '已提交', class: 'status-progress' },
COMPLETED: { text: '已完成', class: 'status-completed' }, REVIEWED: { text: '已评价', class: 'status-completed' },
}; };
const getStatusText = (status: string) => statusMap[status]?.text || status; const getStatusText = (status: string) => statusMap[status]?.text || status;
@ -171,11 +182,13 @@ const goToChildDetail = (childId: number) => {
}; };
const goToTasks = () => { const goToTasks = () => {
router.push('/parent/tasks'); router.push('/parent/tasks'); // childId
}; };
const goToGrowth = () => { const goToGrowth = () => {
router.push('/parent/growth'); //
const childId = children.value[0]?.id;
router.push(childId ? `/parent/growth?childId=${childId}` : '/parent/growth');
}; };
const loadData = async () => { const loadData = async () => {
@ -184,9 +197,16 @@ const loadData = async () => {
const data = await getChildren(); const data = await getChildren();
children.value = data; children.value = data;
// //
const tasksRes = await getMyTasks({ pageNum: 1, pageSize: 5 });
recentTasks.value = tasksRes.items || [];
//
if (data.length > 0) { if (data.length > 0) {
// const growthRes = await getChildGrowthRecords(data[0].id, { pageNum: 1, pageSize: 4 });
recentGrowth.value = growthRes.items || [];
} else {
recentGrowth.value = [];
} }
} catch (error: any) { } catch (error: any) {
message.error(error.response?.data?.message || '加载数据失败'); message.error(error.response?.data?.message || '加载数据失败');
@ -212,6 +232,7 @@ $primary-light: #f6ffed;
$primary-dark: #389e0d; $primary-dark: #389e0d;
.parent-dashboard { .parent-dashboard {
// //
.welcome-banner { .welcome-banner {
background: linear-gradient(135deg, $primary-color 0%, #73d13d 100%); background: linear-gradient(135deg, $primary-color 0%, #73d13d 100%);
@ -550,6 +571,7 @@ $primary-dark: #389e0d;
.task-content, .task-content,
.growth-content { .growth-content {
.task-title, .task-title,
.growth-title { .growth-title {
font-size: 13px; font-size: 13px;

View File

@ -1,14 +1,8 @@
<template> <template>
<a-layout :class="['parent-layout', { 'is-collapsed': collapsed }]"> <a-layout :class="['parent-layout', { 'is-collapsed': collapsed }]">
<!-- 桌面端侧边栏 --> <!-- 桌面端侧边栏 -->
<a-layout-sider <a-layout-sider v-if="!isMobile" v-model:collapsed="collapsed" :trigger="null" collapsible class="parent-sider"
v-if="!isMobile" :width="220">
v-model:collapsed="collapsed"
:trigger="null"
collapsible
class="parent-sider"
:width="220"
>
<div class="logo"> <div class="logo">
<img src="/logo.png" alt="Logo" class="logo-img" /> <img src="/logo.png" alt="Logo" class="logo-img" />
<div v-if="!collapsed" class="logo-text"> <div v-if="!collapsed" class="logo-text">
@ -18,47 +12,45 @@
</div> </div>
<div class="sider-menu-wrapper"> <div class="sider-menu-wrapper">
<a-menu <a-menu v-model:selectedKeys="selectedKeys" mode="inline" theme="light" :inline-collapsed="collapsed"
v-model:selectedKeys="selectedKeys" @click="handleMenuClick" class="side-menu">
mode="inline" <a-menu-item key="dashboard">
theme="light" <template #icon>
:inline-collapsed="collapsed" <HomeOutlined />
@click="handleMenuClick" </template>
class="side-menu" <span>首页</span>
> </a-menu-item>
<a-menu-item key="dashboard"> <a-menu-item key="children">
<template #icon><HomeOutlined /></template> <template #icon>
<span>首页</span> <TeamOutlined />
</a-menu-item> </template>
<a-menu-item key="children"> <span>我的孩子</span>
<template #icon><TeamOutlined /></template> </a-menu-item>
<span>我的孩子</span> <a-menu-item key="lessons">
</a-menu-item> <template #icon>
<a-menu-item key="lessons"> <BookOutlined />
<template #icon><BookOutlined /></template> </template>
<span>阅读记录</span> <span>阅读记录</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="tasks"> <a-menu-item key="tasks">
<template #icon><CheckSquareOutlined /></template> <template #icon>
<span>阅读任务</span> <CheckSquareOutlined />
</a-menu-item> </template>
<a-menu-item key="growth"> <span>阅读任务</span>
<template #icon><FileImageOutlined /></template> </a-menu-item>
<span>成长档案</span> <a-menu-item key="growth">
</a-menu-item> <template #icon>
</a-menu> <FileImageOutlined />
</template>
<span>成长档案</span>
</a-menu-item>
</a-menu>
</div> </div>
</a-layout-sider> </a-layout-sider>
<!-- 移动端抽屉菜单 --> <!-- 移动端抽屉菜单 -->
<a-drawer <a-drawer v-if="isMobile" v-model:open="drawerVisible" placement="left" :closable="false" :width="260"
v-if="isMobile" class="mobile-drawer">
v-model:open="drawerVisible"
placement="left"
:closable="false"
:width="260"
class="mobile-drawer"
>
<template #title> <template #title>
<div class="drawer-header"> <div class="drawer-header">
<img src="/logo.png" alt="Logo" class="drawer-logo" /> <img src="/logo.png" alt="Logo" class="drawer-logo" />
@ -68,35 +60,42 @@
</div> </div>
</div> </div>
</template> </template>
<a-menu <a-menu v-model:selectedKeys="selectedKeys" mode="inline" theme="light" @click="handleMobileMenuClick"
v-model:selectedKeys="selectedKeys" class="drawer-menu">
mode="inline"
theme="light"
@click="handleMobileMenuClick"
class="drawer-menu"
>
<a-menu-item key="dashboard"> <a-menu-item key="dashboard">
<template #icon><HomeOutlined /></template> <template #icon>
<HomeOutlined />
</template>
<span>首页</span> <span>首页</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="children"> <a-menu-item key="children">
<template #icon><TeamOutlined /></template> <template #icon>
<TeamOutlined />
</template>
<span>我的孩子</span> <span>我的孩子</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="lessons"> <a-menu-item key="lessons">
<template #icon><BookOutlined /></template> <template #icon>
<BookOutlined />
</template>
<span>阅读记录</span> <span>阅读记录</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="tasks"> <a-menu-item key="tasks">
<template #icon><CheckSquareOutlined /></template> <template #icon>
<CheckSquareOutlined />
</template>
<span>阅读任务</span> <span>阅读任务</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="growth"> <a-menu-item key="growth">
<template #icon><FileImageOutlined /></template> <template #icon>
<FileImageOutlined />
</template>
<span>成长档案</span> <span>成长档案</span>
</a-menu-item> </a-menu-item>
<a-menu-item key="profile"> <a-menu-item key="profile">
<template #icon><UserOutlined /></template> <template #icon>
<UserOutlined />
</template>
<span>个人信息</span> <span>个人信息</span>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@ -112,16 +111,8 @@
<!-- 桌面端顶部栏 --> <!-- 桌面端顶部栏 -->
<a-layout-header v-if="!isMobile" class="parent-header"> <a-layout-header v-if="!isMobile" class="parent-header">
<div class="header-left"> <div class="header-left">
<MenuUnfoldOutlined <MenuUnfoldOutlined v-if="collapsed" class="trigger" @click="collapsed = !collapsed" />
v-if="collapsed" <MenuFoldOutlined v-else class="trigger" @click="collapsed = !collapsed" />
class="trigger"
@click="collapsed = !collapsed"
/>
<MenuFoldOutlined
v-else
class="trigger"
@click="collapsed = !collapsed"
/>
</div> </div>
<div class="header-right"> <div class="header-right">
@ -129,7 +120,9 @@
<a-dropdown> <a-dropdown>
<a-space class="user-info" style="cursor: pointer;"> <a-space class="user-info" style="cursor: pointer;">
<a-avatar :size="32" class="user-avatar"> <a-avatar :size="32" class="user-avatar">
<template #icon><UserOutlined /></template> <template #icon>
<UserOutlined />
</template>
</a-avatar> </a-avatar>
<span class="user-name">{{ userName }}</span> <span class="user-name">{{ userName }}</span>
<DownOutlined /> <DownOutlined />
@ -166,12 +159,8 @@
<!-- 移动端底部导航 --> <!-- 移动端底部导航 -->
<div v-if="isMobile" class="mobile-bottom-nav"> <div v-if="isMobile" class="mobile-bottom-nav">
<div <div v-for="nav in navItems" :key="nav.key" :class="['nav-item', { active: selectedKeys[0] === nav.key }]"
v-for="nav in navItems" @click="handleNavClick(nav.key)">
:key="nav.key"
:class="['nav-item', { active: selectedKeys[0] === nav.key }]"
@click="handleNavClick(nav.key)"
>
<component :is="nav.icon" class="nav-icon" /> <component :is="nav.icon" class="nav-icon" />
<span class="nav-text">{{ nav.text }}</span> <span class="nav-text">{{ nav.text }}</span>
</div> </div>

View File

@ -1,8 +1,23 @@
<template> <template>
<div class="growth-record-view"> <div class="growth-record-view">
<div class="page-header"> <div class="page-header">
<h2><FileImageOutlined /> 成长档案</h2> <div class="header-left">
<p class="page-desc">记录孩子成长的每一个精彩瞬间</p> <h2>
<FileImageOutlined /> 成长档案
</h2>
<p class="page-desc">记录孩子成长的每一个精彩瞬间</p>
</div>
<a-select
v-if="children.length > 1"
v-model:value="selectedChildId"
placeholder="选择孩子"
style="width: 140px;"
@change="onChildChange"
>
<a-select-option v-for="c in children" :key="c.id" :value="c.id">
{{ c.name }}
</a-select-option>
</a-select>
</div> </div>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
@ -22,7 +37,9 @@
<h3>{{ record.title }}</h3> <h3>{{ record.title }}</h3>
<p v-if="record.content">{{ record.content }}</p> <p v-if="record.content">{{ record.content }}</p>
<div class="card-meta"> <div class="card-meta">
<span><CalendarOutlined /> {{ formatDate(record.recordDate) }}</span> <span>
<CalendarOutlined /> {{ formatDate(record.recordDate) }}
</span>
<span v-if="record.class"> <span v-if="record.class">
<TeamOutlined /> {{ record.class.name }} <TeamOutlined /> {{ record.class.name }}
</span> </span>
@ -38,18 +55,13 @@
</a-spin> </a-spin>
<div class="pagination-section" v-if="total > pageSize"> <div class="pagination-section" v-if="total > pageSize">
<a-pagination <a-pagination v-model:current="currentPage" :total="total" :page-size="pageSize" @change="onPageChange" />
v-model:current="currentPage"
:total="total"
:page-size="pageSize"
@change="onPageChange"
/>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { import {
@ -59,7 +71,7 @@ import {
TeamOutlined, TeamOutlined,
InboxOutlined, InboxOutlined,
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { getChildGrowthRecords, getChildren, type GrowthRecord } from '@/api/parent'; import { getChildGrowthRecords, getChildren, type GrowthRecord, type ChildInfo } from '@/api/parent';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const route = useRoute(); const route = useRoute();
@ -69,7 +81,9 @@ const records = ref<GrowthRecord[]>([]);
const total = ref(0); const total = ref(0);
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(12); const pageSize = ref(12);
const children = ref<ChildInfo[]>([]);
const currentChildId = ref<number | null>(null); const currentChildId = ref<number | null>(null);
const selectedChildId = ref<number | null>(null);
const formatDate = (date: string) => dayjs(date).format('YYYY-MM-DD'); const formatDate = (date: string) => dayjs(date).format('YYYY-MM-DD');
@ -78,25 +92,32 @@ const onPageChange = (page: number) => {
loadRecords(); loadRecords();
}; };
const onChildChange = (val: number) => {
router.replace({ query: { childId: String(val) } });
currentPage.value = 1;
loadRecords();
};
const loadRecords = async () => { const loadRecords = async () => {
loading.value = true; loading.value = true;
try { try {
let childId = route.query.childId ? Number(route.query.childId) : null; let childId = route.query.childId ? Number(route.query.childId) : null;
// childId //
if (!childId) { children.value = await getChildren();
const children = await getChildren(); if (children.value.length === 0) {
if (children && children.length > 0) { records.value = [];
childId = children[0].id; total.value = 0;
router.replace({ query: { childId: String(childId) } }); loading.value = false;
} else { return;
records.value = [];
total.value = 0;
loading.value = false;
return;
}
} }
// childId使
if (!childId) {
childId = children.value[0].id;
router.replace({ query: { childId: String(childId) } });
}
selectedChildId.value = childId;
currentChildId.value = childId; currentChildId.value = childId;
const data = await getChildGrowthRecords(childId, { const data = await getChildGrowthRecords(childId, {
@ -115,6 +136,8 @@ const loadRecords = async () => {
} }
}; };
watch(() => route.query.childId, () => loadRecords());
onMounted(() => { onMounted(() => {
loadRecords(); loadRecords();
}); });
@ -127,6 +150,15 @@ $primary-light: #f6ffed;
.growth-record-view { .growth-record-view {
.page-header { .page-header {
margin-bottom: 24px; margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: 16px;
.header-left {
flex: 1;
}
h2 { h2 {
margin: 0 0 8px; margin: 0 0 8px;

View File

@ -1,8 +1,23 @@
<template> <template>
<div class="lesson-history-view"> <div class="lesson-history-view">
<div class="page-header"> <div class="page-header">
<h2><BookOutlined /> 阅读记录</h2> <div class="header-left">
<p class="page-desc">查看孩子的阅读学习记录</p> <h2>
<BookOutlined /> 阅读记录
</h2>
<p class="page-desc">查看孩子的阅读学习记录</p>
</div>
<a-select
v-if="children.length > 1"
v-model:value="selectedChildId"
placeholder="选择孩子"
style="width: 140px;"
@change="onChildChange"
>
<a-select-option v-for="c in children" :key="c.id" :value="c.id">
{{ c.name }}
</a-select-option>
</a-select>
</div> </div>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
@ -61,18 +76,13 @@
</a-spin> </a-spin>
<div class="pagination-section" v-if="total > pageSize"> <div class="pagination-section" v-if="total > pageSize">
<a-pagination <a-pagination v-model:current="currentPage" :total="total" :page-size="pageSize" @change="onPageChange" />
v-model:current="currentPage"
:total="total"
:page-size="pageSize"
@change="onPageChange"
/>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { import {
@ -82,7 +92,7 @@ import {
FileTextOutlined, FileTextOutlined,
InboxOutlined, InboxOutlined,
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { getChildLessons, getChildren, type LessonRecord } from '@/api/parent'; import { getChildLessons, getChildren, type LessonRecord, type ChildInfo } from '@/api/parent';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
const route = useRoute(); const route = useRoute();
@ -92,7 +102,9 @@ const records = ref<LessonRecord[]>([]);
const total = ref(0); const total = ref(0);
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
const children = ref<ChildInfo[]>([]);
const currentChildId = ref<number | null>(null); const currentChildId = ref<number | null>(null);
const selectedChildId = ref<number | null>(null);
const formatDateTime = (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm'); const formatDateTime = (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm');
@ -101,23 +113,32 @@ const onPageChange = (page: number) => {
loadRecords(); loadRecords();
}; };
const onChildChange = (val: number) => {
router.replace({ query: { childId: String(val) } });
currentPage.value = 1;
loadRecords();
};
const loadRecords = async () => { const loadRecords = async () => {
loading.value = true; loading.value = true;
try { try {
let childId = route.query.childId ? Number(route.query.childId) : null; let childId = route.query.childId ? Number(route.query.childId) : null;
// childId //
if (!childId) { children.value = await getChildren();
const children = await getChildren(); if (children.value.length === 0) {
if (children && children.length > 0) { records.value = [];
childId = children[0].id; total.value = 0;
router.replace({ query: { childId: String(childId) } }); loading.value = false;
} else { return;
loading.value = false;
return;
}
} }
// childId使
if (!childId) {
childId = children.value[0].id;
router.replace({ query: { childId: String(childId) } });
}
selectedChildId.value = childId;
currentChildId.value = childId; currentChildId.value = childId;
const data = await getChildLessons(childId, { const data = await getChildLessons(childId, {
@ -133,6 +154,8 @@ const loadRecords = async () => {
} }
}; };
watch(() => route.query.childId, () => loadRecords());
onMounted(() => { onMounted(() => {
loadRecords(); loadRecords();
}); });
@ -142,6 +165,15 @@ onMounted(() => {
.lesson-history-view { .lesson-history-view {
.page-header { .page-header {
margin-bottom: 24px; margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: 16px;
.header-left {
flex: 1;
}
h2 { h2 {
margin: 0 0 8px; margin: 0 0 8px;

View File

@ -1,13 +1,30 @@
<template> <template>
<div class="task-list-view"> <div class="task-list-view">
<div class="page-header"> <div class="page-header">
<h2><CheckSquareOutlined /> 阅读任务</h2> <div class="header-left">
<p class="page-desc">查看孩子的阅读任务并提交完成情况</p> <h2>
<CheckSquareOutlined /> 阅读任务
</h2>
<p class="page-desc">查看孩子的阅读任务并提交完成情况</p>
</div>
<a-select
v-if="children.length > 1"
v-model:value="selectedChildId"
placeholder="选择孩子"
style="width: 140px;"
allow-clear
@change="onChildChange"
>
<a-select-option :value="null">全部孩子</a-select-option>
<a-select-option v-for="c in children" :key="c.id" :value="c.id">
{{ c.name }}
</a-select-option>
</a-select>
</div> </div>
<a-spin :spinning="loading"> <a-spin :spinning="loading">
<div class="task-list" v-if="tasks.length > 0"> <div class="task-list" v-if="tasks.length > 0">
<div v-for="task in tasks" :key="task.id" class="task-card"> <div v-for="task in tasks" :key="`${task.id}-${task.studentId ?? ''}`" class="task-card">
<div class="card-header"> <div class="card-header">
<div class="task-info"> <div class="task-info">
<div class="task-title-row"> <div class="task-title-row">
@ -36,7 +53,8 @@
</div> </div>
<!-- 已提交内容预览 --> <!-- 已提交内容预览 -->
<div class="submission-preview" v-if="task.status !== 'PENDING' && (task.photos?.length || task.videoUrl || task.audioUrl || task.content)"> <div class="submission-preview"
v-if="task.status !== 'PENDING' && (task.photos?.length || task.videoUrl || task.audioUrl || task.content)">
<div class="preview-title">已提交内容</div> <div class="preview-title">已提交内容</div>
<div class="preview-items"> <div class="preview-items">
<span v-if="task.photos?.length" class="preview-item"> <span v-if="task.photos?.length" class="preview-item">
@ -108,15 +126,8 @@
</a-spin> </a-spin>
<!-- 提交任务弹窗 --> <!-- 提交任务弹窗 -->
<a-modal <a-modal v-model:open="submitModalVisible" :title="isEditSubmit ? '修改提交' : '提交任务完成'" @ok="handleSubmit"
v-model:open="submitModalVisible" :confirm-loading="submitting" width="600px" okText="提交" cancelText="取消">
:title="isEditSubmit ? '修改提交' : '提交任务完成'"
@ok="handleSubmit"
:confirm-loading="submitting"
width="600px"
okText="提交"
cancelText="取消"
>
<div class="task-info-header" v-if="selectedTask"> <div class="task-info-header" v-if="selectedTask">
<h4>{{ selectedTask.task.title }}</h4> <h4>{{ selectedTask.task.title }}</h4>
<p v-if="selectedTask.task.relatedBookName"> <p v-if="selectedTask.task.relatedBookName">
@ -135,12 +146,7 @@
</div> </div>
</div> </div>
</div> </div>
<a-upload <a-upload :customRequest="handlePhotoUpload" :showUploadList="false" accept="image/*" multiple>
:customRequest="handlePhotoUpload"
:showUploadList="false"
accept="image/*"
multiple
>
<div class="upload-btn"> <div class="upload-btn">
<PlusOutlined /> <PlusOutlined />
<span>添加照片</span> <span>添加照片</span>
@ -151,40 +157,22 @@
</a-form-item> </a-form-item>
<a-form-item label="视频链接"> <a-form-item label="视频链接">
<a-input <a-input v-model:value="submitForm.videoUrl" placeholder="请输入视频链接(可选,如腾讯视频、优酷等分享链接)" allow-clear />
v-model:value="submitForm.videoUrl"
placeholder="请输入视频链接(可选,如腾讯视频、优酷等分享链接)"
allow-clear
/>
</a-form-item> </a-form-item>
<a-form-item label="音频链接"> <a-form-item label="音频链接">
<a-input <a-input v-model:value="submitForm.audioUrl" placeholder="请输入音频链接(可选)" allow-clear />
v-model:value="submitForm.audioUrl"
placeholder="请输入音频链接(可选)"
allow-clear
/>
</a-form-item> </a-form-item>
<a-form-item label="阅读心得"> <a-form-item label="阅读心得">
<a-textarea <a-textarea v-model:value="submitForm.content" placeholder="请描述孩子的阅读情况、感受等(可选)" :rows="4" :maxlength="500"
v-model:value="submitForm.content" show-count />
placeholder="请描述孩子的阅读情况、感受等(可选)"
:rows="4"
:maxlength="500"
show-count
/>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-modal> </a-modal>
<!-- 评价详情弹窗 --> <!-- 评价详情弹窗 -->
<a-modal <a-modal v-model:open="feedbackModalVisible" title="教师评价详情" :footer="null" width="500px">
v-model:open="feedbackModalVisible"
title="教师评价详情"
:footer="null"
width="500px"
>
<div class="feedback-detail" v-if="selectedTask?.teacherFeedback"> <div class="feedback-detail" v-if="selectedTask?.teacherFeedback">
<div class="feedback-result-section"> <div class="feedback-result-section">
<div class="result-label">评价结果</div> <div class="result-label">评价结果</div>
@ -214,7 +202,8 @@
<a-divider>提交内容</a-divider> <a-divider>提交内容</a-divider>
<div class="review-photos" v-if="selectedTask.photos?.length"> <div class="review-photos" v-if="selectedTask.photos?.length">
<div class="photos-grid"> <div class="photos-grid">
<img v-for="(photo, idx) in selectedTask.photos" :key="idx" :src="photo" alt="照片" @click="previewImage(photo)" /> <img v-for="(photo, idx) in selectedTask.photos" :key="idx" :src="photo" alt="照片"
@click="previewImage(photo)" />
</div> </div>
</div> </div>
<div class="review-links" v-if="selectedTask.videoUrl || selectedTask.audioUrl"> <div class="review-links" v-if="selectedTask.videoUrl || selectedTask.audioUrl">
@ -233,19 +222,15 @@
</a-modal> </a-modal>
<!-- 图片预览 --> <!-- 图片预览 -->
<a-image <a-image :style="{ display: 'none' }" :preview="{
:style="{ display: 'none' }" visible: imagePreviewVisible,
:preview="{ onVisibleChange: (visible: boolean) => { imagePreviewVisible = visible; },
visible: imagePreviewVisible, }" :src="previewImageUrl" />
onVisibleChange: (visible: boolean) => { imagePreviewVisible = visible; },
}"
:src="previewImageUrl"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { message, Upload } from 'ant-design-vue'; import { message, Upload } from 'ant-design-vue';
import type { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface'; import type { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
@ -267,10 +252,13 @@ import {
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { import {
getChildTasks, getChildTasks,
getMyTasks,
getChildren,
submitTaskCompletion, submitTaskCompletion,
updateTaskCompletion, updateTaskCompletion,
type TaskWithCompletion, type TaskWithCompletion,
type TaskSubmitRequest, type TaskSubmitRequest,
type ChildInfo,
} from '@/api/parent'; } from '@/api/parent';
import { uploadFile } from '@/api/file'; import { uploadFile } from '@/api/file';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -279,7 +267,9 @@ const route = useRoute();
const router = useRouter(); const router = useRouter();
const loading = ref(false); const loading = ref(false);
const tasks = ref<TaskWithCompletion[]>([]); const tasks = ref<TaskWithCompletion[]>([]);
const children = ref<ChildInfo[]>([]);
const currentChildId = ref<number | null>(null); const currentChildId = ref<number | null>(null);
const selectedChildId = ref<number | null>(null); // null = 使 getMyTasks
// //
const submitModalVisible = ref(false); const submitModalVisible = ref(false);
@ -336,20 +326,21 @@ const openSubmitModal = (task: TaskWithCompletion) => {
submitModalVisible.value = true; submitModalVisible.value = true;
}; };
// // APIphotos URL
const handlePhotoUpload = async (options: UploadRequestOption) => { const handlePhotoUpload = async (options: UploadRequestOption) => {
const { file } = options; const { file } = options;
try { try {
const formData = new FormData(); if (submitForm.photos.length >= 9) {
formData.append('file', file as File);
const url = await uploadFile(formData);
if (submitForm.photos.length < 9) {
submitForm.photos.push(url);
} else {
message.warning('最多上传9张照片'); message.warning('最多上传9张照片');
return;
}
const result = await uploadFile(file as File, 'poster');
const url = result.filePath;
if (url) {
submitForm.photos.push(url);
} }
} catch (error: any) { } catch (error: any) {
message.error('上传失败'); message.error(error?.message || '上传失败');
} }
}; };
@ -362,7 +353,8 @@ const removePhoto = (index: number) => {
const handleSubmit = async () => { const handleSubmit = async () => {
if (!selectedTask.value) return; if (!selectedTask.value) return;
const childId = currentChildId.value || (route.query.childId ? Number(route.query.childId) : null); // 使 studentId
const childId = selectedTask.value?.studentId ?? currentChildId.value ?? (route.query.childId ? Number(route.query.childId) : null);
if (!childId) { if (!childId) {
message.warning('请先选择孩子'); message.warning('请先选择孩子');
return; return;
@ -407,28 +399,43 @@ const previewImage = (url: string) => {
imagePreviewVisible.value = true; imagePreviewVisible.value = true;
}; };
// //
const onChildChange = (val: number | null) => {
if (val) {
router.replace({ query: { childId: String(val) } });
} else {
router.replace({ query: {} });
}
loadTasks();
};
// childId getChildTasks childId getMyTasks
const loadTasks = async () => { const loadTasks = async () => {
loading.value = true; loading.value = true;
try { try {
let childId = route.query.childId ? Number(route.query.childId) : null; let childId = route.query.childId ? Number(route.query.childId) : null;
// childId //
if (!childId) { children.value = await getChildren();
const { getChildren } = await import('@/api/parent'); if (children.value.length === 0) {
const children = await getChildren(); tasks.value = [];
if (children && children.length > 0) { loading.value = false;
childId = children[0].id; return;
router.replace({ query: { childId: String(childId) } });
} else {
loading.value = false;
return;
}
} }
currentChildId.value = childId; selectedChildId.value = childId;
const data = await getChildTasks(childId);
tasks.value = data.items; if (childId) {
// getChildTasks
currentChildId.value = childId;
const data = await getChildTasks(childId);
tasks.value = data.items;
} else {
// getMyTasks
currentChildId.value = children.value[0]?.id ?? null;
const data = await getMyTasks();
tasks.value = data.items;
}
} catch (error: any) { } catch (error: any) {
message.error(error.response?.data?.message || '加载数据失败'); message.error(error.response?.data?.message || '加载数据失败');
} finally { } finally {
@ -436,6 +443,9 @@ const loadTasks = async () => {
} }
}; };
// childId
watch(() => route.query.childId, () => loadTasks());
onMounted(() => { onMounted(() => {
loadTasks(); loadTasks();
}); });
@ -448,6 +458,15 @@ $primary-light: #f6ffed;
.task-list-view { .task-list-view {
.page-header { .page-header {
margin-bottom: 24px; margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
gap: 16px;
.header-left {
flex: 1;
}
h2 { h2 {
margin: 0 0 8px; margin: 0 0 8px;
@ -698,6 +717,7 @@ $primary-light: #f6ffed;
// //
.feedback-detail { .feedback-detail {
.feedback-result-section, .feedback-result-section,
.feedback-rating-section { .feedback-rating-section {
margin-bottom: 16px; margin-bottom: 16px;
@ -854,6 +874,7 @@ $primary-light: #f6ffed;
} }
.photo-upload-area { .photo-upload-area {
.photo-item, .photo-item,
.upload-btn { .upload-btn {
width: 60px; width: 60px;

View File

@ -76,7 +76,7 @@
<div class="info-row"> <div class="info-row">
<CalendarOutlined class="info-icon" /> <CalendarOutlined class="info-icon" />
<span class="info-value">{{ calculateAge(student.birthDate) || '--' }}{{ student.birthDate ? '岁' : '' <span class="info-value">{{ calculateAge(student.birthDate) || '--' }}{{ student.birthDate ? '岁' : ''
}}</span> }}</span>
<span class="gender-tag" :class="normalizeGender(student.gender) === '男' ? 'boy' : 'girl'"> <span class="gender-tag" :class="normalizeGender(student.gender) === '男' ? 'boy' : 'girl'">
{{ normalizeGender(student.gender) }} {{ normalizeGender(student.gender) }}
</span> </span>
@ -400,7 +400,7 @@ interface FormState {
name: string; name: string;
gender: string; gender: string;
birthDate?: string | null; birthDate?: string | null;
classId: number; classId?: number;
parentName: string; parentName: string;
parentPhone: string; parentPhone: string;
} }
@ -409,7 +409,7 @@ const formState = reactive<FormState>({
name: '', name: '',
gender: '男', gender: '男',
birthDate: null, birthDate: null,
classId: 0, classId: undefined,
parentName: '', parentName: '',
parentPhone: '', parentPhone: '',
}); });
@ -417,7 +417,9 @@ const formState = reactive<FormState>({
const rules: Record<string, any[]> = { const rules: Record<string, any[]> = {
name: [{ required: true, message: '请输入学生姓名', trigger: 'blur' }], name: [{ required: true, message: '请输入学生姓名', trigger: 'blur' }],
gender: [{ required: true, message: '请选择性别', trigger: 'change' }], gender: [{ required: true, message: '请选择性别', trigger: 'change' }],
classId: [{ required: true, message: '请选择班级', trigger: 'change', type: 'number' }], classId: [
{ required: true, message: '请选择班级', trigger: 'change' },
],
}; };
const calculateAge = (birthDate?: string | null): number | null => { const calculateAge = (birthDate?: string | null): number | null => {
@ -482,7 +484,7 @@ const resetForm = () => {
formState.name = ''; formState.name = '';
formState.gender = '男'; formState.gender = '男';
formState.birthDate = null; formState.birthDate = null;
formState.classId = 0; formState.classId = undefined;
formState.parentName = ''; formState.parentName = '';
formState.parentPhone = ''; formState.parentPhone = '';
}; };
@ -508,6 +510,11 @@ const handleEdit = (record: Student) => {
const handleModalOk = async () => { const handleModalOk = async () => {
try { try {
await formRef.value?.validate(); await formRef.value?.validate();
const classId = formState.classId;
if (Number(classId || '-1') < 0) {
message.warning('请选择班级');
return;
}
submitting.value = true; submitting.value = true;
if (isEdit.value && formState.id) { if (isEdit.value && formState.id) {
@ -515,7 +522,7 @@ const handleModalOk = async () => {
name: formState.name, name: formState.name,
gender: formState.gender, gender: formState.gender,
birthDate: formState.birthDate, birthDate: formState.birthDate,
classId: formState.classId, classId,
parentName: formState.parentName, parentName: formState.parentName,
parentPhone: formState.parentPhone, parentPhone: formState.parentPhone,
}); });
@ -525,7 +532,7 @@ const handleModalOk = async () => {
name: formState.name, name: formState.name,
gender: formState.gender, gender: formState.gender,
birthDate: formState.birthDate, birthDate: formState.birthDate,
classId: formState.classId, classId,
parentName: formState.parentName, parentName: formState.parentName,
parentPhone: formState.parentPhone, parentPhone: formState.parentPhone,
}); });

View File

@ -59,7 +59,7 @@
<div class="class-avatar" :style="{ background: getGradeGradient(cls.grade) }"> <div class="class-avatar" :style="{ background: getGradeGradient(cls.grade) }">
<span class="avatar-text">{{ getGradeInitial(cls.grade) }}</span> <span class="avatar-text">{{ getGradeInitial(cls.grade) }}</span>
</div> </div>
<div class="class-name">{{ cls.name }}</div> <div class="class-name">{{ cls.name || '未命名班级' }}</div>
</div> </div>
<!-- 统计信息 --> <!-- 统计信息 -->

View File

@ -182,21 +182,32 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-form-item label="选择目标" required v-if="createForm.targetType === 'CLASS'"> <a-form-item label="选择班级" required v-if="createForm.targetType === 'CLASS'">
<a-select v-model:value="createForm.targetIds" mode="multiple" placeholder="请选择班级" style="width: 100%;"> <a-select v-model:value="createForm.targetIds" mode="multiple" placeholder="请选择班级" style="width: 100%;">
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id"> <a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
{{ cls.name }} ({{ cls.grade }}) {{ cls.name }} ({{ cls.grade }})
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="选择学生" required v-if="createForm.targetType === 'STUDENT'"> <template v-if="createForm.targetType === 'STUDENT'">
<a-select v-model:value="createForm.targetIds" mode="multiple" placeholder="请选择学生" style="width: 100%;" <a-form-item label="选择班级" required>
:filter-option="filterStudentOption" show-search> <a-select v-model:value="selectedClassIdForStudent" placeholder="请先选择班级" style="width: 100%;" allow-clear
<a-select-option v-for="student in students" :key="student.id" :value="student.id"> @change="onStudentClassChange">
{{ student.name }} - {{ student.class?.name }} <a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
</a-select-option> {{ cls.name }} ({{ cls.grade }})
</a-select> </a-select-option>
</a-form-item> </a-select>
</a-form-item>
<a-form-item label="选择学生" required>
<a-select v-model:value="createForm.targetIds" mode="multiple" placeholder="请选择学生" style="width: 100%;"
:filter-option="filterStudentOption" show-search :loading="studentsLoading"
:disabled="!selectedClassIdForStudent">
<a-select-option v-for="student in students" :key="student.id" :value="student.id">
{{ student.name }}
</a-select-option>
</a-select>
</a-form-item>
</template>
<a-form-item label="关联课程"> <a-form-item label="关联课程">
<a-select v-model:value="createForm.relatedCourseId" placeholder="可选,关联课程包" style="width: 100%;" allowClear <a-select v-model:value="createForm.relatedCourseId" placeholder="可选,关联课程包" style="width: 100%;" allowClear
show-search :filter-option="filterCourseOption"> show-search :filter-option="filterCourseOption">
@ -269,8 +280,8 @@
<a-tooltip :title="completion.parentFeedback || completion.feedback?.comment"> <a-tooltip :title="completion.parentFeedback || completion.feedback?.comment">
<span class="feedback-text">{{ (completion.parentFeedback || completion.feedback?.comment || <span class="feedback-text">{{ (completion.parentFeedback || completion.feedback?.comment ||
'').substring(0, 30) }}{{ (completion.parentFeedback || completion.feedback?.comment || '').length > '').substring(0, 30) }}{{ (completion.parentFeedback || completion.feedback?.comment || '').length >
30 30
? '...' : '' }}</span> ? '...' : '' }}</span>
</a-tooltip> </a-tooltip>
</div> </div>
<span v-else class="no-feedback">暂无反馈</span> <span v-else class="no-feedback">暂无反馈</span>
@ -359,7 +370,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'; import { ref, reactive, onMounted, computed, watch } from 'vue';
import { message, Modal } from 'ant-design-vue'; import { message, Modal } from 'ant-design-vue';
import { import {
CheckSquareOutlined, CheckSquareOutlined,
@ -395,7 +406,7 @@ import {
updateCompletionFeedback, updateCompletionFeedback,
sendTaskReminder, sendTaskReminder,
getTeacherClasses, getTeacherClasses,
getTeacherStudents, getTeacherClassStudents,
getTeacherCourses, getTeacherCourses,
getTaskTemplates, getTaskTemplates,
type TeacherTask, type TeacherTask,
@ -438,6 +449,8 @@ const isEdit = ref(false);
const editTaskId = ref<number | null>(null); const editTaskId = ref<number | null>(null);
const classes = ref<any[]>([]); const classes = ref<any[]>([]);
const students = ref<any[]>([]); const students = ref<any[]>([]);
const studentsLoading = ref(false);
const selectedClassIdForStudent = ref<number | undefined>();
const courses = ref<any[]>([]); const courses = ref<any[]>([]);
// //
@ -506,8 +519,8 @@ const getCompletedCount = (task: TeacherTask) => {
}; };
const filterStudentOption = (input: string, option: any) => { const filterStudentOption = (input: string, option: any) => {
const text = option.children[0].children.toLowerCase(); const student = students.value.find((s: any) => s.id === option.value);
return text.includes(input.toLowerCase()); return student ? student.name.toLowerCase().includes(input.toLowerCase()) : false;
}; };
const filterCourseOption = (input: string, option: any) => { const filterCourseOption = (input: string, option: any) => {
@ -533,22 +546,68 @@ const loadTasks = async () => {
} }
}; };
// //
watch(() => createForm.targetType, () => {
createForm.targetIds = [];
selectedClassIdForStudent.value = undefined;
students.value = [];
});
//
const loadOptions = async () => { const loadOptions = async () => {
try { try {
const [classesData, studentsData, coursesData] = await Promise.all([ const [classesData, coursesData] = await Promise.all([
getTeacherClasses(), getTeacherClasses(),
getTeacherStudents({ pageNum: 1, pageSize: 500 }),
getTeacherCourses({ pageNum: 1, pageSize: 100 }), getTeacherCourses({ pageNum: 1, pageSize: 100 }),
]); ]);
classes.value = classesData; classes.value = classesData;
students.value = studentsData.items || [];
courses.value = coursesData.items || []; courses.value = coursesData.items || [];
} catch (error) { } catch (error) {
console.error('加载选项失败', error); console.error('加载选项失败', error);
} }
}; };
//
const onStudentClassChange = async (value: unknown) => {
const classId = value != null && !Number.isNaN(Number(value)) ? Number(value) : undefined;
createForm.targetIds = [];
if (!classId) {
students.value = [];
return;
}
studentsLoading.value = true;
try {
const res = await getTeacherClassStudents(classId, { pageNum: 1, pageSize: 500 });
students.value = (res as any).list || res.items || [];
} catch (error) {
console.error('加载班级学生失败', error);
students.value = [];
} finally {
studentsLoading.value = false;
}
};
//
const loadAllStudentsForEdit = async () => {
studentsLoading.value = true;
try {
const allStudents: any[] = [];
for (const cls of classes.value) {
const res = await getTeacherClassStudents(cls.id, { pageNum: 1, pageSize: 500 });
const items = (res as any).list || res.items || [];
items.forEach((s: any) => {
if (!allStudents.find(x => x.id === s.id)) allStudents.push({ ...s, class: { name: cls.name, grade: cls.grade } });
});
}
students.value = allStudents;
} catch (error) {
console.error('加载学生失败', error);
students.value = [];
} finally {
studentsLoading.value = false;
}
};
const onPageChange = (page: number) => { const onPageChange = (page: number) => {
currentPage.value = page; currentPage.value = page;
loadTasks(); loadTasks();
@ -567,10 +626,11 @@ const openCreateModal = async () => {
createForm.relatedCourseId = undefined; createForm.relatedCourseId = undefined;
createForm.relatedBookName = ''; // createForm.relatedBookName = ''; //
createForm.dateRange = null; createForm.dateRange = null;
selectedClassIdForStudent.value = undefined;
students.value = [];
createModalVisible.value = true; createModalVisible.value = true;
// await Promise.all([loadOptions(), loadTemplates()]);
await loadTemplates();
}; };
// //
@ -624,24 +684,26 @@ const getTaskTypeText = (type: string) => {
const openEditModal = async (task: TeacherTask) => { const openEditModal = async (task: TeacherTask) => {
isEdit.value = true; isEdit.value = true;
editTaskId.value = task.id; editTaskId.value = task.id;
selectedClassIdForStudent.value = undefined;
createModalVisible.value = true; createModalVisible.value = true;
try { try {
await loadOptions(); // await loadOptions();
const detail = await getTeacherTask(task.id); const detail = await getTeacherTask(task.id);
//
createForm.title = detail.title || ''; createForm.title = detail.title || '';
createForm.description = detail.description || ''; createForm.description = detail.description || '';
createForm.taskType = (detail.type || detail.taskType || 'READING') as 'READING' | 'ACTIVITY' | 'HOMEWORK'; createForm.taskType = (detail.type || detail.taskType || 'READING') as 'READING' | 'ACTIVITY' | 'HOMEWORK';
createForm.targetType = (detail.targetType?.toUpperCase?.() || 'CLASS') as 'CLASS' | 'STUDENT'; createForm.targetType = (detail.targetType?.toUpperCase?.() || 'CLASS') as 'CLASS' | 'STUDENT';
createForm.targetIds = detail.targetIds || []; createForm.targetIds = detail.targetIds || [];
// number API string a-select
const rawCourseId = detail.courseId ?? detail.relatedCourseId; const rawCourseId = detail.courseId ?? detail.relatedCourseId;
createForm.relatedCourseId = rawCourseId; createForm.relatedCourseId = rawCourseId;
createForm.relatedBookName = detail.relatedBookName || ''; createForm.relatedBookName = detail.relatedBookName || '';
// dueDate startDate
createForm.dateRange = detail.startDate createForm.dateRange = detail.startDate
? [dayjs(detail.startDate), dayjs(detail.dueDate || detail.startDate)] ? [dayjs(detail.startDate), dayjs(detail.dueDate || detail.startDate)]
: null; : null;
//
if (createForm.targetType === 'STUDENT' && createForm.targetIds.length > 0) {
await loadAllStudentsForEdit();
}
} catch (error: any) { } catch (error: any) {
message.error(error.message || '加载任务详情失败'); message.error(error.message || '加载任务详情失败');
createModalVisible.value = false; createModalVisible.value = false;
@ -656,7 +718,7 @@ const handleCreate = async () => {
} }
if (!isEdit.value && createForm.targetIds.length === 0) { if (!isEdit.value && createForm.targetIds.length === 0) {
message.warning('请选择目标班级或学生'); message.warning(createForm.targetType === 'STUDENT' ? '请先选择班级并选择学生' : '请选择目标班级');
return; return;
} }

View File

@ -1,11 +1,16 @@
package com.reading.platform.controller.parent; package com.reading.platform.controller.parent;
import com.reading.platform.common.mapper.StudentMapper; import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result; import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.response.StudentResponse; import com.reading.platform.dto.response.ChildInfoResponse;
import com.reading.platform.entity.Student; import com.reading.platform.dto.response.ChildProfileResponse;
import com.reading.platform.service.StudentService; import com.reading.platform.dto.response.LessonRecordResponse;
import com.reading.platform.common.annotation.RequireRole;
import com.reading.platform.common.enums.UserRole;
import com.reading.platform.service.ParentAuthService;
import com.reading.platform.service.ParentChildService;
import com.reading.platform.service.ParentLessonService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -17,30 +22,45 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/api/v1/parent/children") @RequestMapping("/api/v1/parent/children")
@RequiredArgsConstructor @RequiredArgsConstructor
@RequireRole(UserRole.PARENT)
public class ParentChildController { public class ParentChildController {
private final StudentService studentService; private final ParentChildService parentChildService;
private final StudentMapper studentMapper; private final ParentAuthService parentAuthService;
private final ParentLessonService parentLessonService;
@Operation(summary = "Get my children") @Operation(summary = "Get my children")
@GetMapping @GetMapping
public Result<List<StudentResponse>> getMyChildren() { public Result<List<ChildInfoResponse>> getMyChildren() {
Long parentId = SecurityUtils.getCurrentUserId(); Long parentId = SecurityUtils.getCurrentUserId();
List<Student> students = studentService.getStudentsByParentId(parentId); List<ChildInfoResponse> children = parentChildService.getChildrenForParent(parentId);
return Result.success(studentMapper.toVO(students)); return Result.success(children);
} }
@Operation(summary = "Get child by ID") @Operation(summary = "Get child by ID")
@GetMapping("/{id}") @GetMapping("/{id}")
public Result<StudentResponse> getChild(@PathVariable Long id) { public Result<ChildProfileResponse> getChild(@PathVariable Long id) {
Student student = studentService.getStudentById(id); Long parentId = SecurityUtils.getCurrentUserId();
return Result.success(studentMapper.toVO(student)); ChildProfileResponse profile = parentChildService.getChildProfile(parentId, id);
return Result.success(profile);
}
@Operation(summary = "Get child lesson records (reading history)")
@GetMapping("/{id}/lessons")
public Result<PageResult<LessonRecordResponse>> getChildLessons(
@PathVariable Long id,
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize) {
Long parentId = SecurityUtils.getCurrentUserId();
parentAuthService.validateParentStudentRelation(parentId, id);
PageResult<LessonRecordResponse> result = parentLessonService.getLessonsByStudentId(id, parentId, pageNum, pageSize);
return Result.success(result);
} }
@Operation(summary = "Get child growth records") @Operation(summary = "Get child growth records")
@GetMapping("/{id}/growth") @GetMapping("/{id}/growth")
public Result<Object> getChildGrowth(@PathVariable Long id) { public Result<Object> getChildGrowth(@PathVariable Long id) {
// TODO: 实现获取成长记录 // 已迁移至 ParentGrowthController: GET /parent/growth-records/student/:studentId
return Result.success(null); return Result.success(null);
} }

View File

@ -1,81 +1,114 @@
package com.reading.platform.controller.parent; package com.reading.platform.controller.parent;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.mapper.GrowthRecordMapper; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.reading.platform.entity.Clazz;
import com.reading.platform.entity.GrowthRecord;
import com.reading.platform.common.response.PageResult; import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result; import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.request.GrowthRecordCreateRequest; import com.reading.platform.dto.response.GrowthRecordForParentResponse;
import com.reading.platform.dto.request.GrowthRecordUpdateRequest; import com.reading.platform.common.annotation.RequireRole;
import com.reading.platform.dto.response.GrowthRecordResponse; import com.reading.platform.common.enums.UserRole;
import com.reading.platform.entity.GrowthRecord; import com.reading.platform.service.ClassService;
import com.reading.platform.service.GrowthRecordService; import com.reading.platform.service.GrowthRecordService;
import com.reading.platform.service.ParentAuthService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
@Tag(name = "Parent - Growth Record", description = "Growth Record APIs for Parent") /**
* 家长端成长档案 - 只读
* 家长仅可查看孩子的成长档案不可创建/修改/删除
* 返回格式对齐前端 GrowthRecordimages 为数组recordTypeclass
*/
@Tag(name = "Parent - Growth Record", description = "Growth Record APIs for Parent (read-only)")
@RestController @RestController
@RequestMapping("/api/v1/parent/growth-records") @RequestMapping("/api/v1/parent/growth-records")
@RequiredArgsConstructor @RequiredArgsConstructor
@RequireRole(UserRole.PARENT)
public class ParentGrowthController { public class ParentGrowthController {
private final GrowthRecordService growthRecordService; private final GrowthRecordService growthRecordService;
private final GrowthRecordMapper growthRecordMapper; private final ParentAuthService parentAuthService;
private final ClassService classService;
@Operation(summary = "Create growth record") private final ObjectMapper objectMapper;
@PostMapping
public Result<GrowthRecordResponse> createGrowthRecord(@Valid @RequestBody GrowthRecordCreateRequest request) {
Long tenantId = SecurityUtils.getCurrentTenantId();
Long userId = SecurityUtils.getCurrentUserId();
GrowthRecord record = growthRecordService.createGrowthRecord(tenantId, userId, "parent", request);
return Result.success(growthRecordMapper.toVO(record));
}
@Operation(summary = "Update growth record")
@PutMapping("/{id}")
public Result<GrowthRecordResponse> updateGrowthRecord(@PathVariable Long id, @RequestBody GrowthRecordUpdateRequest request) {
GrowthRecord record = growthRecordService.updateGrowthRecord(id, request);
return Result.success(growthRecordMapper.toVO(record));
}
@Operation(summary = "Get growth record by ID") @Operation(summary = "Get growth record by ID")
@GetMapping("/{id}") @GetMapping("/{id}")
public Result<GrowthRecordResponse> getGrowthRecord(@PathVariable Long id) { public Result<GrowthRecordForParentResponse> getGrowthRecord(@PathVariable Long id) {
Long parentId = SecurityUtils.getCurrentUserId();
GrowthRecord record = growthRecordService.getGrowthRecordById(id); GrowthRecord record = growthRecordService.getGrowthRecordById(id);
return Result.success(growthRecordMapper.toVO(record)); parentAuthService.validateParentStudentRelation(parentId, record.getStudentId());
return Result.success(toParentResponse(record));
} }
@Operation(summary = "Get growth records by student ID") @Operation(summary = "Get growth records by student ID")
@GetMapping("/student/{studentId}") @GetMapping("/student/{studentId}")
public Result<PageResult<GrowthRecordResponse>> getGrowthRecordsByStudent( public Result<PageResult<GrowthRecordForParentResponse>> getGrowthRecordsByStudent(
@PathVariable Long studentId, @PathVariable Long studentId,
@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String type) { @RequestParam(required = false) String type) {
Long parentId = SecurityUtils.getCurrentUserId();
parentAuthService.validateParentStudentRelation(parentId, studentId);
Page<GrowthRecord> page = growthRecordService.getGrowthRecordsByStudentId(studentId, pageNum, pageSize, type); Page<GrowthRecord> page = growthRecordService.getGrowthRecordsByStudentId(studentId, pageNum, pageSize, type);
List<GrowthRecordResponse> voList = growthRecordMapper.toVO(page.getRecords()); List<GrowthRecordForParentResponse> voList = page.getRecords().stream()
.map(this::toParentResponse)
.toList();
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
} }
@Operation(summary = "Get recent growth records") @Operation(summary = "Get recent growth records")
@GetMapping("/student/{studentId}/recent") @GetMapping("/student/{studentId}/recent")
public Result<List<GrowthRecordResponse>> getRecentGrowthRecords( public Result<List<GrowthRecordForParentResponse>> getRecentGrowthRecords(
@PathVariable Long studentId, @PathVariable Long studentId,
@RequestParam(required = false, defaultValue = "10") Integer limit) { @RequestParam(required = false, defaultValue = "10") Integer limit) {
Long parentId = SecurityUtils.getCurrentUserId();
parentAuthService.validateParentStudentRelation(parentId, studentId);
List<GrowthRecord> records = growthRecordService.getRecentGrowthRecords(studentId, limit); List<GrowthRecord> records = growthRecordService.getRecentGrowthRecords(studentId, limit);
return Result.success(growthRecordMapper.toVO(records)); return Result.success(records.stream().map(this::toParentResponse).toList());
} }
@Operation(summary = "Delete growth record") private GrowthRecordForParentResponse toParentResponse(GrowthRecord record) {
@DeleteMapping("/{id}") List<String> images = parseImages(record.getImages());
public Result<Void> deleteGrowthRecord(@PathVariable Long id) { GrowthRecordForParentResponse.ClassInfo classInfo = null;
growthRecordService.deleteGrowthRecord(id); if (record.getStudentId() != null) {
return Result.success(); Clazz clazz = classService.getPrimaryClassByStudentId(record.getStudentId());
if (clazz != null) {
classInfo = GrowthRecordForParentResponse.ClassInfo.builder()
.id(clazz.getId())
.name(clazz.getName())
.build();
}
}
return GrowthRecordForParentResponse.builder()
.id(record.getId())
.title(record.getTitle())
.content(record.getContent())
.images(images)
.recordDate(record.getRecordDate())
.recordType(record.getType())
.classInfo(classInfo)
.createdAt(record.getCreatedAt())
.build();
} }
private List<String> parseImages(String imagesJson) {
if (!StringUtils.hasText(imagesJson)) {
return Collections.emptyList();
}
try {
return objectMapper.readValue(imagesJson, new TypeReference<List<String>>() {});
} catch (Exception e) {
return Collections.emptyList();
}
}
} }

View File

@ -1,12 +1,13 @@
package com.reading.platform.controller.parent; package com.reading.platform.controller.parent;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.mapper.NotificationMapper;
import com.reading.platform.common.response.PageResult; import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result; import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.response.NotificationResponse; import com.reading.platform.dto.response.NotificationForParentResponse;
import com.reading.platform.entity.Notification; import com.reading.platform.entity.Notification;
import com.reading.platform.common.annotation.RequireRole;
import com.reading.platform.common.enums.UserRole;
import com.reading.platform.service.NotificationService; import com.reading.platform.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -14,35 +15,51 @@ import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Tag(name = "Parent - Notification", description = "Notification APIs for Parent") @Tag(name = "Parent - Notification", description = "Notification APIs for Parent")
@RestController @RestController
@RequestMapping("/api/v1/parent/notifications") @RequestMapping("/api/v1/parent/notifications")
@RequiredArgsConstructor @RequiredArgsConstructor
@RequireRole(UserRole.PARENT)
public class ParentNotificationController { public class ParentNotificationController {
private final NotificationService notificationService; private final NotificationService notificationService;
private final NotificationMapper notificationMapper;
@Operation(summary = "Get notification by ID") @Operation(summary = "Get notification by ID")
@GetMapping("/{id}") @GetMapping("/{id}")
public Result<NotificationResponse> getNotification(@PathVariable Long id) { public Result<NotificationForParentResponse> getNotification(@PathVariable Long id) {
Notification notification = notificationService.getNotificationById(id); Notification notification = notificationService.getNotificationById(id);
return Result.success(notificationMapper.toVO(notification)); return Result.success(toParentResponse(notification));
} }
@Operation(summary = "Get my notifications") @Operation(summary = "Get my notifications")
@GetMapping @GetMapping
public Result<PageResult<NotificationResponse>> getMyNotifications( public Result<PageResult<NotificationForParentResponse>> getMyNotifications(
@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Integer isRead) { @RequestParam(required = false) Integer isRead,
@RequestParam(required = false) String notificationType) {
Long userId = SecurityUtils.getCurrentUserId(); Long userId = SecurityUtils.getCurrentUserId();
Page<Notification> page = notificationService.getMyNotifications(userId, "parent", pageNum, pageSize, isRead); Page<Notification> page = notificationService.getMyNotifications(userId, "parent", pageNum, pageSize, isRead, notificationType);
List<NotificationResponse> voList = notificationMapper.toVO(page.getRecords()); List<NotificationForParentResponse> voList = page.getRecords().stream()
.map(this::toParentResponse)
.collect(Collectors.toList());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
} }
private NotificationForParentResponse toParentResponse(Notification n) {
return NotificationForParentResponse.builder()
.id(n.getId())
.title(n.getTitle())
.content(n.getContent())
.notificationType(n.getType())
.isRead(n.getIsRead() != null && n.getIsRead() == 1)
.readAt(n.getReadAt())
.createdAt(n.getCreatedAt())
.build();
}
@Operation(summary = "Mark notification as read") @Operation(summary = "Mark notification as read")
@PostMapping("/{id}/read") @PostMapping("/{id}/read")
public Result<Void> markAsRead(@PathVariable Long id) { public Result<Void> markAsRead(@PathVariable Long id) {

View File

@ -1,15 +1,21 @@
package com.reading.platform.controller.parent; package com.reading.platform.controller.parent;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.mapper.TaskMapper; import com.reading.platform.common.mapper.TaskMapper;
import com.reading.platform.common.response.PageResult; import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result; import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.request.TaskCompleteRequest;
import com.reading.platform.dto.request.TaskSubmitRequest; import com.reading.platform.dto.request.TaskSubmitRequest;
import com.reading.platform.dto.response.TaskCompletionDetailResponse; import com.reading.platform.dto.response.TaskCompletionDetailResponse;
import com.reading.platform.dto.response.TaskFeedbackResponse; import com.reading.platform.dto.response.TaskFeedbackResponse;
import com.reading.platform.dto.response.TaskResponse; import com.reading.platform.dto.response.TaskResponse;
import com.reading.platform.dto.response.TaskWithCompletionResponse;
import com.reading.platform.entity.Task; import com.reading.platform.entity.Task;
import com.reading.platform.common.annotation.RequireRole;
import com.reading.platform.common.enums.ErrorCode;
import com.reading.platform.common.enums.UserRole;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.service.ParentAuthService;
import com.reading.platform.service.TaskFeedbackService; import com.reading.platform.service.TaskFeedbackService;
import com.reading.platform.service.TaskService; import com.reading.platform.service.TaskService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@ -24,11 +30,13 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/api/v1/parent/tasks") @RequestMapping("/api/v1/parent/tasks")
@RequiredArgsConstructor @RequiredArgsConstructor
@RequireRole(UserRole.PARENT)
public class ParentTaskController { public class ParentTaskController {
private final TaskService taskService; private final TaskService taskService;
private final TaskMapper taskMapper; private final TaskMapper taskMapper;
private final TaskFeedbackService taskFeedbackService; private final TaskFeedbackService taskFeedbackService;
private final ParentAuthService parentAuthService;
@Operation(summary = "获取任务详情") @Operation(summary = "获取任务详情")
@GetMapping("/{id}") @GetMapping("/{id}")
@ -37,27 +45,28 @@ public class ParentTaskController {
return Result.success(taskMapper.toVO(task)); return Result.success(taskMapper.toVO(task));
} }
@Operation(summary = "获取孩子的任务列表") @Operation(summary = "获取孩子的任务列表(含完成信息与教师评价)")
@GetMapping("/student/{studentId}") @GetMapping("/student/{studentId}")
public Result<PageResult<TaskResponse>> getTasksByStudent( public Result<PageResult<TaskWithCompletionResponse>> getTasksByStudent(
@PathVariable Long studentId, @PathVariable Long studentId,
@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String status) { @RequestParam(required = false) String status) {
Page<Task> page = taskService.getTasksByStudentId(studentId, pageNum, pageSize, status); Long parentId = SecurityUtils.getCurrentUserId();
List<TaskResponse> voList = taskMapper.toVO(page.getRecords()); parentAuthService.validateParentStudentRelation(parentId, studentId);
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); PageResult<TaskWithCompletionResponse> result = taskService.getTasksWithCompletionByStudentId(studentId, pageNum, pageSize, status);
return Result.success(result);
} }
@Operation(summary = "获取我的任务列表") @Operation(summary = "获取我的任务列表(聚合多孩子任务)")
@GetMapping @GetMapping
public Result<PageResult<TaskResponse>> getMyTasks( public Result<PageResult<TaskWithCompletionResponse>> getMyTasks(
@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String status) { @RequestParam(required = false) String status) {
Long parentId = SecurityUtils.getCurrentUserId(); Long parentId = SecurityUtils.getCurrentUserId();
// TODO: 根据 parentId 获取关联学生的任务列表 PageResult<TaskWithCompletionResponse> result = taskService.getMyTasksForParent(parentId, pageNum, pageSize, status);
return Result.success(PageResult.of(List.of(), 0L, Long.valueOf(pageNum == null ? 1 : pageNum), Long.valueOf(pageSize == null ? 10 : pageSize))); return Result.success(result);
} }
// =============== 提交与评价 API =============== // =============== 提交与评价 API ===============
@ -67,6 +76,8 @@ public class ParentTaskController {
public Result<TaskCompletionDetailResponse> submitTask( public Result<TaskCompletionDetailResponse> submitTask(
@PathVariable Long taskId, @PathVariable Long taskId,
@Valid @RequestBody TaskSubmitRequest request) { @Valid @RequestBody TaskSubmitRequest request) {
Long parentId = SecurityUtils.getCurrentUserId();
parentAuthService.validateParentStudentRelation(parentId, request.getStudentId());
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
TaskCompletionDetailResponse response = taskService.submitTaskCompletion(taskId, request, tenantId); TaskCompletionDetailResponse response = taskService.submitTaskCompletion(taskId, request, tenantId);
return Result.success(response); return Result.success(response);
@ -77,6 +88,8 @@ public class ParentTaskController {
public Result<TaskCompletionDetailResponse> updateSubmission( public Result<TaskCompletionDetailResponse> updateSubmission(
@PathVariable Long taskId, @PathVariable Long taskId,
@Valid @RequestBody TaskSubmitRequest request) { @Valid @RequestBody TaskSubmitRequest request) {
Long parentId = SecurityUtils.getCurrentUserId();
parentAuthService.validateParentStudentRelation(parentId, request.getStudentId());
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
TaskCompletionDetailResponse response = taskService.submitTaskCompletion(taskId, request, tenantId); TaskCompletionDetailResponse response = taskService.submitTaskCompletion(taskId, request, tenantId);
return Result.success(response); return Result.success(response);
@ -85,6 +98,8 @@ public class ParentTaskController {
@Operation(summary = "获取教师评价") @Operation(summary = "获取教师评价")
@GetMapping("/completions/{completionId}/feedback") @GetMapping("/completions/{completionId}/feedback")
public Result<TaskFeedbackResponse> getFeedback(@PathVariable Long completionId) { public Result<TaskFeedbackResponse> getFeedback(@PathVariable Long completionId) {
Long parentId = SecurityUtils.getCurrentUserId();
parentAuthService.validateParentCanAccessCompletion(parentId, completionId);
TaskFeedbackResponse response = taskFeedbackService.getFeedbackByCompletionId(completionId); TaskFeedbackResponse response = taskFeedbackService.getFeedbackByCompletionId(completionId);
return Result.success(response); return Result.success(response);
} }
@ -92,6 +107,8 @@ public class ParentTaskController {
@Operation(summary = "获取提交详情") @Operation(summary = "获取提交详情")
@GetMapping("/completions/{completionId}") @GetMapping("/completions/{completionId}")
public Result<TaskCompletionDetailResponse> getCompletionDetail(@PathVariable Long completionId) { public Result<TaskCompletionDetailResponse> getCompletionDetail(@PathVariable Long completionId) {
Long parentId = SecurityUtils.getCurrentUserId();
parentAuthService.validateParentCanAccessCompletion(parentId, completionId);
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
TaskCompletionDetailResponse response = taskService.getCompletionDetail(completionId, tenantId); TaskCompletionDetailResponse response = taskService.getCompletionDetail(completionId, tenantId);
return Result.success(response); return Result.success(response);
@ -99,14 +116,23 @@ public class ParentTaskController {
// =============== 兼容旧接口 =============== // =============== 兼容旧接口 ===============
@Operation(summary = "完成任务(旧接口,兼容使用") @Operation(summary = "完成任务(旧接口,兼容使用,支持 JSON body")
@PostMapping("/{id}/complete") @PostMapping("/{id}/complete")
public Result<Void> completeTask( public Result<Void> completeTask(
@PathVariable Long id, @PathVariable Long id,
@RequestParam Long studentId, @RequestBody(required = false) TaskCompleteRequest body,
@RequestParam(required = false) Long studentId,
@RequestParam(required = false) String content, @RequestParam(required = false) String content,
@RequestParam(required = false) String attachments) { @RequestParam(required = false) String attachments) {
taskService.completeTask(id, studentId, content, attachments); Long sid = (body != null && body.getStudentId() != null) ? body.getStudentId() : studentId;
String cnt = (body != null && body.getContent() != null) ? body.getContent() : content;
String att = (body != null && body.getAttachments() != null) ? body.getAttachments() : attachments;
if (sid == null) {
throw new BusinessException(ErrorCode.INVALID_PARAMETER, "学生ID不能为空");
}
Long parentId = SecurityUtils.getCurrentUserId();
parentAuthService.validateParentStudentRelation(parentId, sid);
taskService.completeTask(id, sid, cnt, att);
return Result.success(); return Result.success();
} }

View File

@ -1,24 +1,34 @@
package com.reading.platform.controller.school; package com.reading.platform.controller.school;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.mapper.ClassMapper; import com.reading.platform.common.mapper.ClassMapper;
import com.reading.platform.common.mapper.ClassTeacherMapper;
import com.reading.platform.common.response.PageResult; import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result; import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.request.ClassCreateRequest; import com.reading.platform.dto.request.ClassCreateRequest;
import com.reading.platform.dto.request.ClassUpdateRequest; import com.reading.platform.dto.request.ClassUpdateRequest;
import com.reading.platform.common.mapper.StudentMapper;
import com.reading.platform.dto.response.ClassResponse; import com.reading.platform.dto.response.ClassResponse;
import com.reading.platform.dto.response.ClassTeacherResponse; import com.reading.platform.dto.response.ClassTeacherResponse;
import com.reading.platform.dto.response.StudentResponse;
import com.reading.platform.entity.Clazz; import com.reading.platform.entity.Clazz;
import com.reading.platform.entity.ClassTeacher; import com.reading.platform.entity.ClassTeacher;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.Student;
import com.reading.platform.entity.Teacher;
import com.reading.platform.mapper.ClassTeacherMapper;
import com.reading.platform.mapper.LessonMapper;
import com.reading.platform.service.ClassService; import com.reading.platform.service.ClassService;
import com.reading.platform.service.StudentService;
import com.reading.platform.service.TeacherService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Tag(name = "School - Class", description = "Class Management APIs for School") @Tag(name = "School - Class", description = "Class Management APIs for School")
@ -30,6 +40,10 @@ public class SchoolClassController {
private final ClassService classService; private final ClassService classService;
private final ClassMapper classMapper; private final ClassMapper classMapper;
private final ClassTeacherMapper classTeacherMapper; private final ClassTeacherMapper classTeacherMapper;
private final StudentService studentService;
private final StudentMapper studentMapper;
private final TeacherService teacherService;
private final LessonMapper lessonMapper;
@Operation(summary = "Create class") @Operation(summary = "Create class")
@PostMapping @PostMapping
@ -59,13 +73,40 @@ public class SchoolClassController {
@GetMapping @GetMapping
public Result<PageResult<ClassResponse>> getClassPage( public Result<PageResult<ClassResponse>> getClassPage(
@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false, defaultValue = "100") Integer pageSize,
@RequestParam(required = false) String keyword, @RequestParam(required = false) String keyword,
@RequestParam(required = false) String grade, @RequestParam(required = false) String grade,
@RequestParam(required = false) String status) { @RequestParam(required = false) String status) {
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
Page<Clazz> page = classService.getClassPage(tenantId, pageNum, pageSize, keyword, grade, status); Page<Clazz> page = classService.getClassPage(tenantId, pageNum, pageSize, keyword, grade, status);
List<ClassResponse> voList = classMapper.toVO(page.getRecords()); List<ClassResponse> voList = classMapper.toVO(page.getRecords());
for (ClassResponse vo : voList) {
int studentCount = studentService.getStudentListByClassId(vo.getId()).size();
long lessonCount = 0;
try {
lessonCount = lessonMapper.selectCount(
new LambdaQueryWrapper<Lesson>().eq(Lesson::getClassId, vo.getId()));
} catch (Exception ignored) {}
vo.setStudentCount(studentCount);
vo.setLessonCount((int) lessonCount);
List<ClassTeacher> classTeachers = classTeacherMapper.selectList(
new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getClassId, vo.getId()));
List<ClassTeacherResponse> teacherList = new ArrayList<>();
for (ClassTeacher ct : classTeachers) {
Teacher t = teacherService.getTeacherById(ct.getTeacherId());
teacherList.add(ClassTeacherResponse.builder()
.id(ct.getId())
.classId(ct.getClassId())
.teacherId(ct.getTeacherId())
.role(ct.getRole())
.teacherName(t != null ? t.getName() : null)
.isPrimary("班主任".equals(ct.getRole()) || "主班".equals(ct.getRole()))
.createdAt(ct.getCreatedAt())
.build());
}
vo.setTeachers(teacherList);
}
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
} }
@ -95,25 +136,39 @@ public class SchoolClassController {
@Operation(summary = "Get students of class") @Operation(summary = "Get students of class")
@GetMapping("/{id}/students") @GetMapping("/{id}/students")
public Result<PageResult<ClassResponse>> getClassStudents( public Result<PageResult<StudentResponse>> getClassStudents(
@PathVariable Long id, @PathVariable Long id,
@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize) { @RequestParam(required = false, defaultValue = "10") Integer pageSize,
// 验证班级属于当前租户 @RequestParam(required = false) String keyword) {
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
classService.getClassByIdWithTenantCheck(id, tenantId); classService.getClassByIdWithTenantCheck(id, tenantId);
// TODO: 实现获取班级学生 Page<Student> page = studentService.getStudentsByClassId(id, pageNum, pageSize, keyword);
return Result.success(PageResult.of(List.of(), 0L, Long.valueOf(pageNum == null ? 1 : pageNum), Long.valueOf(pageSize == null ? 10 : pageSize))); List<StudentResponse> voList = studentMapper.toVO(page.getRecords());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
} }
@Operation(summary = "Get teachers of class") @Operation(summary = "Get teachers of class")
@GetMapping("/{id}/teachers") @GetMapping("/{id}/teachers")
public Result<List<ClassTeacherResponse>> getClassTeachers(@PathVariable Long id) { public Result<List<ClassTeacherResponse>> getClassTeachers(@PathVariable Long id) {
// 验证班级属于当前租户
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
classService.getClassByIdWithTenantCheck(id, tenantId); classService.getClassByIdWithTenantCheck(id, tenantId);
// TODO: 实现获取班级教师 List<ClassTeacher> classTeachers = classTeacherMapper.selectList(
return Result.success(List.of()); new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getClassId, id));
List<ClassTeacherResponse> teacherList = new ArrayList<>();
for (ClassTeacher ct : classTeachers) {
Teacher t = teacherService.getTeacherById(ct.getTeacherId());
teacherList.add(ClassTeacherResponse.builder()
.id(ct.getId())
.classId(ct.getClassId())
.teacherId(ct.getTeacherId())
.role(ct.getRole())
.teacherName(t != null ? t.getName() : null)
.isPrimary("班主任".equals(ct.getRole()) || "主班".equals(ct.getRole()))
.createdAt(ct.getCreatedAt())
.build());
}
return Result.success(teacherList);
} }
@Operation(summary = "Update class teacher role") @Operation(summary = "Update class teacher role")

View File

@ -1,16 +1,20 @@
package com.reading.platform.controller.school; package com.reading.platform.controller.school;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.mapper.ParentMapper; import com.reading.platform.common.mapper.ParentMapper;
import com.reading.platform.common.mapper.ParentStudentMapper; import com.reading.platform.mapper.ParentStudentMapper;
import com.reading.platform.common.response.PageResult; import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result; import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.request.ParentCreateRequest; import com.reading.platform.dto.request.ParentCreateRequest;
import com.reading.platform.dto.request.ParentUpdateRequest; import com.reading.platform.dto.request.ParentUpdateRequest;
import com.reading.platform.dto.response.ChildInfoResponse;
import com.reading.platform.dto.response.ParentResponse; import com.reading.platform.dto.response.ParentResponse;
import com.reading.platform.dto.response.ParentStudentResponse; import com.reading.platform.dto.response.ParentStudentResponse;
import com.reading.platform.entity.Parent; import com.reading.platform.entity.Parent;
import com.reading.platform.entity.ParentStudent;
import com.reading.platform.service.ParentChildService;
import com.reading.platform.service.ParentService; import com.reading.platform.service.ParentService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -27,6 +31,7 @@ import java.util.List;
public class SchoolParentController { public class SchoolParentController {
private final ParentService parentService; private final ParentService parentService;
private final ParentChildService parentChildService;
private final ParentMapper parentMapper; private final ParentMapper parentMapper;
private final ParentStudentMapper parentStudentMapper; private final ParentStudentMapper parentStudentMapper;
@ -64,6 +69,12 @@ public class SchoolParentController {
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
Page<Parent> page = parentService.getParentPage(tenantId, pageNum, pageSize, keyword, status); Page<Parent> page = parentService.getParentPage(tenantId, pageNum, pageSize, keyword, status);
List<ParentResponse> voList = parentMapper.toVO(page.getRecords()); List<ParentResponse> voList = parentMapper.toVO(page.getRecords());
for (ParentResponse vo : voList) {
long count = parentStudentMapper.selectCount(
new LambdaQueryWrapper<ParentStudent>()
.eq(ParentStudent::getParentId, vo.getId()));
vo.setChildrenCount((int) count);
}
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
} }
@ -95,6 +106,29 @@ public class SchoolParentController {
return Result.success(); return Result.success();
} }
@Operation(summary = "Add child to parent (alias for bind student)")
@PostMapping("/{parentId}/children/{studentId}")
public Result<Void> addChild(
@PathVariable Long parentId,
@PathVariable Long studentId,
@RequestBody(required = false) java.util.Map<String, Object> body) {
Long tenantId = SecurityUtils.getCurrentTenantId();
String relationship = null;
Boolean isPrimary = null;
if (body != null) {
if (body.containsKey("relationship")) {
Object v = body.get("relationship");
relationship = v != null ? v.toString() : null;
}
if (body.containsKey("isPrimary")) {
Object v = body.get("isPrimary");
isPrimary = v instanceof Boolean ? (Boolean) v : (v != null && Boolean.parseBoolean(v.toString()));
}
}
parentService.bindStudentWithTenantCheck(parentId, studentId, tenantId, relationship, isPrimary);
return Result.success();
}
@Operation(summary = "Unbind student from parent") @Operation(summary = "Unbind student from parent")
@DeleteMapping("/{parentId}/students/{studentId}") @DeleteMapping("/{parentId}/students/{studentId}")
public Result<Void> unbindStudent(@PathVariable Long parentId, @PathVariable Long studentId) { public Result<Void> unbindStudent(@PathVariable Long parentId, @PathVariable Long studentId) {
@ -103,11 +137,21 @@ public class SchoolParentController {
return Result.success(); return Result.success();
} }
@Operation(summary = "Remove child from parent (alias for unbind student)")
@DeleteMapping("/{parentId}/children/{studentId}")
public Result<Void> removeChild(@PathVariable Long parentId, @PathVariable Long studentId) {
Long tenantId = SecurityUtils.getCurrentTenantId();
parentService.unbindStudentWithTenantCheck(parentId, studentId, tenantId);
return Result.success();
}
@Operation(summary = "Get children of parent") @Operation(summary = "Get children of parent")
@GetMapping("/{id}/children") @GetMapping("/{id}/children")
public Result<List<ParentStudentResponse>> getParentChildren(@PathVariable Long id) { public Result<List<ChildInfoResponse>> getParentChildren(@PathVariable Long id) {
// TODO: 实现获取家长孩子列表 Long tenantId = SecurityUtils.getCurrentTenantId();
return Result.success(List.of()); parentService.getParentByIdWithTenantCheck(id, tenantId);
List<ChildInfoResponse> children = parentChildService.getChildrenForParent(id);
return Result.success(children);
} }
} }

View File

@ -9,6 +9,7 @@ import com.reading.platform.dto.request.StudentCreateRequest;
import com.reading.platform.dto.request.StudentUpdateRequest; import com.reading.platform.dto.request.StudentUpdateRequest;
import com.reading.platform.dto.response.StudentResponse; import com.reading.platform.dto.response.StudentResponse;
import com.reading.platform.entity.Student; import com.reading.platform.entity.Student;
import com.reading.platform.service.ClassService;
import com.reading.platform.service.StudentService; import com.reading.platform.service.StudentService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -25,6 +26,7 @@ import java.util.List;
public class SchoolStudentController { public class SchoolStudentController {
private final StudentService studentService; private final StudentService studentService;
private final ClassService classService;
private final StudentMapper studentMapper; private final StudentMapper studentMapper;
@Operation(summary = "Create student") @Operation(summary = "Create student")
@ -48,7 +50,10 @@ public class SchoolStudentController {
public Result<StudentResponse> getStudent(@PathVariable Long id) { public Result<StudentResponse> getStudent(@PathVariable Long id) {
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
Student student = studentService.getStudentByIdWithTenantCheck(id, tenantId); Student student = studentService.getStudentByIdWithTenantCheck(id, tenantId);
return Result.success(studentMapper.toVO(student)); StudentResponse vo = studentMapper.toVO(student);
var clazz = classService.getPrimaryClassByStudentId(id);
vo.setClassId(clazz != null ? clazz.getId() : null);
return Result.success(vo);
} }
@Operation(summary = "Get student page") @Operation(summary = "Get student page")
@ -58,10 +63,15 @@ public class SchoolStudentController {
@RequestParam(required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String keyword, @RequestParam(required = false) String keyword,
@RequestParam(required = false) String grade, @RequestParam(required = false) String grade,
@RequestParam(required = false) String status) { @RequestParam(required = false) String status,
@RequestParam(required = false) Long classId) {
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
Page<Student> page = studentService.getStudentPage(tenantId, pageNum, pageSize, keyword, grade, status); Page<Student> page = studentService.getStudentPage(tenantId, pageNum, pageSize, keyword, grade, status, classId);
List<StudentResponse> voList = studentMapper.toVO(page.getRecords()); List<StudentResponse> voList = studentMapper.toVO(page.getRecords());
for (StudentResponse vo : voList) {
var clazz = classService.getPrimaryClassByStudentId(vo.getId());
vo.setClassId(clazz != null ? clazz.getId() : null);
}
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
} }

View File

@ -1,5 +1,6 @@
package com.reading.platform.controller.teacher; package com.reading.platform.controller.teacher;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.enums.CourseStatus; import com.reading.platform.common.enums.CourseStatus;
import com.reading.platform.common.mapper.ClassMapper; import com.reading.platform.common.mapper.ClassMapper;
@ -10,16 +11,18 @@ import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result; import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils; import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.response.ClassResponse; import com.reading.platform.dto.response.ClassResponse;
import com.reading.platform.dto.response.ClassStudentsResponse;
import com.reading.platform.dto.response.CourseResponse; import com.reading.platform.dto.response.CourseResponse;
import com.reading.platform.dto.response.LessonTagResponse; import com.reading.platform.dto.response.LessonTagResponse;
import com.reading.platform.dto.response.StudentResponse; import com.reading.platform.dto.response.StudentResponse;
import com.reading.platform.dto.response.TeacherResponse; import com.reading.platform.dto.response.TeacherResponse;
import com.reading.platform.entity.ClassTeacher;
import com.reading.platform.entity.Clazz; import com.reading.platform.entity.Clazz;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.CoursePackage; import com.reading.platform.entity.CoursePackage;
import com.reading.platform.entity.Student; import com.reading.platform.entity.Student;
import com.reading.platform.entity.Teacher; import com.reading.platform.entity.Teacher;
import com.reading.platform.entity.CourseLesson; import com.reading.platform.entity.CourseLesson;
import com.reading.platform.mapper.LessonMapper;
import com.reading.platform.service.ClassService; import com.reading.platform.service.ClassService;
import com.reading.platform.service.CourseLessonService; import com.reading.platform.service.CourseLessonService;
import com.reading.platform.service.CoursePackageService; import com.reading.platform.service.CoursePackageService;
@ -50,13 +53,45 @@ public class TeacherCourseController {
private final ClassMapper classMapper; private final ClassMapper classMapper;
private final StudentMapper studentMapper; private final StudentMapper studentMapper;
private final TeacherMapper teacherMapper; private final TeacherMapper teacherMapper;
private final LessonMapper lessonMapper;
@Operation(summary = "获取教师的班级列表") @Operation(summary = "获取教师的班级列表")
@GetMapping("/classes") @GetMapping("/classes")
public Result<List<ClassResponse>> getClasses() { public Result<List<ClassResponse>> getClasses() {
Long teacherId = SecurityUtils.getCurrentUserId();
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
List<Clazz> classes = classService.getActiveClassesByTenantId(tenantId); List<Clazz> classes = classService.getClassesByTeacherId(teacherId, tenantId);
return Result.success(classMapper.toVO(classes));
List<ClassResponse> voList = classMapper.toVO(classes);
for (ClassResponse vo : voList) {
int studentCount = studentService.getStudentListByClassId(vo.getId()).size();
long lessonCount = 0;
try {
lessonCount = lessonMapper.selectCount(
new LambdaQueryWrapper<Lesson>().eq(Lesson::getClassId, vo.getId()));
} catch (Exception e) {
// 忽略 lesson 表不存在等异常
}
String role = classService.getTeacherRoleInClass(vo.getId(), teacherId);
vo.setStudentCount(studentCount);
vo.setLessonCount((int) lessonCount);
vo.setMyRole(mapRoleToFrontend(role));
vo.setIsPrimary(isPrimaryRole(role));
}
return Result.success(voList);
}
private static String mapRoleToFrontend(String role) {
if (role == null) return "ASSIST";
return switch (role) {
case "班主任", "主班" -> "MAIN";
case "保育员" -> "CARE";
default -> "ASSIST";
};
}
private static boolean isPrimaryRole(String role) {
return role != null && ("班主任".equals(role) || "主班".equals(role));
} }
@Operation(summary = "根据 ID 获取课程") @Operation(summary = "根据 ID 获取课程")
@ -138,14 +173,41 @@ public class TeacherCourseController {
@Operation(summary = "获取班级学生列表") @Operation(summary = "获取班级学生列表")
@GetMapping("/classes/{id}/students") @GetMapping("/classes/{id}/students")
public Result<PageResult<StudentResponse>> getClassStudents( public Result<ClassStudentsResponse> getClassStudents(
@PathVariable Long id, @PathVariable Long id,
@RequestParam(required = false, defaultValue = "1") Integer pageNum, @RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String keyword) { @RequestParam(required = false) String keyword) {
Page<Student> page = studentService.getStudentsByClassId(id, pageNum, pageSize); Long tenantId = SecurityUtils.getCurrentTenantId();
Clazz clazz = classService.getClassByIdWithTenantCheck(id, tenantId);
Page<Student> page = studentService.getStudentsByClassId(id, pageNum, pageSize, keyword);
List<StudentResponse> voList = studentMapper.toVO(page.getRecords()); List<StudentResponse> voList = studentMapper.toVO(page.getRecords());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
int studentCount = studentService.getStudentListByClassId(id).size();
long lessonCount = 0;
try {
lessonCount = lessonMapper.selectCount(
new LambdaQueryWrapper<Lesson>().eq(Lesson::getClassId, id));
} catch (Exception e) {
// 忽略 lesson 表不存在等异常
}
ClassStudentsResponse.ClassInfo classInfo = ClassStudentsResponse.ClassInfo.builder()
.id(clazz.getId())
.name(clazz.getName())
.grade(clazz.getGrade())
.studentCount(studentCount)
.lessonCount((int) lessonCount)
.build();
ClassStudentsResponse response = ClassStudentsResponse.builder()
.list(voList)
.total(page.getTotal())
.pageNum(page.getCurrent())
.pageSize(page.getSize())
.classInfo(classInfo)
.build();
return Result.success(response);
} }
@Operation(summary = "获取班级教师列表") @Operation(summary = "获取班级教师列表")

View File

@ -66,9 +66,13 @@ public class TeacherTaskController {
@RequestParam(required = false) String taskType, @RequestParam(required = false) String taskType,
@RequestParam(required = false) String status) { @RequestParam(required = false) String status) {
Long tenantId = SecurityUtils.getCurrentTenantId(); Long tenantId = SecurityUtils.getCurrentTenantId();
Long teacherId = SecurityUtils.getCurrentUserId();
String typeFilter = (type != null && !type.isEmpty()) ? type : taskType; String typeFilter = (type != null && !type.isEmpty()) ? type : taskType;
Page<Task> page = taskService.getTaskPage(tenantId, pageNum, pageSize, keyword, typeFilter, status); Page<Task> page = taskService.getTaskPage(tenantId, pageNum, pageSize, keyword, typeFilter, status, teacherId);
List<TaskResponse> voList = taskMapper.toVO(page.getRecords()); List<TaskResponse> voList = taskMapper.toVO(page.getRecords());
for (int i = 0; i < voList.size(); i++) {
taskService.enrichTaskResponseWithStats(voList.get(i), page.getRecords().get(i).getId());
}
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize())); return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
} }

View File

@ -39,4 +39,7 @@ public class StudentUpdateRequest {
@Schema(description = "状态") @Schema(description = "状态")
private String status; private String status;
@Schema(description = "所在班级 ID更新时调用 assignStudents 调整班级")
private Long classId;
} }

View File

@ -0,0 +1,21 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 任务完成请求旧接口兼容家长端 JSON 提交
*/
@Data
@Schema(description = "任务完成请求")
public class TaskCompleteRequest {
@Schema(description = "学生ID", required = true)
private Long studentId;
@Schema(description = "完成内容/反馈")
private String content;
@Schema(description = "附件")
private String attachments;
}

View File

@ -0,0 +1,57 @@
package com.reading.platform.dto.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDate;
/**
* 家长端孩子列表响应
* 对齐前端 ChildInfo 结构
*/
@Data
@Builder
@Schema(description = "家长端孩子列表响应")
public class ChildInfoResponse {
@Schema(description = "学生ID")
private Long id;
@Schema(description = "姓名")
private String name;
@Schema(description = "性别")
private String gender;
@Schema(description = "出生日期")
private LocalDate birthDate;
@Schema(description = "与家长关系FATHER/MOTHER/GRANDFATHER/GRANDMOTHER/OTHER")
private String relationship;
@JsonProperty("class")
@Schema(description = "班级信息")
private ClassInfo classInfo;
@Schema(description = "阅读次数student_record 数量)")
private Integer readingCount;
@Schema(description = "上课次数lesson 记录数)")
private Integer lessonCount;
@Data
@Builder
@Schema(description = "班级信息")
public static class ClassInfo {
@Schema(description = "班级ID")
private Long id;
@Schema(description = "班级名称")
private String name;
@Schema(description = "年级")
private String grade;
}
}

Some files were not shown because too many files have changed in this diff Show More