kindergarten_java/reading-platform-frontend/src/api/school.ts
En 8aaa8cdd94 feat: 操作日志模块同步方案
后端新增:
- 新增 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>
2026-03-23 19:57:40 +08:00

1408 lines
39 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';
// ==================== 类型定义 ====================
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}`);