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

+49
View File
@@ -801,3 +801,52 @@ Console 启动的多屏窗口默认每屏锁定一页。到「大屏控制」点
- **以前给某台设备切页后,列表底部会多出一个该设备的离线条目**(根因是翻页会补发一次握手,而浏览器没有稳定 id、服务端每次都新开一条实例),**现在已经修好**:浏览器本机记住自己的实例 id,服务端也做了兜底(同一条连接重复握手复用原实例),升级后不会再出现这种重复条目。
顺带一句:「局域网大屏」卡片里**每一条地址**都能出二维码了——第一条是常驻二维码,其余每条点「**二维码**」按钮临时出码(再点一次 / 按 Esc / 点空白处收起,同一时刻只留一个),不用为别的网段再手敲地址。详见《使用手册》第 9 节、《部署指南》7.5。
**Q108:大屏上那条「赛事日程」侧边栏怎么开?为什么有一块屏没有?**
到管理端「**大屏控制**」页**左列、公告卡下面**那张「**赛事日程**」卡片(**录入员及以上**就能改,与大屏公告同档):
- **先加日程**:点「新增一条」,填**时间**(自由文本,`08:30` / `上午` / `待定` 都行,最长 20 字)、**标题**(必填,最长 40 字)、**备注**(可留空,最长 60 字),最多 **40 条**;顺序用**上移 / 下移**调(只有这两个按钮,**不能拖拽**);
- **再开开关**:把「**侧边栏默认开启**」打开(开 = 所有大屏默认显示;关 = 都不显示),选**位置**(默认右侧)与**宽度**(15–40 %,默认 22 %),点「**保存**」;
- **保存后就生效,不用刷新大屏**:侧边栏是**叠在当前页之上**的,不改当前页、也不停轮播——所以那块屏本来在轮播就继续轮,锁着就继续锁着。
**某一块屏没有,多半是这三件事之一**:
1. 卡片下半部分「**每块屏单独设置**」里,那一块屏被设成了「**强制关**」——下拉切回「跟随总开关」再保存即可(切回「跟随总开关」会把那条设置**真的删掉**);
2. 那块屏**当时没连上**(大屏窗口被关掉过、或网络断过)——它重连 / 刷新后就会带上侧边栏(**配置存在赛事库里**,刷新、重启服务端、切赛事都不丢);还看不到就先点一次管理端的「强制刷新」让它重拉快照;
3. 「侧边栏默认开启」本来就是关的,而那块屏没有单独的「强制开」。
⚠️ 另外两个常见误会:**「当前项」看的是勾选、不是时间**——它就是**第一个没勾「已完成」的条目**;**顺序就是你排的顺序**,系统**不按时间自动排序**(手填的时间是自由文本,`上午` / `待定` 没法可靠解析,所以**不做、也不会做**)。详见《使用手册》9.2 / 17.6。
**Q109:侧边栏上为什么看不到全部日程?字会不会太小或太大?**
**先说结论:它不是「完整日程公示」,是「围绕现在进行到哪一项」的现场提示。**
- 条目**放不下**时,面板会**围绕当前项开窗**:当前项固定在显眼位置、前后各显示若干条,**已完成的与很靠后的条目会滚出视图**(被截住的方向上会写一句「上方 / 下方还有 N 项」)。要**完整公示**日程,请用轮播页或另行打印,别指望这条侧边栏;
- 想让它一次显示更多条:把**宽度**调大一点(最大 40 %),或**精简标题 / 备注**(长文本会占掉行数)——**最多 40 条**是硬上限,但放不下时**绝不会**把字缩到看不清。
**字号**:按文本长度与可用高度**自适应**,且有**硬上下限**——**宁可少显示几行,也不缩到看不清**;长标题最多 3 行、长备注最多 2 行,超出**省略**(按显示宽度算,中文与字母各自计宽);时间与备注比标题小一档(0.8 倍,同样有下限)。**1080p 与 4K 都按屏幕等比缩放**:4K 上字与面板**一起**放大,不会出现「4K 上字特别小」或「字特别大」。
**Q110:打开积分明细或推公告的时候,侧边栏不见了,是坏了吗?**
没坏,这是**故意的**:
- **明细覆盖层**开着期间(「推送到大屏」那块半屏明细);
- **全屏公告**正在播期间。
这两种情况下侧边栏会**自动收起**,**开关状态一个字节都不动**——明细关掉 / 公告退场后**它自己就回来了**,不用重新保存、也不用重新开开关。为什么要这样:这两件事都是「临时要在整块屏上讲另一件事」,被侧边栏压住会看不全。
**Q111:导出件上的名次和大屏上的名次为什么不一样?**
**先说结论:这是一条既有口径**(屏幕上和导出件上「名次取谁」的优先级**故意不同**),**正常赛程遇不到**;真遇到了,**按决赛发布一次**就能让两边一致。
| 看哪 | 名次取谁 |
| --- | --- |
| **屏幕**(大屏的分项成绩 / 实时赛况 / 底栏「最新完成项目」) | **成绩行上的名次优先**,为空时回落「通道名次」 |
| **导出件**(「导出 → 结果」的分项成绩表) | **「通道名次」优先**,为空时回落成绩行上的名次 |
**什么时候两边会分叉**:只有一种序列——**先「按决赛成绩发布」**(会重建通道名次),**之后又只发了一次「只更新名次」**(只写成绩行上的名次、**不重建通道名次**)。这时**导出件显示的是上一次计分的名次,屏幕显示的是新名次**。
**为什么这么设计**:屏幕侧必须「成绩行优先」——因为「只更新名次」的发布**只写成绩行上的名次、不重建通道名次**,一旦让通道名次优先,个人项目在那种发布之后会**显示上一次的旧名次**(见 Q105 与《升级说明》`dev_0.5.15` 的 ①/④)。导出侧则是**既有口径、有测试钉着**,本版按「兼容性最大化、不动已交付的东西」**原样保留**(见《升级说明》`dev_0.5.16` 的 ④)。
**遇到了怎么办**:对该小项 + 组别**再按「按决赛成绩发布」发布一次**即可(通道名次会按当前成绩重建,两边立刻一致),**不用改库、不用重导、也不用重算整场**。正常赛程(预赛只排名次 → 决赛才计分)两边名次本来就是同一个值,不会遇到。