Commit Graph
6 Commits
Author SHA1 Message Date
LoveChengke 11008928a1 fix(island,homework,schedule,web): 16 项反馈修复(联合通知点不开 / 设置不生效 / 红点位置 / 看板当天且铺满 / 单双周与周数可调 / Web 手机端与批量通知等)
一、灵动岛
- ①「紧急通知后来了新作业,收起再点开不了新作业」根因有二,都已修:
  (a) active 仍是紧急那条,点击只会反复打开它 —— 现在点击胶囊优先打开**最新**那条未处理消息(队列里更晚的);
  (b) 上课时段 syncWindow 会把普通通知立刻隐藏(表现为点了没反应)—— 新增 explicitShowId,
      "用户主动点开"的那条守卫放行(岛完全隐藏时仍严格不打扰)。
  冒烟新增「紧急通知后来了新作业:收起再点击打开的是新作业(联合通知回归)」。
- ② 设置实时预览/保存:补「设置页真实链路」断言(window.desktop.islandSetAppearance → IPC → 主进程 →
  渲染进程 CSS 变量 + 窗口尺寸),并断言"岛显示中改尺寸立即生效"(实测均通过)。
- ③ 未读红点移到「通知」图标右上角(此前在文字后面),并加 DOM 几何断言(dx>0 且 dy<0,
  没有未读时自动造一条未读通知再断言、断言后删除)。
- 冒烟稳定性:新增 island.setBlurCollapseEnabled()(默认开),测试期间关掉"失焦收起",
  避免系统抢焦点导致展开态被收回的随机失败;专门验证失焦的用例仍临时打开。

二、作业看板(学生端)
- ⑥ 只看当天:新增「只看今天」开关(默认开),按 createdAt 的本地日期过滤,昨天的过期作业不再混入。
- ⑧ 完整显示:卡片同时渲染标题 + 作业要求全文(列表模式也加「作业要求」列)。
- ⑨ 铺满不滚动:看板外层高度固定 + overflow hidden,按内容/可用高度算缩放系数用 transform: scale 适配
  (工具栏显示当前缩放比例),页面本身不出现滚动条。
- ⑦ 全屏看板标题栏用 #header 插槽:标题左对齐、当天日期时间绝对居中(受「显示时间」开关控制,每分钟刷新)。

三、课表与周数
- ⑤ 学期周数可调:新增 Class.termWeeks(默认 20,迁移 + 维护脚本),班主任在班级管理里改;
  GET /api/dashboard/term?classId= 返回该班 maxWeek,课表周次下拉与新建课表的默认 weekEnd 都按它来
  (不再固定 30 周)。
- ⑩ 课表管理只保留「导入 ClassIsland 课程表」入口(时间表由课程表 JSON 的 TimeLayouts 一并带入),
  「导入时间配置」按钮下线;ui-smoke 第 7.7 段改为断言新弹窗(解析 4 行 + 单双周标签 + 旧入口已下线)。

四、以班级为单位(⑬)与 Web 端
- 个人学生账号登录停用:POST /api/auth/login 对 role=STUDENT 返回 403(改用班级码 + 班级密码);
  学生行仍作为个人数据记录键由班级会话代全班读写;e2e 全面切换到班级会话并新增该断言。
- ④ 作业科目过少:新增共享 SUBJECT_CATALOG(16 个常见科目)+ 种子课程扩到全科目;
  Web 作业发布弹窗可选「其他科目(新建)」,提交前自动建课(同名复用)。
- ⑪ 通知支持多班批量发布(多选 + 逐班发布 + 汇总结果 + 二次确认覆盖所有选中班);
  ⑫ 单班只显示已读情况、跨班才显示已读率;⑭ 手机端课表可横向滚动看全 7 天;
  ⑮ 手机端已连接不再显示孤立绿点(仅未连接时显示文字标签);⑯ 手机端「叫人/发布通知」按钮同宽左对齐;
  ⑬ Web 学生管理去掉「用户名」列(新建自动生成学号式用户名,导入仍支持显式指定)。

