feat: 赛事项目切换 + 记住上次打开的赛事(修「导入赛事包后没数据」)

根因:服务端启动只认 -project(缺省 default),Console 从不传;导入只是把包解到
event_projs/<名字>,没有任何切换入口 → 导入的赛事永远打不开,页面看到的是空的 default。

- 服务端 -project 缺省改为空串;解析顺序:显式 flag(并写回记忆)> <dataDir>/active_project.txt
  > default;backup/passwd 口径一致;启动日志打印生效项目与来源
- GET /api/projects(admin)列出各赛事(是否当前/是否有 event.db/大小/更新时间)
- POST /api/projects/activate(admin):校验后原子写记忆文件 → 审计 → 返回
  {ok,name,restarting:true} → 触发优雅关闭;Console 对非用户发起的退出会自动拉起,
  重启后即打开新赛事(无需改 Console)。本来就是当前赛事则 restarting:false 不重启
- 管理端「导出」页新增赛事项目列表与「切换为当前赛事」;导入成功后提示「已导入为 X,
  但还不是当前赛事」并给「立即切换并重启服务端」;切换后轮询 /api/health 等它回来再刷新
- 文档:使用手册(导入≠切换、生效四步)、常见问题(导入后看不到数据)

自检:服务端 12 包全绿;前端 vue-tsc + build、node --test 66/66;console 14 文件通过。
This commit is contained in:
AskaEth committed 2026-09-10 09:47:06 +08:00
1 parent 005b51b2ec
commit 2d207fd654
14 files changed
+1363 -24

No files matched your search

+6
View File
@@ -16,6 +16,8 @@ func runPasswd(args []string) error {
fs := flag.NewFlagSet("passwd", flag.ExitOnError)
configPath := fs.String("config", "config.yaml", "配置文件路径")
dataDir := fs.String("data", "", "数据目录;非空时覆盖配置文件里的 dataDir")
// passwd 不涉及赛事项目,参数只为与 server / backup 的命令行口径一致(解析顺序、校验规则相同)。
project := fs.String("project", "", "赛事项目名;留空则用记住的上一次赛事,再没有就用 default")
username := fs.String("user", "admin", "用户名")
newUsername := fs.String("new-username", "", "同时把用户名改成这个(留空则不改)")
password := fs.String("password", "", "新密码;留空则随机生成并打印")
@@ -30,6 +32,10 @@ func runPasswd(args []string) error {
// Console 拉起服务端时用 -data 把数据指到安装目录之外;这里必须用同一份,
// 否则改的是安装目录下那个空库里的账号。
cfg.ApplyDataDir(*dataDir)
// 解析一次只为与另外两个命令同名同规则;passwd 不需要赛事项目名,解析结果不参与后续逻辑。
if _, _, err := resolveProjectName(cfg.DataDir, *project); err != nil {
return err
}
st, err := store.Open(cfg.DataDir)
if err != nil {