feat(island,homework,schedule): 灵动岛点开修复+胶囊类型汇总 · 作业看板与未交名单 · 单双周课表与 ClassIsland 课程表导入
一、灵动岛(用户反馈的三处"点不开"全部修复)
- 非紧急叫人:上课时段仍以胶囊保持可见、可点开(不再只进队列导致屏幕上什么都没有);
新增 isOpenable():紧急消息 + 任何"叫人"都允许点开,但普通叫人不会自动展开打断课堂。
- 紧急叫人收起后再点:命中改为"主进程按光标位置兜底校正"(上一轮的 island:set-hit-rect 轮询),
本轮补齐叫人类专项回归(展开 → 收起 → 再次点开)。
- 设置页预览:新增 preview 通道 —— 示例岛直接展开、失焦不收起、30 秒自行消失、关掉不残留。
- 收起态胶囊汇总消息类型:多条时显示「新消息:叫人/作业/通知(共 3 条)」+「点击查看」+ 类型小标签。
- 顺带修掉两个真问题:胶囊态收到新消息会把上一条静默顶掉(现在一律回队);
syncWindow 的上课时段守卫只认"紧急"导致普通叫人被藏掉。
- 性能:岛体矩形上报限流(~10 次/秒 + 尾随补发),修掉每帧 IPC 导致的开合采样骤降。
二、作业(学生端 + Web 教师端)
- 新增**看板模式**:按科目分卡片、点卡片放大全屏、工具栏可切「看板/列表」、
开关「显示时间」、拖「看板字号」(11–28px),偏好持久化到客户端配置 homeworkBoard。
- 去掉「全班标记完成」,改为**未交名单**:勾选没交的同学,其余自动标记已交;
新增 GET/PATCH /api/homeworks/:id/submissions(教师与班级设备可用,普通学生 403)。
三、课表单双周 + ClassIsland 课程表导入
- Schedule 新增 weekParity(ALL/ODD/EVEN)+ 迁移;周视图与上课判定按「第 1 周=单周」过滤,
历史数据默认 ALL 行为不变;Web 手动新增/编辑课表可选单双周,学生端课表打「单周/双周」标签。
- 修复既有 P0 bug:ClassIsland 真实档案里 TimeLayouts 是 "Guid → { Name, Layouts }" 字典,
旧解析器只认数组/.items,导致**真实导出完全导不进来**;现已按真实形态解析(并兼容旧写法),
同时补上 TimeType=3(行动)映射。
- 新增 ClassIsland 课程表(ClassPlan)导入:解析 TimeRule.WeekCountDiv/WeekCountDivTotal → 单双周,
Classes[i] 对齐第 i 个 TimeType=0 的时间点,Subjects 映射科目(缺失自动补建课程),
星期 0=周日 换算为 1=周一 … 7=周日;replace/merge 两种模式,预览只解析不写库。
- 新增维护脚本 packages/server/scripts/apply-parity-column.cjs(手工覆盖 dist 部署时补列用)。
验证:verify:e2e 148/148(新增单双周 12 项 + 课程表导入 5 项)、verify:desktop 71/71(复跑 65/65)、
verify:web 20/20、typecheck / lint / format 全绿;服务端与 Web 已重新打包并部署到本机安装目录。
This commit is contained in:
1 parent
86430c5f4a
commit
b8ef5d8dbb
34 files changed
+2351
-86
No files matched your search
@@ -20,7 +20,7 @@
|
||||
| 1 | pnpm monorepo 脚手架、TypeScript / ESLint / Prettier / 环境变量 | ✅ 已完成 |
|
||||
| 2 | 后端:Prisma schema + 迁移 + 种子数据 + JWT 认证 + RBAC + 模块化 REST API + Socket.IO | ✅ 已完成 |
|
||||
| 3 | Web 管理端:登录、主布局、仪表盘、班级/学生/课表/作业/通知/成绩页面、Axios 封装、实时提示 | ✅ 已完成 |
|
||||
| 4 | EXE 客户端:Electron 主进程/preload/渲染进程、登录、课表/作业/通知/成绩/设置、实时推送、IndexedDB 离线缓存 | ✅ 已完成(冒烟验证 65/65) |
|
||||
| 4 | EXE 客户端:Electron 主进程/preload/渲染进程、登录、课表/作业/通知/成绩/设置、实时推送、IndexedDB 离线缓存 | ✅ 已完成(冒烟验证 71/71) |
|
||||
| 5 | 三端联调脚本、打包命令、完整 README | ✅ 已完成(三套安装包 + 两套 UI 回归测试) |
|
||||
| 6 | 测试账号与种子数据说明 | ✅ 已完成(见下文) |
|
||||
| 7 | 生产化:服务端安装程序(内置 Node)、Web 端 PWA 可安装、Docker + Nginx 部署、生产加固与运维文档 | ✅ 已完成(见 [`docs/production.md`](docs/production.md)) |
|
||||
@@ -75,7 +75,7 @@ class-helper/
|
||||
│ ├── prisma/migrations/ # 迁移历史(已生成并应用)
|
||||
│ ├── prisma/seed.ts # 种子数据
|
||||
│ ├── prisma.config.ts # Prisma 7 配置(迁移/种子/连接串)
|
||||
│ ├── scripts/verify-e2e.mjs # 后端端到端验收脚本(136 项)
|
||||
│ ├── scripts/verify-e2e.mjs # 后端端到端验收脚本(148 项)
|
||||
│ └── src/
|
||||
│ ├── app.ts # Express 装配(静态托管 + 探针 + 限流 + 模块挂载)
|
||||
│ ├── index.ts # 启动入口(自检/初始化 + HTTP + Socket.IO + 优雅退出)
|
||||
@@ -171,15 +171,15 @@ pnpm verify:desktop
|
||||
|
||||
```bash
|
||||
# 另开一个终端先启动后端:pnpm dev:server
|
||||
pnpm verify:e2e # 后端 + REST + Socket.IO + RBAC + 上课时段拦截 + 紧急/普通叫人 + 导入 + 班级账号:136 项
|
||||
pnpm verify:e2e # 后端 + REST + Socket.IO + RBAC + 上课时段拦截 + 紧急/普通叫人 + 单双周课表/ClassIsland 课程表导入 + 导入 + 班级账号:148 项
|
||||
pnpm verify:web # Web 管理端真实点击(含权限入口隐藏、手机适配、叫人入口 + 紧急/普通级别、紧急通知 + 成绩/时间配置导入弹窗):20 项
|
||||
pnpm verify:desktop # Electron 客户端(含灵动岛收起常驻/命中兜底/展开收起再展开、圆角四角一致、开合不震动、紧急与普通叫人、真实链路、课表时间轴、个性化 10 项参数与托盘/退出无残留):65 项
|
||||
pnpm verify:desktop # Electron 客户端(含灵动岛收起常驻/命中兜底/展开收起再展开/胶囊类型汇总、圆角四角一致、开合不震动、紧急与普通叫人、真实链路、课表时间轴、个性化 10 项参数与托盘/退出无残留):71 项
|
||||
```
|
||||
|
||||
后端脚本验证**实时推送时延、作业完成、成绩下发、权限隔离、上课时段紧急通知拦截、导入与失败回滚、班级账号代全班操作、老师端导入不越权**等 136 项,
|
||||
后端脚本验证**实时推送时延、作业完成、成绩下发、权限隔离、上课时段紧急通知拦截、导入与失败回滚、班级账号代全班操作、老师端导入不越权、单双周课表与 ClassIsland 课程表导入**等 148 项,
|
||||
实测通知 37ms、作业 26ms、成绩 25ms 到达(要求 < 5 秒),紧急通知 409 拦截与二次确认后发布均通过;
|
||||
客户端脚本验证 preload 桥接、渲染进程、IndexedDB 读写、断网回退、**灵动岛四种状态切换**与联网集成
|
||||
(**班级账号登录** + 四类数据 + Socket.IO),实测 65/65 通过;退出后再启动一次校验单实例锁与文件锁已释放(离线复跑 59/59)。
|
||||
(**班级账号登录** + 四类数据 + Socket.IO),实测 71/71 通过;退出后再启动一次校验单实例锁与文件锁已释放(离线复跑 65/65)。
|
||||
|
||||
## 默认账号与种子数据
|
||||
|
||||
@@ -220,9 +220,9 @@ pnpm verify:desktop # Electron 客户端(含灵动岛收起常驻/命中
|
||||
| `pnpm dist:dir` | 打包客户端免安装目录 `release/win-unpacked`(含可执行文件,最快) |
|
||||
| `pnpm dist:win` | 打包客户端 nsis 安装包 + portable 单文件 EXE |
|
||||
| `pnpm dist:all` | 服务端安装程序 + 客户端安装程序一起打 |
|
||||
| `pnpm verify:e2e` | 后端端到端验收(136 项,含角色权限矩阵、导入与班级账号,需服务端已启动) |
|
||||
| `pnpm verify:e2e` | 后端端到端验收(148 项,含角色权限矩阵、单双周课表与 ClassIsland 课程表导入、导入与班级账号,需服务端已启动) |
|
||||
| `pnpm verify:web` | Web 管理端 UI 真实点击测试(Electron 驱动,20 项,含权限入口隐藏、叫人紧急/普通与导入弹窗) |
|
||||
| `pnpm verify:desktop` | EXE 客户端冒烟验证(65 项,含灵动岛圆角/收起常驻/命中兜底/展开收起再展开/开合不震动/已读/作业/紧急与普通叫人、课表时间轴、个性化全参数) |
|
||||
| `pnpm verify:desktop` | EXE 客户端冒烟验证(71 项,含灵动岛圆角/收起常驻/命中兜底/展开收起再展开/开合不震动/已读/作业/紧急与普通叫人、课表时间轴、个性化全参数) |
|
||||
| `pnpm typecheck` | 全仓库类型检查(含 `vue-tsc`) |
|
||||
| `pnpm lint` / `pnpm lint:fix` | ESLint 检查 / 自动修复 |
|
||||
| `pnpm format` / `pnpm format:check` | Prettier 格式化 / 检查 |
|
||||
@@ -319,14 +319,23 @@ pnpm verify:desktop # Electron 客户端(含灵动岛收起常驻/命中
|
||||
|
||||
### 功能页面
|
||||
|
||||
| 页面 | 功能 |
|
||||
| ---- | -------------------------------------------------------------------------------------------- |
|
||||
| 登录 | 服务器地址 + **班级码 + 班级密码**,附带「测试连接」;已登录但服务器不可达时可「离线进入」 |
|
||||
| 课表 | 按周展示 7 列课表,支持上一周/下一周/下拉选择周次,高亮"今天",显示周次范围与地点 |
|
||||
| 作业 | 列表(全部/未完成/已完成过滤)、详情抽屉、**全班标记完成**/取消、附件链接、逾期高亮 |
|
||||
| 通知 | 未读红点(侧边栏徽标)、优先级标签、点击自动标为已读、全部已读、未读过滤、详情抽屉 |
|
||||
| 成绩 | 班级账号看到**全班成绩总览**:表格(分数/得分率/等级)+ ECharts 柱状图 + 等级分布 + 最近更新 |
|
||||
| 设置 | 服务器地址(保存并测试)、账号信息、离线缓存条目统计与清空、客户端版本信息、退出登录 |
|
||||
| 页面 | 功能 |
|
||||
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 登录 | 服务器地址 + **班级码 + 班级密码**,附带「测试连接」;已登录但服务器不可达时可「离线进入」 |
|
||||
| 课表 | 按周展示 7 列课表,支持上一周/下一周/下拉选择周次,高亮"今天",显示周次范围与地点;**单周/双周**的课带 `单周`/`双周` 标签 |
|
||||
| 作业 | **看板模式**(按科目卡片、点击卡片放大全屏、可切换「显示时间」与**看板字号**、随时切回列表模式)+ 列表模式、详情抽屉、附件链接;班级设备用**未交名单**勾选谁没交 |
|
||||
| 通知 | 未读红点(侧边栏徽标)、优先级标签、点击自动标为已读、全部已读、未读过滤、详情抽屉 |
|
||||
| 成绩 | 班级账号看到**全班成绩总览**:表格(分数/得分率/等级)+ ECharts 柱状图 + 等级分布 + 最近更新 |
|
||||
| 设置 | 服务器地址(保存并测试)、账号信息、离线缓存条目统计与清空、客户端版本信息、退出登录 |
|
||||
|
||||
**作业看板(学生端)**:默认按科目分卡片(每张卡列该科作业,条目可点开详情),
|
||||
工具栏可切「看板 / 列表」、开关「显示时间」、拖「字号」(11–28px 即时生效),
|
||||
点卡片或「全屏看板」进入全屏放大视图;这些偏好写入客户端配置(`homeworkBoard`),重启后仍生效。
|
||||
|
||||
**未交名单(班级设备)**:作业列表/详情里点「未交名单」→ 勾选**没交作业**的同学 →
|
||||
保存后其余同学自动标记为已完成(`PATCH /api/homeworks/:id/submissions`,
|
||||
`GET .../submissions` 读名单)。教师端 Web 管理端的作业详情抽屉里也能看到未交名单并勾选保存,
|
||||
普通学生调用该接口返回 403(只能标记自己)。
|
||||
|
||||
> 通知到达时还会由**灵动岛**在桌面中上方浮出提醒(见上一节),上课时段自动隐藏、紧急通知立即展开。
|
||||
|
||||
@@ -360,7 +369,9 @@ pnpm verify:desktop # Electron 客户端(含灵动岛收起常驻/命中
|
||||
| 场景 | 灵动岛行为 |
|
||||
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 收到通知(非上课时段) | **直接出现胶囊**(无"上岛"入场动画):`新消息 · 共 N 条` + 发送人 + `点击查看` |
|
||||
| 多条未处理消息 | 胶囊标题汇总类型:`新消息:叫人/作业/通知(共 3 条)` + `点击查看`,右侧附类型小标签(单条时显示具体类型名) |
|
||||
| 点击胶囊 | 展开为**详情卡**(形变 + 淡入,无回弹):标题、内容、时间、`打开应用 / 标为已读 / 知道了` |
|
||||
| **上课时段的"普通叫人"** | **不自动展开**(不打断课堂),但保留 `叫人` 胶囊并**允许学生主动点开**;下课自动展开(`isOpenable`:紧急消息与任何叫人都可点开) |
|
||||
| **收起后胶囊常驻** | 自动收起 / 手动收起只把卡片回缩为胶囊,**有未处理通知时胶囊不会消失** —— 随时都能再次点开(彻底消失只在"知道了 / 标为已读"、队列清空或进入上课时段) |
|
||||
| **收起态点击命中** | 收起瞬间窗口会保持可交互,且渲染进程在每次状态变化后都按**缓存的指针位置**重算命中并被固化为回归用例("收起态点击可再次展开(指针未移动也算命中)") |
|
||||
| 点击卡片空白处 | 回缩为**胶囊**(不直接消失,仍可再次点开) |
|
||||
@@ -400,6 +411,14 @@ pnpm verify:desktop # Electron 客户端(含灵动岛收起常驻/命中
|
||||
| 非上课时段"新消息"胶囊 |  | 268×44 / 536×88 | 直接出现(无入场动画),点击后展开 |
|
||||
| 点击胶囊后的详情 |  | 404×316 / 808×632 | 标题/内容/时间 + 打开应用/标为已读/知道了 |
|
||||
|
||||
**收起态胶囊会汇总消息类型**(用户需求):当有多条未处理消息时,胶囊标题显示
|
||||
`新消息:叫人/作业/通知(共 3 条)`、副标题 `点击查看`,并在右侧把三种类型做成小标签;
|
||||
只有一条时仍显示具体类型名(`新消息` / `新作业` / `老师叫你`)。
|
||||
多类型/多条的状态由渲染进程按 `state.active + state.queued` 现算,收起后在胶囊上就能看清"这堆消息里都有什么"。
|
||||
|
||||
> 设置页的「预览效果」走 `context.preview` 专用通道:示例岛**直接展开**、**失焦不收起**
|
||||
> (用户拖滑块时主窗口一直是焦点)、30 秒后自行消失、点「知道了」不留残影。
|
||||
|
||||
> 截图路径可用 `ISLAND_SHOTS_DIR` 覆盖(默认 `.cache/island-shots/`);
|
||||
> 归档目录里的图就是打包后 EXE 跑自动化验证时产出的同一份文件。
|
||||
|
||||
@@ -574,7 +593,7 @@ pnpm dist:win # nsis 安装包 + portable 单文件(需联网下载 NSIS
|
||||
| **退出后无残留(文件锁 + 二次启动)** | ✅ 配置目录可删除(句柄已释放)→ 用同一配置目录二次启动成功并再次全绿 |
|
||||
|
||||
该验证对**开发产物与打包后的 EXE 都适用**(打包后用 `ELECTRON_SMOKE_RESULT=<file>` 写出 JSON 结果,
|
||||
已实测 `packaged: true`、65/65 通过、退出码 0)。灵动岛各状态的窗口截图会写到
|
||||
已实测 `packaged: true`、71/71 通过、退出码 0)。灵动岛各状态的窗口截图会写到
|
||||
`ISLAND_SHOTS_DIR`(默认 `.cache/island-shots/`,仓库内留档目录 `docs/screenshots/island/`),
|
||||
并对尺寸、宽高比、绘制内容(不透明像素/颜色种类)、紧急态红色像素与外溢红光做断言。
|
||||
冒烟进程使用独立 userData(`ELECTRON_SMOKE_PROFILE`),因此**用户开着客户端也能跑验证**。
|
||||
@@ -714,18 +733,48 @@ POST /api/notifications { …, priority: "NORMAL" }(上课时段)
|
||||
(组件 `packages/web-admin/src/components/TimeLayoutImportDialog.vue`)。
|
||||
|
||||
- **输入**:粘贴 JSON 文本或选择 ClassIsland 导出的 `.json` 文件;
|
||||
- **容错解析**(`packages/server/src/modules/imports/time-layout.service.ts`):
|
||||
顶层数组、`{ TimeLayouts: [...] }`、`{ TimeLayoutItems | Items | items | Layouts | TimeLayout: [...] }` 都能识别;
|
||||
时间支持 `"8:0:0"` / `"08:00"` / ISO 字符串 / `StartSecond: 28800` 秒数;字段名兼容
|
||||
`StartTime|startTime|start`、`EndTime|end`、`Name|Title|Subject`、`TimeType|Type`、`IsSkipped|skip`;
|
||||
- **真实档案形态**(`Profiles/<档案名>.json`,ClassIsland 1.7.106+ / 2.x,字段为 PascalCase):
|
||||
`TimeLayouts` 是 **`{ "<guid>": { Name, Layouts: [ { StartTime: "08:00:00", EndTime, TimeType } ] } }`
|
||||
的字典**,条目在各时间表的 `Layouts` 数组里(解析器会把所有时间表的 Layouts 合并后导入);
|
||||
同时兼容更早的写法:顶层数组、`{ TimeLayouts: [...] }`、`{ items | Items | Layouts | TimeLayout: [...] }`
|
||||
以及单时间表对象 `{ Name, Layouts: [...] }`;
|
||||
- **时间字段**:`StartTime`/`EndTime`(`"08:00:00"`)、旧版 `StartSecond`/`EndSecond`(ISO 字符串或秒数)、
|
||||
`"8:0"` / `"08:00"` 都能识别;
|
||||
- **时间点类型**(`TimeType`):`0=上课`、`1=课间`、`2=分割线`、`3=行动`,与 ClassIsland 一致;
|
||||
- **校验**:时间无法解析、结束不晚于开始、空配置都会给出**逐条原因**(含条序号与名称);
|
||||
时间重叠、分割线无时间、原配置标记跳过等只作为 `warnings`;
|
||||
时间重叠、分割线无时间等只作为 `warnings`;
|
||||
- **写入模式**:
|
||||
- `replace`:整体替换该班同名(缺省「默认时间表」)配置;
|
||||
- `merge`:以**开始时间**为槽位标识合并——同槽位被导入项覆盖,其它旧节次全部保留(不静默丢数据);
|
||||
- **失败回滚**:解析存在 `errors` 时返回 400 `IMPORT_INVALID` / `IMPORT_EMPTY`,
|
||||
**数据库完全不写入**,界面会提示"原有配置不会被修改",不会出现导入到一半的脏状态。
|
||||
|
||||
### 4. ClassIsland 课程表导入(支持单双周)
|
||||
|
||||
界面:Web 管理端 →「课表管理」→「导入 ClassIsland 课程表」
|
||||
(组件 `packages/web-admin/src/components/ClassPlanImportDialog.vue`)。
|
||||
|
||||
- **输入**:同一份 ClassIsland 档案 JSON(含 `TimeLayouts` / `ClassPlans` / `Subjects` 三个 Guid 字典);
|
||||
- **单双周来源**:`ClassPlan.TimeRule.WeekCountDiv`(`0=每周`、`1=单周`、`2=双周`)
|
||||
+ `WeekCountDivTotal`(默认 2)——**与 ClassIsland 源码一致的判定语义**,
|
||||
因此"周一单周上数学 / 双周上英语"在档案里就是**两条 ClassPlan**(`WeekCountDiv` 分别 1 与 2);
|
||||
3 周及以上轮换无法用单双周表达,会给出 `warnings` 并按每周处理;
|
||||
- **节次对齐**:`ClassPlan.Classes[i]` 对应 `Layouts` 里**第 i 个 `TimeType === 0` 的点**
|
||||
(课间/分割线不占位),与 ClassIsland 的 `RefreshClassesList()` 行为一致;
|
||||
- **科目映射**:`Classes[i].SubjectId` → `Subjects[<guid>].Name`;班级里还没有的科目会**自动补建课程**
|
||||
(任课老师取该班班主任),预览里会提前列出"待补建科目";
|
||||
- **星期换算**:ClassIsland `WeekRule.WeekDay` 是 `0=周日 … 6=周六`,导入时换算成我们的 `1=周一 … 7=周日`;
|
||||
- **写入模式**:`replace`(清空该班课表后写入,事务内完成)/ `merge`
|
||||
(按 `星期 + 开始时间 + 单双周` 去重,存在则更新,否则新增);
|
||||
- **接口**:`POST /api/imports/class-plan/preview`(只解析不写库)与 `POST /api/imports/class-plan`;
|
||||
- **课表单双周字段**:`Schedule.weekParity = ALL | ODD | EVEN`;周视图/上课判定都按
|
||||
`第 1 周 = 单周` 的规则过滤(`weekParityOf(week)`,与 ClassIsland 默认相位一致),
|
||||
历史数据默认 `ALL`,行为不变。手动添加课表时也能在弹窗里直接选「每周 / 单周 / 双周」。
|
||||
|
||||
> 手工覆盖 dist 部署(而非跑安装程序)时,已有数据库需要补一列:
|
||||
> `node packages/server/scripts/apply-parity-column.cjs "<安装目录>/server/dist/lib/db.js"`
|
||||
> (安装程序覆盖升级会自动补迁移;仅空库首次启动会执行随包迁移 SQL)。
|
||||
|
||||
## REST API 一览
|
||||
|
||||
统一响应体:`{ "success": true, "data": {}, "message": "" }`(错误为 `success:false` + `code`)。
|
||||
@@ -945,7 +994,7 @@ pnpm db:migrate && pnpm db:seed
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
||||
| `pnpm verify:e2e`(开发环境与**安装后的生产实例**各跑一次) | **133/133** ✅ |
|
||||
| `pnpm verify:web`(Web 管理端真实点击 + 权限入口隐藏 + 手机适配 + 叫人 + 成绩/时间配置导入 + PWA) | **19/19** ✅ |
|
||||
| `pnpm verify:desktop`(客户端冒烟 + WinIsland 架构/连续圆角/收起常驻 + 命中兜底与"展开收起再展开" + 个性化全参数 + 托盘与退出无残留) | **65/65** ✅ |
|
||||
| `pnpm verify:desktop`(客户端冒烟 + WinIsland 架构/连续圆角/收起常驻 + 命中兜底与"展开收起再展开" + 个性化全参数 + 托盘与退出无残留) | **71/71** ✅ |
|
||||
| `pnpm typecheck` / `pnpm lint` / `pnpm format:check` | 全部通过 ✅ |
|
||||
| 安装程序完整生命周期(静默安装 → 启停脚本 → 卸载) | 通过 ✅ |
|
||||
| Docker / Nginx 部署样例 | 文件已提供,本机无 Docker 未实测 ⚠️ |
|
||||
|
||||
Reference in new issue
Block a user