Compare commits
19 Commits
101b0a6a42
...
3183d1d388
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3183d1d388 | ||
|
|
cf7e6bf94f | ||
|
|
c55b2266fb | ||
|
|
5cb903d7ed | ||
|
|
c93d325cee | ||
|
|
463c3d9922 | ||
|
|
de742d9acf | ||
| 9f04daa955 | |||
| 27cb883b23 | |||
| 4ec61e48ca | |||
|
|
161e05dce4 | ||
|
|
26f55da670 | ||
|
|
802327f075 | ||
|
|
37a6aba8cc | ||
| 13fc0e720e | |||
| 79e90410dd | |||
|
|
f90037dd17 | ||
|
|
5a05af18dd | ||
|
|
8502d8b2d3 |
File diff suppressed because it is too large
Load Diff
83
reading-platform-frontend/src/api/course-center.ts
Normal file
83
reading-platform-frontend/src/api/course-center.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { http } from './index';
|
||||
|
||||
// ============= 类型定义 =============
|
||||
|
||||
/** 套餐信息 */
|
||||
export interface CourseCollection {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
packageCount: number;
|
||||
gradeLevels?: string[];
|
||||
status: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
/** 课程包信息 */
|
||||
export interface CoursePackage {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
coverImagePath?: string;
|
||||
pictureBookName?: string;
|
||||
gradeTags: string[];
|
||||
domainTags?: string[];
|
||||
themeId?: number;
|
||||
themeName?: string;
|
||||
durationMinutes?: number;
|
||||
usageCount?: number;
|
||||
avgRating?: number;
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
/** 筛选元数据 - 年级选项 */
|
||||
export interface GradeOption {
|
||||
label: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** 筛选元数据 - 主题选项 */
|
||||
export interface ThemeOption {
|
||||
id: number;
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** 筛选元数据响应 */
|
||||
export interface FilterMetaResponse {
|
||||
grades: GradeOption[];
|
||||
themes: ThemeOption[];
|
||||
}
|
||||
|
||||
// ============= API 接口 =============
|
||||
|
||||
/**
|
||||
* 获取租户的课程套餐列表
|
||||
*/
|
||||
export function getCollections(): Promise<CourseCollection[]> {
|
||||
return http.get<CourseCollection[]>('/v1/school/packages');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取套餐下的课程包列表(支持筛选)
|
||||
*/
|
||||
export function getPackages(
|
||||
collectionId: number,
|
||||
params?: {
|
||||
grade?: string;
|
||||
themeId?: number;
|
||||
keyword?: string;
|
||||
}
|
||||
): Promise<CoursePackage[]> {
|
||||
return http.get<CoursePackage[]>(`/v1/school/packages/${collectionId}/packages`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取套餐的筛选元数据
|
||||
*/
|
||||
export function getFilterMeta(collectionId: number): Promise<FilterMetaResponse> {
|
||||
return http.get<FilterMetaResponse>(`/v1/school/packages/${collectionId}/filter-meta`);
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
*/
|
||||
import type {
|
||||
BasicSettingsUpdateRequest,
|
||||
BatchStudentRecordsRequest,
|
||||
BindStudentParams,
|
||||
ChangePasswordParams,
|
||||
CheckConflictParams,
|
||||
@ -29,8 +30,10 @@ import type {
|
||||
GenerateReadOnlyTokenParams,
|
||||
GetActiveTeachersParams,
|
||||
GetActiveTenantsParams,
|
||||
GetAllCoursesParams,
|
||||
GetAllStudentsParams,
|
||||
GetCalendarViewDataParams,
|
||||
GetChildLessonsParams,
|
||||
GetClassPageParams,
|
||||
GetClassStudents1Params,
|
||||
GetClassStudentsParams,
|
||||
@ -57,8 +60,11 @@ import type {
|
||||
GetSchedules1Params,
|
||||
GetSchedulesParams,
|
||||
GetSchoolCoursesParams,
|
||||
GetStatisticsParams,
|
||||
GetStudentPageParams,
|
||||
GetTaskPage1Params,
|
||||
GetTaskCompletions1Params,
|
||||
GetTaskCompletionsParams,
|
||||
GetTaskListParams,
|
||||
GetTaskPageParams,
|
||||
GetTasksByStudentParams,
|
||||
GetTeacherPageParams,
|
||||
@ -82,7 +88,6 @@ import type {
|
||||
RefreshTokenRequest,
|
||||
RenewRequest,
|
||||
ResetPassword1Params,
|
||||
ResetPasswordParams,
|
||||
ResourceItemCreateRequest,
|
||||
ResourceItemUpdateRequest,
|
||||
ResourceLibraryCreateRequest,
|
||||
@ -95,7 +100,10 @@ import type {
|
||||
StudentCreateRequest,
|
||||
StudentRecordRequest,
|
||||
StudentUpdateRequest,
|
||||
TaskCompleteRequest,
|
||||
TaskCreateRequest,
|
||||
TaskFeedbackRequest,
|
||||
TaskSubmitRequest,
|
||||
TaskTemplateCreateRequest,
|
||||
TaskUpdateRequest,
|
||||
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 获取模板详情
|
||||
*/
|
||||
@ -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 获取模板详情
|
||||
*/
|
||||
@ -826,42 +824,32 @@ const removeClassTeacher = (
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get growth record by ID
|
||||
* @summary 修改任务提交
|
||||
*/
|
||||
const getGrowthRecord2 = (
|
||||
id: number,
|
||||
const updateSubmission = (
|
||||
taskId: number,
|
||||
taskSubmitRequest: TaskSubmitRequest,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/parent/growth-records/${id}`, method: 'GET',
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Update growth record
|
||||
*/
|
||||
const updateGrowthRecord2 = (
|
||||
id: number,
|
||||
growthRecordUpdateRequest: GrowthRecordUpdateRequest,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/parent/growth-records/${id}`, method: 'PUT',
|
||||
{url: `/v1/parent/tasks/${taskId}/submit`, method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: growthRecordUpdateRequest,
|
||||
data: taskSubmitRequest,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Delete growth record
|
||||
* @summary 提交任务完成
|
||||
*/
|
||||
const deleteGrowthRecord2 = (
|
||||
id: number,
|
||||
const submitTask = (
|
||||
taskId: number,
|
||||
taskSubmitRequest: TaskSubmitRequest,
|
||||
) => {
|
||||
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'
|
||||
},
|
||||
);
|
||||
@ -1610,12 +1598,12 @@ const saveStudentRecord = (
|
||||
*/
|
||||
const batchSaveStudentRecords = (
|
||||
id: number,
|
||||
studentRecordRequest: StudentRecordRequest[],
|
||||
batchStudentRecordsRequest: BatchStudentRecordsRequest,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/teacher/lessons/${id}/students/batch-records`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: studentRecordRequest,
|
||||
data: batchStudentRecordsRequest,
|
||||
responseType: 'blob'
|
||||
},
|
||||
);
|
||||
@ -1650,7 +1638,7 @@ const getLessonFeedback = (
|
||||
/**
|
||||
* @summary 提交课时反馈
|
||||
*/
|
||||
const submitFeedback = (
|
||||
const submitFeedback1 = (
|
||||
id: number,
|
||||
lessonFeedbackRequest: LessonFeedbackRequest,
|
||||
) => {
|
||||
@ -1778,40 +1766,9 @@ const createTeacher = (
|
||||
*/
|
||||
const resetPassword = (
|
||||
id: number,
|
||||
params: ResetPasswordParams,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{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'
|
||||
},
|
||||
);
|
||||
@ -2157,14 +2114,17 @@ const assignStudents = (
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Complete task
|
||||
* @summary 完成任务(旧接口,兼容使用,支持 JSON body)
|
||||
*/
|
||||
const completeTask = (
|
||||
id: number,
|
||||
params: CompleteTaskParams,
|
||||
taskCompleteRequest: TaskCompleteRequest,
|
||||
params?: CompleteTaskParams,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/parent/tasks/${id}/complete`, method: 'POST',
|
||||
headers: {'Content-Type': 'application/json', },
|
||||
data: taskCompleteRequest,
|
||||
params,
|
||||
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 即将过期时刷新
|
||||
* @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 获取默认模板
|
||||
*/
|
||||
@ -2941,10 +2914,11 @@ const getCourse = (
|
||||
* @summary 获取所有课程
|
||||
*/
|
||||
const getAllCourses = (
|
||||
|
||||
params?: GetAllCoursesParams,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
{url: `/v1/teacher/courses/all`, method: 'GET',
|
||||
params,
|
||||
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
|
||||
*/
|
||||
@ -3418,7 +3461,7 @@ const getSchoolCourse = (
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get my tasks
|
||||
* @summary 获取我的任务列表(聚合多孩子任务)
|
||||
*/
|
||||
const getMyTasks = (
|
||||
params?: GetMyTasksParams,
|
||||
@ -3432,9 +3475,9 @@ const getMyTasks = (
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get task by ID
|
||||
* @summary 获取任务详情
|
||||
*/
|
||||
const getTask2 = (
|
||||
const getTask1 = (
|
||||
id: number,
|
||||
) => {
|
||||
return customMutator<Blob>(
|
||||
@ -3445,7 +3488,7 @@ const getTask2 = (
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get tasks by student ID
|
||||
* @summary 获取孩子的任务列表(含完成信息与教师评价)
|
||||
*/
|
||||
const getTasksByStudent = (
|
||||
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
|
||||
*/
|
||||
@ -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
|
||||
*/
|
||||
@ -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
|
||||
*/
|
||||
@ -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 UpdateTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTask']>>>
|
||||
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 UpdateTemplateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTemplate']>>>
|
||||
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 UpdateTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTeacher']>>>
|
||||
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 UpdateTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateTemplate1']>>>
|
||||
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 UpdateClassTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateClassTeacher']>>>
|
||||
export type RemoveClassTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['removeClassTeacher']>>>
|
||||
export type GetGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getGrowthRecord2']>>>
|
||||
export type UpdateGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateGrowthRecord2']>>>
|
||||
export type DeleteGrowthRecord2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['deleteGrowthRecord2']>>>
|
||||
export type UpdateSubmissionResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['updateSubmission']>>>
|
||||
export type SubmitTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['submitTask']>>>
|
||||
export type FindOneResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['findOne']>>>
|
||||
export type UpdateResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['update']>>>
|
||||
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 StartLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['startLesson']>>>
|
||||
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 CancelLessonResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['cancelLesson']>>>
|
||||
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 CreateTeacherResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTeacher']>>>
|
||||
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 CreateTemplate1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['createTemplate1']>>>
|
||||
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 MarkAsRead1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['markAsRead1']>>>
|
||||
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 UploadFileResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['uploadFile']>>>
|
||||
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 GetWeeklyStatsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getWeeklyStats']>>>
|
||||
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 GetAllStudentsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getAllStudents']>>>
|
||||
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 GetOverviewResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getOverview']>>>
|
||||
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 FindTenantCollectionsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['findTenantCollections']>>>
|
||||
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 GetSchoolCourseResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getSchoolCourse']>>>
|
||||
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 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 GetNotification1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getNotification1']>>>
|
||||
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 GetRecentGrowthRecordsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getRecentGrowthRecords']>>>
|
||||
export type GetMyChildrenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['getMyChildren']>>>
|
||||
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 GenerateEditTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['generateEditToken']>>>
|
||||
export type GenerateReadOnlyTokenResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getReadingPlatformAPI>['generateReadOnlyToken']>>>
|
||||
|
||||
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
export type CompleteTaskParams = {
|
||||
studentId: number;
|
||||
studentId?: number;
|
||||
content?: string;
|
||||
attachments?: string;
|
||||
};
|
||||
|
||||
@ -16,4 +16,6 @@ export interface CourseCollectionPageQueryRequest {
|
||||
pageSize?: number;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 年级(支持多个,逗号分隔) */
|
||||
gradeLevels?: string;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -20,6 +20,8 @@ export interface CoursePageQueryRequest {
|
||||
category?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 年级(支持多个,逗号分隔) */
|
||||
gradeTags?: string;
|
||||
/** 是否仅查询待审核 */
|
||||
reviewOnly?: boolean;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
import type { CourseLessonResponse } from './courseLessonResponse';
|
||||
import type { LessonTagResponse } from './lessonTagResponse';
|
||||
|
||||
/**
|
||||
* 课程响应
|
||||
@ -89,10 +90,10 @@ export interface CourseResponse {
|
||||
activitiesData?: string;
|
||||
/** 评估数据 */
|
||||
assessmentData?: string;
|
||||
/** 年级标签 */
|
||||
gradeTags?: string;
|
||||
/** 领域标签 */
|
||||
domainTags?: string;
|
||||
/** 年级标签(规范为数组,与套餐管理适用年级对齐) */
|
||||
gradeTags?: string[];
|
||||
/** 领域标签(规范为数组) */
|
||||
domainTags?: string[];
|
||||
/** 是否有集体课 */
|
||||
hasCollectiveLesson?: number;
|
||||
/** 版本号 */
|
||||
@ -129,4 +130,6 @@ export interface CourseResponse {
|
||||
updatedAt?: string;
|
||||
/** 关联的课程环节 */
|
||||
courseLessons?: CourseLessonResponse[];
|
||||
/** 课程环节标签(列表展示用,仅 name 和 lessonType) */
|
||||
lessonTags?: LessonTagResponse[];
|
||||
}
|
||||
|
||||
@ -16,6 +16,8 @@ export interface DayScheduleItem {
|
||||
className?: string;
|
||||
/** 课程包名称 */
|
||||
coursePackageName?: string;
|
||||
/** 课程类型代码 (如 DOMAIN_HEALTH) */
|
||||
lessonType?: string;
|
||||
/** 课程类型名称 */
|
||||
lessonTypeName?: string;
|
||||
/** 教师名称 */
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -10,5 +10,7 @@ export type GetCoursePageParams = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
keyword?: string;
|
||||
category?: string;
|
||||
grade?: string;
|
||||
domain?: string;
|
||||
lessonType?: string;
|
||||
};
|
||||
|
||||
@ -10,4 +10,5 @@ export type GetMyNotifications1Params = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
isRead?: number;
|
||||
notificationType?: string;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -11,5 +11,6 @@ pageNum?: number;
|
||||
pageSize?: number;
|
||||
keyword?: string;
|
||||
type?: string;
|
||||
taskType?: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
@ -10,4 +10,6 @@ export type GetTemplates1Params = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
type?: string;
|
||||
taskType?: string;
|
||||
keyword?: string;
|
||||
};
|
||||
|
||||
@ -10,4 +10,5 @@ export type GetTemplatesParams = {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
type?: string;
|
||||
keyword?: string;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -20,12 +20,16 @@ export * from './basicSettingsUpdateRequest';
|
||||
export * from './batchCreateSchedulesBody';
|
||||
export * from './batchStudentRecordsDto';
|
||||
export * from './batchStudentRecordsDtoRecordsItem';
|
||||
export * from './batchStudentRecordsRequest';
|
||||
export * from './bindStudentParams';
|
||||
export * from './calendarViewResponse';
|
||||
export * from './calendarViewResponseSchedules';
|
||||
export * from './changePasswordParams';
|
||||
export * from './checkConflictParams';
|
||||
export * from './childInfoResponse';
|
||||
export * from './childProfileResponse';
|
||||
export * from './classCreateRequest';
|
||||
export * from './classInfo';
|
||||
export * from './classResponse';
|
||||
export * from './classTeacherResponse';
|
||||
export * from './classUpdateRequest';
|
||||
@ -40,6 +44,7 @@ export * from './courseCollectionResponse';
|
||||
export * from './courseControllerFindAllParams';
|
||||
export * from './courseControllerGetReviewListParams';
|
||||
export * from './courseCreateRequest';
|
||||
export * from './courseInfo';
|
||||
export * from './courseLesson';
|
||||
export * from './courseLessonCreateRequest';
|
||||
export * from './courseLessonResponse';
|
||||
@ -95,8 +100,10 @@ export * from './getActiveTeachersParams';
|
||||
export * from './getActiveTenants200';
|
||||
export * from './getActiveTenants200DataItem';
|
||||
export * from './getActiveTenantsParams';
|
||||
export * from './getAllCoursesParams';
|
||||
export * from './getAllStudentsParams';
|
||||
export * from './getCalendarViewDataParams';
|
||||
export * from './getChildLessonsParams';
|
||||
export * from './getClassPageParams';
|
||||
export * from './getClassStudents1Params';
|
||||
export * from './getClassStudentsParams';
|
||||
@ -127,7 +134,11 @@ export * from './getRecentGrowthRecordsParams';
|
||||
export * from './getSchedules1Params';
|
||||
export * from './getSchedulesParams';
|
||||
export * from './getSchoolCoursesParams';
|
||||
export * from './getStatisticsParams';
|
||||
export * from './getStudentPageParams';
|
||||
export * from './getTaskCompletions1Params';
|
||||
export * from './getTaskCompletionsParams';
|
||||
export * from './getTaskListParams';
|
||||
export * from './getTaskPage1Params';
|
||||
export * from './getTaskPageParams';
|
||||
export * from './getTasksByStudentParams';
|
||||
@ -141,6 +152,7 @@ export * from './grantCollectionRequest';
|
||||
export * from './grantRequest';
|
||||
export * from './growthRecord';
|
||||
export * from './growthRecordCreateRequest';
|
||||
export * from './growthRecordForParentResponse';
|
||||
export * from './growthRecordResponse';
|
||||
export * from './growthRecordUpdateRequest';
|
||||
export * from './immTokenVo';
|
||||
@ -156,14 +168,17 @@ export * from './lessonFeedbackDtoActivitiesDone';
|
||||
export * from './lessonFeedbackDtoStepFeedbacks';
|
||||
export * from './lessonFeedbackRequest';
|
||||
export * from './lessonFeedbackResponse';
|
||||
export * from './lessonInfo';
|
||||
export * from './lessonProgressDto';
|
||||
export * from './lessonProgressDtoProgressData';
|
||||
export * from './lessonProgressRequest';
|
||||
export * from './lessonProgressRequestProgressData';
|
||||
export * from './lessonRecordResponse';
|
||||
export * from './lessonResponse';
|
||||
export * from './lessonStep';
|
||||
export * from './lessonStepCreateRequest';
|
||||
export * from './lessonStepResponse';
|
||||
export * from './lessonTagResponse';
|
||||
export * from './lessonTypeInfo';
|
||||
export * from './lessonUpdateRequest';
|
||||
export * from './libraryCreateRequest';
|
||||
@ -174,6 +189,7 @@ export * from './loginDto';
|
||||
export * from './loginRequest';
|
||||
export * from './loginResponse';
|
||||
export * from './notification';
|
||||
export * from './notificationForParentResponse';
|
||||
export * from './notificationResponse';
|
||||
export * from './notificationSettingsResponse';
|
||||
export * from './notificationSettingsUpdateRequest';
|
||||
@ -197,11 +213,14 @@ export * from './pageResultCourseCollectionResponse';
|
||||
export * from './pageResultCoursePackageResponse';
|
||||
export * from './pageResultCourseResponse';
|
||||
export * from './pageResultGrowthRecord';
|
||||
export * from './pageResultGrowthRecordForParentResponse';
|
||||
export * from './pageResultGrowthRecordResponse';
|
||||
export * from './pageResultLesson';
|
||||
export * from './pageResultLessonFeedbackResponse';
|
||||
export * from './pageResultLessonRecordResponse';
|
||||
export * from './pageResultLessonResponse';
|
||||
export * from './pageResultNotification';
|
||||
export * from './pageResultNotificationForParentResponse';
|
||||
export * from './pageResultNotificationResponse';
|
||||
export * from './pageResultOperationLogResponse';
|
||||
export * from './pageResultParent';
|
||||
@ -211,11 +230,14 @@ export * from './pageResultResourceItemResponse';
|
||||
export * from './pageResultResourceLibrary';
|
||||
export * from './pageResultResourceLibraryResponse';
|
||||
export * from './pageResultSchedulePlanResponse';
|
||||
export * from './pageResultSchoolCourseResponse';
|
||||
export * from './pageResultStudent';
|
||||
export * from './pageResultStudentResponse';
|
||||
export * from './pageResultTask';
|
||||
export * from './pageResultTaskCompletionDetailResponse';
|
||||
export * from './pageResultTaskResponse';
|
||||
export * from './pageResultTaskTemplateResponse';
|
||||
export * from './pageResultTaskWithCompletionResponse';
|
||||
export * from './pageResultTeacher';
|
||||
export * from './pageResultTeacherResponse';
|
||||
export * from './pageResultTenant';
|
||||
@ -247,6 +269,7 @@ export * from './resourceLibraryResponse';
|
||||
export * from './resourceLibraryUpdateRequest';
|
||||
export * from './resultBasicSettingsResponse';
|
||||
export * from './resultCalendarViewResponse';
|
||||
export * from './resultChildProfileResponse';
|
||||
export * from './resultClassResponse';
|
||||
export * from './resultClazz';
|
||||
export * from './resultConflictCheckResult';
|
||||
@ -260,6 +283,7 @@ export * from './resultCourseResponse';
|
||||
export * from './resultDto';
|
||||
export * from './resultDtoData';
|
||||
export * from './resultGrowthRecord';
|
||||
export * from './resultGrowthRecordForParentResponse';
|
||||
export * from './resultGrowthRecordResponse';
|
||||
export * from './resultImmTokenVo';
|
||||
export * from './resultLesson';
|
||||
@ -269,6 +293,7 @@ export * from './resultLessonResponse';
|
||||
export * from './resultLessonStep';
|
||||
export * from './resultLessonStepResponse';
|
||||
export * from './resultListActiveTenantItemResponse';
|
||||
export * from './resultListChildInfoResponse';
|
||||
export * from './resultListClassResponse';
|
||||
export * from './resultListClassTeacherResponse';
|
||||
export * from './resultListClazz';
|
||||
@ -281,6 +306,7 @@ export * from './resultListCoursePackageResponse';
|
||||
export * from './resultListCourseReportResponse';
|
||||
export * from './resultListCourseResponse';
|
||||
export * from './resultListGrowthRecord';
|
||||
export * from './resultListGrowthRecordForParentResponse';
|
||||
export * from './resultListGrowthRecordResponse';
|
||||
export * from './resultListLesson';
|
||||
export * from './resultListLessonResponse';
|
||||
@ -308,7 +334,10 @@ export * from './resultLoginResponse';
|
||||
export * from './resultLong';
|
||||
export * from './resultMapStringObject';
|
||||
export * from './resultMapStringObjectData';
|
||||
export * from './resultMapStringString';
|
||||
export * from './resultMapStringStringData';
|
||||
export * from './resultNotification';
|
||||
export * from './resultNotificationForParentResponse';
|
||||
export * from './resultNotificationResponse';
|
||||
export * from './resultNotificationSettingsResponse';
|
||||
export * from './resultObject';
|
||||
@ -327,11 +356,14 @@ export * from './resultPageResultCourseCollectionResponse';
|
||||
export * from './resultPageResultCoursePackageResponse';
|
||||
export * from './resultPageResultCourseResponse';
|
||||
export * from './resultPageResultGrowthRecord';
|
||||
export * from './resultPageResultGrowthRecordForParentResponse';
|
||||
export * from './resultPageResultGrowthRecordResponse';
|
||||
export * from './resultPageResultLesson';
|
||||
export * from './resultPageResultLessonFeedbackResponse';
|
||||
export * from './resultPageResultLessonRecordResponse';
|
||||
export * from './resultPageResultLessonResponse';
|
||||
export * from './resultPageResultNotification';
|
||||
export * from './resultPageResultNotificationForParentResponse';
|
||||
export * from './resultPageResultNotificationResponse';
|
||||
export * from './resultPageResultOperationLogResponse';
|
||||
export * from './resultPageResultParent';
|
||||
@ -341,11 +373,14 @@ export * from './resultPageResultResourceItemResponse';
|
||||
export * from './resultPageResultResourceLibrary';
|
||||
export * from './resultPageResultResourceLibraryResponse';
|
||||
export * from './resultPageResultSchedulePlanResponse';
|
||||
export * from './resultPageResultSchoolCourseResponse';
|
||||
export * from './resultPageResultStudent';
|
||||
export * from './resultPageResultStudentResponse';
|
||||
export * from './resultPageResultTask';
|
||||
export * from './resultPageResultTaskCompletionDetailResponse';
|
||||
export * from './resultPageResultTaskResponse';
|
||||
export * from './resultPageResultTaskTemplateResponse';
|
||||
export * from './resultPageResultTaskWithCompletionResponse';
|
||||
export * from './resultPageResultTeacher';
|
||||
export * from './resultPageResultTeacherResponse';
|
||||
export * from './resultPageResultTenant';
|
||||
@ -358,6 +393,7 @@ export * from './resultResourceItemResponse';
|
||||
export * from './resultResourceLibrary';
|
||||
export * from './resultResourceLibraryResponse';
|
||||
export * from './resultSchedulePlanResponse';
|
||||
export * from './resultSchoolCourseResponse';
|
||||
export * from './resultSchoolSettingsResponse';
|
||||
export * from './resultSecuritySettingsResponse';
|
||||
export * from './resultStatsResponse';
|
||||
@ -365,8 +401,11 @@ export * from './resultStatsTrendResponse';
|
||||
export * from './resultString';
|
||||
export * from './resultStudent';
|
||||
export * from './resultStudentRecordResponse';
|
||||
export * from './resultStudentRecordsResponse';
|
||||
export * from './resultStudentResponse';
|
||||
export * from './resultTask';
|
||||
export * from './resultTaskCompletionDetailResponse';
|
||||
export * from './resultTaskFeedbackResponse';
|
||||
export * from './resultTaskResponse';
|
||||
export * from './resultTaskTemplateResponse';
|
||||
export * from './resultTeacher';
|
||||
@ -387,6 +426,7 @@ export * from './schedulePlanCreateRequest';
|
||||
export * from './schedulePlanResponse';
|
||||
export * from './schedulePlanUpdateRequest';
|
||||
export * from './schoolControllerImportStudentsParams';
|
||||
export * from './schoolCourseResponse';
|
||||
export * from './schoolFeedbackControllerFindAllParams';
|
||||
export * from './schoolSettingsResponse';
|
||||
export * from './schoolSettingsUpdateRequest';
|
||||
@ -396,24 +436,35 @@ export * from './securitySettingsUpdateRequest';
|
||||
export * from './statsControllerGetActiveTeachersParams';
|
||||
export * from './statsControllerGetLessonTrendParams';
|
||||
export * from './statsControllerGetRecentActivitiesParams';
|
||||
export * from './statsInfo';
|
||||
export * from './statsResponse';
|
||||
export * from './statsTrendResponse';
|
||||
export * from './stepCreateRequest';
|
||||
export * from './student';
|
||||
export * from './studentCreateRequest';
|
||||
export * from './studentInfo';
|
||||
export * from './studentRecordDto';
|
||||
export * from './studentRecordRequest';
|
||||
export * from './studentRecordResponse';
|
||||
export * from './studentRecordsResponse';
|
||||
export * from './studentReportResponse';
|
||||
export * from './studentResponse';
|
||||
export * from './studentUpdateRequest';
|
||||
export * from './studentWithRecordResponse';
|
||||
export * from './submitCourseDto';
|
||||
export * from './task';
|
||||
export * from './taskCompleteRequest';
|
||||
export * from './taskCompletionDetailResponse';
|
||||
export * from './taskCreateRequest';
|
||||
export * from './taskFeedbackRequest';
|
||||
export * from './taskFeedbackResponse';
|
||||
export * from './taskInfo';
|
||||
export * from './taskResponse';
|
||||
export * from './taskSubmitRequest';
|
||||
export * from './taskTemplateCreateRequest';
|
||||
export * from './taskTemplateResponse';
|
||||
export * from './taskUpdateRequest';
|
||||
export * from './taskWithCompletionResponse';
|
||||
export * from './teacher';
|
||||
export * from './teacherCourseControllerFindAllParams';
|
||||
export * from './teacherCourseControllerGetAllStudentsParams';
|
||||
@ -425,6 +476,7 @@ export * from './teacherCreateRequest';
|
||||
export * from './teacherFeedbackControllerFindAllParams';
|
||||
export * from './teacherReportResponse';
|
||||
export * from './teacherResponse';
|
||||
export * from './teacherResponseClassNames';
|
||||
export * from './teacherTaskControllerGetMonthlyStatsParams';
|
||||
export * from './teacherUpdateRequest';
|
||||
export * from './tenant';
|
||||
|
||||
@ -15,5 +15,7 @@ import type { LessonResponse } from './lessonResponse';
|
||||
export interface LessonDetailResponse {
|
||||
lesson?: LessonResponse;
|
||||
course?: CourseResponse;
|
||||
/** 排课选择的课程类型(子课程模式时用于直接进入对应子课程) */
|
||||
lessonType?: string;
|
||||
class?: ClassResponse;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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 {
|
||||
/** 记录ID(student_record.id) */
|
||||
id?: number;
|
||||
lesson?: LessonInfo;
|
||||
/** 专注度评分 */
|
||||
focus?: number;
|
||||
/** 参与度评分 */
|
||||
participation?: number;
|
||||
/** 兴趣度评分 */
|
||||
interest?: number;
|
||||
/** 理解度评分 */
|
||||
understanding?: number;
|
||||
/** 备注 */
|
||||
notes?: string;
|
||||
/** 创建时间 */
|
||||
createdAt?: string;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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};
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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[];
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -28,6 +28,8 @@ export interface Task {
|
||||
description?: string;
|
||||
/** 任务类型 */
|
||||
type?: string;
|
||||
/** 关联绘本名称 */
|
||||
relatedBookName?: string;
|
||||
/** 课程 ID */
|
||||
courseId?: number;
|
||||
/** 创建人 ID */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -16,12 +16,20 @@ export interface TaskCreateRequest {
|
||||
description?: string;
|
||||
/** 任务类型:reading-阅读,homework-作业,activity-活动 */
|
||||
type?: string;
|
||||
/** 任务类型(前端兼容 taskType) */
|
||||
taskType?: string;
|
||||
/** 关联绘本名称(手动填写) */
|
||||
relatedBookName?: string;
|
||||
/** 课程 ID */
|
||||
courseId?: number;
|
||||
/** 课程 ID(前端兼容 relatedCourseId) */
|
||||
relatedCourseId?: number;
|
||||
/** 开始日期 */
|
||||
startDate?: string;
|
||||
/** 截止日期 */
|
||||
dueDate?: string;
|
||||
/** 截止日期(前端兼容 endDate) */
|
||||
endDate?: string;
|
||||
/** 附件(JSON 数组) */
|
||||
attachments?: string;
|
||||
/** 目标类型:class-班级,student-学生 */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -20,6 +20,8 @@ export interface TaskResponse {
|
||||
description?: string;
|
||||
/** 任务类型 */
|
||||
type?: string;
|
||||
/** 关联绘本名称 */
|
||||
relatedBookName?: string;
|
||||
/** 课程 ID */
|
||||
courseId?: number;
|
||||
/** 创建人 ID */
|
||||
@ -34,6 +36,10 @@ export interface TaskResponse {
|
||||
status?: string;
|
||||
/** 附件 */
|
||||
attachments?: string;
|
||||
/** 目标类型:CLASS-班级,STUDENT-学生 */
|
||||
targetType?: string;
|
||||
/** 目标 IDs(班级或学生) */
|
||||
targetIds?: number[];
|
||||
/** 创建时间 */
|
||||
createdAt?: string;
|
||||
/** 更新时间 */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -23,9 +23,9 @@ export interface TaskTemplateCreateRequest {
|
||||
/** 默认持续时间 (天) */
|
||||
defaultDuration?: number;
|
||||
/** 是否默认模板 */
|
||||
isDefault?: number;
|
||||
isDefault?: boolean;
|
||||
/** 模板内容 */
|
||||
content?: string;
|
||||
/** 是否公开 */
|
||||
isPublic?: number;
|
||||
isPublic?: boolean;
|
||||
}
|
||||
|
||||
@ -16,6 +16,14 @@ export interface TaskUpdateRequest {
|
||||
description?: string;
|
||||
/** 任务类型 */
|
||||
type?: string;
|
||||
/** 任务类型(前端兼容 taskType) */
|
||||
taskType?: string;
|
||||
/** 关联绘本名称 */
|
||||
relatedBookName?: string;
|
||||
/** 课程 ID */
|
||||
courseId?: number;
|
||||
/** 课程 ID(前端兼容 relatedCourseId) */
|
||||
relatedCourseId?: number;
|
||||
/** 开始日期 */
|
||||
startDate?: string;
|
||||
/** 截止日期 */
|
||||
@ -24,4 +32,8 @@ export interface TaskUpdateRequest {
|
||||
status?: string;
|
||||
/** 附件(JSON 数组) */
|
||||
attachments?: string;
|
||||
/** 目标类型:CLASS-班级,STUDENT-学生 */
|
||||
targetType?: string;
|
||||
/** 目标 IDs */
|
||||
targetIds?: number[];
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -10,18 +10,20 @@
|
||||
* 教师创建请求
|
||||
*/
|
||||
export interface TeacherCreateRequest {
|
||||
/** 用户名 */
|
||||
/** 用户名/登录账号 */
|
||||
username: string;
|
||||
/** 密码 */
|
||||
password: string;
|
||||
/** 姓名 */
|
||||
name: string;
|
||||
/** 电话 */
|
||||
phone?: string;
|
||||
phone: string;
|
||||
/** 邮箱 */
|
||||
email?: string;
|
||||
/** 性别 */
|
||||
gender?: string;
|
||||
/** 简介 */
|
||||
bio?: string;
|
||||
/** 负责班级ID列表 */
|
||||
classIds?: number[];
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
* Reading Platform Backend Service API Documentation
|
||||
* OpenAPI spec version: 1.0.0
|
||||
*/
|
||||
import type { TeacherResponseClassNames } from './teacherResponseClassNames';
|
||||
|
||||
/**
|
||||
* 教师响应
|
||||
@ -14,8 +15,6 @@ export interface TeacherResponse {
|
||||
id?: number;
|
||||
/** 租户 ID */
|
||||
tenantId?: number;
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
/** 姓名 */
|
||||
name?: string;
|
||||
/** 电话 */
|
||||
@ -30,10 +29,18 @@ export interface TeacherResponse {
|
||||
bio?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 负责班级ID列表 */
|
||||
classIds?: number[];
|
||||
/** 负责班级名称 */
|
||||
classNames?: TeacherResponseClassNames;
|
||||
/** 授课次数 */
|
||||
lessonCount?: number;
|
||||
/** 最后登录时间 */
|
||||
lastLoginAt?: string;
|
||||
/** 创建时间 */
|
||||
createdAt?: string;
|
||||
/** 更新时间 */
|
||||
updatedAt?: string;
|
||||
/** 登录账号 */
|
||||
loginAccount?: string;
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
@ -24,4 +24,6 @@ export interface TeacherUpdateRequest {
|
||||
bio?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 负责班级ID列表 */
|
||||
classIds?: number[];
|
||||
}
|
||||
|
||||
@ -26,8 +26,6 @@ export interface TenantCreateRequest {
|
||||
address?: string;
|
||||
/** Logo URL */
|
||||
logoUrl?: string;
|
||||
/** 套餐类型 */
|
||||
packageType?: string;
|
||||
/** 教师配额 */
|
||||
teacherQuota?: number;
|
||||
/** 学生配额 */
|
||||
@ -36,6 +34,6 @@ export interface TenantCreateRequest {
|
||||
startDate?: string;
|
||||
/** 结束日期 */
|
||||
expireDate?: string;
|
||||
/** 课程套餐 ID(可选) */
|
||||
collectionId?: number;
|
||||
/** 课程套餐 ID 列表(可选,支持多选) */
|
||||
collectionIds?: number[];
|
||||
}
|
||||
|
||||
@ -36,8 +36,8 @@ export interface TenantResponse {
|
||||
maxStudents?: number;
|
||||
/** 最大教师数 */
|
||||
maxTeachers?: number;
|
||||
/** 套餐类型 */
|
||||
packageType?: string;
|
||||
/** 套餐名称列表 */
|
||||
packageNames?: string[];
|
||||
/** 教师配额 */
|
||||
teacherQuota?: number;
|
||||
/** 学生配额 */
|
||||
|
||||
@ -24,10 +24,8 @@ export interface TenantUpdateRequest {
|
||||
logoUrl?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 套餐类型 */
|
||||
packageType?: string;
|
||||
/** 课程套餐ID(用于三层架构) */
|
||||
collectionId?: number;
|
||||
collectionIds?: number[];
|
||||
/** 教师配额 */
|
||||
teacherQuota?: number;
|
||||
/** 学生配额 */
|
||||
|
||||
@ -92,6 +92,7 @@ export interface TaskSubmitRequest {
|
||||
// 带完成信息的任务(兼容旧接口)
|
||||
export interface TaskWithCompletion {
|
||||
id: number;
|
||||
studentId?: number; // 学生ID,多孩子聚合时用于标识任务归属
|
||||
status: TaskCompletionStatus;
|
||||
completedAt?: string;
|
||||
feedback?: string;
|
||||
@ -154,23 +155,41 @@ export const getChildProfile = (childId: number): Promise<ChildProfile> =>
|
||||
|
||||
export const getChildLessons = (
|
||||
childId: number,
|
||||
params?: { pageNum?: number; pageSize?: number }
|
||||
params?: { pageNum?: number; pageSize?: number; page?: 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 ====================
|
||||
|
||||
/** 获取我的任务列表(聚合多孩子任务,无 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 = (
|
||||
childId: number,
|
||||
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/student/${childId}`, { params })
|
||||
.then(res => ({
|
||||
items: res.list || [],
|
||||
total: res.total || 0,
|
||||
page: res.pageNum || 1,
|
||||
pageSize: res.pageSize || 10,
|
||||
}));
|
||||
http.get<{ list: TaskWithCompletion[]; total: number; pageNum: number; pageSize: number }>(`/v1/parent/tasks/student/${childId}`, { params }).then(res => ({
|
||||
items: res.list || [],
|
||||
total: res.total || 0,
|
||||
page: res.pageNum || 1,
|
||||
pageSize: res.pageSize || 10,
|
||||
}));
|
||||
|
||||
// 提交任务完成(新版)
|
||||
export const submitTaskCompletion = (
|
||||
@ -233,8 +252,18 @@ export const getNotifications = (
|
||||
unreadCount: number;
|
||||
page: 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 => ({
|
||||
items: res.list || [],
|
||||
total: res.total || 0,
|
||||
@ -242,6 +271,7 @@ export const getNotifications = (
|
||||
page: res.pageNum || 1,
|
||||
pageSize: res.pageSize || 10,
|
||||
}));
|
||||
};
|
||||
|
||||
export const getUnreadCount = (): Promise<number> =>
|
||||
http.get<number>('/v1/parent/notifications/unread-count').then(res => res || 0);
|
||||
|
||||
@ -338,6 +338,24 @@ export const getCourseCollections = () =>
|
||||
export const getCourseCollectionPackages = (collectionId: number | string) =>
|
||||
http.get<CoursePackageItem[]>(`/v1/school/packages/${collectionId}/packages`);
|
||||
|
||||
// 获取课程包详情(包含课程环节列表)
|
||||
export interface CoursePackageDetail {
|
||||
id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
courses: Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
lessonType?: string;
|
||||
gradeLevel?: string;
|
||||
sortOrder?: number;
|
||||
scheduleRefData?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const getCoursePackageDetail = (packageId: number | string) =>
|
||||
http.get<CoursePackageDetail>(`/v1/school/packages/packages/${packageId}/courses`);
|
||||
|
||||
// 续费课程套餐(三层架构)
|
||||
export const renewCollection = (collectionId: number, data: RenewPackageDto) =>
|
||||
http.post<void>(`/v1/school/packages/${collectionId}/renew`, data);
|
||||
@ -423,8 +441,8 @@ export const getSchoolCourseList = (params?: {
|
||||
}) =>
|
||||
http.get<{ list: Course[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/courses', { params });
|
||||
|
||||
export const getSchoolCourse = (id: number) =>
|
||||
http.get<Course>(`/v1/school/courses/${id}`);
|
||||
export const getSchoolCourse = (id: number | string): Promise<any> =>
|
||||
http.get(`/v1/school/courses/${id}`) as any;
|
||||
|
||||
// ==================== 班级教师管理 ====================
|
||||
|
||||
@ -1260,8 +1278,13 @@ export const getParents = (params?: ParentQueryParams) =>
|
||||
export const getParent = (id: number) =>
|
||||
http.get<Parent>(`/v1/school/parents/${id}`);
|
||||
|
||||
export const createParent = (data: CreateParentDto) =>
|
||||
http.post<Parent>('/v1/school/parents', data);
|
||||
export const createParent = (data: CreateParentDto) => {
|
||||
const { loginAccount, ...rest } = data;
|
||||
return http.post<Parent>('/v1/school/parents', {
|
||||
...rest,
|
||||
username: loginAccount,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateParent = (id: number, data: UpdateParentDto) =>
|
||||
http.put<Parent>(`/v1/school/parents/${id}`, data);
|
||||
@ -1273,8 +1296,8 @@ export const resetParentPassword = (id: number) =>
|
||||
http.post<{ tempPassword: string }>(`/v1/school/parents/${id}/reset-password`);
|
||||
|
||||
export const getParentChildren = async (parentId: number): Promise<ParentChild[]> => {
|
||||
const parent = await http.get<Parent & { children: ParentChild[] }>(`/v1/school/parents/${parentId}`);
|
||||
return parent.children || [];
|
||||
const list = await http.get<ParentChild[]>(`/v1/school/parents/${parentId}/children`);
|
||||
return Array.isArray(list) ? list : [];
|
||||
};
|
||||
|
||||
export const addChildToParent = (parentId: number, data: AddChildDto) =>
|
||||
|
||||
@ -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<{
|
||||
items: Array<{
|
||||
id: number;
|
||||
@ -151,7 +152,13 @@ export function getTeacherClassStudents(classId: number, params?: { pageNum?: nu
|
||||
pageSize: params?.pageSize,
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
// 获取班级教师列表
|
||||
@ -819,9 +826,9 @@ export interface CreateTaskFromTemplateDto {
|
||||
}
|
||||
|
||||
export const getTaskTemplates = (params?: { pageNum?: number; pageSize?: number }) =>
|
||||
http.get<{ records: any[]; total: number }>('/v1/teacher/task-templates', { params })
|
||||
http.get<{ list: any[]; total: number }>('/v1/teacher/task-templates', { params })
|
||||
.then(res => ({
|
||||
items: res.records || [],
|
||||
items: res.list || [],
|
||||
total: res.total || 0,
|
||||
}));
|
||||
|
||||
|
||||
@ -162,8 +162,8 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: 'courses',
|
||||
name: 'SchoolCourses',
|
||||
component: () => import('@/views/school/courses/CourseListView.vue'),
|
||||
meta: { title: '课程管理' },
|
||||
component: () => import('@/views/school/courses-new/CourseCenterView.vue'),
|
||||
meta: { title: '课程中心' },
|
||||
},
|
||||
{
|
||||
path: 'courses/:id',
|
||||
@ -276,7 +276,7 @@ const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: 'courses',
|
||||
name: 'TeacherCourses',
|
||||
component: () => import('@/views/teacher/courses/CourseListView.vue'),
|
||||
component: () => import('@/views/teacher/courses-new/CourseCenterView.vue'),
|
||||
meta: { title: '课程中心' },
|
||||
},
|
||||
{
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<span class="stat-value">{{ statsData.studentCount }}</span>
|
||||
<span class="stat-label">覆盖学生</span>
|
||||
<span class="stat-label">学生总数</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -82,9 +82,9 @@
|
||||
<div class="rank-number" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
|
||||
<div class="rank-content">
|
||||
<span class="rank-name">{{ item.name }}</span>
|
||||
<span class="rank-desc">活跃用户: {{ item.lessonCount }}</span>
|
||||
<span class="rank-desc">活跃教师:{{ item.activeTeacherCount }} 人</span>
|
||||
</div>
|
||||
<a-tag color="blue">{{ item.lessonCount }} 课程</a-tag>
|
||||
<a-tag color="blue">{{ item.completedLessonCount }} 课次</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty v-else description="暂无数据" />
|
||||
@ -292,6 +292,7 @@ const initTrendChart = (data: TrendData[]) => {
|
||||
type: 'value',
|
||||
name: '授课次数',
|
||||
position: 'left',
|
||||
interval: 1,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#F3F4F6',
|
||||
@ -302,6 +303,7 @@ const initTrendChart = (data: TrendData[]) => {
|
||||
type: 'value',
|
||||
name: '学生数',
|
||||
position: 'right',
|
||||
interval: 1,
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
|
||||
@ -11,9 +11,15 @@
|
||||
<p>关注孩子的阅读成长,陪伴每一步进步!</p>
|
||||
</div>
|
||||
<div class="banner-decorations">
|
||||
<span class="decoration"><BookOutlined /></span>
|
||||
<span class="decoration"><StarOutlined /></span>
|
||||
<span class="decoration"><HeartOutlined /></span>
|
||||
<span class="decoration">
|
||||
<BookOutlined />
|
||||
</span>
|
||||
<span class="decoration">
|
||||
<StarOutlined />
|
||||
</span>
|
||||
<span class="decoration">
|
||||
<HeartOutlined />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -22,15 +28,12 @@
|
||||
<!-- 我的孩子 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h3><TeamOutlined /> 我的孩子</h3>
|
||||
<h3>
|
||||
<TeamOutlined /> 我的孩子
|
||||
</h3>
|
||||
</div>
|
||||
<div class="children-grid" v-if="children.length > 0">
|
||||
<div
|
||||
v-for="child in children"
|
||||
:key="child.id"
|
||||
class="child-card"
|
||||
@click="goToChildDetail(child.id)"
|
||||
>
|
||||
<div v-for="child in children" :key="child.id" class="child-card" @click="goToChildDetail(child.id)">
|
||||
<div class="child-avatar">
|
||||
<a-avatar :size="isMobile ? 56 : 64" :style="{ backgroundColor: getAvatarColor(child.id) }">
|
||||
{{ child.name.charAt(0) }}
|
||||
@ -43,8 +46,12 @@
|
||||
</div>
|
||||
<div class="child-class">{{ child.class?.name || '未分班' }}</div>
|
||||
<div class="child-stats">
|
||||
<span><BookOutlined /> {{ child.readingCount }} 次阅读</span>
|
||||
<span><ReadOutlined /> {{ child.lessonCount }} 节课</span>
|
||||
<span>
|
||||
<BookOutlined /> {{ child.readingCount }} 次阅读
|
||||
</span>
|
||||
<span>
|
||||
<ReadOutlined /> {{ child.lessonCount }} 节课
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-arrow">
|
||||
@ -62,11 +69,13 @@
|
||||
<!-- 最近任务 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h3><CheckSquareOutlined /> 最近任务</h3>
|
||||
<h3>
|
||||
<CheckSquareOutlined /> 最近任务
|
||||
</h3>
|
||||
<a-button type="link" @click="goToTasks" class="view-all-btn">查看全部</a-button>
|
||||
</div>
|
||||
<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)">
|
||||
{{ getStatusText(task.status) }}
|
||||
</div>
|
||||
@ -87,7 +96,9 @@
|
||||
<!-- 成长档案 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<h3><FileImageOutlined /> 成长档案</h3>
|
||||
<h3>
|
||||
<FileImageOutlined /> 成长档案
|
||||
</h3>
|
||||
<a-button type="link" @click="goToGrowth" class="view-all-btn">查看全部</a-button>
|
||||
</div>
|
||||
<div class="growth-list" v-if="recentGrowth.length > 0">
|
||||
@ -126,7 +137,7 @@ import {
|
||||
RightOutlined,
|
||||
InboxOutlined,
|
||||
} 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';
|
||||
|
||||
const router = useRouter();
|
||||
@ -153,8 +164,8 @@ const getRelationshipText = (relationship: string) => {
|
||||
|
||||
const statusMap: Record<string, { text: string; class: string }> = {
|
||||
PENDING: { text: '待完成', class: 'status-pending' },
|
||||
IN_PROGRESS: { text: '进行中', class: 'status-progress' },
|
||||
COMPLETED: { text: '已完成', class: 'status-completed' },
|
||||
SUBMITTED: { text: '已提交', class: 'status-progress' },
|
||||
REVIEWED: { text: '已评价', class: 'status-completed' },
|
||||
};
|
||||
|
||||
const getStatusText = (status: string) => statusMap[status]?.text || status;
|
||||
@ -171,11 +182,13 @@ const goToChildDetail = (childId: number) => {
|
||||
};
|
||||
|
||||
const goToTasks = () => {
|
||||
router.push('/parent/tasks');
|
||||
router.push('/parent/tasks'); // 任务页支持聚合视图,无需 childId
|
||||
};
|
||||
|
||||
const goToGrowth = () => {
|
||||
router.push('/parent/growth');
|
||||
// 成长档案需要指定孩子,有孩子时传第一个
|
||||
const childId = children.value[0]?.id;
|
||||
router.push(childId ? `/parent/growth?childId=${childId}` : '/parent/growth');
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
@ -184,9 +197,16 @@ const loadData = async () => {
|
||||
const data = await getChildren();
|
||||
children.value = data;
|
||||
|
||||
// 如果有孩子,加载第一个孩子的任务和成长记录
|
||||
// 加载最近任务(聚合多孩子)
|
||||
const tasksRes = await getMyTasks({ pageNum: 1, pageSize: 5 });
|
||||
recentTasks.value = tasksRes.items || [];
|
||||
|
||||
// 加载第一个孩子的最近成长档案
|
||||
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) {
|
||||
message.error(error.response?.data?.message || '加载数据失败');
|
||||
@ -212,6 +232,7 @@ $primary-light: #f6ffed;
|
||||
$primary-dark: #389e0d;
|
||||
|
||||
.parent-dashboard {
|
||||
|
||||
// 欢迎横幅
|
||||
.welcome-banner {
|
||||
background: linear-gradient(135deg, $primary-color 0%, #73d13d 100%);
|
||||
@ -550,6 +571,7 @@ $primary-dark: #389e0d;
|
||||
|
||||
.task-content,
|
||||
.growth-content {
|
||||
|
||||
.task-title,
|
||||
.growth-title {
|
||||
font-size: 13px;
|
||||
|
||||
@ -1,14 +1,8 @@
|
||||
<template>
|
||||
<a-layout :class="['parent-layout', { 'is-collapsed': collapsed }]">
|
||||
<!-- 桌面端侧边栏 -->
|
||||
<a-layout-sider
|
||||
v-if="!isMobile"
|
||||
v-model:collapsed="collapsed"
|
||||
:trigger="null"
|
||||
collapsible
|
||||
class="parent-sider"
|
||||
:width="220"
|
||||
>
|
||||
<a-layout-sider v-if="!isMobile" v-model:collapsed="collapsed" :trigger="null" collapsible class="parent-sider"
|
||||
:width="220">
|
||||
<div class="logo">
|
||||
<img src="/logo.png" alt="Logo" class="logo-img" />
|
||||
<div v-if="!collapsed" class="logo-text">
|
||||
@ -18,47 +12,45 @@
|
||||
</div>
|
||||
|
||||
<div class="sider-menu-wrapper">
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
mode="inline"
|
||||
theme="light"
|
||||
:inline-collapsed="collapsed"
|
||||
@click="handleMenuClick"
|
||||
class="side-menu"
|
||||
>
|
||||
<a-menu-item key="dashboard">
|
||||
<template #icon><HomeOutlined /></template>
|
||||
<span>首页</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="children">
|
||||
<template #icon><TeamOutlined /></template>
|
||||
<span>我的孩子</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="lessons">
|
||||
<template #icon><BookOutlined /></template>
|
||||
<span>阅读记录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="tasks">
|
||||
<template #icon><CheckSquareOutlined /></template>
|
||||
<span>阅读任务</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="growth">
|
||||
<template #icon><FileImageOutlined /></template>
|
||||
<span>成长档案</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<a-menu v-model:selectedKeys="selectedKeys" mode="inline" theme="light" :inline-collapsed="collapsed"
|
||||
@click="handleMenuClick" class="side-menu">
|
||||
<a-menu-item key="dashboard">
|
||||
<template #icon>
|
||||
<HomeOutlined />
|
||||
</template>
|
||||
<span>首页</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="children">
|
||||
<template #icon>
|
||||
<TeamOutlined />
|
||||
</template>
|
||||
<span>我的孩子</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="lessons">
|
||||
<template #icon>
|
||||
<BookOutlined />
|
||||
</template>
|
||||
<span>阅读记录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="tasks">
|
||||
<template #icon>
|
||||
<CheckSquareOutlined />
|
||||
</template>
|
||||
<span>阅读任务</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="growth">
|
||||
<template #icon>
|
||||
<FileImageOutlined />
|
||||
</template>
|
||||
<span>成长档案</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
|
||||
<!-- 移动端抽屉菜单 -->
|
||||
<a-drawer
|
||||
v-if="isMobile"
|
||||
v-model:open="drawerVisible"
|
||||
placement="left"
|
||||
:closable="false"
|
||||
:width="260"
|
||||
class="mobile-drawer"
|
||||
>
|
||||
<a-drawer v-if="isMobile" v-model:open="drawerVisible" placement="left" :closable="false" :width="260"
|
||||
class="mobile-drawer">
|
||||
<template #title>
|
||||
<div class="drawer-header">
|
||||
<img src="/logo.png" alt="Logo" class="drawer-logo" />
|
||||
@ -68,35 +60,42 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
mode="inline"
|
||||
theme="light"
|
||||
@click="handleMobileMenuClick"
|
||||
class="drawer-menu"
|
||||
>
|
||||
<a-menu v-model:selectedKeys="selectedKeys" mode="inline" theme="light" @click="handleMobileMenuClick"
|
||||
class="drawer-menu">
|
||||
<a-menu-item key="dashboard">
|
||||
<template #icon><HomeOutlined /></template>
|
||||
<template #icon>
|
||||
<HomeOutlined />
|
||||
</template>
|
||||
<span>首页</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="children">
|
||||
<template #icon><TeamOutlined /></template>
|
||||
<template #icon>
|
||||
<TeamOutlined />
|
||||
</template>
|
||||
<span>我的孩子</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="lessons">
|
||||
<template #icon><BookOutlined /></template>
|
||||
<template #icon>
|
||||
<BookOutlined />
|
||||
</template>
|
||||
<span>阅读记录</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="tasks">
|
||||
<template #icon><CheckSquareOutlined /></template>
|
||||
<template #icon>
|
||||
<CheckSquareOutlined />
|
||||
</template>
|
||||
<span>阅读任务</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="growth">
|
||||
<template #icon><FileImageOutlined /></template>
|
||||
<template #icon>
|
||||
<FileImageOutlined />
|
||||
</template>
|
||||
<span>成长档案</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="profile">
|
||||
<template #icon><UserOutlined /></template>
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
<span>个人信息</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@ -112,16 +111,8 @@
|
||||
<!-- 桌面端顶部栏 -->
|
||||
<a-layout-header v-if="!isMobile" class="parent-header">
|
||||
<div class="header-left">
|
||||
<MenuUnfoldOutlined
|
||||
v-if="collapsed"
|
||||
class="trigger"
|
||||
@click="collapsed = !collapsed"
|
||||
/>
|
||||
<MenuFoldOutlined
|
||||
v-else
|
||||
class="trigger"
|
||||
@click="collapsed = !collapsed"
|
||||
/>
|
||||
<MenuUnfoldOutlined v-if="collapsed" class="trigger" @click="collapsed = !collapsed" />
|
||||
<MenuFoldOutlined v-else class="trigger" @click="collapsed = !collapsed" />
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
@ -129,7 +120,9 @@
|
||||
<a-dropdown>
|
||||
<a-space class="user-info" style="cursor: pointer;">
|
||||
<a-avatar :size="32" class="user-avatar">
|
||||
<template #icon><UserOutlined /></template>
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
<span class="user-name">{{ userName }}</span>
|
||||
<DownOutlined />
|
||||
@ -166,12 +159,8 @@
|
||||
|
||||
<!-- 移动端底部导航 -->
|
||||
<div v-if="isMobile" class="mobile-bottom-nav">
|
||||
<div
|
||||
v-for="nav in navItems"
|
||||
:key="nav.key"
|
||||
:class="['nav-item', { active: selectedKeys[0] === nav.key }]"
|
||||
@click="handleNavClick(nav.key)"
|
||||
>
|
||||
<div v-for="nav in navItems" :key="nav.key" :class="['nav-item', { active: selectedKeys[0] === nav.key }]"
|
||||
@click="handleNavClick(nav.key)">
|
||||
<component :is="nav.icon" class="nav-icon" />
|
||||
<span class="nav-text">{{ nav.text }}</span>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,23 @@
|
||||
<template>
|
||||
<div class="growth-record-view">
|
||||
<div class="page-header">
|
||||
<h2><FileImageOutlined /> 成长档案</h2>
|
||||
<p class="page-desc">记录孩子成长的每一个精彩瞬间</p>
|
||||
<div class="header-left">
|
||||
<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>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
@ -22,7 +37,9 @@
|
||||
<h3>{{ record.title }}</h3>
|
||||
<p v-if="record.content">{{ record.content }}</p>
|
||||
<div class="card-meta">
|
||||
<span><CalendarOutlined /> {{ formatDate(record.recordDate) }}</span>
|
||||
<span>
|
||||
<CalendarOutlined /> {{ formatDate(record.recordDate) }}
|
||||
</span>
|
||||
<span v-if="record.class">
|
||||
<TeamOutlined /> {{ record.class.name }}
|
||||
</span>
|
||||
@ -38,18 +55,13 @@
|
||||
</a-spin>
|
||||
|
||||
<div class="pagination-section" v-if="total > pageSize">
|
||||
<a-pagination
|
||||
v-model:current="currentPage"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
@change="onPageChange"
|
||||
/>
|
||||
<a-pagination v-model:current="currentPage" :total="total" :page-size="pageSize" @change="onPageChange" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
@ -59,7 +71,7 @@ import {
|
||||
TeamOutlined,
|
||||
InboxOutlined,
|
||||
} 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';
|
||||
|
||||
const route = useRoute();
|
||||
@ -69,7 +81,9 @@ const records = ref<GrowthRecord[]>([]);
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(12);
|
||||
const children = ref<ChildInfo[]>([]);
|
||||
const currentChildId = ref<number | null>(null);
|
||||
const selectedChildId = ref<number | null>(null);
|
||||
|
||||
const formatDate = (date: string) => dayjs(date).format('YYYY-MM-DD');
|
||||
|
||||
@ -78,25 +92,32 @@ const onPageChange = (page: number) => {
|
||||
loadRecords();
|
||||
};
|
||||
|
||||
const onChildChange = (val: number) => {
|
||||
router.replace({ query: { childId: String(val) } });
|
||||
currentPage.value = 1;
|
||||
loadRecords();
|
||||
};
|
||||
|
||||
const loadRecords = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
let childId = route.query.childId ? Number(route.query.childId) : null;
|
||||
|
||||
// 如果没有指定childId,自动获取第一个孩子
|
||||
if (!childId) {
|
||||
const children = await getChildren();
|
||||
if (children && children.length > 0) {
|
||||
childId = children[0].id;
|
||||
router.replace({ query: { childId: String(childId) } });
|
||||
} else {
|
||||
records.value = [];
|
||||
total.value = 0;
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
// 加载孩子列表
|
||||
children.value = await getChildren();
|
||||
if (children.value.length === 0) {
|
||||
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;
|
||||
|
||||
const data = await getChildGrowthRecords(childId, {
|
||||
@ -115,6 +136,8 @@ const loadRecords = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => route.query.childId, () => loadRecords());
|
||||
|
||||
onMounted(() => {
|
||||
loadRecords();
|
||||
});
|
||||
@ -127,6 +150,15 @@ $primary-light: #f6ffed;
|
||||
.growth-record-view {
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 8px;
|
||||
|
||||
@ -1,8 +1,23 @@
|
||||
<template>
|
||||
<div class="lesson-history-view">
|
||||
<div class="page-header">
|
||||
<h2><BookOutlined /> 阅读记录</h2>
|
||||
<p class="page-desc">查看孩子的阅读学习记录</p>
|
||||
<div class="header-left">
|
||||
<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>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
@ -61,18 +76,13 @@
|
||||
</a-spin>
|
||||
|
||||
<div class="pagination-section" v-if="total > pageSize">
|
||||
<a-pagination
|
||||
v-model:current="currentPage"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
@change="onPageChange"
|
||||
/>
|
||||
<a-pagination v-model:current="currentPage" :total="total" :page-size="pageSize" @change="onPageChange" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
@ -82,7 +92,7 @@ import {
|
||||
FileTextOutlined,
|
||||
InboxOutlined,
|
||||
} 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';
|
||||
|
||||
const route = useRoute();
|
||||
@ -92,7 +102,9 @@ const records = ref<LessonRecord[]>([]);
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const children = ref<ChildInfo[]>([]);
|
||||
const currentChildId = ref<number | null>(null);
|
||||
const selectedChildId = ref<number | null>(null);
|
||||
|
||||
const formatDateTime = (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm');
|
||||
|
||||
@ -101,23 +113,32 @@ const onPageChange = (page: number) => {
|
||||
loadRecords();
|
||||
};
|
||||
|
||||
const onChildChange = (val: number) => {
|
||||
router.replace({ query: { childId: String(val) } });
|
||||
currentPage.value = 1;
|
||||
loadRecords();
|
||||
};
|
||||
|
||||
const loadRecords = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
let childId = route.query.childId ? Number(route.query.childId) : null;
|
||||
|
||||
// 如果没有指定childId,自动获取第一个孩子
|
||||
if (!childId) {
|
||||
const children = await getChildren();
|
||||
if (children && children.length > 0) {
|
||||
childId = children[0].id;
|
||||
router.replace({ query: { childId: String(childId) } });
|
||||
} else {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
// 加载孩子列表
|
||||
children.value = await getChildren();
|
||||
if (children.value.length === 0) {
|
||||
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;
|
||||
|
||||
const data = await getChildLessons(childId, {
|
||||
@ -133,6 +154,8 @@ const loadRecords = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => route.query.childId, () => loadRecords());
|
||||
|
||||
onMounted(() => {
|
||||
loadRecords();
|
||||
});
|
||||
@ -142,6 +165,15 @@ onMounted(() => {
|
||||
.lesson-history-view {
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 8px;
|
||||
|
||||
@ -1,13 +1,30 @@
|
||||
<template>
|
||||
<div class="task-list-view">
|
||||
<div class="page-header">
|
||||
<h2><CheckSquareOutlined /> 阅读任务</h2>
|
||||
<p class="page-desc">查看孩子的阅读任务并提交完成情况</p>
|
||||
<div class="header-left">
|
||||
<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>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<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="task-info">
|
||||
<div class="task-title-row">
|
||||
@ -36,7 +53,8 @@
|
||||
</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-items">
|
||||
<span v-if="task.photos?.length" class="preview-item">
|
||||
@ -108,15 +126,8 @@
|
||||
</a-spin>
|
||||
|
||||
<!-- 提交任务弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="submitModalVisible"
|
||||
:title="isEditSubmit ? '修改提交' : '提交任务完成'"
|
||||
@ok="handleSubmit"
|
||||
:confirm-loading="submitting"
|
||||
width="600px"
|
||||
okText="提交"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a-modal v-model:open="submitModalVisible" :title="isEditSubmit ? '修改提交' : '提交任务完成'" @ok="handleSubmit"
|
||||
:confirm-loading="submitting" width="600px" okText="提交" cancelText="取消">
|
||||
<div class="task-info-header" v-if="selectedTask">
|
||||
<h4>{{ selectedTask.task.title }}</h4>
|
||||
<p v-if="selectedTask.task.relatedBookName">
|
||||
@ -135,12 +146,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-upload
|
||||
:customRequest="handlePhotoUpload"
|
||||
:showUploadList="false"
|
||||
accept="image/*"
|
||||
multiple
|
||||
>
|
||||
<a-upload :customRequest="handlePhotoUpload" :showUploadList="false" accept="image/*" multiple>
|
||||
<div class="upload-btn">
|
||||
<PlusOutlined />
|
||||
<span>添加照片</span>
|
||||
@ -151,40 +157,22 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="视频链接">
|
||||
<a-input
|
||||
v-model:value="submitForm.videoUrl"
|
||||
placeholder="请输入视频链接(可选,如腾讯视频、优酷等分享链接)"
|
||||
allow-clear
|
||||
/>
|
||||
<a-input v-model:value="submitForm.videoUrl" placeholder="请输入视频链接(可选,如腾讯视频、优酷等分享链接)" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="音频链接">
|
||||
<a-input
|
||||
v-model:value="submitForm.audioUrl"
|
||||
placeholder="请输入音频链接(可选)"
|
||||
allow-clear
|
||||
/>
|
||||
<a-input v-model:value="submitForm.audioUrl" placeholder="请输入音频链接(可选)" allow-clear />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="阅读心得">
|
||||
<a-textarea
|
||||
v-model:value="submitForm.content"
|
||||
placeholder="请描述孩子的阅读情况、感受等(可选)"
|
||||
:rows="4"
|
||||
:maxlength="500"
|
||||
show-count
|
||||
/>
|
||||
<a-textarea v-model:value="submitForm.content" placeholder="请描述孩子的阅读情况、感受等(可选)" :rows="4" :maxlength="500"
|
||||
show-count />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
<!-- 评价详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="feedbackModalVisible"
|
||||
title="教师评价详情"
|
||||
:footer="null"
|
||||
width="500px"
|
||||
>
|
||||
<a-modal v-model:open="feedbackModalVisible" title="教师评价详情" :footer="null" width="500px">
|
||||
<div class="feedback-detail" v-if="selectedTask?.teacherFeedback">
|
||||
<div class="feedback-result-section">
|
||||
<div class="result-label">评价结果</div>
|
||||
@ -214,7 +202,8 @@
|
||||
<a-divider>提交内容</a-divider>
|
||||
<div class="review-photos" v-if="selectedTask.photos?.length">
|
||||
<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 class="review-links" v-if="selectedTask.videoUrl || selectedTask.audioUrl">
|
||||
@ -233,19 +222,15 @@
|
||||
</a-modal>
|
||||
|
||||
<!-- 图片预览 -->
|
||||
<a-image
|
||||
:style="{ display: 'none' }"
|
||||
:preview="{
|
||||
visible: imagePreviewVisible,
|
||||
onVisibleChange: (visible: boolean) => { imagePreviewVisible = visible; },
|
||||
}"
|
||||
:src="previewImageUrl"
|
||||
/>
|
||||
<a-image :style="{ display: 'none' }" :preview="{
|
||||
visible: imagePreviewVisible,
|
||||
onVisibleChange: (visible: boolean) => { imagePreviewVisible = visible; },
|
||||
}" :src="previewImageUrl" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { message, Upload } from 'ant-design-vue';
|
||||
import type { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
|
||||
@ -267,10 +252,13 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
getChildTasks,
|
||||
getMyTasks,
|
||||
getChildren,
|
||||
submitTaskCompletion,
|
||||
updateTaskCompletion,
|
||||
type TaskWithCompletion,
|
||||
type TaskSubmitRequest,
|
||||
type ChildInfo,
|
||||
} from '@/api/parent';
|
||||
import { uploadFile } from '@/api/file';
|
||||
import dayjs from 'dayjs';
|
||||
@ -279,7 +267,9 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const tasks = ref<TaskWithCompletion[]>([]);
|
||||
const children = ref<ChildInfo[]>([]);
|
||||
const currentChildId = ref<number | null>(null);
|
||||
const selectedChildId = ref<number | null>(null); // null = 全部孩子,使用 getMyTasks
|
||||
|
||||
// 提交弹窗
|
||||
const submitModalVisible = ref(false);
|
||||
@ -336,20 +326,21 @@ const openSubmitModal = (task: TaskWithCompletion) => {
|
||||
submitModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 处理照片上传
|
||||
// 处理照片上传(符合 API:photos 为 URL 字符串数组)
|
||||
const handlePhotoUpload = async (options: UploadRequestOption) => {
|
||||
const { file } = options;
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file as File);
|
||||
const url = await uploadFile(formData);
|
||||
if (submitForm.photos.length < 9) {
|
||||
submitForm.photos.push(url);
|
||||
} else {
|
||||
if (submitForm.photos.length >= 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) {
|
||||
message.error('上传失败');
|
||||
message.error(error?.message || '上传失败');
|
||||
}
|
||||
};
|
||||
|
||||
@ -362,7 +353,8 @@ const removePhoto = (index: number) => {
|
||||
const handleSubmit = async () => {
|
||||
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) {
|
||||
message.warning('请先选择孩子');
|
||||
return;
|
||||
@ -407,28 +399,43 @@ const previewImage = (url: string) => {
|
||||
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 () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
let childId = route.query.childId ? Number(route.query.childId) : null;
|
||||
|
||||
// 如果没有指定childId,自动获取第一个孩子
|
||||
if (!childId) {
|
||||
const { getChildren } = await import('@/api/parent');
|
||||
const children = await getChildren();
|
||||
if (children && children.length > 0) {
|
||||
childId = children[0].id;
|
||||
router.replace({ query: { childId: String(childId) } });
|
||||
} else {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
// 加载孩子列表(用于选择器)
|
||||
children.value = await getChildren();
|
||||
if (children.value.length === 0) {
|
||||
tasks.value = [];
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
currentChildId.value = childId;
|
||||
const data = await getChildTasks(childId);
|
||||
tasks.value = data.items;
|
||||
selectedChildId.value = childId;
|
||||
|
||||
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) {
|
||||
message.error(error.response?.data?.message || '加载数据失败');
|
||||
} finally {
|
||||
@ -436,6 +443,9 @@ const loadTasks = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 监听路由变化(如从孩子详情页跳转带 childId)
|
||||
watch(() => route.query.childId, () => loadTasks());
|
||||
|
||||
onMounted(() => {
|
||||
loadTasks();
|
||||
});
|
||||
@ -448,6 +458,15 @@ $primary-light: #f6ffed;
|
||||
.task-list-view {
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 8px;
|
||||
@ -698,6 +717,7 @@ $primary-light: #f6ffed;
|
||||
|
||||
// 评价详情弹窗样式
|
||||
.feedback-detail {
|
||||
|
||||
.feedback-result-section,
|
||||
.feedback-rating-section {
|
||||
margin-bottom: 16px;
|
||||
@ -854,6 +874,7 @@ $primary-light: #f6ffed;
|
||||
}
|
||||
|
||||
.photo-upload-area {
|
||||
|
||||
.photo-item,
|
||||
.upload-btn {
|
||||
width: 60px;
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
<template #title>教学管理</template>
|
||||
<a-menu-item key="courses">
|
||||
<template #icon><ReadOutlined /></template>
|
||||
<span>课程管理</span>
|
||||
<span>课程中心</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="school-courses">
|
||||
<template #icon><FolderAddOutlined /></template>
|
||||
|
||||
@ -0,0 +1,599 @@
|
||||
<template>
|
||||
<div class="course-center-page">
|
||||
<!-- 左侧套餐列表 -->
|
||||
<aside class="collection-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h3>课程套餐</h3>
|
||||
</div>
|
||||
<a-spin :spinning="loadingCollections">
|
||||
<div class="collection-list">
|
||||
<div
|
||||
v-for="collection in collections"
|
||||
:key="collection.id"
|
||||
:class="['collection-item', { active: selectedCollectionId === collection.id }]"
|
||||
@click="selectCollection(collection)"
|
||||
>
|
||||
<div class="collection-name">{{ collection.name }}</div>
|
||||
<div class="collection-count">{{ collection.packageCount || 0 }}个课程包</div>
|
||||
</div>
|
||||
<div v-if="!loadingCollections && collections.length === 0" class="empty-collections">
|
||||
<InboxOutlined />
|
||||
<p>暂无可用套餐</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧主内容区 -->
|
||||
<main class="main-content">
|
||||
<template v-if="selectedCollection">
|
||||
<!-- 套餐信息区 -->
|
||||
<section class="collection-info">
|
||||
<h2 class="collection-title">{{ selectedCollection.name }}</h2>
|
||||
<div v-if="selectedCollection.description" class="collection-description">
|
||||
<div ref="descRef" :class="['desc-text', { expanded: descExpanded }]">
|
||||
{{ selectedCollection.description }}
|
||||
</div>
|
||||
<button
|
||||
v-if="showExpandBtn"
|
||||
class="expand-btn"
|
||||
@click="descExpanded = !descExpanded"
|
||||
>
|
||||
{{ descExpanded ? '收起' : '展开更多' }}
|
||||
<DownOutlined :class="{ rotated: descExpanded }" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 筛选区 -->
|
||||
<section class="filter-section">
|
||||
<div class="filter-row">
|
||||
<!-- 年级筛选(标签形式) -->
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">年级:</span>
|
||||
<div class="grade-tags">
|
||||
<span
|
||||
:class="['grade-tag', { active: !selectedGrade }]"
|
||||
@click="selectedGrade = ''"
|
||||
>
|
||||
全部
|
||||
</span>
|
||||
<span
|
||||
v-for="grade in filterMeta.grades"
|
||||
:key="grade.label"
|
||||
:class="['grade-tag', { active: selectedGrade === grade.label }]"
|
||||
@click="selectedGrade = grade.label"
|
||||
>
|
||||
{{ grade.label }}
|
||||
<span class="count">({{ grade.count }})</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-row">
|
||||
<!-- 主题筛选 -->
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">主题:</span>
|
||||
<a-select
|
||||
v-model:value="selectedThemeId"
|
||||
placeholder="全部主题"
|
||||
style="width: 180px"
|
||||
allowClear
|
||||
@change="loadPackages"
|
||||
>
|
||||
<a-select-option :value="undefined">全部主题</a-select-option>
|
||||
<a-select-option
|
||||
v-for="theme in filterMeta.themes"
|
||||
:key="theme.id"
|
||||
:value="theme.id"
|
||||
>
|
||||
{{ theme.name }} ({{ theme.count }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<!-- 搜索 -->
|
||||
<div class="filter-group search-group">
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索课程包..."
|
||||
style="width: 220px"
|
||||
allowClear
|
||||
@search="loadPackages"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 课程包网格 -->
|
||||
<section class="packages-section">
|
||||
<a-spin :spinning="loadingPackages">
|
||||
<div v-if="packages.length > 0" class="packages-grid">
|
||||
<CoursePackageCard
|
||||
v-for="pkg in packages"
|
||||
:key="pkg.id"
|
||||
:pkg="pkg"
|
||||
@click="handlePackageClick"
|
||||
@view="handlePackageView"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="empty-packages">
|
||||
<InboxOutlined class="empty-icon" />
|
||||
<p class="empty-text">暂无符合条件的课程包</p>
|
||||
<p class="empty-hint">试试调整筛选条件</p>
|
||||
</div>
|
||||
</a-spin>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- 未选择套餐时的占位 -->
|
||||
<div v-else class="no-selection">
|
||||
<BookOutlined class="no-selection-icon" />
|
||||
<p>请在左侧选择一个课程套餐</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
InboxOutlined,
|
||||
DownOutlined,
|
||||
BookOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
getCollections,
|
||||
getPackages,
|
||||
getFilterMeta,
|
||||
type CourseCollection,
|
||||
type CoursePackage,
|
||||
type FilterMetaResponse,
|
||||
} from '@/api/course-center';
|
||||
import { getCoursePackageDetail } from '@/api/school';
|
||||
import CoursePackageCard from './components/CoursePackageCard.vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 套餐列表
|
||||
const collections = ref<CourseCollection[]>([]);
|
||||
const loadingCollections = ref(false);
|
||||
const selectedCollectionId = ref<number | null>(null);
|
||||
const selectedCollection = computed(() =>
|
||||
collections.value.find(c => c.id === selectedCollectionId.value)
|
||||
);
|
||||
|
||||
// 筛选元数据
|
||||
const filterMeta = ref<FilterMetaResponse>({ grades: [], themes: [] });
|
||||
|
||||
// 课程包列表
|
||||
const packages = ref<CoursePackage[]>([]);
|
||||
const loadingPackages = ref(false);
|
||||
|
||||
// 筛选条件
|
||||
const selectedGrade = ref('');
|
||||
const selectedThemeId = ref<number | undefined>(undefined);
|
||||
const searchKeyword = ref('');
|
||||
|
||||
// 描述展开
|
||||
const descRef = ref<HTMLElement | null>(null);
|
||||
const descExpanded = ref(false);
|
||||
const showExpandBtn = ref(false);
|
||||
|
||||
// 加载套餐列表
|
||||
const loadCollections = async () => {
|
||||
loadingCollections.value = true;
|
||||
try {
|
||||
const data = await getCollections();
|
||||
collections.value = data || [];
|
||||
// 默认选中第一个
|
||||
if (collections.value.length > 0 && !selectedCollectionId.value) {
|
||||
selectCollection(collections.value[0]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取套餐列表失败');
|
||||
} finally {
|
||||
loadingCollections.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择套餐
|
||||
const selectCollection = async (collection: CourseCollection) => {
|
||||
selectedCollectionId.value = collection.id;
|
||||
// 重置筛选条件
|
||||
selectedGrade.value = '';
|
||||
selectedThemeId.value = undefined;
|
||||
searchKeyword.value = '';
|
||||
descExpanded.value = false;
|
||||
|
||||
// 加载筛选元数据和课程包
|
||||
await Promise.all([
|
||||
loadFilterMeta(),
|
||||
loadPackages(),
|
||||
]);
|
||||
|
||||
// 检查描述是否需要展开按钮
|
||||
nextTick(() => {
|
||||
checkDescHeight();
|
||||
});
|
||||
};
|
||||
|
||||
// 检查描述高度
|
||||
const checkDescHeight = () => {
|
||||
if (descRef.value) {
|
||||
showExpandBtn.value = descRef.value.scrollHeight > descRef.value.clientHeight;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载筛选元数据
|
||||
const loadFilterMeta = async () => {
|
||||
if (!selectedCollectionId.value) return;
|
||||
try {
|
||||
filterMeta.value = await getFilterMeta(selectedCollectionId.value);
|
||||
} catch (error) {
|
||||
console.error('获取筛选元数据失败', error);
|
||||
filterMeta.value = { grades: [], themes: [] };
|
||||
}
|
||||
};
|
||||
|
||||
// 加载课程包列表
|
||||
const loadPackages = async () => {
|
||||
if (!selectedCollectionId.value) return;
|
||||
loadingPackages.value = true;
|
||||
try {
|
||||
packages.value = await getPackages(selectedCollectionId.value, {
|
||||
grade: selectedGrade.value || undefined,
|
||||
themeId: selectedThemeId.value,
|
||||
keyword: searchKeyword.value || undefined,
|
||||
});
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取课程包列表失败');
|
||||
packages.value = [];
|
||||
} finally {
|
||||
loadingPackages.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听年级变化
|
||||
watch(selectedGrade, () => {
|
||||
loadPackages();
|
||||
});
|
||||
|
||||
// 点击课程包
|
||||
const handlePackageClick = (pkg: CoursePackage) => {
|
||||
// 跳转到课程详情页
|
||||
router.push(`/school/courses/${pkg.id}`);
|
||||
};
|
||||
|
||||
// 查看课程包详情
|
||||
const handlePackageView = (pkg: CoursePackage) => {
|
||||
router.push(`/school/courses/${pkg.id}`);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadCollections();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.course-center-page {
|
||||
display: flex;
|
||||
min-height: calc(100vh - 120px);
|
||||
background: #F5F7FA;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 左侧套餐列表 */
|
||||
.collection-sidebar {
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #F0F0F0;
|
||||
}
|
||||
|
||||
.sidebar-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.collection-list {
|
||||
padding: 8px;
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.collection-item {
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border-left: 3px solid transparent;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.collection-item:hover {
|
||||
background: #F5F7FA;
|
||||
}
|
||||
|
||||
.collection-item.active {
|
||||
background: #E6F7FF;
|
||||
border-left-color: #1890ff;
|
||||
}
|
||||
|
||||
.collection-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.collection-count {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-collections {
|
||||
text-align: center;
|
||||
padding: 40px 16px;
|
||||
color: #BFBFBF;
|
||||
}
|
||||
|
||||
/* 右侧主内容区 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
min-height: calc(100vh - 160px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 套餐信息区 */
|
||||
.collection-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.collection-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.collection-description {
|
||||
background: #FAFAFA;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
max-height: 44px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.desc-text.expanded {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.expand-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
color: #1890ff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.expand-btn .anticon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.expand-btn .anticon.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* 筛选区 */
|
||||
.filter-section {
|
||||
background: #FAFAFA;
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-row + .filter-row {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.grade-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.grade-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 14px;
|
||||
border-radius: 16px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
background: #F5F5F5;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.grade-tag:hover {
|
||||
background: #E6F7FF;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.grade-tag.active {
|
||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grade-tag .count {
|
||||
font-size: 12px;
|
||||
margin-left: 2px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 课程包网格 */
|
||||
.packages-section {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.packages-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-packages {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
color: #D9D9D9;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 未选择套餐 */
|
||||
.no-selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
color: #BFBFBF;
|
||||
}
|
||||
|
||||
.no-selection-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 详情抽屉样式 */
|
||||
.cover-preview {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.cover-preview h4,
|
||||
.lessons-section h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.lessons-section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.lesson-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.lesson-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.course-center-page {
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.collection-sidebar {
|
||||
width: 100%;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.packages-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-group :deep(.ant-input-search) {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="package-card" @click="handleClick">
|
||||
<!-- 封面区域 -->
|
||||
<div class="cover-wrapper">
|
||||
<img
|
||||
v-if="pkg.coverImagePath"
|
||||
:src="getImageUrl(pkg.coverImagePath)"
|
||||
class="cover-image"
|
||||
alt="课程包封面"
|
||||
/>
|
||||
<div v-else class="cover-placeholder">
|
||||
<BookFilled class="placeholder-icon" />
|
||||
<span class="placeholder-text">精彩绘本</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-wrapper">
|
||||
<h3 class="package-name" :title="pkg.name">{{ pkg.name }}</h3>
|
||||
<p v-if="pkg.pictureBookName" class="book-name">
|
||||
<BookOutlined /> {{ pkg.pictureBookName }}
|
||||
</p>
|
||||
|
||||
<!-- 年级标签行 -->
|
||||
<div class="tag-row grade-row">
|
||||
<span class="grade-tag">
|
||||
{{ gradeText }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 主题标签行 -->
|
||||
<div v-if="pkg.themeName" class="tag-row theme-row">
|
||||
<span class="theme-tag">
|
||||
{{ pkg.themeName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="meta-row">
|
||||
<span class="meta-item">
|
||||
<ClockCircleOutlined />
|
||||
{{ pkg.durationMinutes || 30 }}分钟
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<TeamOutlined />
|
||||
{{ pkg.usageCount || 0 }}次
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮(学校端只查看详情) -->
|
||||
<button class="action-btn" @click.stop="handleView">
|
||||
<EyeOutlined />
|
||||
<span>查看详情</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
BookOutlined,
|
||||
BookFilled,
|
||||
ClockCircleOutlined,
|
||||
TeamOutlined,
|
||||
EyeOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { CoursePackage } from '@/api/course-center';
|
||||
|
||||
const props = defineProps<{
|
||||
pkg: CoursePackage;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', pkg: CoursePackage): void;
|
||||
(e: 'view', pkg: CoursePackage): void;
|
||||
}>();
|
||||
|
||||
// 年级文本(多年级用圆点分隔)
|
||||
const gradeText = computed(() => {
|
||||
const grades = props.pkg.gradeTags || [];
|
||||
return grades.join(' · ');
|
||||
});
|
||||
|
||||
// 获取图片完整 URL
|
||||
const getImageUrl = (path: string) => {
|
||||
if (!path) return '';
|
||||
if (path.startsWith('http')) return path;
|
||||
return `${import.meta.env.VITE_SERVER_BASE_URL || 'http://localhost:3000'}${path}`;
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click', props.pkg);
|
||||
};
|
||||
|
||||
const handleView = () => {
|
||||
emit('view', props.pkg);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.package-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.package-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 20px rgba(24, 144, 255, 0.15);
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
/* 封面区域 */
|
||||
.cover-wrapper {
|
||||
position: relative;
|
||||
height: 160px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.package-card:hover .cover-image {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.cover-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%);
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 48px;
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 14px;
|
||||
color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-wrapper {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.package-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 4px;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.book-name {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
margin: 0 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* 标签行 */
|
||||
.tag-row {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.grade-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
background: linear-gradient(135deg, #e6f7ff 0%, #f0f5ff 100%);
|
||||
color: #1890ff;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #91d5ff;
|
||||
}
|
||||
|
||||
.theme-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
background: #f6ffed;
|
||||
color: #52c41a;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #b7eb8f;
|
||||
}
|
||||
|
||||
/* 统计信息 */
|
||||
.meta-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed #EEE;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(135deg, #1890ff 0%, #40a9ff 100%);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: linear-gradient(135deg, #096dd9 0%, #1890ff 100%);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
</style>
|
||||
@ -76,7 +76,7 @@
|
||||
<div class="info-row">
|
||||
<CalendarOutlined class="info-icon" />
|
||||
<span class="info-value">{{ calculateAge(student.birthDate) || '--' }}{{ student.birthDate ? '岁' : ''
|
||||
}}</span>
|
||||
}}</span>
|
||||
<span class="gender-tag" :class="normalizeGender(student.gender) === '男' ? 'boy' : 'girl'">
|
||||
{{ normalizeGender(student.gender) }}
|
||||
</span>
|
||||
@ -400,7 +400,7 @@ interface FormState {
|
||||
name: string;
|
||||
gender: string;
|
||||
birthDate?: string | null;
|
||||
classId: number;
|
||||
classId?: number;
|
||||
parentName: string;
|
||||
parentPhone: string;
|
||||
}
|
||||
@ -409,7 +409,7 @@ const formState = reactive<FormState>({
|
||||
name: '',
|
||||
gender: '男',
|
||||
birthDate: null,
|
||||
classId: 0,
|
||||
classId: undefined,
|
||||
parentName: '',
|
||||
parentPhone: '',
|
||||
});
|
||||
@ -417,7 +417,9 @@ const formState = reactive<FormState>({
|
||||
const rules: Record<string, any[]> = {
|
||||
name: [{ required: true, message: '请输入学生姓名', trigger: 'blur' }],
|
||||
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 => {
|
||||
@ -482,7 +484,7 @@ const resetForm = () => {
|
||||
formState.name = '';
|
||||
formState.gender = '男';
|
||||
formState.birthDate = null;
|
||||
formState.classId = 0;
|
||||
formState.classId = undefined;
|
||||
formState.parentName = '';
|
||||
formState.parentPhone = '';
|
||||
};
|
||||
@ -508,6 +510,11 @@ const handleEdit = (record: Student) => {
|
||||
const handleModalOk = async () => {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
const classId = formState.classId;
|
||||
if (Number(classId || '-1') < 0) {
|
||||
message.warning('请选择班级');
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
|
||||
if (isEdit.value && formState.id) {
|
||||
@ -515,7 +522,7 @@ const handleModalOk = async () => {
|
||||
name: formState.name,
|
||||
gender: formState.gender,
|
||||
birthDate: formState.birthDate,
|
||||
classId: formState.classId,
|
||||
classId,
|
||||
parentName: formState.parentName,
|
||||
parentPhone: formState.parentPhone,
|
||||
});
|
||||
@ -525,7 +532,7 @@ const handleModalOk = async () => {
|
||||
name: formState.name,
|
||||
gender: formState.gender,
|
||||
birthDate: formState.birthDate,
|
||||
classId: formState.classId,
|
||||
classId,
|
||||
parentName: formState.parentName,
|
||||
parentPhone: formState.parentPhone,
|
||||
});
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
<div class="class-avatar" :style="{ background: getGradeGradient(cls.grade) }">
|
||||
<span class="avatar-text">{{ getGradeInitial(cls.grade) }}</span>
|
||||
</div>
|
||||
<div class="class-name">{{ cls.name }}</div>
|
||||
<div class="class-name">{{ cls.name || '未命名班级' }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
|
||||
@ -0,0 +1,580 @@
|
||||
<template>
|
||||
<div class="course-center-page">
|
||||
<!-- 左侧套餐列表 -->
|
||||
<aside class="collection-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h3>课程套餐</h3>
|
||||
</div>
|
||||
<a-spin :spinning="loadingCollections">
|
||||
<div class="collection-list">
|
||||
<div
|
||||
v-for="collection in collections"
|
||||
:key="collection.id"
|
||||
:class="['collection-item', { active: selectedCollectionId === collection.id }]"
|
||||
@click="selectCollection(collection)"
|
||||
>
|
||||
<div class="collection-name">{{ collection.name }}</div>
|
||||
<div class="collection-count">{{ collection.packageCount || 0 }}个课程包</div>
|
||||
</div>
|
||||
<div v-if="!loadingCollections && collections.length === 0" class="empty-collections">
|
||||
<InboxOutlined />
|
||||
<p>暂无可用套餐</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧主内容区 -->
|
||||
<main class="main-content">
|
||||
<template v-if="selectedCollection">
|
||||
<!-- 套餐信息区 -->
|
||||
<section class="collection-info">
|
||||
<h2 class="collection-title">{{ selectedCollection.name }}</h2>
|
||||
<div v-if="selectedCollection.description" class="collection-description">
|
||||
<div ref="descRef" :class="['desc-text', { expanded: descExpanded }]">
|
||||
{{ selectedCollection.description }}
|
||||
</div>
|
||||
<button
|
||||
v-if="showExpandBtn"
|
||||
class="expand-btn"
|
||||
@click="descExpanded = !descExpanded"
|
||||
>
|
||||
{{ descExpanded ? '收起' : '展开更多' }}
|
||||
<DownOutlined :class="{ rotated: descExpanded }" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 筛选区 -->
|
||||
<section class="filter-section">
|
||||
<div class="filter-row">
|
||||
<!-- 年级筛选(标签形式) -->
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">年级:</span>
|
||||
<div class="grade-tags">
|
||||
<span
|
||||
:class="['grade-tag', { active: !selectedGrade }]"
|
||||
@click="selectedGrade = ''"
|
||||
>
|
||||
全部
|
||||
</span>
|
||||
<span
|
||||
v-for="grade in filterMeta.grades"
|
||||
:key="grade.label"
|
||||
:class="['grade-tag', { active: selectedGrade === grade.label }]"
|
||||
@click="selectedGrade = grade.label"
|
||||
>
|
||||
{{ grade.label }}
|
||||
<span class="count">({{ grade.count }})</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-row">
|
||||
<!-- 主题筛选 -->
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">主题:</span>
|
||||
<a-select
|
||||
v-model:value="selectedThemeId"
|
||||
placeholder="全部主题"
|
||||
style="width: 180px"
|
||||
allowClear
|
||||
@change="loadPackages"
|
||||
>
|
||||
<a-select-option :value="undefined">全部主题</a-select-option>
|
||||
<a-select-option
|
||||
v-for="theme in filterMeta.themes"
|
||||
:key="theme.id"
|
||||
:value="theme.id"
|
||||
>
|
||||
{{ theme.name }} ({{ theme.count }})
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
|
||||
<!-- 搜索 -->
|
||||
<div class="filter-group search-group">
|
||||
<a-input-search
|
||||
v-model:value="searchKeyword"
|
||||
placeholder="搜索课程包..."
|
||||
style="width: 220px"
|
||||
allowClear
|
||||
@search="loadPackages"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 课程包网格 -->
|
||||
<section class="packages-section">
|
||||
<a-spin :spinning="loadingPackages">
|
||||
<div v-if="packages.length > 0" class="packages-grid">
|
||||
<CoursePackageCard
|
||||
v-for="pkg in packages"
|
||||
:key="pkg.id"
|
||||
:pkg="pkg"
|
||||
@click="handlePackageClick"
|
||||
@prepare="handlePrepare"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="empty-packages">
|
||||
<InboxOutlined class="empty-icon" />
|
||||
<p class="empty-text">暂无符合条件的课程包</p>
|
||||
<p class="empty-hint">试试调整筛选条件</p>
|
||||
</div>
|
||||
</a-spin>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- 未选择套餐时的占位 -->
|
||||
<div v-else class="no-selection">
|
||||
<BookOutlined class="no-selection-icon" />
|
||||
<p>请在左侧选择一个课程套餐</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
InboxOutlined,
|
||||
DownOutlined,
|
||||
BookOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
getCollections,
|
||||
getPackages,
|
||||
getFilterMeta,
|
||||
type CourseCollection,
|
||||
type CoursePackage,
|
||||
type FilterMetaResponse,
|
||||
} from '@/api/course-center';
|
||||
import CoursePackageCard from './components/CoursePackageCard.vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 套餐列表
|
||||
const collections = ref<CourseCollection[]>([]);
|
||||
const loadingCollections = ref(false);
|
||||
const selectedCollectionId = ref<number | null>(null);
|
||||
const selectedCollection = computed(() =>
|
||||
collections.value.find(c => c.id === selectedCollectionId.value)
|
||||
);
|
||||
|
||||
// 筛选元数据
|
||||
const filterMeta = ref<FilterMetaResponse>({ grades: [], themes: [] });
|
||||
|
||||
// 课程包列表
|
||||
const packages = ref<CoursePackage[]>([]);
|
||||
const loadingPackages = ref(false);
|
||||
|
||||
// 筛选条件
|
||||
const selectedGrade = ref('');
|
||||
const selectedThemeId = ref<number | undefined>(undefined);
|
||||
const searchKeyword = ref('');
|
||||
|
||||
// 描述展开
|
||||
const descRef = ref<HTMLElement | null>(null);
|
||||
const descExpanded = ref(false);
|
||||
const showExpandBtn = ref(false);
|
||||
|
||||
// 加载套餐列表
|
||||
const loadCollections = async () => {
|
||||
loadingCollections.value = true;
|
||||
try {
|
||||
const data = await getCollections();
|
||||
collections.value = data || [];
|
||||
// 默认选中第一个
|
||||
if (collections.value.length > 0 && !selectedCollectionId.value) {
|
||||
selectCollection(collections.value[0]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取套餐列表失败');
|
||||
} finally {
|
||||
loadingCollections.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择套餐
|
||||
const selectCollection = async (collection: CourseCollection) => {
|
||||
selectedCollectionId.value = collection.id;
|
||||
// 重置筛选条件
|
||||
selectedGrade.value = '';
|
||||
selectedThemeId.value = undefined;
|
||||
searchKeyword.value = '';
|
||||
descExpanded.value = false;
|
||||
|
||||
// 加载筛选元数据和课程包
|
||||
await Promise.all([
|
||||
loadFilterMeta(),
|
||||
loadPackages(),
|
||||
]);
|
||||
|
||||
// 检查描述是否需要展开按钮
|
||||
nextTick(() => {
|
||||
checkDescHeight();
|
||||
});
|
||||
};
|
||||
|
||||
// 检查描述高度
|
||||
const checkDescHeight = () => {
|
||||
if (descRef.value) {
|
||||
showExpandBtn.value = descRef.value.scrollHeight > descRef.value.clientHeight;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载筛选元数据
|
||||
const loadFilterMeta = async () => {
|
||||
if (!selectedCollectionId.value) return;
|
||||
try {
|
||||
filterMeta.value = await getFilterMeta(selectedCollectionId.value);
|
||||
} catch (error) {
|
||||
console.error('获取筛选元数据失败', error);
|
||||
filterMeta.value = { grades: [], themes: [] };
|
||||
}
|
||||
};
|
||||
|
||||
// 加载课程包列表
|
||||
const loadPackages = async () => {
|
||||
if (!selectedCollectionId.value) return;
|
||||
loadingPackages.value = true;
|
||||
try {
|
||||
packages.value = await getPackages(selectedCollectionId.value, {
|
||||
grade: selectedGrade.value || undefined,
|
||||
themeId: selectedThemeId.value,
|
||||
keyword: searchKeyword.value || undefined,
|
||||
});
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取课程包列表失败');
|
||||
packages.value = [];
|
||||
} finally {
|
||||
loadingPackages.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 点击课程包
|
||||
const handlePackageClick = (pkg: CoursePackage) => {
|
||||
router.push(`/teacher/courses/${pkg.id}`);
|
||||
};
|
||||
|
||||
// 开始备课
|
||||
const handlePrepare = (pkg: CoursePackage) => {
|
||||
router.push(`/teacher/courses/${pkg.id}/prepare`);
|
||||
};
|
||||
|
||||
// 监听年级变化
|
||||
watch(selectedGrade, () => {
|
||||
loadPackages();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadCollections();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.course-center-page {
|
||||
display: flex;
|
||||
min-height: calc(100vh - 120px);
|
||||
background: #F5F7FA;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 左侧套餐列表 */
|
||||
.collection-sidebar {
|
||||
width: 220px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #F0F0F0;
|
||||
}
|
||||
|
||||
.sidebar-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.collection-list {
|
||||
padding: 8px;
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.collection-item {
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border-left: 3px solid transparent;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.collection-item:hover {
|
||||
background: #FFF7E6;
|
||||
}
|
||||
|
||||
.collection-item.active {
|
||||
background: #FFF7E6;
|
||||
border-left-color: #FF8C42;
|
||||
}
|
||||
|
||||
.collection-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.collection-count {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-collections {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #BFBFBF;
|
||||
}
|
||||
|
||||
.empty-collections .anticon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 右侧主内容 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 套餐信息区 */
|
||||
.collection-info {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.collection-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.collection-description {
|
||||
background: #FAFAFA;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
max-height: 44px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.desc-text.expanded {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.expand-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
color: #FF8C42;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.expand-btn .anticon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.expand-btn .anticon.rotated {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* 筛选区 */
|
||||
.filter-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-row + .filter-row {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.grade-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.grade-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 14px;
|
||||
border-radius: 16px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
background: #F5F5F5;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.grade-tag:hover {
|
||||
background: #FFF7E6;
|
||||
color: #FF8C42;
|
||||
}
|
||||
|
||||
.grade-tag.active {
|
||||
background: linear-gradient(135deg, #FF8C42 0%, #FFB347 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grade-tag .count {
|
||||
font-size: 12px;
|
||||
margin-left: 2px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 课程包网格 */
|
||||
.packages-section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
min-height: 400px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.packages-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-packages {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
color: #D9D9D9;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 未选择套餐 */
|
||||
.no-selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
color: #BFBFBF;
|
||||
}
|
||||
|
||||
.no-selection-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.course-center-page {
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.collection-sidebar {
|
||||
width: 100%;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.packages-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-group :deep(.ant-input-search) {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<div class="package-card" @click="handleClick">
|
||||
<!-- 封面区域 -->
|
||||
<div class="cover-wrapper">
|
||||
<img
|
||||
v-if="pkg.coverImagePath"
|
||||
:src="getImageUrl(pkg.coverImagePath)"
|
||||
class="cover-image"
|
||||
alt="课程包封面"
|
||||
/>
|
||||
<div v-else class="cover-placeholder">
|
||||
<BookFilled class="placeholder-icon" />
|
||||
<span class="placeholder-text">精彩绘本</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="content-wrapper">
|
||||
<h3 class="package-name" :title="pkg.name">{{ pkg.name }}</h3>
|
||||
<p v-if="pkg.pictureBookName" class="book-name">
|
||||
<BookOutlined /> {{ pkg.pictureBookName }}
|
||||
</p>
|
||||
|
||||
<!-- 年级标签行 -->
|
||||
<div class="tag-row grade-row">
|
||||
<span class="grade-tag">
|
||||
{{ gradeText }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 主题标签行 -->
|
||||
<div v-if="pkg.themeName" class="tag-row theme-row">
|
||||
<span class="theme-tag">
|
||||
{{ pkg.themeName }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="meta-row">
|
||||
<span class="meta-item">
|
||||
<ClockCircleOutlined />
|
||||
{{ pkg.durationMinutes || 30 }}分钟
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<TeamOutlined />
|
||||
{{ pkg.usageCount || 0 }}次
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<button class="action-btn" @click.stop="handlePrepare">
|
||||
<EditOutlined />
|
||||
<span>开始备课</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
BookOutlined,
|
||||
BookFilled,
|
||||
ClockCircleOutlined,
|
||||
TeamOutlined,
|
||||
EditOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { CoursePackage } from '@/api/course-center';
|
||||
|
||||
const props = defineProps<{
|
||||
pkg: CoursePackage;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', pkg: CoursePackage): void;
|
||||
(e: 'prepare', pkg: CoursePackage): void;
|
||||
}>();
|
||||
|
||||
// 年级文本(多年级用圆点分隔)
|
||||
const gradeText = computed(() => {
|
||||
const grades = props.pkg.gradeTags || [];
|
||||
return grades.join(' · ');
|
||||
});
|
||||
|
||||
// 获取图片完整 URL
|
||||
const getImageUrl = (path: string) => {
|
||||
if (!path) return '';
|
||||
if (path.startsWith('http')) return path;
|
||||
return `${import.meta.env.VITE_SERVER_BASE_URL || 'http://localhost:3000'}${path}`;
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click', props.pkg);
|
||||
};
|
||||
|
||||
const handlePrepare = () => {
|
||||
emit('prepare', props.pkg);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.package-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.package-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 20px rgba(255, 140, 66, 0.15);
|
||||
border-color: #FF8C42;
|
||||
}
|
||||
|
||||
/* 封面区域 */
|
||||
.cover-wrapper {
|
||||
position: relative;
|
||||
height: 160px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.package-card:hover .cover-image {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.cover-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #FFE4C9 0%, #FFF0E0 100%);
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 48px;
|
||||
color: #FF8C42;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 14px;
|
||||
color: #FF8C42;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-wrapper {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.package-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin: 0 0 4px;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.book-name {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
margin: 0 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* 标签行 */
|
||||
.tag-row {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.grade-row {
|
||||
/* 年级标签样式 */
|
||||
}
|
||||
|
||||
.grade-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
background: linear-gradient(135deg, #FFF7E6 0%, #FFECD9 100%);
|
||||
color: #D46B08;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #FFD591;
|
||||
}
|
||||
|
||||
.theme-row {
|
||||
/* 主题标签样式 */
|
||||
}
|
||||
|
||||
.theme-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
background: #E6F7FF;
|
||||
color: #096DD9;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #91D5FF;
|
||||
}
|
||||
|
||||
/* 统计信息 */
|
||||
.meta-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed #EEE;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(135deg, #FF8C42 0%, #FFB347 100%);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: linear-gradient(135deg, #E67635 0%, #FF8C42 100%);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
</style>
|
||||
@ -340,7 +340,7 @@ const prepareCourse = (course: any) => {
|
||||
message.warning('课程信息异常,无法进入备课');
|
||||
return;
|
||||
}
|
||||
router.push(`/teacher/courses/${id}/prepare`);
|
||||
router.push(`/teacher/courses/${id}`);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
<div class="course-header">
|
||||
<div class="header-left">
|
||||
<a-button type="text" @click="goBackToDetail" class="back-btn">
|
||||
<template #icon><LeftOutlined /></template>
|
||||
<template #icon>
|
||||
<LeftOutlined />
|
||||
</template>
|
||||
返回
|
||||
</a-button>
|
||||
<div class="course-cover" v-if="course.coverImagePath">
|
||||
@ -33,15 +35,21 @@
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<a-button @click="showScheduleModal" :disabled="!selectedClassId" class="schedule-btn">
|
||||
<template #icon><CalendarOutlined /></template>
|
||||
<template #icon>
|
||||
<CalendarOutlined />
|
||||
</template>
|
||||
预约上课
|
||||
</a-button>
|
||||
<a-button type="primary" @click="startTeaching" :disabled="!selectedClassId" class="start-btn">
|
||||
<template #icon><PlayCircleOutlined /></template>
|
||||
<template #icon>
|
||||
<PlayCircleOutlined />
|
||||
</template>
|
||||
开始上课
|
||||
</a-button>
|
||||
<a-button @click="handleExit" class="exit-btn">
|
||||
<template #icon><CloseOutlined /></template>
|
||||
<template #icon>
|
||||
<CloseOutlined />
|
||||
</template>
|
||||
退出备课
|
||||
</a-button>
|
||||
</div>
|
||||
@ -52,40 +60,23 @@
|
||||
<a-row :gutter="20">
|
||||
<!-- 左侧:课程导航 -->
|
||||
<a-col :span="6">
|
||||
<PrepareNavigation
|
||||
:course="course"
|
||||
:lessons="lessons"
|
||||
:selected-section="selectedSection"
|
||||
:selected-lesson-id="selectedLessonId"
|
||||
:selected-item="selectedItem"
|
||||
@select-section="handleSelectSection"
|
||||
@select-lesson="handleSelectLesson"
|
||||
@select-item="handleSelectItem"
|
||||
/>
|
||||
<PrepareNavigation :course="course" :lessons="lessons" :selected-section="selectedSection"
|
||||
:selected-lesson-id="selectedLessonId" :selected-item="selectedItem" @select-section="handleSelectSection"
|
||||
@select-lesson="handleSelectLesson" @select-item="handleSelectItem" />
|
||||
</a-col>
|
||||
|
||||
<!-- 右侧:内容预览 -->
|
||||
<a-col :span="18">
|
||||
<PreparePreview
|
||||
:course="course"
|
||||
:selected-type="selectedSection"
|
||||
:selected-lesson="selectedLesson"
|
||||
:selected-item="selectedItem"
|
||||
:selected-step="selectedStep"
|
||||
@select-step="handleSelectStep"
|
||||
@preview-resource="handlePreviewResource"
|
||||
/>
|
||||
<PreparePreview :course="course" :selected-type="selectedSection" :selected-lesson="selectedLesson"
|
||||
:selected-item="selectedItem" :selected-step="selectedStep" @select-step="handleSelectStep"
|
||||
@preview-resource="handlePreviewResource" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
<!-- 文件预览弹窗 -->
|
||||
<FilePreviewModal
|
||||
v-model:open="previewModalVisible"
|
||||
:file-url="previewFileUrl"
|
||||
:file-name="previewFileName"
|
||||
/>
|
||||
<FilePreviewModal v-model:open="previewModalVisible" :file-url="previewFileUrl" :file-name="previewFileName" />
|
||||
|
||||
<!-- 预约上课弹窗(四步流程,与课表页一致) -->
|
||||
<TeacherCreateScheduleModal ref="scheduleModalRef" @success="onScheduleSuccess" />
|
||||
@ -414,13 +405,7 @@ const handleExit = () => {
|
||||
};
|
||||
|
||||
const goBackToDetail = () => {
|
||||
// 优先使用路由中的 ID,避免返回时 courseId 未加载导致跳转到 /courses/undefined
|
||||
const id = route.params.id || courseId.value;
|
||||
if (id && id !== 'undefined' && id !== 'null') {
|
||||
router.push(`/teacher/courses/${id}`);
|
||||
} else {
|
||||
router.push('/teacher/courses');
|
||||
}
|
||||
router.back();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<div class="records-content" v-if="students.length > 0">
|
||||
<div class="records-content" v-if="(students ?? []).length > 0">
|
||||
<!-- 提示信息 -->
|
||||
<div class="tip-banner">
|
||||
<BulbOutlined />
|
||||
@ -292,24 +292,26 @@ const loadRecords = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getStudentRecords(lessonId.value);
|
||||
lessonInfo.value = data.lesson;
|
||||
students.value = data.students;
|
||||
// 兼容后端返回格式:{ lesson, students } 或异常结构
|
||||
const studentList = Array.isArray(data?.students) ? data.students : [];
|
||||
lessonInfo.value = data?.lesson ?? null;
|
||||
students.value = studentList;
|
||||
|
||||
// 初始化记录数据
|
||||
for (const student of data.students) {
|
||||
for (const student of studentList) {
|
||||
records[student.id] = {
|
||||
focus: student.record?.focus || 0,
|
||||
participation: student.record?.participation || 0,
|
||||
interest: student.record?.interest || 0,
|
||||
understanding: student.record?.understanding || 0,
|
||||
notes: student.record?.notes || '',
|
||||
focus: student.record?.focus ?? 0,
|
||||
participation: student.record?.participation ?? 0,
|
||||
interest: student.record?.interest ?? 0,
|
||||
understanding: student.record?.understanding ?? 0,
|
||||
notes: student.record?.notes ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
// 加载课程详情获取更多课程信息
|
||||
await loadLessonDetail();
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取学生记录失败');
|
||||
message.error(error?.message || '获取学生记录失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
<div class="lesson-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<a-button class="exit-btn" @click="exitLesson">
|
||||
<template #icon><ArrowLeftOutlined /></template>
|
||||
<template #icon>
|
||||
<ArrowLeftOutlined />
|
||||
</template>
|
||||
退出上课
|
||||
</a-button>
|
||||
<div class="course-info" v-if="course.name">
|
||||
@ -28,11 +30,14 @@
|
||||
<StepBackwardOutlined /> 上一步
|
||||
</a-button>
|
||||
<a-button type="primary" class="next-btn" @click="nextStep" :disabled="isLastStepOfLastLesson">
|
||||
下一步 <StepForwardOutlined />
|
||||
下一步
|
||||
<StepForwardOutlined />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
<a-button type="primary" class="broadcast-btn" @click="openBroadcastMode">
|
||||
<template #icon><ExpandOutlined /></template>
|
||||
<template #icon>
|
||||
<ExpandOutlined />
|
||||
</template>
|
||||
展播模式
|
||||
</a-button>
|
||||
<a-button class="toolbar-icon-btn" @click="showNotesDrawer = true">
|
||||
@ -44,18 +49,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 课程进度条(多课程时显示) -->
|
||||
<div v-if="lessons.length > 1" class="course-progress-bar">
|
||||
<!-- 课程进度条(多课程时显示,子课程模式不显示) -->
|
||||
<div v-if="displayLessons.length > 1" class="course-progress-bar">
|
||||
<a-steps :current="currentLessonIndex" size="small" class="course-steps">
|
||||
<a-step
|
||||
v-for="(lesson, index) in lessons"
|
||||
:key="lesson.id"
|
||||
:title="getLessonShortName(lesson)"
|
||||
:status="getLessonStatus(index)"
|
||||
:disabled="index > currentLessonIndex"
|
||||
@click="handleLessonClick(index)"
|
||||
class="clickable-step"
|
||||
/>
|
||||
<a-step v-for="(lesson, index) in displayLessons" :key="lesson.id" :title="getLessonShortName(lesson)"
|
||||
:status="getLessonStatus(index)" :disabled="index > currentLessonIndex" @click="handleLessonClick(index)"
|
||||
class="clickable-step" />
|
||||
</a-steps>
|
||||
</div>
|
||||
|
||||
@ -69,12 +68,7 @@
|
||||
环节 {{ currentStepIndex + 1 }}/{{ currentLesson?.steps?.length || 0 }}
|
||||
</span>
|
||||
</div>
|
||||
<a-progress
|
||||
:percent="stepProgressPercent"
|
||||
:show-info="false"
|
||||
stroke-color="#FF8C42"
|
||||
class="step-progress"
|
||||
/>
|
||||
<a-progress :percent="stepProgressPercent" :show-info="false" stroke-color="#FF8C42" class="step-progress" />
|
||||
</div>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
@ -88,16 +82,10 @@
|
||||
</div>
|
||||
|
||||
<div class="step-list">
|
||||
<div
|
||||
v-for="(step, index) in currentLesson?.steps || []"
|
||||
:key="step.id"
|
||||
class="step-item"
|
||||
:class="{
|
||||
active: currentStepIndex === index,
|
||||
completed: index < currentStepIndex
|
||||
}"
|
||||
@click="handleStepClick(index)"
|
||||
>
|
||||
<div v-for="(step, index) in currentLesson?.steps || []" :key="step.id" class="step-item" :class="{
|
||||
active: currentStepIndex === index,
|
||||
completed: index < currentStepIndex
|
||||
}" @click="handleStepClick(index)">
|
||||
<div class="step-number">
|
||||
<CheckOutlined v-if="index < currentStepIndex" />
|
||||
<span v-else>{{ index + 1 }}</span>
|
||||
@ -168,12 +156,8 @@
|
||||
<div v-if="currentStep.images?.length" class="resource-group">
|
||||
<span class="resource-type-label">图片</span>
|
||||
<div class="resource-items">
|
||||
<div
|
||||
v-for="(img, idx) in currentStep.images"
|
||||
:key="idx"
|
||||
class="resource-item"
|
||||
@click="previewResource(img, 'image')"
|
||||
>
|
||||
<div v-for="(img, idx) in currentStep.images" :key="idx" class="resource-item"
|
||||
@click="previewResource(img, 'image')">
|
||||
<PictureOutlined /> {{ img.name || `图片${idx + 1}` }}
|
||||
</div>
|
||||
</div>
|
||||
@ -183,12 +167,8 @@
|
||||
<div v-if="currentStep.videos?.length" class="resource-group">
|
||||
<span class="resource-type-label">视频</span>
|
||||
<div class="resource-items">
|
||||
<div
|
||||
v-for="(vid, idx) in currentStep.videos"
|
||||
:key="idx"
|
||||
class="resource-item"
|
||||
@click="previewResource(vid, 'video')"
|
||||
>
|
||||
<div v-for="(vid, idx) in currentStep.videos" :key="idx" class="resource-item"
|
||||
@click="previewResource(vid, 'video')">
|
||||
<VideoCameraOutlined /> {{ vid.name || `视频${idx + 1}` }}
|
||||
</div>
|
||||
</div>
|
||||
@ -198,12 +178,8 @@
|
||||
<div v-if="currentStep.audioList?.length" class="resource-group">
|
||||
<span class="resource-type-label">音频</span>
|
||||
<div class="resource-items">
|
||||
<div
|
||||
v-for="(aud, idx) in currentStep.audioList"
|
||||
:key="idx"
|
||||
class="resource-item"
|
||||
@click="previewResource(aud, 'audio')"
|
||||
>
|
||||
<div v-for="(aud, idx) in currentStep.audioList" :key="idx" class="resource-item"
|
||||
@click="previewResource(aud, 'audio')">
|
||||
<AudioOutlined /> {{ aud.name || `音频${idx + 1}` }}
|
||||
</div>
|
||||
</div>
|
||||
@ -213,12 +189,8 @@
|
||||
<div v-if="currentStep.pptFiles?.length" class="resource-group">
|
||||
<span class="resource-type-label">课件</span>
|
||||
<div class="resource-items">
|
||||
<div
|
||||
v-for="(ppt, idx) in currentStep.pptFiles"
|
||||
:key="idx"
|
||||
class="resource-item"
|
||||
@click="previewResource(ppt, 'ppt')"
|
||||
>
|
||||
<div v-for="(ppt, idx) in currentStep.pptFiles" :key="idx" class="resource-item"
|
||||
@click="previewResource(ppt, 'ppt')">
|
||||
<FilePptOutlined /> {{ ppt.name || `课件${idx + 1}` }}
|
||||
</div>
|
||||
</div>
|
||||
@ -228,12 +200,8 @@
|
||||
<div v-if="currentStep.documents?.length" class="resource-group">
|
||||
<span class="resource-type-label">文档</span>
|
||||
<div class="resource-items">
|
||||
<div
|
||||
v-for="(doc, idx) in currentStep.documents"
|
||||
:key="idx"
|
||||
class="resource-item"
|
||||
@click="previewResource(doc, 'document')"
|
||||
>
|
||||
<div v-for="(doc, idx) in currentStep.documents" :key="idx" class="resource-item"
|
||||
@click="previewResource(doc, 'document')">
|
||||
<FileTextOutlined /> {{ doc.name || `文档${idx + 1}` }}
|
||||
</div>
|
||||
</div>
|
||||
@ -250,6 +218,19 @@
|
||||
|
||||
<!-- 右侧:工具面板 -->
|
||||
<div class="tool-panel">
|
||||
<!-- 课程类型 -->
|
||||
<div v-if="currentLesson?.lessonType" class="panel-card lesson-type-card">
|
||||
<div class="panel-header">
|
||||
<BookOutlined />
|
||||
<span>课程类型</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<a-tag size="large" class="lesson-type-tag" :style="getLessonTagStyle(currentLesson.lessonType)">
|
||||
{{ getLessonTypeName(currentLesson.lessonType) }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 课程核心资源 -->
|
||||
<div v-if="hasCourseResources" class="panel-card materials-card">
|
||||
<div class="panel-header">
|
||||
@ -258,12 +239,8 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div v-if="courseResources.length > 0" class="materials-list">
|
||||
<div
|
||||
v-for="item in courseResources"
|
||||
:key="item.id"
|
||||
class="material-item"
|
||||
@click="previewCourseResource(item)"
|
||||
>
|
||||
<div v-for="item in courseResources" :key="item.id" class="material-item"
|
||||
@click="previewCourseResource(item)">
|
||||
<div class="material-icon" :class="item.type">
|
||||
<VideoCameraOutlined v-if="item.type === 'video'" />
|
||||
<AudioOutlined v-else-if="item.type === 'audio'" />
|
||||
@ -315,12 +292,7 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div v-if="stepMaterials.length > 0" class="materials-list">
|
||||
<div
|
||||
v-for="item in stepMaterials"
|
||||
:key="item.id"
|
||||
class="material-item"
|
||||
@click="previewMaterial(item)"
|
||||
>
|
||||
<div v-for="item in stepMaterials" :key="item.id" class="material-item" @click="previewMaterial(item)">
|
||||
<div class="material-icon" :class="item.type">
|
||||
<PlayCircleOutlined v-if="item.type === '视频'" />
|
||||
<SoundOutlined v-else-if="item.type === '音频'" />
|
||||
@ -389,12 +361,7 @@
|
||||
</a-modal>
|
||||
|
||||
<!-- 课堂记录抽屉 -->
|
||||
<a-drawer
|
||||
v-model:open="showNotesDrawer"
|
||||
title="课堂记录"
|
||||
placement="right"
|
||||
width="400"
|
||||
>
|
||||
<a-drawer v-model:open="showNotesDrawer" title="课堂记录" placement="right" width="400">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="完成情况">
|
||||
<a-radio-group v-model:value="lessonRecord.completion">
|
||||
@ -410,11 +377,8 @@
|
||||
<a-rate v-model:value="lessonRecord.participationRating" />
|
||||
</a-form-item>
|
||||
<a-form-item label="完成备注">
|
||||
<a-textarea
|
||||
v-model:value="lessonRecord.completionNote"
|
||||
placeholder="记录课程完成情况、学生表现等..."
|
||||
:auto-size="{ minRows: 6, maxRows: 10 }"
|
||||
/>
|
||||
<a-textarea v-model:value="lessonRecord.completionNote" placeholder="记录课程完成情况、学生表现等..."
|
||||
:auto-size="{ minRows: 6, maxRows: 10 }" />
|
||||
</a-form-item>
|
||||
<a-button type="primary" block size="large" @click="saveLessonRecord">
|
||||
保存并结束
|
||||
@ -423,11 +387,7 @@
|
||||
</a-drawer>
|
||||
|
||||
<!-- 文件预览弹窗 -->
|
||||
<FilePreviewModal
|
||||
v-model:open="previewModalVisible"
|
||||
:file-url="previewFileUrl"
|
||||
:file-name="previewFileName"
|
||||
/>
|
||||
<FilePreviewModal v-model:open="previewModalVisible" :file-url="previewFileUrl" :file-name="previewFileName" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -465,6 +425,7 @@ import {
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import * as teacherApi from '@/api/teacher';
|
||||
import FilePreviewModal from '@/components/FilePreviewModal.vue';
|
||||
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@ -487,6 +448,8 @@ let timerInterval: number | null = null;
|
||||
const course = ref<any>({});
|
||||
const classInfo = ref<any>({});
|
||||
const lessons = ref<any[]>([]);
|
||||
/** 排课选择的课程类型(子课程模式:仅展示该子课程,子课程结束即上课结束) */
|
||||
const scheduleLessonType = ref<string | undefined>(undefined);
|
||||
|
||||
const studentEvaluation = ref({
|
||||
overall: 0,
|
||||
@ -502,8 +465,37 @@ const lessonRecord = ref({
|
||||
completionNote: '',
|
||||
});
|
||||
|
||||
/** 判断排课 lessonType 与课程 lessonType 是否匹配(兼容 INTRODUCTION/INTRO、LANGUAGE/DOMAIN_LANGUAGE 等变体) */
|
||||
const lessonTypeMatches = (scheduleType: string, lessonType: string): boolean => {
|
||||
if (!scheduleType || !lessonType) return false;
|
||||
const s = scheduleType.toUpperCase();
|
||||
const l = lessonType.toUpperCase();
|
||||
if (s === l) return true;
|
||||
const pairs: [string, string][] = [
|
||||
['INTRODUCTION', 'INTRO'],
|
||||
['LANGUAGE', 'DOMAIN_LANGUAGE'],
|
||||
['HEALTH', 'DOMAIN_HEALTH'],
|
||||
['SCIENCE', 'DOMAIN_SCIENCE'],
|
||||
['SOCIAL', 'DOMAIN_SOCIAL'],
|
||||
['SOCIETY', 'DOMAIN_SOCIAL'],
|
||||
['ART', 'DOMAIN_ART'],
|
||||
];
|
||||
for (const [a, b] of pairs) {
|
||||
if ((s === a || s === b) && (l === a || l === b)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/** 展示的课程列表:子课程模式时仅包含排课选中的子课程,否则为全部 */
|
||||
const displayLessons = computed(() => {
|
||||
const type = scheduleLessonType.value;
|
||||
if (!type || lessons.value.length === 0) return lessons.value;
|
||||
const matched = lessons.value.filter((l) => lessonTypeMatches(type, l.lessonType || ''));
|
||||
return matched.length > 0 ? matched : lessons.value;
|
||||
});
|
||||
|
||||
// 当前课程
|
||||
const currentLesson = computed(() => lessons.value[currentLessonIndex.value] || null);
|
||||
const currentLesson = computed(() => displayLessons.value[currentLessonIndex.value] || null);
|
||||
|
||||
// 当前环节
|
||||
const currentStep = computed(() => {
|
||||
@ -520,9 +512,9 @@ const stepProgressPercent = computed(() => {
|
||||
// 是否有上一个课程
|
||||
const hasPreviousLesson = computed(() => currentLessonIndex.value > 0);
|
||||
|
||||
// 是否是最后一个课程的最后一个环节
|
||||
// 是否是最后一个课程的最后一个环节(子课程模式下,当前子课程最后一环节即视为最后)
|
||||
const isLastStepOfLastLesson = computed(() => {
|
||||
if (currentLessonIndex.value < lessons.value.length - 1) return false;
|
||||
if (currentLessonIndex.value < displayLessons.value.length - 1) return false;
|
||||
const totalSteps = currentLesson.value?.steps?.length || 1;
|
||||
return currentStepIndex.value >= totalSteps - 1;
|
||||
});
|
||||
@ -701,6 +693,9 @@ const loadLessonData = async () => {
|
||||
course.value = data.course || {};
|
||||
classInfo.value = data.class || {};
|
||||
|
||||
// 排课选择的课程类型(子课程模式:直接进入该子课程,子课程结束即上课结束)
|
||||
scheduleLessonType.value = data.lessonType || undefined;
|
||||
|
||||
// 获取课程列表
|
||||
// 如果授课记录包含多个课程,使用该列表;否则使用课程包的所有课程
|
||||
if (data.lessonCourses && data.lessonCourses.length > 0) {
|
||||
@ -754,50 +749,61 @@ const loadLessonData = async () => {
|
||||
}];
|
||||
}
|
||||
|
||||
// 子课程模式:根据排课 lessonType 直接进入对应子课程(优先于进度恢复和 URL 参数)
|
||||
const matchedLessons = scheduleLessonType.value
|
||||
? lessons.value.filter((l) => lessonTypeMatches(scheduleLessonType.value!, l.lessonType || ''))
|
||||
: [];
|
||||
if (matchedLessons.length > 0) {
|
||||
currentLessonIndex.value = 0;
|
||||
currentStepIndex.value = 0;
|
||||
}
|
||||
|
||||
// 尝试恢复进度
|
||||
try {
|
||||
const progress = await teacherApi.getLessonProgress(lessonId.value);
|
||||
if (progress && (progress.currentLessonId || progress.currentStepId)) {
|
||||
// 有保存的进度,询问用户是否恢复
|
||||
if (progress && (progress.currentLessonId !== undefined || progress.currentStepId !== undefined)) {
|
||||
const isSub = matchedLessons.length > 0;
|
||||
const matchedLesson = matchedLessons[0];
|
||||
const progressIsForMatched = isSub && progress.currentLessonId !== undefined
|
||||
&& matchedLesson && progress.currentLessonId === matchedLesson.id;
|
||||
|
||||
Modal.confirm({
|
||||
title: '检测到上次上课进度',
|
||||
content: `上次上课到:${getProgressDescription(progress)},是否继续?`,
|
||||
okText: '继续上课',
|
||||
cancelText: '重新开始',
|
||||
onOk: () => {
|
||||
// 恢复进度
|
||||
if (progress.currentLessonId !== undefined) {
|
||||
const lessonIndex = lessons.value.findIndex((l) => l.id === progress.currentLessonId);
|
||||
if (lessonIndex >= 0) {
|
||||
currentLessonIndex.value = lessonIndex;
|
||||
}
|
||||
}
|
||||
if (progress.currentStepId !== undefined) {
|
||||
if (isSub && progressIsForMatched && progress.currentStepId !== undefined) {
|
||||
// 子课程模式:仅恢复环节进度
|
||||
currentLessonIndex.value = 0;
|
||||
currentStepIndex.value = progress.currentStepId;
|
||||
} else if (!isSub) {
|
||||
// 非子课程模式:恢复课程和环节进度
|
||||
if (progress.currentLessonId !== undefined) {
|
||||
const lessonIndex = lessons.value.findIndex((l) => l.id === progress.currentLessonId);
|
||||
if (lessonIndex >= 0) currentLessonIndex.value = lessonIndex;
|
||||
}
|
||||
if (progress.currentStepId !== undefined) currentStepIndex.value = progress.currentStepId;
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
// 清除进度,从头开始
|
||||
clearProgress();
|
||||
},
|
||||
onCancel: () => clearProgress(),
|
||||
});
|
||||
}
|
||||
} catch (progressError) {
|
||||
// 没有保存的进度或获取失败,忽略
|
||||
console.log('No saved progress found');
|
||||
}
|
||||
|
||||
// 如果URL指定了课程索引,跳转到该课程(优先级高于恢复的进度)
|
||||
const queryLessonIndex = route.query.lessonIndex ? parseInt(route.query.lessonIndex as string) : 0;
|
||||
if (queryLessonIndex >= 0 && queryLessonIndex < lessons.value.length) {
|
||||
currentLessonIndex.value = queryLessonIndex;
|
||||
}
|
||||
|
||||
// 如果URL指定了环节索引,跳转到该环节(优先级高于恢复的进度)
|
||||
const queryStepIndex = route.query.stepIndex ? parseInt(route.query.stepIndex as string) : 0;
|
||||
const totalSteps = lessons.value[currentLessonIndex.value]?.steps?.length || 0;
|
||||
if (queryStepIndex >= 0 && queryStepIndex < totalSteps) {
|
||||
currentStepIndex.value = queryStepIndex;
|
||||
// 非子课程模式时,URL 参数可覆盖
|
||||
if (matchedLessons.length === 0) {
|
||||
const queryLessonIndex = route.query.lessonIndex ? parseInt(route.query.lessonIndex as string) : 0;
|
||||
if (queryLessonIndex >= 0 && queryLessonIndex < lessons.value.length) {
|
||||
currentLessonIndex.value = queryLessonIndex;
|
||||
}
|
||||
const queryStepIndex = route.query.stepIndex ? parseInt(route.query.stepIndex as string) : 0;
|
||||
const totalSteps = lessons.value[currentLessonIndex.value]?.steps?.length || 0;
|
||||
if (queryStepIndex >= 0 && queryStepIndex < totalSteps) {
|
||||
currentStepIndex.value = queryStepIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// 启动计时器
|
||||
@ -823,10 +829,11 @@ const getProgressDescription = (progress: any): string => {
|
||||
// 保存进度
|
||||
const saveProgress = async () => {
|
||||
try {
|
||||
const list = displayLessons.value;
|
||||
await teacherApi.saveLessonProgress(lessonId.value, {
|
||||
lessonIds: lessons.value.map((l) => l.id),
|
||||
completedLessonIds: lessons.value.slice(0, currentLessonIndex.value).map((l) => l.id),
|
||||
currentLessonId: lessons.value[currentLessonIndex.value]?.id,
|
||||
lessonIds: list.map((l) => l.id),
|
||||
completedLessonIds: list.slice(0, currentLessonIndex.value).map((l) => l.id),
|
||||
currentLessonId: list[currentLessonIndex.value]?.id,
|
||||
currentStepId: currentStepIndex.value,
|
||||
progressData: {
|
||||
timerSeconds: timerSeconds.value,
|
||||
@ -919,7 +926,7 @@ const exitLesson = () => {
|
||||
okText: '确认退出',
|
||||
cancelText: '继续上课',
|
||||
onOk: () => {
|
||||
router.push(`/teacher/courses/${course.value.id}/prepare`);
|
||||
router.back();
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -936,7 +943,7 @@ const saveLessonRecord = async () => {
|
||||
await clearProgress();
|
||||
message.success('课程记录已保存');
|
||||
showNotesDrawer.value = false;
|
||||
router.push(`/teacher/courses/${course.value.id}/prepare`);
|
||||
router.back();
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '保存记录失败');
|
||||
}
|
||||
@ -1212,6 +1219,7 @@ onUnmounted(() => {
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.course-steps {
|
||||
|
||||
:deep(.ant-steps-item-process),
|
||||
:deep(.ant-steps-item-finish) {
|
||||
cursor: pointer;
|
||||
@ -1611,6 +1619,19 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.lesson-type-card {
|
||||
.panel-header {
|
||||
background: #F5F5F5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.lesson-type-tag {
|
||||
font-size: 14px;
|
||||
padding: 6px 14px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.materials-card {
|
||||
.panel-header {
|
||||
background: #FFF5EB;
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
<h2>我的课表</h2>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="showCreateModal">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
预约上课
|
||||
</a-button>
|
||||
</a-space>
|
||||
@ -12,7 +14,9 @@
|
||||
|
||||
<!-- 今日课程 -->
|
||||
<div class="today-section" v-if="todaySchedules.length > 0">
|
||||
<h3><CalendarOutlined /> 今日课程</h3>
|
||||
<h3>
|
||||
<CalendarOutlined /> 今日课程
|
||||
</h3>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6" v-for="schedule in todaySchedules" :key="schedule.id">
|
||||
<a-card size="small" class="today-card" :class="{ 'has-lesson': schedule.hasLesson }">
|
||||
@ -21,31 +25,20 @@
|
||||
</template>
|
||||
<div class="course-name">{{ schedule.courseName || schedule.coursePackageName || '-' }}</div>
|
||||
<div class="class-name">{{ schedule.className || '-' }}</div>
|
||||
<a-tag v-if="schedule.lessonType" size="small" class="today-lesson-type" :style="getLessonTagStyle(schedule.lessonType)">
|
||||
<a-tag v-if="schedule.lessonType" size="small" class="today-lesson-type"
|
||||
:style="getLessonTagStyle(schedule.lessonType)">
|
||||
{{ getLessonTypeName(schedule.lessonType) }}
|
||||
</a-tag>
|
||||
<div class="card-actions">
|
||||
<a-button
|
||||
v-if="schedule.hasLesson && schedule.lessonStatus === 'PLANNED'"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="goToLesson(schedule.lessonId!)"
|
||||
>
|
||||
<a-button v-if="schedule.hasLesson && schedule.lessonStatus === 'PLANNED'" type="primary" size="small"
|
||||
@click="goToLesson(schedule.lessonId!)">
|
||||
开始上课
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else-if="schedule.hasLesson && schedule.lessonStatus === 'IN_PROGRESS'"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="goToLesson(schedule.lessonId!)"
|
||||
>
|
||||
<a-button v-else-if="schedule.hasLesson && schedule.lessonStatus === 'IN_PROGRESS'" type="primary"
|
||||
size="small" @click="goToLesson(schedule.lessonId!)">
|
||||
继续上课
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else
|
||||
size="small"
|
||||
@click="handleStartLessonFromSchedule(schedule)"
|
||||
>
|
||||
<a-button v-else size="small" @click="handleStartLessonFromSchedule(schedule)">
|
||||
创建课堂
|
||||
</a-button>
|
||||
</div>
|
||||
@ -58,13 +51,17 @@
|
||||
<div class="week-navigation">
|
||||
<a-space>
|
||||
<a-button @click="goToPrevWeek">
|
||||
<template #icon><LeftOutlined /></template>
|
||||
<template #icon>
|
||||
<LeftOutlined />
|
||||
</template>
|
||||
上一周
|
||||
</a-button>
|
||||
<a-button @click="goToCurrentWeek">本周</a-button>
|
||||
<a-button @click="goToNextWeek">
|
||||
下一周
|
||||
<template #icon><RightOutlined /></template>
|
||||
<template #icon>
|
||||
<RightOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
<span class="week-range">{{ weekRangeText }}</span>
|
||||
</a-space>
|
||||
@ -74,12 +71,7 @@
|
||||
<div class="timetable-container">
|
||||
<a-spin :spinning="loading">
|
||||
<div class="timetable-header">
|
||||
<div
|
||||
v-for="day in weekDays"
|
||||
:key="day.date"
|
||||
class="day-header"
|
||||
:class="{ 'is-today': day.isToday }"
|
||||
>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-header" :class="{ 'is-today': day.isToday }">
|
||||
<div class="day-name">{{ day.dayName }}</div>
|
||||
<div class="day-date">{{ day.dateDisplay }}</div>
|
||||
</div>
|
||||
@ -87,27 +79,17 @@
|
||||
|
||||
<div class="timetable-body">
|
||||
<div class="timetable-grid">
|
||||
<div
|
||||
v-for="day in weekDays"
|
||||
:key="day.date"
|
||||
class="day-column"
|
||||
:class="{ 'is-today': day.isToday }"
|
||||
>
|
||||
<div
|
||||
v-for="schedule in day.schedules"
|
||||
:key="schedule.id"
|
||||
class="schedule-card"
|
||||
:class="{
|
||||
'school-schedule': schedule.source === 'SCHOOL',
|
||||
'teacher-schedule': schedule.source === 'TEACHER',
|
||||
'has-lesson': schedule.hasLesson,
|
||||
}"
|
||||
@click="showScheduleDetail(schedule)"
|
||||
>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-column" :class="{ 'is-today': day.isToday }">
|
||||
<div v-for="schedule in day.schedules" :key="schedule.id" class="schedule-card" :class="{
|
||||
'school-schedule': schedule.source === 'SCHOOL',
|
||||
'teacher-schedule': schedule.source === 'TEACHER',
|
||||
'has-lesson': schedule.hasLesson,
|
||||
}" @click="showScheduleDetail(schedule)">
|
||||
<div class="schedule-time">{{ schedule.scheduledTime || '待定' }}</div>
|
||||
<div class="schedule-course">{{ schedule.courseName || schedule.coursePackageName || '-' }}</div>
|
||||
<div class="schedule-class">{{ schedule.className || '-' }}</div>
|
||||
<a-tag v-if="schedule.lessonType" size="small" class="schedule-lesson-type" :style="getLessonTagStyle(schedule.lessonType)">
|
||||
<a-tag v-if="schedule.lessonType" size="small" class="schedule-lesson-type"
|
||||
:style="getLessonTagStyle(schedule.lessonType)">
|
||||
{{ getLessonTypeName(schedule.lessonType) }}
|
||||
</a-tag>
|
||||
<div class="schedule-source">
|
||||
@ -133,18 +115,15 @@
|
||||
<TeacherCreateScheduleModal ref="createScheduleModalRef" @success="onCreateScheduleSuccess" />
|
||||
|
||||
<!-- 排课详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="detailVisible"
|
||||
title="排课详情"
|
||||
:footer="null"
|
||||
width="500px"
|
||||
>
|
||||
<a-modal v-model:open="detailVisible" title="排课详情" :footer="null" width="500px">
|
||||
<template v-if="selectedSchedule">
|
||||
<a-descriptions :column="1" bordered>
|
||||
<a-descriptions-item label="班级">{{ selectedSchedule.className || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="课程">{{ selectedSchedule.courseName || selectedSchedule.coursePackageName || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="课程">{{ selectedSchedule.courseName || selectedSchedule.coursePackageName || '-'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="课程类型">
|
||||
<a-tag v-if="selectedSchedule.lessonType" size="small" :style="getLessonTagStyle(selectedSchedule.lessonType)">
|
||||
<a-tag v-if="selectedSchedule.lessonType" size="small"
|
||||
:style="getLessonTagStyle(selectedSchedule.lessonType)">
|
||||
{{ getLessonTypeName(selectedSchedule.lessonType) }}
|
||||
</a-tag>
|
||||
<span v-else>-</span>
|
||||
@ -167,25 +146,16 @@
|
||||
</a-descriptions>
|
||||
<div style="margin-top: 16px; text-align: right;">
|
||||
<a-space>
|
||||
<a-button
|
||||
v-if="selectedSchedule.hasLesson && selectedSchedule.lessonId"
|
||||
type="primary"
|
||||
@click="goToLesson(selectedSchedule.lessonId)"
|
||||
>
|
||||
<a-button v-if="selectedSchedule.hasLesson && selectedSchedule.lessonId" type="primary"
|
||||
@click="goToLesson(selectedSchedule.lessonId)">
|
||||
{{ selectedSchedule.lessonStatus === 'IN_PROGRESS' ? '继续上课' : '查看课堂' }}
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else-if="selectedSchedule.status === 'ACTIVE'"
|
||||
type="primary"
|
||||
@click="handleStartLessonFromSchedule(selectedSchedule)"
|
||||
>
|
||||
<a-button v-else-if="selectedSchedule.status === 'ACTIVE'" type="primary"
|
||||
@click="handleStartLessonFromSchedule(selectedSchedule)">
|
||||
开始上课
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
v-if="selectedSchedule.source === 'TEACHER' && selectedSchedule.status === 'ACTIVE'"
|
||||
title="确定要取消此预约吗?"
|
||||
@confirm="handleCancelSchedule(selectedSchedule.id)"
|
||||
>
|
||||
<a-popconfirm v-if="selectedSchedule.source === 'TEACHER' && selectedSchedule.status === 'ACTIVE'"
|
||||
title="确定要取消此预约吗?" @confirm="handleCancelSchedule(selectedSchedule.id)">
|
||||
<a-button danger>取消预约</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
@ -334,14 +304,14 @@ const handleStartLessonFromSchedule = async (schedule: TeacherSchedule) => {
|
||||
try {
|
||||
const lesson = await startLessonFromSchedule(schedule.id);
|
||||
message.success('课堂创建成功');
|
||||
router.push(`/teacher/lessons/${lesson.id}`);
|
||||
router.push({ path: `/teacher/lessons/${lesson.id}`, query: { from: 'schedule' } });
|
||||
} catch (error) {
|
||||
message.error('创建课堂失败');
|
||||
}
|
||||
};
|
||||
|
||||
const goToLesson = (lessonId: number) => {
|
||||
router.push(`/teacher/lessons/${lessonId}`);
|
||||
router.push({ path: `/teacher/lessons/${lessonId}`, query: { from: 'schedule' } });
|
||||
};
|
||||
|
||||
// 工具方法
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user