Files
AskaEth 9c52dc4b38 feat(display): 大屏「赛事日程侧边栏」(手填日程 + 竖向进度轴 + 热开关 + 每屏覆盖);版本 dev_0.5.16
主人点名要的功能:大屏控制页加一个**可热开关**的侧边栏,在**指定的屏幕**上(不管它当前在哪一页)叠一个
**手填的赛事日程列表**,带**竖向进度条**;日程**不与登记的小项时间关联**、完全手填;竖向时间轴要好看,
**字多时字号自动缩放与换行**要做到位(不能过小、不能过大)。

设计口径(主人逐条拍板):放不下时**围绕当前项开窗**;进度条是**一条竖向时间轴 + 三态节点**;
**深色半透明 + 毛玻璃**;已完成的项**淡化保留**;备注**默认上屏 + 一个总开关**;
明细覆盖层 / 全屏公告期间**自动收起**、结束后自动回来;编辑权限**录入员及以上**;单屏覆盖按**实例 id**。

服务端(走大屏公告那条路 —— PUT /api/settings 是 admin-only,会与「录入员可改」冲突)
- 新增 `schedule` 动作 + 白名单;`POST /api/display/command` 同一个 handler:**先校验 → 再转发 → 最后落库**
  (校验排在转发前,非法配置不会在大屏上闪一帧)
- 落库到赛事库 settings 的 `display.schedule`(**不新建表、不写迁移**,随赛事包走);
  与公告共用那份「写设置键 + bumpVersion」的实现
- **读写不对称容错**:写侧严格 400(含中文提示与第几条),读侧坏数据一律静默当空 —— 老数据 / 手改库不会炸大屏
- 上限:40 条;time ≤20 / title ≤40 / note ≤60(按字符,清洗控制字符);宽度夹 15–40;覆盖 ≤50 条
- 口径收口三处:**时间允许为空**(与管理端同口径)、**showNote 缺键 = true**(区分「键不存在」与「显式 false」)、
  **`inherit` 不落库**(不再白占覆盖预算)

大屏端
- 新增 `ScheduleSidebar.vue` + `schedulePanel.ts`(纯函数:开窗 / 三态 / 整体进度 / 字号与行数 / 按显示宽度截断 /
  单屏覆盖 / 抑制 / 指纹去重)
- 字号是**舞台设计坐标**(1920×1080),靠 `.stage` 的 `transform: scale()` 整体缩放,**不做二次缩放**
  (wiring 断言禁止出现 `window.`/`innerWidth`/`vh`);复用既有的 `fitRowFontPx`(**16–56px 硬上下限**)与 `textEm`;
  固定点迭代 ≤4 轮;**贴下限时宁可行数少也不缩字**;标题 ≤3 行、备注 ≤2 行,超出截断
- 容器 `position:absolute; inset:0; z-index:50`(**不用 fixed** —— `.stage` 带 transform,是 fixed 后代的包含块);
  **独立于 detail 状态机**(goto 会关明细、明细会停轮播,侧边栏必须不受影响)
- 深色半透明 + `backdrop-filter`,并带 `@supports not (backdrop-filter: blur(1px))` 退化为更不透明的深底
- 关着时**不留任何 DOM**(逐字节等同于没有这个功能);`prefers-reduced-motion` 下不做动效

管理端
- 大屏控制页(左列、公告之后)新增「赛事日程」卡片:条目表格(时间/标题/备注/已完成 + 上移/下移/删除)、
  总开关、备注上屏、位置与宽度、**每块屏三态覆盖**(跟随 / 强制开 / 强制关;切回「跟随」会真删掉那条键)
- 保存走 `adminApi.displayCommand`(**不走 admin-only 的设置接口**);本地校验不过**一个请求都不发**
- 排序只有上移 / 下移(该页面禁止挂 pointerdown/mousedown 监听,有既有测试钉着)

测试
- 服务端 16 个包全绿;前端 **1174 例**全过(基线 1096,本批 +78:大屏端 44 + 管理端 34)
- 大屏端另有 **40 项 playwright 实机**(用 `page.routeWebSocket` 假造实时通道):开/关后 DOM 无残留、
  五个页面下都在、`lock=0` 真等轮播翻页且侧边栏不掉、明细与公告期间自动收起并自动回来、
  **1080p 与 4K 字号完全相同**、`reducedMotion` 下无动画;并钉住两条硬指标(真机列表高度 ≥ 纯函数预算、
  容量×行高 ≤ 真机高度)

文档:`docs/升级说明` 新增 `dev_0.5.16`;`使用手册` 9.2 + 17.6;`常见问题` Q108–Q111;`上线检查清单`/`部署指南`
各一条;`devdocs/05·07·13` 同步。

⚠️ 一条**既有边界**(本版故意没改,按主人「兼容性最大化」的要求):导出的名次口径是「通道名次优先」、
屏幕是「成绩行名次优先」,只在「先按决赛计分发布、之后又只更新名次发布一次」这种序列下分叉
(导出显示上次计分的名次)。正常赛程遇不到;遇到时对该组再「按决赛成绩发布」一次即恢复一致。
已写进《升级说明》④ 与《常见问题》Q111。
2026-09-17 20:04:50 +08:00

