fix: 修复保存满分后清登录;配置双写;设置显示账号/密码;抽取比例改按人数加权

- 修复:仅当连接信息(base_url/api_prefix/username/password/verify_ssl)真变化时才清 token + 重连;
  保存满分/布局等不应触发登录卷王(bug:之前一律 unlink token.json,导致保存后所有用户都被踢)
- 修复:默认满分不再误写当前卷;改为显式 apply_pid + apply_score 才会改 full_scores
- 配置双写(DB+文件):避免 DB 不可用时静默回退到出厂默认 admin/123456;
  ny_save_config 同时写文件+DB;ny_load_config 在 DB 可用但无记录时从文件自动迁回
- 设置弹窗新增:登录账号可编辑、密码 type=password 不回显、留空表示不修改;
  保存后根据 ping 自动给出成功/失败反馈(错误密码不再静默)
- 抽取比例改为按组人数加权(默认)+ 各组均分 + 自定义比例 + 不分组,最大余额法守恒分配
- 抽出比例 UI:删除 5:5 复选框、改 #balanceMode 下拉 + 按组渲染比例输入框
- Db::lastError() 暴露 DB 错误用于诊断(config_get 返回 db_driver / db_connected / db_error)
- e2e:test-draw-alloc 18→29 测试用例(加权/自定义/守恒 fuzz);
  新增 test-config-save e2e 27 用例全过(连接信息变化检测 + 自动 ping + 密码不回显 + apply_pid)
- mock-server:账户校验从 'admin' 改为 'mock' 接受 e2e 账号
This commit is contained in:
2026-09-09 13:06:47 +08:00
parent 6772aeae11
commit 6cd17b9cb2
10 changed files with 765 additions and 120 deletions
+9 -1
View File
@@ -77,12 +77,20 @@ $rosterCount = Roster::count();
</div>
</div>
<div class="field btns">
<label class="chk" title="有多个组别时按组别均衡抽取(如 A:B = 5:5),无组别时按班级均衡"><input type="checkbox" id="balanceChk" checked> 分组均衡</label>
<label class="chk" title="决定有分组时如何分配名额"><span style="color:var(--text-2);font-size:12px">分组比例</span>
<select id="balanceMode" style="height:30px;border-radius:8px;border:1px solid var(--border);padding:0 8px;background:#fff;margin-left:4px">
<option value="weight">按人数加权(默认)</option>
<option value="even">各组均分(5:5)</option>
<option value="custom">自定义比例</option>
<option value="off">不分组(整池随机)</option>
</select>
</label>
<label class="chk" title="再次抽取时不与本次会话已抽中的人重复;「重置结果」后清零"><input type="checkbox" id="excludePicked" checked> 排除已抽中</label>
<button class="btn" id="btnExclude" title="勾选不参与抽取的人">🚫 排除管理</button>
<button class="btn btn-primary" id="btnDraw">🎲 开始抽取</button>
<button class="btn" id="btnReset" title="清空本次结果与累计已抽取(历史记录保留)">重置结果</button>
</div>
<div class="field full hidden" id="ratioBox" style="grid-column:1/-1"></div>
</section>
<div id="alert" class="alert hidden"></div>