feat(website,docs,build): 官网加一个下载页(挑版本装机),文档站的照片换成与 README 同一份
下载页(`website/download.html` + `assets/download.js`) - 官网首页的「立即下载」、顶栏与页脚的「取安装包」全部指向新页 `/download.html`。 那一页是「先挑一个版本,再挑要装的那一份」:一个版本分段控件 + 四张卡 (班级端 / 服务端 Windows / 服务端 Linux / ClassIsland 插件),每张卡列出这一版里 真实存在的文件、体积与直链; - 版本与文件清单**不在站点里存副本**,每次打开都由 `assets/download.js` 当场读 GitHub Releases —— 文件名、体积、链接照抄发布页,不靠命名约定去猜; - 读不到时(离线,或匿名接口 60 次/小时按出口 IP 被限流)退回页面上那份 `<meta name="ch-version">`,按发布命名约定拼直链,并在页面上**明说这是拼出来的**; - 老版本缺哪个包就显示「这一版没有发布…」(`v0.1.0-fix` 只有插件包,实测正确); Linux 那一行安装命令跟着选中版本钉上 `--version`(tag 不是 `x.y.z` 时不带); - 动态建出来的卡片与文件行派发 `ch:content`,由 `main.js` 补挂按压反馈 —— 这些元素是拉到清单之后才出现的,启动时还看不到它们。 文档站的照片(`docs/images/`) - 页面里的图一律从 `docs/screenshots/**` 换成 `docs/images/**`:后者就是 README 与官网 吃的那一份(字节相同),前者是 9-26 的冒烟留档,比 README 落后一个版本; - 展示图与回归留档这两套图的分工写进 `docs/dev/testing.md`「截图留档」,含重新采集命令 (冒烟会把「岛外留白对照图」落在同一个目录,所以要先落临时目录再挑); - 顺带修 README 手机截图那张标错的图注(`mobile-dashboard.jpg` 是仪表盘,不是抽屉菜单); - `docs/reference.md` 验收表里那张 ClassIsland「真机实测截图」不再嵌图:它是连着桌面、 任务栏和另一个窗口整屏截下来的,当文档配图不能用。断言与用例名都保留,另外注明了抓图位置。 版本落点 - `scripts/version.mjs` 增加 `website/download.html` 的兜底版本号,落点 13 → 14 处 (`AGENTS.md` §4.4 与 `docs/dev/release.md` 同步); - README「开始使用」表里那三个文件名改成**发布页上真实的名字** (`-x64-client-setup` / `-x64-client-portable` / `-x64-server-setup`)—— 原来写的是构建 产物的名字;「上传前把产物改成 ASCII 名」这一步也一并写进发布流程, 它是官网下载页离线兜底与 README 表格的共同依据。
This commit is contained in:
1 parent
ded10118f5
commit
0ea6bd887f
15 files changed
+1235
-167
No files matched your search
+2
-1
@@ -13,7 +13,7 @@
|
||||
<base href="{{BASE}}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>页面不存在 | 班级小助手</title>
|
||||
<title>页面不存在 | ClassHelper</title>
|
||||
<style>
|
||||
:root {
|
||||
--board: #16231e;
|
||||
@@ -126,6 +126,7 @@
|
||||
<p>链接可能过期了,或者这一页已经被重写进了别的章节。</p>
|
||||
<div class="links">
|
||||
<a class="primary" href="index.html">回到官网首页</a>
|
||||
<a href="download.html">下载</a>
|
||||
<a href="docs/index.html">文档站</a>
|
||||
<a href="docs/get-started/index.html">快速上手</a>
|
||||
<a href="docs/faq.html">常见问题</a>
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
/**
|
||||
* ClassHelper · 下载页(download.html)
|
||||
*
|
||||
* 这一页只做一件事:**挑一个版本,挑一份文件,点下去就下**。
|
||||
* 版本与文件清单有两种来路,按可靠性兜底:
|
||||
*
|
||||
* ① 当场读 GitHub Releases 接口 —— 拿到的就是发布页上真实存在的资源,
|
||||
* 文件名与体积照抄,不靠命名约定去猜(这是正常路径);
|
||||
* ② 读不到时(离线、被限流、被墙)退回 HTML 里那份 `<meta name="ch-version">`,
|
||||
* 按**发布的命名约定**拼直链,并在页面上明说"这是拼出来的"。
|
||||
*
|
||||
* 为什么不把清单预生成成 JSON 放进站点:那样只在部署那一刻是新的,
|
||||
* 发了新版本得等下一次推 master 才更新。而这一页几乎每一步都指回 github.com,
|
||||
* 真离线时它本来也用不了 —— 所以真正要兜的只有"接口被限流",
|
||||
* 那由 ② 兜住就够了。匿名接口每小时 60 次、按出口 IP 算,机房出口很容易撞到。
|
||||
*
|
||||
* 动效沿用 assets/motion.js 那套 beUI token(版本分段控件的指示器与首页实机截图那组同源)。
|
||||
* 建完卡片会派发一次 `ch:content`,让 main.js 把按压反馈补挂上去
|
||||
* —— 这些元素是拉到清单之后才出现的,main.js 启动时还看不到它们。
|
||||
*/
|
||||
|
||||
(() => {
|
||||
'use strict';
|
||||
|
||||
const REPO = 'LoveChengke/ClassHelper';
|
||||
const RELEASES_API = `https://api.github.com/repos/${REPO}/releases?per_page=30`;
|
||||
const RELEASES_PAGE = `https://github.com/${REPO}/releases`;
|
||||
|
||||
/** 版本选择器最多列几版;更早的去 Releases 页看 */
|
||||
const MAX_VERSIONS = 12;
|
||||
|
||||
const motion = window.CHMotion ?? null;
|
||||
|
||||
const $ = (selector, root = document) => root.querySelector(selector);
|
||||
const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];
|
||||
|
||||
/** 页面上那份兜底版本号,由 `pnpm version:bump` 与根 package.json 一起维护 */
|
||||
const FALLBACK_VERSION = ($('meta[name="ch-version"]')?.content ?? '').trim();
|
||||
|
||||
/**
|
||||
* 四个交付物 = 四张卡;卡里每一行 = Release 上的一个文件。
|
||||
*
|
||||
* `match` 用来在真实资源里认领这个文件,`expect` 在没有接口时按命名约定拼直链的名字。
|
||||
* **两个都要跟着发布流程一起改** —— 名字口径与 README「开始使用」那张表、
|
||||
* `.cache/release-notes-*.md` 里那份清单是同一套。
|
||||
*/
|
||||
const CARDS = [
|
||||
{
|
||||
key: 'client',
|
||||
who: '教室机器 / 学生电脑',
|
||||
name: 'ClassHelper 班级端',
|
||||
desc: '第一次打开时填服务器地址和班级码,之后开机就能看课表、作业、通知与成绩;断网时读本地缓存,恢复后自己刷新。',
|
||||
files: [
|
||||
{
|
||||
role: '安装版',
|
||||
note: 'NSIS 安装程序,装完在开始菜单里',
|
||||
match: /-client-setup\.exe$/i,
|
||||
expect: (version) => `ClassHelper-${version}-x64-client-setup.exe`,
|
||||
},
|
||||
{
|
||||
role: '单文件版',
|
||||
note: '不用安装,放 U 盘里就能分发',
|
||||
match: /-client-portable\.exe$/i,
|
||||
expect: (version) => `ClassHelper-${version}-x64-client-portable.exe`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'server',
|
||||
who: '教师电脑 / 学校服务器 · Windows',
|
||||
name: 'ClassHelper 服务端',
|
||||
desc: '内含 Web 管理端与 Node 运行时,装上就算跑起来:建库、建号、开机自启都由它自己办。',
|
||||
files: [
|
||||
{
|
||||
role: '安装程序',
|
||||
note: '装完浏览器打开 http://<服务器地址>:4000',
|
||||
match: /-server-setup\.exe$/i,
|
||||
expect: (version) => `ClassHelper-${version}-x64-server-setup.exe`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'linux',
|
||||
who: 'Linux 服务器',
|
||||
name: '服务端 · systemd',
|
||||
desc: '一行命令装完,附带 classhelper 运维命令(status / upgrade / backup / doctor / password);也可以下压缩包自己解。',
|
||||
files: [
|
||||
{
|
||||
role: '安装包',
|
||||
note: '解压即用,含内置 Node 运行时',
|
||||
match: /^classhelper-server-linux-x64-.*\.tar\.gz$/i,
|
||||
expect: (version) => `classhelper-server-linux-x64-${version}.tar.gz`,
|
||||
},
|
||||
{
|
||||
role: '校验值',
|
||||
note: '安装脚本会自动取它核对',
|
||||
match: /^classhelper-server-linux-x64-.*\.tar\.gz\.sha256$/i,
|
||||
expect: (version) => `classhelper-server-linux-x64-${version}.tar.gz.sha256`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'plugin',
|
||||
who: '教室大屏 · 装在 ClassIsland 上',
|
||||
name: 'ClassIsland 联动插件',
|
||||
desc: '把课表上报给服务端,把老师发的提醒弹到 ClassIsland 的全屏提醒上。上课时段的普通通知先收着,打下课铃补弹。',
|
||||
files: [
|
||||
{
|
||||
role: '插件包',
|
||||
note: '在 ClassIsland 的插件页里选它安装',
|
||||
match: /classislandplugin\.cipx$/i,
|
||||
expect: () => 'ClassHelper.ClassIslandPlugin.cipx',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const tabs = $('#ver-tabs');
|
||||
const indicator = $('#ver-indicator');
|
||||
const note = $('#ver-note');
|
||||
const grid = $('#dl-grid');
|
||||
const gridNote = $('#dl-note');
|
||||
const primary = $('#ver-primary');
|
||||
const command = $('#cmd-linux code');
|
||||
|
||||
if (!tabs || !indicator || !note || !grid || !gridNote || !primary || !command) return;
|
||||
|
||||
/** 当前选中的版本清单来自哪儿:live(读到的) / fallback(拼出来的) */
|
||||
let source = 'live';
|
||||
let releases = [];
|
||||
let current = null;
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
小工具
|
||||
══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
function element(tag, className, text) {
|
||||
const node = document.createElement(tag);
|
||||
if (className) node.className = className;
|
||||
if (text != null) node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
|
||||
/** 体积口径与 GitHub 发布页一致(1024 进制,标签写 MB) */
|
||||
function formatSize(bytes) {
|
||||
if (!bytes) return '';
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
const mb = bytes / 1024 / 1024;
|
||||
if (mb >= 1) return `${mb.toFixed(1)} MB`;
|
||||
return `${Math.round(bytes / 1024)} KB`;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
取清单
|
||||
══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
async function fetchReleases() {
|
||||
const response = await fetch(RELEASES_API, { headers: { accept: 'application/vnd.github+json' } });
|
||||
if (!response.ok) throw new Error(`GitHub 返回 ${response.status}`);
|
||||
const list = await response.json();
|
||||
if (!Array.isArray(list)) throw new Error('返回的不是发布列表');
|
||||
|
||||
return list
|
||||
.filter((item) => item && item.tag_name && !item.draft)
|
||||
.map((item) => ({
|
||||
tag: String(item.tag_name),
|
||||
version: String(item.tag_name).replace(/^v/i, ''),
|
||||
prerelease: Boolean(item.prerelease),
|
||||
publishedAt: String(item.published_at ?? '').slice(0, 10),
|
||||
notesUrl: item.html_url ?? `${RELEASES_PAGE}/tag/${item.tag_name}`,
|
||||
assets: (item.assets ?? []).map((asset) => ({
|
||||
name: String(asset.name ?? ''),
|
||||
url: String(asset.browser_download_url ?? ''),
|
||||
size: Number(asset.size) || 0,
|
||||
})),
|
||||
}))
|
||||
.slice(0, MAX_VERSIONS);
|
||||
}
|
||||
|
||||
/** 最新的一版 = 第一个正式版(预发布不占这个位置) */
|
||||
function markLatest(list) {
|
||||
const index = list.findIndex((item) => !item.prerelease);
|
||||
if (index >= 0) list[index].latest = true;
|
||||
return list;
|
||||
}
|
||||
|
||||
/** 接口读不到时的替身:版本号取自页面上的 meta,文件名按发布命名约定拼 */
|
||||
function fallbackRelease() {
|
||||
const version = FALLBACK_VERSION || '0.0.0';
|
||||
const tag = `v${version}`;
|
||||
const assets = [];
|
||||
for (const card of CARDS) {
|
||||
for (const file of card.files) {
|
||||
const name = file.expect(version);
|
||||
assets.push({ name, url: `${RELEASES_PAGE}/download/${tag}/${name}`, size: 0 });
|
||||
}
|
||||
}
|
||||
return {
|
||||
tag,
|
||||
version,
|
||||
latest: true,
|
||||
prerelease: false,
|
||||
publishedAt: '',
|
||||
notesUrl: `${RELEASES_PAGE}/tag/${tag}`,
|
||||
assets,
|
||||
};
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
版本分段控件 —— beUI Tabs:一个指示器在选项之间滑移
|
||||
══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
let setIndicator = null;
|
||||
if (motion) {
|
||||
const x = new motion.Spring(0, motion.SPRING_LAYOUT, (value) => {
|
||||
indicator.style.transform = `translateX(${motion.round2(value)}px)`;
|
||||
});
|
||||
const width = new motion.Spring(0, motion.SPRING_LAYOUT, (value) => {
|
||||
indicator.style.width = `${motion.round2(value)}px`;
|
||||
});
|
||||
setIndicator = (left, size, animate) => {
|
||||
if (!animate || motion.reduceMotion()) {
|
||||
x.jump(left);
|
||||
width.jump(size);
|
||||
return;
|
||||
}
|
||||
// 首次落位时宽度弹簧还在 0,直接跳过去,免得从 0 撑开
|
||||
if (width.value === 0) width.jump(size);
|
||||
x.set(left);
|
||||
width.set(size);
|
||||
};
|
||||
} else {
|
||||
// 动效层没加载出来也要能用:直接落位
|
||||
setIndicator = (left, size) => {
|
||||
indicator.style.transform = `translateX(${left}px)`;
|
||||
indicator.style.width = `${size}px`;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 位置用 offsetLeft / offsetWidth 量:指示器是绝对定位在 `.dl-tabs`(position: relative)里的,
|
||||
* 两者的参照边都是容器的内边距边 —— 而且这样**与容器的横向滚动无关**
|
||||
* (版本多了以后这排是可以横滚的,用 getBoundingClientRect 会在滚动后对不上)。
|
||||
*/
|
||||
function moveIndicator(tag, animate) {
|
||||
const button = $$('.dl-tab', tabs).find((node) => node.dataset.tag === tag);
|
||||
if (!button) return;
|
||||
setIndicator(button.offsetLeft, button.offsetWidth, animate);
|
||||
}
|
||||
|
||||
function renderTabs() {
|
||||
$$('.dl-tab', tabs).forEach((node) => node.remove());
|
||||
|
||||
for (const release of releases) {
|
||||
const button = element('button', 'dl-tab');
|
||||
button.type = 'button';
|
||||
button.setAttribute('role', 'tab');
|
||||
button.dataset.tag = release.tag;
|
||||
button.dataset.press = '0.96';
|
||||
button.setAttribute('aria-selected', 'false');
|
||||
button.append(element('span', null, release.tag));
|
||||
|
||||
if (release.latest) button.append(element('span', 'dl-tab-flag', '最新'));
|
||||
else if (release.prerelease) button.append(element('span', 'dl-tab-flag dl-tab-flag-quiet', '预发布'));
|
||||
|
||||
button.addEventListener('click', () => select(release.tag));
|
||||
tabs.append(button);
|
||||
}
|
||||
|
||||
// 首帧之后重量一次:楷体是 CDN 来的,字体落定前量出来的宽度是错的
|
||||
moveIndicator(current?.tag ?? releases[0]?.tag, false);
|
||||
if (motion) {
|
||||
motion.nextFrame(() => moveIndicator(current?.tag ?? releases[0]?.tag, false));
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
卡片
|
||||
══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
function buildFileRow(file, asset) {
|
||||
const link = element('a', 'dl-file');
|
||||
link.href = asset.url;
|
||||
link.dataset.press = '0.98';
|
||||
|
||||
const top = element('span', 'dl-file-top');
|
||||
top.append(element('span', 'dl-file-role', file.role));
|
||||
const size = formatSize(asset.size);
|
||||
if (size) top.append(element('span', 'dl-file-size', size));
|
||||
|
||||
link.append(top, element('span', 'dl-file-name', asset.name), element('span', 'dl-file-note', file.note));
|
||||
|
||||
const row = element('li');
|
||||
row.append(link);
|
||||
return row;
|
||||
}
|
||||
|
||||
function buildCard(card, release) {
|
||||
const node = element('div', 'dl-card');
|
||||
node.append(
|
||||
element('p', 'dl-card-who', card.who),
|
||||
element('p', 'dl-card-name', card.name),
|
||||
element('p', 'dl-card-desc', card.desc),
|
||||
);
|
||||
|
||||
const rows = card.files
|
||||
.map((file) => ({ file, asset: release.assets.find((item) => file.match.test(item.name)) }))
|
||||
.filter((row) => row.asset);
|
||||
|
||||
if (rows.length) {
|
||||
const list = element('ul', 'dl-files');
|
||||
for (const row of rows) list.append(buildFileRow(row.file, row.asset));
|
||||
node.append(list);
|
||||
} else {
|
||||
node.append(element('p', 'dl-missing', `这一版没有发布「${card.name}」。`));
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/** 卡片下方那段「这一版还有什么」,顺便说明清单是从哪儿来的 */
|
||||
function renderNote(release) {
|
||||
gridNote.textContent = '';
|
||||
const say = (text) => gridNote.append(document.createTextNode(text));
|
||||
const link = (text, href) => {
|
||||
const anchor = element('a', null, text);
|
||||
anchor.href = href;
|
||||
anchor.target = '_blank';
|
||||
anchor.rel = 'noreferrer';
|
||||
gridNote.append(anchor);
|
||||
};
|
||||
|
||||
if (source !== 'live') {
|
||||
say(
|
||||
'现在读不到版本列表 —— 多半是离线,或者 GitHub 那个匿名接口被限流了(每小时 60 次,按出口 IP 算)。',
|
||||
);
|
||||
say(`下面只列了当前版本 ${release.tag},文件名是按发布命名约定拼出来的直链;打不开就去 `);
|
||||
link('Releases 页', RELEASES_PAGE);
|
||||
say(' 自己挑一份。');
|
||||
return;
|
||||
}
|
||||
|
||||
const sums = release.assets.find((asset) => /^SHA256SUMS-.*\.txt$/i.test(asset.name));
|
||||
say(
|
||||
`这一版${release.publishedAt ? `(${release.publishedAt} 发布)` : ''}在发布页上有 ${release.assets.length} 个文件。`,
|
||||
);
|
||||
if (sums) {
|
||||
say('全部文件的哈希在 ');
|
||||
link(sums.name, sums.url);
|
||||
say(' 里,装之前可以先核对;');
|
||||
}
|
||||
say('发布说明与更早的版本见 ');
|
||||
link('Releases', release.notesUrl);
|
||||
say('。这份清单每打开一次就重新读一次,站点里不留副本。');
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
选中某一版
|
||||
══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/** 版本选择器下面那行状态 —— 它要说清"现在选中的是哪一版" */
|
||||
function renderStatus(release) {
|
||||
if (source !== 'live') {
|
||||
note.textContent =
|
||||
'版本列表暂时取不到(离线,或 GitHub 的匿名接口被限流),下面是按发布命名约定拼出来的直链。';
|
||||
return;
|
||||
}
|
||||
const marks = [];
|
||||
if (release.latest) marks.push('最新版');
|
||||
else if (release.prerelease) marks.push('预发布');
|
||||
if (release.publishedAt) marks.push(`${release.publishedAt} 发布`);
|
||||
marks.push(`${release.assets.length} 个文件`);
|
||||
note.textContent = `已选 ${release.tag}(${marks.join(' · ')})· 列表读自本仓库的 GitHub Releases`;
|
||||
}
|
||||
|
||||
function select(tag, animate = true) {
|
||||
const release = releases.find((item) => item.tag === tag);
|
||||
if (!release) return;
|
||||
current = release;
|
||||
|
||||
for (const button of $$('.dl-tab', tabs)) {
|
||||
button.setAttribute('aria-selected', String(button.dataset.tag === tag));
|
||||
}
|
||||
moveIndicator(tag, animate);
|
||||
|
||||
grid.textContent = '';
|
||||
for (const card of CARDS) grid.append(buildCard(card, release));
|
||||
renderStatus(release);
|
||||
renderNote(release);
|
||||
|
||||
// 首屏那颗按钮:直接下这一版的服务端(其余三个包在下面自己挑)
|
||||
const serverCard = CARDS.find((card) => card.key === 'server');
|
||||
const server = release.assets.find((asset) => serverCard.files[0].match.test(asset.name));
|
||||
if (server) {
|
||||
primary.href = server.url;
|
||||
primary.textContent = `下 ${release.tag} 服务端`;
|
||||
} else {
|
||||
primary.href = release.notesUrl;
|
||||
primary.textContent = '去 Releases 挑一份';
|
||||
}
|
||||
|
||||
// Linux 那一行:钉到当前选中的版本(老版本 tag 不是 x.y.z 形式时不带 --version)
|
||||
const pinned = /^\d+\.\d+\.\d+$/.test(release.version) ? ` --version ${release.version}` : '';
|
||||
command.textContent = `${command.dataset.base}${pinned}`;
|
||||
|
||||
// 卡片与文件行是刚建出来的,让 main.js 把按压反馈补挂上
|
||||
document.dispatchEvent(new CustomEvent('ch:content'));
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
启动
|
||||
══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
async function boot() {
|
||||
// 命令原文先记下来:后面每次都从它拼(版本选择会改写这段文本)
|
||||
command.dataset.base = command.textContent.trim();
|
||||
|
||||
try {
|
||||
releases = markLatest(await fetchReleases());
|
||||
if (!releases.length) throw new Error('发布列表是空的');
|
||||
} catch (error) {
|
||||
console.warn('[download] 取版本列表失败,改用按命名约定拼出来的直链:', error.message);
|
||||
source = 'fallback';
|
||||
releases = [fallbackRelease()];
|
||||
}
|
||||
|
||||
renderTabs();
|
||||
select(releases[0].tag, false);
|
||||
|
||||
window.addEventListener('resize', () => moveIndicator(current?.tag, false), { passive: true });
|
||||
}
|
||||
|
||||
boot();
|
||||
})();
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级小助手 · 官网交互
|
||||
* ClassHelper · 官网交互
|
||||
*
|
||||
* 动效全部走 assets/motion.js 里那套 beUI token(曲线、弹簧、时长口径)。
|
||||
* 这里只做页面逻辑,逐块对着 beUI 的组件写:
|
||||
@@ -1353,4 +1353,9 @@
|
||||
setupNumberTickers();
|
||||
setupCopyButtons();
|
||||
setupShots();
|
||||
|
||||
// 下载页(download.html)的版本卡片是拉到 GitHub 清单之后才建出来的,建完会派发一次
|
||||
// `ch:content` —— 那时候启动已经跑过了,按压反馈得在这儿补挂一次
|
||||
// (bindPressable 对挂过的元素会自己跳过,重复派发不会重复绑定)。
|
||||
document.addEventListener('ch:content', setupPressable);
|
||||
})();
|
||||
+269
-1
@@ -1,5 +1,5 @@
|
||||
/* ============================================================================
|
||||
班级小助手 · 官网样式
|
||||
ClassHelper · 官网样式
|
||||
----------------------------------------------------------------------------
|
||||
两套表面,对应教室里的两样东西:
|
||||
|
||||
@@ -313,6 +313,22 @@ table {
|
||||
background: var(--board-raise);
|
||||
}
|
||||
|
||||
/* .btn-line 的颜色是给黑板调的(粉笔白),落在浅色纸面上会看不见 —— 纸面另给一套 */
|
||||
.band-paper .btn-line {
|
||||
border-color: var(--paper-rule);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.band-paper .btn-line:hover {
|
||||
border-color: var(--blue);
|
||||
background: var(--paper-raise);
|
||||
}
|
||||
|
||||
/* 纸面上的按钮行跟在正文后面,自己带一段上边距(黑板那边靠 .lead 的下边距撑开) */
|
||||
.band-paper .actions {
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
.btn-quiet {
|
||||
margin-left: auto;
|
||||
border-color: var(--board-line);
|
||||
@@ -2069,3 +2085,255 @@ html:not(.reveal-pending) .display .reveal-grad {
|
||||
transition-delay: 0s !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ════════════════════════════════ 下载页 ═══════════════════════════════
|
||||
download.html 专用。页面骨架与官网首页同一套(同样的色带、按钮、页脚),
|
||||
这里只补两样只有这一页才有的东西:版本分段控件、每个交付物的文件卡。
|
||||
|
||||
两条口径和首页一致:
|
||||
蓝(--blue-deep) 产品自己的强调色 —— 这里表示「这是一份真的能下的文件」
|
||||
卡片一律纸面(--paper-raise)——「谁装什么」是要照着一项项核对的,跟名单一样 */
|
||||
|
||||
/* ── 版本选择 ────────────────────────────────────────────────────────────
|
||||
与首页实机截图那组分段控件同一套做法(beUI Tabs:一个指示器在选项之间滑移),
|
||||
只是它管的是版本而不是图组,所以另起一套类名,不去动 .shots-*。 */
|
||||
|
||||
.dl-pick {
|
||||
margin: 34px 0 30px;
|
||||
}
|
||||
|
||||
.dl-pick-label {
|
||||
font-size: var(--fs-xs);
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--chalk-3);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dl-tabs {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--board-line);
|
||||
border-radius: 12px;
|
||||
background: var(--board-deep);
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.dl-tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dl-tab {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font: inherit;
|
||||
font-size: var(--fs-sm);
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--chalk-2);
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: color 0.18s var(--ease-out);
|
||||
}
|
||||
|
||||
.dl-tab:hover,
|
||||
.dl-tab[aria-selected='true'] {
|
||||
color: var(--chalk);
|
||||
}
|
||||
|
||||
/* 旗标:最新版 / 预发布。整排版本里只有它有,是这一行唯一的信息栅格 */
|
||||
.dl-tab-flag {
|
||||
font-size: 0.68rem;
|
||||
line-height: 1;
|
||||
padding: 3px 6px;
|
||||
border-radius: 999px;
|
||||
color: #cfe6fb;
|
||||
background: rgba(47, 143, 230, 0.18);
|
||||
border: 1px solid rgba(47, 143, 230, 0.4);
|
||||
}
|
||||
|
||||
.dl-tab-flag-quiet {
|
||||
color: var(--chalk-2);
|
||||
background: rgba(232, 238, 233, 0.07);
|
||||
border-color: var(--board-line);
|
||||
}
|
||||
|
||||
.dl-tabs-indicator {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
height: calc(100% - 8px);
|
||||
width: 0;
|
||||
border-radius: 9px;
|
||||
background: var(--board-raise);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 版本清单的来路 —— 这段话是这一页的"数据来源声明",取不到时要在这里说清楚 */
|
||||
.dl-pick-note {
|
||||
margin-top: 12px;
|
||||
font-size: var(--fs-xs);
|
||||
line-height: 1.85;
|
||||
color: var(--chalk-3);
|
||||
max-width: 48em;
|
||||
}
|
||||
|
||||
.dl-pick-note a {
|
||||
color: var(--chalk-2);
|
||||
}
|
||||
|
||||
.dl-pick-note code {
|
||||
color: var(--chalk-2);
|
||||
}
|
||||
|
||||
/* ── 文件卡(纸面) ───────────────────────────────────────────────────── */
|
||||
|
||||
/* 正好四张卡:宽度取 400px 起,桌面上一律两列(2×2 比 3+1 那种"落单"好读)。
|
||||
`min(400px, 100%)` 那层是必要的:只写 400px 的话,窄屏(比 400 还窄)上轨道会硬撑到 400,
|
||||
卡片就顶出视口右侧 —— 这是 auto-fit + minmax 的经典坑。 */
|
||||
.dl-grid {
|
||||
margin-top: 36px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(400px, 100%), 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.dl-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--paper-raise);
|
||||
border: 1px solid var(--paper-rule);
|
||||
border-radius: 6px;
|
||||
padding: 22px 22px 18px;
|
||||
}
|
||||
|
||||
/* 「谁在用」那行 —— 与首页特性块的 #标签同一个位置,用蓝色承担 */
|
||||
.dl-card-who {
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--blue-deep);
|
||||
}
|
||||
|
||||
.dl-card-name {
|
||||
margin-top: 5px;
|
||||
font-size: 1.18rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.45;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.dl-card-desc {
|
||||
margin-top: 8px;
|
||||
font-size: var(--fs-sm);
|
||||
line-height: 1.85;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.dl-files {
|
||||
margin-top: 16px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 一行 = 一个文件,整行可点(GitHub 的发布资源带 Content-Disposition,点了就下) */
|
||||
.dl-file {
|
||||
display: block;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--paper-rule);
|
||||
border-radius: var(--radius);
|
||||
background: var(--paper);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
border-color 0.16s var(--ease-out),
|
||||
background-color 0.16s var(--ease-out);
|
||||
}
|
||||
|
||||
.dl-file:hover,
|
||||
.dl-file:focus-visible {
|
||||
border-color: var(--blue);
|
||||
background: #eaf3fd;
|
||||
}
|
||||
|
||||
.dl-file-top {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dl-file-role {
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.dl-file-size {
|
||||
margin-left: auto;
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--ink-3);
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dl-file-name {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.6;
|
||||
color: var(--ink-2);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.dl-file-note {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
/* 这一版没打这个包(老版本里真的会缺) */
|
||||
.dl-missing {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
border: 1px dashed var(--paper-rule);
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.dl-note {
|
||||
margin-top: 24px;
|
||||
font-size: var(--fs-xs);
|
||||
line-height: 1.9;
|
||||
color: var(--ink-3);
|
||||
max-width: 54em;
|
||||
}
|
||||
|
||||
.dl-note code {
|
||||
background: rgba(23, 35, 31, 0.06);
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.dl-card {
|
||||
padding: 18px 16px 14px;
|
||||
}
|
||||
|
||||
.dl-file-name {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<!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.1.2" />
|
||||
<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="dl-pick-label" id="ver-label">版本</p>
|
||||
<div class="dl-tabs" role="tablist" aria-labelledby="ver-label" id="ver-tabs">
|
||||
<span class="dl-tabs-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">
|
||||
按「装在哪台机器上」挑:教室机器装班级端,服务器装服务端,教室大屏再给 ClassIsland 加一个插件。
|
||||
四个包互不依赖顺序,但
|
||||
<strong>服务端要先有一台机器跑着</strong>
|
||||
,别的才连得上。
|
||||
</p>
|
||||
|
||||
<!-- 卡片由 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 && 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>
|
||||
+12
-8
@@ -3,13 +3,13 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>班级小助手 Class Helper — 知道现在是第几节课的班级信息系统</title>
|
||||
<title>ClassHelper Class Helper — 知道现在是第几节课的班级信息系统</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="班级小助手是一套自托管的班级信息管理系统:课表、作业、通知、成绩、叫人合在一个后端,教室大屏与每台学生机实时收到,上课时段自动不打扰课堂。支持 Windows 单机、Linux 服务器与 Docker 部署。"
|
||||
content="ClassHelper是一套自托管的班级信息管理系统:课表、作业、通知、成绩、叫人合在一个后端,教室大屏与每台学生机实时收到,上课时段自动不打扰课堂。支持 Windows 单机、Linux 服务器与 Docker 部署。"
|
||||
/>
|
||||
<meta name="theme-color" content="#16231e" />
|
||||
<meta property="og:title" content="班级小助手 Class Helper" />
|
||||
<meta property="og:title" content="ClassHelper Class Helper" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="课表、作业、通知、成绩、叫人合在一个后端,教室大屏与每台学生机实时收到,上课时段自动不打扰课堂。"
|
||||
@@ -43,7 +43,7 @@
|
||||
<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">班级小助手</span>
|
||||
<span class="brand-name">ClassHelper</span>
|
||||
<span class="brand-ver">1.1.2</span>
|
||||
</a>
|
||||
<nav class="topnav" aria-label="页内导航">
|
||||
@@ -56,7 +56,7 @@
|
||||
<a href="#verify">验收</a>
|
||||
<a href="docs/index.html">文档站</a>
|
||||
</nav>
|
||||
<a class="btn btn-quiet" href="#deploy" data-press="0.95">取安装包</a>
|
||||
<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>
|
||||
@@ -80,7 +80,7 @@
|
||||
</p>
|
||||
|
||||
<div class="actions intro-seq" style="--intro-delay: 250ms">
|
||||
<a class="btn btn-solid" href="#deploy" data-press="0.93">立即下载</a>
|
||||
<a class="btn btn-solid" href="download.html" data-press="0.93">立即下载</a>
|
||||
<a
|
||||
class="btn btn-line"
|
||||
href="https://github.com/LoveChengke/ClassHelper"
|
||||
@@ -575,6 +575,10 @@
|
||||
学生机这边是安装包和单文件版两种:安装包走 NSIS,单文件版不用装、放 U 盘里就能分发。
|
||||
两个都不写死服务器地址 —— 第一次打开时填教室的地址和班级码就行。
|
||||
</p>
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn btn-line" href="download.html" data-press="0.93">去下载页挑一份</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -635,7 +639,7 @@
|
||||
不用先读一遍文档。
|
||||
</p>
|
||||
<div class="cta-actions">
|
||||
<a class="btn btn-solid" href="#deploy" data-press="0.93">取安装包</a>
|
||||
<a class="btn btn-solid" href="download.html" data-press="0.93">取安装包</a>
|
||||
<a
|
||||
class="btn btn-line"
|
||||
href="https://github.com/LoveChengke/ClassHelper"
|
||||
@@ -657,7 +661,7 @@
|
||||
<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.1.2 · 源码公开 · 装在你自己的机器上</p>
|
||||
|
||||
Reference in new issue
Block a user