2026-09-11 22:46:31 +08:00
|
|
|
|
import {
|
|
|
|
|
|
API_PATHS,
|
|
|
|
|
|
type ClassDetailDto,
|
|
|
|
|
|
type ClassDto,
|
2026-09-27 14:59:12 +08:00
|
|
|
|
type ClassIslandNotificationChannel,
|
2026-09-27 16:34:01 +08:00
|
|
|
|
type CourseDto,
|
2026-10-11 16:21:38 +08:00
|
|
|
|
type ExamDto,
|
|
|
|
|
|
type ExamRankingDto,
|
2026-09-27 16:34:01 +08:00
|
|
|
|
type HomeworkDaysDto,
|
2026-09-11 22:46:31 +08:00
|
|
|
|
type DashboardSummary,
|
|
|
|
|
|
type GradeDto,
|
|
|
|
|
|
type HomeworkDto,
|
|
|
|
|
|
type HomeworkStatusDto,
|
2026-09-12 23:48:59 +08:00
|
|
|
|
type HomeworkSubmissionsDto,
|
2026-09-11 22:46:31 +08:00
|
|
|
|
type LoginRequest,
|
|
|
|
|
|
type LoginResponse,
|
|
|
|
|
|
type NotificationDto,
|
|
|
|
|
|
type ScheduleDto,
|
|
|
|
|
|
type ScheduleWeekView,
|
2026-10-06 19:41:18 +08:00
|
|
|
|
type SessionUser,
|
|
|
|
|
|
type StudentGradeDetailDto,
|
2026-09-11 22:46:31 +08:00
|
|
|
|
} from '@classhelper/shared';
|
|
|
|
|
|
import { api } from './http.js';
|
|
|
|
|
|
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/** ClassHelper 班级端只需要这一批接口(与后端同一套 REST 契约) */
|
2026-09-11 22:46:31 +08:00
|
|
|
|
export const authApi = {
|
2026-10-02 00:25:10 +08:00
|
|
|
|
/** 账号登录(教师/管理员用;客户端界面走班级登录,学生个人账号已清理不能登录) */
|
2026-09-11 22:46:31 +08:00
|
|
|
|
login: (payload: LoginRequest): Promise<LoginResponse> => api.post(API_PATHS.auth.login, payload),
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/** 班级账号登录(ClassHelper 班级端主入口):班级码 + 班级密码 */
|
2026-09-12 15:51:08 +08:00
|
|
|
|
classLogin: (payload: { code: string; password: string }): Promise<LoginResponse> =>
|
|
|
|
|
|
api.post('/auth/class-login', payload),
|
2026-09-11 22:46:31 +08:00
|
|
|
|
logout: (): Promise<{ loggedOut: boolean }> => api.post(API_PATHS.auth.logout, {}),
|
2026-10-06 19:41:18 +08:00
|
|
|
|
me: (): Promise<SessionUser> => api.get(API_PATHS.auth.me),
|
2026-10-02 00:25:10 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 修改密码(设置页「账号信息」里的入口)。
|
|
|
|
|
|
* 服务端同一个接口按会话类型分流:班级账号改的是**班级密码**,教师/管理员改的是本人登录密码 ——
|
|
|
|
|
|
* 因此这台机器无论以哪种身份登录,改密入口都走这一条。
|
|
|
|
|
|
*/
|
|
|
|
|
|
changePassword: (payload: {
|
|
|
|
|
|
currentPassword: string;
|
|
|
|
|
|
newPassword: string;
|
|
|
|
|
|
}): Promise<{ changed: boolean }> => api.patch(API_PATHS.auth.changePassword, payload),
|
2026-09-11 22:46:31 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const classApi = {
|
|
|
|
|
|
list: (): Promise<ClassDto[]> => api.get(API_PATHS.classes),
|
|
|
|
|
|
detail: (id: string): Promise<ClassDetailDto> => api.get(`${API_PATHS.classes}/${id}`),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-27 14:59:12 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 通知显示位置(both / client / classisland)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 由**这台教室机器自己**决定"提醒弹在 ClassHelper 还是 ClassIsland":
|
|
|
|
|
|
* 本地存一份用于本机的弹窗/上岛判断,同时写回班级记录,服务端据此决定要不要推 ClassIsland。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const classChannelApi = {
|
|
|
|
|
|
get: (classId: string): Promise<{ notificationChannel: ClassIslandNotificationChannel }> =>
|
|
|
|
|
|
api.get(`${API_PATHS.classes}/${classId}/notification-channel`),
|
|
|
|
|
|
set: (
|
|
|
|
|
|
classId: string,
|
|
|
|
|
|
notificationChannel: ClassIslandNotificationChannel,
|
|
|
|
|
|
): Promise<{ notificationChannel: ClassIslandNotificationChannel }> =>
|
|
|
|
|
|
api.patch(`${API_PATHS.classes}/${classId}/notification-channel`, { notificationChannel }),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-27 16:34:01 +08:00
|
|
|
|
/** 本班 ClassIsland 联动状态(教室客户端「设置 → ClassIsland 联动」展示) */
|
|
|
|
|
|
export const classIslandStatusApi = {
|
|
|
|
|
|
get: (
|
|
|
|
|
|
classId: string,
|
|
|
|
|
|
): Promise<{
|
|
|
|
|
|
connected: boolean;
|
|
|
|
|
|
deviceName: string | null;
|
|
|
|
|
|
deviceCount: number;
|
|
|
|
|
|
lastSeenAt: string | null;
|
|
|
|
|
|
pluginVersion: string | null;
|
|
|
|
|
|
classIslandVersion: string | null;
|
|
|
|
|
|
}> => api.get(`${API_PATHS.classes}/${classId}/classisland-status`),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-11 22:46:31 +08:00
|
|
|
|
export const scheduleApi = {
|
|
|
|
|
|
list: (params: { classId?: string; week?: number }): Promise<ScheduleDto[]> =>
|
|
|
|
|
|
api.get(API_PATHS.schedules, params),
|
|
|
|
|
|
grid: (params: { classId?: string; week?: number }): Promise<ScheduleWeekView> =>
|
|
|
|
|
|
api.get(`${API_PATHS.schedules}/grid`, params),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-27 16:34:01 +08:00
|
|
|
|
export const courseApi = {
|
|
|
|
|
|
list: (classId?: string): Promise<CourseDto[]> =>
|
|
|
|
|
|
api.get(API_PATHS.courses, classId ? { classId } : undefined),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-11 22:46:31 +08:00
|
|
|
|
export const homeworkApi = {
|
2026-09-27 16:34:01 +08:00
|
|
|
|
list: (params?: {
|
|
|
|
|
|
classId?: string;
|
|
|
|
|
|
pendingOnly?: boolean;
|
|
|
|
|
|
courseId?: string;
|
|
|
|
|
|
/** 只看某一天(YYYY-MM-DD,按作业所属日期) */
|
|
|
|
|
|
date?: string;
|
|
|
|
|
|
}): Promise<HomeworkDto[]> => api.get(API_PATHS.homeworks, params),
|
|
|
|
|
|
/** 哪些天有作业(日期选择器高亮) */
|
|
|
|
|
|
days: (params: { classId?: string; from?: string; to?: string; days?: number }): Promise<HomeworkDaysDto> =>
|
|
|
|
|
|
api.get(`${API_PATHS.homeworks}/days`, params),
|
|
|
|
|
|
/** 在教室机器上直接录入作业(班级账号可调用;也可选所属日期) */
|
|
|
|
|
|
create: (payload: {
|
|
|
|
|
|
classId: string;
|
|
|
|
|
|
courseId?: string | null;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
content: string;
|
|
|
|
|
|
assignDate?: string | null;
|
|
|
|
|
|
}): Promise<HomeworkDto> => api.post(API_PATHS.homeworks, payload),
|
2026-09-11 22:46:31 +08:00
|
|
|
|
detail: (id: string): Promise<HomeworkDto> => api.get(`${API_PATHS.homeworks}/${id}`),
|
|
|
|
|
|
updateStatus: (id: string, completed: boolean): Promise<HomeworkStatusDto> =>
|
|
|
|
|
|
api.patch(`${API_PATHS.homeworks}/${id}/status`, { completed }),
|
2026-09-12 23:48:59 +08:00
|
|
|
|
/** 提交名单 / 未交名单(教师与班级设备可用) */
|
|
|
|
|
|
submissions: (id: string): Promise<HomeworkSubmissionsDto> =>
|
|
|
|
|
|
api.get(`${API_PATHS.homeworks}/${id}/submissions`),
|
|
|
|
|
|
saveSubmissions: (id: string, notSubmittedUserIds: string[]): Promise<HomeworkSubmissionsDto> =>
|
|
|
|
|
|
api.patch(`${API_PATHS.homeworks}/${id}/submissions`, { notSubmittedUserIds }),
|
2026-09-11 22:46:31 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const notificationApi = {
|
|
|
|
|
|
list: (params?: { classId?: string; unreadOnly?: boolean }): Promise<NotificationDto[]> =>
|
|
|
|
|
|
api.get(API_PATHS.notifications, params),
|
|
|
|
|
|
unreadCount: (): Promise<{ count: number }> => api.get(`${API_PATHS.notifications}/unread-count`),
|
2026-10-02 00:25:10 +08:00
|
|
|
|
// id 来自服务端或灵动岛(作业卡是本地合成的 `homework-…`),一律 encodeURIComponent:
|
|
|
|
|
|
// 否则含 `/` 或 `..` 的 id 会改变实际请求的路径
|
2026-09-11 22:46:31 +08:00
|
|
|
|
markRead: (id: string): Promise<{ notificationId: string; readAt: string }> =>
|
2026-10-02 00:25:10 +08:00
|
|
|
|
api.post(`${API_PATHS.notifications}/${encodeURIComponent(id)}/read`, {}),
|
2026-09-11 22:46:31 +08:00
|
|
|
|
markAllRead: (): Promise<{ marked: number }> => api.post(`${API_PATHS.notifications}/read-all`, {}),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const gradeApi = {
|
|
|
|
|
|
my: (): Promise<GradeDto[]> => api.get(`${API_PATHS.grades}/my`),
|
2026-10-06 19:41:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 按**学号**查某个学生的成绩明细。
|
|
|
|
|
|
* 学生没有账号,因此这是教室机器代学生查本人的通道;是否开放由班级上的
|
|
|
|
|
|
* `studentGradeQueryEnabled` 开关决定(关闭时服务端返回 403 并给出原因)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
studentDetail: (studentNo: string): Promise<StudentGradeDetailDto> =>
|
|
|
|
|
|
api.get(`${API_PATHS.grades}/student/${encodeURIComponent(studentNo)}`),
|
2026-10-11 16:21:38 +08:00
|
|
|
|
/** 本班出现过的考试(含"这次考试算不算总分") */
|
|
|
|
|
|
exams: (classId: string): Promise<ExamDto[]> => api.get(`${API_PATHS.grades}/exams`, { classId }),
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 一次考试的单科榜与总分榜。
|
|
|
|
|
|
* 总分榜只在老师把这次考试设为"所有科目相加排名"时才有内容;
|
|
|
|
|
|
* 整条通道与按学号查明细共用同一个按班开关(关闭时服务端 403)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
ranking: (classId: string, examName: string): Promise<ExamRankingDto> =>
|
|
|
|
|
|
api.get(`${API_PATHS.grades}/ranking`, { classId, examName }),
|
2026-10-06 19:41:18 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* ClassHelper 班级端设置:「是否允许按学号查询本班学生成绩明细」。
|
|
|
|
|
|
* 与通知显示位置同一套模式 —— 由教室机器自己决定,教师/管理员在 Web 端也能改。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const classGradeQueryApi = {
|
|
|
|
|
|
get: (classId: string): Promise<{ studentGradeQueryEnabled: boolean }> =>
|
|
|
|
|
|
api.get(`${API_PATHS.classes}/${classId}/student-grade-query`),
|
|
|
|
|
|
set: (classId: string, enabled: boolean): Promise<{ studentGradeQueryEnabled: boolean }> =>
|
|
|
|
|
|
api.patch(`${API_PATHS.classes}/${classId}/student-grade-query`, { enabled }),
|
2026-09-11 22:46:31 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const dashboardApi = {
|
|
|
|
|
|
summary: (): Promise<DashboardSummary> => api.get(API_PATHS.dashboard),
|
2026-09-13 08:39:11 +08:00
|
|
|
|
term: (classId?: string): Promise<{ currentWeek: number; maxWeek: number }> =>
|
|
|
|
|
|
api.get('/dashboard/term', classId ? { classId } : undefined),
|
2026-09-11 22:46:31 +08:00
|
|
|
|
};
|