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。
This commit is contained in:
AskaEth committed 2026-09-17 20:04:50 +08:00
1 parent 56ed90ff8b
commit 9c52dc4b38
33 files changed
+4736 -10

No files matched your search

+91
View File
@@ -6,6 +6,97 @@
---
## 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
### 一句话