kindergarten_java/reading-platform-frontend/src/api/teacher.ts
zhonghua c8ecbe277c fix: 教师端课程与授课记录优化
- 修复 assessment_data JSON 字段:普通文本自动包装为有效 JSON
- 修复返回时页面 ID 丢失:校验无效 ID 并跳转,goBackToDetail 优先使用路由 ID
- 修复上课记录列表:getLessons 支持 pageNum、日期范围、状态映射,list 转 items
- 修复班级与课程取值:LessonResponse 增加 courseName/className,接口返回时自动填充
- 备课/详情页增加 ID 校验,防止跳转到 undefined

Made-with: Cursor
2026-03-17 11:24:25 +08:00

811 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { http } from './index';
import type {
TeacherCourseControllerGetTeacherSchedulesParams,
TeacherCourseControllerGetTeacherTimetableParams,
TeacherFeedbackControllerFindAllParams,
} from './generated/model';
// ============= 类型定义(保持向后兼容) =============
// ==================== 教师课程 API ====================
export interface TeacherCourseQueryParams {
pageNum?: number;
pageSize?: number;
grade?: string;
keyword?: string;
}
export interface TeacherCourse {
id: number;
name: string;
pictureBookName?: string;
coverImagePath?: string;
gradeTags: string[];
domainTags: string[];
duration: number;
avgRating: number;
usageCount: number;
publishedAt: string;
}
// 教师班级信息(更新:新增角色字段)
export interface TeacherClass {
id: number;
name: string;
grade: string;
studentCount: number;
lessonCount: number;
myRole: 'MAIN' | 'ASSIST' | 'CARE'; // 我在该班级的角色
isPrimary: boolean; // 是否班主任
}
// 班级教师信息
export interface TeacherClassTeacher {
teacherId: number;
teacherName: string;
teacherPhone?: string;
role: 'MAIN' | 'ASSIST' | 'CARE';
isPrimary: boolean;
}
// 获取教师可用的课程列表
export function getTeacherCourses(params: TeacherCourseQueryParams): Promise<{
items: TeacherCourse[];
total: number;
page: number;
pageSize: number;
}> {
// 使用 http 直接调用 API后端返回 list 字段,需要转换为 items
return http.get<{ list?: TeacherCourse[]; records?: TeacherCourse[]; total?: number | string; pageNum?: number; pageSize?: number }>('/v1/teacher/courses', {
params: {
pageNum: params.pageNum,
pageSize: params.pageSize,
keyword: params.keyword,
category: params.grade,
},
}).then(res => {
const list = res.list ?? res.records ?? [];
return {
items: Array.isArray(list) ? list : [],
total: typeof res.total === 'string' ? parseInt(res.total, 10) || 0 : (res.total || 0),
page: res.pageNum || 1,
pageSize: res.pageSize || 10,
};
});
}
// 获取课程详情id 使用 string 避免 Long 精度丢失)
export function getTeacherCourse(id: number | string): Promise<any> {
return http.get(`/v1/teacher/courses/${id}`) as any;
}
// 获取教师的班级列表
export function getTeacherClasses(): Promise<TeacherClass[]> {
return http.get<TeacherClass[]>('/v1/teacher/classes');
}
// 获取教师所有学生列表(跨班级)
export function getTeacherStudents(params?: { pageNum?: number; pageSize?: number; keyword?: string }): Promise<{
items: Array<{
id: number;
name: string;
gender?: string;
birthDate?: string;
classId: number;
class?: {
id: number;
name: string;
grade: string;
};
parentName?: string;
parentPhone?: string;
createdAt: string;
}>;
total: number;
page: number;
pageSize: number;
}> {
return http.get('/v1/teacher/students', {
params: {
pageNum: params?.pageNum,
pageSize: params?.pageSize,
keyword: params?.keyword,
},
}) as any;
}
// 获取班级学生列表
export function getTeacherClassStudents(classId: number, params?: { pageNum?: number; pageSize?: number; keyword?: string }): Promise<{
items: Array<{
id: number;
name: string;
gender?: string;
birthDate?: string;
parentName?: string;
parentPhone?: string;
lessonCount?: number;
readingCount?: number;
createdAt: string;
}>;
total: number;
page: number;
pageSize: number;
class?: {
id: number;
name: string;
grade: string;
studentCount: number;
lessonCount: number;
};
}> {
return http.get(`/v1/teacher/classes/${classId}/students`, {
params: {
pageNum: params?.pageNum,
pageSize: params?.pageSize,
keyword: params?.keyword,
},
}) as any;
}
// 获取班级教师列表
export function getClassTeachers(classId: number): Promise<TeacherClassTeacher[]> {
return http.get(`/v1/teacher/classes/${classId}/teachers`) as any;
}
// ==================== 授课记录 API ====================
export interface CreateLessonDto {
courseId: number | string; // string 避免 Long 精度丢失
classId: number;
teacherId: number;
title: string;
lessonDate: string; // YYYY-MM-DD
startTime?: string; // HH:mm
endTime?: string;
plannedDatetime?: string; // 兼容:前端可传此字段,由 adapter 转换为 lessonDate+startTime
}
export interface FinishLessonDto {
overallRating?: string;
participationRating?: string;
completionNote?: string;
actualDuration?: number;
}
export interface StudentRecordDto {
focus?: number;
participation?: number;
interest?: number;
understanding?: number;
notes?: string;
}
// 后端状态值: scheduled, in_progress, completed, cancelled
const STATUS_TO_BACKEND: Record<string, string> = {
PLANNED: 'scheduled',
IN_PROGRESS: 'in_progress',
COMPLETED: 'completed',
CANCELLED: 'cancelled',
};
// 获取授课记录列表
export function getLessons(params?: {
pageNum?: number;
page?: number;
pageSize?: number;
status?: string;
startDate?: string;
endDate?: string;
}): Promise<{
items: any[];
total: number;
page: number;
pageSize: number;
}> {
const pageNum = params?.pageNum ?? params?.page ?? 1;
const status = params?.status ? (STATUS_TO_BACKEND[params.status] || params.status) : undefined;
return http
.get<{ list?: any[]; records?: any[]; total?: number | string; pageNum?: number; pageSize?: number }>(
'/v1/teacher/lessons',
{
params: {
pageNum,
pageSize: params?.pageSize ?? 10,
status,
startDate: params?.startDate,
endDate: params?.endDate,
},
}
)
.then((res) => {
const list = res?.list ?? res?.records ?? [];
return {
items: Array.isArray(list) ? list : [],
total: typeof res?.total === 'string' ? parseInt(res.total, 10) || 0 : (res?.total ?? 0),
page: res?.pageNum ?? pageNum,
pageSize: res?.pageSize ?? 10,
};
});
}
// 获取单个授课记录详情id 使用 string 避免 Long 精度丢失)
export function getLesson(id: number | string): Promise<any> {
return http.get(`/v1/teacher/lessons/${id}`) as any;
}
// 创建授课记录(备课)
export function createLesson(data: CreateLessonDto): Promise<any> {
return http.post('/v1/teacher/lessons', data) as any;
}
// 开始上课id 使用 string 避免 Long 精度丢失)
export function startLesson(id: number | string): Promise<any> {
return http.post(`/v1/teacher/lessons/${id}/start`) as any;
}
// 结束上课id 使用 string 避免 Long 精度丢失)
export function finishLesson(id: number | string, data: FinishLessonDto): Promise<any> {
return http.post(`/v1/teacher/lessons/${id}/complete`, data) as any;
}
// 取消课程id 使用 string 避免 Long 精度丢失)
export function cancelLesson(id: number | string): Promise<any> {
return http.post(`/v1/teacher/lessons/${id}/cancel`) as any;
}
// 保存学生评价记录lessonId 使用 string 避免 Long 精度丢失)
export function saveStudentRecord(
lessonId: number | string,
studentId: number,
data: StudentRecordDto
): Promise<any> {
return http.post(`/v1/teacher/lessons/${lessonId}/students/${studentId}/record`, data) as any;
}
// 获取课程所有学生记录
export interface StudentWithRecord {
id: number;
name: string;
gender?: string;
record: {
id: number;
focus?: number;
participation?: number;
interest?: number;
understanding?: number;
notes?: string;
} | null;
}
export interface StudentRecordsResponse {
lesson: {
id: number;
status: string;
className: string;
};
students: StudentWithRecord[];
}
export function getStudentRecords(lessonId: number | string): Promise<StudentRecordsResponse> {
return http.get(`/v1/teacher/lessons/${lessonId}/students/records`) as any;
}
// 批量保存学生评价记录lessonId 使用 string 避免 Long 精度丢失)
export function batchSaveStudentRecords(
lessonId: number | string,
records: Array<{ studentId: number } & StudentRecordDto>
): Promise<{ count: number; records: any[] }> {
return http.post(`/v1/teacher/lessons/${lessonId}/students/batch-records`, { records }) as any;
}
// ==================== 教师首页 API ====================
export interface DashboardData {
stats: {
classCount: number;
studentCount: number;
lessonCount: number;
courseCount: number;
};
todayLessons: Array<{
id: number;
courseId: number;
courseName: string;
pictureBookName?: string;
classId: number;
className: string;
plannedDatetime: string;
status: string;
duration: number;
}>;
recommendedCourses: Array<{
id: number;
name: string;
pictureBookName?: string;
coverImagePath?: string;
duration: number;
usageCount: number;
avgRating: number;
gradeTags: string[];
}>;
weeklyStats: {
lessonCount: number;
studentParticipation: number;
avgRating: number;
totalDuration: number;
};
recentActivities: Array<{
id: number;
type: string;
description: string;
time: string;
}>;
}
export const getTeacherDashboard = () =>
http.get('/v1/teacher/dashboard') as any;
export const getTodayLessons = () =>
http.get('/v1/teacher/today-lessons') as any;
export const getRecommendedCourses = () =>
http.get('/v1/teacher/recommended-courses') as any;
export const getWeeklyStats = () =>
http.get('/v1/teacher/weekly-stats') as any;
// ==================== 教师统计趋势 ====================
export interface TeacherLessonTrendItem {
month: string;
lessonCount: number;
avgRating: number;
}
export interface TeacherCourseUsageItem {
name: string;
value: number;
}
export const getTeacherLessonTrend = (months?: number) => {
return http.get('/v1/teacher/lesson-trend', { params: { months } }) as any;
};
export const getTeacherCourseUsage = () =>
http.get('/v1/teacher/course-usage') as any;
// ==================== 课程反馈 API ====================
export interface FeedbackDto {
designQuality?: number;
participation?: number;
goalAchievement?: number;
stepFeedbacks?: any;
pros?: string;
suggestions?: string;
activitiesDone?: any;
}
export interface LessonFeedback {
id: number;
lessonId: number;
teacherId: number;
designQuality?: number;
participation?: number;
goalAchievement?: number;
stepFeedbacks?: any;
pros?: string;
suggestions?: string;
activitiesDone?: any;
createdAt: string;
updatedAt: string;
teacher?: {
id: number;
name: string;
};
lesson?: {
id: number;
startDatetime?: string;
course: {
id: number;
name: string;
pictureBookName?: string;
};
class: {
id: number;
name: string;
};
};
}
// 提交课程反馈
export function submitFeedback(lessonId: number, data: FeedbackDto): Promise<LessonFeedback> {
return http.post(`/v1/teacher/lessons/${lessonId}/feedback`, data) as any;
}
// 获取课程反馈
export function getFeedback(lessonId: number): Promise<LessonFeedback | null> {
return http.get(`/v1/teacher/lessons/${lessonId}/feedback`) as any;
}
// ==================== 学校端反馈 API ====================
export interface FeedbackQueryParams {
pageNum?: number;
pageSize?: number;
teacherId?: number;
courseId?: number;
}
export interface FeedbackStats {
totalFeedbacks: number;
avgDesignQuality: number;
avgParticipation: number;
avgGoalAchievement: number;
courseStats: Record<number, { count: number; avgRating: number }>;
}
// 获取学校端反馈列表
export function getSchoolFeedbacks(params: FeedbackQueryParams): Promise<{
list: LessonFeedback[];
total: number;
pageNum: number;
pageSize: number;
}> {
return http.get<{ list: LessonFeedback[]; total: number; pageNum: number; pageSize: number }>('/v1/school/feedbacks', {
params: {
pageNum: params.pageNum,
pageSize: params.pageSize,
teacherId: params.teacherId,
courseId: params.courseId,
},
});
}
// 获取反馈统计
export function getFeedbackStats(): Promise<FeedbackStats> {
return http.get<FeedbackStats>('/v1/school/feedbacks/stats');
}
// 获取教师自己的反馈列表
export function getTeacherFeedbacks(params: FeedbackQueryParams): Promise<{
items: LessonFeedback[];
total: number;
page: number;
pageSize: number;
}> {
// 直接使用 http 调用后端 API
return http.get<{ list: LessonFeedback[]; total: number; pageNum: number; pageSize: number }>('/v1/teacher/feedbacks', {
params: {
pageNum: params.pageNum,
pageSize: params.pageSize,
},
}).then(res => ({
items: res.list || res.records || [],
total: res.total || 0,
page: res.pageNum || 1,
pageSize: res.pageSize || 10,
}));
}
// 获取教师自己的反馈统计
export function getTeacherFeedbackStats(): Promise<FeedbackStats> {
return http.get<{
totalFeedbacks: number;
avgDesignQuality: number;
avgParticipation: number;
avgGoalAchievement: number;
byType: Record<string, number>;
}>('/v1/teacher/feedbacks/stats').then(res => ({
totalFeedbacks: res.totalFeedbacks || 0,
avgDesignQuality: res.avgDesignQuality || 0,
avgParticipation: res.avgParticipation || 0,
avgGoalAchievement: res.avgGoalAchievement || 0,
courseStats: res.byType || {},
}));
}
// ==================== 课程进度追踪 API ====================
export interface LessonProgress {
id: number;
lessonIds: number[];
completedLessonIds: number[];
currentLessonId?: number;
currentStepId?: number;
progressData: any;
}
export interface SaveLessonProgressDto {
lessonIds?: number[];
completedLessonIds?: number[];
currentLessonId?: number;
currentStepId?: number;
progressData?: any;
}
// 保存课程进度lessonId 使用 string 避免 Long 精度丢失)
export function saveLessonProgress(lessonId: number | string, data: SaveLessonProgressDto): Promise<LessonProgress> {
return http.put(`/v1/teacher/lessons/${lessonId}/progress`, data) as any;
}
// 获取课程进度lessonId 使用 string 避免 Long 精度丢失)
export function getLessonProgress(lessonId: number | string): Promise<LessonProgress> {
return http.get(`/v1/teacher/lessons/${lessonId}/progress`) as any;
}
// ==================== 排课管理 API ====================
export interface TeacherSchedule {
id: number;
classId: number;
className: string;
courseId: number;
courseName: string;
teacherId?: number;
scheduledDate?: string;
scheduledTime?: string;
weekDay?: number;
repeatType: 'NONE' | 'DAILY' | 'WEEKLY';
source: 'SCHOOL' | 'TEACHER';
status: 'ACTIVE' | 'CANCELLED';
note?: string;
hasLesson: boolean;
lessonId?: number;
lessonStatus?: string;
createdAt: string;
}
export interface CreateTeacherScheduleDto {
classId: number;
courseId: number;
scheduledDate?: string;
scheduledTime?: string;
weekDay?: number;
repeatType?: 'NONE' | 'DAILY' | 'WEEKLY';
repeatEndDate?: string;
note?: string;
}
export interface TeacherTimetableItem {
date: string;
weekDay: number;
schedules: TeacherSchedule[];
}
export const getTeacherSchedules = (params?: {
startDate?: string;
endDate?: string;
status?: string;
pageNum?: number;
pageSize?: number;
}) => {
return http.get<{ list: TeacherSchedule[]; total: number }>('/v1/teacher/schedules', { params })
.then(res => ({
list: res.list || res.records || [],
total: res.total || 0,
}));
};
export const getTeacherTimetable = (params: { startDate: string; endDate: string }) => {
return http.get<any[]>('/v1/teacher/schedules/timetable', { params })
.then(res => {
// 后端返回的是数组格式或包含 schedules 的对象
if (Array.isArray(res)) {
return res;
}
return res?.schedules || res?.data || [];
});
};
export const getTodayTeacherSchedules = () =>
http.get<TeacherSchedule[]>('/v1/teacher/schedules/today');
export const createTeacherSchedule = (data: CreateTeacherScheduleDto) =>
http.post<TeacherSchedule>('/v1/teacher/schedules', data);
export const updateTeacherSchedule = (id: number, data: Partial<CreateTeacherScheduleDto> & { status?: string }) =>
http.put<TeacherSchedule>(`/v1/teacher/schedules/${id}`, data);
export const cancelTeacherSchedule = (id: number) =>
http.delete<{ message: string }>(`/v1/teacher/schedules/${id}`);
// ==================== 阅读任务 API ====================
export interface TeacherTask {
id: number;
tenantId: number;
title: string;
description?: string;
taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK';
targetType: 'CLASS' | 'STUDENT';
status: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
relatedCourseId?: number;
startDate: string;
endDate: string;
createdBy: number;
createdAt: string;
updatedAt: string;
course?: {
id: number;
name: string;
};
targetCount?: number;
completionCount?: number;
}
export interface TaskCompletion {
id: number;
taskId: number;
studentId: number;
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED';
completedAt?: string;
feedback?: string;
parentFeedback?: string;
createdAt: string;
student: {
id: number;
name: string;
gender?: string;
class?: {
id: number;
name: string;
};
};
}
export interface CreateTeacherTaskDto {
title: string;
description?: string;
taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK';
targetType: 'CLASS' | 'STUDENT';
targetIds: number[];
relatedCourseId?: number;
startDate: string;
endDate: string;
}
export interface UpdateTaskCompletionDto {
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED';
feedback?: string;
}
// 获取教师任务列表
export const getTeacherTasks = (params?: { pageNum?: number; pageSize?: number; keyword?: string; type?: string; status?: string }) =>
http.get<{ list: any[]; total: number; pageNum: number; pageSize: number }>('/v1/teacher/tasks', { params })
.then(res => ({
items: res.list || [],
total: res.total || 0,
page: res.pageNum || 1,
pageSize: res.pageSize || 10,
}));
export const getTeacherTask = (id: number) =>
http.get(`/v1/teacher/tasks/${id}`) as any;
// 教师端没有这些接口,返回空数据
export const getTeacherTaskCompletions = (_taskId: number) =>
Promise.resolve([]);
export const createTeacherTask = (data: CreateTeacherTaskDto) =>
http.post('/v1/teacher/tasks', data) as any;
export const updateTeacherTask = (id: number, data: Partial<CreateTeacherTaskDto> & { status?: string }) =>
http.put(`/v1/teacher/tasks/${id}`, data) as any;
export const deleteTeacherTask = (id: number) =>
http.delete(`/v1/teacher/tasks/${id}`) as any;
// 后端没有这些接口
export const updateTaskCompletion = (_taskId: number, _studentId: number, _data: UpdateTaskCompletionDto) =>
Promise.reject(new Error('接口未实现'));
export const sendTaskReminder = (_taskId: number) =>
Promise.reject(new Error('接口未实现'));
// ==================== 任务模板 API ====================
export interface TaskTemplate {
id: number;
tenantId: number;
name: string;
description?: string;
taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK';
relatedCourseId?: number;
defaultDuration: number;
isDefault: boolean;
status: string;
createdBy: number;
createdAt: string;
updatedAt: string;
course?: {
id: number;
name: string;
pictureBookName?: string;
};
}
export interface CreateTaskTemplateDto {
name: string;
description?: string;
taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK';
relatedCourseId?: number;
defaultDuration?: number;
isDefault?: boolean;
}
export interface CreateTaskFromTemplateDto {
templateId: number;
targetIds: number[];
targetType: 'CLASS' | 'STUDENT';
startDate?: string;
}
export const getTaskTemplates = () =>
http.get<{ records: any[]; total: number }>('/v1/teacher/task-templates')
.then(res => ({
items: res.records || [],
total: res.total || 0,
}));
export const getTaskTemplate = (id: number) =>
http.get(`/v1/teacher/task-templates/${id}`) as any;
export const getDefaultTaskTemplate = (taskType: string) =>
http.get('/v1/teacher/task-templates/default', { params: { taskType } }) as any;
export const createTaskFromTemplate = (data: CreateTaskFromTemplateDto) =>
http.post('/v1/teacher/tasks/from-template', data) as any;
// ==================== 任务统计 API ====================
export interface TaskStats {
totalTasks: number;
publishedTasks: number;
completedTasks: number;
inProgressTasks: number;
pendingCount: number;
totalCompletions: number;
completionRate: number;
}
export interface TaskStatsByType {
[key: string]: {
total: number;
completed: number;
rate: number;
};
}
export interface TaskStatsByClass {
classId: number;
className: string;
grade: string;
total: number;
completed: number;
rate: number;
}
export interface MonthlyTaskStats {
month: string;
tasks: number;
completions: number;
completed: number;
rate: number;
}
export const getTaskStats = () =>
http.get('/v1/teacher/tasks/stats') as any;
export const getTaskStatsByType = () =>
http.get('/v1/teacher/tasks/stats/by-type') as any;
export const getTaskStatsByClass = () =>
http.get('/v1/teacher/tasks/stats/by-class') as any;
export const getMonthlyTaskStats = (months?: number) => {
return http.get('/v1/teacher/tasks/stats/monthly', { params: { months } }) as any;
};