28 Commits
Author SHA1 Message Date
AskaEth 08a5740fb2 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 自定义大屏模板设计文档)。
2026-09-11 04:05:02 +08:00
AskaEth 6483acf9ad feat: 人员年龄/号码布、图片上传、多显示器菜单、Console 两栏、exe 改名等一批改动
人员与报名:
- 人员唯一标识改为「证件号 或 号码布」二选一(0013/0014 迁移 + 导入匹配/回填/冲突)
- 人员新增「年龄」(0015 迁移):表单/列表/导入模板表头 11 列;冲突比较只在两边
  都有值且不同时才算差异,旧数据不误报
- 冲突决策键改为「证件号优先 → 号码布」(前端原先只用 idCard,号码布行会互相覆盖)
- 报名选择器支持无证件号但有号码布的运动员

大屏与 Console:
- 大屏控制新增「显示器」菜单:可给指定显示器重建全屏窗口;单屏/主显示器强制窗口化,
  限制在 Console 侧判定,不信任页面传参
- Console 服务管理窗口改左右两栏,运行日志独占右栏满高;窗口默认尺寸 1280×860
- 大屏右上角时钟精确到秒(同时把刷新间隔 15s 改为 1s)

图片与配置:
- 新增图片上传 POST /api/uploads(recorder+,png/jpg/webp/gif,≤5MiB)与匿名静态
  访问 GET /uploads/{name}(防目录穿越);文件落赛事项目目录 uploads/,随赛事包
  一起导出/导入
- 「赛事信息」新增赛事会标 / 大屏会标 / 大屏背景图三个上传项
- 参赛单位 logo 输入框右侧加上传按钮

关于页与构建:
- 构建时间打通:version.BuildTime + /api/system/about 返回 + build.bat 注入
  (用 PowerShell 取时间戳,避免本地化 %DATE% 的非 ASCII 星期)

安装包:
- 主程序改名 Scoreboard_Console.exe(win.executableName;productName/显示名不变)
- 记住上次安装目录:自定义 NSIS 宏优先沿用自存路径(卸载不清除),并在 customInit
  里补写回 electron-builder 的键,避免安装模式页把 $INSTDIR 打回默认

自检:服务端 12 包全绿;前端 vue-tsc + build 通过、node --test 58/58;console 12 文件通过。
2026-09-10 09:05:06 +08:00
AskaEth 8039da384d fix(build): 署名自检改用 Node 字节比对(findstr 在 chcp 65001 下匹配不了 UTF-8 中文)
findstr 会误报 NOT found,而实测 exe 里署名是存在的。改成 verify-signature.mjs
读字节 includes 比对,缺失时让构建硬失败。
2026-09-09 22:21:28 +08:00
AskaEth e7948088df fix(build): 署名注入改走 Node 规范化 + for /f 直读文件
上一版用 PowerShell 管道读取:PS 5.1 的 Get-Content 默认按 ANSI 读,输出再经
cmd(chcp 65001) 解码一次,中文署名被转成乱码 —— 日志写着 injected,但 exe 里的
字节与预期不符(实测 sig_in_exe=False)。
改为 deploy/prepare-signature.mjs 读 UTF-8 并剥 BOM/空白,写出 signature.build.txt
(gitignore),build.bat 用 for /f 直接读文件(不经过任何编码转换)。
另加一条 findstr 自检:注入后若 exe 里找不到署名就打印 [warn]。
2026-09-09 22:19:01 +08:00
AskaEth 13f1f03cd0 fix: 修掉十路审查发现的 9 个 P0 与一批 P1(数据正确性 / 越权 / 现场可用性)
P0(9 条全修):
- 多通道项目奖牌重复计数:domain 层按 (EventID,CategoryID,EntryID/Subject) 去重,
  通道优先级 athlete→team→id
- 「只排名不计分」项目(scored:false)不再进奖牌榜,REST/WS/导出口径统一
- aggregate 流水 entry_id 恒 NULL 导致重算后分数翻倍:作废范围改按 event+category 定位
- 已发布成绩仍可被「存草稿」覆盖并清空 rank:服务端 409 STATE_INVALID + 前端禁编辑
- voidResult / 规则导入重算后名次不回写:接住 Ranked 调 applyComputedRanks
- 赛事包覆盖导入可能「旧项目已删、新项目没成」:先改名成功再清理,失败回滚
- 导入赛事包可覆盖正在运行的赛事目录:name==当前项目直接 400
- Console spawn 失败后守护器永久卡死:清 child/pid + 调度重启 + stop 超时兜底
- /ws 快照向非 admin 下发 admin-only 配置:按角色裁剪 settings