验证:verify:e2e 149/149、verify:web 20/20、verify:desktop 75/75(复跑 68/68,打包便携版 75/75 packaged=true)、
typecheck / lint / format 全绿;服务端与 Web 已重新打包并部署到本机安装目录。
2026-09-13 08:39:11 +08:00
LoveChengke 0690546307 fix(island,server): 连续圆角四角修正 + 叫人分紧急/普通两级
灵动岛圆角(用户反馈"左上角和右下角缺一块 / 圆角边缘割裂"):
- squircle.ts 改为"每个角独立圆心 + 角度参数"的超椭圆采样(原本四角共用同一个偏移向量,
  左上/右下被切掉一块);四角几何与 WinIsland 的连续超椭圆一致,r 与 h/2、w/2 自动收敛。
- verify:desktop 新增像素级断言「连续圆角四角一致」:从四角沿对角线找岛体边界,
  四角步进必须两两一致(展开 15/15/15/15、紧急 14/14/14/14、胶囊 5/5/5/5),
  并保留路径绕行 |winding|≈2π 断言(抓"角上往回折")。该断言即本次回归的守卫。
- 截图留档同时写入裁剪图与整窗图,便于人工复核角形。

叫人分紧急 / 普通两级(用户要求"叫人也分为紧急和普通"):
- shared:CreateCallRequest 新增 urgent;紧急叫人 → URGENT(无视上课时段立即展开),
  普通叫人(默认)→ HIGH(按普通通知排队)。
- desktop:isImmediate 只认 priority=URGENT,不再把 kind=call 一律视为立即消息;
  普通叫人回到与普通通知相同的排队 / 隐藏 / 下课弹出路径。
- web:通知发布 → 叫人、学生管理 → 叫人 两个弹窗新增"级别"单选(默认普通)+ 联动提示文案。

验证:verify:e2e 135/135(新增普通 / 紧急叫人级别断言)、verify:web 20/20(新增级别单选断言)、
verify:desktop 59/59 + 离线复跑 53/53、typecheck / lint / format 全绿。
2026-09-12 19:54:00 +08:00
LoveChengke 30e4c36809 feat(rbac): 落地角色权限模型(需求 7)——班级/人员/名单/成绩收紧到管理员,班主任管本班课表,科任仅作业·叫人·通知
## 权限矩阵(前后端共用一份)

- 新增 `packages/shared/src/permissions.ts`:`ClassRole`(ADMIN/HEAD/SUBJECT/STUDENT/NONE)、
  `resolveClassRole()`、`canManageClasses/AssignTeachers/Roster/Schedule/Courses/PublishContent/ManageGrades/Import`
  与 `PERMISSION_MATRIX`(README 与验收脚本据此展开)。
- 后端 `lib/access.ts`:新增 `resolveUserClassRole()`(`Class.teacherId` → 班主任,`ClassTeacher` → 科任)
  与 `assertCanManageClasses/AssignTeachers/Roster/Schedule/PublishContent/ManageGrades`;
  各 service 在**服务层**断言(路由仅做角色过滤),越权统一 403。
- 收紧项:班级增删改、班主任/科任分配、学生名单与账号 → 仅 ADMIN;
  课表/课程增删改 → ADMIN 或本班班主任;成绩录入/修改/批量 → 仅 ADMIN(按需求清单);
  作业/通知/叫人 → ADMIN、班主任、科任皆可。
- 读权限保持不变:教师仍可查看自己班级的课表/作业/通知/成绩列表与统计。

## 前端按同一矩阵隐藏入口

- 侧边栏:教师端不再显示「班级管理 / 学生管理 / 成绩录入」(管理员仍是 7 项)。
- 班级页:新建/编辑/删除按钮仅管理员可见;课表页:增删改按钮仅"管理员或本班班主任"可见;
  成绩页:录入/批量/删除按钮仅管理员可见。
- **叫人入口调整**:教师需要叫人,而「学生管理」是管理员专属,因此教师入口移到
  「通知发布 → 叫人」(选班级 → 选学生 → 快捷短语/自定义消息;名单走 `/classes/:id/students`,
  不使用管理员专属的 `/students`);管理员在学生管理页仍保留逐行「叫人」。

## 顺手修掉的两个真实缺陷

