feat: 支持单套试卷同一提交人多次刷卷并展示
- DataService::build 新增 aggregateByStudent 按学号聚合: dedupe=true 仅保留最新一份;false 则每学号一行(代表成绩取最高分) 附 attempts(次数) 与 attempts_list(每次提交明细),修正提交人数被多次提交inflate的问题 - api.php dashboard 透传 attempts/attempts_list;statOf 增加 attempts 求和;export 增加刷题次数列 - dashboard.php 成员榜加"刷N次"徽标,点击下钻弹窗看每次提交(时间/成绩/时长/暂存);hero 显示总提交次数 - 主统计页成绩明细同样加"刷N次"徽标与下钻弹窗 - stats 新增 attempts_total
This commit is contained in:
+73
-13
@@ -128,9 +128,8 @@ class DataService
|
||||
$includeTemp = !empty($cfg['include_temp_save']);
|
||||
$dedupe = !empty($cfg['dedupe']);
|
||||
|
||||
$rows = [];
|
||||
$rawRows = []; // 已匹配学号的原始 row(每条 = 一次提交)
|
||||
$unmatched = [];
|
||||
$seenSid = [];
|
||||
|
||||
foreach ($answers as $a) {
|
||||
$tempSave = isset($a['tempSave']) ? (int)$a['tempSave'] : 1;
|
||||
@@ -226,19 +225,15 @@ class DataService
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dedupe && isset($seenSid[$sid])) {
|
||||
$idx = $seenSid[$sid];
|
||||
// 保留提交时间最新的一份
|
||||
if ($ts >= ($rows[$idx]['submit_ts'] ?? 0)) {
|
||||
$rows[$idx] = $row;
|
||||
$seenSid[$sid] = $idx;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$seenSid[$sid] = count($rows);
|
||||
$rows[] = $row;
|
||||
$rawRows[] = $row;
|
||||
}
|
||||
|
||||
// 按学号聚合:dedupe=true 仅保留最新一份;false 则每名一行(代表=最高分)
|
||||
// 并附带 attempts(次数) / attempts_list(每次提交明细),供看板展示"多次刷题"
|
||||
$agg = self::aggregateByStudent($rawRows, $dedupe);
|
||||
$rows = $agg['rows'];
|
||||
$attemptsTotal = $agg['attempts_total'];
|
||||
|
||||
// 成绩明细默认按提交时间降序(最新在前),学号作为次级排序
|
||||
usort($rows, static function (array $x, array $y): int {
|
||||
$d = ($y['submit_ts'] ?? 0) <=> ($x['submit_ts'] ?? 0);
|
||||
@@ -292,6 +287,7 @@ class DataService
|
||||
'min_score' => $scores !== [] ? min($scores) : null,
|
||||
'avg_duration' => $durs !== [] ? (int)round(array_sum($durs) / count($durs)) : null,
|
||||
'answer_total' => count($answers),
|
||||
'attempts_total'=> $attemptsTotal,
|
||||
];
|
||||
|
||||
return [
|
||||
@@ -312,4 +308,68 @@ class DataService
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 按学号聚合一次提交的答卷记录,解决"同人多次刷卷"在看板中被当成多个人的问题。
|
||||
* - dedupe=true:每学号仅保留提交时间最新的一份(原行为)
|
||||
* - dedupe=false:每学号一行,代表成绩取最高分,附 attempts(次数) / attempts_list(每次提交明细)
|
||||
*
|
||||
* @param array $rows 已匹配学号的原始 row(每条 = 一次提交)
|
||||
* @return array{rows:array, attempts_total:int}
|
||||
*/
|
||||
public static function aggregateByStudent(array $rows, bool $dedupe): array
|
||||
{
|
||||
$bySid = [];
|
||||
foreach ($rows as $r) {
|
||||
$bySid[(string)($r['sid'] ?? '')][] = $r;
|
||||
}
|
||||
$agg = [];
|
||||
$attemptsTotal = 0;
|
||||
foreach ($bySid as $sid => $list) {
|
||||
$attemptsTotal += count($list);
|
||||
// 升序:旧 -> 新
|
||||
$sorted = $list;
|
||||
usort($sorted, static function (array $x, array $y): int {
|
||||
return ($x['submit_ts'] ?? 0) <=> ($y['submit_ts'] ?? 0);
|
||||
});
|
||||
$attemptsList = array_map(static function (array $a): array {
|
||||
return [
|
||||
'submit_at' => $a['submit_at'],
|
||||
'submit_ts' => $a['submit_ts'],
|
||||
'score' => $a['score'],
|
||||
'duration' => $a['duration'],
|
||||
'duration_txt' => $a['duration_txt'],
|
||||
'answer_id' => $a['answer_id'],
|
||||
'temp_save' => $a['temp_save'],
|
||||
];
|
||||
}, $sorted);
|
||||
|
||||
if ($dedupe) {
|
||||
// 仅保留提交时间最新的一份(明细列表也只留这一份,与"只保留最新"语义一致)
|
||||
$row = $sorted[count($sorted) - 1];
|
||||
$row['attempts'] = 1;
|
||||
$row['best_score'] = $row['score'];
|
||||
$row['attempts_list'] = [$attemptsList[count($attemptsList) - 1]];
|
||||
} else {
|
||||
// 代表成绩取最高分(null 成绩不参与比较,全部为 null 时回退为最近一份)
|
||||
$best = null;
|
||||
$bestScore = null;
|
||||
foreach ($sorted as $a) {
|
||||
if ($a['score'] !== null && ($bestScore === null || $a['score'] > $bestScore)) {
|
||||
$bestScore = $a['score'];
|
||||
$best = $a;
|
||||
}
|
||||
}
|
||||
if ($best === null) {
|
||||
$best = $sorted[count($sorted) - 1];
|
||||
}
|
||||
$row = $best;
|
||||
$row['attempts'] = count($list);
|
||||
$row['best_score'] = $bestScore;
|
||||
$row['attempts_list'] = $attemptsList;
|
||||
}
|
||||
$agg[] = $row;
|
||||
}
|
||||
return ['rows' => $agg, 'attempts_total' => $attemptsTotal];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user