diff --git a/AGENTS.md b/AGENTS.md index 3dd95b4..f060e7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# AGENTS.md — 班级小助手(Class Helper) +# AGENTS.md — ClassHelper(Class Helper) 本文件写给在本仓库里干活的 AI 编码代理。先读这里,再动代码。 文中「本机环境」「已知坑」两节按**实测**写成;若与本机现状不符,以本机实测为准并顺手改掉这里。 @@ -28,7 +28,7 @@ ▼ Socket.IO ──► class:{classId} 房间 ──► 学生桌面客户端实时更新 UI -ClassIsland(教室机器)──► 班级小助手联动插件 ──► /api/integrations/classisland/*(设备令牌鉴权) +ClassIsland(教室机器)──► ClassHelper联动插件 ──► /api/integrations/classisland/*(设备令牌鉴权) ▲ │ └──── 老师下发的提醒(上报返回值里顺带带回)◄┘ ``` @@ -73,18 +73,24 @@ docs/ **文档站**(docfx 项目;不进 pnpm workspac docfx.json / toc.yml(顶栏)+ 各章节自己的 toc.yml(侧栏) get-started/ app/ management/ dev/(四章内容) management/{production,linux-deploy,mysql}.md(部署与换库) - dev/winisland-design-tokens.md / screenshots/(回归留档的截图) + dev/winisland-design-tokens.md + images/(**展示图**:文档站 / README / 官网共用的同一份,1× 逻辑像素) + screenshots/(回归留档的截图:island / client / web-mobile / classisland, + 页面不展示;两者分工见 docs/dev/testing.md「截图留档」) templates/classhelper/(模板覆盖:_master.tmpl + public/main.css + 图标) build.mjs(构建,注入版本号与 404 的 base)/ serve.mjs(本地预览 :5181) _site/(构建产物,gitignored) ⚠ 站点的顶栏版本徽标取自根 package.json(build.mjs 用 --metadata 注入), 不要在文档里再写一份版本号。 + ⚠ 页面里的照片一律指向 images/ —— 它就是 README 与官网吃的那一份。 + 别再往 screenshots/ 引图:那是冒烟脚本滚动的留档,随时被下一次验证覆盖。 ⚠ 模板目录里的 logo.png / favicon.png 由 `pnpm icons` 派生(见 §5 第 62 条), 别手工替换;改图标只改 build/classhelper.png 再跑 pnpm icons。 ⚠ 文档站只发布 docs/ 这一棵树,页面里**不要**写指向 docs/ 之外的相对链接 (如 ../AGENTS.md)—— 发布出去全是 404,要用 GitHub 的绝对 URL。 website/ 产品官网(**纯静态、无构建步骤**,不进 pnpm workspace) - index.html / assets/{styles.css,motion.js,main.js,icon.png,shots/} + index.html(门面)/ download.html(下载页,首页的「立即下载」跳到它) + assets/{styles.css,motion.js,main.js,download.js,icon.png,shots/} serve.mjs(本地预览)/ tools/capture-shots.mjs(实机图采集) 404.html(**站点根** 404,含 {{BASE}} 占位符,由 build-pages.mjs 注入) ⚠ serve.mjs 把 ../docs/_site 挂在 /docs/ 下,为的是让本地与线上同形 —— @@ -93,11 +99,19 @@ website/ 产品官网(**纯静态、无构建步骤**, (main.js 会跳过它)。拆成 per-char 的 inline-block 后每个字各自建一层合成, 祖先的 background-clip: text 就穿不过去,渐变会整行失效。 ⚠ assets/motion.js 是动效层:曲线与弹簧 token 逐条取自 beUI - (beui.dev/components/motion,见 README §7「官网」)。 + (beui.dev/components/motion,见 docs/reference.md 的「官网」一节)。 改官网动效请改 token 表,不要在组件里另写 duration / cubic-bezier。 - ⚠ assets/shots/ 里全是**跑起来截的**实机图,重采集步骤见 README - §7「采集实机图」;客户端那两张要配合冒烟的 + ⚠ assets/shots/ 里全是**跑起来截的**实机图,重采集步骤见 + docs/dev/testing.md 的「截图留档」;客户端那两张要配合冒烟的 ELECTRON_CLIENT_SHOTS_DIR / ELECTRON_SMOKE_ISLAND_* 开关。 + ⚠ download.html 的版本清单是**当场**读 GitHub Releases 的 + (assets/download.js)—— 渲染出的卡片与文件行都是拉到数据之后才建的, + 建完会派发一次 ch:content,由 main.js 补挂按压反馈(新增动态内容照这个来)。 + 读不到时退回 并按发布命名约定拼直链 —— + 那个 meta 是 version.mjs 的落点之一,发版必须一起改。 + 命名约定与 README「开始使用」表里那三个名字要对齐 + (ClassHelper-<版本>-x64-client-setup.exe / -client-portable.exe / + -x64-server-setup.exe),它们是离线兜底链接的唯一依据。 packages/shared/src/ types.ts / constants.ts / permissions.ts / utils.ts / index.ts packages/server/ prisma/schema.prisma 数据模型(14 个 model,无 enum、无 @db.*;连接串在 prisma.config.ts) @@ -122,7 +136,7 @@ packages/desktop-client/ src/main/ 主进程:index / config(含作业短语与看板偏好)/ ipc / island / tray / update / logger / smoke src/preload/ contextBridge 白名单桥(index.ts + island.ts,**不暴露 ipcRenderer 本体**) src/island/ 灵动岛渲染进程:IslandApp.vue / spring.ts / squircle.ts / main.ts - src/renderer/ 学生端渲染进程:api / cache(IndexedDB) / stores / views / island / router + src/renderer/ ClassHelper 班级端渲染进程:api / cache(IndexedDB) / stores / views / island / router src/types/desktop.d.ts 主进程 ↔ 渲染进程契约 scripts/ build-main / dev / smoke / dist-win packages/classisland-plugin/ .NET 8 插件(独立于 pnpm workspace,不参与 pnpm install) @@ -187,12 +201,12 @@ pnpm build:classisland-plugin | -------------- | ------------------------------- | ------------ | | 管理员 | `admin` | `admin123` | | 教师 | `teacher1` / `teacher2` | `teacher123` | -| 学生端班级账号 | 班级码 `G101` / `G102` / `G203` | `123456` | +| ClassHelper 班级端班级账号 | 班级码 `G101` / `G102` / `G203` | `123456` | > 密码是**种子脚本里硬编码**的(`admin123` / `teacher123` / 班级 `123456`); > `.env` 的 `DEFAULT_CLASS_PASSWORD` 只影响**新建**班级时的初始班级密码。 > **学生没有个人账号**(2026-10-01 清理):`student01…15` 只是名单记录(成绩/未交/叫人/已读按名单), -> `User.passwordHash` 为空串、不能登录,也没有任何密码接口;学生端统一用班级码 + 班级密码。 +> `User.passwordHash` 为空串、不能登录,也没有任何密码接口;ClassHelper 班级端统一用班级码 + 班级密码。 > 班级码 `G203` 是「高二(3)班」,不是 `G103`。 --- @@ -228,8 +242,8 @@ pnpm build:classisland-plugin | `pnpm dist:server` / `dist:win` / `dist:dir` / `dist:all` | 打包服务端安装程序 / 客户端安装包 / 免安装目录 / 全部 | | `pnpm dist:server:linux` | 打包 **Linux 服务端安装包**(`classhelper-server-linux-x64-<版本>.tar.gz` + `.sha256`)—— 见 §5 第 59 条 | | `pnpm icons` | 从 `build/classhelper.png` 派生全部图标(PWA / favicon / 托盘 / 安装包 / 插件 / 官网 / 文档站 / 界面内品牌标) | -| `pnpm version:check` | 逐处列出并比对 13 个落点的版本号,不一致即 exit 1(见 §4.4「版本迭代」) | -| `pnpm version:bump patch\|minor\|major` | **改版本号的唯一入口**:一次改写全部 13 处(含插件清单、C# 里的常量,以及官网上手写的三处版本号) | +| `pnpm version:check` | 逐处列出并比对 14 个落点的版本号,不一致即 exit 1(见 §4.4「版本迭代」) | +| `pnpm version:bump patch\|minor\|major` | **改版本号的唯一入口**:一次改写全部 14 处(含插件清单、C# 里的常量、官网上手写的三处,以及下载页的兜底值) | | `pnpm docs:build` | 构建文档站到 `docs/_site`(需要 .NET SDK 8 + `dotnet tool install -g docfx`) | | `pnpm docs:serve` | 构建文档站并起本地预览 → http://127.0.0.1:5181 | | `pnpm docs:check` | 构建文档站并把 docfx 的警告当错误(发布前用;最常见的警告是"某个 .md 没被任何 toc.yml 引用") | @@ -254,11 +268,11 @@ pnpm build:classisland-plugin ``` releases/ client/<版本>/ - 安装包/ 班级小助手-<版本>-x64-setup.exe / -portable.exe(+ .blockmap) + 安装包/ ClassHelper-<版本>-x64-setup.exe / -portable.exe(+ .blockmap) 免安装/ electron-builder 的 win-unpacked 内容 构建中间/ builder-*.yml、*.nsis.7z server/<版本>/ - 安装包/ 班级小助手服务端-<版本>-x64-setup.exe + 安装包/ ClassHelper服务端-<版本>-x64-setup.exe 免安装/ classhelper-server 免安装目录(含 .env、node.exe、data/、logs/) linux-x64/ classhelper-server-linux-x64-<版本>.tar.gz + .sha256 + 同内容解包目录 构建中间/ server-installer.generated.nsi @@ -296,9 +310,11 @@ pnpm dist:classisland-plugin > 算一次发布:攒够一批功能 / 修好一个用户能感知的 bug / 要给别人一份新安装包。 > 不算:改注释、改文档、重构、加测试。 -**同一个版本号写在 13 处(11 个文件)里**(5 份 `package.json`、`deploy/package.runtime.json`、 -两个 `docker-compose*.yml` 的镜像 tag、插件 `manifest.yml`、以及插件 C# 里的 `PluginVersion` 常量)。 -手工改必然漏一处,而漏掉的那处**不会报错** —— 只会在某个不常走的分支上表现为"版本号对不上"。 +**同一个版本号写在 14 处(12 个文件)里**(5 份 `package.json`、`deploy/package.runtime.json`、 +两个 `docker-compose*.yml` 的镜像 tag、插件 `manifest.yml`、插件 C# 里的 `PluginVersion` 常量、 +官网首页上三处手写值,以及下载页 `` 那个兜底值)。 +手工改必然漏一处,而漏掉的那处**不会报错** —— 只会在某个不常走的分支上表现为"版本号对不上" +(官网那三处的后果是首页一直挂着旧版本号;下载页那一处的后果是离线访客点到一串不存在的直链)。 所以**改版本号的唯一入口是脚本**: ```bash @@ -331,7 +347,7 @@ pnpm build && pnpm verify:web # ④ 打安装包(产物落在 releases/<组件>/<版本>/) pnpm dist:all -pnpm verify:packaged --exe "releases/client/<版本>/免安装/班级小助手.exe" +pnpm verify:packaged --exe "releases/client/<版本>/免安装/ClassHelper.exe" # ⑤ 整理跨组件哈希清单 releases/SHA256SUMS-<版本>.txt(人读;Linux 那一行由 CI 合并进来) # ⑥ 提交并推上去 @@ -346,6 +362,29 @@ gh release upload v<版本> \ releases/SHA256SUMS-<版本>.txt --clobber ``` +> **上传前把三个 EXE 改成 ASCII 名。** 构建产物是中文名(客户端 `ClassHelper-<版本>-x64-setup.exe` +> / `-x64-portable.exe`,服务端 `ClassHelper 服务端-<版本>-x64-setup.exe`),而 Release 上的名字 +> 是给别人看、要贴进脚本的 —— 一律用 ASCII 形式,与构建产物一一对应(内容相同、sha256 相同): +> +> | 构建产物 | Release 上的名字 | +> | --------------------------- | ------------------------------------------- | +> | `<客户端>-x64-setup.exe` | `ClassHelper-<版本>-x64-client-setup.exe` | +> | `<客户端>-x64-portable.exe` | `ClassHelper-<版本>-x64-client-portable.exe` | +> | `<服务端>-x64-setup.exe` | `ClassHelper-<版本>-x64-server-setup.exe` | +> +> **这三个名字是外部契约**:README 的「开始使用」表、官网下载页的离线兜底直链都按它们拼 —— +> 改名要三处一起改。上传前改名比上传后 `gh release edit` 省事: +> +> ```bash +> V=<版本> +> mkdir -p .cache/release-staging +> cp "releases/client/$V/安装包/"*-x64-setup.exe ".cache/release-staging/ClassHelper-$V-x64-client-setup.exe" +> cp "releases/client/$V/安装包/"*-x64-portable.exe ".cache/release-staging/ClassHelper-$V-x64-client-portable.exe" +> cp "releases/server/$V/安装包/"*-x64-setup.exe ".cache/release-staging/ClassHelper-$V-x64-server-setup.exe" +> cp "releases/classisland-plugin/$V/安装包/ClassHelper.ClassIslandPlugin.cipx" .cache/release-staging/ +> gh release upload v$V .cache/release-staging/* "releases/SHA256SUMS-$V.txt" --clobber +> ``` + **第 ⑦ 步的顺序有讲究**:`.github/workflows/release-linux-server.yml` 由 tag 推送触发, 它在**最后一步**才把自己的那一行**合并进** Release 上已有的 `SHA256SUMS-<版本>.txt` (`grep -v` 掉旧的 linux 行再追加,Windows / 插件那几行原样保留)。 @@ -368,6 +407,7 @@ gh workflow run release-linux-server.yml -f tag=v<版本> -f attach=true ``` https://lovechengke.github.io/ClassHelper/ ← 官网(website/,纯静态无构建) +https://lovechengke.github.io/ClassHelper/download.html ← 下载页(同一棵树里的一个页面) https://lovechengke.github.io/ClassHelper/docs/ ← 文档站(docfx 构建 docs/) ``` @@ -378,7 +418,11 @@ https://lovechengke.github.io/ClassHelper/docs/ ← 文档站(docfx 构建 > 以及文档站模板 `docs/templates/classhelper/layout/_master.tmpl` 里那个 `{{_rel}}../` 的「官网」入口。 - 顶栏的版本徽标取自根 `package.json`(`docs/build.mjs` 用 `--metadata _chVersion=…` 注入), - **不要在文档里再写一份版本号**;官网首屏那排芯片里的 `v1.1.2` 是手写的,发版时要一起改; + **不要在文档里再写一份版本号**;官网首页那排芯片里的 `v1.1.2` 与下载页的 + `` 是手写的,发版时要一起改(都在 `pnpm version:bump` 的 14 个落点里); +- 下载页的版本清单**不在站点里存副本**:每次打开都由 `assets/download.js` 当场读 + GitHub Releases(匿名接口 60 次/小时/IP),读不到才退回上面那个 meta 按发布命名约定拼直链。 + 所以发完 Release 不用重新部署这一页,但**兜底值必须跟着发版改**; - 首次启用需要在仓库 Settings → Pages 把 Source 选成「GitHub Actions」(只需一次); - 404 页的 `` 由 `scripts/build-pages.mjs`(本地 `./`)与 workflow(CI 传 `/<仓库名>/`)注入 —— 项目页挂在 `/<仓库名>/` 子路径下,而浏览器是按**被请求的路径**解析 404 页里的相对链接的; @@ -400,7 +444,7 @@ database / update`。 `@classhelper/shared` 的 `resolveClassRole` → `ADMIN / HEAD(班主任)/ SUBJECT(科任)/ STUDENT / NONE`, 权限判 `canManageSchedule` / `canPublishContent` / `canManageRoster` / `canManageGrades` 等 (人类可读矩阵是 `PERMISSION_MATRIX`,README 直接引用它),**不要在页面里另写一套规则**。 -4. **学生端主体是「班级账号」。** 班级码 + 班级密码登录后,`classId` 即身份;该设备的「已读 / 完成」 +4. **ClassHelper 班级端主体是「班级账号」。** 班级码 + 班级密码登录后,`classId` 即身份;该设备的「已读 / 完成」 代全班操作,个人学生登录已停用。API 里对此有专门分支,别按个人账号语义改。 5. **实时推送只走房间。** 服务端 `src/realtime/`(`SOCKET_ROOMS`:`class:{classId}`、`user:{id}`、 `teacher:{id}`、`role:{role}`、`students`、`teachers`;`bus.ts` 事件总线),客户端 @@ -1129,7 +1173,7 @@ database / update`。 ```bash # 判据:看交付物(以及 node_modules/electron/dist)有没有 Low 标签 - icacls packages\desktop-client\release\win-unpacked\班级小助手.exe | findstr Mandatory + icacls packages\desktop-client\release\win-unpacked\ClassHelper.exe | findstr Mandatory # 修法:把交付物改成 Medium。dist-win.mjs 收尾会自动做,手工补救用: icacls packages\desktop-client\release /setintegritylevel Medium /T /C # 目录自身要还原成继承,否则下次构建时自带 Low 标签的 7za / makensis 写不进去 @@ -1229,8 +1273,8 @@ database / update`。 同理,断言岛体尺寸别写死"胶囊尺寸 + 留白":那一刻是胶囊还是卡片取决于前面用例留下的状态, 应改成**与形态无关的不变量**(如"窗口 = 卡片实测尺寸 + 2×阴影留白")。 -23. **客户端安装包有硬底:`班级小助手.exe` 单独压缩后就有 85~90MB,"压到 60MB"做不到 —— 别再为这个反复折腾。** - 实测(2026-10-05,Electron 44.3.0):`win-unpacked` 372MB,其中 `班级小助手.exe`(就是 electron.exe, +23. **客户端安装包有硬底:`ClassHelper.exe` 单独压缩后就有 85~90MB,"压到 60MB"做不到 —— 别再为这个反复折腾。** + 实测(2026-10-05,Electron 44.3.0):`win-unpacked` 372MB,其中 `ClassHelper.exe`(就是 electron.exe, rcedit 只加了 74KB)**234.7MB**;brotli-9 抽样压缩后估算它单独就有 85~90MB。也就是说把渲染产物 从 3.2MB 压到 2.1MB、把图标从 293 个减到 42 个,对安装包只影响约 1MB。 剩下能动的只有这些(合计约 12MB,风险不成比例,**当前一律不做**): diff --git a/README.md b/README.md index 3a931d1..9421309 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@
-# 班级小助手 +# ClassHelper -班级小助手 · Web 管理端仪表盘 +ClassHelper · Web 管理端仪表盘 [![正式版](https://img.shields.io/github/v/release/LoveChengke/ClassHelper?style=flat-square&color=%233fb950&label=正式版)](https://github.com/LoveChengke/ClassHelper/releases/latest) [![下载量](https://img.shields.io/github/downloads/LoveChengke/ClassHelper/total?style=social&label=下载量&logo=github)](https://github.com/LoveChengke/ClassHelper/releases) @@ -16,7 +16,7 @@ ![Electron](https://img.shields.io/badge/Electron-44-47848f?style=flat-square&logo=electron&logoColor=white) [![仓库大小](https://img.shields.io/github/repo-size/LoveChengke/ClassHelper?style=flat-square&color=3cb371)](https://github.com/LoveChengke/ClassHelper) -班级小助手是给中小学班级用的一套信息管理系统。
+ClassHelper是给中小学班级用的一套信息管理系统。
**老师发一条通知,教室那台机器上的灵动岛和 [ClassIsland](https://classisland.tech) 会同时弹出来**;
学生打开客户端就能看到当天的课表和作业。 @@ -78,7 +78,7 @@ - [x] 教室机器上报课表与上课状态,Web 端能看到「现在在上什么」 - [x] 老师发的提醒在教室大屏上全屏弹出,支持语音朗读,播完回执不重复弹 -- [x] 可以把班级小助手排好的课表镜像回 ClassIsland,不影响老师原有的课表 +- [x] 可以把ClassHelper排好的课表镜像回 ClassIsland,不影响老师原有的课表 - [x] 提醒弹在客户端还是 ClassIsland,由教室那台机器自己选 ### 部署与运维 @@ -150,18 +150,22 @@ | 场景 | 截图 | | --- | --- | -| 抽屉菜单 | 手机 · 仪表盘 | +| 仪表盘 | 手机 · 仪表盘 | | 通知发布 | 手机 · 通知发布 | ## 开始使用 -| 装在哪 | 装什么 | 从 Releases 里拿 | -| ----------------------- | ------------------------------------------------- | --------------------------------------------------------- | -| 教师的电脑 / 学校服务器 | **服务端**(内含 Web 管理端与 Node 运行时) | `班级小助手服务端-<版本>-x64-setup.exe` | -| 教室机器 / 学生电脑 | **学生客户端** | `班级小助手-<版本>-x64-setup.exe`,或 `-portable.exe` 免安装 | -| 教室机器的 ClassIsland | **联动插件** | `ClassHelper.ClassIslandPlugin.cipx` | +| 装在哪 | 装什么 | 从 Releases 里拿 | +| ----------------------- | ------------------------------------------------- | --------------------------------------------------------------------------- | +| 教师的电脑 / 学校服务器 | **服务端**(内含 Web 管理端与 Node 运行时) | `ClassHelper-<版本>-x64-server-setup.exe` | +| 教室机器 / 学生电脑 | **学生客户端** | `ClassHelper-<版本>-x64-client-setup.exe`,或 `-client-portable.exe` 免安装 | +| 教室机器的 ClassIsland | **联动插件** | `ClassHelper.ClassIslandPlugin.cipx` | + +也可以在官网的[下载页](https://lovechengke.github.io/ClassHelper/download.html)上挑: +选一个版本,四个包排成四张卡,文件名、体积与 SHA256 清单都在那儿 —— +那一页的清单是**当场**读 GitHub Releases 的,显示什么就能下什么。 服务端**装上就算跑起来了**:双击安装程序,建库、建号、开机自启都由它自己办, 装完在浏览器打开 `http://<服务器地址>:4000` 就能登录。教师的电脑、学校的服务器、 @@ -208,7 +212,7 @@ pnpm dev # 后端 4000 + Web 端 5 | -------------- | --------------- | ------------ | | 管理员 | `admin` | `admin123` | | 教师 | `teacher1` | `teacher123` | -| 学生端(班级) | 班级码 `G101` | `123456` | +| ClassHelper 班级端(班级) | 班级码 `G101` | `123456` | - [AGENTS.md](AGENTS.md) —— 工程约定与本机踩坑,**改代码前先读它** - [docs/reference.md](docs/reference.md) —— 完整参考(功能行为、接口、验收记录) diff --git a/docs/app/island.md b/docs/app/island.md index 49f1ece..6f3bf89 100644 --- a/docs/app/island.md +++ b/docs/app/island.md @@ -14,14 +14,14 @@ description: 桌面客户端的通知浮窗:胶囊与卡片、多条通知的 ## 各形态长什么样 -| 形态 | 截图 | 说明 | -| ---------------------- | ----------------------------------------------------------------- | ------------------------------------------------- | -| 非上课时段的「新消息」 | ![新消息胶囊](../screenshots/island/island-4-pill.png) | 268×44,直接出现(无入场动画),点击后展开 | -| 点开后的详情卡 | ![点击后展开](../screenshots/island/island-5-clicked.png) | 标题 / 内容 / 时间 + 打开应用 / 标为已读 / 知道了 | -| 下课后自动弹出 | ![下课后自动弹出](../screenshots/island/island-2-after-class.png) | 上课期间暂存的通知,下课后自动弹出 | -| 上课期间的紧急通知 | ![紧急通知立即展开](../screenshots/island/island-3-urgent.png) | 卡片**内部**红色光晕 + 紧急角标,无需点击 | -| 新作业 | ![新作业](../screenshots/island/island-7-homework.png) | 青蓝描边 + 书本图标 | -| 叫人 | ![叫人](../screenshots/island/island-8-call.png) | 琥珀金卡片 + 「叫人」徽标 + 「收到」按钮 | +| 形态 | 截图 | 说明 | +| ---------------------- | ----------------------------------------------------- | ------------------------------------------------- | +| 非上课时段的「新消息」 | ![新消息胶囊](../images/island-4-pill.png) | 268×44,直接出现(无入场动画),点击后展开 | +| 点开后的详情卡 | ![点击后展开](../images/island-5-clicked.png) | 标题 / 内容 / 时间 + 打开应用 / 标为已读 / 知道了 | +| 下课后自动弹出 | ![下课后自动弹出](../images/island-2-after-class.png) | 上课期间暂存的通知,下课后自动弹出 | +| 上课期间的紧急通知 | ![紧急通知立即展开](../images/island-3-urgent.png) | 卡片**内部**红色光晕 + 紧急角标,无需点击 | +| 新作业 | ![新作业](../images/island-7-homework.png) | 青蓝描边 + 书本图标 | +| 叫人 | ![叫人](../images/island-8-call.png) | 琥珀金卡片 + 「叫人」徽标 + 「收到」按钮 | ## 什么时候弹、弹成什么样 diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index c27d59b..b27b66e 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -55,7 +55,8 @@ class-helper/ │ ├── index.md # 首页 │ ├── get-started/ app/ management/ dev/ # 四个章节 │ ├── templates/classhelper/ # docfx 模板覆盖(_master.tmpl + public/main.css + 图标) -│ ├── screenshots/ # 回归测试自动留档的截图(island / client / web-mobile / classisland) +│ ├── images/ # 展示图:文档站 / README / 官网共用的同一份(1× 逻辑像素) +│ ├── screenshots/ # 回归测试留档的截图(island / client / web-mobile / classisland),页面不展示 │ ├── build.mjs # 构建入口(注入版本号与 404 的 base) │ ├── serve.mjs # 本地预览(:5181) │ └── _site/ # 构建产物(gitignore) diff --git a/docs/dev/release.md b/docs/dev/release.md index 758cd76..4f4aa69 100644 --- a/docs/dev/release.md +++ b/docs/dev/release.md @@ -19,22 +19,24 @@ description: 改版本号、跑验收、打安装包、发 GitHub Release、发 | 要给别人一份新安装包 | 只在自己机器上跑 | | 插件单独发补丁(只动插件第 4 段版本号) | — | -## 版本号写在哪(13 处 / 11 个文件) +## 版本号写在哪(14 处 / 12 个文件) -同一个版本号分散在 13 个落点上,**手工改必然漏一处**,而漏掉的那处不会报错: +同一个版本号分散在 14 个落点上,**手工改必然漏一处**,而漏掉的那处不会报错: -| 文件 | 内容 | -| ---------------------------------------------------------------- | ------------------------------- | -| `package.json` | 单一来源(服务端运行时读它) | -| `packages/{shared,server,web-admin,desktop-client}/package.json` | 各包的版本 | -| `deploy/package.runtime.json` | 安装包 / 容器里的运行时依赖清单 | -| `deploy/docker-compose.yml`、`docker-compose.sqlite.yml` | 镜像 tag 的默认值 | -| `packages/classisland-plugin/manifest.yml` | 插件清单(四段:`x.y.z.0`) | -| `packages/classisland-plugin/src/Services/BridgeService.cs` | 插件上报的 `PluginVersion` 常量 | -| `website/index.html` × 3 | 官网顶栏徽标 / 首屏芯片 / 页脚 | +| 文件 | 内容 | +| ---------------------------------------------------------------- | ---------------------------------- | +| `package.json` | 单一来源(服务端运行时读它) | +| `packages/{shared,server,web-admin,desktop-client}/package.json` | 各包的版本 | +| `deploy/package.runtime.json` | 安装包 / 容器里的运行时依赖清单 | +| `deploy/docker-compose.yml`、`docker-compose.sqlite.yml` | 镜像 tag 的默认值 | +| `packages/classisland-plugin/manifest.yml` | 插件清单(四段:`x.y.z.0`) | +| `packages/classisland-plugin/src/Services/BridgeService.cs` | 插件上报的 `PluginVersion` 常量 | +| `website/index.html` × 3 | 官网顶栏徽标 / 首屏芯片 / 页脚 | +| `website/download.html`(``) | 下载页读不到版本清单时的兜底版本号 | -最后一行值得单独说:官网是**纯静态站、没有构建步骤**,没有哪段代码会去读 `package.json`, -所以那三处只能靠脚本一起改 —— 漏了的话首页会一直挂着旧版本号,而那是别人看到的第一眼。 +最后两行值得单独说:官网是**纯静态站、没有构建步骤**,没有哪段代码会去读 `package.json`, +所以那几处只能靠脚本一起改 —— 漏了的话首页会一直挂着旧版本号(而那是别人看到的第一眼), +下载页则会给离线访客一串指向**不存在版本**的直链。 所以**改版本号的唯一入口是脚本**: @@ -97,7 +99,7 @@ pnpm dist:all # = dist:server + dist:win + dist:classisland-pl 打完之后**对打包副本再验一次**(开发产物通过 ≠ 用户机器上的副本通过): ```bash -pnpm verify:packaged --exe "releases/client/<版本>/免安装/班级小助手.exe" +pnpm verify:packaged --exe "releases/client/<版本>/免安装/ClassHelper.exe" ``` > Linux 包**只能在 Linux 上构建**(依赖是平台相关的),所以不在这条命令里 —— @@ -127,16 +129,29 @@ git push origin master ### 7. 开 Release -```bash -gh release create v1.1.3 --title "v1.1.3" --generate-notes +上传前先把产物改成 ASCII 名 —— 构建产物是中文名,Release 上的名字是给别人看、要贴进脚本的: -gh release upload v1.1.3 \ - releases/client/<版本>/安装包/*.exe \ - releases/classisland-plugin/<版本>/安装包/*.cipx \ - releases/server/<版本>/安装包/*.exe \ - releases/SHA256SUMS-<版本>.txt --clobber +```bash +V=1.1.3 +mkdir -p .cache/release-staging +cp "releases/client/$V/安装包/"*-x64-setup.exe ".cache/release-staging/ClassHelper-$V-x64-client-setup.exe" +cp "releases/client/$V/安装包/"*-x64-portable.exe ".cache/release-staging/ClassHelper-$V-x64-client-portable.exe" +cp "releases/server/$V/安装包/"*-x64-setup.exe ".cache/release-staging/ClassHelper-$V-x64-server-setup.exe" +cp "releases/classisland-plugin/$V/安装包/ClassHelper.ClassIslandPlugin.cipx" .cache/release-staging/ + +gh release create v$V --title "v$V" --generate-notes +gh release upload v$V .cache/release-staging/* "releases/SHA256SUMS-$V.txt" --clobber ``` +| 构建产物 | Release 上的名字 | +| --------------------------- | -------------------------------------------- | +| `<客户端>-x64-setup.exe` | `ClassHelper-<版本>-x64-client-setup.exe` | +| `<客户端>-x64-portable.exe` | `ClassHelper-<版本>-x64-client-portable.exe` | +| `<服务端>-x64-setup.exe` | `ClassHelper-<版本>-x64-server-setup.exe` | + +这三个名字是**外部契约**:README 的「开始使用」表、官网下载页读不到版本清单时拼的兜底直链, +都按它们写;改名要三处一起改。 + `gh release create` 会**创建 tag 并推送**,这一步会自动触发 [`release-linux-server.yml`](https://github.com/LoveChengke/classhelper/blob/master/.github/workflows/release-linux-server.yml) 构建 Linux 包(ubuntu runner)。它跑完之后会: @@ -169,8 +184,12 @@ https://lovechengke.github.io/ClassHelper/docs/ ← 文档站 (一个仓库在 Pages 上只有一个站点,所以是拼在一起发的 —— 官网在根、文档站在 `/docs/` 下。) 文档站顶栏的版本徽标取自根 `package.json`,所以第 1 步改完就跟着变了; -官网首屏那排芯片、顶栏徽标与页脚里的版本号也是**由 `pnpm version:bump` 一起改的** -(它们是那 13 个落点里的三个)。 +官网首屏那排芯片、顶栏徽标与页脚里的版本号,以及下载页那个兜底版本号, +也都是**由 `pnpm version:bump` 一起改的**(它们是那 14 个落点里的四个)。 + +> 官网的「立即下载」会跳到 `/download.html`,那一页的版本清单是**当场**从 GitHub Releases 读的 +> (读不到才退回上面那个兜底版本号),所以发完 Release 不用重新部署那一页 —— 但**兜底值要跟着发版一起改**, +> 不然离线访客点到的就是旧版本的直链。 ### 9. 检查 diff --git a/docs/dev/testing.md b/docs/dev/testing.md index 9675cae..f599dc3 100644 --- a/docs/dev/testing.md +++ b/docs/dev/testing.md @@ -80,20 +80,43 @@ pnpm build && pnpm verify:web ## 截图留档 -回归测试顺手把截图存到 `docs/screenshots/`(island / client / web-mobile / classisland 四组), -文档站里用的就是这些图。要重新采集: +截图分两套,各管一件事: + +| 目录 | 谁在用 | 谁产出 | +| ------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------- | +| `docs/images/` | **文档站、README、官网用的是同一份图**,页面上展示的都是它 | `capture-shots.mjs`(Web / 手机)+ 客户端冒烟(客户端 / 灵动岛) | +| `docs/screenshots/` | 回归留档,页面不展示;`verify:*` 的像素级断言比对的就是它们 | 各冒烟脚本自动写入(island / client / web-mobile / classisland 四组) | + +**展示图重新采集**(保证文档站与 README、官网用的是同一份图): + +```bash +pnpm dev:server # 后端同时托管 Web 管理端 +CAPTURE_OUT=docs/images,website/assets/shots node website/tools/capture-shots.mjs # → web-*.jpg / mobile-*.jpg + +# 客户端与灵动岛走的是冒烟脚本。它除了正片,还会顺手落一份「岛外留白对照图」 +# (*-window.png / *-backdrop.png),所以先落到临时目录,再挑要用的拷进去。 +ISLAND_SHOTS_DIR="$PWD/.cache/shots" ELECTRON_CLIENT_SHOTS_DIR="$PWD/.cache/shots" \ +ELECTRON_SMOKE_ISLAND_ACCENT='#0a84ff' ELECTRON_SMOKE_ISLAND_STYLE=black \ +ELECTRON_SMOKE_USER=teacher1 ELECTRON_SMOKE_PASSWORD=123456 \ + node packages/desktop-client/scripts/smoke.mjs +cp .cache/shots/{island-*,client-*}.png docs/images/ +rm -f docs/images/*-window.png docs/images/*-backdrop.png +cp docs/images/{island-*,client-*}.png website/assets/shots/ # 官网那份保持同步 +``` + +**回归留档重新采集**(同一套冒烟,把输出指到留档目录): ```bash pnpm dev:server -ISLAND_SHOTS_DIR=F:/ClassHelper/.cache/island-shots \ -ELECTRON_CLIENT_SHOTS_DIR=F:/ClassHelper/.cache/client-shots \ +ISLAND_SHOTS_DIR="$PWD/.cache/island-shots" \ +ELECTRON_CLIENT_SHOTS_DIR="$PWD/.cache/client-shots" \ ELECTRON_SMOKE_ISLAND_ACCENT='#0a84ff' ELECTRON_SMOKE_ISLAND_STYLE=black \ ELECTRON_SMOKE_USER=teacher1 ELECTRON_SMOKE_PASSWORD=123456 \ node packages/desktop-client/scripts/smoke.mjs ``` -官网的实机图则用 `website/tools/capture-shots.mjs`(采集步骤见 `README.md` §7)。 - +> 展示图都是**逻辑像素 1×**(灵动岛的 268×44、424×230 就是这个数,不再乘 2), +> 页面按原始尺寸显示即可 —— 换图时别顺手放大,否则整页的尺寸对不上。 > 采集脚本会把窗口真的弹在屏幕上。客户端留档会**临时停掉 CSS 动画**再截图 —— > 窗口被遮挡时 Chromium 冻结动画,而页面入场动画的起始帧是 `opacity: 0`, > 不停掉的话截出来是**整片空白**(DOM 里文本齐全,只有图是空的)。 diff --git a/docs/index.md b/docs/index.md index 87572f5..5c44921 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,12 +1,12 @@ --- title: 首页 -description: 班级小助手(Class Helper)的官方文档:安装部署、功能说明、运维手册与开发参考。 +description: ClassHelper(Class Helper)的官方文档:安装部署、功能说明、运维手册与开发参考。 --- -# 班级小助手文档 +# ClassHelper文档 -班级小助手(Class Helper)是给中小学班级用的一套信息管理系统。**一份代码,四个交付物**: -装在服务器上的后端、给老师用的 Web 管理端、装在教室机器的桌面客户端(学生端 / 教室大屏), +ClassHelper(Class Helper)是给中小学班级用的一套信息管理系统。**一份代码,四个交付物**: +装在服务器上的后端、给老师用的 Web 管理端、装在教室机器的桌面客户端(ClassHelper 班级端 / 教室大屏), 以及装在 [ClassIsland](https://classisland.tech) 上的联动插件。

@@ -16,7 +16,7 @@ description: 班级小助手(Class Helper)的官方文档:安装部署、

第一次装 - 从零把服务端、管理端、学生端和 ClassIsland 插件跑起来,大约半小时。 + 从零把服务端、管理端、ClassHelper 班级端和 ClassIsland 插件跑起来,大约半小时。 已经在用了 @@ -35,9 +35,9 @@ description: 班级小助手(Class Helper)的官方文档:安装部署、 ## 它长什么样
- 桌面客户端 · 课表「今天」时间轴 - 灵动岛 · 点开通知后的详情卡 - Web 管理端 · 手机上的通知发布页 + 桌面客户端 · 课表「今天」时间轴 + 灵动岛 · 点开通知后的详情卡 + Web 管理端 · 手机上的抽屉导航与通知发布页
> 这些图都是脚本把程序真的跑起来截的,不是示意图。采集方式见 [验收与测试](dev/testing.md)。 diff --git a/docs/reference.md b/docs/reference.md index e19093e..bb29b95 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1,7 +1,7 @@ -# 班级小助手 · 完整参考 +# ClassHelper · 完整参考 班级信息管理系统。一个 pnpm monorepo,一份代码产出四个交付物:**后端服务**、**Web 管理端**(教师 / 管理员)、 -**桌面客户端**(学生端 / 教室机器)与 **ClassIsland 联动插件**(装在教室的 ClassIsland 上)。**当前版本 1.1.2**。 +**桌面客户端**(ClassHelper 班级端 / 教室机器)与 **ClassIsland 联动插件**(装在教室的 ClassIsland 上)。**当前版本 1.1.2**。 核心链路: @@ -19,7 +19,7 @@ ClassIsland(教室机器)──► 联动插件 ──► /api/integrations/ └──── 老师下发的提醒 ◄────────┘ ``` -> 本文是班级小助手的**完整参考**:功能行为、接口契约、验收证据与已知问题都在这里,是功能口径的权威来源。 +> 本文是ClassHelper的**完整参考**:功能行为、接口契约、验收证据与已知问题都在这里,是功能口径的权威来源。 > 面向「第一次打开仓库」的展示型介绍在 [README](https://github.com/LoveChengke/classhelper/blob/master/README.md);按读者分组的引导式文档在文档站(本目录)。 > 工程约定(改代码前必读)与本机踩坑在 [AGENTS.md](https://github.com/LoveChengke/classhelper/blob/master/AGENTS.md);生产部署在 > [production.md](management/production.md);切换数据库在 [mysql.md](management/mysql.md)。 @@ -62,7 +62,7 @@ ClassIsland(教室机器)──► 联动插件 ──► /api/integrations/ | -------------------- | ----------------------------- | -------------------------------------------------------------------- | --------------------------------- | | 后端服务 | `packages/server` | Express 5 + Prisma 7 + Socket.IO,接口前缀 `/api` | 其它三端 | | Web 管理端 | `packages/web-admin` | Vue 3 + Vite + Element Plus(构建后由后端托管,PWA 可安装) | 教师、管理员(手机可用) | -| 桌面客户端 | `packages/desktop-client` | Electron + Vue 3,含「灵动岛」浮窗、托盘、离线缓存 | 学生端 / 教室机器 | +| 桌面客户端 | `packages/desktop-client` | Electron + Vue 3,含「灵动岛」浮窗、托盘、离线缓存 | ClassHelper 班级端 / 教室机器 | | 共享契约 | `packages/shared` | 三端共用的类型 / 常量 / 权限矩阵 / 工具函数 | 上面三个 + 插件 | | ClassIsland 联动插件 | `packages/classisland-plugin` | .NET 8 / C#,装在教室的 ClassIsland 上(上报课表与上课状态、弹提醒) | 教室机器(独立于 pnpm workspace) | | 官网 | `website/` | 纯 HTML / CSS / JS 的静态页面,**没有构建步骤** | 展示与说明(`node website/serve.mjs` 本地预览) | @@ -78,10 +78,10 @@ ClassIsland(教室机器)──► 联动插件 ──► /api/integrations/ | 产物 | 产物文件位置(打包后) | 用途 | | ------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| **服务端 + Web 管理端安装程序** | `releases/server/<版本>/安装包/班级小助手服务端-<版本>-x64-setup.exe` | 装到教师电脑 / 校服务器即完整系统,**内置 Node 运行时**,双击安装、开机自启、自动建库建号 | +| **服务端 + Web 管理端安装程序** | `releases/server/<版本>/安装包/ClassHelper服务端-<版本>-x64-setup.exe` | 装到教师电脑 / 校服务器即完整系统,**内置 Node 运行时**,双击安装、开机自启、自动建库建号 | | **服务端 Linux 安装包** | `releases/server/<版本>/linux-x64/classhelper-server-linux-x64-<版本>.tar.gz` | 给 `deploy/install.sh` 用(另见同名 `.sha256`);由 Actions 在 Linux 上构建后挂 Release | -| **学生客户端安装程序** | `releases/client/<版本>/安装包/班级小助手-<版本>-x64-setup.exe` | 学生机安装(NSIS) | -| **学生客户端单文件版** | `releases/client/<版本>/安装包/班级小助手-<版本>-x64-portable.exe` | 免安装直接运行(U 盘分发) | +| **学生客户端安装程序** | `releases/client/<版本>/安装包/ClassHelper-<版本>-x64-setup.exe` | 学生机安装(NSIS) | +| **学生客户端单文件版** | `releases/client/<版本>/安装包/ClassHelper-<版本>-x64-portable.exe` | 免安装直接运行(U 盘分发) | | Web 管理端(PWA) | 由服务端在 `/` 直接托管 | 浏览器打开即用,可在 Edge/Chrome 中「安装为应用」;已适配手机小屏 | | **ClassIsland 联动插件** | `releases/classisland-plugin/<版本>/安装包/ClassHelper.ClassIslandPlugin.cipx` | 装在教室机器的 ClassIsland 上(也可把 `ClassHelper.ClassIslandPlugin/` 目录丢进 Plugins) | @@ -94,7 +94,7 @@ ClassIsland(教室机器)──► 联动插件 ──► /api/integrations/ | 场景 | 形态 | 入口 | | ----------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| 学校机房 / 教师电脑(Windows 单机) | Windows 服务端安装程序(内置 Node,双击即用) | `releases/server/<版本>/安装包/班级小助手服务端-<版本>-x64-setup.exe` | +| 学校机房 / 教师电脑(Windows 单机) | Windows 服务端安装程序(内置 Node,双击即用) | `releases/server/<版本>/安装包/ClassHelper服务端-<版本>-x64-setup.exe` | | 云服务器 / 多终端共享(推荐长期) | Docker Compose + MySQL(另有 SQLite 单容器版) | `deploy/Dockerfile`、`deploy/docker-compose{,.sqlite}.yml` | | 已有 Linux 服务器 | **一键安装器 + `classhelper` 运维命令**(systemd / docker) | `sudo bash deploy/install.sh`(先 `--check` 体检);运维见 `classhelper help` | | 自定义 / 已有 Node 环境 | 手动部署免安装目录 | `pnpm dist:server` → `releases/server/<版本>/免安装/` | @@ -147,7 +147,7 @@ pnpm dev > `{ "success": false, "code": "NOT_FOUND" }`。Web 管理端开发地址是 **5173**,构建后由后端在 4000 托管。 > 也可以分开启动:`pnpm dev:server`(仅后端)/ `pnpm dev:web`(仅 Web 端)。 -### 启动客户端(学生端) +### 启动客户端(ClassHelper 班级端) ```bash # 开发模式:自动构建主进程 + 启动 Vite 渲染进程(5174) + 打开 Electron 窗口(热更新) @@ -183,14 +183,14 @@ pnpm verify:desktop | 管理员 | `admin` | `admin123` | 可访问全部班级 | | 教师 | `teacher1` | `teacher123` | 张老师:高一(1)班、高二(3)班班主任 | | 教师 | `teacher2` | `teacher123` | 李老师:高一(2)班班主任,高二(3)班协作教师 | -| 班级 | 班级码 `G101` | `123456` | 高一(1)班(**学生端唯一登录方式**) | +| 班级 | 班级码 `G101` | `123456` | 高一(1)班(**ClassHelper 班级端唯一登录方式**) | | 班级 | 班级码 `G102` | `123456` | 高一(2)班 | | 班级 | 班级码 `G203` | `123456` | 高二(3)班(不是 `G103`) | - 密码由**种子脚本硬编码**(`admin123` / `teacher123` / 班级 `123456`);`.env` 的 `DEFAULT_CLASS_PASSWORD` 只决定**新建**班级时的初始班级密码。 - **学生没有个人账号**(2026-10-01 起彻底清理):学生只是名单记录(成绩 / 未交名单 / 已读人数 / 叫人都按名单), - 没有用户名密码、不能登录;学生端统一用班级码 + 班级密码。 + 没有用户名密码、不能登录;ClassHelper 班级端统一用班级码 + 班级密码。 - 教学周由 `TERM_START_DATE`(第 1 教学周的周一)换算,当前周次用于课表默认视图。 ### 常用命令 @@ -270,13 +270,12 @@ Element Plus 也通过对齐它的 `--el-border-radius-*` 一并改造: 详情抽屉宽 86vw 且 `el-descriptions` 由 2~3 列降为单列;统计卡片两列。 实现位置:`src/composables/useResponsive.ts`、`src/layouts/AdminLayout.vue`、`src/styles/index.css`。 -实拍(`pnpm verify:web` 自动留档到 `docs/screenshots/web-mobile/`): +实拍(`node website/tools/capture-shots.mjs` 采集,落在 `docs/images/` —— 与 README、官网用的是同一份图): -| 场景 | 截图 | -| ----------------------------------------- | --------------------------------------------------------------------- | -| 手机抽屉菜单 | ![手机抽屉菜单](screenshots/web-mobile/mobile-1-drawer.png) | -| 手机通知页(工具栏铺满 + 表格卡片内滚动) | ![手机通知页](screenshots/web-mobile/mobile-2-notifications.png) | -| 手机弹窗自适应 | ![手机弹窗](screenshots/web-mobile/mobile-3-dialog.png) | +| 场景 | 截图 | +| ---------------------------------------------- | ------------------------------------------------ | +| 手机抽屉菜单(叠在「通知发布」页上) | ![手机抽屉菜单](images/mobile-notifications.jpg) | +| 手机仪表盘(统计卡两列,表格在卡片内横向滚动) | ![手机仪表盘](images/mobile-dashboard.jpg) | > 手机访问:与电脑同一局域网时用 `http://<主机IP>:4000` 打开管理端;支持「添加到主屏幕」(PWA)。 @@ -289,7 +288,7 @@ Element Plus 也通过对齐它的 `--el-border-radius-*` 一并改造: | 删除教师有护栏 | 四种情况一律 409:还是**班主任**、名下有课程 / 班级分配、**发布过作业或通知**(后两项也是级联删除,删账号会连带删掉他在各班发过的内容) | | 创建班级仅管理员 | `POST /api/classes` = `requireRole('ADMIN')`,教师(含班主任)403 | | **设置 / 更改班主任** | 新建班级时可选班主任(默认自己);班级详情顶部「更换班主任」下拉随时改(`PATCH /api/classes/:id/head-teacher`,仅管理员),原班主任仍留在协作教师名单 | -| 学生录入 | 「学生管理」:单个新建(登录名自动生成)+导入名单;学生端主体是班级账号 | +| 学生录入 | 「学生管理」:单个新建(登录名自动生成)+导入名单;ClassHelper 班级端主体是班级账号 | > 学生名单接口(`/api/students`)与教师全量接口(`/api/teachers`)都是 **`requireRole('ADMIN')`**; > 「班级 → 学生名单」(`/api/classes/:id/students`)读接口对班级可见,写接口为 ADMIN/TEACHER(服务层再按班级角色兜底)。 @@ -371,7 +370,7 @@ Element Plus 也通过对齐它的 `--el-border-radius-*` 一并改造: (`renderer/components/OnboardingWelcome.vue`),关闭即从 DOM 卸载(不用 el-dialog,避免「关闭后 DOM 残留」 的断言陷阱);显隐由 `stores/onboarding.ts` 承载,登录页 / 设置页 / App.vue 三处共用一个开关。 -#### 作业看板(学生端) +#### 作业看板(ClassHelper 班级端) 默认按科目分卡片(每张卡列该科作业,条目可点开详情),工具栏可切「看板 / 列表」、开关「显示时间」、 拖「字号」(11–28px 即时生效),点卡片或「全屏看板」进入全屏放大视图;这些偏好写入客户端配置 @@ -403,15 +402,16 @@ Element Plus 也通过对齐它的 `--el-border-radius-*` 一并改造: | 时间轴 | 左侧时间栏(开始 / 结束)+ 竖向轨道与节点 + 右侧课卡:**已结束**(置灰)、**正在上课**(强调色实底 + 进度条 + 距下课倒计时)、**下一节**(橙色标记 + 还有多久开始) | | 周视图 | 保留「节次 × 星期」表格(`本周` 标签) | -实拍(`docs/screenshots/client/`): +实拍(`node website/tools/capture-shots.mjs` 采集,落在 `docs/images/` —— 与 README、官网用的是同一份图): -| 页面 | 截图 | -| --------------------- | ------------------------------------------------------------ | -| 课表 · 今天(时间轴) | ![客户端课表时间轴](screenshots/client/01-schedule.png) | -| 作业 | ![客户端作业](screenshots/client/02-homeworks.png) | -| 通知 | ![客户端通知](screenshots/client/03-notifications.png) | -| 成绩 | ![客户端成绩](screenshots/client/04-grades.png) | -| 设置 | ![客户端设置](screenshots/client/05-settings.png) | +| 页面 | 截图 | +| --------------------- | ---------------------------------------------------- | +| 课表 · 今天(时间轴) | ![客户端课表时间轴](images/client-schedule.png) | +| 作业看板 | ![客户端作业看板](images/client-homeworks.png) | +| 通知 | ![客户端通知](images/client-notifications.png) | +| 成绩 | ![客户端成绩](images/client-grades.png) | +| 设置 · 灵动岛 | ![客户端设置灵动岛](images/client-settings-island.png) | +| 设置 · 外观(深色) | ![客户端设置外观](images/client-settings-appearance.png) | #### 个性化设置与系统托盘 @@ -440,7 +440,7 @@ Element Plus 也通过对齐它的 `--el-border-radius-*` 一并改造: 教师 / 管理员本人密码在 Web 管理端顶栏下拉修改 —— 防止教室机器上误改班级密码后其他机器被锁在门外。 **系统托盘**:关闭主窗口不退出程序,而是隐藏到托盘后台继续接收通知;托盘图标(左键单击 / 双击)恢复主窗口, -右键菜单提供「显示主窗口 / 隐藏到托盘 / 退出班级小助手」。退出时统一清理:销毁托盘 → 销毁灵动岛 +右键菜单提供「显示主窗口 / 隐藏到托盘 / 退出ClassHelper」。退出时统一清理:销毁托盘 → 销毁灵动岛 (含帧循环与定时器)→ 销毁全部窗口,并用 `app.exit(0)` 兜底,任务管理器中不留残留进程。 #### 离线缓存与自动同步 @@ -518,15 +518,18 @@ Element Plus 也通过对齐它的 `--el-border-radius-*` 一并改造: - 相关文件:`src/main/island.ts`(控制器 + IPC)、`src/preload/island.ts`、`src/island/{IslandApp.vue,spring.ts,squircle.ts}`、 `src/renderer/island/bridge.ts`。 -各形态实拍(由 `pnpm verify:desktop` 自动截取并归档到 `docs/screenshots/island/`,并做像素级校验): +各形态实拍(显示的是 `docs/images/` —— 与 README、官网同一份,按**逻辑像素 1×** 采集; +`pnpm verify:desktop` 另有一套逐像素校验过的留档在 `docs/screenshots/island/`,那是回归产物,不在这页展示): -| 形态 | 截图 | 尺寸(逻辑 / 图片) | 说明 | -| ------------------------ | ------------------------------------------------------------------- | ------------------- | ------------------------------------------------- | -| 下课后自动弹出详情 | ![下课后自动弹出](screenshots/island/island-2-after-class.png) | 424×230 / 848×460 | 上课期间暂存的通知,下课后自动弹出 | -| 上课期间紧急通知 | ![紧急通知立即展开](screenshots/island/island-3-urgent.png) | 440×246 / 880×492 | 卡片**内部**红色光晕 + 紧急角标,无需点击 | -| 非上课时段「新消息」胶囊 | ![新消息胶囊](screenshots/island/island-4-pill.png) | 268×44 / 536×88 | 直接出现(无入场动画),点击后展开 | -| 点击胶囊后的详情 | ![点击后展开](screenshots/island/island-5-clicked.png) | 424×230 / 848×460 | 标题 / 内容 / 时间 + 打开应用 / 标为已读 / 知道了 | -| 新作业胶囊 / 叫人卡片 | `island-7-homework.png` / `island-8-call.png` | 424×230 / 456×262 | 作业与叫人各自的配色与徽标 | +| 形态 | 截图 | 尺寸(逻辑 px) | 说明 | +| ------------------------ | ---------------------------------------------------- | --------------- | ------------------------------------------------- | +| 下课后自动弹出详情 | ![下课后自动弹出](images/island-2-after-class.png) | 424×230 | 上课期间暂存的通知,下课后自动弹出 | +| 上课期间紧急通知 | ![紧急通知立即展开](images/island-3-urgent.png) | 440×246 | 卡片**内部**红色光晕 + 紧急角标,无需点击 | +| 非上课时段「新消息」胶囊 | ![新消息胶囊](images/island-4-pill.png) | 268×44 | 直接出现(无入场动画),点击后展开 | +| 点击胶囊后的详情 | ![点击后展开](images/island-5-clicked.png) | 424×230 | 标题 / 内容 / 时间 + 打开应用 / 标为已读 / 知道了 | +| 多条通知竖向排列 | ![多条通知](images/island-9-list.png) | 424×303 | 按重要程度排序,默认显示 3 条 +「展开更多」 | +| 新作业胶囊 | ![新作业](images/island-7-homework.png) | 424×230 | 青蓝描边 + 书本图标 | +| 叫人卡片 | ![叫人](images/island-8-call.png) | 456×262 | 琥珀金卡片 +「叫人」徽标 +「收到」按钮 | > 截图路径可用 `ISLAND_SHOTS_DIR` 覆盖(默认 `.cache/island-shots/`)。 @@ -556,7 +559,7 @@ Element Plus 也通过对齐它的 `--el-border-radius-*` 一并改造: 老师在 **Web 管理端 → 学生管理 → 叫人**(或 **通知发布 → 叫人**)选中学生,配合**快捷短语**或**自定义消息**发送。 学生管理页是管理员专属,而班主任与科任老师都需要叫人,因此教师的叫人入口在「通知发布 → 叫人」。 -| 级别 | 落库级别 | 学生端行为 | +| 级别 | 落库级别 | ClassHelper 班级端行为 | | ------------ | -------- | --------------------------------------------------------------------------------------------------------- | | **普通叫人** | `HIGH` | 课间:先显示「叫人」胶囊,点击展开;**上课时段:只进队列、不打断课堂**,下课后自动弹出详情(90 秒后收起) | | **紧急叫人** | `URGENT` | **无视上课时段立即展开**(与紧急通知同待遇),上课时被收起后仍保留胶囊、可再次点开 | @@ -680,15 +683,15 @@ POST /api/notifications { …, priority: "NORMAL" }(上课时段) | 方向 | 做什么 | 老师在哪看 | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | -| ClassIsland → 班级小助手 | 上报**课表 + 节次时间 + 上课状态**(现在上什么 / 下一节 / 本节起止时间),写入本班课表 | Web 端「ClassIsland 联动」实时状态卡片 | -| 班级小助手 → ClassIsland | 老师在 Web 端发一条**提醒** → 落库 → 教室机器的插件取回 → ClassIsland 上**全屏弹出**(可语音朗读),播完回执、不会重复弹 | Web 端「ClassIsland 联动 → 下发提醒」 | -| (可选)班级 → ClassIsland | 把班级小助手上排好的课表**镜像**回 ClassIsland:新建一份 `班级小助手-<班级名>` 档案课表,不动老师原有的课表 | 同上页面的「开镜像」开关 | +| ClassIsland → ClassHelper | 上报**课表 + 节次时间 + 上课状态**(现在上什么 / 下一节 / 本节起止时间),写入本班课表 | Web 端「ClassIsland 联动」实时状态卡片 | +| ClassHelper → ClassIsland | 老师在 Web 端发一条**提醒** → 落库 → 教室机器的插件取回 → ClassIsland 上**全屏弹出**(可语音朗读),播完回执、不会重复弹 | Web 端「ClassIsland 联动 → 下发提醒」 | +| (可选)班级 → ClassIsland | 把ClassHelper上排好的课表**镜像**回 ClassIsland:新建一份 `ClassHelper-<班级名>` 档案课表,不动老师原有的课表 | 同上页面的「开镜像」开关 | #### 接入流程(三步) 1. **Web 端签发设备令牌**:管理员 / 本班班主任在「ClassIsland 联动」页点「接入新设备」,选择班级并生成令牌 (形如 `chci_xxxxxxxx…`,**明文只显示这一次**,服务端只存 sha256); -2. **教室机器填令牌**:在 ClassIsland 的「设置 → 班级小助手联动」里填入后端地址 + 令牌,点「立即上报」; +2. **教室机器填令牌**:在 ClassIsland 的「设置 → ClassHelper联动」里填入后端地址 + 令牌,点「立即上报」; 3. **验证**:页面顶部出现设备卡片与「在线」标记,说明链路通了 —— 之后课表与上课状态自动同步, 老师发的提醒也会在这台机器上弹出来。 @@ -718,8 +721,8 @@ POST /api/notifications { …, priority: "NORMAL" }(上课时段) | 要同步的内容 | 需要打开的开关 | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 服务端 → ClassIsland(镜像课表) | ① 插件设置「把班级课表镜像回 ClassIsland」+ ② Web 端该设备的「镜像课表」 | -| ClassIsland → 服务端(上报课表) | 插件设置「上报课表到班级小助手」(关着时日志会明说「仅状态(开关已关闭)」);同步是**双向对齐**的:老师在 ClassIsland 里删掉 / 挪动的节次会一并清理,Web 端手排的课不会被删 | -| 提醒下发 | ① 教室客户端「通知显示位置」不是「只在 ClassHelper」+ ② 插件「接收班级小助手提醒」 | +| ClassIsland → 服务端(上报课表) | 插件设置「上报课表到ClassHelper」(关着时日志会明说「仅状态(开关已关闭)」);同步是**双向对齐**的:老师在 ClassIsland 里删掉 / 挪动的节次会一并清理,Web 端手排的课不会被删 | +| 提醒下发 | ① 教室客户端「通知显示位置」不是「只在 ClassHelper」+ ② 插件「接收ClassHelper提醒」 | #### 提醒弹在哪个端:由教室客户端自己选 @@ -852,10 +855,11 @@ class-helper/ │ ├── production.md # 生产部署指南(四种形态 + Linux systemd + 运维 + 安全清单) │ ├── mysql.md # MySQL 切换指南 │ ├── winisland-design-tokens.md # 灵动岛照 WinIsland 提取的设计 token -│ └── screenshots/ # 回归测试自动留档的截图(island / client / web-mobile / classisland) +│ ├── images/ # 展示图:文档站 / README / 官网共用的同一份(1× 逻辑像素) +│ └── screenshots/ # 回归测试留档的截图(island / client / web-mobile / classisland),页面不展示 ├── website/ # 产品官网(纯静态,无构建;node serve.mjs 本地预览) -│ ├── index.html -│ └── assets/{styles.css,motion.js,main.js,icon.png,shots/} # motion.js = beUI 动效 token 与弹簧引擎 +│ ├── index.html / download.html +│ └── assets/{styles.css,motion.js,main.js,download.js,icon.png,shots/} # motion.js = beUI 动效 token 与弹簧引擎 └── packages/ ├── shared/ # 三端共享:类型契约、常量、权限矩阵、工具函数 │ └── src/{types,constants,permissions,utils,index}.ts @@ -890,7 +894,7 @@ class-helper/ │ ├── src/Services/ScheduleMapper.cs # 课表与单双周映射 │ ├── src/Services/ClassPlanWriter.cs # 镜像回 ClassIsland(含课间时间点) │ ├── src/Services/ClassHelperNotificationProvider.cs # 把老师发的提醒显示到 ClassIsland 上 - │ ├── src/Views/BridgeSettingsPage.axaml(.cs) # 「班级小助手联动」设置页(Avalonia) + │ ├── src/Views/BridgeSettingsPage.axaml(.cs) # 「ClassHelper联动」设置页(Avalonia) │ ├── src/Interop/ClassHelperClient.cs # 后端 HTTP 客户端 + 对齐服务端 zod 的 DTO │ └── scripts/{build,verify}.mjs # 构建打包(.cipx)与静态契约校验 ├── web-admin/ # Web 管理端(Vue 3 + Vite + Element Plus + PWA) @@ -1191,7 +1195,7 @@ pnpm db:generate && pnpm --filter @classhelper/server db:deploy && pnpm db:seed | 验收项 | 结果 | 证据 | | ------------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 教师 Web 端发布通知,学生端 5 秒内收到 | ✅ | `verify:e2e`:`notification:new` 实测 30–50ms 到达(要求 < 5 秒) | +| 教师 Web 端发布通知,ClassHelper 班级端 5 秒内收到 | ✅ | `verify:e2e`:`notification:new` 实测 30–50ms 到达(要求 < 5 秒) | | 教师发布作业,学生能查看并标记完成 | ✅ | `homework:new` 实时到达,`PATCH /homeworks/:id/status` 200 且列表回显 `completed=true` | | 教师录入成绩,学生能查看个人成绩 | ✅ | `grade:updated` 实时到达,`/grades/my` 返回记录;批量录入与统计接口通过 | | 学生能查看课表,支持按周切换 | ✅ | `/schedules/grid?week=1` 返回周视图条目,`week` 过滤 `weekStart ≤ week ≤ weekEnd`,单双周按第 1 周 = 单周过滤 | @@ -1211,7 +1215,7 @@ pnpm db:generate && pnpm --filter @classhelper/server db:deploy && pnpm db:seed | 成绩 / 名单表格导入(xlsx·xls·csv) | ✅ | 模板下载 + 预览映射 + 重复处理 + 行号级错误:`verify:e2e` 覆盖 20 余项,`verify:web` 弹窗实测 | | ClassIsland 时间配置导入(覆盖 / 合并 / 回滚) | ✅ | 合法 200、非法 400 `IMPORT_INVALID` 且原配置仍在、merge 覆盖与保留行为符合预期 | | 安装版覆盖升级自动补迁移 | ✅ | 真实旧库升级日志:`升级安装:已应用 N 个迁移文件,跳过 M 个已存在对象`,新表自动建好 | -| 学生端主体 = 班级(班级码 + 班级密码登录) | ✅ | `verify:e2e` 班级账号 19 项:登录 / 错误密码 401 / 跨班 403 / 发布 403 / 班级码重复 400 / 密码重置 / 班级码用后还原 | +| ClassHelper 班级端主体 = 班级(班级码 + 班级密码登录) | ✅ | `verify:e2e` 班级账号 19 项:登录 / 错误密码 401 / 跨班 403 / 发布 403 / 班级码重复 400 / 密码重置 / 班级码用后还原 | | 班级设备代全班操作(已读 · 完成 · 成绩总览) | ✅ | 标记已读写入全班、教师端 `readCount` = 班级学生数、`completedCount` 一致、`/grades/my` 返回全班成绩 | | 客户端只保留班级登录入口 | ✅ | `verify:desktop`:导航「成绩」标题变为「本班成绩」;个人学生登录 403 | | **教师录入(仅管理员)** | ✅ | `verify:e2e`:教师读 / 建教师 403、管理员建 201 → 编辑 → 重置密码(新密码 200 / 旧密码 401)→ 删除(有职责 409 / 无职责 200);`verify:web`:管理员真人点「教师管理 → 新建教师 → 保存 → 列表出现 → 删除」 | @@ -1225,12 +1229,12 @@ pnpm db:generate && pnpm --filter @classhelper/server db:deploy && pnpm db:seed | **客户端设置改版(分组侧栏 / 深色模式 / 关于页)** | ✅ | `verify:desktop`:「侧边栏点击导航」适配「学习 / 设置」分组;「主题切换与侧边栏折叠(真实点击、写回配置)」——`html.dark` 翻转 + 折叠宽度 200→64 + 双双落盘;「关于页渲染」含 configPath | | **数据库管理(仅管理员:状态 / 备份 / 导入导出 / 一键切换)** | ✅ | `verify:e2e` 13 项:状态与 14 表行数、教师全端点 403、连接测试(当前库可达 / 不可达 MySQL ok=false)、**Redis 主库 422**、切换校验分支(同库 400)、备份 → 快照导入 → 备份恢复 roundtrip(临时数据被清除)、定时配置校验、SQLite 文件下载;**切换子进程链路真机实测**:快照 728 行 → generate → db push → apply-snapshot 逐表一致;`verify:web`:数据库管理页(仅管理员)状态卡与备份表渲染 | | **更新检查(服务端 / Web 端 / 客户端)** | ✅ | `verify:e2e` 4 项:未登录 401、管理员结构完整且本机版本取自 package.json、教师可查但 `force` 仅管理员生效、健康检查暴露版本号与 15 个模块;`verify:desktop` 4 项:IPC 返回结构正确(无外网时降级 `ok=false` 且带原因)、结果带缓存、冒烟模式跳过启动自动检查、关于页出现「检查更新」;`verify:web`:「顶栏用户菜单 → 弹窗给出结论并可关闭」。**真机联网实测**:`GET /api/update/check` → `ok=true`、`latestVersion=1.0.0`、5 个附件、更新说明 1358 字 | -| **其他页面发通知也会联动 ClassIsland** | ✅ | `verify:classisland`:「通知发布页发的通知也会推送」「叫人也会推送」;真机实测截图 [03-from-notify-page](screenshots/classisland/03-from-notify-page.png) | +| **其他页面发通知也会联动 ClassIsland** | ✅ | `verify:classisland`:「通知发布页发的通知也会推送」「叫人也会推送」;2026-09-27 在真机(ClassIsland 2.1.0.1 + 本插件)上实测过,当时的抓图留在 `docs/screenshots/classisland/` | | **作业按天查看(日期选择器 + 有作业日期高亮)** | ✅ | `verify:e2e`:`assignDate` 默认服务器当天、`?date=` 只返回该天、`/homeworks/days` 返回有作业日期、非法日期 422 | | **支持在客户端录入作业(含自定义快捷短语)** | ✅ | `verify:e2e`:班级账号录入 201(归属班主任)、跨班 403;客户端录入弹窗 + 「设置 → 通用」管理短语 | | **提醒弹在哪个端由客户端自行选择** | ✅ | 客户端「设置 → 提醒」三选一(`verify:desktop`:设置页真实点击 → 写回本地配置);服务端按班级设置决定是否推送(`verify:classisland`:`client` 时通知与联动页下发都被跳过) | -| **班级小助手能读取 ClassIsland 的课表** | ✅ | `verify:classisland`:插件上报课表 → 本班课表新增 / 更新(重复上报幂等)、节次时间写入、设备状态快照可在 Web 端展示;`verify:web`:教师在「ClassIsland 联动」页真实签发设备令牌 | -| **用户可选择通知是否在 ClassIsland 上显示** | ✅ | 插件设置页开关「接收班级小助手提醒」(本机总闸)+ Web 端「下发提醒」(标题 / 内容 / 时长 / 优先级 / 语音朗读 / 是否同步通知中心);`verify:classisland`:提醒落库 → 待提醒 → 回执 → 确认后不补发 | +| **ClassHelper能读取 ClassIsland 的课表** | ✅ | `verify:classisland`:插件上报课表 → 本班课表新增 / 更新(重复上报幂等)、节次时间写入、设备状态快照可在 Web 端展示;`verify:web`:教师在「ClassIsland 联动」页真实签发设备令牌 | +| **用户可选择通知是否在 ClassIsland 上显示** | ✅ | 插件设置页开关「接收ClassHelper提醒」(本机总闸)+ Web 端「下发提醒」(标题 / 内容 / 时长 / 优先级 / 语音朗读 / 是否同步通知中心);`verify:classisland`:提醒落库 → 待提醒 → 回执 → 确认后不补发 | | 能成功打包 Windows EXE | ✅ | `-x64-setup.exe` / `-x64-portable.exe` / `win-unpacked/*.exe`(见 [7. 打包与交付](#7-打包与交付)) | | 提供完整 README(启动、构建、打包、默认账号) | ✅ | 本文档含快速开始、命令表、API、WebSocket、RBAC、模块化、数据库、打包与常见问题 | @@ -1287,7 +1291,7 @@ pnpm db:generate && pnpm --filter @classhelper/server db:deploy && pnpm db:seed | 客户端渲染产物 `dist/renderer` | 3.2MB | **2.1MB** | 成绩页图表从 echarts 全量(1.12MB,占 35%)换成内置 HTML/SVG 组件;Element Plus 图标从全量注册 293 个改为按需 42/33 个 | | 客户端主进程产物体积 | 242KB | 242KB | 未变(冒烟代码拆分会重复 `island`/`config` 单例,esbuild 的 CJS 打包无法代码分割,故保留) | | 客户端 `win-unpacked` | 372MB | **323MB** | `electronLanguages: [zh-CN]`,只留中文语言包(其余 54 个 `locales/*.pak` 共 48MB) | -| 客户端 `setup.exe` / `portable.exe` | 107MB | **98MB** | 同上。**安装包有硬底**:`班级小助手.exe`(electron.exe)压缩后本身就有 85~90MB,60MB 不可达 | +| 客户端 `setup.exe` / `portable.exe` | 107MB | **98MB** | 同上。**安装包有硬底**:`ClassHelper.exe`(electron.exe)压缩后本身就有 85~90MB,60MB 不可达 | | 服务端 `dist` | 3.8MB | **0.6MB** | `tsconfig.build.json` 关掉 `declaration`/`declarationMap`/`sourceMap`(占原体积 82%) | | 服务端免安装目录 | 436MB | **335MB** | 打包末尾的运行时裁剪:删掉 Prisma 为 postgresql/cockroachdb/sqlserver 准备的查询编译器副本、`*.map`、文档与测试夹具(共 2329 项 / 101MB) | | 服务端安装程序 | 71MB | **62MB** | 同上 | @@ -1302,7 +1306,7 @@ pnpm db:generate && pnpm --filter @classhelper/server db:deploy && pnpm db:seed ### 客户端(Windows EXE) ```bash -pnpm dist:dir # 免安装目录:releases/client/<版本>/免安装/班级小助手.exe +pnpm dist:dir # 免安装目录:releases/client/<版本>/免安装/ClassHelper.exe pnpm dist:win # nsis 安装包 + portable 单文件(需联网下载 NSIS 工具,可用镜像) ``` @@ -1313,7 +1317,7 @@ pnpm dist:win # nsis 安装包 + portable 单文件(需联网下载 NSIS electron-builder 的原始布局整理成 `安装包/`、`免安装/`、`构建中间/`; 收尾把交付物完整性标签改成 Medium(Low 标签会导致装不上 / 双击没反应,见 [8. 常见问题](#8-常见问题))。 -产物结构:`免安装/班级小助手.exe`(Electron 运行时)+ `resources/app.asar` +产物结构:`免安装/ClassHelper.exe`(Electron 运行时)+ `resources/app.asar` (仅含 `dist/` 与 `package.json`,运行时不需要 node_modules)。 > **把修复交付到「已安装的客户端」**:`pnpm dist:win` 只产出 `releases/client/<版本>/`。 @@ -1325,7 +1329,7 @@ electron-builder 的原始布局整理成 `安装包/`、`免安装/`、`构建 > # 2) 用新产物覆盖安装目录(不删 Uninstall 等安装器文件,所以不加 /MIR) > robocopy "packages\desktop-client\release\win-unpacked" "D:\class\@classhelperdesktop-client" /E /NFL /NDL /NJH /NJS /R:2 /W:1 > # 3) 实测**那个副本**真的带上了修复(packaged=true) -> pnpm verify:packaged --exe "D:\class\@classhelperdesktop-client\班级小助手.exe" +> pnpm verify:packaged --exe "D:\class\@classhelperdesktop-client\ClassHelper.exe" > ``` 最近一次打包实测(产物不入库): @@ -1333,7 +1337,7 @@ electron-builder 的原始布局整理成 `安装包/`、`免安装/`、`构建 | 产物 | 大小 | 冒烟结果 | | -------------------------------------- | ------ | -------------------------------------------------------------------- | | 服务端安装程序(内置 Node + Web 端) | ~62MB | ✅ 静默安装(升级保留 `.env` 与数据库)→ 自动建库建号 → 服务就绪 | -| `releases/client/<版本>/免安装/班级小助手.exe` | ~235MB | ✅ 打包版冒烟全绿,`packaged: true`,退出码 0 | +| `releases/client/<版本>/免安装/ClassHelper.exe` | ~235MB | ✅ 打包版冒烟全绿,`packaged: true`,退出码 0 | | `releases/client/<版本>/安装包/…-portable.exe` | ~98MB | ✅ 打包后冒烟全绿,`packaged: true`,退出码 0 | | `releases/client/<版本>/安装包/…-setup.exe` | ~98MB | ✅ 构建成功,已嵌入自定义图标 | | `releases/classisland-plugin/<版本>/…cipx` | ~88KB | ✅ 全量编译通过(1 警告 0 错误),包内 DLL 哈希与 `bin/Release` 一致 | @@ -1404,25 +1408,16 @@ node website/serve.mjs # http://127.0.0.1:5180,只是本地预览用 > 标题与引文用的是楷体(优先加载霞鹜文楷 Screen,失败回落到系统 `KaiTi` / `STKaiti`), > 正文用产品自己那套 UI 字体。字体是 CDN 引入的,**断网时会自动回落,不影响阅读**。 -#### 采集实机图(`website/assets/shots/`) +#### 采集实机图 -官网上那一栏实机截图**不是画的示意**,是脚本真的把程序跑起来截的。要重新采集: +官网上那一栏实机截图**不是画的示意**,是脚本真的把程序跑起来截的:Web / 手机走 +`website/tools/capture-shots.mjs`,客户端页面与灵动岛走客户端冒烟。 -```bash -pnpm dev:server # 后端(同时托管 Web 管理端) -node website/tools/capture-shots.mjs # → web-*.jpg / mobile-*.jpg - -# 客户端页面 + 灵动岛各形态(跑一遍客户端冒烟,顺便留档) -ISLAND_SHOTS_DIR=F:/ClassHelper/.cache/island-shots \ -ELECTRON_CLIENT_SHOTS_DIR=F:/ClassHelper/.cache/client-shots \ -ELECTRON_SMOKE_ISLAND_ACCENT='#0a84ff' ELECTRON_SMOKE_ISLAND_STYLE=black \ -ELECTRON_SMOKE_USER=teacher1 ELECTRON_SMOKE_PASSWORD=123456 \ - node packages/desktop-client/scripts/smoke.mjs -``` +展示图统一放在 `docs/images/` —— 文档站、README 与官网吃的是**同一份文件**, +重新采集的完整命令(含目录与清理)写在[验收与测试](dev/testing.md)的「截图留档」一节。 两个环境变量在 `smoke.ts` 里是**默认值为洋红**的开关:冒烟平时故意用洋红主题色把验证实例的岛 和用户自己的岛区分开(见 AGENTS.md §5 第 38 条),采集留档图时才换成产品默认外观。 -指向 `website/assets/shots/` 的那两个目录变量不在 `website/`,要自己拷过去。 > 采集脚本会把窗口**真的弹在屏幕上**(和 `verify:web` / `verify:desktop` 一样),跑的时候别动键鼠。 > 客户端留档会**临时停掉 CSS 动画**再截图:窗口被遮挡时 Chromium 冻结动画,页面入场动画的起始帧是 diff --git a/scripts/version.mjs b/scripts/version.mjs index 52e735a..575730a 100644 --- a/scripts/version.mjs +++ b/scripts/version.mjs @@ -4,8 +4,9 @@ * * ## 为什么要这个脚本 * - * 这个仓库里**同一个版本号写在 13 处(11 个文件)**:5 份 package.json、运行时常量、两个 compose 文件、 - * 插件清单、插件 C# 源码里的一个 const,以及官网页面上三处手写的版本号。手工改必然漏一处, + * 这个仓库里**同一个版本号写在 14 处(12 个文件)**:5 份 package.json、运行时常量、两个 compose 文件、 + * 插件清单、插件 C# 源码里的一个 const,以及官网页面上三处手写的版本号、下载页那一处兜底值。 + * 手工改必然漏一处, * 而漏掉的那处**不会报错**,只会在某个不常走的分支上表现为"版本号对不上": * * - `deploy/docker-compose*.yml` 镜像 tag 落后 → 服务器上跑的还是旧镜像; @@ -14,7 +15,9 @@ * (清单里的 version 与实际上报的 PluginVersion 不一致时, * Web 端「ClassIsland 联动」页显示的版本会对不上插件市场); * - `website/index.html` 的那三处 官网是纯静态站、无构建步骤,没有哪段代码会去读 - * `package.json`,漏改的后果是**首页上一直挂着旧版本号** —— 那是别人看到的第一眼。 + * `package.json`,漏改的后果是**首页上一直挂着旧版本号** —— 那是别人看到的第一眼; + * - `website/download.html` 的 `` 下载页拿它当"读不到版本清单"时的兜底, + * 漏改的后果是**离线访客点到的下载链接指向一个不存在的版本**。 * * 因此:**改版本号的唯一入口是这个脚本**。它按表逐个改写,任何一处没匹配到(或匹配到多处) * 就直接失败退出,不做"尽力而为"的部分改写。 @@ -74,8 +77,8 @@ const PLUGIN_TARGETS = [ ]; /** - * 官网首屏与页脚那三处手写的版本号。官网是纯静态站,没有构建步骤、也没有哪段代码会去读 - * `package.json`,所以这三处只能靠脚本一起改 —— 漏了的话官网会一直显示旧版本号, + * 官网首页与下载页上那几处手写的版本号。官网是纯静态站,没有构建步骤、也没有哪段代码会去读 + * `package.json`,所以这几处只能靠脚本一起改 —— 漏了的话官网会一直显示旧版本号, * 而它是别人看到的第一眼。 */ const SITE_TARGETS = [ @@ -90,6 +93,11 @@ const SITE_TARGETS = [ label: '(页脚)', pattern: /(

版本 )(\d+\.\d+\.\d+)( ·)/, }, + { + file: 'website/download.html', + label: '(版本清单的兜底值)', + pattern: /()/, + }, ]; const ALL_TARGETS = [...PRODUCT_TARGETS, ...PLUGIN_TARGETS, ...SITE_TARGETS]; diff --git a/website/404.html b/website/404.html index af89c73..f9a9c6c 100644 --- a/website/404.html +++ b/website/404.html @@ -13,7 +13,7 @@ - 页面不存在 | 班级小助手 + 页面不存在 | ClassHelper