- `NotificationsView.openCreate()`:班级列表异步加载期间点「发布通知」会导致「目标班级」为空、
  校验失败且**静默不发布**;现在打开弹窗前会确保班级已加载(冒烟同步等待下拉就绪)。
- `desktop-client/scripts/smoke.mjs`:上一次冒烟被强杀后配置目录仍被句柄占用,`rmSync` 报 ENOTEMPTY
  直接让冒烟启动失败;改为按进程号使用独立目录并容错清理。

## 验证

- `pnpm verify:e2e` **82/82** ✅:新增 14 项角色权限断言(班主任/科任建班被拒、班主任删班被拒、
  班主任/科任分配人员被拒、管理员分配与取消成功、科任管理课表被拒、科任可布置作业、
  科任/班主任录入成绩被拒、管理员录入成功),并把历史用例中"仅管理员可执行"的操作切到管理员令牌。
- `pnpm verify:web` **17/17** ✅:新增「教师端隐藏无权入口」断言(可见菜单=仪表盘/课表/作业/通知,
  越权入口=[]),叫人入口改到通知页后仍验证弹窗、8 条快捷短语、自定义输入与可选中态。
- `pnpm verify:desktop` **34/34** ✅;`lint` / `format:check` / `typecheck` 全绿。
- 数据清理:清掉历史遗留的测试班级与 4 条探针课表,演示数据恢复为 3 个班级。
2026-09-12 15:02:02 +08:00
LoveChengke f759aac0a2 feat(web-admin): 手机小屏适配(1Panel 风格抽屉导航)并补齐移动端自动化验证
## 布局与交互(沿用 1Panel 风格)

- 新增 `src/composables/useResponsive.ts`:断点 768 / 992px + resize/orientationchange 监听
- `AdminLayout.vue`:
  - >992px 桌面保持原样(210px 深色侧栏 + 顶栏连接状态 + "已接收 N 条实时事件")
  - <=768px 手机:侧边栏不再渲染,顶栏出现汉堡按钮(`aria-expanded` 标记状态),
    点击唤出 240px 深色抽屉菜单,点菜单项自动收起;顶栏显示当前页标题,
    用户菜单收成头像图标(身份信息移入下拉菜单首行)
  - 复用同一套 `.ch-sidebar` 样式,抽屉内容与桌面侧栏视觉一致
- `styles/index.css` 增加全局响应式规则:工具栏控件铺满整行、`.table-card` 表格卡片内横向滚动、
  弹窗 `calc(100vw - 24px)`、消息框/抽屉自适应、`.stat-grid` 小屏两列、分页居中、标签页可横滑
- 各视图:7 个表格卡片加 `.table-card`;班级详情/作业详情的 `el-descriptions` 小屏降为单列;
  登录页卡片自适应宽度并加安全内边距

## 自动化验证(scripts/ui-smoke)

- 新增 `runMobileChecks`:把窗口真实缩到 390×844,用真实点击验证 7 项——
  侧边栏收起 + 汉堡出现、抽屉滑出且菜单完整、抽屉内点击"通知发布"跳转并收起、
  页面无横向溢出(表格自身滚动不计入)、表格在卡片内可滚动、工具栏控件铺满、弹窗宽度自适应
- 移动端截图留档到 `docs/screenshots/web-mobile/`(`UI_SMOKE_SHOTS_DIR` 可覆盖)
- 踩坑修复:隐藏窗口下 Chromium 会冻结 CSS transition,Vue `<Transition>` 收不到 transitionend,
  Element Plus 抽屉会卡在 `enter-from`(表现为"点了没反应")——移动端检查改为 `showInactive()` 并断言
  "抽屉滑到位/收起到位",而不是只看元素是否存在

## 安装包升级健壮性

- `scripts/nsis/server-installer.nsi`:覆盖安装前先 `RMDir /r web|server|node_modules`,
  避免旧 hash 分片、旧 dist 模块、旧依赖残留导致"新旧版本混装"
  (此前 web/assets 里累积了 4 份 AdminLayout 分片)

## 验证结果(均在安装后的生产实例上)

