feat: 分批发布 + 实时赛况页 + 显式批次层 + 成绩框实时提示 + 窄屏适配 + 版本号单一真相
## 分批发布(现场:100 米跑道只有 8 条,不能等所有人跑完才发) - 空成绩从"错误"降级为"提示",不再阻塞发布;真会算错(写法无法识别、秒位 ≥ 60、 手动名次缺名次)仍挡住。发布确认框说明"只发已填的 M 人,其余保持未发布"。 - 服务端核实:publishResults/computeEventScore 本就不会因缺成绩报错 (collectCandidates 直接跳过没有 results 的报名项),根因在前端防呆。 ## 实时赛况(新大屏页 + 定向接口) - 新增 `GET /api/display/live`(guest 可读):返回「当前项目」判定 + 该项目**全量**已发布成绩 (不受快照 latestResults 的 20 行截断)。回退链:manual 指定 > 排期命中 > 最近发布。 设置键 display.live_events / display.live_source。 - 大屏新页 `live`「实时赛况」:1 个项目单栏、2 个自动左右分栏;失败保留上一次画面 (下一场开始前持续显示上一场);30s 轮询(判定依赖当前时间)+ 快照变更触发。 - 管理端「大屏控制」新增「实时赛况」卡;该卡走 PUT /api/settings(admin-only), 故对非管理员整组置灰 + 提示条(不新增角色)。 ## 显式批次层(组别之下) - 迁移 0019 `entries.heat`;批次仅是录入/展示分组,**不参与算分**(有"分批与不分批结果 完全一致"的断言守住)。批量设置接口 + 导入 CSV 可选列 heat + 录入页批次下拉 + 一键均分(按 N 人切片,已有批次号接着往后排)。 ## 成绩输入框实时悬浮提示 - 聚焦格旁的浮动气泡:pointer-events:none、不占布局不改行高、贴底自动上翻; 合法显示解析结果(1:06 → = 66 秒)、可疑/不合法显示原因并带那一格原文; 与发布防呆共用同一套纯函数(entryGuards.ts),口径不会分叉。 ## 窄屏 / 手机端布局 - 真浏览器(Playwright + chromium)在 1440/1024/768/390 四宽度逐页审计 + 溢出探针, 四轮迭代修复;桌面端 1440 前后像素差异 < 0.25%(无回归);修掉如「修改密码」输入框 塌成 22px 这类真问题。 ## 版本号单一真相 - 新增仓库根 `VERSION`:构建时注入二进制(-X main.version)并同步 console/package.json 的版本(产物名跟随)。源码里不再有人工维护的版本号(兜底值改为 `dev`,故意不写数字)。 测试:服务端 13 包全绿;前端 vue-tsc 通过、249 例全过;Console 18 文件。 docs/ 与 devdocs/ 同步(FAQ 到 Q80、新增 devdocs/16 自定义大屏模板设计文档)。
This commit is contained in:
1 parent
e5e0944d26
commit
08a5740fb2
64 files changed
+4968
-270
No files matched your search
+12
-3
@@ -27,6 +27,11 @@ call pnpm build || exit /b 1
|
||||
cd ..
|
||||
|
||||
echo === [3/4] server build ===
|
||||
REM version: repo-root VERSION is the single source of truth. The Node script
|
||||
REM writes deploy\version.gen.bat (pure ASCII, for cmd) and syncs the installer
|
||||
REM version in console\package.json. Must run before the go build below.
|
||||
node deploy\prepare-extra.mjs || exit /b 1
|
||||
call deploy\version.gen.bat || exit /b 1
|
||||
cd server
|
||||
if not exist ..\dist mkdir ..\dist
|
||||
REM internal signature is injected from a machine-local file (gitignored, never in repo).
|
||||
@@ -36,6 +41,9 @@ REM deploy\signature.build.txt, and for /f reads that file directly (no re-encod
|
||||
node ..\deploy\prepare-signature.mjs || exit /b 1
|
||||
set "INTERNAL_SIGNATURE="
|
||||
if exist "..\deploy\signature.build.txt" for /f "usebackq delims= eol=" %%A in ("..\deploy\signature.build.txt") do if not defined INTERNAL_SIGNATURE set "INTERNAL_SIGNATURE=%%A"
|
||||
REM compile-time version: -X main.version targets package main (no import path),
|
||||
REM unlike the two vars below which are import-path-qualified.
|
||||
set "VERSION_LDFLAG=-X 'main.version=%SCOREBOARD_VERSION%'"
|
||||
REM build time for the About page. Do NOT use %DATE%/%TIME%: a localized Windows
|
||||
REM prints non-ASCII weekday names, which cmd mis-parses (see the header note).
|
||||
set "BUILD_TIME="
|
||||
@@ -43,14 +51,15 @@ for /f "usebackq delims=" %%T in (`powershell -NoProfile -Command "(Get-Date).To
|
||||
set "BUILD_TIME_LDFLAG=-X 'git.yeij.top/AskaEth/Scoreboard/server/internal/version.BuildTime=%BUILD_TIME%'"
|
||||
if defined INTERNAL_SIGNATURE (
|
||||
REM single quotes keep spaces inside the value intact for go's ldflags parser
|
||||
go build -ldflags "-X 'git.yeij.top/AskaEth/Scoreboard/server/internal/version.InternalSignature=%INTERNAL_SIGNATURE%' %BUILD_TIME_LDFLAG%" -o ..\dist\scoreboard.exe . || exit /b 1
|
||||
go build -ldflags "-X 'git.yeij.top/AskaEth/Scoreboard/server/internal/version.InternalSignature=%INTERNAL_SIGNATURE%' %VERSION_LDFLAG% %BUILD_TIME_LDFLAG%" -o ..\dist\scoreboard.exe . || exit /b 1
|
||||
node ..\deploy\verify-signature.mjs || exit /b 1
|
||||
echo internal signature injected
|
||||
) else (
|
||||
go build -ldflags "%BUILD_TIME_LDFLAG%" -o ..\dist\scoreboard.exe . || exit /b 1
|
||||
go build -ldflags "%VERSION_LDFLAG% %BUILD_TIME_LDFLAG%" -o ..\dist\scoreboard.exe . || exit /b 1
|
||||
echo no internal signature
|
||||
)
|
||||
echo build time %BUILD_TIME%
|
||||
echo version %SCOREBOARD_VERSION%
|
||||
cd ..
|
||||
|
||||
echo === [4/4] console package (electron-builder) ===
|
||||
@@ -64,7 +73,7 @@ if not exist "rules" (
|
||||
echo [ERROR] rules dir missing - electron-builder extraFiles needs it
|
||||
exit /b 1
|
||||
)
|
||||
node deploy\prepare-extra.mjs || exit /b 1
|
||||
REM prepare-extra.mjs already ran in step 3 (version, user docs, installer script).
|
||||
cd console
|
||||
REM console\pnpm-workspace.yaml uses allowBuilds so electron's install script is
|
||||
REM allowed to run; do NOT add --ignore-scripts here, it makes pnpm run fail its
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// 构建前准备两样东西:
|
||||
// 构建前准备三样东西:
|
||||
// 1) 把 docs/ 下的使用说明与报名名单导入规范暂存到 console/extra/,供 electron-builder 的 extraFiles 平铺到部署包根目录;
|
||||
// 2) 生成安装器自定义脚本(console/resources/installer-custom.nsh):许可页顶部文案(署名来自构建机本地文件)
|
||||
// + 记住上次安装目录(重装时沿用,而不是退回系统默认路径)。
|
||||
// + 记住上次安装目录(重装时沿用,而不是退回系统默认路径);
|
||||
// 3) 读仓库根 VERSION 文件:生成 deploy/version.gen.bat(供 build.bat 注入 Go 二进制)
|
||||
// 并同步 console/package.json 的 version(决定安装包/免安装包的文件名)。
|
||||
// 用 Node 而不是批处理:中文文件名/中文内容在 .bat 里会被 cmd 解析错乱。
|
||||
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
@@ -104,3 +106,48 @@ writeFileSync(nshFile, nsh, 'utf8')
|
||||
// 改名前生成的旧脚本已经没人引用,构建机上顺手清掉,免得看起来还在用。
|
||||
rmSync(legacyNshFile, { force: true })
|
||||
console.log(` installer custom script ready (${existsSync(signatureFile) ? 'with' : 'without'} internal signature)`)
|
||||
|
||||
// ---- 3. version -------------------------------------------------------------
|
||||
// 版本号只维护仓库根的 VERSION 文件(唯一真相):这里读出来分两路用——
|
||||
// deploy/version.gen.bat 给 build.bat,编译期用 -X main.version 写进二进制;
|
||||
// console/package.json electron-builder 据此给安装包/免安装包命名。
|
||||
// 二进制里的版本在编译那一刻定死,运行期不再读任何文件。
|
||||
const versionFile = join(root, 'VERSION')
|
||||
if (!existsSync(versionFile)) {
|
||||
console.error(' [version] missing VERSION file at repo root')
|
||||
process.exit(1)
|
||||
}
|
||||
const version = (readFileSync(versionFile, 'utf8')
|
||||
.replace(/^\uFEFF/, '')
|
||||
.split(/\r?\n/)
|
||||
.find((line) => line.trim() !== '') ?? '').trim()
|
||||
if (version === '') {
|
||||
console.error(' [version] VERSION file is empty')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// 必须纯 ASCII:cmd.exe 会错解 .bat 里的非 ASCII 文本(见本文件头部说明)。
|
||||
// 用 CRLF:与 .gitattributes 里 *.bat 的约定一致,cmd 在 Windows 上按 CRLF 读最稳。
|
||||
// 只 set 不加 setlocal:build.bat 用 call 调它,变量要留在调用方的环境里。
|
||||
const genBatFile = join(root, 'deploy', 'version.gen.bat')
|
||||
const genBat = [
|
||||
'@echo off',
|
||||
'REM Generated by deploy/prepare-extra.mjs from the VERSION file - do not edit.',
|
||||
`set "SCOREBOARD_VERSION=${version}"`,
|
||||
'',
|
||||
].join('\r\n')
|
||||
writeFileSync(genBatFile, genBat, 'utf8')
|
||||
console.log(` version ${version} -> deploy/version.gen.bat`)
|
||||
|
||||
// console/package.json 的 version 是 semver,不带 dev_ 前缀(dev_ 只用于二进制版本号)。
|
||||
// 逐字替换而不是 JSON.parse/stringify:保住原有缩进与末尾换行,改动最小。
|
||||
const pkgVersion = version.replace(/^dev_/, '')
|
||||
const pkgFile = join(root, 'console', 'package.json')
|
||||
const pkgRaw = readFileSync(pkgFile, 'utf8')
|
||||
const pkgNext = pkgRaw.replace(/^(\s*"version"\s*:\s*)"[^"]*"/m, `$1"${pkgVersion}"`)
|
||||
if (pkgNext === pkgRaw) {
|
||||
console.log(` console/package.json version is ${pkgVersion}`)
|
||||
} else {
|
||||
writeFileSync(pkgFile, pkgNext, 'utf8')
|
||||
console.log(` console/package.json version -> ${pkgVersion}`)
|
||||
}
|
||||
Reference in new issue
Block a user