P1(计分口径 / 契约 / 现场):
- standings.tieBreak 三处接线;profiles[].recordBonusMode 生效;破纪录校验
  RecordEligible 与 entry 归属;破纪录登记校验已发布成绩
- export.loadRanked 与主路径对齐;ListBackups reason 解析;CLI backup 前缀修正
  (原来只增不减会撑爆磁盘);单位积分明细/导出统一权重折算;停用单位展示
- 规则导入原子化(重算并入同一事务)+ 返回 compatible/affectedEventCount/
  recalculatedLedgerRows;登录审计 user_id 修正
- WS 策略性断开用 Close(code,reason);HelloMessage 补 page/instanceId;
  实例注册表清理;settings/单位/项目写操作补 change_log 触发大屏刷新
- 大屏:全量快照不再被版本守卫丢弃;个人积分榜刷新;公告返回页/轮播;行数截断;
  REST 竞态;1008 停止重连;握手超时;时区补 Z;401 仅 AUTH_REQUIRED 才登出
- 管理端:published 行禁编辑;saveDraft 补 unit(1:06.33 显示);前端预览改用服务端
  points;空成绩行不再恒「已修改」;修订详情按 id 拉取;切项目请求序号与 dirty 保护;
  发布后刷新项目状态;NAV/路由按角色过滤;recorder 不再打 admin-only 接口
- Console:大屏窗口销毁后重建;大屏 URL 复用 host 归一化;backup.bat 改纯 ASCII
  (已在 Meowhome 实跑验证)

自检:服务端 gofmt/build/vet 通过、12 包测试全绿;前端 vue-tsc + build 通过、
node --test 24/24;console check-syntax 12 文件通过。
2026-09-09 22:16:58 +08:00
AskaEth 2c9ff65fc4 feat: 补齐第一批已知缺口(关闭握手 / 定时备份 / Origin 白名单 / DTO 收敛)
- httpapi:新增 POST /api/shutdown(admin 会话或 X-Shutdown-Token 常量时间比对)
  与 GET /api/rules/switches(recorder 可读,只回布尔开关);settings/standings 的
  重复 DTO 收敛到 internal/dto;standings.showIndividualMedals 贯通到设置读写与前端
- export:抽出 export.Service,新增自动备份定时任务(backup.intervalMinutes/keep),
  自动、手动、发布前三类备份共用同一把互斥锁与滚动清理
- realtime:/ws 去掉 InsecureSkipVerify,改用 OriginPatterns(同源与无 Origin 放行,
  跨站需命中 server.allowedOrigins)
- console:停止服务改走 POST /api/shutdown 优雅关闭(5 秒宽限后回退强杀),
  服务管理器新增「已连接的大屏实例」卡片
- deploy:build.bat 改用 for /f 读内部署名——set /p 在 chcp 65001 下返回空,
  导致最近这次构建的 exe 里根本没注入署名
