feat: 看板支持A/B组对比下钻 + 全屏 + 删除返回按钮 + 全系统组别(A/B)支持

This commit is contained in:
2026-09-08 19:44:45 +08:00
parent 6d14860b9c
commit 1b7683baab
7 changed files with 251 additions and 77 deletions
+53 -24
View File
@@ -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'],