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:
1 parent
d0830f6830
commit
7536abfc95
33 files changed
+1236
-162
No files matched your search
@@ -304,6 +304,12 @@ async function main() {
|
||||
published.status === 201 && Boolean(publishPush),
|
||||
`status=${published.status} 待提醒=${(pendingAfterPublish.payload?.data?.notifications ?? []).length}`,
|
||||
);
|
||||
// kind 决定插件"上课时段弹不弹":通知类会被暂存,叫人属于主动通知要立刻弹
|
||||
record(
|
||||
'通知类推送的 kind=notification(上课时段插件会暂存)',
|
||||
publishPush?.kind === 'notification',
|
||||
`kind=${publishPush?.kind}`,
|
||||
);
|
||||
if (publishPush) {
|
||||
await api('/integrations/classisland/ack', {
|
||||
method: 'POST',
|
||||
@@ -326,6 +332,11 @@ async function main() {
|
||||
Boolean(firstStudent) && call.status === 201 && Boolean(callPush),
|
||||
firstStudent ? `status=${call.status}` : '班级里没有学生,跳过',
|
||||
);
|
||||
record(
|
||||
'叫人推送的 kind=call(算主动通知,上课时段也立刻弹)',
|
||||
callPush?.kind === 'call',
|
||||
`kind=${callPush?.kind}`,
|
||||
);
|
||||
if (callPush) {
|
||||
await api('/integrations/classisland/ack', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -455,6 +455,36 @@ async function main() {
|
||||
createdHomework.payload?.data?.dueAt === undefined,
|
||||
`dueAt=${String(createdHomework.payload?.data?.dueAt)}`,
|
||||
);
|
||||
// 按天查看:作业带"所属日期"(本地日期),并且能按它过滤
|
||||
const assignDate = createdHomework.payload?.data?.assignDate;
|
||||
record(
|
||||
'作业带所属日期(assignDate,默认服务器当天)',
|
||||
typeof assignDate === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(assignDate),
|
||||
`assignDate=${assignDate}`,
|
||||
);
|
||||
const byDay = await api(`/homeworks?classId=${classId}&date=${assignDate}`, { token: teacherToken });
|
||||
record(
|
||||
'按天查看(?date=)只返回该天的作业',
|
||||
byDay.status === 200 &&
|
||||
(byDay.payload?.data ?? []).length > 0 &&
|
||||
(byDay.payload?.data ?? []).every((item) => item.assignDate === assignDate),
|
||||
`当天条数=${(byDay.payload?.data ?? []).length}`,
|
||||
);
|
||||
const otherDay = await api(`/homeworks?classId=${classId}&date=2000-01-01`, { token: teacherToken });
|
||||
record(
|
||||
'没有作业的日期返回空列表',
|
||||
otherDay.status === 200 && (otherDay.payload?.data ?? []).length === 0,
|
||||
`条数=${(otherDay.payload?.data ?? []).length}`,
|
||||
);
|
||||
const dayMarks = await api(`/homeworks/days?classId=${classId}`, { token: teacherToken });
|
||||
const marks = dayMarks.payload?.data?.days ?? [];
|
||||
record(
|
||||
'作业日历返回有作业的日期(供日期选择器高亮)',
|
||||
dayMarks.status === 200 && marks.some((item) => item.date === assignDate && item.count >= 1),
|
||||
`日期数=${marks.length} 命中=${marks.find((item) => item.date === assignDate)?.count ?? 0}`,
|
||||
);
|
||||
const badDay = await api(`/homeworks/days?classId=${classId}&from=2026-13-45`, { token: teacherToken });
|
||||
record('作业日历非法日期被拒(422)', badDay.status === 422, `status=${badDay.status}`);
|
||||
|
||||
try {
|
||||
const { payload, elapsed } = await homeworkWait;
|
||||
@@ -1867,6 +1897,46 @@ async function main() {
|
||||
`status=${classScheduleWrite.status}`,
|
||||
);
|
||||
|
||||
// 教室机器录入作业(需求:支持作业在客户端录入):班级账号可以发,跨班不行
|
||||
const classHomeworkCreate = await api('/homeworks', {
|
||||
method: 'POST',
|
||||
token: classToken,
|
||||
body: {
|
||||
classId,
|
||||
title: '教室录入验证作业',
|
||||
content: 'P 大本 第 3 课',
|
||||
assignDate: '2026-03-02',
|
||||
},
|
||||
});
|
||||
record(
|
||||
'班级账号可在教室机器上录入作业',
|
||||
classHomeworkCreate.status === 201 && classHomeworkCreate.payload?.data?.assignDate === '2026-03-02',
|
||||
`status=${classHomeworkCreate.status} assignDate=${classHomeworkCreate.payload?.data?.assignDate}`,
|
||||
);
|
||||
if (foreignClass) {
|
||||
const classHomeworkCreateCross = await api('/homeworks', {
|
||||
method: 'POST',
|
||||
token: classToken,
|
||||
body: { classId: foreignClass.id, title: '跨班录入作业', content: '不应成功' },
|
||||
});
|
||||
record(
|
||||
'班级账号跨班录入作业被拒绝(403)',
|
||||
classHomeworkCreateCross.status === 403,
|
||||
`status=${classHomeworkCreateCross.status}`,
|
||||
);
|
||||
}
|
||||
if (classHomeworkCreate.payload?.data?.id) {
|
||||
const cleanupHomework = await api(`/homeworks/${classHomeworkCreate.payload.data.id}`, {
|
||||
method: 'DELETE',
|
||||
token: teacherToken,
|
||||
});
|
||||
record(
|
||||
'清理:教室录入的验证作业已删除',
|
||||
cleanupHomework.status === 200,
|
||||
`status=${cleanupHomework.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 班级设备代全班操作:通知已读
|
||||
const classNameNotice = await api('/notifications', {
|
||||
method: 'POST',
|
||||
|
||||
Reference in new issue
Block a user