|
@@ -14,7 +14,7 @@
|
|
|
$scoreTotal = $summary['score_total'] ?? null;
|
|
$scoreTotal = $summary['score_total'] ?? null;
|
|
|
$scoreRate = $summary['score_rate'] ?? null;
|
|
$scoreRate = $summary['score_rate'] ?? null;
|
|
|
$averageMastery = $summary['average_mastery'] ?? null;
|
|
$averageMastery = $summary['average_mastery'] ?? null;
|
|
|
- $overallLabel = $summary['overall_label'] ?? '待评估';
|
|
|
|
|
|
|
+ $examHitKpSet = array_fill_keys(array_map('strval', $exam_hit_kp_codes ?? []), true);
|
|
|
$difficultySummary = $summary['difficulty'] ?? [];
|
|
$difficultySummary = $summary['difficulty'] ?? [];
|
|
|
$comparisonSummary = $summary['comparison'] ?? [];
|
|
$comparisonSummary = $summary['comparison'] ?? [];
|
|
|
$overallLabelDetail = $summary['overall_label_detail'] ?? [];
|
|
$overallLabelDetail = $summary['overall_label_detail'] ?? [];
|
|
@@ -50,42 +50,13 @@
|
|
|
|
|
|
|
|
$statusColor = function (string $status): string {
|
|
$statusColor = function (string $status): string {
|
|
|
return match ($status) {
|
|
return match ($status) {
|
|
|
- '良好' => '#16a34a',
|
|
|
|
|
- '一般' => '#f97316',
|
|
|
|
|
- '薄弱' => '#e11d48',
|
|
|
|
|
|
|
+ '已掌握' => '#16a34a',
|
|
|
|
|
+ '薄弱' => '#f59e0b',
|
|
|
|
|
+ '未入门' => '#ef4444',
|
|
|
default => '#64748b',
|
|
default => '#64748b',
|
|
|
};
|
|
};
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- $n = max(1, count($radar));
|
|
|
|
|
- $cx = 210;
|
|
|
|
|
- $cy = 155;
|
|
|
|
|
- $r = 108;
|
|
|
|
|
- $outer = [];
|
|
|
|
|
- $inner = [];
|
|
|
|
|
- for ($i = 0; $i < $n; $i++) {
|
|
|
|
|
- $angle = -M_PI / 2 + (2 * M_PI * $i / $n);
|
|
|
|
|
- $ox = $cx + $r * cos($angle);
|
|
|
|
|
- $oy = $cy + $r * sin($angle);
|
|
|
|
|
- $outer[] = [$ox, $oy];
|
|
|
|
|
-
|
|
|
|
|
- $value = isset($radar[$i]['value']) ? (float) $radar[$i]['value'] : 0.0;
|
|
|
|
|
- $ratio = max(0.0, min(1.0, $value / 5));
|
|
|
|
|
- $ix = $cx + $r * $ratio * cos($angle);
|
|
|
|
|
- $iy = $cy + $r * $ratio * sin($angle);
|
|
|
|
|
- $inner[] = [$ix, $iy];
|
|
|
|
|
- }
|
|
|
|
|
- $outerPoints = implode(' ', array_map(fn ($p) => round($p[0], 2).','.round($p[1], 2), $outer));
|
|
|
|
|
- $innerPoints = implode(' ', array_map(fn ($p) => round($p[0], 2).','.round($p[1], 2), $inner));
|
|
|
|
|
-
|
|
|
|
|
- $insightMap = [];
|
|
|
|
|
- foreach (($question_insights ?? []) as $insight) {
|
|
|
|
|
- $no = $insight['question_number'] ?? $insight['question_id'] ?? null;
|
|
|
|
|
- if ($no !== null) {
|
|
|
|
|
- $insightMap[$no] = $insight;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
$analysisWrongMap = [];
|
|
$analysisWrongMap = [];
|
|
|
foreach (($analysis_data['question_analysis'] ?? []) as $qa) {
|
|
foreach (($analysis_data['question_analysis'] ?? []) as $qa) {
|
|
|
$qid = $qa['question_bank_id'] ?? $qa['question_id'] ?? null;
|
|
$qid = $qa['question_bank_id'] ?? $qa['question_id'] ?? null;
|
|
@@ -215,7 +186,7 @@
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
$clusterCards = [];
|
|
$clusterCards = [];
|
|
|
- $allClusterPoints = [];
|
|
|
|
|
|
|
+ $allStagePoints = [];
|
|
|
foreach ($radar as $moduleItem) {
|
|
foreach ($radar as $moduleItem) {
|
|
|
$children = is_array($moduleItem['children'] ?? null) ? $moduleItem['children'] : [];
|
|
$children = is_array($moduleItem['children'] ?? null) ? $moduleItem['children'] : [];
|
|
|
$greatMap = [];
|
|
$greatMap = [];
|
|
@@ -229,8 +200,8 @@
|
|
|
$parentCode = trim((string) ($child['parent_code'] ?? ''));
|
|
$parentCode = trim((string) ($child['parent_code'] ?? ''));
|
|
|
$parentName = $parentCode !== '' ? $parentCode : '未分组';
|
|
$parentName = $parentCode !== '' ? $parentCode : '未分组';
|
|
|
}
|
|
}
|
|
|
- $mastery = isset($child['mastery_level']) ? (float) $child['mastery_level'] : null;
|
|
|
|
|
- $status = $childMasteryStatus($mastery);
|
|
|
|
|
|
|
+ $childMasteryLevel = isset($child['mastery_level']) ? (float) $child['mastery_level'] : null;
|
|
|
|
|
+ $status = $childMasteryStatus($childMasteryLevel);
|
|
|
if (!isset($greatMap[$greatKey])) {
|
|
if (!isset($greatMap[$greatKey])) {
|
|
|
$greatMap[$greatKey] = [];
|
|
$greatMap[$greatKey] = [];
|
|
|
}
|
|
}
|
|
@@ -244,12 +215,20 @@
|
|
|
'code' => (string) ($child['code'] ?? ''),
|
|
'code' => (string) ($child['code'] ?? ''),
|
|
|
'name' => (string) ($child['name'] ?? '未命名知识点'),
|
|
'name' => (string) ($child['name'] ?? '未命名知识点'),
|
|
|
'path' => (string) ($child['path'] ?? ''),
|
|
'path' => (string) ($child['path'] ?? ''),
|
|
|
- 'mastery_level' => $mastery,
|
|
|
|
|
|
|
+ 'mastery_level' => $childMasteryLevel,
|
|
|
'change' => isset($child['change']) ? (float) $child['change'] : null,
|
|
'change' => isset($child['change']) ? (float) $child['change'] : null,
|
|
|
'status' => $status,
|
|
'status' => $status,
|
|
|
'color' => $childStatusColor($status),
|
|
'color' => $childStatusColor($status),
|
|
|
'is_hit' => !empty($child['is_hit']),
|
|
'is_hit' => !empty($child['is_hit']),
|
|
|
];
|
|
];
|
|
|
|
|
+ $allStagePoints[] = [
|
|
|
|
|
+ 'code' => (string) ($child['code'] ?? ''),
|
|
|
|
|
+ 'name' => (string) ($child['name'] ?? '未命名知识点'),
|
|
|
|
|
+ 'mastery_level' => $childMasteryLevel,
|
|
|
|
|
+ 'status' => $status,
|
|
|
|
|
+ 'change' => isset($child['change']) ? (float) $child['change'] : null,
|
|
|
|
|
+ 'is_hit' => !empty($child['is_hit']),
|
|
|
|
|
+ ];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$greatGroups = [];
|
|
$greatGroups = [];
|
|
@@ -329,6 +308,7 @@
|
|
|
foreach (($great['grand_groups'] ?? []) as $grand) {
|
|
foreach (($great['grand_groups'] ?? []) as $grand) {
|
|
|
$gStats = $grand['stats'] ?? ['learned' => 0, 'total' => 0];
|
|
$gStats = $grand['stats'] ?? ['learned' => 0, 'total' => 0];
|
|
|
$clusterCards[] = [
|
|
$clusterCards[] = [
|
|
|
|
|
+ 'module_name' => (string) ($moduleItem['name'] ?? '未分组'),
|
|
|
'great_name' => $great['great_name'] ?? '未分组',
|
|
'great_name' => $great['great_name'] ?? '未分组',
|
|
|
'grand_name' => $grand['grand_name'] ?? '未分组',
|
|
'grand_name' => $grand['grand_name'] ?? '未分组',
|
|
|
'parent_groups' => $grand['parent_groups'] ?? [],
|
|
'parent_groups' => $grand['parent_groups'] ?? [],
|
|
@@ -343,21 +323,14 @@
|
|
|
return (($sb['learned'] ?? 0) <=> ($sa['learned'] ?? 0))
|
|
return (($sb['learned'] ?? 0) <=> ($sa['learned'] ?? 0))
|
|
|
?: (($sb['total'] ?? 0) <=> ($sa['total'] ?? 0));
|
|
?: (($sb['total'] ?? 0) <=> ($sa['total'] ?? 0));
|
|
|
});
|
|
});
|
|
|
- foreach ($clusterCards as $card) {
|
|
|
|
|
- foreach (($card['parent_groups'] ?? []) as $pg) {
|
|
|
|
|
- foreach (($pg['points'] ?? []) as $p) {
|
|
|
|
|
- $allClusterPoints[] = $p;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
$kpStatsTotal = [
|
|
$kpStatsTotal = [
|
|
|
- 'total' => count($allClusterPoints),
|
|
|
|
|
|
|
+ 'total' => count($allStagePoints),
|
|
|
'mastered' => 0,
|
|
'mastered' => 0,
|
|
|
'weak' => 0,
|
|
'weak' => 0,
|
|
|
'beginner' => 0,
|
|
'beginner' => 0,
|
|
|
'unlearned' => 0,
|
|
'unlearned' => 0,
|
|
|
];
|
|
];
|
|
|
- foreach ($allClusterPoints as $p) {
|
|
|
|
|
|
|
+ foreach ($allStagePoints as $p) {
|
|
|
$st = (string) ($p['status'] ?? '未学习');
|
|
$st = (string) ($p['status'] ?? '未学习');
|
|
|
if ($st === '已掌握') {
|
|
if ($st === '已掌握') {
|
|
|
$kpStatsTotal['mastered']++;
|
|
$kpStatsTotal['mastered']++;
|
|
@@ -372,10 +345,11 @@
|
|
|
$moduleRowsWithStatus = array_values(array_filter($modules, function ($m) {
|
|
$moduleRowsWithStatus = array_values(array_filter($modules, function ($m) {
|
|
|
$status = trim((string) ($m['status'] ?? ''));
|
|
$status = trim((string) ($m['status'] ?? ''));
|
|
|
$masteryLevel = $m['mastery_level'] ?? null;
|
|
$masteryLevel = $m['mastery_level'] ?? null;
|
|
|
|
|
+ $questionCount = (int) ($m['question_count'] ?? 0);
|
|
|
if ($masteryLevel !== null) {
|
|
if ($masteryLevel !== null) {
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
- return $status !== '' && ! in_array($status, ['暂无', '-', '未涉及'], true);
|
|
|
|
|
|
|
+ return $questionCount > 0 && $status !== '' && ! in_array($status, ['暂无', '-', '未涉及', '未学习'], true);
|
|
|
}));
|
|
}));
|
|
|
$pathTagByModuleName = [];
|
|
$pathTagByModuleName = [];
|
|
|
foreach (['keep' => '保分不错', 'boost' => '需要加强', 'key' => '优先加强'] as $bucket => $tagName) {
|
|
foreach (['keep' => '保分不错', 'boost' => '需要加强', 'key' => '优先加强'] as $bucket => $tagName) {
|
|
@@ -427,8 +401,8 @@
|
|
|
];
|
|
];
|
|
|
$kpQuestionTypeMap = [];
|
|
$kpQuestionTypeMap = [];
|
|
|
foreach (($questions ?? []) as $qItem) {
|
|
foreach (($questions ?? []) as $qItem) {
|
|
|
- $kpName = trim((string) ($qItem['knowledge_point_name'] ?? $qItem['knowledge_point'] ?? ''));
|
|
|
|
|
- if ($kpName === '') {
|
|
|
|
|
|
|
+ $kpCode = trim((string) ($qItem['knowledge_point'] ?? ''));
|
|
|
|
|
+ if ($kpCode === '') {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
$rawType = strtolower(trim((string) ($qItem['question_type'] ?? '')));
|
|
$rawType = strtolower(trim((string) ($qItem['question_type'] ?? '')));
|
|
@@ -436,10 +410,10 @@
|
|
|
if ($typeLabel === '') {
|
|
if ($typeLabel === '') {
|
|
|
$typeLabel = '未知题型';
|
|
$typeLabel = '未知题型';
|
|
|
}
|
|
}
|
|
|
- if (! isset($kpQuestionTypeMap[$kpName])) {
|
|
|
|
|
- $kpQuestionTypeMap[$kpName] = [];
|
|
|
|
|
|
|
+ if (! isset($kpQuestionTypeMap[$kpCode])) {
|
|
|
|
|
+ $kpQuestionTypeMap[$kpCode] = [];
|
|
|
}
|
|
}
|
|
|
- $kpQuestionTypeMap[$kpName][$typeLabel] = true;
|
|
|
|
|
|
|
+ $kpQuestionTypeMap[$kpCode][$typeLabel] = true;
|
|
|
}
|
|
}
|
|
|
$moduleKpSuggestions = [];
|
|
$moduleKpSuggestions = [];
|
|
|
foreach ($moduleRowsWithStatus as $m) {
|
|
foreach ($moduleRowsWithStatus as $m) {
|
|
@@ -496,8 +470,6 @@
|
|
|
return $sa <=> $sb;
|
|
return $sa <=> $sb;
|
|
|
});
|
|
});
|
|
|
$weakest = $unlearned[0];
|
|
$weakest = $unlearned[0];
|
|
|
- } else {
|
|
|
|
|
- $weakest = $lowestStarted;
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
@@ -511,20 +483,33 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
if (! is_array($weakest)) {
|
|
if (! is_array($weakest)) {
|
|
|
|
|
+ $moduleKpSuggestions[] = [
|
|
|
|
|
+ 'module_name' => $moduleName,
|
|
|
|
|
+ 'path_tag' => $pathTagByModuleName[$moduleName] ?? '待观察',
|
|
|
|
|
+ 'kp_name' => '',
|
|
|
|
|
+ 'kp_code' => '',
|
|
|
|
|
+ 'mastery_level' => null,
|
|
|
|
|
+ 'status' => '当前模块暂无需额外关注知识点',
|
|
|
|
|
+ 'question_types' => [],
|
|
|
|
|
+ 'is_empty' => true,
|
|
|
|
|
+ ];
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
$kpName = (string) ($weakest['name'] ?? '');
|
|
$kpName = (string) ($weakest['name'] ?? '');
|
|
|
if ($kpName === '') {
|
|
if ($kpName === '') {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
- $types = array_keys($kpQuestionTypeMap[$kpName] ?? []);
|
|
|
|
|
|
|
+ $kpCode = (string) ($weakest['code'] ?? '');
|
|
|
|
|
+ $types = array_keys($kpQuestionTypeMap[$kpCode] ?? []);
|
|
|
$moduleKpSuggestions[] = [
|
|
$moduleKpSuggestions[] = [
|
|
|
'module_name' => $moduleName,
|
|
'module_name' => $moduleName,
|
|
|
'path_tag' => $pathTagByModuleName[$moduleName] ?? '待观察',
|
|
'path_tag' => $pathTagByModuleName[$moduleName] ?? '待观察',
|
|
|
'kp_name' => $kpName,
|
|
'kp_name' => $kpName,
|
|
|
|
|
+ 'kp_code' => $kpCode,
|
|
|
'mastery_level' => $weakest['mastery_level'] ?? null,
|
|
'mastery_level' => $weakest['mastery_level'] ?? null,
|
|
|
'status' => $childMasteryStatus($weakest['mastery_level'] ?? null),
|
|
'status' => $childMasteryStatus($weakest['mastery_level'] ?? null),
|
|
|
'question_types' => $types,
|
|
'question_types' => $types,
|
|
|
|
|
+ 'is_empty' => false,
|
|
|
];
|
|
];
|
|
|
}
|
|
}
|
|
|
$moduleSuggestionByName = [];
|
|
$moduleSuggestionByName = [];
|
|
@@ -534,15 +519,25 @@
|
|
|
$moduleSuggestionByName[$name] = $sug;
|
|
$moduleSuggestionByName[$name] = $sug;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- $kpChangeItems = array_values(array_filter($allClusterPoints, function ($p) {
|
|
|
|
|
- $change = $p['change'] ?? null;
|
|
|
|
|
- return $change !== null && is_numeric($change) && !empty($p['is_hit']);
|
|
|
|
|
- }));
|
|
|
|
|
- if (empty($kpChangeItems)) {
|
|
|
|
|
- $kpChangeItems = array_values(array_filter($allClusterPoints, function ($p) {
|
|
|
|
|
- $change = $p['change'] ?? null;
|
|
|
|
|
- return $change !== null && is_numeric($change);
|
|
|
|
|
- }));
|
|
|
|
|
|
|
+ $kpChangeItems = [];
|
|
|
|
|
+ foreach (($mastery['items'] ?? []) as $item) {
|
|
|
|
|
+ $code = trim((string) ($item['kp_code'] ?? $item['code'] ?? ''));
|
|
|
|
|
+ if ($code !== '' && ! empty($examHitKpSet) && ! isset($examHitKpSet[$code])) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $level = $item['mastery_level'] ?? null;
|
|
|
|
|
+ if ($level === null || ! is_numeric($level)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $change = $item['mastery_change'] ?? $item['change'] ?? 0.0;
|
|
|
|
|
+ $kpChangeItems[] = [
|
|
|
|
|
+ 'code' => $code,
|
|
|
|
|
+ 'name' => (string) ($item['kp_name'] ?? $item['name'] ?? ($code !== '' ? $code : '-')),
|
|
|
|
|
+ 'mastery_level' => (float) $level,
|
|
|
|
|
+ 'change' => is_numeric($change) ? (float) $change : 0.0,
|
|
|
|
|
+ 'status' => $childMasteryStatus((float) $level),
|
|
|
|
|
+ 'is_hit' => true,
|
|
|
|
|
+ ];
|
|
|
}
|
|
}
|
|
|
usort($kpChangeItems, function ($a, $b) {
|
|
usort($kpChangeItems, function ($a, $b) {
|
|
|
return abs((float) ($b['change'] ?? 0)) <=> abs((float) ($a['change'] ?? 0));
|
|
return abs((float) ($b['change'] ?? 0)) <=> abs((float) ($a['change'] ?? 0));
|
|
@@ -648,16 +643,7 @@
|
|
|
box-shadow: none;
|
|
box-shadow: none;
|
|
|
}
|
|
}
|
|
|
.overall-meta { margin-top: 8px; font-size: 9px; color: #64748b; line-height: 1.6; white-space: nowrap; }
|
|
.overall-meta { margin-top: 8px; font-size: 9px; color: #64748b; line-height: 1.6; white-space: nowrap; }
|
|
|
- .radar-center { text-align: center; }
|
|
|
|
|
- .legend { margin-top: 8px; font-size: 12px; color: #475569; }
|
|
|
|
|
- .legend span { margin: 0 8px; }
|
|
|
|
|
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 999px; margin-right: 4px; vertical-align: middle; }
|
|
.dot { display: inline-block; width: 10px; height: 10px; border-radius: 999px; margin-right: 4px; vertical-align: middle; }
|
|
|
- /* PDF 版优先上下结构,避免左右分栏导致拥挤 */
|
|
|
|
|
- .radar-split { display: block; width: 100%; }
|
|
|
|
|
- .radar-left { width: 100%; text-align: center; }
|
|
|
|
|
- .radar-right { width: 100%; padding-left: 0; margin-top: 10px; }
|
|
|
|
|
- .radar-desc { border: 1px solid #dbeafe; background: #f8fbff; border-radius: 12px; padding: 12px; text-align: left; }
|
|
|
|
|
- .radar-item { display: block; margin: 6px 0; font-size: 12px; }
|
|
|
|
|
.cluster-toolbar {
|
|
.cluster-toolbar {
|
|
|
margin-bottom: 8px;
|
|
margin-bottom: 8px;
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
@@ -763,74 +749,6 @@
|
|
|
.tag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; color: #334155; background: #e5e7eb; }
|
|
.tag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; color: #334155; background: #e5e7eb; }
|
|
|
.error-kp-tag { display: inline-block; margin: 0 6px 6px 0; padding: 1px 7px; border-radius: 999px; font-size: 10px; color: #334155; background: #f8fafc; border: 1px solid #d1d5db; }
|
|
.error-kp-tag { display: inline-block; margin: 0 6px 6px 0; padding: 1px 7px; border-radius: 999px; font-size: 10px; color: #334155; background: #f8fafc; border: 1px solid #d1d5db; }
|
|
|
.error-kp-tag.high-risk { color: #b91c1c; border-color: #fca5a5; background: #fff; font-weight: 600; }
|
|
.error-kp-tag.high-risk { color: #b91c1c; border-color: #fca5a5; background: #fff; font-weight: 600; }
|
|
|
- .question-card { border:1px solid #e5e7eb; border-radius:8px; padding:6px 9px; margin-bottom:5px; background:#fff; page-break-inside:auto; break-inside:auto; }
|
|
|
|
|
- .question-block { margin-bottom: 5px; padding: 5px; border-radius: 4px; page-break-inside: auto; break-inside: auto; }
|
|
|
|
|
- .question-card,
|
|
|
|
|
- .question-card .math-content,
|
|
|
|
|
- .question-card .solution-content { font-size: 12px; line-height: 1.7; }
|
|
|
|
|
- .question-card .question-stem svg,
|
|
|
|
|
- .question-card .math-content svg { max-width: 100%; height: auto; display: block; shape-rendering: geometricPrecision; text-rendering: geometricPrecision; }
|
|
|
|
|
- .question-card .question-stem svg text {
|
|
|
|
|
- font-family: "Noto Serif", "Noto Serif CJK SC", "Noto Sans CJK SC", "Noto Sans", "STSongti-SC", "PingFang SC", "Songti SC", serif !important;
|
|
|
|
|
- font-size: 13px !important;
|
|
|
|
|
- font-weight: bold;
|
|
|
|
|
- dominant-baseline: middle;
|
|
|
|
|
- text-anchor: middle;
|
|
|
|
|
- }
|
|
|
|
|
- .question-card .question-stem svg circle,
|
|
|
|
|
- .question-card .question-stem svg line,
|
|
|
|
|
- .question-card .question-stem svg polygon,
|
|
|
|
|
- .question-card .question-stem svg polyline { shape-rendering: geometricPrecision; }
|
|
|
|
|
- .question-card .question-stem img,
|
|
|
|
|
- .question-card .question-main img {
|
|
|
|
|
- display: block;
|
|
|
|
|
- max-width: 220px;
|
|
|
|
|
- max-height: 60mm;
|
|
|
|
|
- width: auto;
|
|
|
|
|
- height: auto;
|
|
|
|
|
- margin: 6px auto;
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
- object-fit: contain;
|
|
|
|
|
- -webkit-print-color-adjust: exact;
|
|
|
|
|
- print-color-adjust: exact;
|
|
|
|
|
- image-rendering: -webkit-optimize-contrast;
|
|
|
|
|
- }
|
|
|
|
|
- .question-card .question-stem .katex { font-size: 1em !important; vertical-align: 0; }
|
|
|
|
|
- .question-card .question-stem .katex-display { margin: 0.35em 0 !important; }
|
|
|
|
|
- .question-card .solution-content img,
|
|
|
|
|
- .question-card .report-answer-meta img {
|
|
|
|
|
- display: block;
|
|
|
|
|
- max-width: 220px;
|
|
|
|
|
- max-height: 60mm;
|
|
|
|
|
- width: auto;
|
|
|
|
|
- height: auto;
|
|
|
|
|
- margin: 6px auto;
|
|
|
|
|
- object-fit: contain;
|
|
|
|
|
- -webkit-print-color-adjust: exact;
|
|
|
|
|
- print-color-adjust: exact;
|
|
|
|
|
- }
|
|
|
|
|
- .solution-content {
|
|
|
|
|
- display: block;
|
|
|
|
|
- line-height: 1.75;
|
|
|
|
|
- white-space: normal;
|
|
|
|
|
- word-break: break-word;
|
|
|
|
|
- overflow-wrap: anywhere;
|
|
|
|
|
- page-break-inside: auto;
|
|
|
|
|
- break-inside: auto;
|
|
|
|
|
- }
|
|
|
|
|
- .report-options { margin-top: 6px; page-break-inside: auto; break-inside: auto; }
|
|
|
|
|
- .report-options.options-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px 12px; }
|
|
|
|
|
- .report-options.options-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 20px; }
|
|
|
|
|
- .report-options.options-grid-1 { display: grid; grid-template-columns: 1fr; gap: 8px; }
|
|
|
|
|
- .report-options .option { display: flex; align-items: baseline; font-size: 12px; line-height: 1.6; page-break-inside: auto; break-inside: auto; }
|
|
|
|
|
- .report-options .option strong { margin-right: 4px; flex: 0 0 auto; }
|
|
|
|
|
- .report-options .option-value.option-short { white-space: nowrap; }
|
|
|
|
|
- .report-options .option-value.option-long { white-space: normal; word-break: break-word; }
|
|
|
|
|
- .report-options .option p, .report-options .option div { margin: 0; display: inline; }
|
|
|
|
|
- .report-options .option img { max-width: 100%; height: auto; vertical-align: middle; }
|
|
|
|
|
- .report-answer-meta { font-size: 12px; color: #2f2f2f; line-height: 1.75; margin-top: 6px; page-break-inside: auto; break-inside: auto; }
|
|
|
|
|
- .report-answer-meta .answer-line + .answer-line { margin-top: 4px; }
|
|
|
|
|
- .report-answer-meta .solution-content { display: inline; line-height: 1.75; }
|
|
|
|
|
.muted { color: #6b7280; font-size: 12px; }
|
|
.muted { color: #6b7280; font-size: 12px; }
|
|
|
</style>
|
|
</style>
|
|
|
</head>
|
|
</head>
|
|
@@ -855,7 +773,6 @@
|
|
|
暂无得分数据
|
|
暂无得分数据
|
|
|
@endif
|
|
@endif
|
|
|
</li>
|
|
</li>
|
|
|
- <li>得分率:{{ $scoreRate !== null ? number_format((float) $scoreRate * 100, 1) . '%' : '暂无得分率' }}</li>
|
|
|
|
|
<li>平均掌握度:{{ $averageMastery !== null ? number_format((float) $averageMastery * 100, 1) . '%' : '暂无掌握度' }}</li>
|
|
<li>平均掌握度:{{ $averageMastery !== null ? number_format((float) $averageMastery * 100, 1) . '%' : '暂无掌握度' }}</li>
|
|
|
<li>
|
|
<li>
|
|
|
难度匹配:
|
|
难度匹配:
|
|
@@ -928,7 +845,7 @@
|
|
|
@foreach($clusterCards as $cluster)
|
|
@foreach($clusterCards as $cluster)
|
|
|
<div class="cluster-card">
|
|
<div class="cluster-card">
|
|
|
<div class="cluster-card-title">
|
|
<div class="cluster-card-title">
|
|
|
- {{ $cluster['grand_name'] }}
|
|
|
|
|
|
|
+ {{ $cluster['module_name'] }} / {{ $cluster['grand_name'] }}
|
|
|
</div>
|
|
</div>
|
|
|
@if(!empty($cluster['parent_groups']))
|
|
@if(!empty($cluster['parent_groups']))
|
|
|
@foreach($cluster['parent_groups'] as $parent)
|
|
@foreach($cluster['parent_groups'] as $parent)
|
|
@@ -992,7 +909,10 @@
|
|
|
@foreach($kpChangeItems as $item)
|
|
@foreach($kpChangeItems as $item)
|
|
|
@php
|
|
@php
|
|
|
$delta = (float) ($item['change'] ?? 0);
|
|
$delta = (float) ($item['change'] ?? 0);
|
|
|
- $deltaText = ($delta >= 0 ? '+' : '') . number_format($delta * 100, 1) . '%';
|
|
|
|
|
|
|
+ $deltaArrow = $delta > 0.0005 ? '↑' : ($delta < -0.0005 ? '↓' : '→');
|
|
|
|
|
+ $deltaText = $deltaArrow === '→'
|
|
|
|
|
+ ? '→'
|
|
|
|
|
+ : ($deltaArrow . number_format(abs($delta) * 100, 1) . '%');
|
|
|
$deltaColor = $delta > 0 ? '#16a34a' : ($delta < 0 ? '#dc2626' : '#64748b');
|
|
$deltaColor = $delta > 0 ? '#16a34a' : ($delta < 0 ? '#dc2626' : '#64748b');
|
|
|
$masteryText = isset($item['mastery_level']) && $item['mastery_level'] !== null
|
|
$masteryText = isset($item['mastery_level']) && $item['mastery_level'] !== null
|
|
|
? number_format((float) $item['mastery_level'] * 100, 1) . '%'
|
|
? number_format((float) $item['mastery_level'] * 100, 1) . '%'
|
|
@@ -1006,9 +926,25 @@
|
|
|
@endforeach
|
|
@endforeach
|
|
|
</ul>
|
|
</ul>
|
|
|
@else
|
|
@else
|
|
|
- <div class="muted" style="margin-top:4px;">暂无可用的知识点变化数据</div>
|
|
|
|
|
|
|
+ <div class="muted" style="margin-top:4px;">
|
|
|
|
|
+ @if(!empty($kpWrongStats))
|
|
|
|
|
+ 暂无本学案命中知识点的掌握度数据,以下方知识点错误率作为本学案影响依据。
|
|
|
|
|
+ @else
|
|
|
|
|
+ 暂无可用的知识点变化数据
|
|
|
|
|
+ @endif
|
|
|
|
|
+ </div>
|
|
|
@endif
|
|
@endif
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ @if(!empty($kpWrongStats))
|
|
|
|
|
+ <div style="margin-bottom:8px; padding:8px 10px; border:1px solid #e5e7eb; border-radius:8px; background:#fff7ed;">
|
|
|
|
|
+ <div style="font-size:12px; font-weight:700; color:#9a3412; margin-bottom:6px;">知识点错误率</div>
|
|
|
|
|
+ <div style="font-size:12px; color:#475569; line-height:1.7;">
|
|
|
|
|
+ @foreach($kpWrongStats as $item)
|
|
|
|
|
+ <span class="error-kp-tag {{ $item['rate'] > 0.5 ? 'high-risk' : '' }}">{{ $item['kp_name'] }}:{{ $item['wrong'] }}/{{ $item['total'] }}({{ number_format($item['rate'] * 100, 1) }}%)</span>
|
|
|
|
|
+ @endforeach
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ @endif
|
|
|
<div style="margin-bottom:8px; padding:8px 10px; border:1px solid #e5e7eb; border-radius:8px; background:#fff;">
|
|
<div style="margin-bottom:8px; padding:8px 10px; border:1px solid #e5e7eb; border-radius:8px; background:#fff;">
|
|
|
<div style="font-size:12px;color:#334155;">
|
|
<div style="font-size:12px;color:#334155;">
|
|
|
本次学案影响模块:
|
|
本次学案影响模块:
|
|
@@ -1032,7 +968,7 @@
|
|
|
<th style="width: 18%;">掌握度<br>(学案影响)</th>
|
|
<th style="width: 18%;">掌握度<br>(学案影响)</th>
|
|
|
<th style="width: 12%;">掌握状态</th>
|
|
<th style="width: 12%;">掌握状态</th>
|
|
|
<th style="width: 9%;">题目数</th>
|
|
<th style="width: 9%;">题目数</th>
|
|
|
- <th style="width: 11%;">得分率</th>
|
|
|
|
|
|
|
+ <th style="width: 11%;">得分</th>
|
|
|
<th style="width: 11%;">路径建议</th>
|
|
<th style="width: 11%;">路径建议</th>
|
|
|
<th style="width: 25%;">关注知识点</th>
|
|
<th style="width: 25%;">关注知识点</th>
|
|
|
</tr>
|
|
</tr>
|
|
@@ -1066,15 +1002,19 @@
|
|
|
$focus = $moduleSuggestionByName[$moduleName] ?? null;
|
|
$focus = $moduleSuggestionByName[$moduleName] ?? null;
|
|
|
$focusText = '-';
|
|
$focusText = '-';
|
|
|
if (is_array($focus)) {
|
|
if (is_array($focus)) {
|
|
|
- $focusName = (string) ($focus['kp_name'] ?? '');
|
|
|
|
|
- $focusTypes = !empty($focus['question_types']) ? implode('、', $focus['question_types']) : '';
|
|
|
|
|
- $focusMastery = isset($focus['mastery_level']) && $focus['mastery_level'] !== null
|
|
|
|
|
- ? number_format((float) $focus['mastery_level'] * 100, 1) . '%'
|
|
|
|
|
- : '--';
|
|
|
|
|
- $focusSuffix = $focusTypes !== '' ? (',' . $focusTypes) : '';
|
|
|
|
|
- $focusText = $focusName !== ''
|
|
|
|
|
- ? ($focusName . '(' . $focusMastery . $focusSuffix . ')')
|
|
|
|
|
- : '-';
|
|
|
|
|
|
|
+ if (!empty($focus['is_empty'])) {
|
|
|
|
|
+ $focusText = (string) ($focus['status'] ?? '当前模块暂无需额外关注知识点');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $focusName = (string) ($focus['kp_name'] ?? '');
|
|
|
|
|
+ $focusTypes = !empty($focus['question_types']) ? implode('、', $focus['question_types']) : '';
|
|
|
|
|
+ $focusMastery = isset($focus['mastery_level']) && $focus['mastery_level'] !== null
|
|
|
|
|
+ ? number_format((float) $focus['mastery_level'] * 100, 1) . '%'
|
|
|
|
|
+ : '--';
|
|
|
|
|
+ $focusSuffix = $focusTypes !== '' ? (',' . $focusTypes) : '';
|
|
|
|
|
+ $focusText = $focusName !== ''
|
|
|
|
|
+ ? ($focusName . '(' . $focusMastery . $focusSuffix . ')')
|
|
|
|
|
+ : '当前模块暂无需额外关注知识点';
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
@endphp
|
|
@endphp
|
|
|
<tr>
|
|
<tr>
|
|
@@ -1108,260 +1048,6 @@
|
|
|
</tbody>
|
|
</tbody>
|
|
|
</table>
|
|
</table>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- @if(!empty($wrongQuestions))
|
|
|
|
|
- <div class="section" style="page-break-inside:auto; break-inside:auto;">
|
|
|
|
|
- <div class="section-title">四、这次错题记录</div>
|
|
|
|
|
- @if(!empty($kpWrongStats))
|
|
|
|
|
- <div style="margin-bottom:8px; padding:8px; border:1px solid #e5e7eb; border-radius:6px; background:#f8fafc;">
|
|
|
|
|
- <div style="font-size:12px; font-weight:600; margin-bottom:6px;">知识点错误率</div>
|
|
|
|
|
- <div style="font-size:12px; color:#475569; line-height:1.7;">
|
|
|
|
|
- @foreach($kpWrongStats as $item)
|
|
|
|
|
- <span class="error-kp-tag {{ $item['rate'] > 0.5 ? 'high-risk' : '' }}">{{ $item['kp_name'] }}:{{ $item['wrong'] }}/{{ $item['total'] }}({{ number_format($item['rate'] * 100, 1) }}%)</span>
|
|
|
|
|
- @endforeach
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- @endif
|
|
|
|
|
-
|
|
|
|
|
- @foreach($wrongQuestions as $q)
|
|
|
|
|
- @php
|
|
|
|
|
- $studentAnswer = $q['student_answer'] ?? null;
|
|
|
|
|
- $correctAnswer = $q['answer'] ?? $q['correct_answer'] ?? null;
|
|
|
|
|
- $isCorrect = $q['is_correct'] ?? null;
|
|
|
|
|
- if ($isCorrect === null && !empty($studentAnswer) && !empty($correctAnswer)) {
|
|
|
|
|
- $isCorrect = (trim($studentAnswer) === trim($correctAnswer)) ? 1 : 0;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $statusText = '';
|
|
|
|
|
- $statusColorValue = '';
|
|
|
|
|
- if ($isCorrect === 1) {
|
|
|
|
|
- $statusText = '正确';
|
|
|
|
|
- $statusColorValue = '#10b981';
|
|
|
|
|
- } elseif ($isCorrect === 0) {
|
|
|
|
|
- $statusText = '错误';
|
|
|
|
|
- $statusColorValue = '#ef4444';
|
|
|
|
|
- }
|
|
|
|
|
- $showStatus = $statusText !== '';
|
|
|
|
|
-
|
|
|
|
|
- $insight = $insightMap[$q['question_number']] ?? ($insightMap[$q['display_number'] ?? null] ?? []);
|
|
|
|
|
- $fullScore = $insight['full_score'] ?? ($q['score'] ?? null);
|
|
|
|
|
- if ($isCorrect === 1) {
|
|
|
|
|
- $score = $fullScore;
|
|
|
|
|
- } elseif ($isCorrect === 0) {
|
|
|
|
|
- $score = $q['score_obtained'] ?? 0;
|
|
|
|
|
- } else {
|
|
|
|
|
- $score = null;
|
|
|
|
|
- }
|
|
|
|
|
- $analysisRaw = $insight['analysis']
|
|
|
|
|
- ?? $insight['thinking_process']
|
|
|
|
|
- ?? $insight['feedback']
|
|
|
|
|
- ?? $insight['suggestions']
|
|
|
|
|
- ?? $insight['reason']
|
|
|
|
|
- ?? ($insight['correct_solution'] ?? null);
|
|
|
|
|
- if (empty($analysisRaw) && !empty($insight['next_steps'])) {
|
|
|
|
|
- $analysisRaw = '后续建议:' . (is_array($insight['next_steps']) ? implode(';', $insight['next_steps']) : $insight['next_steps']);
|
|
|
|
|
- }
|
|
|
|
|
- $analysis = is_array($analysisRaw) ? json_encode($analysisRaw, JSON_UNESCAPED_UNICODE) : $analysisRaw;
|
|
|
|
|
- if ($analysis === null || $analysis === '') {
|
|
|
|
|
- $analysis = '暂无解题思路,待补充';
|
|
|
|
|
- }
|
|
|
|
|
- if (is_string($analysis)) {
|
|
|
|
|
- $analysis = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $analysis);
|
|
|
|
|
- }
|
|
|
|
|
- $formatSolutionLikeGrading = function ($text) {
|
|
|
|
|
- if (!is_string($text) || trim($text) === '') {
|
|
|
|
|
- return $text;
|
|
|
|
|
- }
|
|
|
|
|
- $normalized = preg_replace('/\s*;\s*步骤\s*(\d+)/u', ";\n步骤$1", $text);
|
|
|
|
|
- $normalized = preg_replace('/\s*。\s*步骤\s*(\d+)/u', "。\n步骤$1", $normalized);
|
|
|
|
|
- $normalized = preg_replace('/(?<!^)(步骤\s*\d+\s*[::])/u', "\n$1", $normalized);
|
|
|
|
|
- $normalized = preg_replace('/(?<!^)(第\s*\d+\s*步\s*[::]?)/u', "\n$1", $normalized);
|
|
|
|
|
- $normalized = preg_replace('/\n{3,}/u', "\n\n", $normalized);
|
|
|
|
|
- $normalized = preg_replace('/^[\h\x{3000}]+/mu', '', $normalized);
|
|
|
|
|
- return trim($normalized);
|
|
|
|
|
- };
|
|
|
|
|
- $stepsRaw = $insight['steps'] ?? $insight['solution_steps'] ?? $insight['analysis_steps'] ?? null;
|
|
|
|
|
- $steps = [];
|
|
|
|
|
- if (is_array($stepsRaw)) {
|
|
|
|
|
- $steps = $stepsRaw;
|
|
|
|
|
- } elseif (is_string($stepsRaw) && trim($stepsRaw) !== '') {
|
|
|
|
|
- $steps = preg_split('/[\r\n]+/', trim($stepsRaw));
|
|
|
|
|
- }
|
|
|
|
|
- $typeMap = ['choice' => '选择题', 'fill' => '填空题', 'answer' => '解答题'];
|
|
|
|
|
- $typeLabel = $typeMap[$q['question_type'] ?? ''] ?? ($q['question_type'] ?? '题型未标注');
|
|
|
|
|
- $questionText = is_string($q['question_text']) ? $q['question_text'] : json_encode($q['question_text'], JSON_UNESCAPED_UNICODE);
|
|
|
|
|
- $solution = $q['solution'] ?? null;
|
|
|
|
|
- if (is_string($solution)) {
|
|
|
|
|
- $solution = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $solution);
|
|
|
|
|
- }
|
|
|
|
|
- $solution = $formatSolutionLikeGrading($solution);
|
|
|
|
|
- $analysis = $formatSolutionLikeGrading($analysis);
|
|
|
|
|
- $renderLikeGrading = function ($text) {
|
|
|
|
|
- if (is_array($text)) {
|
|
|
|
|
- $text = json_encode($text, JSON_UNESCAPED_UNICODE);
|
|
|
|
|
- }
|
|
|
|
|
- $text = is_string($text) ? trim($text) : '';
|
|
|
|
|
- if ($text === '') {
|
|
|
|
|
- return '';
|
|
|
|
|
- }
|
|
|
|
|
- // 兼容题库里常见的转义写法:\$x\$、\$\frac{...}\$
|
|
|
|
|
- $text = preg_replace('/\\\\\\$/u', '$', $text);
|
|
|
|
|
- return \App\Services\MathFormulaProcessor::processFormulas($text);
|
|
|
|
|
- };
|
|
|
|
|
- $questionTextRendered = $renderLikeGrading($questionText);
|
|
|
|
|
- $displayCorrectAnswer = is_array($correctAnswer) ? json_encode($correctAnswer, JSON_UNESCAPED_UNICODE) : (string) $correctAnswer;
|
|
|
|
|
- $questionTypeRaw = strtolower(trim((string) ($q['question_type'] ?? '')));
|
|
|
|
|
- $isChoiceQuestion = in_array($questionTypeRaw, ['choice', 'multiple_choice', 'single_choice', '选择题', 'select'], true);
|
|
|
|
|
- $normalizedOptions = [];
|
|
|
|
|
- $correctAnswerLetters = [];
|
|
|
|
|
- if ($isChoiceQuestion) {
|
|
|
|
|
- $rawOptions = $q['options'] ?? [];
|
|
|
|
|
- if (is_string($rawOptions)) {
|
|
|
|
|
- $decodedOptions = json_decode($rawOptions, true);
|
|
|
|
|
- $rawOptions = is_array($decodedOptions) ? $decodedOptions : [];
|
|
|
|
|
- }
|
|
|
|
|
- if (is_array($rawOptions)) {
|
|
|
|
|
- foreach ($rawOptions as $optKey => $optValue) {
|
|
|
|
|
- $letter = null;
|
|
|
|
|
- if (is_string($optKey) && preg_match('/([A-H])/i', $optKey, $m)) {
|
|
|
|
|
- $letter = strtoupper($m[1]);
|
|
|
|
|
- } elseif (is_array($optValue)) {
|
|
|
|
|
- $candidate = $optValue['label'] ?? $optValue['key'] ?? $optValue['option'] ?? null;
|
|
|
|
|
- if (is_string($candidate) && preg_match('/([A-H])/i', $candidate, $m)) {
|
|
|
|
|
- $letter = strtoupper($m[1]);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if ($letter === null) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- $content = is_array($optValue) ? ($optValue['content'] ?? $optValue['text'] ?? $optValue['value'] ?? '') : $optValue;
|
|
|
|
|
- if (!is_string($content)) {
|
|
|
|
|
- $content = json_encode($content, JSON_UNESCAPED_UNICODE);
|
|
|
|
|
- }
|
|
|
|
|
- $content = trim((string) $content);
|
|
|
|
|
- if ($content !== '') {
|
|
|
|
|
- $normalizedOptions[$letter] = $content;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (trim((string) $correctAnswer) !== '') {
|
|
|
|
|
- preg_match_all('/[A-H]/i', strtoupper((string) $correctAnswer), $answerMatches);
|
|
|
|
|
- $correctAnswerLetters = array_values(array_unique($answerMatches[0] ?? []));
|
|
|
|
|
- }
|
|
|
|
|
- if (!empty($normalizedOptions) && !empty($correctAnswerLetters)) {
|
|
|
|
|
- $mappedAnswers = [];
|
|
|
|
|
- foreach ($correctAnswerLetters as $letter) {
|
|
|
|
|
- if (isset($normalizedOptions[$letter])) {
|
|
|
|
|
- $mappedAnswers[] = $letter . '. ' . $normalizedOptions[$letter];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (!empty($mappedAnswers)) {
|
|
|
|
|
- $displayCorrectAnswer = implode(';', $mappedAnswers);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- $choiceOptionLetters = !empty($normalizedOptions) ? array_keys($normalizedOptions) : [];
|
|
|
|
|
- sort($choiceOptionLetters);
|
|
|
|
|
- $choiceLayoutClass = 'options-grid-1';
|
|
|
|
|
- $layoutDecider = app(\App\Support\OptionLayoutDecider::class);
|
|
|
|
|
- if (! empty($normalizedOptions) && ! empty($choiceOptionLetters)) {
|
|
|
|
|
- $optValuesForLayout = [];
|
|
|
|
|
- foreach ($choiceOptionLetters as $L) {
|
|
|
|
|
- $optValuesForLayout[] = $normalizedOptions[$L];
|
|
|
|
|
- }
|
|
|
|
|
- $layoutMeta = $layoutDecider->decide($optValuesForLayout, 'grading');
|
|
|
|
|
- $choiceLayoutClass = $layoutMeta['class'] ?? 'options-grid-1';
|
|
|
|
|
- }
|
|
|
|
|
- @endphp
|
|
|
|
|
- <div class="question-card">
|
|
|
|
|
- <div style="display:flex; justify-content:space-between; align-items:center; gap:8px; margin-bottom:4px;">
|
|
|
|
|
- <div style="display:flex; align-items:center; gap:8px; font-weight:600;">
|
|
|
|
|
- <span class="tag">题号 {{ $q['display_number'] ?? $q['question_number'] }} · {{ $typeLabel }}</span>
|
|
|
|
|
- @php
|
|
|
|
|
- $kpName = $q['knowledge_point_name'] ?? $q['knowledge_point'] ?? null;
|
|
|
|
|
- if (!empty($kpName) && $kpName !== '-' && $kpName !== '未标注') {
|
|
|
|
|
- echo '<span class="tag" style="background: #eef2ff; color:#4338ca;">' . e($kpName) . '</span>';
|
|
|
|
|
- }
|
|
|
|
|
- @endphp
|
|
|
|
|
- @if($showStatus)
|
|
|
|
|
- <span class="tag" style="background: {{ $statusColorValue }}; color:#fff;">{{ $statusText }}</span>
|
|
|
|
|
- @endif
|
|
|
|
|
- </div>
|
|
|
|
|
- @if($score !== null && $fullScore !== null)
|
|
|
|
|
- <div class="muted">得分 {{ $score }} / {{ $fullScore }}</div>
|
|
|
|
|
- @endif
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="question-stem math-content" style="margin-bottom:6px;">{!! $questionTextRendered !!}</div>
|
|
|
|
|
-
|
|
|
|
|
- @if(!empty($isChoiceQuestion) && !empty($normalizedOptions))
|
|
|
|
|
- <div class="report-options {{ $choiceLayoutClass }}">
|
|
|
|
|
- @foreach($choiceOptionLetters as $optLetter)
|
|
|
|
|
- @php
|
|
|
|
|
- $isCorrectOpt = in_array($optLetter, $correctAnswerLetters ?? [], true);
|
|
|
|
|
- $rawOpt = (string) ($normalizedOptions[$optLetter] ?? '');
|
|
|
|
|
- $normalizedOpt = str_replace('\\dfrac', '\\frac', $rawOpt);
|
|
|
|
|
- $normalizedOpt = str_replace('\\displaystyle', '', $normalizedOpt);
|
|
|
|
|
- $normalizedOpt = $layoutDecider->normalizeCompactMathForDisplay($normalizedOpt);
|
|
|
|
|
- $rawOptPlain = html_entity_decode(strip_tags($rawOpt), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
|
|
|
- $rawOptPlain = preg_replace('/\s+/u', '', $rawOptPlain ?? '');
|
|
|
|
|
- $isShortOption = mb_strlen((string) $rawOptPlain, 'UTF-8') <= 8;
|
|
|
|
|
- $valClass = $isShortOption ? 'option-short' : 'option-long';
|
|
|
|
|
- $renderedOpt = $renderLikeGrading($normalizedOpt);
|
|
|
|
|
- @endphp
|
|
|
|
|
- <div class="option option-compact">
|
|
|
|
|
- <strong>{{ $optLetter }}.</strong>
|
|
|
|
|
- <span class="option-value {{ $valClass }}">{!! $renderedOpt !!}</span>
|
|
|
|
|
- @if($isCorrectOpt)
|
|
|
|
|
- <span style="margin-left:4px; font-size:13px; color:#15803d; font-weight:700;">✅</span>
|
|
|
|
|
- @endif
|
|
|
|
|
- </div>
|
|
|
|
|
- @endforeach
|
|
|
|
|
- </div>
|
|
|
|
|
- @endif
|
|
|
|
|
-
|
|
|
|
|
- @if(!empty($correctAnswer) && (!$isChoiceQuestion || empty($normalizedOptions)))
|
|
|
|
|
- <div class="question-block" style="background:#f0fdf4; border-left:3px solid #10b981;">
|
|
|
|
|
- <div style="font-weight:600; font-size:12px; color:#111827; margin-bottom:3px;">正确答案</div>
|
|
|
|
|
- <div class="math-content" style="line-height:1.7; color:#374151;">
|
|
|
|
|
- {!! $renderLikeGrading($displayCorrectAnswer) !!}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- @endif
|
|
|
|
|
-
|
|
|
|
|
- @if(!empty($solution))
|
|
|
|
|
- <div class="report-answer-meta">
|
|
|
|
|
- <div class="answer-line">
|
|
|
|
|
- <strong>解题思路:</strong>
|
|
|
|
|
- <span class="solution-content">{!! $renderLikeGrading($solution) !!}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- @elseif(!empty($analysis) && $analysis !== '暂无解题思路记录')
|
|
|
|
|
- <div class="report-answer-meta">
|
|
|
|
|
- <div class="answer-line">
|
|
|
|
|
- <strong>解题思路:</strong>
|
|
|
|
|
- <span class="solution-content">{!! $renderLikeGrading($analysis) !!}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- @endif
|
|
|
|
|
-
|
|
|
|
|
- @if(!empty($steps))
|
|
|
|
|
- <div style="margin-top:6px; font-size:12px;">
|
|
|
|
|
- <div style="font-weight:600; margin-bottom:3px;">解题步骤</div>
|
|
|
|
|
- <ol style="margin:0; padding-left:18px;">
|
|
|
|
|
- @foreach($steps as $s)
|
|
|
|
|
- @php
|
|
|
|
|
- $stepText = is_array($s) ? json_encode($s, JSON_UNESCAPED_UNICODE) : (string) $s;
|
|
|
|
|
- @endphp
|
|
|
|
|
- <li style="margin-bottom:2px;">{!! nl2br($renderLikeGrading($stepText)) !!}</li>
|
|
|
|
|
- @endforeach
|
|
|
|
|
- </ol>
|
|
|
|
|
- </div>
|
|
|
|
|
- @endif
|
|
|
|
|
- </div>
|
|
|
|
|
- @endforeach
|
|
|
|
|
- </div>
|
|
|
|
|
- @endif
|
|
|
|
|
</div>
|
|
</div>
|
|
|
<script src="/js/katex.min.js"></script>
|
|
<script src="/js/katex.min.js"></script>
|
|
|
<script src="/js/auto-render.min.js"></script>
|
|
<script src="/js/auto-render.min.js"></script>
|