Files
ClassHelper/website/index.html
T
LoveChengke c7f215a09c feat(courseware,docs): 完善课件传输与岛编辑并统一全仓文档
- 增加课件上传、班级可见范围、设备补收与传输进度
- 整合自定义灵动岛编辑器和相关客户端交互
- 同步三端契约、数据库迁移、部署配置与隔离回归
- 按源码统一文档与官网说明,移除过时任务书和审查记录
- 保持产品版本为 1.4.0
2026-10-18 11:53:37 +08:00

755 lines
36 KiB
HTML
Raw 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 Class Helper — 知道现在是第几节课的班级信息系统</title>
<meta
name="description"
content="ClassHelper是一套自托管的班级信息管理系统:课表、作业、通知、成绩、叫人合在一个后端,教室大屏与每台学生机实时收到,上课时段自动不打扰课堂。支持 Windows 单机、Linux 服务器与 Docker 部署。"
/>
<meta name="theme-color" content="#16231e" />
<meta property="og:title" content="ClassHelper Class Helper" />
<meta
property="og:description"
content="课表、作业、通知、成绩、叫人合在一个后端,教室大屏与每台学生机实时收到,上课时段自动不打扰课堂。"
/>
<meta property="og:type" content="website" />
<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="#top" data-press="0.96">
<img class="brand-mark" src="assets/icon.png" alt="" width="32" height="32" />
<span class="brand-name">ClassHelper</span>
<span class="brand-ver">1.4.0</span>
</a>
<nav class="topnav" aria-label="页内导航">
<span class="nav-pill" aria-hidden="true"></span>
<a href="#journey">一条通知的旅程</a>
<a href="#policy">上课时段策略</a>
<a href="#parts">组成部分</a>
<a href="#shots">实机</a>
<a href="#deploy">部署</a>
<a href="#verify">验收</a>
<a href="docs/index.html">文档站</a>
</nav>
<a class="btn btn-quiet" href="download.html" 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">课表、作业、通知、成绩、叫人</p>
<p class="lead intro-seq" style="--intro-delay: 200ms">
一个后端全管,老师在电脑或手机上发布,教室大屏和每台学生机同时收到;
断网时用本地缓存接着看,网络恢复自动补齐。
</p>
<div class="actions intro-seq" style="--intro-delay: 250ms">
<a class="btn btn-solid" href="download.html" data-press="0.93">立即下载</a>
<a
class="btn btn-line"
href="https://github.com/LoveChengke/ClassHelper"
target="_blank"
rel="noreferrer"
data-press="0.93"
>
GitHub 仓库
</a>
</div>
<ul class="hero-badges intro-seq" style="--intro-delay: 300ms">
<li>v1.4.0</li>
<li>Windows 10/11</li>
<li>Linux · Docker</li>
<li>触摸屏可用</li>
</ul>
</div>
<div
class="screen"
role="img"
aria-label="教室机器上的界面示意:屏幕顶部中央浮着一个黑色灵动岛,正在展开老师发来的通知;下面是大时钟、正在上的数学课与下一节语文。"
>
<div class="screen-bar">
<span class="screen-dot" aria-hidden="true"></span>
<span class="screen-title">教室机器 · 高一(1)班</span>
<span class="screen-live">实时连接正常</span>
</div>
<div class="screen-stage" id="stage">
<div class="island-slot">
<div class="island" id="island" data-kind="pill">
<svg class="island-shape" id="island-shape" width="268" height="44" aria-hidden="true">
<defs>
<!-- 紧急/叫人卡片顶部那层呼吸光晕。真实产品用的是卡片内部的 inset 阴影,
这里用同一个 path 做一层渐变,效果一致且不会被窗口裁掉 -->
<radialGradient id="island-tint-urgent" cx="50%" cy="0" r="120%">
<stop offset="0" stop-color="#ff453a" stop-opacity="0.28" />
<stop offset="0.68" stop-color="#ff453a" stop-opacity="0" />
</radialGradient>
<radialGradient id="island-tint-call" cx="50%" cy="0" r="120%">
<stop offset="0" stop-color="#ff9f0a" stop-opacity="0.26" />
<stop offset="0.68" stop-color="#ff9f0a" stop-opacity="0" />
</radialGradient>
</defs>
<path class="island-base" id="island-path" d="" />
<path class="island-tint" id="island-tint" d="" />
</svg>
<div class="island-layers">
<div class="island-pill" id="island-pill">
<span class="pill-icon" id="pill-icon"></span>
<span class="pill-title" id="pill-title">新消息</span>
<span class="pill-sub" id="pill-sub">张老师 · 点击查看</span>
</div>
<!-- 卡片内容由 main.js 按 beUI DynamicIsland 的 Slot 机制挂进来 -->
</div>
</div>
</div>
<div class="screen-body">
<div class="clock">
<p class="clock-hm">
08:42
<span class="clock-s">07</span>
</p>
<p class="clock-week">第 6 教学周 · 单周 · 周二</p>
</div>
<div class="clock-now">
<p class="clock-current">
正在上
<b>数学</b>
</p>
<p class="clock-sub">08:00–08:45 · 教学楼 A301 · 距下课 3 分钟</p>
<div class="clock-bar"><i style="width: 88%"></i></div>
</div>
<div class="clock-next">
<p class="next-head">下一节</p>
<p class="next-name">
语文
<span>08:55–09:40</span>
</p>
<p class="next-sub">教学楼 A302 · 还有 13 分钟</p>
</div>
</div>
</div>
</div>
<p class="hero-cap">
上面这个灵动岛不是截图 —— 圆角路径与尺寸都是产品的(胶囊 268×44、展开 424×230、连续圆角 4.2
次方), 形变用的是 beUI 的 Dynamic Island 那套弹簧:壳 0.8 秒、回弹 0.2,内容层回弹 0.35。
</p>
<ul class="facts">
<li>装着 SQLite 就能跑,需要时一键换到 MySQL —— 数据也跟着搬过去。</li>
<li>课表由教师在管理端维护,教室客户端实时接收变更。</li>
<li>课件按日期与类型归档,教师可向授课班级共享或推送,灵动岛实时显示接收进度。</li>
<li>通知实时送达客户端;经典模式课堂暂存普通提醒,自定义模式提示铃铛。</li>
</ul>
</div>
</section>
<!-- ═══════════════════════ 下载:首屏下面直接给安装包 ═══════════════════════
与 /download.html 是同一段(同一套类名、同一个 assets/download.js)——
放这儿是为了让访客不跳页就能下;完整的下载页仍然保留(顶栏「下载」指着它)。 -->
<section class="band band-paper" id="download">
<div class="wrap">
<h2 class="h2">装什么,挑一下</h2>
<p class="lead narrow">
三个交付物互不依赖安装顺序,但
<strong>服务端要先有一台机器跑着</strong>
,管理端与客户端才连得上。 下面的版本与文件清单是当场从本仓库的 GitHub Releases
读出来的:显示什么,就真能下到什么。
</p>
<!-- 版本选择:Tab 由 assets/download.js 按发布列表建出来 -->
<div class="dl-pick">
<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>
<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>
</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>
<!-- 分幕标题:居中大标题 + 中间一段渐变高亮(照 ClassIsland 官网的分节写法) -->
<section class="band act" aria-label="第一部分:它是怎么工作的">
<div class="wrap">
<!-- prettier-ignore -->
<h2 class="act-title">它是<span class="act-hl">怎么工作</span>的</h2>
</div>
</section>
<section class="band band-board" id="journey">
<div class="wrap">
<h2 class="h2">从按下「发布」到教室屏幕上弹出来</h2>
<p class="lead narrow">
服务端保存内容后通过 WebSocket 广播,客户端刷新对应页面。实际延迟受网络与设备影响, 验收时由
<code>pnpm verify:e2e</code>
的本次输出记录。
</p>
<ol class="pipeline">
<li>
<p class="pipe-step">老师发布</p>
<p class="pipe-body">Web 管理端或手机浏览器。通知、作业、叫人、成绩都在这里录。</p>
</li>
<li>
<p class="pipe-step">后端落库</p>
<p class="pipe-body">Express 5 + Prisma 7 写进库,再按班级房间广播一条事件。</p>
</li>
<li>
<p class="pipe-step">教室收到</p>
<p class="pipe-body">
Socket.IO 推到
<code>class:班级</code>
房间 —— 教室机器、学生机、大屏同时收到。
</p>
</li>
<li>
<p class="pipe-step">判一次时间</p>
<p class="pipe-body">课表服务回答「现在上不上课」,当场弹还是先收着,这一步就定下来了。</p>
</li>
<li>
<p class="pipe-step">客户端显示</p>
<p class="pipe-body">通知中心同步记录,灵动岛按经典或自定义模式提示。</p>
</li>
</ol>
</div>
</section>
<!-- ═══════════════════════ 上课时段策略 ═══════════════════════ -->
<section class="band band-board" id="policy">
<div class="wrap">
<h2 class="h2">上课的时候,通知会先排队</h2>
<p class="lead narrow">
以下示例展示经典固定模式。课表决定课堂状态;自定义常驻模式通过铃铛提示消息,由用户主动展开。
</p>
<div class="policy" id="policy-widget">
<div class="policy-clock">
<p class="policy-time" id="policy-time">09:20</p>
<p class="policy-state" id="policy-state">正在上 第三节 · 数学</p>
</div>
<div class="day">
<div class="day-track" id="day-track" aria-hidden="true"></div>
<div class="day-marker" id="day-marker" aria-hidden="true"></div>
<input
class="day-input"
id="day-input"
type="range"
min="420"
max="1030"
step="1"
value="560"
aria-label="拖动选择一天中的时刻,查看经典模式在课堂和课间的通知表现"
/>
<div class="day-scale" aria-hidden="true">
<span>07:00</span>
<span>10:00</span>
<span>13:00</span>
<span>16:00</span>
<span>17:10</span>
</div>
</div>
<div class="outcomes">
<div class="outcome">
<p class="outcome-what">普通通知</p>
<p class="outcome-where">学生机 · 灵动岛</p>
<p class="outcome-say" id="o-normal-client">先收着,打下课铃自动弹出来</p>
</div>
<div class="outcome">
<p class="outcome-what">普通通知</p>
<p class="outcome-where">教室大屏 · ClassHelper</p>
<p class="outcome-say" id="o-normal-screen">先收着,下课补弹</p>
</div>
<div class="outcome outcome-alert">
<p class="outcome-what">紧急通知 / 紧急叫人</p>
<p class="outcome-where">教室大屏 · ClassHelper</p>
<p class="outcome-say" id="o-urgent-screen">灵动岛立即展开提醒</p>
</div>
<div class="outcome">
<p class="outcome-what">不管什么时候</p>
<p class="outcome-where">通知中心</p>
<p class="outcome-say">照常记一条,未读红点随时可查,不会被吞掉</p>
</div>
</div>
<p class="policy-note">
经典模式普通叫人上课时保留可点击胶囊,不自动展开;紧急叫人立即展开。自定义模式都先提示铃铛。
那是老师正在等学生。老师在 Web 端发紧急通知时会先撞上全屏二次确认,确认按钮带 3 秒倒计时,
防止手滑。这一层拦在服务端,绕过界面直接调接口也一样会被拒。
</p>
</div>
</div>
</section>
<!-- 分幕标题:居中大标题 + 中间一段渐变高亮(照 ClassIsland 官网的分节写法) -->
<section class="band act" aria-label="第二部分:它是怎么构成的">
<div class="wrap">
<!-- prettier-ignore -->
<h2 class="act-title">它是<span class="act-hl">怎么构成</span>的</h2>
</div>
</section>
<section class="band band-paper" id="parts">
<div class="wrap">
<h2 class="h2">一份代码,三个交付物</h2>
<p class="lead narrow">
一个 pnpm monorepo。三端共用同一份类型与权限矩阵,所以不会出现「Web 端说有、客户端说没有」。
</p>
<div class="table-scroll">
<table class="sheet">
<caption class="sr-only">三个交付物、它们的形态与使用者</caption>
<thead>
<tr>
<th scope="col">交付物</th>
<th scope="col">形态</th>
<th scope="col">谁在用</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">后端服务</th>
<td>
Express 5 + Prisma 7 + Socket.IO,接口前缀
<code>/api</code>
</td>
<td>管理端与客户端</td>
</tr>
<tr>
<th scope="row">Web 管理端</th>
<td>Vue 3 + Element Plus,手机可用,可在浏览器里装成应用</td>
<td>老师、管理员</td>
</tr>
<tr>
<th scope="row">桌面客户端</th>
<td>Electron + Vue 3,带灵动岛与系统托盘,断网可用</td>
<td>教室机器、学生机</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- ═══════════════════════ 实机截图 ═══════════════════════ -->
<section class="band band-board" id="shots">
<div class="wrap">
<h2 class="h2">每个界面都是跑起来截的</h2>
<p class="lead narrow">
下面没有一张是画的示意。这些图是脚本真的把后端、Web 管理端、教室客户端和灵动岛跑起来之后
一屏一屏截下来的 —— 数据也是库里真实的数据。
</p>
<div class="shots" id="shots-widget">
<div class="shots-tabs" role="tablist" aria-label="按端筛选截图">
<span class="shots-indicator" data-tabs-indicator aria-hidden="true"></span>
</div>
<div class="shots-grid" id="shots-grid" role="tabpanel" tabindex="0"></div>
</div>
<p class="shots-hint">点任意一张看大图。</p>
</div>
</section>
<!-- 大图浮层:从被点的那张缩略图原地展开(beUI 的 morphing lightbox 用共享 layoutId,这里用 FLIP 等价实现) -->
<div class="lightbox" id="lightbox" hidden>
<div class="lightbox-backdrop" id="lightbox-backdrop"></div>
<div class="lightbox-panel" id="lightbox-panel">
<img class="lightbox-img" id="lightbox-img" alt="" />
</div>
<p class="lightbox-cap" id="lightbox-cap"></p>
<button class="lightbox-close" id="lightbox-close" type="button" aria-label="关闭大图">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<path d="M6 6l12 12M18 6L6 18" />
</svg>
</button>
</div>
<!-- ═══════════════════════ 纸张:功能 ═══════════════════════ -->
<section class="band band-paper" id="features">
<div class="wrap">
<h2 class="h2">老师那边,教室这边</h2>
<div class="feature-groups">
<div class="feature-group">
<h3 class="h3">老师与管理员</h3>
<div class="feature-blocks">
<div class="feature-block">
<p class="feature-tag">#班级人员</p>
<h4 class="feature-title">建班、换班主任、录学生</h4>
<p class="feature-body">
名单可以直接导入表格。教师录入与重置密码只对管理员开放,班主任也不行。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#课表</p>
<h4 class="feature-title">按周排课,支持单双周</h4>
<p class="feature-body">
也可以直接导入 ClassIsland 的档案 JSON —— 节次时间跟着课表一起进来,不用排两遍。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#作业</p>
<h4 class="feature-title">按「所属日期」归类</h4>
<p class="feature-body">
晚上八点留的作业算今天。教室机器也能录;未交名单勾一下,其余同学自动标成已完成。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#通知与叫人</p>
<h4 class="feature-title">分优先级发布,点名分两档</h4>
<p class="feature-body">
普通叫人上课时先收着,紧急的才当场打断课堂 —— 那是老师正在等学生。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#成绩</p>
<h4 class="feature-title">单条、批量,或者整张表导进来,还带排名</h4>
<p class="feature-body">
重复判定按「同班 + 同学生 + 同考试 + 同课程」,导入报错精确到 Excel
行号。单科排名自动出;这次考试要不要把各科相加排总分榜,你说了算。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#数据库</p>
<h4 class="feature-title">备份、导入导出、一键换库</h4>
<p class="feature-body">
SQLite 与 MySQL 互相搬数据,中途失败会自动回滚 —— 现有的库不会被动到。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#权限</p>
<h4 class="feature-title">谁能做什么,点着勾</h4>
<p class="feature-body">
建权限组、把账号分进去。没分组的按角色默认生效,分组之后只按组的并集算 ——
既能放权,也能收权。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#教室机器</p>
<h4 class="feature-title">统一管理设备、远控与更新</h4>
<p class="feature-body">
统一查看客户端在线状态、版本与远控结果,按设备或班级下发更新;可立即安装,或等待放学后安装。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#大模型助手</p>
<h4 class="feature-title">说一句话就发出去</h4>
<p class="feature-body">
它只把话解析成待确认的卡片,你点了才落库 —— 权限与上课时段保护仍复用既有流程。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#批量发布</p>
<h4 class="feature-title">勾几个班,一次发完</h4>
<p class="feature-body">
通知与作业都能批量。个别班失败不影响其余班级,哪个班为什么没发出去会逐班写清楚。
</p>
</div>
</div>
</div>
<div class="feature-group">
<h3 class="h3">教室机器与学生机</h3>
<div class="feature-blocks">
<div class="feature-block">
<p class="feature-tag">#课表时间轴</p>
<h4 class="feature-title">大时钟、正在上的课、距下课倒计时</h4>
<p class="feature-body">已结束的置灰,正在上的带进度条,下一节还有多久开始 —— 一眼看完。</p>
</div>
<div class="feature-block">
<p class="feature-tag">#作业看板</p>
<h4 class="feature-title">按科目分卡,整屏缩放到刚好放下</h4>
<p class="feature-body">
标题和要求全文不截断,也不用上下翻页;全屏看板会自己算最合适的列数。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#灵动岛</p>
<h4 class="feature-title">消息到达的第一现场</h4>
<p class="feature-body">
无边框透明置顶的小窗,通知、作业、叫人在里面展开成卡片;鼠标穿透,不占任务栏。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#成绩排名</p>
<h4 class="feature-title">教室里就能看排名</h4>
<p class="feature-body">
单科榜按得分率排、并列同名次;老师把这次考试设成「各科相加」后,还多一张总分榜。开不开由服务端定,教室里只读。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#离线缓存</p>
<h4 class="feature-title">断网的时候接着看</h4>
<p class="feature-body">
课表、作业、通知、成绩先落本地,断网顶部提示离线、照旧显示,网络恢复自己刷新。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#外观</p>
<h4 class="feature-title">深浅两套,岛也能整</h4>
<p class="feature-body">
高度、宽度、圆角、字号、停靠位置、视觉风格都能调;越界的值在主进程统一夹紧。
</p>
</div>
<div class="feature-block">
<p class="feature-tag">#自愈</p>
<h4 class="feature-title">崩了会自己回来</h4>
<p class="feature-body">
渲染进程挂了先安静地重建窗口;断网期间错过的通知,重新上线时补弹一次。
</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 分幕标题:居中大标题 + 中间一段渐变高亮(照 ClassIsland 官网的分节写法) -->
<section class="band act" aria-label="第三部分:它是怎么到你手里的">
<div class="wrap">
<!-- prettier-ignore -->
<h2 class="act-title">它是<span class="act-hl">怎么到你手里</span>的</h2>
</div>
</section>
<section class="band band-paper" id="deploy">
<div class="wrap">
<h2 class="h2">四种装法,按场景挑</h2>
<div class="table-scroll">
<table class="sheet">
<caption class="sr-only">四种部署形态与各自入口</caption>
<thead>
<tr>
<th scope="col">场景</th>
<th scope="col">形态</th>
<th scope="col">入口</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">教师电脑、学校机房单机</th>
<td>Windows 服务端安装程序,内置 Node 运行时</td>
<td>双击安装,自动建库建号、开机自启</td>
</tr>
<tr>
<th scope="row">云服务器、多终端共享</th>
<td>Docker Compose,MySQL 与 SQLite 两种编排都在仓库里</td>
<td><code>deploy/docker-compose.yml</code></td>
</tr>
<tr>
<th scope="row">已有 Linux 服务器</th>
<td>
一行命令装成 systemd 服务,附带
<code>classhelper</code>
运维命令
</td>
<td>见下面的命令</td>
</tr>
<tr>
<th scope="row">已有 Node 环境</th>
<td>从源码构建,使用已安装的 Node</td>
<td>
<code>pnpm build</code>
后启动服务端,详见部署文档
</td>
</tr>
</tbody>
</table>
</div>
<div class="snippet">
<div class="snippet-head">
<span class="snippet-label">Linux 服务器上跑这一条</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">
脚本自己从 GitHub Release 取最新版、核对发布页上的
<code>.sha256</code>
,再装成 systemd 服务。 想先不改动系统,把末尾换成
<code>--check</code>
就只体检。装完之后,
<code>classhelper upgrade</code>
、
<code>backup</code>
、
<code>doctor</code>
、
<code>password</code>
这些命令都在
<code>/usr/local/bin</code>
里等着。
</p>
<p class="lead narrow">
学生机这边是安装包和单文件版两种:安装包走 NSIS,单文件版不用装、放 U 盘里就能分发。
两个都不写死服务器地址 —— 第一次打开时填教室的地址和班级码就行。
</p>
<div class="actions">
<a class="btn btn-line" href="download.html" data-press="0.93">去下载页挑一份</a>
</div>
</div>
</section>
<!-- ═══════════════════════ 验收 ═══════════════════════ -->
<section class="band band-board" id="verify">
<div class="wrap">
<h2 class="h2">验收脚本可以重复运行</h2>
<p class="lead narrow">
仓库里带着这几套脚本,跑到哪儿、过了几条由脚本自己数,不是文档里写死的数字。 交付前它们必须全绿 ——
包括对
<strong>已经打包装好</strong>
的那份客户端再跑一遍。
</p>
<div class="ledger">
<div class="ledger-row">
<code class="ledger-cmd">pnpm verify:e2e</code>
<span class="ledger-what">
接口、实时推送、权限越权、上课时段、表格导入、班级账号、数据库管理、更新检查
</span>
<span class="ledger-num">隔离</span>
</div>
<div class="ledger-row">
<code class="ledger-cmd">pnpm verify:desktop</code>
<span class="ledger-what">默认后台快速检查:启动、桥接、缓存与关闭;视觉验收需 --visual</span>
<span class="ledger-num">后台</span>
</div>
<div class="ledger-row">
<code class="ledger-cmd">pnpm verify:packaged</code>
<span class="ledger-what">对打包后 / 已安装的那份 EXE 复跑同一套冒烟</span>
<span class="ledger-num">后台</span>
</div>
</div>
<p class="ledger-foot">
覆盖范围和数量以本次输出为准;默认后台冒烟不占用桌面,动画和透明度使用显式视觉验收。
</p>
</div>
</section>
<!-- 收尾 CTA:和 ClassIsland 官网一样,末尾放一整块「下一步做什么」 -->
<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="download.html" data-press="0.93">取安装包</a>
<a
class="btn btn-line"
href="https://github.com/LoveChengke/ClassHelper"
target="_blank"
rel="noreferrer"
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">版本 1.4.0 · 源码公开 · 装在你自己的机器上</p>
</div>
</div>
<nav class="foot-nav" aria-label="相关链接">
<a href="https://github.com/LoveChengke/ClassHelper" target="_blank" rel="noreferrer">源码仓库</a>
<a href="docs/index.html">文档站</a>
<a href="docs/management/production.html">生产部署</a>
<a href="docs/management/linux-deploy.html">Linux 运维</a>
<a href="docs/management/mysql.html">切换 MySQL</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>