feat: 公告持久化(底栏常驻)+ 团体可单独报名 + 榜单系数开关
公告: - 服务端把当前公告落库到 settings(display.announce_text / announce_seconds), 作为 display.announce 随 snapshot 与 /api/settings 下发(非 admin 快照也保留) - announce 指令除转发外写库;新增 announce_clear 清空并广播;两者写审计 - realtime 协议登记 announce_clear(补上,使清除能即时推到大屏) - 大屏把「全屏推送」与「底栏常驻」拆开:推送 seconds 秒后只退出全屏态,公告留在底栏, 直到被替换或清除;新增公告状态机(可测)与底栏取值优先级 推送中 > 服务端持久值 > 「暂无公告」;管理端加「清除公告」与当前公告状态 团体报名: - EntryPicker 团体/对抗模式允许不勾队员直接报名该单位(此前必须勾人,导致 「只想给单位报个团体项目」做不到、单位下没有运动员时完全报不了) - 核实录入页团体行:显示/填名次/保存/发布/算分闭环无缺陷(用真实服务端跑过), 抽出共享的 entryRowLabel 供两处复用 榜单口径: - 基础数据 → 积分模板页新增「榜单口径(全局)」卡片:individualToTeamWeight 数字输入(默认 1、0–100),说明「个人项目得分按该系数折算计入单位总分」, 保存走 PUT /api/settings;因该值在服务端是全局键、模板表里没有此字段, 故做成独立卡片并标注全局,不塞进模板表单 自检:服务端 11 包全绿;前端 vue-tsc + build、node --test 99/99。
This commit is contained in:
1 parent
cded54e440
commit
b5f40642ff
24 files changed
+1073
-67
No files matched your search
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 「从已有名单勾选报名」的纯函数部分,以及报名导入冲突的差异 / 决策计算。
|
||||
* 报名相关的纯函数:勾选转 CSV、报名行文案,以及报名导入冲突的差异 / 决策计算。
|
||||
*
|
||||
* 之所以复用导入通道的表头与 CSV 格式:服务端只认 /entries/import 这一个报名入口,
|
||||
* 勾选结果转成同格式 CSV 后走同一个接口,字段校验、按 (组别, 单位, 运动员) upsert
|
||||
@@ -39,6 +39,26 @@ export const MANDATORY_COLUMNS: readonly string[] = REQUIRED_COLUMNS.filter(
|
||||
|
||||
export type SelectionMode = 'individual' | 'team'
|
||||
|
||||
/**
|
||||
* 「加入报名」是否放行。
|
||||
*
|
||||
* 个人项目一人一条报名,必须勾人;团体 / 对抗项目一条报名只挂单位(服务端 athlete_id 为 NULL
|
||||
* 的报名合法),队员名单可以留空后补,所以 0 勾选也要放行。
|
||||
*/
|
||||
export function selectionAllowsSubmit(mode: SelectionMode, chosenCount: number): boolean {
|
||||
return mode === 'team' || chosenCount > 0
|
||||
}
|
||||
|
||||
/** 报名行的展示文案:运动员姓名 → 团体队员首人 → 单位简称 / 全称。 */
|
||||
export function entryRowLabel(row: {
|
||||
athleteName?: string | null
|
||||
memberNames?: string[] | null
|
||||
teamShort?: string | null
|
||||
teamName?: string | null
|
||||
}): string {
|
||||
return row.athleteName || row.memberNames?.[0] || row.teamShort || row.teamName || ''
|
||||
}
|
||||
|
||||
export interface SelectionInput {
|
||||
eventCode: string
|
||||
categoryName: string
|
||||
@@ -54,7 +74,7 @@ export interface SelectionInput {
|
||||
/**
|
||||
* 生成与导入表头一致的二维数组。
|
||||
*
|
||||
* 个人项目一人一行;团体 / 对抗项目整体一行,队员姓名进 member_names。
|
||||
* 个人项目一人一行;团体 / 对抗项目整体一行,队员姓名进 member_names(可以为空,即只报单位)。
|
||||
* 服务端按 (组别, 单位, 运动员) upsert,团体行运动员为空,因此同组别 + 单位只留一条报名。
|
||||
*/
|
||||
export function buildSelectionRows(input: SelectionInput): string[][] {
|
||||
|
||||
Reference in new issue
Block a user