feat(courseware,docs): 完善课件传输与岛编辑并统一全仓文档
- 增加课件上传、班级可见范围、设备补收与传输进度 - 整合自定义灵动岛编辑器和相关客户端交互 - 同步三端契约、数据库迁移、部署配置与隔离回归 - 按源码统一文档与官网说明,移除过时任务书和审查记录 - 保持产品版本为 1.4.0
This commit is contained in:
1 parent
536379036a
commit
c7f215a09c
127 files changed
+4334
-8482
No files matched your search
@@ -79,18 +79,20 @@ beUI 的组件本身是 React,用不到这里,但它的动效语言与框架
|
||||
两端各有一个 `prefers-reduced-motion` 的全局降级块,把动画压到 1ms ——
|
||||
**是压短而不是删掉**(`transition: none` 会让依赖 `transitionend` 的逻辑失灵)。
|
||||
|
||||
唯一的例外是**加载指示旋**:它是放慢而不是停,停掉会让界面看起来像卡死。
|
||||
主窗口中的例外是**加载指示旋**:它是放慢而不是停,停掉会让界面看起来像卡死。
|
||||
|
||||
灵动岛使用独立动画控制,应用内开关优先,具体见[灵动岛](island.md)。
|
||||
|
||||
## 客户端设置页
|
||||
|
||||
「设置」是一个可展开的分组,六个子页各有自己的路由:
|
||||
「设置」是可展开分组,常用子页如下;开发者模式还提供开发者页,自定义岛编辑为灵动岛子路由:
|
||||
|
||||
| 子页 | 内容 |
|
||||
| ------ | ----------------------------------------------------------------------------- |
|
||||
| 通用 | 服务器连接、离线缓存、作业录入短语、使用引导 |
|
||||
| 外观 | 浅色 / 深色 |
|
||||
| 灵动岛 | 尺寸、字号、主题色、视觉风格、停靠、边距、空闲细缝(详见[灵动岛](island.md)) |
|
||||
| 提醒 | 通知显示在哪一端 |
|
||||
| 提醒 | 本机课程等提醒开关与行为 |
|
||||
| 账号 | 当前班级信息、退出登录 |
|
||||
| 关于 | 版本 / Electron / Chromium / Node、诊断信息、检查更新、鸣谢 |
|
||||
|
||||
|
||||
+13
-47
@@ -1,56 +1,22 @@
|
||||
---
|
||||
title: 叫人
|
||||
description: 老师点名让学生过来:普通与紧急两档、快捷短语、谁能叫谁,以及它在教室里的表现。
|
||||
description: 点名、优先级、可见范围和客户端展示。
|
||||
---
|
||||
|
||||
# 叫人
|
||||
|
||||
「叫人」是老师点名让某位同学来一趟 —— 它**不是**一条普通通知,因为它有一个明确的收件人和一个明确的动作
|
||||
(学生会点「收到」)。
|
||||
在 Web“通知发布 → 叫人”选学生,填写事由或选择快捷短语,需 call:create 和班级范围权限。
|
||||
可调用的学生必须属于相应在读班级且未转出;岗位范围、任课或班主任关系由服务端检查。
|
||||
学生没有账号,无法以个人身份调用接口。
|
||||
|
||||
## 在哪发
|
||||
| 级别 | 落库优先级 | 经典 fixed 展示 |
|
||||
| -------- | ---------- | ------------------------------------------------------------------ |
|
||||
| 普通叫人 | HIGH | 胶囊可点击,上课也允许主动查看;不自动展开打断课堂,下课可自动展开 |
|
||||
| 紧急叫人 | URGENT | 上课也立即展开,收起后仍可主动再打开 |
|
||||
|
||||
| 入口 | 谁能用 |
|
||||
| --------------- | ------------ |
|
||||
| 通知发布 → 叫人 | 教师、管理员 |
|
||||
| 学生管理 → 叫人 | 管理员 |
|
||||
自定义 custom 是常驻模式,两种叫人都先提示铃铛,由用户主动查看;详见[通知与提醒](notifications.md)。
|
||||
卡片内容包括被叫学生、教师和事由,“收到”复用通知已读机制。
|
||||
自定义消息优先于快捷短语,消息长度以接口 schema 校验为准。
|
||||
|
||||
学生管理页是管理员专属,但班主任与科任老师同样需要叫人,所以给老师留的入口在「通知发布 → 叫人」。
|
||||
|
||||
## 两档
|
||||
|
||||
| 级别 | 落库级别 | ClassHelper 班级端行为 |
|
||||
| ------------ | -------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| **普通叫人** | `HIGH` | 课间先显示「叫人」胶囊,点击展开;**上课时段只进队列、不打断课堂**,下课后自动弹出详情(90 秒后收起) |
|
||||
| **紧急叫人** | `URGENT` | **无视上课时段立即展开**,上课时被收起后仍保留胶囊、可再次点开 |
|
||||
|
||||
两档的卡片都是「请 XXX 同学找 XXX 老师」+ 具体事由,徽标是「叫人」,按钮是「收到」
|
||||
(复用通知的已读机制)。
|
||||
|
||||
> **是否立即展开只由 `priority` 决定**,与普通通知共用同一条排队 / 隐藏 / 下课弹出路径。
|
||||
|
||||
## 怎么说
|
||||
|
||||
两种输入方式,**自定义消息优先于快捷短语**:
|
||||
|
||||
- **快捷短语**(8 条,点击选中、再点取消):请到办公室找我 / 请到讲台找我 / 请到实验室找我 /
|
||||
请到门卫处找我 / 请带上作业本找我 / 请带上试卷找我 / 请到教室门口等我 / 请马上来一趟;
|
||||
- **自定义消息**:最长 200 字,例如「带上昨天的数学作业到办公室」。
|
||||
|
||||
## 谁能叫谁
|
||||
|
||||
| 角色 | 限制 |
|
||||
| ------ | ---------------------------------------------------------- |
|
||||
| 学生 | 调用被拒(403) |
|
||||
| 教师 | 只能叫**自己班级**的学生;跨班、或学生不在本班 → 400 / 403 |
|
||||
| 管理员 | 任意班级 |
|
||||
|
||||
## 服务端做了什么
|
||||
|
||||
`POST /api/calls`(教师 / 管理员)→ `urgent: true` 落库成 `URGENT`,否则 `HIGH`,
|
||||
然后广播两个事件:
|
||||
|
||||
- `notification:new` 到班级房间(通知中心与灵动岛都靠它);
|
||||
- `call:new` 定向到被叫学生 / 班级设备。
|
||||
|
||||
叫人通过实时通道送达本班客户端与灵动岛。
|
||||
`POST /api/calls` 保存为 Notification,带本地所属日期;随后发送 notification:new 班级广播和 call:new 班级会话定向事件。
|
||||
定向主体是班级会话,学生本身没有 Socket 房间;双播按消息 id 合并去重。
|
||||
+28
-83
@@ -1,98 +1,43 @@
|
||||
---
|
||||
title: 班级、学生与教师
|
||||
description: 班级账号、学生名单、班主任与各科任课老师,以及"谁能看什么、谁能改什么"。
|
||||
description: 名单、班级会话、任课关系与年度升级。
|
||||
---
|
||||
|
||||
# 班级、学生与教师
|
||||
|
||||
## 班级账号:ClassHelper 班级端的身份
|
||||
## 班级账号
|
||||
|
||||
学生**没有个人账号**。一台教室机器用一个**班级账号**登录:
|
||||
教室客户端使用班级码和班级密码登录,形成 CLASS_DEVICE 会话,主体是班级。
|
||||
班级码保存时统一大写;管理员在“班级管理”维护班级码、密码与班主任。
|
||||
班级端的已读、作业完成等操作代表全班,学生没有个人登录账号。
|
||||
客户端没有修改密码入口;员工本人改密码在 Web 顶栏菜单。
|
||||
|
||||
```
|
||||
班级码(例如 G203) + 班级密码 → 这台机器代表"高二(3)班"
|
||||
```
|
||||
班级名由入学年份和班号组成,例如“2026级1班”;年级独立保存,学年升级时班名不随之改写。
|
||||
班主任可以暂空,新学年自动创建的高一班级仅有骨架,需要管理员后续分配教师。
|
||||
|
||||
登录之后,这台机器上的「已读 / 完成 / 未交」都是**代全班**操作的;它看到的是本班的数据。
|
||||
## 学生名单
|
||||
|
||||
班级码是管理员在「班级管理」里设的(新建时自动生成,可以改成好记的)。
|
||||
校验时会统一按大写比对(码存大写)。
|
||||
Student 存学号、姓名、班级、性别、家长电话和状态,没有密码或角色字段。
|
||||
学号为必填唯一标识,格式为 1–32 位字母、数字或 . _ -;不会自动生成登录名。
|
||||
管理员或本班班主任在具备名单管理权限后管理学生,教师也可按授权范围只读名单。
|
||||
名单表格导入仍要求管理员及相应导入权限;学号查重在 Student 表完成,不当成 User 用户名。
|
||||
调班保留历史内容原归属;转出独立记账并从现行业务名单过滤,学号不可复用。
|
||||
|
||||
> 班级密码在新建班级时由 `.env` 的 `DEFAULT_CLASS_PASSWORD` 决定初始值,
|
||||
> 之后由管理员在「班级管理 → 修改班级账号」维护。
|
||||
>
|
||||
> **客户端没有改密码入口**,这是刻意移除的:教室机器上误改班级密码会让别的机器登录不了。
|
||||
> 教师 / 管理员本人改密码在 Web 端顶栏头像菜单里。
|
||||
## 教师与科目
|
||||
|
||||
## 学生
|
||||
教师使用工号和密码登录,管理入口受 teacher:read/teacher:manage 等功能权限控制。
|
||||
教师名单导入还要求管理员身份和相应导入权限。班主任关系在 Class.teacherId;
|
||||
科任关系唯一存于 Course(classId, name, teacherId),分配任课关系由班级管理权限及服务端断言限制。
|
||||
班主任不任教某科就不能修改该科作业/成绩;科任老师有相应权限时可以管理自己任教科目。
|
||||
|
||||
**学生管理**(仅管理员)→ 新建 / 导入名单。
|
||||
删除教师前,服务端检查其负责的班级、任课课程、发布过的作业/通知与所属课件;仍有业务内容则返回冲突,避免级联丢失。
|
||||
教师本人改密码、管理员修改教师密码、班级账号密码是不同入口;学生没有密码接口。
|
||||
|
||||
学生记录**只是名单**:成绩、未交名单、叫人、已读记录都按名单记录,所以名单不全这几项就不准。
|
||||
## 权限、学年与归档
|
||||
|
||||
| 项目 | 说明 |
|
||||
| -------- | ------------------------------------------------------------------------------------ |
|
||||
| 用户名 | `^[A-Za-z0-9_.-]{3,32}$`;单个新建时自动生成 |
|
||||
| 密码 | **没有密码概念**。学生的 `passwordHash` 是空串占位,不能登录,也没有任何重置密码接口 |
|
||||
| 所属班级 | 一个学生属于一个主班级 |
|
||||
|
||||
> 学生**不是账号**(`Student` 表里没有密码、没有角色),拿学号去登录只会得到 401 ——
|
||||
> 否则"密码错(401)"和"密码对但学生账号已停用(403)"的差异会把学生账号密码变成可离线验证的预言机。
|
||||
|
||||
## 教师
|
||||
|
||||
| 能力 | 在哪 | 谁能做 |
|
||||
| -------------------- | ------------------------------------------------ | ------------- |
|
||||
| 新建 / 编辑 / 删除 | 「教师管理」 | **仅管理员** |
|
||||
| 导入名单(xlsx/csv) | 「教师管理 → 导入名单」 | **仅管理员** |
|
||||
| 重置密码 | 「教师管理」(可指定新密码,留空用默认初始密码) | **仅管理员** |
|
||||
| 加进某个班 | 班级详情 → 任课老师(班级 + 科目 + 教师) | 管理员 |
|
||||
| 换班主任 | 班级详情顶部「更换班主任」 | **仅管理员** |
|
||||
|
||||
> **教师录入只有管理员能做**:`/api/teachers` 整组接口都是 `requireRole('ADMIN')`,教师调用返回 403。
|
||||
> 前端也只在管理员登录时显示这个菜单项。
|
||||
|
||||
### 删除教师有护栏
|
||||
|
||||
下面四种情况一律拒绝(409),因为删账号会级联删掉他在各班发过的内容:
|
||||
|
||||
1. 他还是某个班的**班主任**;
|
||||
2. 名下有**课程**;
|
||||
3. 有**任课关系**(`Course.teacherId === 他`);
|
||||
4. **发布过作业或通知**。
|
||||
|
||||
(后两项曾经漏检,会静默删掉该教师发过的作业与通知。)
|
||||
|
||||
## 角色与可见范围
|
||||
|
||||
班级内的角色由「账号角色 + 与该班级的关系」共同决定:
|
||||
|
||||
| 角色 | 怎么判定 | 可见范围 |
|
||||
| ------------------ | ------------------------------ | -------------------------- |
|
||||
| `ADMIN` 管理员 | `User.role = ADMIN` | 全部班级 |
|
||||
| 班主任 `HEAD` | `Class.teacherId === 当前用户` | 本班 |
|
||||
| 科任老师 `SUBJECT` | 该班 `Course.teacherId` 有当前用户 | 被任课的班级 |
|
||||
| 学生 `STUDENT` | `User.classId` / 班级账号 | 自己的班级(只读业务数据) |
|
||||
|
||||
### 权限矩阵(摘要)
|
||||
|
||||
| 操作 | 管理员 | 班主任 | 科任老师 |
|
||||
| ------------------------------------ | :----: | :----------: | :------: |
|
||||
| 班级创建 / 修改 / 删除 | ✅ | ❌ | ❌ |
|
||||
| 分配班主任与科任老师 | ✅ | ❌ | ❌ |
|
||||
| 学生名单管理(增删 / 导入) | ✅ | ❌ | ❌ |
|
||||
| 教师录入(新建 / 导入 / 改密码) | ✅ | ❌ | ❌ |
|
||||
| 数据库管理(备份 / 导入导出 / 切换) | ✅ | ❌ | ❌ |
|
||||
| 课表管理(增删改 / 导入) | ✅ | ✅(仅本班) | ❌ |
|
||||
| 布置作业 | ✅ | ✅ | ✅ |
|
||||
| 叫人 | ✅ | ✅ | ✅ |
|
||||
| 发布通知 | ✅ | ✅ | ✅ |
|
||||
| 成绩录入 / 修改 / 导入(仅本班) | ✅ | ✅(仅本班) | ❌ |
|
||||
| 维护班级密码 | ✅ | ❌ | ❌ |
|
||||
|
||||
完整矩阵与实现见[角色与权限](../dev/permissions.md)。
|
||||
|
||||
## 学期周数
|
||||
|
||||
每个班有自己的学期周数(`Class.termWeeks`,默认 20,上限 30),**只有管理员**能改。
|
||||
它决定"现在是第几教学周",见[课表](schedules.md)。
|
||||
角色给默认权限模板,实际入口由可编辑权限组决定;岗位组还能配置年级或部范围。
|
||||
范围不代替科目写权限,详见[角色与权限](../dev/permissions.md)。
|
||||
学期周数受全校可配置上限约束,默认上限 30、允许 1–60,见[课表](schedules.md)。
|
||||
学年升级按计划时间运行,执行前保存快照,以年份幂等;启动发现漏跑需管理员预览补跑。
|
||||
高三归档、高一/高二升一级,新高一不复制任课关系。归档页查看届别历史与转出记录,
|
||||
不再通过手动创建归档来代替年度升级。撤回转出等操作仍受归档与管理员业务检查约束。
|
||||
@@ -0,0 +1,37 @@
|
||||
# 课件上传与传输
|
||||
|
||||
教师可在 Web 管理端的「教学事务 → 课件」上传演示文稿、文档、表格、图片、音视频、压缩包等文件,单文件上限为 **512 MB**。
|
||||
|
||||
## 上传与可见范围
|
||||
|
||||
1. 选择本地文件。
|
||||
2. 选择可以查看课件的班级。候选项只包含本人担任任课教师的在读班级;仅担任班主任、级长或部长不会额外获得上传到这些班级的权限。
|
||||
3. 选择「仅保存云端」或「保存后推送所选班级」,点击上传。
|
||||
|
||||
不选择班级时,课件仅自己可见。仅保存云端并指定可见班级后,这些班级可在客户端自主下载。推送模式会先把文件完整保存到服务端,再给所选班级创建下载任务;不会从教师电脑直接传到教室电脑。
|
||||
|
||||
教师可在列表中按文件名、教师、归档日期和文件类型筛选,修改自己课件的可见范围,或再次推送。再次推送时只能选择「本人授课班级」与「该文件可见范围」的交集。管理员可查看归档列表,但上传和推送自己的文件同样遵守任课关系。
|
||||
|
||||
「查看与下载课件」和「上传、设置可见范围与推送自己的课件」为独立权限,可在权限组中分配。默认教师与管理员、内置教师与班主任岗位包含这两项权限;升级时自定义权限组需管理员按需授权。
|
||||
|
||||
## 班级客户端
|
||||
|
||||
客户端侧栏新增「课件」,仅列出本班可见的文件,显示文件名、类型、上传教师、日期与大小。点击「下载」将课件保存到本机,点击「打开文件夹」定位已下载文件。客户端不会自动运行文件。
|
||||
|
||||
收到教师推送后,客户端自动下载;离线时任务保留在服务端,上线后的心跳会补拉,每台设备独立回执。实时通道正常时会立即触发检查;断线时通过心跳继续补收。传输失败会显示原因,推送任务在后续心跳中重试,不会影响远控和在线状态上报。
|
||||
|
||||
下载期间,固定岛和自定义常驻岛都会显示文件名、文件类型、传输教师、接收字节数和进度条。下载完成后校验 SHA256,再保存正式文件并回执。断网时可以查看已缓存的课件目录与已经下载的本地文件;重新联网后同步当前可见范围。
|
||||
|
||||
修改可见范围后,被移除班级无法再获取文件,尚未完成的推送任务同步取消。已经下载到教室电脑的文件无法远程撤回,需要在本机自行删除。
|
||||
|
||||
## 服务端归档与备份
|
||||
|
||||
默认文件实体目录为 `<服务端根目录的上一级>/data/courseware/YYYY-MM-DD/<类型>/<随机编号>`,展示名称与扩展名单独保存在数据库中。可通过环境变量 `COURSEWARE_DIR` 指定文件目录。上传使用流式写入,未完成文件使用 `.part` 临时文件;失败或上传中断时清理。只有完整收到文件并完成摘要计算后才记录元数据和创建推送任务。
|
||||
|
||||
数据库迁移 `20261018000000_add_courseware` 新增课件、可见班级、推送和设备回执四张表。SQLite 安装部署按现有自动迁移流程更新;源码开发部署执行 `pnpm --filter @classhelper/server db:deploy`。MySQL 按现有换库流程基于更新后的 schema 建模。
|
||||
|
||||
数据库 JSON 备份包含这四张表,但**不包含二进制文件本身**。完整备份必须同时备份 `data/courseware`(自定义目录时备份对应目录)。SQLite Docker 的现有数据卷覆盖此目录;MySQL Docker 配置增加 `server-data:/app/data` 数据卷。复制到新机器时需同时恢复数据库和文件目录。
|
||||
|
||||
使用反向代理时,需允许课件上传大小和足够的读写超时。仓库的 Nginx 样例单独为 `/api/courseware/` 配置 512 MB 和 1800 秒超时,其他接口维持原有大小限制。
|
||||
|
||||
开发验收:`pnpm verify:courseware` 自动使用隔离数据库和临时文件目录,不读取或向真实班级发送课件。
|
||||
+33
-41
@@ -1,56 +1,48 @@
|
||||
---
|
||||
title: 成绩
|
||||
description: 成绩录入与导入、等级与得分率、ClassHelper 班级端看到的全班总览。
|
||||
description: 录入、按学号查询、排名与关闭个人查询后的集体分析。
|
||||
---
|
||||
|
||||
# 成绩
|
||||
|
||||
## 三种录入方式
|
||||
Web 管理端支持单条、批量和表格导入成绩;记录通过 Grade.studentId 关联学生名单。
|
||||
科目来自班级课程与共享科目目录,分数、总分和等级按录入规则校验。
|
||||
成绩写入需 grade:write 或相应导入权限,并通过班级和科目检查:
|
||||
管理员可管理,教师只能编辑自己任课科目,无科目内容按班主任规则;班级端不能写成绩。
|
||||
表格导入另有管理员/本班班主任门槛,班主任也只能导入符合科目权限的行;它与单条/批量录入的准入不同。
|
||||
|
||||
Web 管理端「成绩录入」页:
|
||||
## 查看、排名与隐私
|
||||
|
||||
| 方式 | 适合 | 说明 |
|
||||
| -------- | --------------------------------- | ---------------------------------------------------------------- |
|
||||
| 单条录入 | 个别缺漏补录 | 选学生、科目、考试名称、分数、总分 |
|
||||
| 批量录入 | 一次录一个班的某一科 | 一屏列出全班学生,逐个填分 |
|
||||
| 导入表格 | 已经有 Excel 的(期中期末最常见) | 支持列名同义词映射、先预览再提交,见[表格与课表导入](imports.md) |
|
||||
班级客户端可显示本班考试、成绩总览、等级分布,以及按学号查询明细。
|
||||
是否允许读取个人数据由 Class.studentGradeQueryEnabled 控制,班级端只读该设置。
|
||||
|
||||
**科目**下拉是全校统一的 18 科目录;选中一个该班还没有的科目时,前端会自动为该班建同名课程,
|
||||
不用先去把科目建一遍。
|
||||
| 状态 | 班级端可读取内容 |
|
||||
| ------------ | -------------------------------------------------------------------- |
|
||||
| 个人查询开启 | 本班原始成绩、个人明细、考试及排名,仍检查班级归属 |
|
||||
| 个人查询关闭 | 页面可打开,只返回服务端集体聚合;个人明细、原始全班成绩和排名被拒绝 |
|
||||
|
||||
## 谁能录
|
||||
聚合对少于 5 名学生的样本、稀疏分段及补集抑制,不提供最高/最低分。
|
||||
客户端不会先拿个人成绩再做隐藏或聚合;教师/管理员按自身权限读取,不受班级端个人查询开关限制。
|
||||
|
||||
| 角色 | 权限 |
|
||||
| --------------- | -------------------------------------- |
|
||||
| 管理员 | ✅ 全部班级 |
|
||||
| **本班班主任** | ✅ 仅本班(录入 / 修改 / 删除 / 导入) |
|
||||
| 科任老师 | ❌ 403 |
|
||||
| 学生 / 班级账号 | ❌ 只能看 |
|
||||
考试“是否计入总分”属于 Exam,教师在管理端设置,客户端只展示。
|
||||
同一学生同一科多条成绩取最高分,排名并列用 1、2、2、4;总分只在开启时显示。
|
||||
图表使用本地 HTML/CSS/SVG 组件。
|
||||
|
||||
权限在**服务层**兜底,前端只是按同一份矩阵隐藏按钮。
|
||||
## 接口入口
|
||||
|
||||
## 学生看到的是什么
|
||||
以下省略 /api 前缀,详情与参数以 grades.module.ts、grades.schemas.ts 为准。
|
||||
|
||||
ClassHelper 班级端(教室机器)用的是**班级账号**,所以它看到的不是"我的成绩",而是**全班成绩总览**:
|
||||
| 方法与路径 | 用途 |
|
||||
| ----------------------------------------------------- | -------------------------------------------------- |
|
||||
| GET /grades/overview | 班级端按开关返回原始总览或集体聚合 |
|
||||
| GET /grades/my | 班级端原始成绩,关闭个人查询时拒绝;员工返回空数组 |
|
||||
| GET /grades/collective?classId= | 服务端集体分析 |
|
||||
| GET /grades/student/:studentNo | 按学号查询,班级端受班级开关控制 |
|
||||
| GET /grades?classId=&courseId=&studentId=&examName= | 员工列表,功能键与范围检查 |
|
||||
| GET /grades/stats | 员工统计 |
|
||||
| GET/PUT /grades/exams | 考试列表 / 设置总分口径 |
|
||||
| GET /grades/ranking | 考试排名,班级端受个人查询开关控制 |
|
||||
| POST /grades、/grades/bulk | 单条或批量录入 |
|
||||
| PATCH /grades/levels、/grades/:id;DELETE /grades/:id | 等级与成绩维护 |
|
||||
|
||||
- 表格:分数 / **得分率** / **等级**;
|
||||
- 柱状图;
|
||||
- 等级分布;
|
||||
- 最近更新。
|
||||
|
||||
图表是自研的轻量组件(柱状图是纯 HTML/CSS,折线图是 SVG),项目里**不引图表库**。
|
||||
|
||||
> 学生没有个人账号,所以系统里不存在"某个学生登录后看自己的成绩"这条路径。
|
||||
> 成绩记录本身是按学生名单存的(`Grade.userId`),只是入口统一在班级账号上。
|
||||
|
||||
## 接口
|
||||
|
||||
| 方法 | 路径 | 权限 |
|
||||
| -------------- | ---------------------------------------------- | --------------------------- |
|
||||
| GET | `/grades/my` | 登录(班级会话 → 全班总览) |
|
||||
| GET | `/grades?classId=&courseId=&userId=&examName=` | 教师 / 管理员 |
|
||||
| GET | `/grades/stats?classId=&courseId=&examName=` | 教师 / 管理员 |
|
||||
| POST | `/grades`、`/grades/bulk` | 班主任 / 管理员 |
|
||||
| PATCH / DELETE | `/grades/:id` | 班主任 / 管理员 |
|
||||
|
||||
写入后会广播 `grade:updated`,客户端立刻刷新。
|
||||
变更发送 grade:updated,事件载荷同样须遵守个人数据保护,客户端按需重新读取。
|
||||
@@ -83,10 +83,10 @@ Web 端作业详情抽屉里同样可看可改。
|
||||
|
||||
发布 / 修改 / 删除作业会分别广播:
|
||||
|
||||
| 事件 | 客户端反应 |
|
||||
| ------------------ | ------------------------------------- |
|
||||
| `homework:new` | 作业列表刷新 + 灵动岛弹「新作业」胶囊 |
|
||||
| `homework:updated` | 刷新;带 `deleted: true` 表示删除 |
|
||||
| `homework:status` | 完成状态变化时刷新统计 |
|
||||
| 事件 | 客户端反应 |
|
||||
| ------------------ | --------------------------------------------------- |
|
||||
| `homework:new` | 列表刷新;经典岛显示胶囊/课堂暂存,自定义岛提示铃铛 |
|
||||
| `homework:updated` | 刷新;带 `deleted: true` 表示删除 |
|
||||
| `homework:status` | 完成状态变化时刷新统计 |
|
||||
|
||||
课表、作业、通知、成绩都是同一个套路:写库 → 按班级房间广播 → 客户端更新内存状态并写回 IndexedDB。
|
||||
+35
-98
@@ -1,115 +1,52 @@
|
||||
---
|
||||
title: 表格与课表导入
|
||||
description: 成绩、学生名单、教师名单的表格导入,以及从 ClassIsland 档案导入节次时间与课表。
|
||||
description: 表格预览、名单与成绩校验,以及完整离线档案导入。
|
||||
---
|
||||
|
||||
# 表格与课表导入
|
||||
|
||||
五条导入链路**都在服务端完成解析、校验与去重**,前端只负责收集文件与展示结果。
|
||||
所以 Web 端、客户端与直接调接口拿到的结果是同一套规则、同一份统计。
|
||||
解析与写入均在服务端完成,Web 收集文件、展示预览和逐行结果。
|
||||
表格支持成绩、学生、教师与班级任课关系等 kind,类型和模板以 shared/imports 模块为准。
|
||||
|
||||
## 通用流程
|
||||
## 表格流程
|
||||
|
||||
表格类导入都是三段式(成绩 / 学生 / 教师都一样):
|
||||
下载模板 → 选择 xlsx/xls/csv/tsv → 预览列名与映射 → 确认模式并提交。
|
||||
文件上限 8 MB,JSON 请求体 12 MB,超限 413 IMPORT_TOO_LARGE;结果含新增/更新/跳过/失败、errors/warnings。
|
||||
错误行号包含表头,便于在 Excel 定位。
|
||||
|
||||
1. **下载模板** —— `GET /imports/template?kind=grades&format=xlsx|csv`。
|
||||
CSV 带 BOM,Excel 直接双击打开不乱码。
|
||||
2. **上传预览** —— 服务端返回列名、前 20 行、总行数与**建议的字段映射**,以及校验问题。
|
||||
列名按同义词自动映射(「用户名→学号」「得分→分数」「满分→总分」「课程→科目」……),也可以手动改。
|
||||
**缺必填列不会静默通过**,会直接给出「缺少必填列「…」」。
|
||||
3. **确认导入** —— 按映射与写入模式提交:
|
||||
- 学生成绩按学号匹配,姓名兜底要求同班唯一;转出学生不参与匹配,重复记录和写入模式由服务校验。
|
||||
- 学生模板使用学号、姓名、班级、性别、家长电话,无密码;学号 1–32 位字母/数字/._-,在 Student 表查重。
|
||||
- 教师模板使用工号、姓名、手机号、初始密码、角色;学生/教师名单导入仍要求管理员和导入权限。
|
||||
- 成绩表格导入当前先要求管理员或本班班主任,再校验每行科目任课关系;科任老师的单条/批量成绩写入权限不代表能走表格导入。
|
||||
- 任课关系导入用于既有班级、科目和教师的对应关系,不把档案中的 TeacherName 当账号授权。
|
||||
|
||||
| 模式 | 已存在的记录 |
|
||||
| -------- | ------------ |
|
||||
| `upsert` | 更新(默认) |
|
||||
| `append` | 跳过 |
|
||||
## 完整 ClassIsland 档案
|
||||
|
||||
返回 `{ total, inserted, updated, skipped, failed, errors[], warnings[] }`。
|
||||
`errors[].row` 是 **Excel 视角的行号(含表头)**,方便老师改完再导。
|
||||
在“课表管理 → 导入 ClassIsland 课程表”选择或粘贴 JSON,先预览再导入。
|
||||
完整输入包含 TimeLayouts、ClassPlans、Subjects 字典,可包含 ClassPlanGroups 和 OrderedSchedules 等配置。
|
||||
JSON 只作为离线文件导入,不依赖在线联动插件。
|
||||
|
||||
**体积限制**:文件上限 **8MB**,请求体上限 12MB(文件以 base64 放进 JSON,约 4/3 倍)。
|
||||
超限返回 413 `IMPORT_TOO_LARGE` 与中文提示 —— 如果你看到一句英文的「request entity too large」,
|
||||
那说明这个异常没被映射,属于 bug。
|
||||
现行写入由 class-plans/profile-import.service.ts 完成:
|
||||
|
||||
## 1. 成绩表格导入
|
||||
| 项目 | 写入规则 |
|
||||
| --------- | ----------------------------------------------------------------------- |
|
||||
| 时间类型 | 0/1/2/3 对应 LESSON/BREAK/DIVIDER/ACTION,保存为关系化时间点 |
|
||||
| 课程对齐 | Classes[i] 引用第 i 个上课时间点,课间/分割线不占课位,数据库用显式外键 |
|
||||
| 星期 | 完整导入 0=不按天、1–7=周一至周日 |
|
||||
| 轮换 | 原样保留 WeekCountDiv/WeekCountDivTotal,支持 1–9 周,不降级成每周 |
|
||||
| 状态 | 保留空课、停用、临时换课标记和关联层 |
|
||||
| 群组/预定 | 保存群组及可识别的预定/临时选择 |
|
||||
| 缺失科目 | 复用该班课程;新建需该班已有班主任,教师名不授予权限 |
|
||||
| merge | 基于班级与来源 id 生成稳定 id,重复导入更新同一套对象 |
|
||||
| replace | 事务内删除该班预定和日课表后写入,保留的对象以实现为准 |
|
||||
|
||||
- 入口:成绩录入 → 「导入表格」
|
||||
- 模板列:`学生学号 / 学生姓名 / 考试名称 / 分数 / 总分 / 科目 / 等级`
|
||||
- 支持 `.xlsx / .xls / .csv / .tsv`(自动识别分隔符)
|
||||
- **重复判定**:同班级 + 同学生 + 同考试 + 同课程
|
||||
- 学生按「用户名」优先、「姓名」兜底匹配
|
||||
日课表导入需本班班主任/管理员检查。旧 /imports/class-plan/preview 是扁平预览,
|
||||
不完整展示群组/空课/停用等结构;提交直接调用完整导入,不能把预览的单双周列当作实际数据模型。
|
||||
完整导入失败时事务回滚;部分缺失引用可能返回 warnings,须检查导入结果和日课表编辑器。
|
||||
|
||||
## 2. 学生名单导入
|
||||
## 时间配置兼容入口
|
||||
|
||||
- 入口:学生管理 → 「导入名单」(仅管理员)
|
||||
- 模板列:`学号 / 姓名 / 班级 / 性别 / 家长手机号` —— 学生**不是账号**,没有密码列
|
||||
- 用户名必须匹配 `^[A-Za-z0-9_.-]{3,32}$`
|
||||
- 用户名已经被某个教师 / 管理员账号占用时,**这一行进错误列表**,不会覆盖他人账号
|
||||
旧 /imports/time-layout 接口保留,写入和删除需 timetable:manage,现行写入同步关系化时间点。
|
||||
常规使用时间表/日课表编辑器或整套档案导入;旧预览解析兼容性不代表完整导入接受所有旧输入形态。
|
||||
|
||||
## 3. 教师名单导入
|
||||
|
||||
- 入口:教师管理 → 「导入名单」(仅管理员)
|
||||
- 模板列:`工号 / 姓名 / 手机号 / 初始密码 / 角色`
|
||||
- 角色取 `ADMIN` / `TEACHER`,留空按 `TEACHER`
|
||||
- 与新建教师同一套校验,导入完可以直接登录
|
||||
|
||||
## 4. 从 ClassIsland 导入课程表
|
||||
|
||||
**这是最省事的一条路**:如果教室里已经在用 ClassIsland,直接导入它的档案 JSON,
|
||||
课表、节次时间、单双周会一起进来,不用排两遍。
|
||||
|
||||
- 入口:课表管理 → 「导入 ClassIsland 课程表」
|
||||
- 输入:同一份档案 JSON(`ClassPlans` / `TimeLayouts` / `Subjects` 三个字典),
|
||||
粘贴或选择 `.json` 文件
|
||||
|
||||
### 真实档案的形态
|
||||
|
||||
ClassIsland 2.x 的档案在 `Profiles/<档案名>.json`,字段是 PascalCase:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"TimeLayouts": {
|
||||
"<guid>": { "Name": "…", "Layouts": [{ "StartTime": "08:00:00", "EndTime": "08:45:00", "TimeType": 0 }] },
|
||||
},
|
||||
"ClassPlans": { "<guid>": { "Name": "…", "Classes": [/* 与上课时间点逐位对齐 */] } },
|
||||
"Subjects": { "<guid>": { "Name": "语文" } },
|
||||
}
|
||||
```
|
||||
|
||||
解析器同时兼容顶层数组、`{ TimeLayouts: [...] }`、`{ items | Items | Layouts | TimeLayout: [...] }`
|
||||
与单个时间表对象。
|
||||
|
||||
| 细节 | 说明 |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| 时间字段 | `StartTime` / `EndTime`(`"08:00:00"`);旧版 `StartSecond` / `EndSecond`(ISO 字符串或秒数);`"8:0"` / `"08:00"` 都认 |
|
||||
| `TimeType` | `0=上课`、`1=课间`、`2=分割线`、`3=行动`,与 ClassIsland 一致 |
|
||||
| **节次对齐** | `ClassPlan.Classes[i]` 对应 `Layouts` 里**第 i 个 `TimeType === 0` 的时间点**(课间、分割线不占位) |
|
||||
| 单双周 | `TimeRule.WeekCountDiv`(`0=每周`、`1=单周`、`2=双周`)+ `WeekCountDivTotal`(默认 2) |
|
||||
| 星期换算 | ClassIsland 是 `0=周日 … 6=周六`,导入时换算成 `1=周一 … 7=周日` |
|
||||
| 科目映射 | `Subjects[<guid>].Name`;该班还没有的科目**自动补建课程**(任课老师取该班班主任),预览里会提前列出 |
|
||||
| 写入模式 | `replace` 清空该班课表后写入(事务内完成)/ `merge` 按「星期 + 开始时间 + 单双周」去重 |
|
||||
|
||||
**解析出 `errors` 时返回 400 且数据库完全不写入**(`IMPORT_INVALID` / `IMPORT_EMPTY`)——
|
||||
先保证不写坏,再谈导入。
|
||||
|
||||
时间重叠、分割线没有时间这类问题只作 `warnings`,不阻断导入。
|
||||
|
||||
> **3 周及以上的轮换**没法用单双周表达,会给出 warning 并按"每周"处理。
|
||||
|
||||
## 5. 只导入节次时间(旧入口)
|
||||
|
||||
「导入时间配置」按钮**已经下线** —— 因为同一份档案 JSON 里的 `TimeLayouts` 已经跟着课表一起进来了。
|
||||
组件与接口(`/imports/time-layout/*`)仍然保留给旧脚本调用。
|
||||
|
||||
它的写入模式也是 `replace`(整体替换该班同名配置)与 `merge`(以**开始时间**为槽位标识合并,
|
||||
同槽位被导入项覆盖,其它旧节次保留,不静默丢数据)。
|
||||
|
||||
## 相关接口
|
||||
|
||||
| 路径 | 说明 |
|
||||
| ------------------------------------------------------ | ------------------------------------------ |
|
||||
| `/imports/template?kind=&format=` | 下载模板(`csv` 走 JSON,`xlsx` 走二进制) |
|
||||
| `/imports/table/preview` | 上传解析预览(base64) |
|
||||
| `/imports/table/commit` | 按映射与模式导入 |
|
||||
| `/imports/class-plan/preview`、`/imports/class-plan` | ClassIsland 课程表预览 / 导入 |
|
||||
| `/imports/time-layout/preview`、`/imports/time-layout` | 时间配置预览 / 导入(旧入口) |
|
||||
接口索引见[REST API](../dev/api.md),迁移边界见[当前实现约定](../dev/implementation.md)。
|
||||
+18
-17
@@ -8,30 +8,31 @@ description: ClassHelper每一项功能怎么用:课表、作业、通知、
|
||||
这一章按功能讲,重点是**行为上的约定**——什么时段会弹、谁看得见、什么情况下不动。
|
||||
很多"看起来像故障"的现象其实是设计行为,下面每一页都会点出来。
|
||||
|
||||
| 页面 | 讲什么 |
|
||||
| --------------------------------- | ------------------------------------------------------ |
|
||||
| [课表](schedules.md) | 周视图与「今天」时间轴、单双周、学期周数、课表来源 |
|
||||
| [作业](homeworks.md) | 按所属日期归类、看板与全屏看板、教室机器录入、未交名单 |
|
||||
| [通知与提醒](notifications.md) | 三档优先级、上课时段策略、二次确认、已读与红点 |
|
||||
| [叫人](calls.md) | 普通叫人与紧急叫人的差别、快捷短语、谁能叫谁 |
|
||||
| [成绩](grades.md) | 录入方式、导入、等级分布、学生看到的是什么 |
|
||||
| [班级、学生与教师](classes.md) | 班级账号、名单、班主任与各科任课老师、谁看得见什么 |
|
||||
| [灵动岛](island.md) | 胶囊与卡片、多条通知的竖排列表、停靠与外观、触摸屏 |
|
||||
| [外观、主题与动效](appearance.md) | 深浅色、设计令牌、动效语言、降低动态效果 |
|
||||
| [表格与课表导入](imports.md) | 成绩 / 名单 / 教师表格导入与 ClassIsland 档案导入 |
|
||||
| 页面 | 讲什么 |
|
||||
| --------------------------------- | ---------------------------------------------------------- |
|
||||
| [课表](schedules.md) | 周视图与「今天」时间轴、单双周、学期周数、课表来源 |
|
||||
| [课件](courseware.md) | 上传、班级可见范围、推送与逐设备接收 |
|
||||
| [作业](homeworks.md) | 按所属日期归类、看板与全屏看板、教室机器录入、未交名单 |
|
||||
| [通知与提醒](notifications.md) | 四档优先级与两种岛模式、上课时段策略、二次确认、已读与红点 |
|
||||
| [叫人](calls.md) | 普通叫人与紧急叫人的差别、快捷短语、谁能叫谁 |
|
||||
| [成绩](grades.md) | 录入方式、导入、等级分布、学生看到的是什么 |
|
||||
| [班级、学生与教师](classes.md) | 班级账号、名单、班主任与各科任课老师、谁看得见什么 |
|
||||
| [灵动岛](island.md) | 胶囊与卡片、多条通知的竖排列表、停靠与外观、触摸屏 |
|
||||
| [外观、主题与动效](appearance.md) | 深浅色、设计令牌、动效语言、降低动态效果 |
|
||||
| [表格与课表导入](imports.md) | 成绩 / 名单 / 教师表格导入与 ClassIsland 档案导入 |
|
||||
|
||||
## 贯穿所有功能的三条规则
|
||||
|
||||
**一、角色决定可见范围。** 同一个页面,管理员、班主任、科任老师看到的数据与能点的按钮都不一样。
|
||||
**一、权限与范围共同决定可见内容。** 同一个页面,管理员、班主任、科任老师看到的数据与能点的按钮都不一样。
|
||||
前端会按权限隐藏入口,但**真正拦人的是服务端**——直接调接口一样会被拒。完整矩阵在
|
||||
[角色与权限](../dev/permissions.md)。
|
||||
|
||||
**二、上课时段不打扰课堂。** 「上课时段」由课表实时判定,普通通知与普通叫人只进队列、下课补弹;
|
||||
紧急通知与紧急叫人才会立刻打断。客户端与灵动岛共用这套规则。
|
||||
**二、课堂提醒遵循本机布局规则。** 经典模式普通通知暂存到下课,普通叫人保留可点胶囊;
|
||||
紧急通知与紧急叫人立即展开;自定义常驻模式先显示铃铛,由用户主动查看。
|
||||
细节见[通知与提醒](notifications.md)。
|
||||
|
||||
**三、实时推送只走班级房间。** 老师一发内容,服务端写库后按班级房间广播,客户端立刻更新,
|
||||
不靠轮询。教室机器断线重连后会自动补上漏掉的数据,灵动岛也会把上课期间暂存的通知补弹出来。
|
||||
**三、实时广播配合恢复读取。** 老师发布后服务端按班级等房间广播,客户端刷新;断线恢复重新拉取。
|
||||
设备心跳和课件补领另有轮询机制,普通通知课堂暂存由客户端处理。
|
||||
|
||||
## 两端的差异
|
||||
|
||||
@@ -40,7 +41,7 @@ description: ClassHelper每一项功能怎么用:课表、作业、通知、
|
||||
| 事情 | Web 管理端(老师) | 桌面客户端(教室机器) |
|
||||
| ---------- | ------------------------------- | --------------------------------------------------------- |
|
||||
| 作业 | 发布 / 编辑 / 删除 / 看未交名单 | 看作业 + **录入作业** + 勾未交名单,不显示"完成 / 待完成" |
|
||||
| 成绩 | 录入 / 导入 / 改 / 删,带统计 | 只看全班总览 |
|
||||
| 成绩 | 录入 / 导入 / 改 / 删,带统计 | 按班级开关看个人数据或集体分析 |
|
||||
| 课表 | 排课、导入、改 | 只看(周视图 + 今天时间轴) |
|
||||
| 通知 | 发布 / 删除 / 看已读 | 看、标为已读 |
|
||||
| 班级与人员 | 管理员可管 | 只能看本班 |
|
||||
|
||||
+46
-101
@@ -1,121 +1,66 @@
|
||||
---
|
||||
title: 灵动岛
|
||||
description: 桌面客户端的通知浮窗:胶囊与卡片、多条通知的竖排列表、停靠与外观、触摸屏下的行为。
|
||||
description: 经典提醒、自定义常驻方案、课程与课件传输。
|
||||
---
|
||||
|
||||
# 灵动岛
|
||||
|
||||
灵动岛是桌面客户端上那个独立于主窗口的浮窗:无边框、透明、置顶、不占任务栏。
|
||||
它是「消息到达的第一现场」—— 老师发的内容先在岛上出现,学生点开才进主界面。
|
||||
灵动岛是客户端的独立透明窗口,不占任务栏。设置中可选择经典 fixed 或自定义 custom,默认 fixed。
|
||||
自定义当前即常驻模式;旧 residentEnabled 字段只作兼容,不是另一个独立开关。
|
||||
两种布局共用一个原生窗口,切布局不会新开第二个岛。
|
||||
|
||||
视觉参照 [WinIsland](https://github.com/WinIslandProject/WinIsland):
|
||||
连续圆角(超椭圆)而不是 `border-radius`、纯黑 / 毛玻璃 / 主题色三种底、Apple 系统色强调色、
|
||||
6 个停靠位、可选的「空闲细缝」。逐项设计令牌见[灵动岛设计令牌](../dev/winisland-design-tokens.md)。
|
||||
## 两种消息展示
|
||||
|
||||
## 各形态长什么样
|
||||
| 场景 | 经典 fixed | 自定义 custom |
|
||||
| ----------------- | ------------------------------ | -------------------------- |
|
||||
| 空闲 | 默认隐藏,可选细缝 | 显示常态组件行 |
|
||||
| 课间普通通知/作业 | 胶囊,点击详情 | 铃铛和未处理数量,点击查看 |
|
||||
| 课堂普通通知/作业 | 暂存,下课补弹 | 铃铛提示,允许主动查看 |
|
||||
| 普通叫人 | 课堂也保留可点胶囊,不自动展开 | 铃铛提示,主动查看 |
|
||||
| 紧急通知/紧急叫人 | 无论课堂状态立即展开 | 铃铛提示,由用户主动展开 |
|
||||
| 无消息时点击 | 按经典空闲状态处理 | 展示方案的无通知展开内容 |
|
||||
| 处理完成 | 队列清空后隐藏或细缝 | 回到常态组件行 |
|
||||
|
||||
| 形态 | 截图 | 说明 |
|
||||
| ---------------------- | ----------------------------------------------------- | ------------------------------------------------- |
|
||||
| 非上课时段的「新消息」 |  | 268×44,直接出现(无入场动画),点击后展开 |
|
||||
| 点开后的详情卡 |  | 标题 / 内容 / 时间 + 打开应用 / 标为已读 / 知道了 |
|
||||
| 下课后自动弹出 |  | 上课期间暂存的通知,下课后自动弹出 |
|
||||
| 上课期间的紧急通知 |  | 卡片**内部**红色光晕 + 紧急角标,无需点击 |
|
||||
| 新作业 |  | 青蓝描边 + 书本图标 |
|
||||
| 叫人 |  | 琥珀金卡片 + 「叫人」徽标 + 「收到」按钮 |
|
||||
多条消息按 id 去重,按优先级和时间排序;详情显示列表与“展开更多”,超出工作区提示到应用中操作。
|
||||
“知道了/标为已读”按当前列表语义处理;本机刚录的作业通过指纹避免重复提醒自己。
|
||||
课堂发布紧急通知的二次确认是服务端规则,见[通知](notifications.md),不因本机布局改变。
|
||||
|
||||
## 什么时候弹、弹成什么样
|
||||
经典布局的历史展示图(供外观参考,具体行为以上表和当前设置为准):
|
||||
|
||||
| 场景 | 灵动岛行为 |
|
||||
| --------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| 收到通知(非上课时段) | 直接出现胶囊:`新消息 · 共 N 条` + 发送人 + `点击查看` |
|
||||
| 多条未处理消息 | 胶囊标题汇总类型:`新消息:叫人/作业/通知(共 3 条)`,右侧带类型小标签 |
|
||||
| 点击胶囊(只有一条) | 展开为**详情卡**:标题、内容、时间 + 三个按钮 |
|
||||
| **点击胶囊(多条)** | 展开为**竖排列表**,按重要程度排序(紧急 > 重要 > 普通 > 低,同级按时间新的在前) |
|
||||
| **点「展开更多」** | 把放得下的都铺出来;铺到工作区下沿还放不下时改成「更多请前往应用内操作」 |
|
||||
| **多条时的「知道了 / 标为已读」** | 都是**整批**操作 |
|
||||
| 上课时段的**普通叫人** | **不自动展开**(不打断课堂),但保留胶囊并**允许学生主动点开**;下课自动展开 |
|
||||
| **紧急叫人** | 上课时段也立即展开,卡片是琥珀金 + 「叫人」徽标,按钮为「收到」 |
|
||||
| 上课时段的普通通知 | **完全不显示**(窗口隐藏)并进队列;下课自动弹出详情 → 收起为胶囊 |
|
||||
| 上课时段的紧急通知 | **无论是否上课立刻展开**,45 秒后收起 |
|
||||
| 点击卡片空白处 / 屏幕任意位置 | 回缩为**胶囊**(不直接消失,仍可再次点开) |
|
||||
| **收起后胶囊常驻** | 只要还有未处理通知,胶囊就不会消失;彻底消失只在「知道了 / 标为已读」、队列清空或进入上课时段 |
|
||||
| 场景 | 展示图 |
|
||||
| -------- | --------------------------------------- |
|
||||
| 经典胶囊 |  |
|
||||
| 展开详情 |  |
|
||||
| 紧急通知 |  |
|
||||
|
||||
> **本机刚录入的作业不会上岛** —— 自己通知自己没意义。
|
||||
## 编辑自定义方案
|
||||
|
||||
## 多条通知时的排版
|
||||
“设置 → 灵动岛 → 自定义 → 编辑岛内组件”进入主窗口设置页。
|
||||
方案支持复制、删除、JSON 导入导出;组件支持日期、时钟、倒计时、课程、文本、分隔符、通知/作业/叫人及组合容器。
|
||||
分别编辑常态 rows 与无消息展开 expandedRows,拖动调整结构,设置内容、外观、尺寸与隐藏条件。
|
||||
方案保存到本机 config.json.islandLayout,不增加服务端同步。
|
||||
编辑预览不创建额外桌面窗口;打开编辑页不自动切布局,应用后启用自定义方案。
|
||||
保存按序进行,失败可重试,主题与关怀模式沿用客户端设置。
|
||||
|
||||
这是最容易出问题的地方:卡片要长高,还得刚好放得下。规则是**主进程与渲染进程各算一次同一个数**
|
||||
(`islandListLayout()`,在 `@classhelper/shared` 里):
|
||||
## 外观、位置与输入
|
||||
|
||||
- 默认只显示前 `defaultVisibleRows`(3)条,下面是「展开更多(还有 N 条)」;
|
||||
- 点开后铺到 `maxCardHeight` 为止,仍放不下的改成「更多请前往应用内操作」;
|
||||
- `maxCardHeight` 是主进程按工作区 / 任务栏算出的**可用高度**,随状态下发。
|
||||
经典模式可调宽高、字号、主题色、黑色/半透明/渐变、停靠与边距;自定义内容根据实际测量适配工作区。
|
||||
自定义方案可配置缩放、圆角、颜色和行间距,默认值与范围在 shared/island-layout.ts。
|
||||
左右边距只在左/右停靠时生效,居中锚点仍在屏幕中线。
|
||||
触摸屏自动进入触摸模式,保留触摸命中和失焦宽限;鼠标模式按光标控制输入穿透。
|
||||
自定义常驻内容支持鼠标避让淡化及反转,交互入口、展开/编辑和触摸操作按实现暂停避让。
|
||||
应用内动画开关决定岛动画,不能用主窗口的减少动态效果说明替代岛自身规则。
|
||||
|
||||
窗口的包围盒会**在卡片形变之前**先长高(这一步用户看不到),否则底部的按钮会被裁掉。
|
||||
经典几何与配色参考见[设计令牌](../dev/winisland-design-tokens.md),实现入口见[当前实现约定](../dev/implementation.md)。
|
||||
|
||||
## 外观设置
|
||||
## 课程与课件
|
||||
|
||||
客户端「设置 → 灵动岛」:
|
||||
准备、上课、下课、下课前倒数和课中进度等提醒在本机配置独立开关,课程依据服务端解析后的课表。
|
||||
课件接收时显示文件名、类型、教师、字节和进度,覆盖当前固定/自定义内容;完成后校验 SHA256 并回执。
|
||||
客户端不会自动执行收到的文件,见[课件](courseware.md)。
|
||||
|
||||
| 设置项 | 区间 / 取值 | 默认 |
|
||||
| --------------- | -------------------------------------- | ------------------------- |
|
||||
| 高度 | 36–72px | 44 |
|
||||
| 宽度 | 220–420px | 268 |
|
||||
| 圆角 | 8–32px | 20 |
|
||||
| 不透明度 | 40%–100% | 100% |
|
||||
| **字号** | 11–20px | 13 |
|
||||
| 动画速度 | 0.5×–2× | 1× |
|
||||
| 主题色 | 取色器 | `#0a84ff`(Apple 系统蓝) |
|
||||
| **视觉风格** | 纯黑 / 毛玻璃 / 主题色渐变 | 纯黑 |
|
||||
| **显示位置** | 顶部 / 底部 × 左 / 中 / 右(6 个锚点) | 顶部居中 |
|
||||
| 左右 / 上下边距 | 0–200px / 0–160px | 都是 8px |
|
||||
| 跟随鼠标屏幕 | 开 / 关 | 关 |
|
||||
| 动画开关 | 开 / 关 | 开 |
|
||||
| 始终置顶 | 开 / 关 | 开 |
|
||||
| **空闲细缝** | 开 / 关 | 关 |
|
||||
## 验证与排查
|
||||
|
||||
几个容易困惑的点:
|
||||
|
||||
- **字号是"基础字号"**:岛内所有文本 = 基础字号 × 各自的排版系数(标题 1.08 / 正文 0.95 / 次要 0.78 /
|
||||
徽标 0.76 / 胶囊标题 0.92 ……),所以调一个滑块就能整体放大,比例不会乱。
|
||||
- **左右边距只在「左 / 右停靠」时生效**:默认是「顶部居中」,水平锚点就是屏幕中线,
|
||||
这时调左右边距不会有任何变化 —— 设置页会把那个滑块置灰并写明原因。
|
||||
- **「毛玻璃」不是窗口材质**,是 CSS 半透明深色卡。用系统级背景材质(`setBackgroundMaterial`)
|
||||
会在卡片外那一圈垫出一块浅色"白底面板",而且**调用过一次就再也回不去逐像素透明**。
|
||||
- **「预览效果」**会本地推一条通知,立刻看到当前尺寸 / 配色 / 动画。预览是专用通道:
|
||||
直接展开、失焦不收起、30 秒后自行消失。
|
||||
|
||||
越界的值在**主进程**统一夹紧,配置填坏了也不会让布局错乱。
|
||||
|
||||
## 触摸屏(希沃白板等)
|
||||
|
||||
触摸屏上灵动岛走的是**另一条命中链路**。
|
||||
|
||||
原因:常规的命中判定有两条路径 ——渲染进程转发鼠标移动、主进程读系统光标位置 ——
|
||||
**两条都以"鼠标指针移动"为前提**。而手指触摸既不产生 `mousemove`、也不移动系统光标,
|
||||
窗口就会永远停在"穿透"状态,**手指点不开岛**。
|
||||
|
||||
修法是:客户端检测到 `navigator.maxTouchPoints > 0` 后进入触摸模式,
|
||||
把窗口**贴合岛体**并**始终接收输入**(Windows 上 Electron 没有窗口区域能力,这是唯一可行的办法)。
|
||||
|
||||
触摸模式下有三条额外的护栏,都是为了不闪、不裁:
|
||||
|
||||
- **窗口可见期间只缩不放**(需要变小一律等窗口隐藏之后再做);
|
||||
- 命中矩形按**窗口实际尺寸**算偏移;
|
||||
- 失焦宽限期放宽到 1200ms(触摸没有光标,"假失焦"会让刚展开的岛被缩回胶囊)。
|
||||
|
||||
非触摸屏的机器**完全不走这条路**。
|
||||
|
||||
## 鼠标穿透
|
||||
|
||||
窗口是按最大形态创建的固定包围盒,比卡片大一圈。默认整块穿透,渲染层做命中测试,
|
||||
只在指针进入岛体时打开命中 —— 所以这个"大窗口"不会吞掉桌面上的点击。
|
||||
另有一层保险:主进程每 120ms 用系统光标位置校正一次命中。
|
||||
|
||||
## 排查"闪一下"
|
||||
|
||||
- **开合时底座闪一下**:卡片投影是常驻的 CSS `filter`,形态切换只改参数。
|
||||
给元素增删 `filter` 会让 Chromium 重建渲染表面,首帧可能被当成空内容画出去。
|
||||
- **收回一瞬间闪一下**:多半是**自动化验证实例的叠影**,不是产品的问题 ——
|
||||
冒烟会真的在屏幕上放一个灵动岛,如果它和你自己的客户端叠在一起,你看到的是两个窗口。
|
||||
(验证实例的岛是洋红主题色,一眼可辨。)
|
||||
默认 verify:desktop/verify:packaged 是后台快速检查,不显示岛或托盘。
|
||||
只有显式 --visual 或 verify:desktop:visual 才进行桌面像素、透明度、动画和截图验收。
|
||||
若出现多岛,先确认是否存在用户实例与显式视觉验证实例;不要把默认后台冒烟解释成可见验证岛。
|
||||
+32
-58
@@ -1,76 +1,50 @@
|
||||
---
|
||||
title: 通知与提醒
|
||||
description: 通知的三档优先级、上课时段策略、紧急通知的二次确认、提醒弹在哪个端、已读与红点。
|
||||
description: 优先级、课堂确认与经典/自定义常驻模式的提醒区别。
|
||||
---
|
||||
|
||||
# 通知与提醒
|
||||
|
||||
通知是这套系统里规则最多的一项 —— 因为它要在"老师想说"和"别打断上课"之间做取舍。
|
||||
这一页把规则一次讲清楚。
|
||||
Web 发布通知需 notification:publish 和班级范围权限。通知按本地 assignDate 归档,支持按日期、发布人查看与批量发布。
|
||||
服务端保存后立即广播;上课暂存与是否自动展开是客户端本机展示规则。
|
||||
|
||||
## 三档优先级
|
||||
## 优先级与本机展示
|
||||
|
||||
| 优先级 | ClassHelper 班级端表现 |
|
||||
| ------ | -------------------------------------------------------------------------------------------- |
|
||||
| 普通 | 课间弹「新消息」胶囊,点击展开详情;**上课时段完全不显示**(窗口隐藏)并进队列,下课自动补弹 |
|
||||
| 重要 | 同上,卡片上有优先级标签 |
|
||||
| 紧急 | **无论是否上课都立即展开**,卡片内部红色呼吸光晕 + 「紧急」角标,无需点击,45 秒后收起 |
|
||||
共享优先级为 LOW(低)、NORMAL(普通)、HIGH(重要)、URGENT(紧急)。
|
||||
经典固定模式和自定义常驻模式有不同展示:
|
||||
|
||||
紧急通知的光晕画在**卡片内部**(`inset` 阴影),因为窗口比卡片只大 2~4px,向外的阴影会被裁成硬边。
|
||||
| 场景 | 经典 fixed | 自定义 custom |
|
||||
| ---------------------- | ------------------------------------------ | -------------------------------------- |
|
||||
| 普通/重要/低通知、作业 | 课间显示胶囊;上课暂存,下课补弹 | 常驻内容旁提示铃铛与去重数量,点击查看 |
|
||||
| 紧急通知/紧急叫人 | 无论是否上课立即展开 | 提示铃铛,由用户主动展开 |
|
||||
| 普通叫人 | 上课保留可点击胶囊,不自动展开;课间可展开 | 提示铃铛,可主动查看 |
|
||||
| 主动查看 | 普通通知受课堂状态限制,叫人和紧急可点开 | 上课也可主动展开 |
|
||||
|
||||
## 上课时段策略
|
||||
经典紧急通知默认约 45 秒收起;列表与消息处理见[灵动岛](island.md)。课件传输另显示专用进度。
|
||||
|
||||
「上课时段」由课表实时判定(见[课表](schedules.md))。判定之后:
|
||||
## 课堂发布紧急通知
|
||||
|
||||
| ClassHelper 班级端行为 | 上课时段 | 课间 / 放学 |
|
||||
| ---------------------- | ---------------------------------------- | ----------- |
|
||||
| 普通通知 | 收着不弹(窗口隐藏),进待发队列 | 直接弹 |
|
||||
| 紧急通知 | **立刻弹** | 立刻弹 |
|
||||
| 任何点击 | 一律不响应(不会展开、也不会回缩出胶囊) | 正常 |
|
||||
上课时发布 URGENT,服务端先返回 409 URGENT_DURING_CLASS。
|
||||
Web 显示全屏确认和倒计时,再次请求带 confirmDuringClass:true 才放行。
|
||||
这是服务端发布校验,不受客户端选择 fixed/custom 影响;普通叫人和紧急叫人走[叫人](calls.md)的接口规则。
|
||||
|
||||
## 紧急通知的二次确认
|
||||
## 已读与撤回
|
||||
|
||||
上课时段发紧急通知,**服务端会拦一次**(`409 URGENT_DURING_CLASS`):
|
||||
客户端通知页有未读数,打开详情可标已读;班级会话的已读操作代表全班在读学生。
|
||||
删除需相应员工权限和范围,实时撤回会清理客户端列表、缓存与岛队列。
|
||||
同 id 的通知与叫人双播合并,不重复计数。
|
||||
|
||||
```
|
||||
POST /api/notifications { classId, title, content, priority: "URGENT" }
|
||||
→ 409 URGENT_DURING_CLASS(响应体里带着正在上的那节课)
|
||||
## 接口入口
|
||||
|
||||
POST /api/notifications { …, priority: "URGENT", confirmDuringClass: true }
|
||||
→ 201 发出去
|
||||
```
|
||||
以下省略 /api,均先鉴权;具体功能键及范围见[REST API](../dev/api.md)。
|
||||
|
||||
Web 端在提交前会先查一次上课状态,命中就弹一个**全屏二次确认**:全屏遮罩 + 强警示配色,
|
||||
文案是"现在为上课时间段……" + 正在上的课时段 + 待发布的标题。确认按钮带 **3 秒倒计时**
|
||||
(圆环进度),倒计时结束前点不动,防误触。
|
||||
| 方法与路径 | 用途 |
|
||||
| ----------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| GET /notifications | 列表;classId、priority、unreadOnly、keyword、date/from/to、createdBy |
|
||||
| GET /notifications/days、/notifications/creators | 日期与发布人归档 |
|
||||
| GET /notifications/unread-count | 未读数 |
|
||||
| POST /notifications、/notifications/bulk | 单班/批量发布,批量逐班返回结果 |
|
||||
| POST /notifications/:id/read、/notifications/read-all | 标记已读 |
|
||||
| DELETE /notifications/:id | 撤回 |
|
||||
|
||||
**这个拦截在服务端,不是前端提醒。** 绕过界面直接调接口一样会被拦 —— 这是产品行为,不是可以关掉的提示。
|
||||
|
||||
## 提醒显示
|
||||
|
||||
通知通过实时通道发送到 ClassHelper 客户端与灵动岛。设置页显示本机提醒规则,旧的跨应用通知渠道已移除。
|
||||
|
||||
## 已读、红点与删除
|
||||
|
||||
| 能力 | 说明 |
|
||||
| -------- | ------------------------------------------------------------------------------ |
|
||||
| 未读红点 | 客户端侧边栏「通知」项右上角的徽标;数字来自 `GET /notifications/unread-count` |
|
||||
| 标为已读 | 点开通知自动标为已读;也有「全部已读」 |
|
||||
| 班级设备 | 教室机器的"已读"是**代全班**操作的(它用的是班级账号) |
|
||||
| 删除 | 只有教师 / 管理员能删 |
|
||||
|
||||
## 发布入口只有一个实现
|
||||
|
||||
「通知发布」与叫人复用同一套服务端发布流程和实时广播。
|
||||
|
||||
## 接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| ------ | -------------------------------------------------------- | ------------------------------- |
|
||||
| GET | `/notifications?classId=&priority=&unreadOnly=&keyword=` | 列表(带已读状态) |
|
||||
| GET | `/notifications/unread-count` | 未读数 |
|
||||
| POST | `/notifications` | 发布(广播 `notification:new`) |
|
||||
| POST | `/notifications/:id/read`、`/notifications/read-all` | 标记已读 |
|
||||
| DELETE | `/notifications/:id` | 删除 |
|
||||
|
||||
完整清单见 [REST API](../dev/api.md)。
|
||||
ClassIsland 在线提醒渠道已移除;本机提醒设置不会写回旧渠道字段。
|
||||
+24
-55
@@ -1,70 +1,39 @@
|
||||
---
|
||||
title: 课表
|
||||
description: 周课表与「今天」时间轴、单双周、学期周数、课表来源,以及上课时段是怎么判定的。
|
||||
description: 日课表、共享作息、轮换、临时换课和教学周。
|
||||
---
|
||||
|
||||
# 课表
|
||||
|
||||
课表不只是"给学生们看看"的表格 —— **「上课时段」的判定完全基于它**。
|
||||
课表排错,提醒就会在该弹的时候不弹。所以这一页值得读完。
|
||||
Web 管理端可查看周课表并编辑时间表与日课表,客户端提供“今天”时间轴和“本周”视图。
|
||||
上课状态、灵动岛课程提醒和紧急通知确认共用服务端的课表判定。
|
||||
|
||||
## 两个视图
|
||||
## 日课表与时间表
|
||||
|
||||
| 视图 | 在哪 | 内容 |
|
||||
| -------------- | ----------------------------------------- | -------------------------------------------------------- |
|
||||
| **周视图** | Web 端「课表管理」、客户端「课表 → 本周」 | 节次 × 星期 的 7 列表格 |
|
||||
| **今天时间轴** | 客户端「课表 → 今天」 | 大时钟 + 当前状态卡 + 时间轴(已结束 / 正在上 / 下一节) |
|
||||
时间表保存时间点:上课、课间、分割线、行动;日课表引用时间表,每节课程通过外键关联时间点。
|
||||
可以维护多套班级作息或共享作息、复制日课表、调整课程、配置启用规则和群组。
|
||||
时间点排序不会改变课程关联。行动配置当前保存数据,不执行外部动作。
|
||||
|
||||
「今天」时间轴是客户端默认打开的那一屏:
|
||||
日课表按星期、周次范围和轮换规则启用,支持 1–9 周轮换;单双周是 2 周轮换的特例,第 1 教学周为单周。
|
||||
可以预定某天使用某张表,临时换课/叠加层保留原表。相同日期的最终启用选择由服务端唯一解析器完成。
|
||||
|
||||
- **大时钟**:时:分(大字)+ 秒(小字),下面一行是日期 / 星期 / 第 N 教学周。
|
||||
- **当前状态卡**:「正在上 XXX」+ 时段地点 + **距下课 X 分钟**;没课时显示"今天的课都上完了"或"今天没有课程安排"。
|
||||
- **时间轴**:左侧时间栏(开始 / 结束)+ 竖向轨道与节点 + 右侧课卡。已结束的置灰、
|
||||
正在上的用强调色实底 + 进度条 + 距下课倒计时、下一节用橙色标记 + "还有多久开始"。
|
||||
## 教学周
|
||||
|
||||
## 单双周
|
||||
班级有自己的学期周数;全校上限由“学期周次”设置,默认 30、可设 1–60,班级不能超过该上限。
|
||||
默认班级周数为 20。管理员可在界面修改全校开学日和逐周起止区间,班级可有独立设置。
|
||||
计算优先使用班级逐周区间、再全校区间;无适用区间时按开学日线性计算。
|
||||
开学日解析还会参考第 1 周起点,环境变量 TERM_START_DATE 是兜底,具体顺序见 `lib/term.ts`。
|
||||
|
||||
一门课可以设成**每周**(默认)、**单周**或**双周**。判定口径是 **第 1 周 = 单周**。
|
||||
## 上课状态与提醒
|
||||
|
||||
从 ClassIsland 导入时,`WeekCountDiv`(`0=每周 / 1=单周 / 2=双周`)会换算成这套 `ALL / ODD / EVEN`。
|
||||
**3 周及以上的轮换没法用单双周表达**,导入时会给出 warning 并按"每周"处理 ——
|
||||
如果学校里真的是三周轮换,目前只能用两份课表 + 手工切换的方式绕过。
|
||||
`GET /api/schedules/current?classId=<id>&at=<ISO时间>` 返回 inClass/current/next/week/serverTime 等状态;at 用于诊断。
|
||||
`/schedules` 是新模型的兼容门面,旧 Schedule 只保留迁移审计记录。
|
||||
经典岛普通通知上课暂存、下课补弹;普通叫人保留可点击胶囊,紧急消息立即展开。
|
||||
自定义常驻岛先显示铃铛,由用户主动查看。紧急通知在课堂发布仍需服务端二次确认。
|
||||
|
||||
## 学期周数
|
||||
## 权限与导入
|
||||
|
||||
每个班有自己的**学期周数**(`Class.termWeeks`,默认 20,**上限 30**),只有管理员能改。
|
||||
|
||||
它决定三件事:课表的周次下拉能选到第几周、`weekEnd` 的默认值、以及"现在是第几教学周"。
|
||||
周次换算的起点是服务端 `.env` 里的 `TERM_START_DATE`(当学期第 1 教学周的周一)。
|
||||
|
||||
> **`TERM_START_DATE` 设错,整套周次都错。** 它不在界面上改,要改 `.env` 后重启服务端。
|
||||
> 上限三处必须一致(`MAX_TERM_WEEK`、周次校验、班级设置),曾经出现过"班级能设到 40 周、
|
||||
> 而课表只到 30 周"的错位。
|
||||
|
||||
## 上课时段是怎么判定的
|
||||
|
||||
服务端有一个接口专门回答"这个班现在是不是在上课":
|
||||
|
||||
```
|
||||
GET /api/schedules/current?classId=<班级 id>[&at=<ISO 时间>]
|
||||
```
|
||||
|
||||
返回 `{ inClass, current, next, week, serverTime }`。`at` 参数可以覆盖判定时刻,用来联调与测试。
|
||||
|
||||
判定结果被用在三处,规则完全一致:
|
||||
|
||||
1. 客户端灵动岛:上课时段普通通知**不显示**(窗口直接隐藏)并进队列,下课自动补弹;
|
||||
2. 服务端发布接口:上课时段发紧急通知会被拦(`409 URGENT_DURING_CLASS`),确认后放行;
|
||||
3. Web 端:提交紧急通知前先查一次状态,命中就弹全屏二次确认。
|
||||
|
||||
## 课表来源
|
||||
|
||||
课表由管理端维护,支持手工录入与离线 JSON 导入。升级会保留旧联动课表并将其来源归为 `manual`。
|
||||
|
||||
## 排课 / 改课 / 导入
|
||||
|
||||
- 手工排课在 **课表管理**(管理员 / 本班班主任)。科目下拉是全校统一的 18 科目录,
|
||||
选中一个该班还没有的科目会自动建课。
|
||||
- 从 ClassIsland 档案导入最省事,课表、节次时间、单双周会一起进来,
|
||||
见[表格与课表导入](imports.md)。
|
||||
- 课表变更后会广播 `schedule:updated`,客户端与灵动岛立刻刷新。
|
||||
日课表写入需 schedule:write,并通过管理员或本班班主任检查;时间表写入需 timetable:manage。
|
||||
离线导入完整 ClassIsland 档案可创建班级自有作息,不依赖在线联动插件。
|
||||
完整导入保留 1–9 周轮换、空课、停用、群组和预定;旧预览界面的单双周展示不代表新模型只能表达双周。
|
||||
见[表格与课表导入](imports.md)。修改后广播 schedule:updated,客户端刷新。
|
||||
@@ -4,6 +4,8 @@
|
||||
href: schedules.md
|
||||
- name: 作业
|
||||
href: homeworks.md
|
||||
- name: 课件上传与传输
|
||||
href: courseware.md
|
||||
- name: 通知与提醒
|
||||
href: notifications.md
|
||||
- name: 叫人
|
||||
|
||||
Reference in new issue
Block a user