AskaEth
|
45edfbeb71
|
fix: P0 计分根因 + 奖牌结算/奖牌通道 + 身份口径改回证件号 + 19 条 P1(dev_0.1.8)
## P0:双通道项目重复发布跳分(45→85→收敛)
results.rank 一列同时充当「个人名次输出」「手工名次输入」「榜单数据源」,
团体手工通道把个人名次当团体名次又按团体表发一遍分。
- 新增 channel_ranks(每个计分通道各自持久化名次,发布时全量重建)
与 channel_manual_ranks(按单位录入的团体手工名次,故意不挂通道外键,
避免规则包重导时级联删掉录入数据)
- results.rank 语义收窄为「只表示个人名次」,只由 scope=athlete 通道写回;
团体手工名次的来源改为 TeamRanks 优先、团体报名项兜底,
个人报名项的名次绝不进团体通道
- writeLedgerTx 内重建 channel_ranks;buildRanked / export.loadRanked /
realtime.loadRanked 三份同源 SQL 一起改读新表
- 回归:发布两次流水与名次逐字节一致(domain + httpapi 各一条,含变异验证)
## 奖牌结算开关与奖牌通道(P1(5),新功能)
- events.award_medals + 通道 count_to_medals / count_to_team_medals
(迁移默认 1,升级不改变既有榜单)
- 单位榜入选 = 小项结算奖牌 && 通道发奖牌 && 前三名 &&(团体项目 || 个人奖牌并入单位)
- 个人榜只取个人通道;金牌榜与积分榜同源同数据、按通道过滤独立计算
- 前端:小项「该项启用奖牌结算」、通道「发奖牌/个人奖牌并入单位金牌榜」、
录入页新增「团体名次」区块(含新接口 GET/PUT .../team-ranks)
## 身份口径改回证件号(P1(15),推翻第三十六轮)
- 匹配顺序恢复 证件号 → 号码布 → 姓名+单位;新增 athleteByIDCard
- 同表重复证件号 = 同一人报多个组别(notices 提示,不报错);
只有「同证件号 + 同小项同组别」才是真重复(conflicts[].duplicate)
- 补齐后端缺失但前端/文档已在依赖的三处契约
## 其余 P1
- 安全:会话撤销断开 ws、实时层按角色裁剪、整库快照限流(含 hello 路径,
被限流的请求挂起并由心跳兜底补发,不丢刷新)
- 错误分级:手填名次非正数 / gender / 组别年龄 / 导出不存在的小项与单位 /
积分模板 awardRanks → 400 或 404,不再 500
- 算分一致性:破纪录加分看得失状态;批量退赛·恢复·移出、作废成绩、
报名导入改状态一律无条件重算推榜(不受延迟结算约束,与实时模式逐字节等价)
- 配置改动触发重算:通道 / 小项参数 / 积分模板
- 升级自愈:channel_ranks 空而已有已发布成绩时,启动时一次性全量重算
(服务对外监听之前完成,避免升级后奖牌榜全 0)
- 示例数据模式下破坏性操作被阻断,不再假成功
- Console:修复「只剩一块屏」后屏幕监听器被永久摘掉
- 大屏模板引擎:嵌套深度与体积安全阀,任何输入都不抛异常
## 文档
- docs/:使用手册(奖牌开关、奖牌通道、团体名次、枚举列留空口径)、
常见问题(奖牌排查、升级后奖牌榜为 0、历史重放已知限制)、
部署指南、上线检查清单
- 版本 dev_0.1.7 → dev_0.1.8(console/package.json 同步)
自检:服务端 13 包 ×2 全绿;前端 vue-tsc + 449 例 ×2 + pnpm build;
Console 35 例 + 语法检查。
|
2026-09-11 21:47:07 +08:00 |
|
AskaEth
|
631c8dd8e1
|
feat: 8 条功能 + 身份口径改为号码布为准(含批量退赛/移出、基础数据导入导出、登录有效期)
用户给出的 8 条功能 + 一条口径变更,全部带真机/真浏览器验证:
① 录入页姓名后直接显示号码布(原「号码」列保留)
② 录入页批次进度条:`第 2 批 已录 5/8 · 3 条有错`,录完变绿;已录判据复用
entryGuards.judgeEntryRow(与发布防呆同源,不会出现"进度条说录完、发布说缺 8 条")
⑦ 小项列表双击 → 跳到该小项的组别(编辑/删除按钮不误触)
③ 赛事开关 `standings.live_on_publish`(默认开=保持原行为):关闭后分批发布只上屏成绩,
名次/流水/榜单一起等该组录完再结算 —— 代理实读代码后纠正了我的前提:
奖牌榜读的是 results.rank(buildRanked)而非流水汇总,只延迟流水会出现
「有金牌但 0 分」。另提供 POST /api/results/settle 人工兜底 + GET /api/results/settlement 未结算清单
④ 组别「就地更新」PUT /api/events/{id}/categories/{categoryId}:有报名数据时也能改名/改年龄/
改性别/改权重(旧路径是整表替换,一改就被 409 拦下);改名不动报名/成绩/流水(真机逐字节比对);
权重真的变了才触发该组重算(容差 1e-9)
④辅 运动员列表多选+全选,POST /api/athletes/batch 一次请求完成整批停用/彻底删除(逐条回报,
dryRun 预览走同一段 SQL 但不提交、不写审计)——这才是绕开限流的正解
新增 报名项批量:POST /api/entries/batch(scratch/restore/remove)。退赛只动 registered、
跳过 dq/dns 并说明原因;移出是物理删除,必须显式清 score_ledger(该表 entry_id 是
ON DELETE SET NULL,不显式删会留下"人没了分还在"的孤儿流水,验证基线 orphan=0);
退赛同时清 results.rank(否则出现"0 分还挂着金牌")
⑤ GET /api/export/entries.xlsx 报名名单导出(13 列含批次,状态中文与录入页逐字一致)
⑥ 基础数据包:GET /api/export/basedata.xlsx + POST /api/export/basedata/import
(四张表、表头只有一份定义、表头行自动定位、列缺席≠写空、按编码 upsert、绝不删除、
行级中文错误带真实 Excel 行号、枚举走既有校验、新增单位一定是启用)
⑧ 登录有效期面板可配:系统设置新页签,1–720 小时,默认 12h → 24h;滑动续期 +
Cookie 同步刷新(只服务端续期是假的,浏览器侧的 Max-Age 到点仍会丢 Cookie)
身份口径:号码布为唯一标识、证件号退出匹配(号码布不同=同名的两个人,新建并单独报告);
同一份表重复证件号不再报错;两个不同的人共用证件号仍报行级错误(唯一索引保留)
其它对齐:分项成绩表草稿行不再消失(成绩列「分数未录入」、名次/积分 `—`,没成绩的行同占位);
大屏实时赛况单栏铺满(留白 370→78px)、字号上限 56→96px、≥7 行自动拆左右两块;
删除报名项/退赛/权重等改动都会重算并推榜单。
测试:服务端 13 包(连跑两遍)、前端 335 例、vue-tsc + build、Console 18 文件 —— 全绿。
文档:README、docs/ 四份(含 debug/使用说明 副本)、devdocs 05/08/04/13。
版本号 dev_0.1.5 → dev_0.1.6。
|
2026-09-11 17:14:02 +08:00 |
|
AskaEth
|
592e47aef7
|
feat: 小项类型加「个人+团体」+ 报名导入拆团体/个人两部分
小项类型:
- 迁移 0016 给 events.event_type 的 CHECK 增加 individual_team。SQLite 改不了 CHECK,
而 events 被多张子表引用(含 ON DELETE CASCADE/SET NULL),事务内 DROP 重建会级联删数据,
故用 writable_schema 就地改写建表语句文本(只追加一个枚举值,'match' 在建表语句里仅出现一次,
无副作用),零数据搬动
- domain 加 EventTypeIndividualTeam;管理端下拉加「个人+团体」并提示会同时走两条通道;
guards/rules 的 eventType 白名单同步(否则会被降级成 individual)
- 注意:该类型目前只解决「能被选出来」,双计的算分语义(个人分不计入单位、团体按队内平均排名)
尚未实现,等口径确认后再动数据模型
报名导入:
- 卡片拆成「个人报名 / 团体报名」两个 tab,小项+组别提到 tab 之上共享
- 团体报名 = 从已配置单位清单里多选(默认全不勾,正好表达「部分单位不参加」),
可逐单位为登记队员(0 人即纯单位报名),一次提交为多个单位各建一条报名
- 已报名单位仍可覆盖名单,但会提示「提交将清空原名单」,且「全选」只勾启用中且未报名的单位
自检:服务端 12 包全绿;前端 vue-tsc + build、node --test 117/117;console 16 文件通过。
|
2026-09-10 11:19:31 +08:00 |
|
AskaEth
|
b5f40642ff
|
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。
|
2026-09-10 11:06:58 +08:00 |
|
AskaEth
|
6483acf9ad
|
feat: 人员年龄/号码布、图片上传、多显示器菜单、Console 两栏、exe 改名等一批改动
人员与报名:
- 人员唯一标识改为「证件号 或 号码布」二选一(0013/0014 迁移 + 导入匹配/回填/冲突)
- 人员新增「年龄」(0015 迁移):表单/列表/导入模板表头 11 列;冲突比较只在两边
都有值且不同时才算差异,旧数据不误报
- 冲突决策键改为「证件号优先 → 号码布」(前端原先只用 idCard,号码布行会互相覆盖)
- 报名选择器支持无证件号但有号码布的运动员
大屏与 Console:
- 大屏控制新增「显示器」菜单:可给指定显示器重建全屏窗口;单屏/主显示器强制窗口化,
限制在 Console 侧判定,不信任页面传参
- Console 服务管理窗口改左右两栏,运行日志独占右栏满高;窗口默认尺寸 1280×860
- 大屏右上角时钟精确到秒(同时把刷新间隔 15s 改为 1s)
图片与配置:
- 新增图片上传 POST /api/uploads(recorder+,png/jpg/webp/gif,≤5MiB)与匿名静态
访问 GET /uploads/{name}(防目录穿越);文件落赛事项目目录 uploads/,随赛事包
一起导出/导入
- 「赛事信息」新增赛事会标 / 大屏会标 / 大屏背景图三个上传项
- 参赛单位 logo 输入框右侧加上传按钮
关于页与构建:
- 构建时间打通:version.BuildTime + /api/system/about 返回 + build.bat 注入
(用 PowerShell 取时间戳,避免本地化 %DATE% 的非 ASCII 星期)
安装包:
- 主程序改名 Scoreboard_Console.exe(win.executableName;productName/显示名不变)
- 记住上次安装目录:自定义 NSIS 宏优先沿用自存路径(卸载不清除),并在 customInit
里补写回 electron-builder 的键,避免安装模式页把 $INSTDIR 打回默认
自检:服务端 12 包全绿;前端 vue-tsc + build 通过、node --test 58/58;console 12 文件通过。
|
2026-09-10 09:05:06 +08:00 |
|
AskaEth
|
24fff8dd08
|
feat: 人员唯一标识改为证件号 + 导入冲突逐条决策
需求:人员以证件号为准(避免重名);批量导入时若证件号已存在于本地人员数据,
先导入其余人员,再把重复的人单独列出,由用户逐条/多选决定保留已有数据还是新数据。
服务端:
- 迁移 0013:athletes 增加 id_card + 部分唯一索引(空值可共存)
- athleteDTO/CRUD 读写 idCard,重复返回 409
- 导入表头新增可选列 id_card/gender;有姓名的行必须给证件号
- 匹配顺序:证件号 → 姓名+单位(且库中证件号为空则回填)→ 新建人员
- 证件号命中但姓名/性别/单位不一致 → 冲突,本轮不落库(报名也不建)
- 新增 POST /api/entries/import/resolve(file + decisions),keep-existing/keep-new
- 10 个新测试覆盖匹配/回填/冲突/决策/唯一索引
前端:
- 运动员表单与列表新增「证件号」,搜索支持证件号
- 报名导入:新模板 10 列;导入后冲突面板(每行复选框 + existing↔incoming 差异高亮
+ 全选/清空 + 保留已有数据/使用新数据 批量与逐条),未决策项始终可见
- 勾选报名(EntryPicker)缺证件号的运动员禁止勾选并提示
- 新增 import-conflicts.test.ts 等,前端测试 39/39
文档:使用手册(运动员证件号、导入表头与冲突流程)、常见问题(证件号重复怎么办)。
注:报名行的 team_code 与人员绑定单位不一致会计入冲突,由用户决策,不会静默改单位。
|
2026-09-10 02:09:15 +08:00 |
|
AskaEth
|
c9d5b8ea89
|
feat(admin): 报名导入新增「从已有名单勾选报名」
原来只能上传 CSV/XLSX。现在同一个 tab 上方多一个选择器:选小项 → 组别 → 单位,
从该单位已有运动员里勾人,一键加入报名。
- 个人项目:勾 N 人 → N 条报名(一人一行)
- 团体/对抗项目:勾 N 人 → 1 条报名,姓名拼进 member_names(再次提交即更新队员名单)
- 已报名的人/单位会标「已报名」并禁止重复勾选
- 复用服务端唯一报名入口 POST /entries/import:勾选结果转成同格式 CSV 提交,
字段校验、按 (组别,单位,运动员) upsert、行级报错回显与表格导入完全一致
- 纯函数 entrySelection.ts(表头唯一来源 + RFC 4180 转义)+ 4 个单测;
表头定义从 ImportSection/mockBaseData 收敛到一处
- 号码布/种子仍走表格导入,界面与文档都写明
自检:vue-tsc + pnpm build 通过,前端 node --test 30/30。
|
2026-09-10 02:03:16 +08:00 |
|