- `pnpm verify:web` 15/15 ✅(8 项原有 + 7 项移动端)
- `pnpm verify:e2e` 62/62 ✅
- 覆盖安装:`.env` 与数据库保持不变,`web/assets` 只剩当前版本分片,服务 `healthz` 正常
- `pnpm lint` / `format:check` / `typecheck` 全部通过
2026-09-12 10:09:11 +08:00
LoveChengke 3a9f8812fc feat: 客户端灵动岛 + 上课时段紧急通知二次确认,并修复打包/校验脚本在 Electron 宿主下的多个缺陷
## 客户端灵动岛(动态岛)

- 新增独立透明置顶窗口:桌面中上方常驻、不占任务栏、不抢焦点(268x44 胶囊 / 404x316 详情 / 424x344 紧急)
- 三种形态与动画:隐藏 ↔ 胶囊 ↔ 详情,尺寸用 easeOutCubic 260ms 逐帧缓动、透明度单独淡入淡出
  (尺寸与淡入使用各自动画令牌,避免互相打断导致窗口卡在胶囊尺寸)
- 行为:普通通知变「新消息 · 共 N 条」胶囊,点击展开详情;上课时段自动隐藏并进入待发队列,
  下课后自动弹出;紧急通知无论是否上课立即展开(红色呼吸光晕 + 紧急角标,无需点击)
- 上课状态来自 GET /api/schedules/current(10s 心跳 + 下课时长定时器),
  新增 src/main/island.ts(控制器/IPC)、src/preload/island.ts、src/island/IslandApp.vue、
  src/renderer/island/bridge.ts、island.html 与多页 Vite 构建

## 上课时段发布紧急通知的二次确认

- 服务端:priority=URGENT 且当前处于上课时间段且未带 confirmDuringClass 时返回
  409 URGENT_DURING_CLASS(携带 current/next/week/serverTime),绕过界面调接口同样被拦截
- 新增 GET /api/schedules/current(inClass/current/next,at 参数用于诊断与测试)
- Web 管理端:全屏警告遮罩(UrgentClassWarning.vue),显示正在上的课时段与待发布标题,
  确认按钮带 3 秒倒计时(圆环进度,倒计时结束前禁用),取消即不发;并发命中 409 同样兜底弹窗

## 打包与验证脚本修复(Electron 宿主环境)

- scripts/lib/node-runtime.mjs:定位真正的 node.exe。此前经 pnpm 运行时 process.execPath 指向
  Electron,dist:server 会把 Electron 复制成安装包内的 node.exe,dist:win 会让 electron-builder 报
  Unknown argument: .../cli.js(改为显式使用真实 Node)
- scripts/lib/electron-env.mjs:启动 Electron 前清理继承的 ELECTRON_RUN_AS_NODE(否则 Electron 退化为
  纯 Node,require('electron') 直接 MODULE_NOT_FOUND),并从 path.txt 定位 electron.exe
  (process.versions.electron 存在时 import 'electron' 返回的是 API 对象而非路径)
- scripts/dist-server.mjs:新增构建产物新鲜度校验(曾出现 server 已重建但打进安装包的 shared/dist 是旧的,
  安装后服务启动即报 does not provide an export named MAX_TERM_WEEK)
- scripts/ui-smoke:新增 live-probe.mjs 真实制造/清理上课时段,Electron 驱动真实点击断言全屏警告文案、
  按钮禁用与 3 秒倒计时时长(实测 2966ms),测试后自动删除探针课表与测试通知

## 验证结果

- pnpm verify:e2e 62/62(新增上课时段拦截、二次确认后发布、普通通知不受限等 8 项)
- pnpm verify:web 8/8(新增上课时段紧急通知全屏警告 + 倒计时)
- pnpm verify:desktop 18/18(新增灵动岛窗口/上课隐藏/下课弹出/紧急立即展开/胶囊点击展开 + 截图)
- 打包后 EXE 冒烟 15/15(packaged: true,退出码 0)
- 服务端安装程序升级安装:.env 与数据库保持不变,/api/schedules/current 正常
- pnpm lint / format:check / typecheck 全部通过
2026-09-12 08:41:08 +08:00
LoveChengke 84ab49de10 feat: 班级小助手 阶段1-3(monorepo + 后端 REST/Socket.IO + Web 管理端) 2026-09-11 21:57:38 +08:00