- 家长端阅读任务:从 task_target 获取任务(学生+班级),支持家长关联孩子查看 - 家长端提交任务:修复照片上传,使用 uploadFile(file, poster) 符合 API 规范 - 教师端任务列表:TaskResponse 新增 targetCount、completionCount,填充目标人数与完成人数 - 数据库迁移:V45 添加 task_completion.photos,V46 添加 submitted_at、reviewed_at - 其他:班级学生、家长孩子、学校班级统计等修复 Made-with: Cursor
894 lines
24 KiB
TypeScript
894 lines
24 KiB
TypeScript
import { http } from './index';
|
||
|
||
// ============= 类型定义(保持向后兼容) =============
|
||
|
||
// ==================== 教师课程 API ====================
|
||
|
||
export interface TeacherCourseQueryParams {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
grade?: string;
|
||
domain?: string;
|
||
lessonType?: string;
|
||
keyword?: string;
|
||
}
|
||
|
||
/** 课程环节标签(列表展示) */
|
||
export interface LessonTag {
|
||
name: string;
|
||
lessonType: string;
|
||
}
|
||
|
||
export interface TeacherCourse {
|
||
id: number;
|
||
name: string;
|
||
pictureBookName?: string;
|
||
coverImagePath?: string;
|
||
gradeTags: string[];
|
||
domainTags: string[];
|
||
lessonTags?: LessonTag[];
|
||
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,
|
||
grade: params.grade,
|
||
domain: params.domain,
|
||
lessonType: params.lessonType,
|
||
},
|
||
}).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;
|
||
}
|
||
|
||
// 获取班级学生列表
|
||
// 后端返回 { 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;
|
||
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,
|
||
},
|
||
}).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,
|
||
}));
|
||
}
|
||
|
||
// 获取班级教师列表
|
||
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;
|
||
}
|
||
|
||
/** 从排课开始上课:创建课时并开始,适用于课表/排课详情的「开始上课」按钮 */
|
||
export function startLessonFromSchedule(schedulePlanId: number): Promise<{ id: number }> {
|
||
return http.post(`/v1/teacher/lessons/from-schedule/${schedulePlanId}/start`) 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;
|
||
coursePackageName?: string; // 课程包名称,与 courseName 二选一展示
|
||
lessonType?: string; // 课程类型代码
|
||
lessonTypeName?: 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 {
|
||
name: string; // 计划名称,与学校端 SchedulePlanCreateRequest 一致
|
||
classId: number;
|
||
courseId: number;
|
||
coursePackageId?: number; // 课程包 ID
|
||
lessonType?: string; // 课程类型 (INTRODUCTION/COLLECTIVE/LANGUAGE 等)
|
||
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;
|
||
relatedBookName?: string; // 关联绘本名称
|
||
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' | 'SUBMITTED' | 'REVIEWED';
|
||
statusText?: string;
|
||
photos?: string[];
|
||
videoUrl?: string;
|
||
audioUrl?: string;
|
||
content?: string;
|
||
parentFeedback?: string; // 家长反馈(兼容旧字段)
|
||
submittedAt?: string;
|
||
reviewedAt?: string;
|
||
completedAt?: string; // 完成时间(兼容旧字段)
|
||
createdAt: string;
|
||
student: {
|
||
id: number;
|
||
name: string;
|
||
avatar?: string;
|
||
gender?: string;
|
||
class?: {
|
||
id: number;
|
||
name: string;
|
||
grade?: string;
|
||
};
|
||
classInfo?: {
|
||
id: number;
|
||
name: string;
|
||
grade?: string;
|
||
};
|
||
};
|
||
feedback?: TaskFeedback;
|
||
}
|
||
|
||
export interface TaskFeedback {
|
||
id: number;
|
||
completionId: number;
|
||
result: 'EXCELLENT' | 'PASSED' | 'NEEDS_WORK';
|
||
resultText?: string;
|
||
rating?: number;
|
||
comment?: string;
|
||
teacherId?: number;
|
||
teacherName?: string;
|
||
teacherAvatar?: string;
|
||
createdAt?: string;
|
||
}
|
||
|
||
export interface CreateTeacherTaskDto {
|
||
title: string;
|
||
description?: string;
|
||
taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK';
|
||
targetType: 'CLASS' | 'STUDENT';
|
||
targetIds: number[];
|
||
relatedCourseId?: number;
|
||
relatedBookName?: string; // 关联绘本名称
|
||
startDate: string;
|
||
endDate: string;
|
||
}
|
||
|
||
export interface TaskFeedbackDto {
|
||
result: 'EXCELLENT' | 'PASSED' | 'NEEDS_WORK';
|
||
rating?: number;
|
||
comment?: 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, params?: {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
status?: string;
|
||
}) =>
|
||
http.get<{ list: any[]; total: number; pageNum: number; pageSize: number }>(
|
||
`/v1/teacher/tasks/${taskId}/completions`,
|
||
{ params }
|
||
).then(res => ({
|
||
items: res.list || [],
|
||
total: res.total || 0,
|
||
page: res.pageNum || 1,
|
||
pageSize: res.pageSize || 10,
|
||
}));
|
||
|
||
// 获取提交详情
|
||
export const getCompletionDetail = (completionId: number) =>
|
||
http.get<TaskCompletion>(`/v1/teacher/tasks/completions/${completionId}`) as any;
|
||
|
||
// 提交评价
|
||
export const submitCompletionFeedback = (completionId: number, data: TaskFeedbackDto) =>
|
||
http.post<TaskFeedback>(`/v1/teacher/tasks/completions/${completionId}/feedback`, data) as any;
|
||
|
||
// 修改评价
|
||
export const updateCompletionFeedback = (completionId: number, data: TaskFeedbackDto) =>
|
||
http.put<TaskFeedback>(`/v1/teacher/tasks/completions/${completionId}/feedback`, data) as any;
|
||
|
||
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('请使用 submitCompletionFeedback 或 updateCompletionFeedback'));
|
||
|
||
export const sendTaskReminder = (_taskId: number) =>
|
||
http.post(`/v1/teacher/tasks/${_taskId}/remind`) as any;
|
||
|
||
// ==================== 任务模板 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 = (params?: { pageNum?: number; pageSize?: number }) =>
|
||
http.get<{ list: any[]; total: number }>('/v1/teacher/task-templates', { params })
|
||
.then(res => ({
|
||
items: res.list || [],
|
||
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;
|
||
};
|
||
|