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

+34 -2
View File
@@ -2209,6 +2209,38 @@ async function runIslandChecks(
);
}
record('个性设置:停靠位置生效(6 个锚点)', positionOk, positionDetails.join(' '));
// 11.3b) 边距:左右/上下边距必须真的把岛挪走(用户反馈「调边距没反应」:
// 滑块在渲染层、定位在主进程,主进程没重跑就会出现「数字在变、岛不动」)
island.setAppearance({ ...appearanceBefore, position: 'top-left', marginX: 48, marginY: 40 });
await sleep(450);
const marginLeft = (await readIslandGeometry())?.island ?? null;
const marginLeftOk =
Boolean(marginLeft) &&
Math.abs((marginLeft as Electron.Rectangle).x - (workArea.x + 48)) <= 1 &&
Math.abs((marginLeft as Electron.Rectangle).y - (workArea.y + 40)) <= 1;
island.setAppearance({ ...appearanceBefore, position: 'top-right', marginX: 48, marginY: 8 });
await sleep(450);
const marginRight = (await readIslandGeometry())?.island ?? null;
const marginRightOk =
Boolean(marginRight) &&
Math.abs(
(marginRight as Electron.Rectangle).x +
(marginRight as Electron.Rectangle).width -
(workArea.x + workArea.width - 48),
) <= 1;
record(
'个性设置:边距生效(左右 + 上下)',
marginLeftOk && marginRightOk,
'top-left(marginX=48,marginY=40)=' +
(marginLeft ? marginLeft.x + ',' + marginLeft.y : '-') +
(marginLeftOk ? '✓' : '✗') +
' top-right(marginX=48)=' +
(marginRight ? marginRight.x + ',' + marginRight.y : '-') +
(marginRightOk ? '✓' : '✗'),
);
island.setAppearance(appearanceBefore);
// 11.4 置顶开关 → 窗口 alwaysOnTop
@@ -2557,10 +2589,10 @@ async function runSettingsPageChannelCheck(
for (let i = 0; i < 40 && !group; i += 1) {
await wait(100);
group = Array.from(document.querySelectorAll('.el-card')).find((card) =>
(card.querySelector('.el-card__header')?.textContent ?? '').includes('通知显示位置'),
(card.querySelector('.el-card__header')?.textContent ?? '').includes('ClassIsland 联动'),
);
}
if (!group) return { ok: false, detail: '设置页没有「通知显示位置」卡片' };
if (!group) return { ok: false, detail: '设置页没有「ClassIsland 联动」卡片' };
const radios = Array.from(group.querySelectorAll('.el-radio'));
// 注意:这段脚本本身是外层模板字符串,里面不能再出现反引号(会截断外层字符串)
if (radios.length < 3) return { ok: false, detail: '单选项只有 ' + radios.length + ' 个' };