diff --git a/api.php b/api.php index 351b230..92c922e 100644 --- a/api.php +++ b/api.php @@ -148,29 +148,56 @@ try { $rowsByCls[$k][] = $r; } + // 通用统计器:给定学生集合 + 答卷集合,算一组指标 + $statOf = static function (array $stu, array $rs, int $fs): array { + $total = count($stu); + $submitted = count($rs); + $scores = array_values(array_filter(array_map(static fn($r) => $r['score'] ?? null, $rs), static fn($v) => $v !== null)); + $avg = $scores !== [] ? round(array_sum($scores) / count($scores), 2) : null; + $pass = count(array_filter($rs, static fn($r) => ($r['score'] ?? null) !== null && ($r['score'] >= 0.6 * $fs))); + $passRate = $submitted > 0 ? round($pass / $submitted * 100, 1) : 0; + $durs = array_values(array_filter(array_map(static fn($r) => $r['duration'] ?? null, $rs), static fn($v) => $v !== null)); + $avgDur = $durs !== [] ? (int)round(array_sum($durs) / count($durs)) : null; + return [ + 'total' => $total, + 'submitted' => $submitted, + 'absent' => max(0, $total - $submitted), + 'rate' => $total > 0 ? round($submitted / $total * 100, 1) : 0, + 'avg' => $avg, + 'pass_rate' => $passRate, + 'max' => $scores !== [] ? max($scores) : null, + 'min' => $scores !== [] ? min($scores) : null, + 'avg_duration' => $avgDur, + ]; + }; + $classes = []; foreach ($byCls as $k => $stu) { - $rs = $rowsByCls[$k] ?? []; - $total = count($stu); - $submitted = count($rs); - $scores = array_values(array_filter(array_map(static fn($r) => $r['score'] ?? null, $rs), static fn($v) => $v !== null)); - $avg = $scores !== [] ? round(array_sum($scores) / count($scores), 2) : null; - $pass = count(array_filter($rs, static fn($r) => ($r['score'] ?? null) !== null && ($r['score'] >= 0.6 * $fs))); - $passRate = $submitted > 0 ? round($pass / $submitted * 100, 1) : 0; - $durs = array_values(array_filter(array_map(static fn($r) => $r['duration'] ?? null, $rs), static fn($v) => $v !== null)); - $avgDur = $durs !== [] ? (int)round(array_sum($durs) / count($durs)) : null; - $classes[] = [ - 'class' => $k, - 'total' => $total, - 'submitted' => $submitted, - 'absent' => max(0, $total - $submitted), - 'rate' => $total > 0 ? round($submitted / $total * 100, 1) : 0, - 'avg' => $avg, - 'pass_rate' => $passRate, - 'max' => $scores !== [] ? max($scores) : null, - 'min' => $scores !== [] ? min($scores) : null, - 'avg_duration' => $avgDur, - ]; + $rs = $rowsByCls[$k] ?? []; + $row = $statOf($stu, $rs, $fs); + $row['class'] = $k; + + // 班级内按组别(A/B 等)再聚合,用于看板下钻对比 + $stuByGrp = []; + foreach ($stu as $s) { + $g = trim((string)($s['grp'] ?? '')) ?: '(未分组)'; + $stuByGrp[$g][] = $s; + } + $rowsByGrp = []; + foreach ($rs as $r) { + $g = trim((string)($r['grp'] ?? '')) ?: '(未分组)'; + $rowsByGrp[$g][] = $r; + } + $groups = []; + foreach ($stuByGrp as $g => $gstu) { + $gs = $statOf($gstu, $rowsByGrp[$g] ?? [], $fs); + $gs['group'] = $g; + $groups[] = $gs; + } + usort($groups, static fn(array $a, array $b): int => strcmp((string)$a['group'], (string)$b['group'])); + $row['groups'] = $groups; + + $classes[] = $row; } usort($classes, static function (array $a, array $b): int { $an = is_numeric($a['class']) ? (int)$a['class'] : $a['class']; @@ -233,6 +260,7 @@ try { 'sid' => (string)($s['sid'] ?? ''), 'name' => (string)($s['name'] ?? ''), 'cls' => trim((string)($s['cls'] ?? '')), + 'grp' => trim((string)($s['grp'] ?? '')), ]; } Roster::save($students); @@ -263,17 +291,18 @@ try { $out = fopen('php://output', 'w'); if ($action === 'export_absent') { - fputcsv($out, ['学号', '姓名', '班级']); + fputcsv($out, ['学号', '姓名', '班级', '组别']); foreach ($result['absent'] as $r) { - fputcsv($out, [$r['sid'], $r['name'], $r['cls'] ?? '']); + fputcsv($out, [$r['sid'], $r['name'], $r['cls'] ?? '', $r['grp'] ?? '']); } } else { - fputcsv($out, ['学号', '姓名', '班级', '成绩', '提交时间', '考试时长', '考试时长(秒)']); + fputcsv($out, ['学号', '姓名', '班级', '组别', '成绩', '提交时间', '考试时长', '考试时长(秒)']); foreach ($result['rows'] as $r) { fputcsv($out, [ $r['sid'], $r['name'], $r['cls'] ?? '', + $r['grp'] ?? '', $r['score'] === null ? '' : $r['score'], $r['submit_at'], $r['duration_txt'], diff --git a/assets/app.js b/assets/app.js index f5af05d..d194a15 100644 --- a/assets/app.js +++ b/assets/app.js @@ -287,7 +287,7 @@ const tb = $('#tbody'); if (!rows.length) { - tb.innerHTML = '