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 配置的说明与实现对齐
This commit is contained in:
1 parent
4d56c73ae6
commit
2c9ff65fc4
42 files changed
+1271
-275
No files matched your search
@@ -8,11 +8,8 @@ import PageFrame from '../PageFrame.vue'
|
||||
const { athletes, settings } = useScoreboard()
|
||||
|
||||
const rows = computed(() => athletes.value)
|
||||
// 奖牌列由独立开关控制;旧配置没有 showIndividualMedals 时回退到「个人榜上屏」开关,避免升级后突然多出一列。
|
||||
const showMedals = computed(() => {
|
||||
const standings = settings.value?.standings
|
||||
return standings?.showIndividualMedals ?? standings?.showIndividualBoard ?? true
|
||||
})
|
||||
// 奖牌列由独立开关控制;缺字段时的回退已在 guards.parseSettings 里按「跟随个人榜上屏」处理。
|
||||
const showMedals = computed(() => settings.value?.standings.showIndividualMedals ?? true)
|
||||
|
||||
const rowFont = computed(() => fitRowFont(rows.value.length, 560))
|
||||
</script>
|
||||
|
||||
+13
-14
@@ -296,14 +296,10 @@ export function parseRecordEntry(source: unknown): RecordEntry {
|
||||
}
|
||||
|
||||
/**
|
||||
* 大屏侧扩展的赛事配置。
|
||||
*
|
||||
* `standings.showIndividualMedals` 还没进入 shared 契约,但大屏需要它独立控制奖牌列;
|
||||
* 声明成可选,旧配置(服务端只给 showIndividualBoard)仍可赋值,页面再回退。
|
||||
* 大屏侧赛事配置。字段与 shared 契约一致;
|
||||
* `showIndividualMedals` 缺失时由 parseSettings 回退到 `showIndividualBoard`(旧服务端只下发后者)。
|
||||
*/
|
||||
export type DisplaySettings = EventSettings & {
|
||||
standings: EventSettings['standings'] & { showIndividualMedals?: boolean }
|
||||
}
|
||||
export type DisplaySettings = EventSettings
|
||||
|
||||
/**
|
||||
* 嵌套路径取值。
|
||||
@@ -330,6 +326,10 @@ function settingValue(
|
||||
|
||||
export function parseSettings(source: unknown): DisplaySettings {
|
||||
const rec = record(source)
|
||||
const showIndividualBoard = bool(
|
||||
settingValue(rec, 'standings.showIndividualBoard', 'standings.show_individual_board'),
|
||||
true,
|
||||
)
|
||||
return {
|
||||
event: {
|
||||
name: str(settingValue(rec, 'event.name', 'event.name')),
|
||||
@@ -355,13 +355,12 @@ export function parseSettings(source: unknown): DisplaySettings {
|
||||
settingValue(rec, 'standings.rankingMode', 'standings.ranking_mode'),
|
||||
'points',
|
||||
),
|
||||
showIndividualBoard: bool(
|
||||
settingValue(rec, 'standings.showIndividualBoard', 'standings.show_individual_board'),
|
||||
true,
|
||||
),
|
||||
showIndividualMedals: boolOrUndefined(
|
||||
settingValue(rec, 'standings.showIndividualMedals', 'standings.show_individual_medals'),
|
||||
),
|
||||
showIndividualBoard,
|
||||
// 旧服务端只下发 showIndividualBoard,奖牌列跟随它,避免升级后突然多出一列。
|
||||
showIndividualMedals:
|
||||
boolOrUndefined(
|
||||
settingValue(rec, 'standings.showIndividualMedals', 'standings.show_individual_medals'),
|
||||
) ?? showIndividualBoard,
|
||||
},
|
||||
defaultProfileId: numOrNull(rec.defaultProfileId),
|
||||
requireApproval: bool(rec.requireApproval),
|
||||
|
||||
@@ -271,6 +271,7 @@ export function createMockSnapshot(): SnapshotMessage {
|
||||
individualToTeamWeight: 1,
|
||||
rankingMode: 'points',
|
||||
showIndividualBoard: true,
|
||||
showIndividualMedals: true,
|
||||
},
|
||||
defaultProfileId: null,
|
||||
requireApproval: false,
|
||||
|
||||
Reference in new issue
Block a user