1128 lines
114 KiB
Markdown
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.
# 升级说明
> **按版本倒序,最新在最上面。** 每次升级前先看「要升到的那一版」这一节,照着做完再开赛。
> 数据、配置、规则包都在用户数据目录(`%LOCALAPPDATA%\ScoreboardConsole\`),**覆盖安装不会丢**,见《部署指南》3.4 / 3.5。
> 常规升级流程(停服务 → 覆盖安装 → 启动 → 核对)见《部署指南》3.1 / 3.4。
---
## dev_0.5.16
### 一句话
**没有任何数据迁移:不新建表、不写迁移,已有数据一行不改**,成绩、名次、积分、榜单与**所有导出件一个字不变**,不用重配、不用重新导入规则包。这一版**新增一个可以热开关的大屏功能**——**赛事日程侧边栏**:把手填的赛事日程做成**一条竖向进度轴**,叠在**大屏当前页之上**(那块屏此刻在哪一页都不影响),管理端「大屏控制」页也多了一张「赛事日程」卡片,**录入员**就能改。
### ① 新:大屏上的「赛事日程侧边栏」
**它是什么**:一块**深色半透明(毛玻璃)面板**,贴在屏幕**右侧**(可改成左侧)、**默认占屏 22%**(可设 **15–40%**)。面板左侧一条**竖向时间轴**,一条日程一个节点,节点**三态**:
| 状态 | 判据 | 长什么样 |
| --- | --- | --- |
| 已完成 | 勾了「已完成」 | 实心节点 + 整条**淡化保留**(一眼看出走过多远) |
| 当前 | **第一个没勾「已完成」的条目** | 节点**放大 + 光晕**(最醒目) |
| 未开始 | 其余 | 低对比 |
面板顶部一行读数 **`已完成 x / 共 y`**;**备注默认上屏**(可在卡片里整体关掉)。
**三条必须交代清楚的现场口径**:
1. **它叠在当前页之上,不改当前页、不停轮播**——大屏正在轮播、或锁在某一页,都照旧跑;侧边栏只是多一层;
2. **「当前项」看的是勾选、不是时间**:`时间` 那一栏是**完全手填的自由文本**(`08:30` / `上午` / `待定` 都行),系统**不解析、不比较、不按时间排序**——**上屏顺序就是你在卡片里排的顺序**;
3. ⚠️ **它不是「完整日程公示」**:条目放不下时面板会**围绕当前项开窗**(当前项固定在显眼位置、前后各显示若干条),**已完成的与很靠后的条目会滚出视图**,被截住的方向上会写一句「上方 / 下方还有 N 项」。要公示**完整**日程,用轮播页或另行打印,**别指望这条侧边栏**。
**排版**:字号按**文本长度与可用高度自适应**,但有**硬上下限**——**宁可少显示几行,也不把字缩到看不清**;长标题最多 **3 行**、长备注最多 **2 行**,超出按**显示宽度**省略(中文与字母按各自宽度算,不按字符个数切);时间与备注比标题小一档(**0.8 倍**,同样有下限)。**1080p 与 4K 都按屏幕等比缩放**(4K 上字与面板一起放大,不会多放大一次)。
**老浏览器**:面板用 `backdrop-filter` 毛玻璃;**不支持毛玻璃的浏览器会退化成更不透明的深底**(保证白字看得清),功能不缺。
**什么情况下它会自动收起**(开关状态一个字节不动,事情过去自动回来):管理端推送的**积分明细覆盖层**开着期间;**全屏公告**正在播期间。
⚠️ **关着的时候**(总开关关掉,或这份配置从来没配过):侧边栏**连一个元素都不留在页面上**,大屏的画面、轮播与其它一切,与**没有这个功能时逐字节一致**。
### ② 权限、存哪、以及一条代价
- **谁能改**:**录入员及以上**(与大屏公告、逐屏切页同一档;未登录 401、只读访客不能改)。为什么不走「赛事设置」那条路:`PUT /api/settings` 是**管理员专有**,而现场要求**录入员也能改**——所以走的是**同一条大屏指令**(`POST /api/display/command` 的 `schedule` 动作,与大屏公告同一档),改动**记审计**;
- **存哪**:赛事库的**一个设置键**(一份 JSON)——**不新建表、不写迁移**,而且**随赛事包一起走**(导出赛事包自然带上、换机器一致),**刷新 / 重启 / 切赛事都不丢**;
- **保存 = 整份替换**,并**立刻推给所有大屏**(这就是「热开关」:改完就生效,不用刷新、不用重启);
- ⚠️ **这是低频动作**:**每次保存都会让全场大屏做一次全量重拉**(局域网内几百毫秒,现场基本无感)。它的用途就是**开赛前配一次 + 现场偶尔勾一下「已完成」**,**不要**拿它当高频刷新用(例如每几秒保存一次)。
### ③ 管理端「大屏控制 → 赛事日程」卡片
位置在**左列、公告卡之后、「实时赛况」之前**,卡头徽标写 `N 条 · 默认开启 / 关闭`。
| 这块 | 怎么用 |
| --- | --- |
| 条目表格 | **时间**(自由填,最长 20 字)|**标题**(必填,40 字)|**备注**(可留空,60 字)|**已完成**(开关)|**上移 / 下移 / 删除**(首行上移、末行下移是灰的) |
| 新增 | 表格下方「新增一条」,右侧写 `共 N / 40 条`;一条都没有时显示「还没有日程条目」 |
| 开关组 | **侧边栏默认开启**(总开关,所有大屏的默认值)、**备注上屏**(关掉就只显示时间与标题) |
| 几何 | **位置**(左侧 / 右侧)+ **宽度**(15–40 %) |
| 每块屏单独设置 | 一行一块屏(显示名 = 手动别名 → 自动设备标签 → 实例 id),三态下拉 **跟随总开关 / 强制开 / 强制关**;切回「跟随总开关」会**真的把那条覆盖删掉**(不留垃圾键) |
| 保存 | 整份替换 + 立刻生效;成功有提示,失败把**服务端原文**显示出来 |
- **校验不过时一个请求都不发**:字段级红字贴在该格下方,条数与宽度这类整表错误列在表格下方,同时提示「赛事日程校验未通过 / 请先修正标红的字段」;
- ⚠️ **时间也要填**(可以写「待定」这种字,但不能留空):留空时服务端会退回一句「第 N 条日程的时间不能为空」,这一次保存不生效;
- **只读身份**(未登录 / 服务端未连接)时**整张卡片禁用**并写明原因;**示例数据模式**下会明确提示改动未真正保存;
- 排序**只有上移 / 下移两个按钮、没有拖拽**(有意的),**最多 40 条**。
### ④ ⚠️ 一条边界:**导出的名次与屏幕上可能不一样**(本版**故意没改**)
这一条**不是本版引入的**,而是一条**既有口径**(有测试钉着);本版按「兼容性最大化、不动已交付的东西」**原样保留**:
| 看哪 | 名次取谁 |
| --- | --- |
| **屏幕**(大屏的分项成绩 / 实时赛况 / 底栏) | **成绩行上的名次优先**,为空时回落「通道名次」 |
| **导出件**(结果导出的分项成绩表) | **「通道名次」优先**,为空时回落成绩行上的名次 |
**什么时候会遇到**:只有一种序列——**先「按决赛成绩发布」**(会重建通道名次),**之后又只发一次「只更新名次」**(只写成绩行上的名次、**不重建通道名次**)。这时**导出件显示的是上一次计分的名次、屏幕显示的是新名次**。**正常赛程遇不到**:预赛只排名次 → 决赛才计分,两边名次是同一个值。
**遇到了怎么办**:对该小项 + 组别再**按「按决赛成绩发布」发布一次**即可——通道名次会按当前成绩重建,两边立刻一致(不用改库、不用重导)。
### ⑤ 升级后 5 分钟核对
1. 到「大屏控制 → 赛事日程」(左列、公告卡下面):「新增一条」加两条日程(例如 `08:30` / `男子 100 米预赛` / 备注 `检录 08:00`),点「保存」;
2. 看大屏:**当前那一页上应当叠出一块右侧面板**,顶部写 `已完成 0 / 共 2`、第一条带光晕;**大屏没有换页,轮播也没有停**(本来在轮的继续轮);
3. 回卡片勾上第一条的**「已完成」**再保存:大屏上这条**淡化**、第二条变成带光晕的「当前」,读数变 `已完成 1 / 共 2`;
4. 在「积分榜」页点某单位的「推送到大屏」(或推一条公告):**明细覆盖层 / 全屏公告期间侧边栏应当自动收起**,关掉明细 / 公告退场后**原样回来**(卡片上的开关一个字都不用动);
5. 把卡片里的**总开关关掉**再保存:大屏应当**恢复成没有这块侧边栏的样子**(与没开过这个功能时一致);
6. 其余照旧:**不需要数据迁移、不需要重新导入规则包、不需要重配任何东西**;已有成绩、榜单与导出件**一个字不变**。
### ⑥ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 卡片每一项怎么填、侧边栏长什么样 | 《使用手册》9.2(赛事日程卡片)、17.6(大屏上的侧边栏) |
| 为什么看不到全部日程 /「当前项」为什么按勾选 / 字会不会太小 | 《常见问题》Q109 |
| 打开明细或推公告时它不见了 | 《常见问题》Q110 |
| 导出件与屏幕的名次为什么不一样 | 本节 ④、《常见问题》Q111 |
| 谁能改、「每次保存都会让全场重拉」 | 本节 ②、《使用手册》9.2 |
---
## dev_0.5.15
### 一句话
**没有任何数据迁移:不改一行已有数据、不用重配、不用重新导入规则包**,已经录过、发过的成绩与积分**不用重发、不用重算**——新版起来之后,**大屏下一次刷新就显示对**。这一版**修两处现场看得见的错**(大屏上纯团体项目的名次全是 `-`,见 ①;分项成绩页第 3 栏看不到第 1 名,见 ②),另有五处**新增 / 改口径**(运动员列可限宽、发布改成弹窗里二选一、大屏设备名可读且能改名、局域网地址条目都能出二维码、录入页多了搜索框,见 ③–⑦)。
### ① 修:大屏上「纯团体项目」的名次全是 `-`
**先说清楚它不是什么**:**不是算分错、也不是团体名次没算出来**——名次早就在库里,**导出件与积分明细下钻一直是对的**,录入页也一直看得见。**只是大屏那两个读者没读到它。**
**为什么只有大屏看不到**:纯团体项目的名次(例如团体 4×100 接力的单位名次)**只落在「通道名次」上**(发布时算出来的那份名次);而成绩行上的「个人名次」这一列**是有意收窄的**——它只记个人名次,**对团体主体一直是空的**。大屏的两个读者(快照的「最新成绩 / 分项成绩 / 底栏」与「实时赛况」)原来**只读「个人名次」、没有兜底**,于是团体行画出来就是一个 `-`;而**导出件与积分明细下钻早就有兜底**——所以当时是「导出件有名次、屏幕上没有」。
**现在会怎样**:这两个读者都改成 **「个人名次」优先、为空时回落到「通道名次」**。**算分与写回一行没动**(名次怎么算、流水怎么写、榜单怎么更新全都没变)。
> ⚠️ **为什么是「个人名次优先」而不是「通道名次优先」**:**「只更新名次」的发布只写「个人名次」、不重建通道名次**(见 `dev_0.5.8` 那一节)。要是让通道名次优先,个人项目在那种发布之后会**显示上一次的旧名次**。反过来「个人名次为空才回落」正好补上团体主体——团体名次本来就不写「个人名次」。
**现场要强调的**:**已经发布过的数据不用重发、不用重算**,新版起来大屏下一次刷新就显示出来;名次在录入页本来就看得见,这一版只是把同一份名次也画到大屏上。
⚠️ **仍未覆盖的一种情形(如实写)**:**只在「团体名次」卡片里按单位录了名次、成绩行上没填**的那类项目(例如拔河只用卡片录),名次只存在「按单位录入的团体名次」里、**没有对应的成绩行**,大屏上**仍然显示 `-`**。这一版只修「名次落在通道名次上」的那一类。
### ② 修:分项成绩页「第 3 栏看不到第 1 名」
**症状的形状很特别**:同样几个项目,**放到前两栏就没事,落到第 3 栏就看不到第 1 名**——这个毛病**跟着栏位走、不跟着项目走**。
**真正的原因**:服务端快照里「最新成绩」的窗口原来是**按行取 20 行**,而大屏上是「一组一栏」——窗口的切口**只能落在最旧的那一栏(通常就是第 3 栏)内部**,把那一组里**建号最早的行**(手录项目通常就是第 1 名)挤了出去。
**现在会怎样**:窗口改成 **「最近 8 组 × 每组 10 行」**——先按每组最近一次发布时间取**最近 8 组**,再在每组里**按名次保底取前 10 行**。名次的切口不再落在某一栏内部,**第 3 栏从第 1 名开始画**。**底栏那条「最新完成项目」也一起吃这个修复。**
**取舍(要写清)**:单组**超过 10 行**的部分**不再下发**。前端每栏只画 6 行、「实时赛况」上限也是 10 行,所以三个页面都看不出来;⚠️ **将来若把每栏行数上限调到 10 以上,必须同步调整这个「每组 10 行」**(这两处是一对)。
### ③ 新:录分列表「运动员 / 队」这一列可限宽 + 手动展开
多人团体项目的名单(`张伟(A001)、李强(A002)…`)以前会把整列撑得很宽。现在:
- **收起时限宽**(**14em ≈ 182px**,超出显示省略号);
- 文本够长时(超过 **24 个「显示宽度」**——中文算 2、字母算 1),这一格右侧出现**逐行的「展开 / 收起」**小按钮;
- **个人项目永远不会出现这个按钮**;
- 展开状态**只在本机内存**、**按报名项记**,换项目 / 刷新即恢复收起。
**取舍**:展开那一行会把列略微撑宽(成绩列右移约 **80px**),点回收起就恢复。
### ④ 改:录入页「以决赛成绩发布」从勾选框改成发布弹窗里选
**成绩录入页**:工具栏上那个常驻勾选框(与旁边的口径徽标)**已移除**。现在**点「发布」弹出一个窗口**(标题形如 `发布该组:请选择本次的发布口径`),里面**二选一**:
| 选项 | 会发生什么 |
| --- | --- |
| **只更新名次(不计分、不发奖牌)** | **默认选中**——名次照常算出来、写进系统,但不产生积分、不发奖牌 |
| **按决赛成绩发布(重算名次并计分、发奖牌、榜单跟着更新)** | 与以前「勾上再发布」完全一样:算分、发牌、上榜 |
- 同一句口径说明也写在选项下面(`本次只更新名次:不计分、不发奖牌,榜单分数不变。` / `本次按决赛成绩发布:算分、发奖牌、榜单跟着更新。`);
- **取消 = 什么都不发生**;
- **每次打开都回到默认**(**不吃上一次的选择**)——这条是故意的:避免「换个小项再点发布,把预赛当成决赛算分」这种最贵的错法;
- `Alt+Enter` 快捷键走**同一个窗口**;
- 窗口里同时写明本次将发布多少人、还有多少人没成绩;选「按决赛成绩发布」时才会报「影响 N 个单位、最大分差 X 分」(只更新名次时报这两个数是假话)。
⚠️ **成绩审核页保持原来的常驻勾选框不变**(两个页面口径不同,别以为审核页也变了)——见《使用手册》4.5 与第 5 节。
### ⑤ 新:大屏控制页的「在线大屏」列表 —— 可读设备名 + 手动重命名 + 修一个重复条目
**自动设备标签**(局域网浏览器一连上来就带着):
| 设备 | 标签 |
| --- | --- |
| 安卓手机 | `安卓 13 · Chrome 120 · 1080×2400 @3x · 手机` |
| iPad | `iPadOS 17 · Safari 17 · 2048×2732 @2x · 平板` |
| Windows 电脑 | `Windows 10 · Edge 120 · 1920×1080 · 桌面` |
| 微信内置浏览器 | `安卓 13 · 微信 8 · 1080×2340 @2.75x · 手机` |
写法是 **系统 + 版本 · 浏览器 · 分辨率 · 形态(手机 / 平板 / 桌面)**,缺的段自动跳过。
⚠️ **做不到什么,必须如实说清**:浏览器**没有**读主机名 / 设备名的 API(这是安全边界),所以**拿不到那种「XIAOMI-PAD-05」的真机名**;`model` 字段只在**部分安卓浏览器**上可能有值。**自动标签只是辅助区分**,两台同型号设备还得靠**手动重命名**或**来源 IP** 分辨。
**手动重命名**:列表每条有「改名」入口(**录入员及以上**可用,与切页同档):
- 点它**就地出现一个输入框**(示例 `二号楼平板`),按「保存」或 `Enter` 提交;旁边的**「清除」**回到自动标签、**「取消」**什么都不改。名字**存在服务端**,**重启与切赛事都不丢**(**最长 40 个字符**);
- 显示优先级:**手动名 → 自动标签 → 那个随机 id**(id 降级成小字,排障时还在);
- 上限 **200 条**,**满了会拒绝新增并提示先清理**(不会悄悄顶掉别的设备);
- 别名跟着**设备**走:设备太久没连、条目被清理之后,**别名仍留着**,同一台设备再连上来还是那个名字。
**修:给某台设备切页之后,列表底部会多出一个该设备的离线条目**。根因是**翻页会补发一次握手**,而浏览器**没有稳定 id**、服务端每次都新开一条实例。现在**浏览器本机记住自己的实例 id**,服务端也做了兜底(同一条连接重复握手复用原实例)——**升级后不会再出现这种重复条目**。
### ⑥ 新:局域网地址条目都能出二维码了
以前**只有第一条地址**自动带二维码。现在**其余每条**多了一个「**二维码**」按钮:**点一下临时显示**那条地址的码,**再点一次 / 按 Esc / 点空白处**都能收起,**同一时刻只留一个**。**第一条那条常驻二维码与「复制链接」都没变。**
### ⑦ 新:成绩录入页加了「搜姓名 / 号码布」搜索框
3 公里、5 公里那种人多的项目,找人不费劲了。位置在**工具栏「批次」下拉右边**:
- **即时模糊匹配**:大小写不敏感、首尾空格与全角空格都当空白;号码布支持数字口径——**`007` 搜 `7`、`7` 搜 `007` 都能找到**;
- **命中高亮**(只加一层淡底色);
- 提示 `找到 N 条 / 共 M 条`(**M 是批次口径的总行数**);
- 清除有三种:× 按钮、输入框里按 Esc、「清除搜索」按钮;搜不到时给明确的空状态与提示;
- 搜索范围是**行的姓名 / 队员名单 / 队员(含号码布)/ 行号码布**,**不含单位名**;
- 换小项 / 换组别会**清空搜索**。
⚠️ **「搜索只影响表格显示,不影响发布范围与进度」**(界面上也有这行小字):筛完再点发布,发的仍是**该小项 + 该组别里所有已填的成绩**;进度条的「已录 x/y」也仍是**该批次总人数**。
⚠️ **粘贴跟着屏幕走**:搜索状态下按**屏幕顺序**粘贴(**隐藏的行不会被写到**)——**这是有意的**,否则第二条成绩会静默写到看不见的人身上。
### ⑧ 升级后 5 分钟核对
1. 打开一个**纯团体项目**(例如团体 4×100 接力)已发布成绩的大屏页面:**分项成绩、实时赛况、底栏「最新完成项目」三处都应当有名次**,不再是 `-`(**不要重发、不要重算**,刷新一下大屏即可);
2. 看**分项成绩页的第 3 栏**:**应当从第 1 名开始**(升级前第 1 名会被挤出这一栏);
3. 到「成绩录入」页选一个**多人团体项目**:**「运动员 / 队」这一列收起时是限宽的**,长名单右侧有「展开 / 收起」;再看一个**个人项目**,那里不应当有这个按钮;
4. 点一次「发布」:应弹出**二选一**的窗口,**默认是「只更新名次(不计分、不发奖牌)」**,按「取消」什么都不发生;想计分发牌就选第二项再确认。⚠️ 到**成绩审核页**看一眼:那里**还是原来的常驻勾选框**;
5. 到「大屏控制 → 在线大屏」:局域网设备的条目应当**写着 `安卓 13 · Chrome 120 · … · 手机` 这样的可读标签**(不是一串随机 id),点「改名」能起个自己的名字;再给某台设备切一次页,**列表底部不该多出离线条目**;
6. 到「大屏控制 → 局域网大屏」:**每条地址都有「二维码」按钮**,点开再点一次能收起(第一条的常驻二维码照旧);
7. 到「成绩录入」页找一个人多的项目,在**「批次」下拉右边**输入姓名或号码布(`007` 与 `7` 互通):应看到 `找到 N 条 / 共 M 条` 与高亮;**「发布」窗口里的人数与进度条数字不受搜索影响**。
### ⑨ 详细说明在哪
《使用手册》4.2(录入页搜索框、「运动员 / 队」列的收起 / 展开)、4.3(粘贴跟着屏幕走)、4.5(发布的弹窗二选一口径)、第 5 节(审核页仍是常驻勾选框)、第 9 节(大屏设备标签 / 改名 / 地址二维码按钮、重复条目已修);《常见问题》Q105–Q107;《部署指南》7.5(局域网地址与二维码)。
---
## dev_0.5.14
### 一句话
**没有任何数据迁移:不改一行已有数据、不用重配、不用重新导入规则包**,已有的成绩、名次、积分、榜单与**所有导出件一个字不变**。这一版**新增两种导出**:在「导出 → 结果 → 报名名单」卡片里,多了一组**与报名导入模板逐列一致**的导出(原来的报表导出**原样保留、一个字没改**)——以后**导出的报名名单可以直接拿回去重新导入**了。
### ① 新增:「报名名单」卡片里的两种「导入格式」导出
**先说清楚为什么要有它**:原来那张「导出报名名单」是**给人看的报表**(含小项名称、单位名称与备注,姓名列拼了队员、状态写「已退赛」),**导入器不认它**——想「导出 → 改几笔 → 再导回去」这条路以前走不通。现在同一个卡片里加了两个按钮:
| 按钮 | 导出来是什么 | 文件名 |
| --- | --- | --- |
| 导出(汇总·导入格式) | 13 列,所有行**汇总在一张工作表** | `报名名单(导入格式).xlsx` |
| 导出(分小项·导入格式) | 13 列,按「小项名称@组别名称」**分成多张工作表** | `报名名单(导入格式·分小项).xlsx` |
- **13 列与报名导入模板逐列一致**(顺序也是这个):项目编码 / 组别 / 单位编码 / 姓名 / 证件号 / 性别 / 年龄 / 号码布 / 种子 / 批次 / 状态 / 队员名单 / 队伍;**导出的文件可以直接拿去重新导入**(往返已测,见下);
- 卡片上的四个筛选(小项 / 组别 / 单位 / 批次)与原来的报表**完全一样**,都可留空;权限也一样,**访客也能导**;
- 新加的**表头语言**(中文表头 / 英文表头)**只影响这两个导出**,对上面的报表**不生效**;
- **原来的「导出报名名单」一个字没改**,照旧是给人看的报表(含小项名称、单位名称与备注)。
**往返测过**:把这两种导出件**直接喂回导入接口**各跑一遍——**零错误、行数一致、13 列逐项一致、团体行没有被变成个人报名**(队员关联还在)。真实服务端上也手工验过一遍:导出的文件回灌 `POST /api/entries/import` 得到 `created:0, updated:2, errors:[]`。
⚠️ 但它**不是"完全无损"的往返**,有三条限制请一并交代给用的人(详见《报名名单导入规范》第九节):
1. **号码布可能撞冲突面板**:导出件写的是**报名上登记的号码布**,导入时对个人行会拿它跟**运动员档案里的号码布**比;两边都非空且不同,这一行就进「冲突」列表等人工决策(库里两者一致是常态,那时是静默幂等);
2. **联队的份额权重带不回来**:13 列里联队只能写 `A+B`,重导回去是**均分(各 50%)**;原来是 `2:1` 这种非均分的会变均分,要保权重只能在编辑界面改;
3. **「队员名单」只增不减**:导出时会把界面上后来补进这支队的队员一并补上,所以重导后**队员不会少**,但个别名单的顺序 / 写法可能与最初那张表不完全一样。
### ② 升级后 5 分钟核对
1. 到「导出 → 结果」,在**报名名单**卡片点「**导出(汇总·导入格式)**」:打开下载到的 `报名名单(导入格式).xlsx`,确认**第一行就是 13 列表头**(项目编码 / 组别 / 单位编码 / 姓名 / 证件号 / 性别 / 年龄 / 号码布 / 种子 / 批次 / 状态 / 队员名单 / 队伍),并且**团体那一行的「姓名」列是空的**(队员在「队员名单」列);
2. 把刚导出的这个文件**直接**在同一页的「基础数据 → 报名导入」原样上传(**中间不要改任何一格**):应当是**零错误**,提示的「更新 N 条」里名单与上传前**看不出变化**;
3. 想要分小项的,回到卡片点「**导出(分小项·导入格式)**」:得到的文件里**每个「小项名称@组别名称」一张工作表**(没人报名的组合不会出空表),表名与导入模板同一套写法;
4. 其余照旧:**不需要数据迁移、不需要重新导入规则包、不需要重配任何东西**;原来的「导出报名名单」以及其它导出件**一个字不变**。
### ③ 详细说明在哪
《使用手册》§11.1 结果导出(xlsx);《报名名单导入规范》第九节「导出的名单怎么导回去」(含三条限制)。
---
## dev_0.5.13
### 一句话
**纯界面 / 宿主程序的小修,没有任何数据迁移:升级后不用重配、不用重新导入规则包**,已有的成绩、名次、积分、榜单与导出件**一个数字都不变**。这一版修一处**现场看得见的延迟**:**大屏上的「单位总分」要等几十秒才更新**(清空本组、发布、调整之后都可能遇到)。
### ① 修:「大屏上的单位总分等几十秒才降」
**先说清楚它不是什么**:不是「清空没生效」、也不是「分数算错了」——**分数在服务端是当场就变了的**,数据库、榜单接口、导出件从你点下确认的那一刻就已经是新值。**卡住的只是大屏上那一格数字的画法。**
**真正的原因**:大屏上「单位总分」那一格是**逐帧滚动**过去的(一帧一帧把数字从旧值滚到新值),而浏览器对**被别的窗口盖住的窗口**会**停掉逐帧绘制**——数字就停在旧值上,**卡多久取决于被盖住多久**,跟后台算分一点关系都没有。所以你会看到「等几十秒才降」,而且**把盖住大屏的窗口挪开以后,数字是「啪」地一下跳到新值**,不是慢慢滚过去。
> 为什么只有这一格?因为**全场只有「单位总分」这个数字是滚动显示的**,奖牌榜、分项成绩、实时赛况里的数字都是直接写上去的——这也是为什么现场感觉「别的都立刻变、只有总分不动」。
**现在会怎样**:
1. 就算一帧都画不出来,**最迟约 1.3 秒**数字也会直接落到新值(不再无限期停在旧值);
2. 大屏窗口**重新露出来 / 重新被点活**时,数字**立刻**是正确值,**不会再补滚一遍过时动画**;
3. 宿主程序(Console)同时关掉了浏览器对「被遮挡 / 后台」窗口的降频,大屏更不容易掉帧。
### ② 顺带修:偶发「某一版变更漏掉、之后一直没补上」
大屏与管理面板在**短时间内连续发生多次变化**时,会向服务端要一次「补数据」。原先这个请求**在 3 秒冷却期内会被直接丢掉**,而界面此时已经认为「这一版处理过了」——于是该补的那一次**不会再补**,要等下一条无关的变化才顺带修正(现场表现可能是某块榜单的内容停在几秒前的样子)。现在改成**排队**:冷却一过立刻补发,不会再漏。
### ③ 升级后 5 分钟核对
1. 打开大屏的**积分榜**页,随便做一次会让分数变化的操作(例如「清空本组」,或发布一批成绩),**清空后不要动鼠标**:那一格数字应当在 **1~2 秒内**落到新值;
2. 故意拿一个窗口**把大屏窗口完全盖住 10 秒**,再挪开:数字应当**立刻就是正确值**(不是从旧值滚一遍过来);
3. 奖牌榜、分项成绩、实时赛况本来就是直接显示,行为与升级前一致;
4. 其余照旧:**不需要重新导入规则包、不需要重配任何东西**;已有成绩、榜单与导出件**一个字不变**。
### ④ 详细说明在哪
《常见问题》Q103(大屏上的分数为什么过一会儿才变 / 后来一直不变了)。
---
## dev_0.5.12
### 一句话
**这不是一次「数据迁移级」的变更:没有任何一行已有数据被改动,升级后不用重配、不用重新导入规则包**,已有的成绩、名次、积分、榜单与导出件**一个字不变**。这一版做两件事:**修好**「在基础数据里改了小项的显示名称,回到成绩录入页发现**「撤销发布」按钮不见了**」——**根因不是改名**,是回到录入页时**不记得上次录的是哪个小项**(见 ①);**新增**「**清空本组 / 清空本批**」成绩(把一组或一批成绩整个作废重来,**危险操作**,要勾选确认,见 ②)。
(修的是**纯前端**的一处选择逻辑:录入页记住上次录入的小项。没有任何结构迁移、没有动过一条成绩。)
### ① 修:「改了小项名称之后,撤销发布按钮消失」
**先说结论:这不是改名弄坏的。** 改名那条路一直是干净的——录入页加载报名名单、读成绩、引擎算分**全都按小项的 ID 关联**,**不看名字**(这一版还补了一条「小项名 + 组别名一起改」的回归用例,证明改完之后录入页状态、积分流水、榜单**逐字节不变**)。
**真正的原因**:进入「成绩录入」页时,系统**不记得你上次录的是哪个小项**,于是兜底挑**第一个「待录入」的小项**;而**刚发布完的小项状态已经是「已发布」**,正好被这条兜底排除——所以你离开一趟(例如去基础数据改个名字)再回来,右侧已经被换成**另一个小项**;而「撤销发布」按钮是**按当前这张表里有没有已发布的行**显示的,换到那个还没发过的小项上,按钮自然就不在。
⇒ 现场看到的「**左栏那个小项仍写着已发布**」「**单位成绩仍有加分**」「**撤销发布按钮不见了**」这三条**本来不矛盾 —— 它们说的是两个不同的小项**。
**现在会怎样**:录入页**记住你上次录的那个小项**(按 ID 存在本机),离开再回来仍是它,按钮就在。优先顺序是:地址里带的小项(例如仪表盘点「录入」进来的)→ **上次录的那个**(已发布的也算)→ 第一个「待录入」的 → 列表第一个。**记忆失效时会自动退回原来的兜底**(那个小项被删了、或浏览器不允许存本机数据,例如隐私模式),**不会把人挡在录入页外面**。
### ② 新增:「清空本组 / 清空本批」成绩
右侧表格上方的**工具栏**里(**和「撤销发布」并排**)多了「**清空本组**」;上面选了某一批次时,旁边还会出现「**清空本批**」。用途是**这一组 / 这一批整个录错了,要从头再来**。
⚠️ **清空 = 先撤销发布、再把成绩内容一起清掉**:这一范围里每一行的成绩、名次、备注、以及行上的状态(DNS / DQ / DNF / 这类标记)都回到没录过的样子,原来已发布的分**一并收回**,榜单与大屏跟着回退。清**整组**时还会**一并清掉这一组按单位录入的「团体名次」**(否则会出现「成绩都清了、榜上还有分」)。
**「撤销发布」与「清空」的区别(这两个最容易混)**
| 动作 | 成绩内容 | 已产生的分 |
| --- | --- | --- |
| **撤销发布** | **保留**(退回草稿,改完重新发布即可) | 收回(该范围的积分流水作废) |
| **清空** | **一起清掉** | 也收回 |
**只想收回分数、成绩内容留着改 —— 用「撤销发布」;想让这一组回到「没录过」—— 才用「清空」。**
**清空前必须先勾选**:点按钮会弹确认框,写明**哪个小项、哪个组别、清多少条、其中多少条已发布**、以及会收回多少分;**必须勾上「我知道这会清掉已发布的分数」**,确认键才点得动(服务端也会再兜一次,没勾选一律拒绝)。**没有「清空整个小项」这个按钮**——清空以「小项 + 组别(或其中一批)」为界。
**谁会看到这两粒按钮**:**录入员及以上**(与录成绩同一档)。**访客 / 未登录**时按钮是灰的并写明原因;**示例数据模式**下不可用(与「发布」「撤销发布」同一口径)。**谁清空了什么**会记进审计日志(审计里的动作名是 `result.clear`:谁、什么时候、哪个小项哪个组、哪个范围、清了几条、收回多少分),「变更历史」里也会多一个版本节点并写明清了多少条。
### ③ 升级后 5 分钟核对
1. 打开「成绩录入」,选中一个**已发布**的小项 → 切到别的页面 / 去「基础数据 → 小项」随手改一下这个小项的显示名称 → **回到「成绩录入」页**:应当**还是刚才那个小项**,工具栏上的「**撤销发布**」按钮**还在**(这就是这一版修的那条);
2. 拿一个**可以随便试的小项**(动手前最好先做一次备份,见《部署指南》8.2 与《使用手册》14.2),选一个小项 + 一个组别,点「**清空本组**」:确认框里应写明清多少条、其中多少条已发布,**不勾选那行字时确认键点不动**;确认后看**积分榜**:这一组原先贡献的分应当**同步降下来**(大屏也会跟着回退);
3. 想只收回分、把成绩留着改,仍然用原来的「**撤销发布**」——它不会清掉成绩内容;
4. 其余照旧:**不需要重新导入规则包,也不需要重配任何东西**;已有成绩、榜单与导出件**一个字不变**。
### ④ 详细说明在哪
《使用手册》4.7(撤销发布与清空的并列口径、两颗按钮的出现条件与勾选要求)、4.1(回到录入页记住上次的小项)、《常见问题》Q102(「我改了小项名称,回来发现撤销发布按钮没了」)。
---
## dev_0.5.11
### 一句话
**这不是一次「数据迁移级」的变更:没有任何一行已有数据被改动,升级后不用重配、不用重新导入规则包**,已有的名次、积分、榜单与导出件**一个字不变**。只是给「按队内平均」的通道**多了一个可选的汇总口径**——**「队内最低分」**(队内最差的那一份成绩):越野 / 行军这类**取团队最后一人的成绩(最后一名冲线)**的项目现在能直接配出来,不用手工录名次。
(库里有一条结构迁移 `0027`,它只做一件事:把「聚合口径」这一列允许的取值从 `avg / sum` 放开到 `avg / sum / worst`。老通道的值原样保留、默认仍是「平均」,所以升级后榜单与你上次看到的一模一样。)
### ① 新能力:团体成绩可以按「队内最低分」算
- **在哪配**:「基础数据 → 计分通道」,名次来源是「按队内平均」的那一行,**「聚合口径」列**现在有**三个按钮**:**平均 / 总和 / 队内最低分**。点「队内最低分」→ 点「保存全部」生效,该小项立即重算;
- **它算的是什么**:团体成绩 = 该单位成员里**最差的那一份**成绩。⚠️ **取的是谁,由小项的「比较方式」决定**,这是最容易搞反的地方:
| 小项的「比较方式」 | 项目例子 | 「队内最低分」取的是 |
| --- | --- | --- |
| **升序**(越小越好) | 越野 / 行军(时间) | **跑得最慢的那个人**——也就是**最后一名冲线**的那个人的成绩 |
| **降序**(越大越好) | 引体向上(次数) | **做得最少的那个** |
- 所以它**不是**「取最小的那一格」:时间项目里"最小"是最快的人,正好反了。界面上按钮旁边鼠标停一下就有这句提示;
- **弃赛 / 失格的人不算**:队伍成绩只在实际出赛的人里取最差的那一份(弃赛那一格填得再差也不代表队伍);
- **一个有效成绩都没有的单位不参与团体排名、也不得分**(没人冲线就没有"最后一名冲线"可言)。这一条**与「平均值口径」选哪个无关**——「队内最低分」没有"分母"这回事;
- 它和已有的两个口径**互不影响**:不改口径的通道仍然按「平均」(默认)跑,**升级后榜单不会变**。
### ② 规则包里也能写
规则包的通道上写 `aggregateMode: worst` 即可(不写 = `avg`,与以前一致)。**不认识的值会被拦下报错**,不会静默按「平均」处理——特别地,**`min` / `max` 不是合法取值**:方向由小项的「比较方式」决定,写死成一个方向的项目一半会算反,所以要写 `worst`。
### ③ 升级后 5 分钟核对
1. 打开「基础数据 → 计分通道」,选中一个名次来源是「按队内平均」的小项:**「聚合口径」那一列应该是三个按钮,默认停在「平均」**;
2. 随便找一个小项,什么都不改点一次「保存全部」,然后看单位总分榜 / 名次表:**与升级前一致**(老口径一字不变);
3. 要试新口径的话:挑一个**时间类(比较方式 = 升序)**的小项,把它的团体通道切成「队内最低分」并保存,榜单上该单位的团体成绩应等于**该单位里最慢那个人的成绩**(不是平均、也不是最快那个);改用「平均」再保存一次,应立刻退回原来的名次;
4. 其余照旧:**不需要重新导入规则包,也不需要重配任何东西**。
### ④ 详细说明在哪
《使用手册》10.4 计分通道 → 第 5 条(聚合口径三个按钮与两种方向的对照)。
---
## dev_0.5.9
### 一句话
**没有数据迁移,升级后不用重配任何东西**:只改**提示与模板上的说明**。以前在团体项目的报名表里把队员姓名填进「姓名」列,导入时只回一句「填了队伍 / 队员名单,但这一行有运动员姓名,按个人报名处理」——**没告诉你该怎么改**,于是现场反复换填法重试。现在这两条提示会**直接写出两步动作**(要报团体就把姓名列留空、号码布填到「队员名单」列;确实是个人报名就把那一格删掉),下载的模板里**「姓名」与「队员名单」两列表头也各挂了一条说明**(鼠标停上去或点中该列的格子就能看到)。**导入表、模板结构、已有数据一个字都没动。**
### ① 两条导入提示改成「能照着改」的话
- 以前现场看到的那句(**问题就出在这里**)只说清后果,没说出改法:`第 N 行填了队伍「一队」,但这一行有运动员姓名、按个人报名处理:队伍只对团体报名(不填姓名那几行)生效,这一格没有写进库里`;
- 现在改成(**原文**,队员名单那条同理):
> 第 N 行填了队伍「一队」,但这一行也填了运动员姓名,系统按个人报名处理:队伍只对团体报名生效,这一格没有写进库里。**要报团体(一支队)请把姓名列留空、把队员的号码布填到「队员名单」列**;**这一行确实是个人报名,把「队伍」这一格删掉即可**
- **判据本身没变**(这也是没把它当 bug 的原因):**「姓名」那一格填了,这一行就是个人报名;团体报名要把它留空**,队员写进「队员名单」列(写号码布才会关联到人)。系统一直是这样区分的,这次只是**把话说明白**。
- ⚠️ 一行提示现在约 120 字(含两步动作):一次导入几十行时看着会长一点,但**每行只说一次**;嫌吵的话可以跟我们要一版更短的。
### ② 模板里的「姓名」与「队员名单」加了说明
- 下载的模板(分工作表 xlsx)现在有三列表头挂了**单元格批注 + 该列的输入提示**(中英各一份):原来的「**单位编码**」(联队怎么写),加上新的「**姓名**」(填了姓名就是个人报名;团体报名请把姓名留空,队员写进「队员名单」列)与「**队员名单**」(团体报名的队员写这里,**写号码布**才会关联到人、参与个人算分);
- **模板的表头文字、列序、13 列、工作表命名一个字都没动**(有测试逐字钉着)——**手里已有的表格照常导入**。
### ③ 顺带写清:「队员名单」那一列怎么分隔
- 多人之间用 **`,`、`,`(中文逗号)或 `/`** 分开,**三种都认、可以混用**;**空格不是分隔符**(`张三 李四` 会被当成一段);
- **每一段要单独是一个号码布**:`A001`、`A002/101` 都行;**`张三(A001)` 这种「姓名 + 号码布写在同一段」认不出人**——那是**导出件**的显示格式,不是导入接受的写法。要「名字也看得见」,写号码布就行(导出件与录入页会显示成 `张三(A001)`)。
### ④ 升级后 5 分钟核对
1. 拿一份**含团体报名**的报名表(原来会触发那两条提示的那种)试着导入一次:报错里应能看清**两步动作**,而不是只有「没写进库里」;
2. 打开「基础数据 → 报名导入」下载一份「**分工作表 · 中文表头**」,鼠标停在「**姓名**」和「**队员名单**」表头上,应各弹出一条说明;
3. 其余照旧:**不需要重新导入规则包,也不需要重配任何东西**;已有报名、成绩、榜单与导出件**一个字不变**。
---
## dev_0.5.8
### 一句话
**没有数据迁移,升级后不用重配任何东西**:给「发布」加了一个**口径开关** —— 分轮次比赛(预赛 / 复赛 / 决赛)时,**只有决赛那次发布才计分发牌**;预赛发布**只更新名次、不计分**。这样同一个项目的多轮成绩可以各发各的,而榜单上只认最后那一轮的分。
### ① 新增:「以决赛成绩发布」开关
- **位置**:成绩**审核**页的「发布」按钮旁边,一个勾选框:**「以决赛成绩发布(勾选才计分发牌)」**,**默认不勾**。旁边有一个小徽标随时告诉你当前是哪种口径:**只更新名次(不计分)** / **计分发布(算分发牌)**。
> ⚠️ **本条的位置后来变过一处,口径以 `dev_0.5.15` 那一节为准**:**成绩录入页**上这个常驻勾选框与徽标**已移除**,改成点「发布」时**弹窗二选一**(默认「只更新名次」);**成绩审核页保持原样的常驻勾选框不变**。算法、写库行为与在榜口径提示**一个字节都没变**,只换了入口形状(见上面 `## dev_0.5.15` 的 ④)。
- **不勾(默认)**:这次发布**只更新名次** —— 名次会照常算出来、写进系统(预赛也要排名次),但**不产生积分、不发奖牌**。
- **勾上**:与升级前**完全一样** —— 算分、发牌、上榜。
- **勾选是"这一次发布"的口径,不是页面设置**:发完自动复位(免得您换到别的小项时,把预赛当成决赛算分)。
- ⚠️ **不勾时不会动已有的分**:如果这个小项之前已经**按决赛发布过**(榜上已有分),现在再用「只更新名次」发一次(比如改个名次),那份积分与奖牌会**原样保留、不随名次变动**。界面上会明确提示这一点(见下)。
### ② 界面会告诉你「这一组现在在榜上是什么」
发过一次之后,录入页会常驻显示一行**在榜口径**:
- 「**本组在榜上只有名次**(上次是按只更新名次发布的)」——还没有分;
- 「**本组在榜上是已计分的成绩**(已有 X 分来自上一次按决赛发布)」——分已经在榜上。
文案说得比代码准:系统不记「上次勾了什么」,它看的是**榜面上有没有分**这个事实。
**确认框里也会写清这次会发生什么**:勾选时标题是「确认按决赛成绩发布该组?」,不勾时是「确认发布该组(只更新名次)?」;不勾时不会再报「影响 N 个单位、最大分差 X 分」(那是计分才有的效果),而已有在榜分时会加一句提醒。
### ③ 两条使用上的提醒
1. **两个发布入口口径一致**:成绩录入页与成绩审核页 —— 从哪一页发布口径都一样(否则从审核页发布预赛会被悄悄算成分)。⚠️ `dev_0.5.15` 起录入页的开关改成**发布弹窗里的二选一**(审核页仍是常驻勾选框),见上面 `## dev_0.5.15` 的 ④。
2. **只在"分轮次"的项目上用**:一次就比完的项目照旧**勾上**发布(与以前一样),别漏勾 —— 漏勾的话名次会更新,但**不会计分**,榜上不会有这一项的分(界面上有徽标和在榜口径提示,发布前看一眼就不会错)。
### ④ 升级后 5 分钟核对
1. 打开一个**已经发布过成绩**的小项,看发布按钮旁边的徽标与那行「在榜口径」提示是否说得对(有分的应显示「已计分的成绩」);
2. 找一个可以试的项目:不勾选发布一次,确认**名次更新了、分数没动**;
3. 再勾选发布一次,确认**分数按决赛口径正常算出来**;
4. 其余照旧:**不需要重新导入规则包,也不需要重配任何东西**。
---
## dev_0.5.7
### 一句话
**没有数据迁移,升级后不用重配任何东西**:修好一个小地方 —— 成绩录入页**补报名弹窗里的搜索框**以前只能按**姓名 / 工号**找人,现在也能按**号码布**搜了。号码布是本系统里运动员的唯一标识,现场拿号码布找人是常态。
### ① 改了什么
- **补报名弹窗**里有两个搜索框(一个筛**个人报名**的名单,一个筛**团体报名**的队员名单),原先都只匹配**姓名**与**工号**;现在**同时匹配号码布**。两个框的提示文字也相应改成「搜索姓名 / 工号 / 号码布」。
- 匹配逻辑收口成一个共用函数(姓名、工号、号码布三维并列),所以两个框的口径**不会再各走各的**。
- 关键字处理与原来一致(去掉首尾空格、忽略大小写、空搜索不筛人)。
### ② 升级后核对
1. 打开成绩录入页 → 点「补报名」→ 在搜索框里输入某个运动员的**号码布**,应该能筛出那个人;
2. 在团体报名的「队员」选择里输号码布,同样应该能筛出来;
3. 其余照旧:**不需要重新导入规则包,也不需要重配任何东西**。
---
## dev_0.5.6
### 一句话
**有一条数据迁移(`0026`),老数据一个字节都不变**:团体 / 对抗项目的报名现在能**挂具体的队员了**——界面本来就能一次勾多个运动员,但以前提交时只把姓名拼成一串文字,**身份丢了**,所以配了「个人通道」的团体项目**给不出个人分、个人奖牌,队员也上不了个人积分榜**。升级后:队员**每人各拿一份完整的个人分、各一枚个人奖牌**(是「每人各一份」,不是几个人分同一份);**单位总分与单位奖牌不受影响**(由计分通道的开关决定,双打现场通常都设成不计入)。没有挂队员的老报名、以及只配了团体通道的项目(现场绝大多数球类项目),**行为与升级前逐字一样**。
### ① 数据迁移:一条(`0026`)
- **它做什么**:新建一张 `entry_athletes` 表,记「这条报名里有哪几个运动员」;**报名表本身一个字节没动**(原来那串姓名文字 `member_names` 保留为**显示用名单**,报名的唯一键、队名、名单导出都不受影响)。
- **老数据回填(尽力而为,匹配不上就留空)**:拿原来那串姓名文字去对运动员档案——**先按号码布认**;号码布对不上再按「**姓名 + 单位**」,且**同一个单位里同名的人只有一个**时才认(重名留给人工确认);**认不上的不猜,留空**(维持升级前的行为:这条报名只有单位、没有队员身份)。
- **回填之后会发生什么**:配了**个人通道**的老团体项目会**开始给这些队员发个人分与个人奖牌**(这正是这一版的目的);**只配了团体通道的项目一个字节都不变**(单位总分、单位奖牌、榜单都不动)。⚠️ 所以升级后请**抽查几个团体项目**:原来只写姓名的报名可能仍然没有队员(那就手动挂上,见下)。
- **怎么看它跑过**:启动一次 Console,在运行日志或 `logs\operations\` 当天文件里能看到 `已应用迁移 … name=0026_entry_athletes.sql`。
### ② 新能力:团体报名的队员(谁在这支队里)
| 场景 | 怎么做 |
| --- | --- |
| **导入报名表** | 「队员名单」那一格写**号码布**(例如 `101/102`)→ 自动关联到人;**写姓名只当文字**(名单照常显示,但拿不到个人分),也**不报错**。⚠️ 号码布要**单独成段**:`张三(101)` 这种混写认不出人;一条报名最多 30 人 |
| **补报名时勾人** | 「成绩录入」页的「补报名」/「基础数据 → 报名导入」的团体报名页签:勾上人**会真正提交**到报名上 |
| **录入页改队员** | 「成绩录入」页表格里**「运动员 / 队」那一格旁边的「队员」小按钮**(只有纯团体行才有)→ 勾选式名单(带搜索)→ 保存。改完**当场重算该组别并推榜** |
**口径**:
- **号码布是运动员的唯一标识**(不是证件号、也不是姓名):系统**只按号码布认人**;号码布认到的是**别的单位**的人时**照样关联**,只给一条提示让你核对;
- **每人各拿一份完整的分**(一对双打赢了拿 10 分 → 两个人各 10 分),**各一枚个人奖牌**;
- **单位那本账不变**:单位总分与单位奖牌由「计分通道」的 `计入单位总分` / 个人奖牌是否并入单位榜两个开关决定;**双打现场通常都是不计入**;
- **个人报名(按人报名的那些)没有队员**——它自己就是那个人的身份;**一条报名仍然只占一行**(挂了两个队员也不会变成两行)。
### ③ 显示:一个队员写成 `姓名(号码布)`
- **导出件**:报名名单「姓名」列、分项成绩表 / 全部成绩单页 / 单位奖牌明细的「运动员 / 队」列都写成 `张三(101)`,多个用 `、` 连;**个人得分明细里每人各占一行**(双打两行,各一整份分);
- **大屏**:分项成绩 / 实时赛况的运动员列、底栏「最新完成项目」都带上队员名单(**只写姓名**,号码布太长会把整屏字号压小);
- ⚠️ **没有队员关联的老数据一个字不变**:老报名的导出件与大屏画面与升级前逐字 / 逐像素一致(姓名文字照旧、行为照旧)。
### ④ 升级后 5 分钟核对
1. 启动一次 Console,看运行日志 / `logs\operations\` 当天文件:应能看到 `0026` 这条迁移记录,没有别的报错;
2. 刷新管理端「积分榜」与大屏:**单位总分榜、单位奖牌榜的数字与升级前一致**(本版不动单位那本账);
3. 打开「成绩录入」页:挑一条**团体报名**看「运动员 / 队」那一格——纯团体行旁边应有一个**「队员」小按钮**(个人行没有,这是对的);点开看看有没有队员(老报名可能一个都没有,那是回填没认上,属正常);
4. 挑一个**原来只写了姓名**的团体报名(羽毛球双打那种),点「队员」按钮**把队员勾上**保存;再到「积分榜 → 个人积分榜」确认**这些队员出现了、各有一条分**;
5. 拿一份**写着号码布**的报名表(「队员名单」列写成 `101/102`)试导一次,导入结果里应能看到一条关于队员的汇总提示;
6. 用《上线检查清单》过一遍。
### ⑤ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 队员怎么挂、改在哪、显示成什么样 | 《使用手册》4.13(以及 4.2、10.7 的衔接说明) |
| 导入表里「队员名单」怎么写(号码布 / 姓名各是什么效果) | 《报名名单导入规范》第三节「`member_names` 队员名单」、第四节的例 6、第六节 |
| 迁移与两处判定 | `server/migrations/event/0026_entry_athletes.sql` |
---
## dev_0.5.5
### 一句话
**有一条数据迁移(`0025`),老数据一个字节都不变**:团体项目现在支持**一个单位在一个组别派多支队**(羽毛球双打那种「一队 / 二队」)——以前数据库的唯一索引只允许一个单位一条团体报名,第二支队录不进去。升级后:**现有的报名、成绩、名次、积分、奖牌、导出件与升级前完全一样**(老数据的队名都是空的,在新索引下仍然「一个单位一条」),**多队能力只有在真的给某个单位报了第二支队之后才会体现**;**报名表由 12 列变成 13 列**(最后一列新增「队伍」),**老 12 列表照常导入**。
### ① 数据迁移:一条(`0025`)
- **它做什么**:给报名表加一列 `squad`(队伍,自由文本,**默认空串**),并把「同一个单位在同一个组别只能有一条团体报名」的那个唯一索引换成**带上队伍的版本**(`组别 + 单位 + 队伍`)。**带的运动员的报名不受影响**:它们仍然按「组别 + 单位 + 运动员」唯一,同一个运动员不会因为这次改动而在同一组别报两次。
- **老数据零变化**:升级后所有旧报名的队伍都是**空串**,在新索引下等价于「一个单位一条」——**原来能跑的名单、成绩、榜单、导出件一个字节都不变**(对拍证据见下)。
- **怎么看它跑过**:启动一次 Console,在运行日志或 `logs\operations\` 当天文件里能看到 `已应用迁移 … name=0025_entry_squad.sql`。
- **对拍证据**:接口层把老数据 `GET /entries` 的 JSON 里新字段剥掉后**逐字节相同**;导出层把 9 份导出件的**全部单元格(共 750 行)dump 出来比对**,**md5 相同**(另带敏感度自检,确认这份对拍真的能发现差异)。
### ② 新能力:团体项目多队(只给团体项目)
**怎么做**:一个单位在同一个组别报两支队时——两队**各写一行**,靠队伍列 / 队名区分:
| 场景 | 怎么做 |
| --- | --- |
| **导入报名表** | 用**最后一列「队伍」**写队名(`一队` / `二队` / `青年队`,随便写);**只有一支队的单位这一列留空**。中文表头叫「队伍」,也认「队名」「队伍名」 |
| **补报名(勾选)** | 「成绩录入」页的「补报名」或「基础数据 → 报名导入」的**团体报名**页签:勾上单位后**那一行右侧出现「队伍(留空=不区分)」输入框**,填队名提交即可;**没勾的单位不显示这个框**;已报过的单位徽标写成 `已报名(一队、二队)` |
| **改队名** | 填错不用删掉重建:「成绩录入」页**点那一行的「单位」格** → 弹窗里的 **「队伍」输入框**(**只有纯团体报名才显示**)→ 保存(按钮会按改了啥写成 `保存队伍` / `保存成员单位` / `保存成员单位与队伍`,**没改动时是灰的**) |
**口径**:
- **队伍只对团体项目**:个人项目(按人报名的那些)**不受影响**,也没有「队」这回事;个人行即使填了队名也**不会写进库里**,导入结果里会给一条提示;
- **留空 = 不区分队伍**:绝大多数单位只有一支队,**这一列留空 / 整列不写 / 表头里没有它**都是一样的(= 升级前的行为);
- **同一个单位 + 同一个组别 + 同一个队名只能有一条**:要派两支队就写**两个不同的队名**;写重了会被**逐行拦下**并说清是哪个单位、哪个队,**其余行照常导入**;
- **一个单位两个队都进前三 → 这个单位拿 2 枚牌**(两支队各算各的名次,分与牌都记到同一个单位头上);
- **队伍不影响算分**:两队的成绩、名次、积分各算各的,最后都汇到同一个单位的总分里;
- **不涉及「按单位手工录名次」的项目**(篮球那种每个单位只有一支队):名次仍然按单位录,与队名无关。
### ③ 显示口径:`单位名 + 队名`
- 写出来**不加括号、不加分隔符**:`第一分公司二队`;**队名留空时逐字保持原样**(`第一分公司`);
- **写队名的地方**:成绩录入页 / 成绩审核页的「单位」列、批量操作的确认框、报错里的行名、大屏的分项成绩 / 实时赛况 / 底栏「最新完成项目」,以及**导出件的「单位」列**(分项成绩表、全部成绩单页、报名名单、单位奖牌明细总表、单位积分总明细总表);
- **不写队名的地方**:**单位总分榜、单位奖牌榜、个人积分榜**——这三处是单位级汇总,分与奖牌本来就归到单位。
### ④ 报名表 12 列 → 13 列(老表照常导入)
- 新增的是**最后一列「队伍」**(英文规范名 `squad`,中文别名「队伍」/「队名」/「队伍名」),**可选**:整列不写、格子留空、表头里没有它,三种都合法;
- 新列加在**表尾**,所以**手里原来的 12 列表一列都不会错位、照常导入**;下载的新模板是 13 列(分工作表模板也一样);
- 填法、报错文案与例子见《报名名单导入规范》第二节 / 第三节「队伍」/ 第五节的例 5。
### ⑤ 改完队名,记得顺手改报名表
⚠️ 导入是**按「小项 + 组别 + 单位 + 队名」认人**的:在界面上把「二队」改成「青年队」之后,如果以后再导一份**还写着「二队」**的报名表,系统会**新增出「二队」那一条报名**(不会静默合并,也不会报错)。所以**改队名时把报名表里的队名也改成一样**。
### ⑥ 升级后 5 分钟核对
1. 启动一次 Console,看运行日志 / `logs\operations\` 当天文件:应能看到 `0025` 这条迁移记录,没有别的报错;
2. 刷新管理端「积分榜」与大屏:**单位总分榜、单位奖牌榜、个人积分榜的每一个数字与升级前一致**(本版不改任何算分口径);
3. 打开「成绩录入」页:**「单位」那一列与升级前长得一样**(老报名的队名是空的,不会多出任何字);点某一行的「单位」格,**纯团体报名**的弹窗里应多出一个「队伍」输入框(**个人报名看不到这个框**,这是对的);
4. 打开「基础数据 → 报名导入」,下载一份模板:**表头最后一列应是「队伍」**(共 13 列);拿一份升级前的老 12 列表导一次,应与升级前一样正常导入;
5. 拿一个真要多队的项目试一次:同一个单位、同一个组别报两行、队伍列分别写 `一队` / `二队`,导入后到录入页确认**两支队各占一行、单位列写成 `第一分公司一队` / `第一分公司二队`**,录成绩发布后**两队各拿各的名次、单位的分数是两队之和**;
6. 用《上线检查清单》过一遍。
### ⑦ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 多队怎么录、显示成什么样、改队名在哪 | 《使用手册》4.12(以及 4.2、4.11、10.7、11.1、17.1 各处的衔接说明) |
| 导入表「队伍」列怎么写、报什么错 | 《报名名单导入规范》第二节、第三节「队伍」、第五节的例 5、第六节 |
| 迁移与唯一索引 | `server/migrations/event/0025_entry_squad.sql` |
---
## dev_0.5.4
### 一句话
**没有数据迁移,升级后不用重配任何东西**:修掉一个会让**成绩录入页整页白屏**的问题 —— 在「按单位手动录名次」的项目(例如篮球)底部**「团体名次」输入框里填数字**,或者在顶部工具条里填**批次号 / 每批人数**,页面会整个变成空白。**已经录好的成绩、名次、榜单数据都不受影响**,这三处只是以前一填数字就崩。
### ① 修了什么(三处输入框,同一个根因)
- **团体名次**(「按单位手工录名次」的项目,录成绩页底部那个区块):往某个单位的名次框里填数字 → **整页白屏**;
- **批次号 / 每批人数**(录成绩页顶部「批量设置批次」工具条):同样填数字 → **整页白屏**;
- 根因在浏览器与框架这一层:这几个框是**数字输入框**,填进去的值会被**自动转成数字**,而处理它的代码是按「文本」写的(对值调用文本方法)→ 当场抛错。又因为它发生在**页面渲染过程**里,所以表现不是「弹个错误」,而是**整页变空白**(这也是为什么它看起来比一般错误严重)。
- 三处都改成**先归一成文本再解析**;**填同样的内容,结果与以前预期的完全一致**(团体名次填 `3` 就是第 3 名,填 `0` 或留空仍是「清空该单位」)。
- 顺带把仓库里**其余同类输入框逐个体检了一遍**(共 21 处),确认只有上面这三处会出问题,其余的写法本来就是安全的。
### ② 升级后 5 分钟核对
1. 打开一个「按单位手工录名次」的项目(例如篮球)的录成绩页,在底部**「团体名次」**里给某个单位填一个数字:**页面应当正常显示**;名次重复或超出参赛单位数时,输入框会描红并给出提示(**只提示不拦截,仍然允许保存**);
2. 在录成绩页顶部**「批量设置批次」**里填一个数字,确认页面不白屏、按钮可点;
3. 打开一个**联队**的报名(单位编码形如 `T1+T2`),确认单位那格显示两个单位的名字与份额 —— 这是上一版(0.5.3)新增的能力;
4. 其余照旧:**不需要重新导入规则包,也不需要重配任何东西**。
---
## dev_0.5.3
### 一句话
**有一条数据迁移(`0024`),但升级后不用重配任何东西、老数据一个数字都不变**:一条报名现在可以挂**多个单位、按份额分成**——两个单位因参赛人数不足合并成一个联队参赛时,分数按份额拆给各单位(默认均分,允许出现 7.5 这种小数),奖牌**每个成员单位各记一枚整枚**。升级前就有的赛事,榜单、名次、积分、奖牌、导出件与升级前一模一样——**联队能力只有在真的把某条报名配成联队之后才会体现**。不用重新导入规则包。
### ① 数据迁移:一条(`0024`)
- **它做什么**:新建一张 `entry_team_shares` 表,记「这条报名归哪些单位、各占多少」(积分份额 + 奖牌是否记给本单位),并把**已有的每一条报名回填成「主单位独占 100%」**。`entries.team_id` 保留为**主单位**(联队时就是加号左边那个),既有的查询、唯一键、榜单、导入都还认它。
- **只加一张表 + 回填**:不改任何既有字段、不删任何数据;回填用的是「已经有份额就不动」的写法,重复执行(或迁移被重放)**不会覆盖已经配好的联队份额**。
- **怎么看它跑过**:启动一次 Console,在运行日志或 `logs\operations\` 当天文件里能看到 `已应用迁移 … name=0024_entry_team_shares.sql`。
### ② 老数据零变化(有对拍证据)
- **无联队的老数据在引擎层、接口层、导出层三层都做过签名 / 逐行对拍,差异 0 处**,签名取自**联队实现之前**的代码,实现之后必须一字不差:
- 引擎层:`server/internal/domain/legacy_forms_golden_test.go`(`TestLegacyFormsScoringUnchanged`,签名 `a2aa1c97…`);
- 接口层:`server/internal/httpapi/legacy_forms_golden_test.go`(`TestLegacyFormsAPISnapshotUnchanged`,签名 `aa6cea37…`);
- 导出层:`server/internal/export/joint_team_export_test.go` 的 `TestSingleUnitExportsHaveNoJointTeamText`(只有一条独占份额时,各份导出件一个字都不能多)。
- 所以:**升级后单位总分榜、单位奖牌榜、个人积分榜的每一个数字、每一条名次、每一枚奖牌、每一份导出件都与升级前一模一样**。本事只在真的把某条报名配成联队之后才生效。
### ③ 不用重新导入规则包
- 本版**没有改规则包格式、没有改计分口径、没有改任何计分通道配置**:随包规则包与现场那份都不用动,也不需要重算。
- (对照:dev_0.5.1 那次是建议重新导入的,本版不是。)
### ④ 新增能力:联队 + 份额分成(口径已定稿)
| 口径 | 定稿写法 |
| --- | --- |
| 份额 | **可配、默认均分**(权重都留空 = 各 50%;填 2 与 1 = 67% 与 33%) |
| 积分 | **平均拆,接受 .5**:团体第 1 名 15 分 → 两个单位各 **7.5 分**(榜单上就写 7.5) |
| 奖牌 | **每个成员单位各记一枚整枚**:联队的金牌,两个单位**各记 1 枚**(不是各 0.5 枚) |
| 显示 | 联队的名字**都写出来**:`第一分公司+第二分公司联队` |
| 导入 | 报名表的**单位编码**列写 `A+B`(加号左边那个是**主单位**;全角 `+` 与两侧空格等价) |
怎么用:
- **配联队的两个入口**:
1. **导入报名表**时把单位编码写成 `A+B`(默认均分;最多 5 个单位;`A+`、`+B`、`A++B`、`A+A` 这些写法会被**逐行拦下**并说清是第几行、错在哪,**其余行照常导入**);报名模板(分工作表 xlsx)的「单位编码」表头挂了一条**单元格批注**、该列还有**输入提示**,中英各一份,内容就是这套写法——**表头文字、列序、12 列、工作表命名一个字都没动**,老表格照常导入(**这一版是 12 列;`dev_0.5.5` 起表尾新增了第 13 列「队伍」,见那一节**);
2. 导入后到**「成绩录入」页点那一行的「单位」格**,打开「**成员单位与份额**」弹窗改(加成员单位、填权重、去掉成员)。⚠️ **非均分(例如 2:1)不能在导入表里写**,只能在弹窗里改。
- **谁能改**:份额编辑与录成绩同档,**录入员及以上**;访客只读时输入框置灰并写明原因。
- **改完当场生效**:保存立即重算该小项该组别并推送大屏。
- **录入与复核页看得到**:联队的单位格显示成 `联队 第一分公司 50% 第二分公司 50%`(非均分时带 `(权重 2)`)。**普通报名(只有一个单位)的格子文案、元素一个字没动**,只是多了一个可点的入口和一行悬停提示。
- **大屏**:分项成绩表与实时赛况的「单位」列写 `第一分公司+第二分公司联队`,并**把两个单位的 logo 都放上**(单位列宽会为该行的 logo 数留位);底栏「最新完成项目」同样写联队全名;单位奖牌榜与总积分榜本来就是逐单位一行,服务端已按份额拆好(例:A、B 各 1 枚金、各 7.5 分)。
### ⑤ 导出件里现在写成什么样
| 位置 | 现在写成 |
| --- | --- |
| 分项成绩表 / 全部成绩(单页)「单位」列 | `第一分公司+第二分公司联队` |
| 同上「备注」列 | `联队分拆:第一分公司 5 分(50%)、第二分公司 5 分(50%)` |
| 报名名单「单位编码 / 单位名 / 备注」 | `T1+T2` / `第一分公司+第二分公司联队` / `联队分拆:第一分公司 50%、第二分公司 50%` |
| 单位积分明细 / 总明细「备注」列 | `联队分拆:… 各多少分(各多少 %)` |
| 个人积分总明细「来源」列 | `成绩(联队分拆:…)` |
| 单位奖牌明细「奖牌」列 | `金牌(联队 第一分公司+第二分公司,各单位各记 1 枚)` |
| 总表 / 单位奖牌明细的副标题 | 追加 `· 注:本赛事含联队,奖牌按「每个成员单位各记 1 枚」统计,各单位奖牌数之和会大于实际颁发数` |
- **按单位筛选导出**:成员单位的名单里也能看到**主单位是另一个单位的联队报名**(否则 B 导出自己的名单会发现「没派人上场」);
- 导出件的**值只在真的有联队时才变**:没有联队的赛事,导出件与升级前逐字一致。
### ⑥ 两条要主动告知的口径后果
1. **各单位的奖牌数之和会大于实际颁发的奖牌数**——因为联队的每一枚奖牌**每个成员单位各记 1 枚**。例:A、B 联队拿 1 枚金牌,奖牌榜上 A 记 1 金、B 记 1 金,而场上只颁发了 1 枚。**这是定稿口径,不是数据错了**;含联队的「总表 / 单位奖牌明细」副标题会写这句话,奖牌明细里联队那一格也写着「各单位各记 1 枚」。**拿奖牌数去和实际颁发的奖牌核对时请记住这一点。**
2. **个人积分总明细里,联队里的同一个人会出现两行**——引擎按份额给两个成员单位各留了一行流水,导出侧只在「来源」列写清这两行为什么出现(`成绩(联队分拆:…)`)。**看两行加起来才是这个人的总数**;这不是重复计分(他的总分没有被算两遍),是按份额拆开的结果。
### ⑦ 顺带:单位奖牌明细的「奖牌」列加宽并允许换行
- 导出件「单位奖牌明细」的「奖牌」列原来是**宽 8、居中**,写不下 `金牌(联队 第一分公司+第二分公司,各单位各记 1 枚)` 那句(会被右边的「运动员/队」裁掉),现在**加宽到 26 并允许换行**。
- **列里的值没变**:没有联队时这一格仍然只是「金牌」「银牌」「铜牌」三个字。
### ⑧ 升级后 5 分钟核对
1. 启动一次 Console,看运行日志 / `logs\operations\` 当天文件:应能看到 `0024` 这条迁移记录,没有别的报错;
2. 刷新管理端「积分榜」与大屏:**单位总分榜、单位奖牌榜、个人积分榜的每一个数字与升级前一致**(本版不改任何计分口径;只有你之后自己配了联队的地方才会变);
3. 打开「成绩录入」页:普通报名的**「单位」那一格与升级前长得一样**(只是鼠标移上去变成手型、有一行悬停提示,点一下会打开「成员单位与份额」弹窗,什么都不改直接关掉等于没动过);
4. 打开「基础数据 → 报名导入」,下载一份「**分工作表 · 中文表头**」,把鼠标停在表头「**单位编码**」那一格上:应弹出「单位编码怎么写」的批注(内容含 `A+B`);点中该列任一格应弹出同样的输入提示;
5. 真要用联队的:按 ④ 把那条报名配好(导入写 `A+B`,或点单位格改份额),确认榜单上两个单位**各拿到一半分、各记一枚牌**、大屏上两个单位的 logo 都在;
6. 用《上线检查清单》过一遍。
### ⑨ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 联队怎么录、份额怎么改、分成后长什么样 | 《使用手册》4.11 |
| 导入表里联队怎么写、写错了报什么错 | 《报名名单导入规范》第三节的「单位编码」、第六节 |
| 大屏与导出件上联队写成什么 | 《使用手册》17.1、8.3、11.1 |
| 迁移与份额表结构 | `server/migrations/event/0024_entry_team_shares.sql` |
---
## dev_0.5.2
### 一句话
**没有数据迁移,升级后不用重配任何东西**:报名模板新增「分工作表」两种(一个小项组别再挤在一张总表里,填表不容易串行);修好了「同一个人报多个项目被误判成号码布重复、整块拦下」;自动备份加固——备份写到一半时关程序、或备份失败,都不会在备份目录里留下半截文件。
### ① 新增:报名模板支持「一个小项组别一张工作表」
- 「基础数据 → 报名导入」的「下载模板」现在有四个按钮:**中文表头**、**英文表头**(这两种还是**一份总表** CSV,全部报名挤在一张表里,与升级前完全一样),以及新的 **分工作表 · 中文表头**、**分工作表 · 英文表头**(XLSX)。
- 后两种由系统按当前赛事**已配好的小项 × 组别**生成:**一个小项组别一张工作表**,工作表名就是「小项名称@组别名称」(例:`100 米跑@男子组 30岁以下`)。填表的人不用在一张大表里反复抄小项名与组别名,也就抄不串了;收上来直接导入,不用再拆。
- **导入时工作表名和表内的小项 / 组别列都认**:两者说的是同一个项目就照常导入;**说法不一致会把那张表整张拦下、并指出是哪一张**,不会闷头按其中一个算。
- 小项名称、组别名称里**不能再用 `@`(含全角 @)**:它是工作表名的分隔符,写进名字会让「小项@组别」拆不开。新建 / 改名时会当场拦下并说明原因;**规则包里名字带 `@` 的**这类写法仍能导出、也仍留在库里,只是**不能再导入**;库里已经存在的带 `@` 名字照旧能用,只是**不会出现在分工作表模板里**(这种名字拼不出合法的工作表名)。
- 原来那种单表 CSV 导入(中文表头 / 英文表头两套)**一个字没改**,手上已有的表格照常导入。
### ② 修复:同一个人报多个项目,不再被误判成「号码布重复」
- 以前判重只看号码布,而**证件号是选填的**:同一个人用一个号码布报两个项目,会被整块拦下、提示「号码布 XXX 在报名表内重复出现,这些行未导入」,只能人工删行再传一次。
- 现在按**证件号**来分辨:号码布相同、**证件号也相同**(能证明是同一个人)→ 放行;号码布相同、**证件号都填了且不一样**(真抢号)→ 仍然报错;**一方证件号没填** → 放行,并在导入结果里给一条提示,让人自己扫一眼确认。
- 团体报名行里的号码布也一并查重(同一条规则)。
### ③ 加固:自动备份不会再在备份目录里留下半截文件
- 半截备份(程序被强杀、磁盘写满、备份写一半时退出——**文件名看着正常、内容却不完整**)以前会被列进「系统 → 备份」的列表里,**手一抖当成正常备份去「恢复」就会出事**。
- 现在备份一律**先写临时名、写完整了才改成正式名**;关停程序时也会**等一下正在写的这一轮备份收尾**(最多 10 秒,等不到只记一条警告,不会卡住关不掉)。
- **升级后建议顺手看一眼**:打开「系统 → 备份」,如果列表里有个**明显偏小或 0 字节**的备份,那是老版本留下的半截文件。界面没有删除按钮,请到数据目录 `%LOCALAPPDATA%\ScoreboardConsole\data\backups\` 手工把它删掉。**留着也不会被自动使用**——点「恢复」时服务端会先校验这份备份能不能用,不合格会直接拒。
### ④ 升级后 5 分钟核对
1. 覆盖安装后启动 Console,看运行日志:**这一版没有数据迁移**,只应有正常的服务启动信息,没有迁移记录、也没有报错;
2. 打开「基础数据 → 报名导入」,「下载模板」处确认有四个按钮;点「分工作表 · 中文表头」下载一份 xlsx,打开应看到**多个工作表**、表名形如「小项@组别」;
3. 拿手上的一份报名表试导入一次,结果应与升级前一致(该进的进、该报的报);
4. 打开「系统 → 备份」,确认列表正常,并点一次「立即备份」;
5. 用《上线检查清单》过一遍。
---
## dev_0.5.1
### 一句话
**两条数据迁移、三处计分口径变化、安装器换新,升级后建议重新导入一次新版规则包**:迁移 `0022` 把「小项已被删除、积分流水却还挂在榜上」的历史脏数据一次性作废(**只影响分数,不影响名次与奖牌,人工调整分不受影响**);迁移 `0023` 给计分通道加一列「聚合口径」(默认「平均」,不切就与升级前一样);**单位奖牌榜改为按奖牌数独立排名,可能与积分榜顺序不同**;安装器换新——界面全面重做,并且**安装时改了三处行为**(一启动就申请管理员、自动关闭正在运行的旧版本、装到哪与「上次安装目录」的关系,见 ③)。
### ① 数据迁移:两条(0022 / 0023)
**`0022` —— 历史孤儿积分流水一次性作废**(现场可能少几分,那是升级前就该清掉的分)
- **它清理什么**:升级前删掉一个「只按单位录名次、没有报名行」的小项时,它名下**已经产生**的积分流水会留在库里、并且没有被作废——分数继续算在单位总分里,但这个小项已经不存在,此后任何一次重算都再也碰不到这些流水(只能改库才能清)。
- **只作废「成绩流水」与「破纪录加分」**:这两类流水一定挂着小项,空小项 = 小项已被删除。**人工调整分不受影响**——人工调整本来就可以不挂小项,是合法的,不会被误清。
- **影响面**:**只影响分数**。这些流水本来就没有名次、也没有奖牌,所以**名次与奖牌一件不变**;作废后这些分在**单位总分与单位积分明细里一起消失**(不会出现「榜上无分、明细有分」)。它是**作废不是删除**:流水行与作废时间都留着,并注明是升级时清理的历史数据,事后查得到。
- **怎么确认**:启动一次 Console,在运行日志或 `logs\operations\` 当天文件里能看到迁移记录(`已应用迁移 … name=0022_void_historical_orphan_ledger.sql`)。**现场确认删过「带分的小项」的机器上,相应单位的总分会少一点**——那是清掉了历史脏数据,不是这次升级算错了。
- **想核对到条**:升级前先导出一次单位积分明细存底(「积分榜」页的「导出该单位明细」或「导出总明细」),升级后再导一次对比,就能看出少了哪几条。
**`0023` —— 计分通道新增一列「聚合口径」**(只加列、不动既有数据)
- 老通道一律取默认**「平均」**(团体成绩 = 该单位成员成绩的平均值),**升级前后榜单一分不差**;想改成「总和」的,到「基础数据 → 计分通道」手工切(见《使用手册》10.4)。
### ② 三处口径变化(现场看到的数字 / 顺序可能不同)
**1)「末位归零」:末位不再丢分**
- 本版随包规则包里两个模板的「末位归零」都设成**关**:**参赛人数少于表里档位数时,最后一名按表拿分**(例:3 人参赛、模板有 6 档,第 3 名按表拿 6 分),不再被抹成 0。重新导入规则包会把库里这个开关一起写成「关」——**此前在模板页手工勾过它、或导入过把它勾上的规则包**的机器,这一步之后末位分数会变(这是定稿口径:规则里没配的名次分数就是 0 分,分只按表发)。
- 另外,**开关打开时也只抹「真正落在最末那一档」的那一条**:并列档位从更靠前的名次起步、区间跨过末位的并列组**不再被整组抹 0**,按他们并列到的名次照表拿分(7 人里三人并列第 5、前六档表 → 每人按第 5 名拿分;升级前是 0 分)。
- **名次与奖牌不受影响**:这一段改的只是「这一组拿多少分」。
**2)单位奖牌榜改为按奖牌数独立排名**
- 顺序与名次:**金牌 → 银牌 → 铜牌 → 单位编码**,**不再跟着单位总分榜**。所以「金牌多、总分低」的单位在奖牌榜上排前面、在单位总分榜上排后面——**这是口径不同,不是数据错了**(升级前奖牌榜沿用总分榜顺序,会出现「金牌多的排在后面」)。
- 三处同一个口径:管理端「积分榜 → 单位奖牌榜」、大屏的单位奖牌榜页、大屏底栏滚动的奖牌 Top3。
- 「导出奖牌明细」(`单位奖牌明细.xlsx`)逐枚列出奖牌的总表不受影响;各单位**分表的先后仍按单位总分排**(得分高的在前),与奖牌榜的顺序不同,这不是错。
**3)同分并列段的排列按规则包的排序口径**
- 总积分榜(及个人积分榜)里**总分相同**的单位那一段内,先后按规则包里的排序口径排——本赛会包的默认是 **总分 → 金 → 银 → 铜 → 单位编码**。
- 重新导入规则包会把这条口径按规则包的值写回库里(本赛会包是 **总分 → 金 → 银 → 铜 → 单位编码**):**此前导入过排序口径写法不同的规则包**的机器,这一段里单位的先后可能变化——**变的是顺序,分数与奖牌都不变**,看到顺序变了不要当成故障。
- **单位奖牌榜不受这条影响**:它固定按金 → 银 → 铜 → 单位编码(见上面第 2 条)。
### ③ 安装器换新(界面重做 + 三处安装行为变化)
- **界面全面重做**:安装程序(`Scoreboard Console-<版本>-x64.exe`)的欢迎 / 目录 / 许可 / 进度 / 完成各页与按钮换成新素材,并**支持 4K 高 DPI**(系统缩放到 200% 时界面按缩放后的尺寸排布,字与按钮不会糊成一团)。
- **① 安装程序现在一启动就申请管理员**:双击安装包会**先弹一次 UAC**(Windows 的「是否允许此应用对你的设备进行更改?」),点「是」之后整个安装过程都在提权状态下进行 —— 装到 `C:\Program Files\…`、自动放行防火墙入站 19540(规则名 `Scoreboard Console (TCP 19540)`,卸载时会自动删掉)都靠它。
- **管理员机器**:会弹这一次 UAC;装完不用再手工放行防火墙。
- **受限账户(不是管理员)的机器**:**不会弹 UAC**(弹了也没用),安装程序会**自动降级为「仅为我安装」**,装到你的用户目录(`%LOCALAPPDATA%\Programs\…`),**不装**到 Program Files,也**加不了**防火墙规则 —— 这种机器要照《部署指南》7.5 在管理员账户下手工放行一次。
- **② 安装时会自动关闭正在运行的旧版本**:升级前**不用手工退出**「赛事积分榜」——安装程序会自己找到在跑的旧版本进程并关掉(连它拉起的 `scoreboard.exe` 服务端进程一起关)。以前那个「请先关闭程序」的提示框会被新界面压在下面,看不到又挡着流程,现场很容易卡在那儿出不来。
- **③ 安装位置(装到哪个目录)**:
- 向导里**默认填这台机器上次装的那个目录**;
- 切换「为所有用户安装 / 仅为我安装」时,**目录输入框会跟着变成该模式的默认目录**(分别是 `C:\Program Files\Scoreboard_Console` 和你的用户目录);
- 但你**手工改过路径(或点过「浏览…」)之后,再切模式也不会冲掉你填的那个目录** —— 想回到模式默认目录,把路径清空重新切一次模式即可;
- 想知道这次装到哪了:右键桌面「赛事积分榜」快捷方式 →「属性」→ 看「目标」。
- **静默安装**(`安装包.exe /S`,无人值守用):**会跳过旧版本卸载器** —— 旧卸载器自己可能弹一个没人能点的提权框,跳过它才能保证无人值守下一定跑得完。静默安装也可以用 `/allusers` / `/currentuser` 指定装法;用 `/D=` 指定目录时**以 `/D=` 为准**(不取「上次安装目录」的记忆)。
- 装到哪、装什么、桌面快捷方式(「赛事积分榜」)、卸载方式、数据与配置在 `%LOCALAPPDATA%\ScoreboardConsole\`(**覆盖安装不会丢**)—— 这些与升级前一致(交互式升级仍会先跑一遍旧版本的卸载器,这也是数据不能放在安装目录里的原因,见《部署指南》3.4)。
### ④ 升级后建议:把新版规则包重新导入一次
**做法**:管理端「导出 → 规则包 → 选择 YAML → **预览差异** → 确认导入并全量重算」,导入随本次发布的新版规则包。
**为什么建议做**:旧包缺少本次修正的通道配置(几个双计项目的团体通道口径、纯团体项目该走「按成绩自动排名」还是「人工名次」等),**不重新导入就不会生效**。
⚠️ **动手前先看清这个前提**:**导入会按规则包重写各小项的计分通道配置**——规则包覆盖到的小项,库里现有的通道会被整包替换。所以:
- **建议先导入、再按需要手工微调个别项目**;顺序反过来(先微调再导入),手工改动会被这次导入覆盖掉;
- 导入前先看「预览差异」里列出的通道增删改,确认没有把现场专门配过的项目改掉;发现被改,导入后到「基础数据 → 计分通道」改回来即可(保存后该小项会重算);
- 导入会**全量重算积分并刷新榜单**,尽量安排在**没有正在进行的项目**时做;
- 导入后正是上面那三处口径变化的生效点,请对着 ② 逐条看一遍榜单。
### ⑤ 升级时可能遇到的拦截(这两处以前会静默通过)
- **导入规则包时**:如果这份规则包存在**配置冲突**——典型情形是某个小项的**名次是靠人工录入的**(例如球类、拔河这类由裁判判名次的项目),规则包却给它配了**「按成绩自动排名」**(或「按队内平均」)的通道——导入会被**整包拒收**,一个字节都不写,并直接说明**是哪个小项、为什么不行、怎么改**(给这个小项单独写一段人工名次的通道配置,或调整规则包的匹配条件别套到它身上)。升级前这种情况是「先把通道换掉、紧接着重算时才报错」,名次表会停在半空(分数还在、奖牌却清零),自愈与重试导入都会因为同一个小项再失败一次——**现在不会留下这种坏状态**。
- **保存积分模板时**:如果**分值表里配的名次超过了「录取名次」**(例如分值表配到第 7 档、录取名次还留着 6),保存会被**拒绝**并说明怎么改:要么把录取名次改成 7、要么改成 0(0 = 不按名次封顶),要么删掉超出录取名次的那几档(例子里的第 7 档)。升级前这种配置能安静存下来,但第 7 档及以后在榜上恒为 0 分,界面上看不出任何异常——**现在会在保存这一步就说清楚**。
### ⑥ 升级后 5 分钟核对
1. 启动一次 Console,看运行日志 / `logs\operations\` 当天文件:应能看到 `0022` 与 `0023` 两条迁移记录,没有别的报错;
2. 刷新管理端「积分榜」:**单位总分榜、个人积分榜的数字与升级前一致**(除了 `0022` 清掉的历史脏数据);**单位奖牌榜的先后可能变了**(现在按金 → 银 → 铜排);
3. **看到「同分单位的先后变了」不要当故障**:重导规则包会把排序口径按规则包的值写回(本赛会包是 总分 → 金 → 银 → 铜 → 单位编码),**变的是顺序,分数与奖牌一件不变**(见 ② 第 3 条);
4. 打开「基础数据 → 计分通道」,选中一个双计项目(例如团体引体向上),确认「聚合口径」一列是**「平均」**——不切换就与升级前口径一致;
5. 打开「成绩录入」页:已发布的行右侧应**并排**「修改本条」与「作废本条」两个按钮;点一下成绩格会弹确认框(**点「取消」什么都不做**);
6. 按 ④ 导入新版规则包,导入后核对该项目的计分通道与榜单;
7. 用《上线检查清单》过一遍。
### ⑦ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 已发布成绩怎么改(修改 / 作废的区别) | 《使用手册》4.10;《常见问题》Q98 |
| 单位明细里的运动员 / 来源 / 已修订 | 《使用手册》8.1、8.3、17.3 |
| 团体分的「平均 / 总和」口径 | 《使用手册》10.4;《常见问题》Q15 |
| 单位奖牌榜为什么与积分榜顺序不同 | 《使用手册》8.2 |
| 导入被拒 / 保存模板被拒是什么原因 | 本文件 ⑤ |
| 迁移与安装 | 本文件 ① ③;《部署指南》3.1、7.5 |
---
## dev_0.5.0
### 一句话
**没有数据迁移,旧赛事项目包照常导入**:本版改的是「大屏控制 → 局域网大屏」卡片的取证逻辑——地址改为**按地址类别**判定(`169.254` 自动配置地址不再被排除),并新增**宽松兜底 / 默认路由推断 / 手动添加地址**三层,空态会说明被跳过的网卡与原因。以前卡片会把「自动配置地址」当成没有可用地址,现已修正。
### ① 局域网大屏卡片改为按地址类别判定
- **以前**:只取「接口 Up 且非回环」的网卡,并把**链路本地地址(`169.254.x.x`)也排除**,理由写的是「DHCP 没拿到地址才自配的,说明网络不健康」——那是对网络健不健康的**猜测**,不是地址能不能用的**事实**。
- **现场形态**:有机器就在 **`169.254` 自动配置网段**(本网段没有 DHCP,设备靠链路本地地址互访),平板实测能访问 `http://169.254.79.190:19540/display`,网络与防火墙全通;而卡片却因为那条猜测显示「未检测到局域网地址」——**那是误判**。
- **现在**:只排除四类「作为对端访问目标没有意义」的地址——**回环、未指定(`0.0.0.0`)、组播、广播**;**链路本地(自动配置)地址与私网、全局地址一样进候选**,只是**排在最后**并带一句说明「自动配置地址:本网段没有 DHCP,同网段设备可以互相访问」。判定全部走标准库谓词,**不抄一份网段字面量到代码里**(抄错或漏一段就会变成「明明有地址却说没有」,正是这次要修的毛病)。
### ② 新增三层兜底(宽松兜底 / 默认路由推断 / 手动添加地址)
| 层次 | 触发时机 | 卡片表现 |
| --- | --- | --- |
| 严格轮 | 正常机器(网卡 up 位读得到) | 地址照常列出,不加提示 |
| **宽松兜底** | 严格轮一无所获时(Windows 上部分驱动 / 虚拟网卡**读不到 up 位**) | 不再要求 up 位,把地址捡回来并提示「网卡状态读不到(可能是驱动或虚拟网卡),地址本身可用,打不开就换下面一条」 |
| **默认路由推断** | 前两轮都为空时 | 问操作系统「去局域网走哪块网卡」(UDP Dial:只查路由表、**不发一个包**),地址提示「这条地址是按系统默认路由推断出来的(网卡枚举里没有它),地址本身可用,打不开就换下面一条」 |
| **手动添加地址** | 以上都没给出你要的网段时 | 卡片下方「手动添加地址」填入 `ipconfig` 里的 IPv4 地址,点「添加」立刻出链接与二维码;手填的**排第一条**、带「手动」徽标、可「删除」,记在浏览器本地**刷新不丢**(最多 10 个) |
严格轮有结果就绝不跑宽松轮,前两轮任意一轮有结果就绝不跑路由探测——只有真的一个地址都拿不到时才会多花那几次本地 UDP connect,正常机器上一次都不会发生。
### ③ 空态会说明被跳过的网卡与原因
- 空态从一句「未检测到局域网地址(可能是没插网线 / 只有回环)。」改成逐条列出**检查过、但没贡献地址**的网卡与原因,例如「没找到可用的局域网地址。已检查:以太网(接口未连接)、WLAN(只有 IPv6 地址)。」;四种原因是「接口未连接」「只有 IPv6 地址」「接口没有地址」「回环接口」,**最多列 5 条**,网卡更多时其余折叠成「**等共 N 块网卡**」;
- 老服务端不返回这份清单时,显示的仍是原来那句,行为与升级前完全一致。
### ④ 没有数据迁移
- 本版**没有任何数据库迁移**,也没有设置项迁移;旧赛事项目包照常导入、切换、导出,已有榜单数字与升级前一致。
### ⑤ 升级后 3 分钟核对
1. 启动 Console,打开 `logs\console.log`:没有意外报错;正常应有一行「局域网地址:…(网卡名)」,一个地址都没有时会是一条带原因的 WARN,方便直接看出该修哪张网卡;
2. 用录入员及以上账号进「大屏控制」页,确认「局域网大屏」卡片列出地址;只有 `169.254.x.x` 的机器上应能看到它、并带「自动配置地址」那句说明;
3. 用平板 / 手机扫第一个地址的二维码,确认能打开大屏;
4. 自动检测为空时试一次「手动添加地址」(填 `ipconfig` 里的地址),确认立刻出链接与二维码;
5. 局域网设备连不上的机器,先确认装的时候选的是哪一项:选**「为所有用户安装」**的,安装程序提权时已经自动放行入站 **19540**(防火墙里能看到规则 `Scoreboard Console (TCP 19540)`);选「仅为我安装」的,按《部署指南》7.5 的命令手工放行一次;
6. 用《上线检查清单》过一遍。
### ⑥ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 三类地址的含义、手动添加地址 | 《使用手册》第 9 节 |
| 防火墙放行与排障常识 | 《部署指南》7.5、7.9 |
| 空态原因与自检工具 | 《常见问题》Q101 |
| 接口字段与判定顺序 | `devdocs/05` 大屏控制与运维 |
---
## dev_0.4.0
### 一句话
**没有数据迁移,旧赛事项目包照常导入**:本版修了两个现场问题(kiosk 窗口误点穿、kiosk 窗口被任务栏盖住)并新增一个功能(大屏控制页的「局域网大屏」卡片)。⚠️ 唯一要留意的:**老机器配置文件里的 `display.ignoreMouse: true` 已废弃、不再生效**——升级后大屏窗口自动变成「不穿透」,想恢复穿透要改用新键名。
### ① kiosk 窗口不再点击穿透,旧键 `display.ignoreMouse` 已废弃
- **以前**:多屏 kiosk 窗口默认开启 `setIgnoreMouseEvents(true)`,本意是「不接收误触」,但它的真实语义是「**本窗口完全忽略鼠标**」——点击会落到大屏**后面**的窗口或桌面上,现场想防的误触反而变成误点别的东西;被忽略鼠标的窗口也点不活、被别的窗口盖住后抢不回前台。
- **现在**:大屏窗口**默认吞掉鼠标点击、不穿透**,点击不会漏到后面的窗口或桌面,被盖住后点一下就能回到最前。
- **开关改名**:`display.ignoreMouse` → **`display.allowClickThrough`**(默认 `false`)。旧键现在**完全不读**——老机器配置文件里那个 `ignoreMouse: true` 不会再让大屏穿透。
- **为什么换键名而不是只改默认值**:旧版默认就是穿透,而且旧版模板已经把 `ignoreMouse: true` 写进了现场机器的配置文件(`%LOCALAPPDATA%\ScoreboardConsole\console\config.json`),而 Console 的规矩是**已存在的用户配置绝不覆盖**——光改默认值救不了老机器,配置文件里那个显式 `true` 会一直压着默认值;也没法区分「操作员刻意要穿透」和「旧模板留下的 true」。换个新键名,旧文件里的旧键自然失效,现场不动配置文件就回到「不穿透」。
- **升级后第一次启动**,如果配置文件里还留着旧键,Console 会在 `logs\console.log` 里记一条**中文警告(只记一次,不随大屏重排重复刷)**,原文是:
> 警告:配置项 display.ignoreMouse 已废弃、不再生效:本机配置文件里写的 ignoreMouse: true 会被忽略。大屏窗口现在默认「不穿透」,点击不会再漏到大屏后面的窗口或桌面;要让大屏恢复点击穿透,请改用 display.allowClickThrough: true
(`ignoreMouse` 后面跟的值会原样照抄你配置文件里写的内容。)**旧键的值一律忽略,也不会被自动改写进你的配置文件。**
- **确实需要穿透**(把大屏当纯展示层、同一块屏上还要操作别的窗口)才显式写 `display.allowClickThrough: true`,代价是:点击会漏到大屏后面的窗口 / 桌面(误触会点到别的东西),大屏也点不活。
### ② kiosk 窗口现在压得住任务栏
- **以前**:先 `setKiosk(true)` 再 `setBounds(...)`,在 Windows 上会把窗口从真正的全屏态拽回「恰好铺满该屏的无边框窗口」——它不是置顶层,**任务栏与任何拖到这块屏上的窗口都能盖在它上面**;再叠加上一条的穿透,大屏被盖住后还点不活。
- **现在**:多屏 kiosk 窗口固定置顶在 **`screen-saver` 层级**(Windows 上 `setAlwaysOnTop(true)` 的默认层级 `floating` 仍画在任务栏**下面**,只有 pop-up-menu 及以上才压得住任务栏)。效果:**任务栏盖不住它,别的窗口拖到这块屏上也盖不住它,被盖住后点一下就能回到最前**。
- 调用顺序固定为「**先 `setBounds` 定位 → 再 `setKiosk(true)` → 最后 `setAlwaysOnTop(true, 'screen-saver')`**」,并加了「已经是对的状态就直接返回」的守卫,避免重排时反复进出 kiosk 导致大屏白闪。窗口化(单屏调试态)会把置顶摘掉。
### ③ 大屏控制页新增「局域网大屏」卡片
- 卡片列出主机在局域网里**可用的地址**,拼成完整链接(形如 `http://192.168.1.5:19540/display`):链接可点开,旁边有**复制**按钮(复制成功短暂显示「已复制」),**第一个地址还带二维码**,手机扫码即可打开;
- 地址与端口由服务端 `GET /api/system/network` 下发(真实监听端口与路径,不是写死的),按现场最可能用到的顺序排(`192.168.*` → `10.*` → `172.16–31.*` → 其它);
- 没有可用地址时显示「未检测到局域网地址(可能是没插网线 / 只有回环)。」——这不是故障,表示主机当前没有别的设备能连上的地址;
- 这张卡片**需要录入员(recorder)及以上**登录才看得到,只读访客会被提前拦住。
### ④ 没有数据迁移
- 本版**没有任何数据库迁移**,也没有设置项迁移;旧赛事项目包照常导入、切换、导出,已有榜单数字与升级前一致。
- 配置文件里那份旧键我们**不会**替你删改(现场那份原样留着,人自己决定),它只是不再生效。
### ⑤ 升级后 3 分钟核对
1. 启动 Console,打开 `logs\console.log`:确认没有意外的报错;
2. 看多屏大屏:窗口应当压住任务栏、点一下能回到最前,且**点击不会再点到桌面或后面的窗口**(即不穿透);
3. 若有老机器配置文件里写着 `ignoreMouse: true`,日志里应能看到上面那条「已废弃」警告——这是正常的,行为已是「不穿透」;
4. (可选)确实要穿透的机器:在 `%LOCALAPPDATA%\ScoreboardConsole\console\config.json` 里写 `"allowClickThrough": true`,重启 Console;
5. 用录入员及以上账号进「大屏控制」页,确认右侧有「局域网大屏」卡片,点「复制」能复制链接、手机能扫第一个地址的二维码打开大屏;
6. 用《上线检查清单》过一遍。
### ⑥ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 多屏 kiosk 的鼠标 / 置顶行为 | 《部署指南》6.2 配置表、7.4 |
| 「局域网大屏」卡片怎么用 | 《使用手册》第 9 节 |
| 接口与字段 | `devdocs/05` 大屏控制与运维 |
---
## dev_0.3.0
### 一句话
**旧版赛事项目包仍可导入**:本版只有一条迁移(**0021**),只给破纪录登记表加一列可空的「理由」。升级后榜单数字应当与升级前一致;新增四个现场入口(作废 / 重新录入成绩、作废已批准的破纪录、录入历史纪录、仪表盘四张新卡片)。
### ① 迁移只有一条(0021),而且只是「加一列」
- 迁移 **`0021_record_breaks_note.sql`**:给 `record_breaks` 表加一列 **`note`**(TEXT,**可空**,默认 NULL)——记「这条破纪录登记为什么被驳回 / 作废」。
- 它**只加列**:不重建表、不改任何既有数据行;老登记的 `note` 保持 `NULL`(「本来就没有理由」是事实,不会被写成一条假理由)。
- ✅ **旧版赛事项目包仍可导入**。导入一个不含 `note` 列的旧包后,它**成为当前赛事时(切换过去、服务端启动打开赛事库时)会自动补跑迁移**,**不需要任何手工处理**;旧的破纪录登记、纪录库、成绩与积分一行不丢。
- 备份同理:升级前的备份文件(旧 `event.db`)照常能恢复,恢复后再打开时也会自动补迁移。
### ② 四个新入口
| 入口 | 位置 | 做什么 |
| --- | --- | --- |
| **作废本条 / 重新录入** | 成绩录入页每行的「操作」列 | 手滑填错了不必整组撤销发布:已发布的行仍然是锁的,但可以点「作废本条」(**必填原因**)让这一条退出名次与积分;已作废的行显示红徽标「已作废」,点「重新录入」(必填原因)退回草稿,改完再发布本组。两者都**立即重算并推大屏**,不受「分批发布时实时更新榜单」这个延迟结算开关影响 |
| **已批准的破纪录 / 作废** | 破纪录页新增的「已批准的破纪录」卡片 | 批错的破纪录可以撤回:点「作废」(**必填原因**)→ 撤回该笔加分、重算推榜,并把该级别的当前纪录**恢复为上一条** |
| **新增历史纪录 / 删除** | 破纪录页「纪录库」卡片 | 赛前手工登记历史纪录(小项 / 级别 / 成绩值或文本 / 保持人 / 保持单位 / 创造日期);每行可「删除」,删的是当前纪录时会恢复上一条。**「来源」列现在真的会显示「历史」或「本届」**(以前没有历史录入入口,实际只会是「本届」) |
| **仪表盘四张新卡片** | 仪表盘 | 今天的小项(按排期 + 进度)、草稿待发布、待确认破纪录、大屏在线数;旧卡片保留 |
### ③ 「拒绝」从内存改持久带来的行为变化
- 以前点「拒绝」只在**本机浏览器里**把这条疑似破纪录藏起来,刷新就回来。现在改成**先登记再驳回**:服务端会登记一条破纪录记录并立即驳回(理由写「现场判定不是破纪录」),**刷新后不再出现**;谁在什么时候否掉了哪一条,也能在审计日志里查到。
- ⚠️ **升级前在本机「拒绝」过的疑似破纪录,升级后刷新会重新出现一次**——因为那次拒绝从来没有写到服务端。重新点一次「拒绝」即可(这次会落到服务端)。
- 只有真的写不进去时(示例数据模式 / 未连接服务端)才退回本机隐藏,提示会如实说明「刷新页面后该条会重新出现」。
### ④ 升级后 5 分钟核对
1. 打开一次「成绩录入」页,确认表格最后一列是「操作」;
2. 打开「破纪录」页,确认多出「已批准的破纪录」卡片,且「纪录库」卡片有「新增历史纪录」按钮与每行的「删除」;
3. 打开仪表盘,确认多出今天的小项 / 草稿待发布 / 待确认破纪录 / 大屏在线数四张卡片;
4. 旧赛事包核对:导入一个**升级前导出的赛事项目包**并切换为当前赛事,确认能正常打开、破纪录与纪录库数据都在(迁移 0021 会自动补跑);
5. 用《上线检查清单》过一遍。
### ⑤ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 手滑填错一条成绩怎么改 | 《使用手册》4.10;《常见问题》Q98 |
| 破纪录批错了 / 怎么拒绝 | 《使用手册》6.1;《常见问题》Q99 |
| 历史纪录怎么录 | 《使用手册》6.2;《常见问题》Q100 |
| 仪表盘四张卡片 | 《使用手册》第 3 节;开发侧 `devdocs/07` §7 |
---
## dev_0.2.0
### 一句话
**没有需要迁移的数据**:这一版是 100 多项缺陷与体验修复的合集(含一次改名、几处口径收紧)。升级后榜单数字应当与升级前一致。
### ① 三件升级前必须知道的事
**1)「团体奖牌榜」改名为「单位奖牌榜」**,导出件也跟着改了名:
- 界面上:大屏控制页的切页下拉、大屏奖牌榜页标题、管理端「积分榜」页的页签,都叫 **单位奖牌榜**;
- 导出件:下载文件名由 `团体奖牌明细.xlsx` 改为 **`单位奖牌明细.xlsx`**,Excel 里的工作表名同步改为 **`单位奖牌榜`** / **`单位奖牌明细`**。
→ **如果你把旧文件名写进了对账脚本、打印模板或交接文档,记得改**。
- **旧名仍然可用**:老链接(`?page=团体奖牌榜`)与老配置(`display.pages` 里写 `medals` 或旧名)照常生效,不用改。
**2)审计日志的动作前缀统一了**(`entries.import` → `entry.import`,`entries.import.resolve` → `entry.import.resolve`):
- **升级前落库的历史记录不会跟着改**——它们仍写着旧前缀。所以**查历史时请用子串 `import` 筛**(新旧都能筛到);用 `entry.` 只能筛到升级之后的记录。审计是只追加的,我们不会改写历史。
**3)密码长度改成按「字符」数**(原来是按字节):
- 下限仍是 8 **位**,但现在按字符算:**7 个汉字(原来 21 字节能过)现在会被拒**;`3 个汉字 + 5 个字母`(8 字符)正常通过。
- 上限仍是 72 字节(bcrypt 的边界),文案里会分别写清「位」与「字节」。
### ② 现场会看到的变化
- **导出件里的「未结算」**:开了延迟结算时,「已发布但那一组还没结算」的行,导出的「分项成绩表 / 全部 / 全部(单页)」现在写 **名次 `—` / 积分 `—` / 状态 `未结算`**(成绩照常打印)。原来这三列是「空名次 + 0 分 + 有效」,读起来像「算出来就是 0 分」。
- **基础数据包导入遇到「已彻底删除的单位」**:那一行**整行跳过**并在导入结果里说明原因(不再更新它的字段、也不会把它翻出来);要让单位回来,先到「基础数据 → 参赛单位」勾选「显示已彻底删除的单位」再点「恢复」。
- **报名名单导入换了单位会明确提示**:「第 N 行:某某 换了单位,原单位 A 在本组别的报名已移出,已改在单位 B 加入」——数据行为没变(一直是这样),现在会说出来了。
- **「从已有名单勾选报名」有冲突时不再报「已加入」**:会明确提示「有 N 行没有加入,需要逐条确认」,并在页内列出是哪几行。
- **导出核对更省事**:单位积分明细的「成绩」列与分项成绩表、个人明细**逐字一致**(原来少个单位,如 `11.58` vs `11.58 秒`);空单位的「单位积分明细」也会打印末行「合计」。
- **收不到回执的危险操作不再误导**:一键恢复请求超时/断连时,提示由「恢复未执行」改为「**没收到回执,恢复可能已经生效**」,并单独放宽了超时时间;两个管理员同时点恢复时,第二个会拿到「正在恢复,请稍后」。
### ③ 部署相关的两处
- **赛事包上传上限是 512MB**(名单 CSV 与基础数据包那一档仍是 32MB,赛事包刻意放宽——它要装字体、背景图、各单位 logo;字体单个就允许 20MB)。超限会提示「解析上传失败」。
- **反向代理(nginx 等)**:若以前遇到「登录一定 403」,本版起错误提示会指明原因与修法;正解是让代理透传原 Host(`proxy_set_header Host $host;`),或配置可信代理并把原 Host 透传在 `X-Forwarded-Host` 上。**不要**为了绕过它把校验放宽——那等于开登录 CSRF 的口子。
- 顺带:**丢了 CSRF Cookie 不再等于「能看不能写」**——管理面板会自动补发,症状消失后无需再刷新页面。
### ④ 升级后 5 分钟核对
1. 打开服务管理窗口的运行日志:**这一次升级没有那条「通道名次自愈」记录是正常的**(本版没有新迁移,不需要重算);若看到它,说明你从上上版直接跳过来,等它跑完即可;
2. 刷新积分大屏与管理面板:**单位总分榜、单位奖牌榜、个人积分榜的数字与升级前一致**;
3. 随便挑一个小项导出一次「分项成绩表」,确认表头与列都对;
4. 用《上线检查清单》过一遍。
### ⑤ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 榜名/导出文件名的变化 | 本文件 ①;《使用手册》8.2、11.1 |
| 导出里三种「还没出结果」的行 | 《使用手册》11.1;《常见问题》Q90 |
| 基础数据包遇到已删除的单位 | 《使用手册》11.4;《常见问题》Q88 |
| 恢复与安全副本保留份数 | 《部署指南》8.3;《常见问题》Q40 |
| 反代/登录 403 | 《部署指南》7.x 与应用内提示 |
---
## dev_0.1.8
### 一句话
修掉了一个**会多算单位总分**的计分缺陷;奖牌改成可以按小项、按通道开关;报名名单认人改成**以证件号为准**。
### ① 升级后必须补的一件事(本版唯一需要人工补录的地方)
**「团体引体向上」这类「个人+团体」项目的团体名次,要到成绩录入页补录一遍。**
原因是旧版**没有录团体名次的地方**:这类项目的团体分是拿**个人名次顶替**着算出来的(所以会算多)。本版改成从「团体名次」区块读,**没录就是 0 分**。
- 补录位置:**成绩录入**页 → 选中该小项与组别 → 表格下方 **「团体名次」** 卡片 → 按单位填名次 → **保存团体名次**(留空 = 清空该单位;名次允许并列;个人名次不受影响)。
- 如果某单位不参加该项,留空即可。
- **不想手填的替代方案**:把该小项的团体通道从「手动名次」改成「按队内平均」(**基础数据 → 计分通道**),系统会按每队成员成绩自动算团体名次。
**两种口径二选一,由赛会规则决定**——「团体引体向上」这条现在还挂着旧模板(手动名次),要按领导定的口径确认一次。
### ② 分数会变,这是对的(不是坏了)
- **单位总分**:旧版在「个人+团体」项目上会把**个人**名次当成**团体**名次、按团体表再发一次分。实测某队在成绩没改的情况下,第一次发布 45 分、再点一次发布变 85 分。
本版已修正:**同一份数据无论发布几次,结果完全一致**。
- 所以升级后如果你之前录过并发布过这类项目,**单位总分会出现变化**(变小是正常的,那是修正后的值)。按 ① 把团体名次补录完,再核对一次即可。
- **个人积分不受影响**(个人通道的算法没变);奖牌数可能变化(见 ③)。
### ③ 现场会看到的新东西
**奖牌可以开关了**(三层,从粗到细):
1. 小项级:**基础数据 → 大项 / 小项 → 编辑小项**,「选项」里有 **「该项启用奖牌结算」**(**默认勾上**)。取消 = 这个小项只出积分、一块牌都不发;
2. 通道级:**基础数据 → 计分通道**,表里多了 **「奖牌通道」**列,两个开关——**「发奖牌」**(这条通道的名次发不发牌)与 **「个人奖牌并入单位金牌榜」**(个人项目拿的牌算不算进单位金牌总数);
3. 名次仍然只发给**前三名**(并列口径不变)。
> ⚠️ 迁移默认把三个开关都设成**开**,也就是和升级前一致,不会让既有小项突然掉牌。要改哪个小项,按赛事规则逐个去关。
**成绩录入页多了「团体名次」卡片**——只在配了「团体名次 = 手动名次」通道的小项上出现;纯团体项目(篮球等)和「按队内平均」的项目不会出现,那是正常的。
**报名名单认人改成以证件号为准**:匹配顺序 **证件号 → 号码布 → 姓名 + 单位**。同一份表里同一证件号出现多次 = 同一个人报多个组别(会给提示,不再报错);只有「同证件号 + 同一个小项同一个组别」才会进冲突面板问你保留哪一条。
**下面这些动作现在会立刻重算并刷新大屏,即使管理员开了「延迟结算」**:
撤销发布 / 作废单条成绩 / 批量退赛·恢复·移出报名 / **报名名单导入改了状态**(例如把某人改成退赛)。
「延迟结算」只影响**发布成绩**这一件事(不因一批成绩跳动榜单),不会让退赛、作废这类「谁还算数」的改动被拖着。
### ④ 其它修复
- 管理员**撤销会话 / 停用账号 / 改密码**后,那台机器上还开着的实时连接会立刻断开,不再继续收数据;
- 访客(guest)身份收不到系统配置类的实时推送;
- 导出一份不存在的小项 / 单位、手填名次填 0 或负数、性别或年龄填了非法值 → 现在是**明确的错误提示**,不再是「服务端异常」;
- **示例数据模式**(后端连不上时的演示模式)下,批量停用 / 批量或单条彻底删除会被拦下并提示,不会假成功;
- **服务管理窗口**:显示器短暂只剩一块(拔线 / 电视待机)之后,再把扩展屏插回来会**自动恢复全屏**,不用重启程序;
- **自定义大屏模板**:嵌套过深或体积过大的模板会给出明确提示,不会让大屏白屏。
### ⑤ 升级后 5 分钟核对
1. 打开服务管理窗口的**运行日志**,看有没有那两行自愈记录(`检测到通道名次为空但已有已发布成绩…` / `一次性全量重算完成…`),**这是正常的**;
2. 刷新积分大屏 / 管理面板:**奖牌榜不是全 0**;
3. **单位总分与升级前一致**(除了 ② 说的「个人+团体」项目);
4. 打开一次成绩录入页,确认该录团体名次的小项上有**「团体名次」**卡片;
5. 打开一次**基础数据 → 大项 / 小项**,确认「该项启用奖牌结算」是按赛事规则勾的;
6. 用《上线检查清单》从头过一遍。
### ⑥ 详细说明在哪
| 想了解 | 看 |
| --- | --- |
| 奖牌开关怎么用 / 什么时候关 | 《使用手册》10.2(小项)、10.4(计分通道) |
| 奖牌榜没牌 / 金牌数不对怎么查 | 《常见问题》Q94 |
| 团体名次怎么录 / 为什么没有那一块 | 《使用手册》4.9、《常见问题》Q95 |
| 升级后奖牌榜是空的 | 《部署指南》3.4、《常见问题》Q96 |
| 名单导入怎么认人、怎么填 | 《报名名单导入规范》 |
| 延迟结算下哪些动作会立刻生效 | 《使用手册》4.8、《常见问题》Q91 |
| 历史成绩版本重放与团体名次 | 《常见问题》Q97(已知限制) |
---
## dev_0.1.7 及更早
本文件从 **dev_0.1.8** 起维护;更早版本的改动不再逐版补记。若需要,可在《使用手册》与当时的发布记录里查。