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
@@ -118,6 +118,12 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<div class="mx-app admin-shell">
|
||||
<div
|
||||
v-if="sidebarOpen"
|
||||
class="sidebar-backdrop"
|
||||
aria-hidden="true"
|
||||
@click="sidebarOpen = false"
|
||||
/>
|
||||
<aside class="mx-sidebar" :class="{ open: sidebarOpen }">
|
||||
<div class="mx-logo brand">
|
||||
<img class="app-icon" src="/scoreboard.png" alt="" width="28" height="28" />
|
||||
@@ -157,7 +163,7 @@ onMounted(async () => {
|
||||
<MxIcon name="logs" :size="18" />
|
||||
</button>
|
||||
<div class="mx-breadcrumb">
|
||||
<span>管理面板</span>
|
||||
<span class="crumb-root">管理面板</span>
|
||||
<span class="mx-breadcrumb-sep">/</span>
|
||||
<span>{{ currentTitle }}</span>
|
||||
</div>
|
||||
@@ -237,9 +243,31 @@ onMounted(async () => {
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
/*
|
||||
* 桌面壳是 .mx-app(≥1024 为 row)的 flex 子项:vendor 给的是 width:100%,
|
||||
* 会按容器整宽撑开,再叠加 240px 侧边栏就把右侧内容顶到视口外;
|
||||
* .admin-shell 又是 overflow:hidden,多出来的部分直接被裁掉(1024 下表格右侧列、
|
||||
* 顶栏按钮全部消失)。flex:1 + min-width:0 让壳按剩余宽度收缩,
|
||||
* 宽表格的横向滚动条才能落在卡片内部而不是撑破整页。
|
||||
*/
|
||||
.mx-desktop-shell {
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.mx-desktop-main {
|
||||
min-width: 0;
|
||||
}
|
||||
.menu-btn {
|
||||
display: none;
|
||||
}
|
||||
.sidebar-backdrop {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 480;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
.brand {
|
||||
padding: 6px 10px 14px;
|
||||
}
|
||||
@@ -270,6 +298,18 @@ onMounted(async () => {
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
/*
|
||||
* 顶栏左侧:vendor 只有 .mx-topbar-left,没有 .topbar-left,
|
||||
* 不补这条的话菜单按钮与面包屑会各占一行堆成 58px 高,把 52px 顶栏顶穿
|
||||
* (按钮顶端被视口裁掉,手机上点不到)。
|
||||
*/
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
/* 窄屏下 vendor 不提供桌面顶栏样式,这里补一份,保证 1024 以下也能用。 */
|
||||
.topbar {
|
||||
display: flex;
|
||||
@@ -339,6 +379,17 @@ onMounted(async () => {
|
||||
/* vendor 的 layout.css 在窄屏隐藏桌面壳,管理端必须始终显示主内容区。 */
|
||||
.mx-desktop-shell {
|
||||
display: flex !important;
|
||||
/* vendor 只在 ≥1024 的规则里给了 flex-direction:column;
|
||||
窄屏下必须自己补一份,否则顶栏与主内容区并排成一行(row),
|
||||
主内容区被压成几十像素宽——窄屏"整页空白"就是这么来的。 */
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.mx-desktop-main {
|
||||
/* 窄屏收紧留白,把宽度让给内容 */
|
||||
padding: 14px 14px 28px;
|
||||
min-width: 0;
|
||||
}
|
||||
.menu-btn {
|
||||
display: inline-flex;
|
||||
@@ -347,15 +398,71 @@ onMounted(async () => {
|
||||
position: fixed;
|
||||
inset: 0 auto 0 0;
|
||||
z-index: 500;
|
||||
width: min(280px, 84vw);
|
||||
max-width: 100%;
|
||||
transform: translateX(-100%);
|
||||
transition: transform var(--mx-d2) var(--mx-ease);
|
||||
display: block;
|
||||
box-shadow: var(--mx-sh3);
|
||||
}
|
||||
.mx-sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.sidebar-backdrop {
|
||||
display: block;
|
||||
}
|
||||
.who-text {
|
||||
display: none;
|
||||
}
|
||||
/* 抽屉在平板宽度也要用,vendor 只在 ≤767 给了 44px,这里补到整个窄屏区间。 */
|
||||
.mx-nav-item {
|
||||
min-height: 44px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
/* 手机端顶栏只保留:菜单 / 当前页名 / 连接徽标 / 主题 / 刷新 / 头像 / 退出。
|
||||
版本号与面包屑前缀会把 390px 顶栏顶到屏幕外,这里逐项减负。 */
|
||||
.topbar {
|
||||
padding: 0 10px;
|
||||
gap: 8px;
|
||||
}
|
||||
.topbar-left {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.topbar-right {
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.version {
|
||||
display: none;
|
||||
}
|
||||
/* 只留当前页名:前缀"管理面板 /"占掉一半宽度,会把标题挤成省略号。 */
|
||||
.crumb-root,
|
||||
.mx-breadcrumb-sep {
|
||||
display: none;
|
||||
}
|
||||
.mx-breadcrumb {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.readonly-banner,
|
||||
.mock-banner {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.readonly-banner .mx-btn,
|
||||
.error-banner .mx-btn {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
/* 超窄屏再把头像让出来给当前页名,登录人身份在窄屏不是必须信息。 */
|
||||
.who {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user