- docs:备份策略、关闭超时、Origin 配置的说明与实现对齐
2026-09-09 21:41:08 +08:00
AskaEth 4d56c73ae6 fix(build): build.bat 注释改回纯 ASCII
cmd.exe 会错解 .bat 里的 UTF-8 文本,上一版加的中文 REM 让构建日志里
冒出一行 "'PNPM_IGNORED_BUILDS' is not recognized"。文件头部本来就写明必须纯 ASCII。
2026-09-09 21:38:23 +08:00
AskaEth 33d4b39eb9 fix(build): pnpm 11 用 allowBuilds 放行 electron 安装脚本
pnpm 11 起 onlyBuiltDependencies 列表形式被静默忽略,导致
ERR_PNPM_IGNORED_BUILDS 让 pnpm run 的依赖校验直接失败(pnpm run dist 都起不来)。
改为 pnpm 自己写入的 allowBuilds 映射形式,并去掉 build.bat 里的 --ignore-scripts。
另外修复 set VAR=value && ... 的尾随空格:它会把 ELECTRON_MIRROR 变成
'.../electron/ v38.8.6/...' 从而 404。
2026-09-09 21:35:21 +08:00
AskaEth fc3361f766 fix(build): ensure_electron 始终补写 path.txt 2026-09-09 21:11:50 +08:00
AskaEth d5b45761d7 fix(build): 恢复手动放置 Electron 运行时的步骤(electron-builder 自下会撞 Node 24 bug) 2026-09-09 21:11:02 +08:00
AskaEth c3d2c1416f fix(build): console 安装用 --ignore-scripts,绕开 pnpm 11 的 ERR_PNPM_IGNORED_BUILDS 2026-09-09 21:10:21 +08:00
AskaEth 1399bae59e fix(build): build.bat 全 ASCII + Node 脚本处理中文路径 + pnpm-workspace.yaml 构建脚本白名单 2026-09-09 21:09:38 +08:00
AskaEth de4bd63594 fix(build): build.bat 块内改 ASCII(cmd 对 UTF-8 中文块解析错乱)+ pnpm 允许 electron 构建脚本 2026-09-09 21:09:02 +08:00
AskaEth 7f9701c465 fix: 审查修复波次(P0x4 + P1x30+)
- domain/rules: 计分与排名解耦、破纪录按组别、tieBreak 生效、调整分不丢、移除 aggregate 假能力
- realtime: 新建 internal/dto,快照三项契约对齐,/ws 只认会话身份
- httpapi: 流水幂等、standings 整表载荷、广播接线、强制改密、XFF 防伪造、records 写入、审计、赛事包内容、发布前备份、result.points、实例列表
- web: FormData 上传、YAML 解析、快照归一化、大屏解析层、bootstrap 降级、访客判定、主题切换
- console: 优雅关闭握手、中文化、免验证改管理员账号、选屏与日志轮转修复
- docs: 用户文档三份 + README
- 主代理: 破纪录加分接线(levels 未加载导致恒 0)
2026-09-09 21:06:40 +08:00
AskaEth 7f183d5327 build: 安装器许可页顶部文案自动注入内部署名(构建时生成 .nsh) 2026-09-09 20:39:02 +08:00
AskaEth 2f75c31509 build: 回归 electron-builder 打包(win.icon 写入 PE 图标),下载走 gh-proxy 2026-09-09 20:20:04 +08:00
AskaEth ad3770cfc9 build: 用 rcedit 给 Scoreboard Console.exe 嵌入图标与版本信息 2026-09-09 20:19:02 +08:00
AskaEth 74e48bf5bc build: 署名注入用单引号包裹,支持含空格的值 2026-09-09 20:15:09 +08:00
AskaEth 76e725ca96 feat: 内部署名改为编译期注入(构建机本地文件,不进仓库);关于页展示 2026-09-09 20:07:59 +08:00
AskaEth 5ae55b8057 fix(server): 赛事包导出传入项目名,修复 GET /api/export/project.zip 恒 500 2026-09-09 19:57:23 +08:00
AskaEth e86ca24d0e build: 输出改为 dist\ScoreboardConsole,运行时直接从官方 zip 解压(绕开损坏目录) 2026-09-09 19:36:02 +08:00
AskaEth 7243de93ce build: 运行时只装一次,避免覆盖只读文件被拒 2026-09-09 19:34:28 +08:00
AskaEth 1af62a690e build: build.bat 改用 ASCII 注释 + chcp 65001,避免中文注释被 GBK 解析拆行 2026-09-09 19:33:44 +08:00
AskaEth 29162f5c1d build: Console 改为就地覆盖组装,规避 .pak 只读删除失败 2026-09-09 19:33:07 +08:00
AskaEth 8b792496d6 build: 用 PowerShell 强制清理只读的 Console 产物目录 2026-09-09 19:32:29 +08:00
AskaEth 68ee99a5a2 build: 组装 Console 前先结束旧进程,避免文件占用 2026-09-09 19:31:47 +08:00
AskaEth e47251658b fix(console): 默认端口改 19540;打包改为手动组装便携版(不依赖 GitHub 下载) 2026-09-09 19:15:13 +08:00
AskaEth 0d32903b1a build: 增加 Windows 一键构建脚本 deploy/build.bat 2026-09-09 18:41:03 +08:00