后端新增: - 新增 LogModule 枚举类,统一管理操作日志模块 - 新增 LogOperationType 枚举类,统一管理操作类型 - 修改 @Log 注解,module 参数改为 LogModule 枚举类型 - 修改 LogAspect 切面,将枚举转换为字符串存储 - 新增 GET /api/v1/school/operation-logs/modules 接口 前端修改: - 新增 logOperationType.ts 常量文件 - 修改 OperationLogView.vue,通过 API 动态获取模块列表 - 修改 school.ts,新增 getOperationLogModules API 数据库修改: - OperationLog 实体新增 requestParams 字段,用于记录请求参数 - 新增 V48 迁移脚本,添加 request_params 字段 重构: - 所有 Controller 中的 @Log 注解改为使用 LogModule 枚举 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1408 lines
39 KiB
TypeScript
1408 lines
39 KiB
TypeScript
import { http } from './index';
|
||
|
||
// ==================== 类型定义 ====================
|
||
|
||
export interface TeacherQueryParams {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
keyword?: string;
|
||
status?: string;
|
||
}
|
||
|
||
export interface Teacher {
|
||
id: number;
|
||
name: string;
|
||
phone: string;
|
||
email?: string;
|
||
loginAccount: string;
|
||
status: string;
|
||
classIds: number[];
|
||
classNames?: string | string[];
|
||
lessonCount?: number;
|
||
createdAt: string;
|
||
}
|
||
|
||
export interface CreateTeacherDto {
|
||
name: string;
|
||
phone: string;
|
||
email?: string;
|
||
loginAccount: string;
|
||
password?: string;
|
||
classIds?: number[];
|
||
}
|
||
|
||
export interface StudentQueryParams {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
classId?: number;
|
||
keyword?: string;
|
||
}
|
||
|
||
export interface Student {
|
||
id: number;
|
||
name: string;
|
||
gender?: string;
|
||
birthDate?: string;
|
||
classId: number;
|
||
className?: string;
|
||
parentName?: string;
|
||
parentPhone?: string;
|
||
lessonCount?: number;
|
||
readingCount?: number;
|
||
avgScore?: number;
|
||
createdAt: string;
|
||
}
|
||
|
||
export interface CreateStudentDto {
|
||
name: string;
|
||
gender?: string;
|
||
birthDate?: string;
|
||
classId: number;
|
||
parentName?: string;
|
||
parentPhone?: string;
|
||
}
|
||
|
||
export interface ClassInfo {
|
||
id: number;
|
||
name: string;
|
||
grade: string;
|
||
teacherId?: number;
|
||
teacherName?: string;
|
||
studentCount: number;
|
||
lessonCount: number;
|
||
createdAt?: string;
|
||
teachers?: ClassTeacher[]; // 新增:教师团队
|
||
}
|
||
|
||
export interface ClassTeacher {
|
||
id: number;
|
||
teacherId: number;
|
||
teacherName: string;
|
||
teacherPhone?: string;
|
||
teacherEmail?: string;
|
||
role: 'MAIN' | 'ASSIST' | 'CARE';
|
||
isPrimary: boolean;
|
||
createdAt?: string;
|
||
}
|
||
|
||
export interface AddClassTeacherDto {
|
||
teacherId: number;
|
||
role: 'MAIN' | 'ASSIST' | 'CARE';
|
||
isPrimary?: boolean;
|
||
}
|
||
|
||
export interface UpdateClassTeacherDto {
|
||
role?: 'MAIN' | 'ASSIST' | 'CARE';
|
||
isPrimary?: boolean;
|
||
}
|
||
|
||
export interface TransferStudentDto {
|
||
toClassId: number;
|
||
reason?: string;
|
||
}
|
||
|
||
export interface StudentClassHistory {
|
||
id: number;
|
||
fromClass: { id: number; name: string; grade: string } | null;
|
||
toClass: { id: number; name: string; grade: string };
|
||
reason?: string;
|
||
operatedBy?: number;
|
||
createdAt: string;
|
||
}
|
||
|
||
export interface CreateClassDto {
|
||
name: string;
|
||
grade: string;
|
||
teacherId?: number;
|
||
}
|
||
|
||
export interface SchoolStats {
|
||
teacherCount: number;
|
||
studentCount: number;
|
||
classCount: number;
|
||
lessonCount: number;
|
||
}
|
||
|
||
export interface PackageInfo {
|
||
packageType: string;
|
||
teacherQuota: number;
|
||
studentQuota: number;
|
||
storageQuota: number;
|
||
teacherCount: number;
|
||
studentCount: number;
|
||
storageUsed: number;
|
||
startDate: string;
|
||
expireDate: string;
|
||
status: string;
|
||
}
|
||
|
||
export interface PackageUsage {
|
||
teacher: {
|
||
used: number;
|
||
quota: number;
|
||
percentage: number;
|
||
};
|
||
student: {
|
||
used: number;
|
||
quota: number;
|
||
percentage: number;
|
||
};
|
||
storage: {
|
||
used: number;
|
||
quota: number;
|
||
percentage: number;
|
||
};
|
||
}
|
||
|
||
// ==================== 教师管理 ====================
|
||
|
||
export const getTeachers = (params: TeacherQueryParams) =>
|
||
http.get<{ list: Teacher[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/teachers', { params });
|
||
|
||
export const getTeacher = (id: number) =>
|
||
http.get<Teacher>(`/v1/school/teachers/${id}`);
|
||
|
||
export const createTeacher = (data: CreateTeacherDto) =>
|
||
http.post<Teacher>('/v1/school/teachers', data);
|
||
|
||
export const updateTeacher = (id: number, data: Partial<CreateTeacherDto>) =>
|
||
http.put<Teacher>(`/v1/school/teachers/${id}`, data);
|
||
|
||
export const deleteTeacher = (id: number) =>
|
||
http.delete(`/v1/school/teachers/${id}`);
|
||
|
||
export const resetTeacherPassword = (id: number) =>
|
||
http.post<{ tempPassword: string }>(`/v1/school/teachers/${id}/reset-password`);
|
||
|
||
// ==================== 学生管理 ====================
|
||
|
||
export const getStudents = (params: StudentQueryParams) =>
|
||
http.get<{ list: Student[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/students', { params });
|
||
|
||
export const getStudent = (id: number) =>
|
||
http.get<Student>(`/v1/school/students/${id}`);
|
||
|
||
export const createStudent = (data: CreateStudentDto) =>
|
||
http.post<Student>('/v1/school/students', data);
|
||
|
||
export const updateStudent = (id: number, data: Partial<CreateStudentDto>) =>
|
||
http.put<Student>(`/v1/school/students/${id}`, data);
|
||
|
||
export const deleteStudent = (id: number) =>
|
||
http.delete(`/v1/school/students/${id}`);
|
||
|
||
// ==================== 学生批量导入 ====================
|
||
|
||
export interface ImportResult {
|
||
success: number;
|
||
failed: number;
|
||
errors: Array<{ row: number; message: string }>;
|
||
}
|
||
|
||
export interface ImportTemplate {
|
||
headers: string[];
|
||
example: string[];
|
||
notes: string[];
|
||
}
|
||
|
||
export const getStudentImportTemplate = () =>
|
||
http.get<ImportTemplate>('/v1/school/students/import/template');
|
||
|
||
export const importStudents = (file: File, defaultClassId?: number): Promise<ImportResult> => {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
const params = defaultClassId ? { defaultClassId } : {};
|
||
return http.post('/v1/school/students/import', formData, {
|
||
headers: {
|
||
'Content-Type': 'multipart/form-data',
|
||
},
|
||
params,
|
||
});
|
||
};
|
||
|
||
// ==================== 班级管理 ====================
|
||
|
||
export const getClasses = () =>
|
||
http.get<{ list: ClassInfo[]; total: number }>('/v1/school/classes').then(res => res.list);
|
||
|
||
export const getClass = (id: number) =>
|
||
http.get<ClassInfo>(`/v1/school/classes/${id}`);
|
||
|
||
export const createClass = (data: CreateClassDto) =>
|
||
http.post<ClassInfo>('/v1/school/classes', data);
|
||
|
||
export const updateClass = (id: number, data: Partial<CreateClassDto>) =>
|
||
http.put<ClassInfo>(`/v1/school/classes/${id}`, data);
|
||
|
||
export const deleteClass = (id: number) =>
|
||
http.delete(`/v1/school/classes/${id}`);
|
||
|
||
export const getClassStudents = (classId: number, params?: { pageNum?: number; pageSize?: number; keyword?: string }) =>
|
||
http.get<{ list: Student[]; total: number; pageNum: number; pageSize: number; pages: number; class?: ClassInfo }>(`/v1/school/classes/${classId}/students`, { params });
|
||
|
||
// ==================== 统计数据 ====================
|
||
|
||
export const getSchoolStats = () =>
|
||
http.get<SchoolStats>('/v1/school/stats');
|
||
|
||
export const getActiveTeachers = (limit?: number) =>
|
||
http.get<Array<{
|
||
teacherId: number | string;
|
||
teacherName: string;
|
||
classNames: string;
|
||
lessonCount: number;
|
||
courseCount: number;
|
||
lastActiveAt?: string;
|
||
activityLevelCode: string;
|
||
activityLevelDesc: string;
|
||
}>>('/v1/school/stats/teachers', { params: { limit } });
|
||
|
||
export const getCourseUsageStats = (startDate?: string, endDate?: string) => {
|
||
const params: Record<string, string> = {};
|
||
if (startDate) params.startDate = startDate;
|
||
if (endDate) params.endDate = endDate;
|
||
return http.get<Array<{ courseId: number; courseName: string; usageCount: number; studentCount?: number; avgDuration?: number; lastUsedAt?: string }>>('/v1/school/stats/courses', { params });
|
||
};
|
||
|
||
// ==================== 套餐信息(旧 API,保留兼容) ====================
|
||
|
||
export const getPackageInfo = () =>
|
||
http.get<PackageInfo>('/v1/school/package');
|
||
|
||
export const getPackageUsage = () =>
|
||
http.get<PackageUsage>('/v1/school/package/usage');
|
||
|
||
// ==================== 套餐管理(两层结构) ====================
|
||
|
||
// 课程包项(对应后端 CourseCollectionResponse.CoursePackageItem / CoursePackageResponse)
|
||
export interface CoursePackageItem {
|
||
id: number | string;
|
||
name: string;
|
||
description?: string;
|
||
gradeLevels: string[] | string;
|
||
courseCount: number;
|
||
sortOrder?: number;
|
||
/** getPackagesByCollection 返回的课程包包含课程列表 */
|
||
courses?: Array<{
|
||
id: number;
|
||
name: string;
|
||
gradeLevel?: string;
|
||
sortOrder?: number;
|
||
scheduleRefData?: string;
|
||
}>;
|
||
}
|
||
|
||
// 课程套餐(最上层,对应后端 CourseCollectionResponse)
|
||
export interface CourseCollection {
|
||
id: number | string;
|
||
name: string;
|
||
description?: string;
|
||
price: number;
|
||
discountPrice?: number;
|
||
discountType?: string;
|
||
gradeLevels: string[];
|
||
status: string;
|
||
packageCount: number;
|
||
createdAt: string;
|
||
publishedAt?: string;
|
||
submittedAt?: string;
|
||
reviewedAt?: string;
|
||
updatedAt?: string;
|
||
startDate?: string; // 开始日期(租户套餐)
|
||
endDate?: string; // 结束日期(租户套餐)
|
||
packages?: CoursePackageItem[]; // 包含的课程包列表
|
||
}
|
||
|
||
// 课程包(中间层,7 步流程创建的教学资源)
|
||
export interface CoursePackage {
|
||
id: number | string;
|
||
name: string;
|
||
description?: string;
|
||
pictureBookName?: string;
|
||
gradeTags?: string[];
|
||
gradeLevels?: string[];
|
||
status: string;
|
||
courseCount: number;
|
||
duration?: number;
|
||
sortOrder?: number;
|
||
courses?: Array<{
|
||
id: number;
|
||
name: string;
|
||
gradeLevel: string;
|
||
sortOrder: number;
|
||
scheduleRefData?: string;
|
||
}>;
|
||
}
|
||
|
||
export interface RenewPackageDto {
|
||
endDate: string;
|
||
pricePaid?: number;
|
||
}
|
||
|
||
// 获取课程套餐列表(三层架构)
|
||
export const getCourseCollections = () =>
|
||
http.get<CourseCollection[]>('/v1/school/packages');
|
||
|
||
// 获取课程套餐下的课程包列表(返回 CoursePackageItem 列表)
|
||
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);
|
||
|
||
|
||
// ==================== 系统设置 ====================
|
||
|
||
export interface SystemSettings {
|
||
id: number;
|
||
tenantId: number;
|
||
schoolName?: string;
|
||
schoolLogo?: string;
|
||
address?: string;
|
||
notifyOnLesson: boolean;
|
||
notifyOnTask: boolean;
|
||
notifyOnGrowth: boolean;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
|
||
export interface UpdateSettingsDto {
|
||
schoolName?: string;
|
||
schoolLogo?: string;
|
||
address?: string;
|
||
notifyOnLesson?: boolean;
|
||
notifyOnTask?: boolean;
|
||
notifyOnGrowth?: boolean;
|
||
}
|
||
|
||
export const getSettings = () =>
|
||
http.get<SystemSettings>('/v1/school/settings');
|
||
|
||
export const updateSettings = (data: UpdateSettingsDto) =>
|
||
http.put<SystemSettings>('/v1/school/settings', data);
|
||
|
||
// ==================== 课程管理 ====================
|
||
|
||
export interface Course {
|
||
id: number;
|
||
tenantId?: number;
|
||
name: string;
|
||
code?: string;
|
||
description?: string;
|
||
coverUrl?: string;
|
||
coverImagePath?: string;
|
||
pictureBookName?: string;
|
||
category?: string;
|
||
ageRange?: string;
|
||
difficultyLevel?: string;
|
||
durationMinutes?: number;
|
||
duration?: number;
|
||
objectives?: string;
|
||
status: string;
|
||
isSystem: number;
|
||
version?: string;
|
||
usageCount?: number;
|
||
teacherCount?: number;
|
||
gradeTags?: string[];
|
||
domainTags?: string[];
|
||
createdAt?: string;
|
||
updatedAt?: string;
|
||
publishedAt?: string;
|
||
}
|
||
|
||
export interface SchoolCourseQueryParams {
|
||
keyword?: string;
|
||
grade?: string; // 小班|中班|大班
|
||
domain?: string; // 健康|语言|社会|科学|艺术(传英文码)
|
||
lessonType?: string; // INTRODUCTION|COLLECTIVE|LANGUAGE|HEALTH|SCIENCE|SOCIAL|ART
|
||
}
|
||
|
||
export const getSchoolCourses = (params?: SchoolCourseQueryParams) =>
|
||
http.get<Course[]>('/v1/school/courses', { params });
|
||
|
||
// 分页版本(用于课程管理页面)
|
||
export const getSchoolCourseList = (params?: {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
keyword?: string;
|
||
grade?: string;
|
||
domain?: string;
|
||
lessonType?: string;
|
||
}) =>
|
||
http.get<{ list: Course[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/courses', { params });
|
||
|
||
export const getSchoolCourse = (id: number | string): Promise<any> =>
|
||
http.get(`/v1/school/courses/${id}`) as any;
|
||
|
||
// ==================== 班级教师管理 ====================
|
||
|
||
export const getClassTeachers = (classId: number) =>
|
||
http.get<ClassTeacher[]>(`/v1/school/classes/${classId}/teachers`);
|
||
|
||
export const addClassTeacher = (classId: number, data: AddClassTeacherDto) =>
|
||
http.post<ClassTeacher>(`/v1/school/classes/${classId}/teachers`, data);
|
||
|
||
export const updateClassTeacher = (classId: number, teacherId: number, data: UpdateClassTeacherDto) =>
|
||
http.put<ClassTeacher>(`/v1/school/classes/${classId}/teachers/${teacherId}`, data);
|
||
|
||
export const removeClassTeacher = (classId: number, teacherId: number) =>
|
||
http.delete<{ message: string }>(`/v1/school/classes/${classId}/teachers/${teacherId}`);
|
||
|
||
// ==================== 学生调班 ====================
|
||
|
||
export const transferStudent = (studentId: number, data: TransferStudentDto) =>
|
||
http.post<{ message: string }>(`/v1/school/students/${studentId}/transfer`, data);
|
||
|
||
export const getStudentClassHistory = (studentId: number) =>
|
||
http.get<StudentClassHistory[]>(`/v1/school/students/${studentId}/history`);
|
||
|
||
// ==================== 排课管理 ====================
|
||
|
||
// 课程类型枚举(与后端 LessonTypeEnum 对齐)
|
||
export type LessonType =
|
||
| 'INTRODUCTION' | 'INTRO' | 'COLLECTIVE'
|
||
| 'LANGUAGE' | 'HEALTH' | 'SCIENCE' | 'SOCIAL' | 'SOCIETY' | 'ART'
|
||
| 'DOMAIN_HEALTH' | 'DOMAIN_LANGUAGE' | 'DOMAIN_SOCIAL' | 'DOMAIN_SCIENCE' | 'DOMAIN_ART';
|
||
|
||
// 课程类型信息
|
||
export interface LessonTypeInfo {
|
||
lessonType: string;
|
||
lessonTypeName: string;
|
||
count: number;
|
||
}
|
||
|
||
// 日历视图 - 每日排课项
|
||
export interface DayScheduleItem {
|
||
id: number;
|
||
className: string;
|
||
coursePackageName: string;
|
||
courseName?: string; // 兼容 coursePackageName 的别名
|
||
lessonType?: string;
|
||
lessonTypeName: string;
|
||
teacherName: string;
|
||
scheduledTime: string;
|
||
status: string;
|
||
}
|
||
|
||
// 日历视图响应
|
||
export interface CalendarViewResponse {
|
||
startDate: string;
|
||
endDate: string;
|
||
schedules: Record<string, DayScheduleItem[]>;
|
||
}
|
||
|
||
// 批量创建排课请求(按班级)
|
||
export interface CreateSchedulesByClassesDto {
|
||
coursePackageId: number;
|
||
courseId: number;
|
||
lessonType: LessonType;
|
||
classIds: number[];
|
||
teacherId: number;
|
||
scheduledDate: string;
|
||
scheduledTime: string;
|
||
repeatType?: 'NONE' | 'WEEKLY' | 'BIWEEKLY';
|
||
repeatEndDate?: string;
|
||
note?: string;
|
||
}
|
||
|
||
export interface SchedulePlan {
|
||
id: number;
|
||
tenantId: number;
|
||
name?: string;
|
||
classId: number;
|
||
className?: string;
|
||
courseId: number;
|
||
courseName?: string;
|
||
coursePackageId?: number;
|
||
coursePackageName?: string;
|
||
lessonType?: LessonType;
|
||
lessonTypeName?: string;
|
||
teacherId?: number;
|
||
teacherName?: string;
|
||
scheduledDate?: string;
|
||
scheduledTime?: string;
|
||
weekDay?: number;
|
||
repeatType: 'NONE' | 'DAILY' | 'WEEKLY' | 'BIWEEKLY';
|
||
repeatEndDate?: string;
|
||
source: 'SCHOOL' | 'TEACHER';
|
||
status: 'ACTIVE' | 'CANCELLED' | 'scheduled' | 'cancelled';
|
||
note?: string;
|
||
createdBy?: number;
|
||
createdAt?: string;
|
||
updatedAt?: string;
|
||
}
|
||
|
||
export interface CreateScheduleDto {
|
||
classId: number;
|
||
courseId: number;
|
||
teacherId?: number;
|
||
scheduledDate?: string;
|
||
scheduledTime?: string;
|
||
weekDay?: number;
|
||
repeatType: 'NONE' | 'DAILY' | 'WEEKLY';
|
||
repeatEndDate?: string;
|
||
note?: string;
|
||
}
|
||
|
||
export interface UpdateScheduleDto {
|
||
teacherId?: number;
|
||
scheduledDate?: string;
|
||
scheduledTime?: string;
|
||
weekDay?: number;
|
||
repeatType?: 'NONE' | 'DAILY' | 'WEEKLY';
|
||
repeatEndDate?: string;
|
||
note?: string;
|
||
status?: string;
|
||
}
|
||
|
||
export interface ScheduleQueryParams {
|
||
classId?: number;
|
||
teacherId?: number;
|
||
courseId?: number;
|
||
startDate?: string;
|
||
endDate?: string;
|
||
status?: string;
|
||
source?: string;
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
}
|
||
|
||
export interface TimetableItem {
|
||
date: string;
|
||
weekDay: number;
|
||
schedules: SchedulePlan[];
|
||
}
|
||
|
||
export interface TimetableQueryParams {
|
||
startDate: string;
|
||
endDate: string;
|
||
classId?: number;
|
||
teacherId?: number;
|
||
}
|
||
|
||
export const getSchedules = (params?: ScheduleQueryParams) =>
|
||
http.get<{ list: SchedulePlan[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/schedules', { params });
|
||
|
||
export const getSchedule = (id: number) =>
|
||
http.get<SchedulePlan>(`/v1/school/schedules/${id}`);
|
||
|
||
export const createSchedule = (data: CreateScheduleDto) =>
|
||
http.post<SchedulePlan>('/v1/school/schedules', data);
|
||
|
||
export const updateSchedule = (id: number, data: UpdateScheduleDto) =>
|
||
http.put<SchedulePlan>(`/v1/school/schedules/${id}`, data);
|
||
|
||
export const cancelSchedule = (id: number) =>
|
||
http.delete<{ message: string }>(`/v1/school/schedules/${id}`);
|
||
|
||
export const getTimetable = (params: TimetableQueryParams) =>
|
||
http.get<{
|
||
byDate: Record<string, SchedulePlan[]>;
|
||
byWeekDay: Record<number, SchedulePlan[]>;
|
||
total: number;
|
||
}>('/v1/school/schedules/timetable', { params });
|
||
|
||
export interface BatchScheduleItem {
|
||
classId: number;
|
||
courseId: number;
|
||
teacherId?: number;
|
||
scheduledDate: string;
|
||
scheduledTime?: string;
|
||
note?: string;
|
||
}
|
||
|
||
export interface BatchCreateResult {
|
||
success: number;
|
||
failed: number;
|
||
results: SchedulePlan[];
|
||
errors: Array<{ index: number; message: string }>;
|
||
}
|
||
|
||
export const batchCreateSchedules = (schedules: BatchScheduleItem[]) =>
|
||
http.post<BatchCreateResult>('/v1/school/schedules/batch', { schedules });
|
||
|
||
// 获取课程包的课程类型列表
|
||
export const getCoursePackageLessonTypes = (coursePackageId: number) =>
|
||
http.get<LessonTypeInfo[]>(`/v1/school/schedules/course-packages/${coursePackageId}/lesson-types`);
|
||
|
||
// 批量创建排课(按班级)
|
||
export const createSchedulesByClasses = (data: CreateSchedulesByClassesDto) =>
|
||
http.post<SchedulePlan[]>('/v1/school/schedules/batch-by-classes', data);
|
||
|
||
// 获取日历视图数据
|
||
export const getCalendarViewData = (params?: {
|
||
startDate?: string;
|
||
endDate?: string;
|
||
classId?: number;
|
||
teacherId?: number;
|
||
}) => http.get<CalendarViewResponse>('/v1/school/schedules/calendar', { params });
|
||
|
||
// ==================== 趋势与分布统计 ====================
|
||
|
||
export interface LessonTrendItem {
|
||
date: string; // 日期(MM-dd 格式)
|
||
lessonCount: number;
|
||
studentCount: number;
|
||
}
|
||
|
||
export interface CourseDistributionItem {
|
||
name: string;
|
||
value: number;
|
||
}
|
||
|
||
// 后端趋势数据响应(对象数组格式)
|
||
export type LessonTrendResponse = LessonTrendItem[];
|
||
|
||
export const getLessonTrend = (startDate?: string, endDate?: string) => {
|
||
const params: Record<string, string> = {};
|
||
if (startDate) params.startDate = startDate;
|
||
if (endDate) params.endDate = endDate;
|
||
return http.get<LessonTrendResponse>('/v1/school/stats/lesson-trend', { params });
|
||
};
|
||
|
||
export const getCourseDistribution = () =>
|
||
http.get<CourseDistributionItem[]>('/v1/school/stats/course-distribution');
|
||
|
||
// ==================== 数据导出 ====================
|
||
|
||
export const exportLessons = (startDate?: string, endDate?: string) => {
|
||
const params = new URLSearchParams();
|
||
if (startDate) params.append('startDate', startDate);
|
||
if (endDate) params.append('endDate', endDate);
|
||
|
||
const token = localStorage.getItem('token');
|
||
const url = `/api/v1/school/export/lessons?${params.toString()}`;
|
||
|
||
return fetch(url, {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
},
|
||
}).then((res) => {
|
||
const contentType = res.headers.get('content-type');
|
||
if (contentType && contentType.includes('application/json')) {
|
||
// 空数据时返回 JSON,解析并抛出错误
|
||
return res.json().then(data => {
|
||
throw new Error(data.message || '暂无数据');
|
||
});
|
||
}
|
||
if (!res.ok) throw new Error('导出失败');
|
||
return res.blob();
|
||
}).then((blob) => {
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `授课记录_${startDate || ''}_${endDate || ''}.xlsx`;
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
});
|
||
};
|
||
|
||
export const exportTeacherStats = (startDate?: string, endDate?: string) => {
|
||
const params = new URLSearchParams();
|
||
if (startDate) params.append('startDate', startDate);
|
||
if (endDate) params.append('endDate', endDate);
|
||
|
||
const token = localStorage.getItem('token');
|
||
const url = `/api/v1/school/export/teacher-stats?${params.toString()}`;
|
||
|
||
return fetch(url, {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
},
|
||
}).then((res) => {
|
||
const contentType = res.headers.get('content-type');
|
||
if (contentType && contentType.includes('application/json')) {
|
||
// 空数据时返回 JSON,解析并抛出错误
|
||
return res.json().then(data => {
|
||
throw new Error(data.message || '暂无数据');
|
||
});
|
||
}
|
||
if (!res.ok) throw new Error('导出失败');
|
||
return res.blob();
|
||
}).then((blob) => {
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `教师绩效统计_${startDate || ''}_${endDate || ''}.xlsx`;
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
});
|
||
};
|
||
|
||
export const exportStudentStats = (classId?: number) => {
|
||
const params = new URLSearchParams();
|
||
if (classId) params.append('classId', String(classId));
|
||
|
||
const token = localStorage.getItem('token');
|
||
const url = `/api/v1/school/export/student-stats?${params.toString()}`;
|
||
|
||
return fetch(url, {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
},
|
||
}).then((res) => {
|
||
const contentType = res.headers.get('content-type');
|
||
if (contentType && contentType.includes('application/json')) {
|
||
// 空数据时返回 JSON,解析并抛出错误
|
||
return res.json().then(data => {
|
||
throw new Error(data.message || '暂无数据');
|
||
});
|
||
}
|
||
if (!res.ok) throw new Error('导出失败');
|
||
return res.blob();
|
||
}).then((blob) => {
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = `学生统计.xlsx`;
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
});
|
||
};
|
||
|
||
// ==================== 排课模板 ====================
|
||
|
||
export interface ScheduleTemplate {
|
||
id: number;
|
||
tenantId: number;
|
||
name: string;
|
||
courseId: number;
|
||
courseName?: string;
|
||
classId?: number;
|
||
className?: string;
|
||
teacherId?: number;
|
||
teacherName?: string;
|
||
scheduledTime?: string;
|
||
weekDay?: number;
|
||
duration: number;
|
||
isDefault: boolean;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
|
||
export interface CreateScheduleTemplateDto {
|
||
name: string;
|
||
courseId: number;
|
||
classId?: number;
|
||
teacherId?: number;
|
||
scheduledTime?: string;
|
||
weekDay?: number;
|
||
duration?: number;
|
||
isDefault?: boolean;
|
||
}
|
||
|
||
export interface UpdateScheduleTemplateDto {
|
||
name?: string;
|
||
classId?: number;
|
||
teacherId?: number;
|
||
scheduledTime?: string;
|
||
weekDay?: number;
|
||
duration?: number;
|
||
isDefault?: boolean;
|
||
}
|
||
|
||
export interface ApplyTemplateDto {
|
||
scheduledDate: string;
|
||
classId?: number;
|
||
teacherId?: number;
|
||
}
|
||
|
||
export const getScheduleTemplates = (params?: { classId?: number; courseId?: number }) =>
|
||
http.get<ScheduleTemplate[]>('/v1/school/schedule-templates', { params });
|
||
|
||
export const getScheduleTemplate = (id: number) =>
|
||
http.get<ScheduleTemplate>(`/v1/school/schedule-templates/${id}`);
|
||
|
||
export const createScheduleTemplate = (data: CreateScheduleTemplateDto) =>
|
||
http.post<ScheduleTemplate>('/v1/school/schedule-templates', data);
|
||
|
||
export const updateScheduleTemplate = (id: number, data: UpdateScheduleTemplateDto) =>
|
||
http.put<ScheduleTemplate>(`/v1/school/schedule-templates/${id}`, data);
|
||
|
||
export const deleteScheduleTemplate = (id: number) =>
|
||
http.delete<{ message: string }>(`/v1/school/schedule-templates/${id}`);
|
||
|
||
export const applyScheduleTemplate = (id: number, data: ApplyTemplateDto) =>
|
||
http.post<SchedulePlan>(`/v1/school/schedule-templates/${id}/apply`, data);
|
||
|
||
// ==================== 操作日志 ====================
|
||
|
||
export interface OperationLog {
|
||
id: number;
|
||
tenantId: number;
|
||
userId: number;
|
||
userType: string;
|
||
action: string;
|
||
module: string;
|
||
description: string;
|
||
targetId: number | null;
|
||
oldValue: string | null;
|
||
newValue: string | null;
|
||
ipAddress: string | null;
|
||
createdAt: string;
|
||
}
|
||
|
||
export interface OperationLogStats {
|
||
total: number;
|
||
modules: { name: string; count: number }[];
|
||
actions: { name: string; count: number }[];
|
||
}
|
||
|
||
export const getOperationLogs = (params?: {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
module?: string;
|
||
action?: string;
|
||
startDate?: string;
|
||
endDate?: string;
|
||
}) => http.get<{ list: any; total: number; pageNum: number; pageSize: number; pages: number }>(
|
||
'/v1/school/operation-logs',
|
||
{ params }
|
||
).then(res => {
|
||
// 字段映射:后端 userRole -> 前端 userType,后端 details -> 前端 description
|
||
return {
|
||
list: (res.list || []).map(log => ({
|
||
...log,
|
||
userType: (log as any).userRole, // 后端字段 userRole 映射为前端 userType
|
||
description: (log as any).details, // 后端字段 details 映射为前端 description
|
||
oldValue: null, // 后端暂未支持
|
||
newValue: null, // 后端暂未支持
|
||
})),
|
||
total: res.total || 0,
|
||
pageNum: res.pageNum || 1,
|
||
pageSize: res.pageSize || 10,
|
||
pages: res.pages || 0,
|
||
};
|
||
});
|
||
|
||
export const getOperationLogStats = (startDate?: string, endDate?: string) =>
|
||
http.get<{ totalLogs: number; byModule: Record<string, number>; byOperator: Record<string, number> }>('/v1/school/operation-logs/stats', {
|
||
params: { startDate, endDate },
|
||
}).then(res => {
|
||
// 字段映射:后端 byModule -> 前端 modules 数组格式
|
||
return {
|
||
total: res.totalLogs || 0,
|
||
modules: Object.entries(res.byModule || {}).map(([name, count]) => ({ name, count })),
|
||
byModule: res.byModule || {},
|
||
byOperator: res.byOperator || {},
|
||
};
|
||
});
|
||
|
||
export const getOperationLogById = (id: number) =>
|
||
http.get<OperationLog>(`/v1/school/operation-logs/${id}`);
|
||
|
||
/**
|
||
* 获取可用模块列表
|
||
* 用于操作日志页面的模块筛选下拉框
|
||
*/
|
||
export const getOperationLogModules = () =>
|
||
http.get<string[]>('/v1/school/operation-logs/modules');
|
||
|
||
// ==================== 任务模板 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 UpdateTaskTemplateDto {
|
||
name?: string;
|
||
description?: string;
|
||
relatedCourseId?: number;
|
||
defaultDuration?: number;
|
||
isDefault?: boolean;
|
||
status?: string;
|
||
}
|
||
|
||
export const getTaskTemplates = (params?: {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
taskType?: string;
|
||
keyword?: string;
|
||
}) => http.get<{ list: TaskTemplate[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/task-templates', { params })
|
||
.then(res => ({
|
||
list: res.list || [],
|
||
total: res.total || 0,
|
||
pageNum: res.pageNum || 1,
|
||
pageSize: res.pageSize || 10,
|
||
pages: res.pages || 0,
|
||
}));
|
||
|
||
export const getTaskTemplate = (id: number) =>
|
||
http.get<TaskTemplate>(`/v1/school/task-templates/${id}`);
|
||
|
||
export const getDefaultTaskTemplate = (taskType: string) =>
|
||
http.get<TaskTemplate | null>(`/v1/school/task-templates/default/${taskType}`);
|
||
|
||
export const createTaskTemplate = (data: CreateTaskTemplateDto) =>
|
||
http.post<TaskTemplate>('/v1/school/task-templates', data);
|
||
|
||
export const updateTaskTemplate = (id: number, data: UpdateTaskTemplateDto) =>
|
||
http.put<TaskTemplate>(`/v1/school/task-templates/${id}`, data);
|
||
|
||
export const deleteTaskTemplate = (id: number) =>
|
||
http.delete<{ message: string }>(`/v1/school/task-templates/${id}`);
|
||
|
||
// ==================== 任务统计 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 = () =>
|
||
Promise.resolve({
|
||
totalTasks: 0,
|
||
publishedTasks: 0,
|
||
completedTasks: 0,
|
||
inProgressTasks: 0,
|
||
pendingCount: 0,
|
||
totalCompletions: 0,
|
||
completionRate: 0,
|
||
});
|
||
|
||
export const getTaskStatsByType = () =>
|
||
Promise.resolve({});
|
||
|
||
export const getTaskStatsByClass = () =>
|
||
Promise.resolve([]);
|
||
|
||
export const getMonthlyTaskStats = (_months?: number) =>
|
||
Promise.resolve([]);
|
||
|
||
// ==================== 任务管理 API ====================
|
||
|
||
// 任务完成状态(新设计)
|
||
export type TaskCompletionStatus = 'PENDING' | 'SUBMITTED' | 'REVIEWED';
|
||
|
||
// 评价结果
|
||
export type FeedbackResult = 'EXCELLENT' | 'PASSED' | 'NEEDS_WORK';
|
||
|
||
// 教师评价
|
||
export interface TaskTeacherFeedback {
|
||
id: number;
|
||
result: FeedbackResult;
|
||
resultText?: string;
|
||
rating?: number;
|
||
comment?: string;
|
||
createdAt: string;
|
||
teacherName?: string;
|
||
}
|
||
|
||
// 任务完成记录(新设计)
|
||
export interface TaskCompletionRecord {
|
||
id: number;
|
||
taskId: number;
|
||
studentId: number;
|
||
status: TaskCompletionStatus;
|
||
statusText?: string;
|
||
photos?: string[];
|
||
videoUrl?: string;
|
||
audioUrl?: string;
|
||
content?: string;
|
||
submittedAt?: string;
|
||
reviewedAt?: string;
|
||
createdAt: string;
|
||
student?: {
|
||
id: number;
|
||
name: string;
|
||
avatar?: string;
|
||
gender?: string;
|
||
classInfo?: {
|
||
id: number;
|
||
name: string;
|
||
grade?: string;
|
||
};
|
||
};
|
||
feedback?: TaskTeacherFeedback;
|
||
}
|
||
|
||
export interface SchoolTask {
|
||
id: number;
|
||
tenantId: number;
|
||
title: string;
|
||
description?: string;
|
||
taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK';
|
||
targetType: 'CLASS' | 'STUDENT';
|
||
relatedCourseId?: number;
|
||
relatedBookName?: string;
|
||
course?: {
|
||
id: number;
|
||
name: string;
|
||
};
|
||
startDate: string;
|
||
endDate: string;
|
||
status: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
|
||
createdBy: number;
|
||
creatorName?: string;
|
||
targetCount?: number;
|
||
completionCount?: number;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
|
||
// 旧接口类型(兼容)
|
||
export interface TaskCompletion {
|
||
id: number;
|
||
taskId: number;
|
||
studentId: number;
|
||
studentName: string;
|
||
className: string;
|
||
status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED';
|
||
completedAt?: string;
|
||
parentFeedback?: string;
|
||
rating?: number;
|
||
}
|
||
|
||
export interface CreateSchoolTaskDto {
|
||
title: string;
|
||
description?: string;
|
||
taskType: 'READING' | 'ACTIVITY' | 'HOMEWORK';
|
||
targetType: 'CLASS' | 'STUDENT';
|
||
targetIds: number[];
|
||
relatedCourseId?: number;
|
||
startDate: string;
|
||
endDate: string;
|
||
}
|
||
|
||
export interface UpdateSchoolTaskDto {
|
||
title?: string;
|
||
description?: string;
|
||
taskType?: 'READING' | 'ACTIVITY' | 'HOMEWORK';
|
||
relatedCourseId?: number;
|
||
startDate?: string;
|
||
endDate?: string;
|
||
status?: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED';
|
||
}
|
||
|
||
// 学校端任务查询参数(多维度筛选)
|
||
export interface SchoolTaskQueryParams {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
keyword?: string;
|
||
type?: string;
|
||
status?: string;
|
||
classIds?: number[];
|
||
teacherIds?: number[];
|
||
dateType?: 'startDate' | 'endDate' | 'deadline';
|
||
startDate?: string;
|
||
endDate?: string;
|
||
completionRate?: 'high' | 'medium' | 'low';
|
||
sortBy?: 'createdAt' | 'startDate' | 'endDate' | 'completionRate';
|
||
sortOrder?: 'asc' | 'desc';
|
||
}
|
||
|
||
// ========== 新 API:只读接口 ==========
|
||
|
||
// 获取学校端任务列表(只读,多维度筛选)
|
||
export const getReadingTaskList = (params?: SchoolTaskQueryParams) =>
|
||
http.get<{ list: SchoolTask[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/reading-tasks', { params })
|
||
.then(res => ({
|
||
items: res.list || [],
|
||
total: res.total || 0,
|
||
pageNum: res.pageNum || 1,
|
||
pageSize: res.pageSize || 10,
|
||
pages: res.pages || 0,
|
||
}));
|
||
|
||
// 获取任务详情(只读)
|
||
export const getReadingTaskDetail = (taskId: number) =>
|
||
http.get<SchoolTask>(`/v1/school/reading-tasks/${taskId}`);
|
||
|
||
// 获取任务完成情况列表
|
||
export const getReadingTaskCompletions = (taskId: number, params?: {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
status?: string;
|
||
}) => http.get<{ list: TaskCompletionRecord[]; total: number; pageNum: number; pageSize: number }>(
|
||
`/v1/school/reading-tasks/${taskId}/completions`,
|
||
{ params }
|
||
).then(res => ({
|
||
items: res.list || [],
|
||
total: res.total || 0,
|
||
pageNum: res.pageNum || 1,
|
||
pageSize: res.pageSize || 10,
|
||
}));
|
||
|
||
// 获取学生提交详情
|
||
export const getCompletionDetail = (completionId: number) =>
|
||
http.get<TaskCompletionRecord>(`/v1/school/reading-tasks/completions/${completionId}`);
|
||
|
||
// ========== 旧 API(保留向后兼容,但不再使用) ==========
|
||
|
||
export const getSchoolTasks = (params?: {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
status?: string;
|
||
taskType?: string;
|
||
keyword?: string;
|
||
}) => http.get<{ list: SchoolTask[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/tasks', { params });
|
||
|
||
export const getSchoolTask = (id: number) =>
|
||
http.get<SchoolTask>(`/v1/school/tasks/${id}`);
|
||
|
||
export const createSchoolTask = (data: CreateSchoolTaskDto) =>
|
||
http.post<SchoolTask>('/v1/school/tasks', data);
|
||
|
||
export const updateSchoolTask = (id: number, data: UpdateSchoolTaskDto) =>
|
||
http.put<SchoolTask>(`/v1/school/tasks/${id}`, data);
|
||
|
||
export const deleteSchoolTask = (id: number) =>
|
||
http.delete<{ message: string }>(`/v1/school/tasks/${id}`);
|
||
|
||
export const getSchoolTaskCompletions = (taskId: number) =>
|
||
http.get<TaskCompletion[]>(`/v1/school/tasks/${taskId}/completions`);
|
||
|
||
export const getSchoolClasses = () =>
|
||
http.get<ClassInfo[]>('/v1/school/classes');
|
||
|
||
// ==================== 数据报告 API ====================
|
||
|
||
export interface ReportOverview {
|
||
reportDate: string;
|
||
totalTeachers: number;
|
||
totalStudents: number;
|
||
totalClasses: number;
|
||
monthlyLessons: number;
|
||
monthlyTasksCompleted: number;
|
||
}
|
||
|
||
export interface TeacherReport {
|
||
teacherId: number;
|
||
teacherName: string;
|
||
lessonCount: number;
|
||
taskCount: number;
|
||
averageRating: number;
|
||
lastLessonTime?: string;
|
||
}
|
||
|
||
export interface CourseReport {
|
||
courseId: number;
|
||
courseName: string;
|
||
lessonCount: number;
|
||
studentCount: number;
|
||
averageRating: number;
|
||
completionRate: number;
|
||
}
|
||
|
||
export interface StudentReport {
|
||
studentId: number;
|
||
studentName: string;
|
||
className: string;
|
||
taskCount: number;
|
||
readingCount: number;
|
||
growthRecordCount: number;
|
||
attendanceRate: number;
|
||
}
|
||
|
||
export const getReportOverview = (startDate?: string, endDate?: string) => {
|
||
const params: Record<string, string> = {};
|
||
if (startDate) params.startDate = startDate;
|
||
if (endDate) params.endDate = endDate;
|
||
return http.get<ReportOverview>('/v1/school/reports/overview', { params });
|
||
};
|
||
|
||
export const getTeacherReports = (startDate?: string, endDate?: string, limit: number = 50) => {
|
||
const params: Record<string, string | number> = { limit };
|
||
if (startDate) params.startDate = startDate;
|
||
if (endDate) params.endDate = endDate;
|
||
return http.get<TeacherReport[]>('/v1/school/reports/teachers', { params });
|
||
};
|
||
|
||
export const getCourseReports = (startDate?: string, endDate?: string, limit: number = 50) => {
|
||
const params: Record<string, string | number> = { limit };
|
||
if (startDate) params.startDate = startDate;
|
||
if (endDate) params.endDate = endDate;
|
||
return http.get<CourseReport[]>('/v1/school/reports/courses', { params });
|
||
};
|
||
|
||
export const getStudentReports = (startDate?: string, endDate?: string, limit: number = 50) => {
|
||
const params: Record<string, string | number> = { limit };
|
||
if (startDate) params.startDate = startDate;
|
||
if (endDate) params.endDate = endDate;
|
||
return http.get<StudentReport[]>('/v1/school/reports/students', { params });
|
||
};
|
||
|
||
// ==================== 数据报告 - 图表 API ====================
|
||
|
||
export interface TeacherActivityItem {
|
||
teacherId: number;
|
||
teacherName: string;
|
||
classNames: string;
|
||
lessonCount: number;
|
||
courseCount: number;
|
||
lastActiveAt?: string;
|
||
activityLevelCode: string;
|
||
activityLevelDesc: string;
|
||
}
|
||
|
||
/**
|
||
* 获取活跃教师排行
|
||
*/
|
||
export const getTeacherStats = (startDate?: string, endDate?: string, limit: number = 10) => {
|
||
const params: Record<string, string | number> = { limit };
|
||
if (startDate) params.startDate = startDate;
|
||
if (endDate) params.endDate = endDate;
|
||
return http.get<TeacherActivityItem[]>('/v1/school/stats/teachers', { params });
|
||
};
|
||
|
||
// ==================== 家长管理 ====================
|
||
|
||
export interface ParentQueryParams {
|
||
pageNum?: number;
|
||
pageSize?: number;
|
||
keyword?: string;
|
||
status?: string;
|
||
}
|
||
|
||
export interface ParentChild {
|
||
id: number;
|
||
name: string;
|
||
relationship: string;
|
||
class?: {
|
||
id: number;
|
||
name: string;
|
||
};
|
||
}
|
||
|
||
export interface Parent {
|
||
id: number;
|
||
name: string;
|
||
phone: string;
|
||
email?: string;
|
||
loginAccount: string;
|
||
status: string;
|
||
tenantId: number;
|
||
childrenCount: number;
|
||
children?: ParentChild[];
|
||
createdAt: string;
|
||
}
|
||
|
||
export interface CreateParentDto {
|
||
name: string;
|
||
phone: string;
|
||
email?: string;
|
||
loginAccount: string;
|
||
password?: string;
|
||
}
|
||
|
||
export interface UpdateParentDto {
|
||
name?: string;
|
||
phone?: string;
|
||
email?: string;
|
||
}
|
||
|
||
export interface AddChildDto {
|
||
studentId: number;
|
||
relationship: string;
|
||
}
|
||
|
||
export const getParents = (params?: ParentQueryParams) =>
|
||
http.get<{ list: Parent[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/school/parents', { params });
|
||
|
||
export const getParent = (id: number) =>
|
||
http.get<Parent>(`/v1/school/parents/${id}`);
|
||
|
||
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);
|
||
|
||
export const deleteParent = (id: number) =>
|
||
http.delete<{ message: string }>(`/v1/school/parents/${id}`);
|
||
|
||
export const resetParentPassword = (id: number) =>
|
||
http.post<{ tempPassword: string }>(`/v1/school/parents/${id}/reset-password`);
|
||
|
||
export const getParentChildren = async (parentId: number): Promise<ParentChild[]> => {
|
||
const list = await http.get<ParentChild[]>(`/v1/school/parents/${parentId}/children`);
|
||
return Array.isArray(list) ? list : [];
|
||
};
|
||
|
||
export const addChildToParent = (parentId: number, data: AddChildDto) =>
|
||
http.post<ParentChild>(`/v1/school/parents/${parentId}/children/${data.studentId}`, { relationship: data.relationship });
|
||
|
||
export const removeChildFromParent = (parentId: number, studentId: number) =>
|
||
http.delete<{ message: string }>(`/v1/school/parents/${parentId}/children/${studentId}`);
|