完成批次 L-R:限流移除/白名单驱动/打印增强/全屏/表格对齐/主题变量/滚动闪烁修复

- 移除登录失败限流;fix-config.php 修正 e2e 串味的 127.0.0.1 连接

- 白名单驱动缺/待考名单(DataService + SurveyKingClient::getWhitelist,复用已取 survey)

- 打印成绩明细增强(范围/合格列默认关/去页脚);看板全屏占满;表格 vertical-align:middle;隐藏登陆账号;导入默认追加

- 主题变量收口(:root 新增 brand/primary-2/ok-2 等,替换硬编码渐变);删死规则

- 表头/数据列对齐修复(index 未匹配表、dashboard 成员榜、draw 结果/历史/排除表);看板占位卡片配色一致

- 修复滚动闪烁:row-drop-in 动画经 _dropAnim 门控,仅强制重绘时播放

- 测试:新增 test-whitelist.js;run-all.sh 全绿 132
This commit is contained in:
2026-09-13 10:25:48 +08:00
parent 804b04de60
commit 94ac381579
17 changed files with 856 additions and 301 deletions
+86 -22
View File
@@ -30,9 +30,15 @@ class DataService
return $out;
}
// children 在根或子节点
// 不同卷王版本题目可能挂在 children / questions / list 下,统一兼容
$self = $node;
$children = (isset($node['children']) && is_array($node['children'])) ? $node['children'] : null;
$children = null;
foreach (['children', 'questions', 'list', 'items'] as $ck) {
if (isset($node[$ck]) && is_array($node[$ck])) {
$children = $node[$ck];
break;
}
}
$id = isset($self['id']) ? (string)$self['id'] : '';
$title = isset($self['title']) ? self::cleanTitle((string)$self['title']) : '';
@@ -138,26 +144,15 @@ class DataService
}
}
$students = Roster::all();
// 免考:从名单中剔除,使其既不计入「总人数」也不计入「缺考」
// 基础名单(全名单):用于答卷匹配,不受白名单影响
$rosterAll = Roster::all();
if ($exempt !== []) {
$students = array_values(array_filter(
$students,
$rosterAll = array_values(array_filter(
$rosterAll,
static fn(array $s): bool => !isset($exempt[trim((string)($s['sid'] ?? ''))])
));
}
// 姓名 -> 学号 / 班级(规范化)
$studentInfo = [];
foreach ($students as $s) {
$k = ny_norm_name((string)$s['name']);
if ($k !== '') {
$studentInfo[$k] = [
'sid' => (string)$s['sid'],
'cls' => (string)($s['cls'] ?? ''),
'grp' => (string)($s['grp'] ?? ''),
];
}
}
// 注:$students(应考名单)与 $studentInfo 在解析出 $survey(含白名单)后统一构建,见下方
$project = $client->getProject($projectId);
$survey = $project['survey'] ?? null;
@@ -167,6 +162,73 @@ class DataService
$survey = $decoded;
}
}
// 卷内「答题白名单」:决定「应考名单」(缺/待考名单来源)。
// 未设置白名单时「按现在的方案显示」(使用全名单)。
$paperWhitelist = [];
try {
$paperWhitelist = $client->getWhitelist($projectId, $survey);
} catch (Throwable $e) {
$paperWhitelist = [];
}
$useWhitelist = ($paperWhitelist !== []);
$whitelistTotal = 0;
if ($useWhitelist) {
// 以学号 / 登陆账号 / 姓名(大小写不敏感)把白名单成员匹配回名单,
// 命中的取名单里的姓名/班级/组别;未命中的以白名单值作占位,避免漏统计。
$rosterMap = [];
foreach ($rosterAll as $s) {
$sid = strtolower(trim((string)($s['sid'] ?? '')));
$login = strtolower(trim((string)($s['login'] ?? '')));
$name = strtolower(trim((string)($s['name'] ?? '')));
if ($sid) { $rosterMap['sid:' . $sid] = $s; }
if ($login) { $rosterMap['login:' . $login] = $s; }
if ($name) { $rosterMap['name:' . $name] = $s; }
}
$students = [];
$seen = [];
foreach ($paperWhitelist as $v) {
$key = strtolower(trim((string)$v));
$m = $rosterMap['sid:' . $key] ?? $rosterMap['login:' . $key] ?? $rosterMap['name:' . $key] ?? null;
if ($m !== null) {
$sid = trim((string)($m['sid'] ?? ''));
if ($sid !== '' && isset($seen[$sid])) {
continue;
}
if ($sid !== '') { $seen[$sid] = true; }
$students[] = $m;
} else {
if (isset($seen['wk:' . $key])) {
continue;
}
$seen['wk:' . $key] = true;
$students[] = ['sid' => $v, 'name' => $v, 'cls' => '', 'grp' => '', 'login' => ''];
}
}
$whitelistTotal = count($students);
} else {
$students = $rosterAll;
}
// 姓名 -> 学号 / 班级(规范化):全名单 + 白名单独有成员都纳入,
// 保证所有提交(含白名单外提交)都能匹配到姓名。
$studentInfo = [];
$addInfo = static function (array $s) use (&$studentInfo): void {
$k = ny_norm_name((string)($s['name'] ?? ''));
if ($k !== '') {
$studentInfo[$k] = [
'sid' => (string)$s['sid'],
'cls' => (string)($s['cls'] ?? ''),
'grp' => (string)($s['grp'] ?? ''),
];
}
};
foreach ($rosterAll as $s) { $addInfo($s); }
if ($useWhitelist) {
foreach ($students as $s) { $addInfo($s); }
}
$questions = self::flattenQuestions($survey);
$keywords = (string)($cfg['name_keywords'] ?? '姓名');
@@ -218,7 +280,7 @@ class DataService
continue;
}
$v = ny_flatten_value($answerMap[$qid]);
if ($v !== '' && isset($nameMap[ny_norm_name($v)])) {
if ($v !== '' && isset($studentInfo[ny_norm_name($v)])) {
$name = $v;
break;
}
@@ -227,7 +289,7 @@ class DataService
if ($name === '' && $questions === []) {
foreach ($answerMap as $v) {
$s = ny_flatten_value($v);
if ($s !== '' && isset($nameMap[ny_norm_name($s)])) {
if ($s !== '' && isset($studentInfo[ny_norm_name($s)])) {
$name = $s;
break;
}
@@ -252,8 +314,8 @@ class DataService
$ts = ny_to_timestamp($a['updateAt'] ?? null);
}
// 考试时长
$ai = $a['metaInfo']['answerInfo'] ?? [];
// 考试时长(metaInfo 可能缺失,先整体判空避免每答案两条 Undefined key 警告)
$ai = ($a['metaInfo'] ?? [])['answerInfo'] ?? [];
$start = is_array($ai) ? (int)($ai['startTime'] ?? 0) : 0;
$end = is_array($ai) ? (int)($ai['endTime'] ?? 0) : 0;
$dur = null;
@@ -371,6 +433,8 @@ class DataService
'stats' => $stats,
'roster' => $students,
'roster_count' => count($students),
'whitelist_driven' => $useWhitelist,
'whitelist_total' => $whitelistTotal,
// 本卷独立配置(前端据此回显满分/合格率/免考);full_score 已按本卷解析
'project_cfg' => [
'full_score' => $pCfg['full_score'] ?? null,