feat(homeworks,island,classisland,desktop,web,server): 作业按天查看与教室机器录入 + 提醒上课时段暂存规则 + 灵动岛边距/跟随鼠标屏幕

- 作业:新增 Homework.assignDate(本地日期 YYYY-MM-DD;UTC 切片在晚上会把今天的作业算成明天),列表按天过滤与排序,新增 GET /homeworks?date= 与 GET /homeworks/days(日期高亮)供客户端与 Web 共用;客户端作业页改为"看作业 + 录作业"(不再显示完成状态),支持快捷短语(存本机配置,「设置 → 作业录入」可增删/恢复默认)
- 教室机器录入:POST /homeworks 去掉 requireRole,权限下沉到 homeworks.service(staff 或本班班级账号);班级账号的 sub 是班级 id,写 createdBy 前换成该班班主任,避免撞 User 外键
- 提醒类型:ClassIslandPush.kind(notification / call)。通知类上课时段暂存、下课补弹,叫人/紧急立即弹;客户端灵动岛与 ClassIsland 插件共用同一套规则
- 灵动岛:新增 marginX / marginY(默认仍 8px,不动老外观)与「跟随鼠标屏幕」;文案统一成"灵动岛 / ClassIsland"
- 设置页:新增「ClassIsland 联动」(联动状态 + 通知模式)与「作业录入」分区
- 校验:verify:e2e 增补 assignDate 默认当天 / ?date= 过滤 / days / 非法日期 422 / 班级账号录入 201 与跨班 403;verify:classisland 与插件静态契约增补上课时段暂存断言(57/57)
- 文档:README 与 AGENTS.md 同步(作业按天、上课时段策略、两个方向的开关表、新增踩坑)

门禁:pnpm lint 0 error、pnpm typecheck 四包通过、verify:classisland-plugin 57/57 通过。
未跑:verify:e2e / verify:classisland(需后端启动)、verify:web / verify:desktop(需先构建产物)。
This commit is contained in:
LoveChengke committed 2026-09-27 16:34:01 +08:00
1 parent d0830f6830
commit 7536abfc95
33 files changed
+1236 -162

No files matched your search

@@ -3,6 +3,8 @@ import {
type ClassDetailDto,
type ClassDto,
type ClassIslandNotificationChannel,
type CourseDto,
type HomeworkDaysDto,
type DashboardSummary,
type GradeDto,
type HomeworkDto,
@@ -49,6 +51,20 @@ export const classChannelApi = {
api.patch(`${API_PATHS.classes}/${classId}/notification-channel`, { notificationChannel }),
};
/** 本班 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`),
};
export const scheduleApi = {
list: (params: { classId?: string; week?: number }): Promise<ScheduleDto[]> =>
api.get(API_PATHS.schedules, params),
@@ -56,9 +72,30 @@ export const scheduleApi = {
api.get(`${API_PATHS.schedules}/grid`, params),
};
export const courseApi = {
list: (classId?: string): Promise<CourseDto[]> =>
api.get(API_PATHS.courses, classId ? { classId } : undefined),
};
export const homeworkApi = {
list: (params?: { classId?: string; pendingOnly?: boolean; courseId?: string }): Promise<HomeworkDto[]> =>
api.get(API_PATHS.homeworks, params),
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),
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 }),