Files
ClassHelper/website/download.html
T
LoveChengke 227117ccb3 fix(integrations,grades,clients,web,client,plugin): 课表同步改为按需(修「课表被反复删改」)+ 客户端页合并 + 成绩排名(1.3.1)
线上问题的根因
- ClassIsland 插件默认每 15 秒上报一次,每次都带全量课表,上报完还顺手把课表镜像回教室
  (本机 `UploadSchedule` / `MirrorSchedule` 两个开关默认都是开)。于是教室的课表每 15 秒被重写、
  服务端也每 15 秒 merge + 清理一次幽灵行 —— 老师看到的就是"课表被反复删改"。
- 现在两个方向都只由 Web 端的一次性待办触发,插件侧那两个开关整体删除;
  定时上报只报**运行状态**(当前上什么课),不带课表。

课表同步(#3)
- 新增 `IntegrationDevice.requestMirrorSchedule` + `POST /integrations/devices/:id/mirror-schedule`
  (「下发课表」),与既有的 `request-schedule`(「获取课表」)对称;拉取成功即清标记
- 插件 `ClassPlanWriter.Apply(..., force: true)`:停用同一天同一单双周相位下冲突的旧课表
  —— **只停用不删除**,内容原样留在档案里,老师随时能重新启用
- 插件设置页删掉「自动上报课表」「镜像回 ClassIsland」两个开关,改为一句说明

「客户端」页(#4)
- 把「ClassIsland 联动」与「客户端设备」合成一页 `ClientHubView.vue`:一个班一张卡片,
  两条链路状态放一起,勾选可批量下发课表 / 更新,点卡片进详情做逐台操作
- 「下发提醒」也搬了过来;入口权限用 `classisland:notify`(默认 ADMIN + TEACHER),
  页内按钮再各自按 device:manage / remote:control / client:update-push 显示或禁用
- 两个旧页面 `meta.hidden: true` **保留路由**(安装包缓存管理、联动步骤说明仍在),
  从详情抽屉里给链接 —— 不删功能

成绩(#5/#6/#7)
- 新增 `Exam`(班级 + 考试名 + `countInTotal`)与迁移;`GET/PUT /api/grades/exams`、
  `GET /api/grades/ranking`(两条都**不挂权限键**,班级端的有效权限是空集,边界在 service 里)
- 排名口径写在服务端:**同一学生同一科目只取最高分**(补录 / 重考会多行,不去重等于"总分多一科")、
  并列同名次(1、2、2、4)
- 「成绩录入」页加「考试口径」开关(按班级 + 考试名记住);客户端「成绩」页新增总分榜 + 单科榜
- `updateStudentGradeQuery` 改成只有教师 / 管理员能改 —— 原先班级端自己也能改
- 客户端的成绩查询开关改为只读(只显示服务端下发的状态)

其他(#2/#5/#8/#9)
- 全校学期开始日期改成 `AppSetting` 的 `term.startDate`,管理员在「学期周次」页直接改、立刻重算周次
  (原先这一级只能来自 `.env`)
- 大模型助手发紧急通知补上「上课时段 3 秒全屏警告」:判据抽成 `composables/useClassInSession.ts`,
  与手动发布页共用
- 客户端去掉「关怀模式」「自动更新」两个开关(教室机器是统一投放的终端)
- 修掉「选中设置二级菜单后收起分组、高亮块仍留在原地」:底片只在路由 / 滚动 / 缩放时重测,
   而收起分组动不了 `.aside` 的盒子 —— 补上"子项被 overflow:hidden 裁掉"的判据 + 观察分组 `<ul>` 高度
- `src/types/express.d.ts` 里 `req.device` 改成引用 `DeviceContext`:原先手抄了一份结构,
  往接口加字段时两边就漂了,而报错会落在 `getDevice()` 的 `return device;` 上,看不出真正原因

验收:`lint` / `typecheck` / `check:icons` / `docs:check` 全绿;
后端端到端 **247/247**、联动链路 **44/44**、插件静态契约 **76/76**、
Web 真实点击 **34/34**(含新增的「客户端」页用例)、客户端冒烟 **90/90**。
2026-10-11 16:21:38 +08:00

273 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>下载 ClassHelper —— 挑版本、挑平台</title>
<meta
name="description"
content="从 GitHub Releases 直接下载 ClassHelper:教室机器要的班级端、服务器要的服务端(Windows 安装程序 / Linux 压缩包)、教室大屏要的 ClassIsland 联动插件。版本与文件清单取自本仓库的发布页,显示什么就能下什么。"
/>
<meta name="theme-color" content="#16231e" />
<meta property="og:title" content="下载 ClassHelper" />
<meta
property="og:description"
content="班级端、服务端(Windows / Linux)、ClassIsland 联动插件 —— 选好版本,下面显示什么就能下什么。"
/>
<meta property="og:type" content="website" />
<!-- 页面上所有版本号与文件名都从这一行起步:脚本会去 GitHub Releases 取真实清单,
取不到时(离线、被限流)就按这份版本号 + 发布命名约定拼直链。
它和根 package.json 一起由 `pnpm version:bump` 维护 —— 别在这儿单独改。 -->
<meta name="ch-version" content="1.3.1" />
<link rel="icon" href="assets/icon.png" />
<link rel="apple-touch-icon" href="assets/icon.png" />
<!-- 标题与引文用的楷体(霞鹜文楷 Screen)。加载失败时自动回落到系统的楷体 / 衬线 -->
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/lxgw-wenkai-screen-webfont@1.7.0/style.css"
media="print"
onload="this.media = 'all'"
/>
<link rel="stylesheet" href="assets/styles.css" />
<!-- 脚本在,先把标题藏起来准备逐字上浮;4 秒兜底,脚本坏在半路也不会把标题永久藏住 -->
<script>
document.documentElement.classList.add('reveal-pending');
setTimeout(function () {
document.documentElement.classList.remove('reveal-pending');
}, 4000);
</script>
</head>
<body>
<a class="skip" href="#main">跳到正文</a>
<header class="topbar">
<div class="wrap topbar-inner">
<a class="brand" href="index.html" data-press="0.96">
<img class="brand-mark" src="assets/icon.png" alt="" width="32" height="32" />
<span class="brand-name">ClassHelper</span>
</a>
<nav class="topnav" aria-label="站内导航">
<span class="nav-pill" aria-hidden="true"></span>
<a href="index.html">官网首页</a>
<a href="index.html#parts">组成部分</a>
<a href="index.html#shots">实机</a>
<a href="docs/index.html">文档站</a>
</nav>
<a
class="btn btn-quiet"
href="https://github.com/LoveChengke/ClassHelper/releases"
target="_blank"
rel="noreferrer"
data-press="0.95"
>
全部版本
</a>
</div>
<!-- 阅读进度:beUI ScrollProgressBar(2px,origin-left,弹簧缓跟) -->
<div class="scroll-progress" aria-hidden="true"></div>
</header>
<main id="main">
<!-- ═══════════════════════ 首屏:挑版本、挑平台 ═══════════════════════ -->
<section class="band band-board hero" id="top">
<div class="wrap">
<div class="hero-intro">
<h1 class="display" data-reveal>
<span class="reveal-line">先挑一个版本,</span>
<span class="reveal-line reveal-grad">再挑要装的那一份</span>
</h1>
<p class="hero-sub intro-seq" style="--intro-delay: 150ms">班级端 · 服务端 · ClassIsland 插件</p>
<p class="lead intro-seq" style="--intro-delay: 200ms">
下面的版本与文件清单是当场从本仓库的 GitHub Releases 读出来的:文件名、体积都照抄发布页,
页面显示什么,就真能下到什么。
</p>
<!-- 版本选择:Tab 由 assets/download.js 按发布列表建出来 -->
<div class="dl-pick intro-seq" style="--intro-delay: 250ms">
<p class="sr-only" id="ver-label">选择版本</p>
<div class="dl-seg" role="tablist" aria-labelledby="ver-label" id="ver-tabs">
<span class="dl-seg-indicator" id="ver-indicator" aria-hidden="true"></span>
</div>
<p class="dl-pick-note" id="ver-note" role="status">正在读取版本列表……</p>
</div>
<noscript>
<p class="dl-pick-note">
这一页的版本清单要脚本才能读。直接去
<a href="https://github.com/LoveChengke/ClassHelper/releases">GitHub Releases</a>
挑一份 —— 文件名与体积那儿都有。
</p>
</noscript>
<div class="actions intro-seq" style="--intro-delay: 300ms">
<a
class="btn btn-solid"
id="ver-primary"
href="https://github.com/LoveChengke/ClassHelper/releases/latest"
data-press="0.93"
>
下载最新版客户端
</a>
<a
class="btn btn-line"
href="https://github.com/LoveChengke/ClassHelper/releases"
target="_blank"
rel="noreferrer"
data-press="0.93"
>
全部版本与更新日志
</a>
</div>
<ul class="hero-badges intro-seq" style="--intro-delay: 350ms">
<li>Windows 10/11 x64</li>
<li>Linux x64 · Docker</li>
<li>ClassIsland 2.x 插件</li>
<li>包内自带 Node 运行时</li>
</ul>
</div>
</div>
</section>
<!-- ═══════════════════════ 装什么、装到哪台机器 ═══════════════════════ -->
<section class="band band-paper" id="pick">
<div class="wrap">
<h2 class="h2">装什么,挑一下</h2>
<p class="lead narrow">
四个交付物互不依赖安装顺序,但
<strong>服务端要先有一台机器跑着</strong>
,另外三个才连得上 —— 不确定就先切到「服务端」看一眼。
</p>
<div class="dl-groups">
<p class="dl-pick-label" id="group-label">装什么</p>
<div class="dl-seg" role="tablist" aria-labelledby="group-label" id="group-tabs">
<span class="dl-seg-indicator" id="group-indicator" aria-hidden="true"></span>
<button class="dl-seg-item" type="button" role="tab" data-value="client">客户端</button>
<button class="dl-seg-item" type="button" role="tab" data-value="server">服务端</button>
<button class="dl-seg-item" type="button" role="tab" data-value="plugin">插件</button>
</div>
<p class="dl-pick-note" id="group-note" role="status"></p>
</div>
<!-- 卡片由 assets/download.js 按「当前版本 + 当前分组」渲染 -->
<div class="dl-grid" id="dl-grid" role="tabpanel" tabindex="0" aria-live="polite"></div>
<p class="dl-note" id="dl-note"></p>
</div>
</section>
<!-- ═══════════════════════ Linux 与 Docker ═══════════════════════ -->
<section class="band band-board" id="server-linux">
<div class="wrap">
<h2 class="h2">Linux 服务器上,一行命令装完</h2>
<p class="lead narrow">
脚本自己去 Releases 取这一版的压缩包、核对旁边的
<code>.sha256</code>
,再装成 systemd 服务,顺手把
<code>classhelper</code>
运维命令放到
<code>/usr/local/bin</code>
里。
</p>
<div class="snippet">
<div class="snippet-head">
<span class="snippet-label">在服务器上跑这一条</span>
<button
class="copy"
type="button"
data-press="0.97"
data-copy="#cmd-linux"
data-copy-done="已复制"
>
<span class="copy-label">复制</span>
</button>
</div>
<pre
id="cmd-linux"
><code>curl -fsSL -o /tmp/classhelper-install.sh https://raw.githubusercontent.com/LoveChengke/ClassHelper/master/deploy/install.sh &amp;&amp; sudo bash /tmp/classhelper-install.sh</code></pre>
</div>
<p class="lead narrow">
想先不改动系统,在末尾加
<code>--check</code>
就只体检。装完之后
<code>classhelper upgrade</code>
、
<code>backup</code>
、
<code>doctor</code>
、
<code>password</code>
这些命令都在等着,升级与回滚也走它们。
</p>
<p class="lead narrow">
云服务器、多终端共享的场合可以走 Docker Compose —— MySQL 与 SQLite 两种编排都在仓库的
<code>deploy/</code>
里,镜像 tag 与版本号对齐。完整步骤见
<a href="docs/management/production.html">生产部署</a>
与
<a href="docs/management/linux-deploy.html">Linux 运维</a>
。
</p>
</div>
</section>
<!-- 收尾 CTA:装完去哪儿 -->
<section class="band cta" aria-label="装完之后">
<div class="wrap">
<!-- prettier-ignore -->
<h2 class="cta-title">装完,<span class="act-hl">从这三页开始</span></h2>
<p class="cta-lead">
一台机器装完就能跑起来,学生机和教室大屏接上同一个地址就行。第一次部署大约半小时,
每一步都对着截图写好了。
</p>
<div class="cta-actions">
<a class="btn btn-solid" href="docs/get-started/index.html" data-press="0.93">第一次装</a>
<a class="btn btn-line" href="docs/management/production.html" data-press="0.93">
部署形态怎么选
</a>
<a class="btn btn-line" href="docs/faq.html" data-press="0.93">装不上怎么办</a>
</div>
</div>
</section>
</main>
<!-- ═══════════════════════ 页脚 ═══════════════════════ -->
<footer class="foot">
<div class="wrap foot-inner">
<div class="foot-brand">
<img src="assets/icon.png" alt="" width="36" height="36" />
<div>
<p class="foot-name">
ClassHelper
<span>Class Helper</span>
</p>
<p class="foot-ver">版本以 GitHub Releases 为准 · 源码公开 · 装在你自己的机器上</p>
</div>
</div>
<nav class="foot-nav" aria-label="相关链接">
<a href="index.html">官网首页</a>
<a href="docs/index.html">文档站</a>
<a href="https://github.com/LoveChengke/ClassHelper/releases" target="_blank" rel="noreferrer">
全部版本
</a>
<a href="https://github.com/LoveChengke/ClassHelper" target="_blank" rel="noreferrer">源码仓库</a>
<a href="https://classisland.tech" target="_blank" rel="noreferrer">ClassIsland</a>
</nav>
</div>
</footer>
<script src="assets/motion.js" defer></script>
<script src="assets/main.js" defer></script>
<script src="assets/download.js" defer></script>
</body>
</html>