@php $v3 = $v3 ?? []; $summary = $v3['summary'] ?? []; $radar = $v3['radar'] ?? []; $modules = $v3['modules'] ?? []; $paths = $v3['paths'] ?? ['keep' => [], 'boost' => [], 'key' => []]; $rawPaperId = $paper['id'] ?? $paper['paper_id'] ?? 'unknown'; preg_match('/paper_(\d{15})/', $rawPaperId, $matches); $reportCode = $matches[1] ?? preg_replace('/[^0-9]/', '', (string) $rawPaperId); $generateDateTime = now()->format('Y年m月d日 H:i:s'); $scoreObtained = $summary['score_obtained'] ?? null; $scoreTotal = $summary['score_total'] ?? null; $scoreRate = $summary['score_rate'] ?? null; $averageMastery = $summary['average_mastery'] ?? null; $examHitKpSet = array_fill_keys(array_map('strval', $exam_hit_kp_codes ?? []), true); $difficultySummary = $summary['difficulty'] ?? []; $comparisonSummary = $summary['comparison'] ?? []; $overallLabelDetail = $summary['overall_label_detail'] ?? []; $historySummary = $comparisonSummary['history'] ?? []; $peerSummary = $comparisonSummary['peers'] ?? []; $overallScore = isset($overallLabelDetail['composite_score']) ? (float) $overallLabelDetail['composite_score'] : null; $overallGrade = (string) ($overallLabelDetail['grade'] ?? 'D'); $currentPart = (float) ($overallLabelDetail['current_score'] ?? 0); $historyPart = (float) ($overallLabelDetail['history_score'] ?? 0); $peerPart = (float) ($overallLabelDetail['peer_score'] ?? 0); $adjustPart = (float) ($overallLabelDetail['difficulty_adjust'] ?? 0); $compositeFormulaResult = (0.50 * $currentPart) + (0.25 * $historyPart) + (0.25 * $peerPart) + $adjustPart; $overallBadge = function (string $grade): array { return match ($grade) { 'S' => ['bg' => '#f5f3ff', 'border' => '#6d28d9', 'text' => '#6d28d9', 'class' => 'badge-s'], 'A' => ['bg' => '#ecfdf3', 'border' => '#22c55e', 'text' => '#166534', 'class' => 'badge-excellent'], 'B' => ['bg' => '#eff6ff', 'border' => '#3b82f6', 'text' => '#1d4ed8', 'class' => 'badge-good'], 'C' => ['bg' => '#fff7ed', 'border' => '#f59e0b', 'text' => '#b45309', 'class' => 'badge-average'], default => ['bg' => '#fef2f2', 'border' => '#ef4444', 'text' => '#b91c1c', 'class' => 'badge-weak'], }; }; $overallVisual = $overallBadge((string) $overallGrade); $trendVisual = function (string $trend): array { return match ($trend) { '显著提升' => ['icon' => '▲', 'color' => '#16a34a'], '小幅提升' => ['icon' => '↗', 'color' => '#0ea5e9'], '基本持平' => ['icon' => '•', 'color' => '#64748b'], '小幅回落' => ['icon' => '↘', 'color' => '#f59e0b'], '明显回落' => ['icon' => '▼', 'color' => '#ef4444'], default => ['icon' => '•', 'color' => '#64748b'], }; }; $statusColor = function (string $status): string { return match ($status) { '已掌握' => '#16a34a', '薄弱' => '#f59e0b', '未入门' => '#ef4444', default => '#64748b', }; }; $analysisWrongMap = []; foreach (($analysis_data['question_analysis'] ?? []) as $qa) { $qid = $qa['question_bank_id'] ?? $qa['question_id'] ?? null; if ($qid === null || $qid === '') { continue; } $rawCorrect = $qa['is_correct'] ?? null; $isWrongFromAnalysis = false; if (is_array($rawCorrect)) { $isWrongFromAnalysis = in_array(0, $rawCorrect, true); } elseif ($rawCorrect !== null) { $isWrongFromAnalysis = !boolval($rawCorrect); } if ($isWrongFromAnalysis) { $analysisWrongMap[(string) $qid] = true; } } $wrongQuestions = []; foreach (($questions ?? []) as $qItem) { $isCorrectProbe = $qItem['is_correct'] ?? null; $studentAnswerProbe = $qItem['student_answer'] ?? null; $correctAnswerProbe = $qItem['answer'] ?? ($qItem['correct_answer'] ?? null); if ($isCorrectProbe === null && !empty($studentAnswerProbe) && !empty($correctAnswerProbe)) { $isCorrectProbe = (trim((string) $studentAnswerProbe) === trim((string) $correctAnswerProbe)) ? 1 : 0; } $normalizedCorrect = $isCorrectProbe; if ($isCorrectProbe !== null) { $normalizedCorrect = is_bool($isCorrectProbe) ? ($isCorrectProbe ? 1 : 0) : intval($isCorrectProbe); } $qidProbe = (string) ($qItem['question_bank_id'] ?? $qItem['question_id'] ?? ''); $isWrongByAnalysis = ($qidProbe !== '' && isset($analysisWrongMap[$qidProbe])); if ($normalizedCorrect === 0 || $isWrongByAnalysis) { $wrongQuestions[] = $qItem; } } $kpStats = []; foreach (($questions ?? []) as $qItem) { $kpName = trim((string) ($qItem['knowledge_point_name'] ?? $qItem['knowledge_point'] ?? '未标注知识点')); $kpName = $kpName === '' ? '未标注知识点' : $kpName; if (!isset($kpStats[$kpName])) { $kpStats[$kpName] = ['total' => 0, 'wrong' => 0]; } $kpStats[$kpName]['total']++; } foreach ($wrongQuestions as $qItem) { $kpName = trim((string) ($qItem['knowledge_point_name'] ?? $qItem['knowledge_point'] ?? '未标注知识点')); $kpName = $kpName === '' ? '未标注知识点' : $kpName; if (!isset($kpStats[$kpName])) { $kpStats[$kpName] = ['total' => 0, 'wrong' => 0]; } $kpStats[$kpName]['wrong']++; } $kpWrongStats = []; foreach ($kpStats as $kpName => $stat) { if (($stat['wrong'] ?? 0) <= 0) { continue; } $total = max(1, intval($stat['total'] ?? 0)); $wrong = intval($stat['wrong'] ?? 0); $kpWrongStats[] = [ 'kp_name' => $kpName, 'wrong' => $wrong, 'total' => $total, 'rate' => $wrong / $total, ]; } usort($kpWrongStats, function ($a, $b) { if ($a['rate'] === $b['rate']) { return $b['wrong'] <=> $a['wrong']; } return $b['rate'] <=> $a['rate']; }); $pcMasteryPercent = function ($mastery): ?int { if ($mastery === null) { return null; } // 与 PC 保持一致:API 先保留 2 位小数,前端再 Math.round 到 0-100。 return (int) round(round((float) $mastery, 2) * 100); }; $formatMasteryPct = function ($mastery) use ($pcMasteryPercent): string { $percent = $pcMasteryPercent($mastery); return $percent === null ? '-' : ($percent . '%'); }; $childMasteryStatus = function ($mastery) use ($pcMasteryPercent): string { $m = $pcMasteryPercent($mastery); if ($m === null) { return '未学习'; } if ($m >= 85) { return '已掌握'; } if ($m >= 60) { return '薄弱'; } return '未入门'; }; $childStatusColor = function ($status): string { return match ($status) { '已掌握' => '#52c41a', '薄弱' => '#faad14', '未入门' => '#f5222d', default => '#d9d9d9', }; }; $calcStats = function (array $points): array { $total = count($points); $learned = 0; $mastered = 0; $weak = 0; $beginner = 0; $unlearned = 0; $hit = 0; foreach ($points as $p) { if (($p['mastery_level'] ?? null) !== null) { $learned++; } if (! empty($p['is_hit'])) { $hit++; } $status = (string) ($p['status'] ?? '未学习'); if ($status === '已掌握') { $mastered++; } elseif ($status === '薄弱') { $weak++; } elseif ($status === '未入门') { $beginner++; } else { $unlearned++; } } return [ 'total' => $total, 'learned' => $learned, 'mastered' => $mastered, 'weak' => $weak, 'beginner' => $beginner, 'unlearned' => $unlearned, 'hit' => $hit, ]; }; $clusterCards = []; $allStagePoints = []; foreach ($radar as $moduleItem) { $children = is_array($moduleItem['children'] ?? null) ? $moduleItem['children'] : []; $greatMap = []; foreach ($children as $child) { $greatKey = trim((string) ($child['great_grand_parent_name'] ?? '')); $greatKey = $greatKey !== '' ? $greatKey : '未分组'; $grandKey = trim((string) ($child['grand_parent_name'] ?? '')); $grandKey = $grandKey !== '' ? $grandKey : '未分组'; $parentName = trim((string) ($child['parent_name'] ?? '')); if ($parentName === '') { $parentCode = trim((string) ($child['parent_code'] ?? '')); $parentName = $parentCode !== '' ? $parentCode : '未分组'; } $childMasteryLevel = isset($child['mastery_level']) ? (float) $child['mastery_level'] : null; $status = $childMasteryStatus($childMasteryLevel); if (!isset($greatMap[$greatKey])) { $greatMap[$greatKey] = []; } if (!isset($greatMap[$greatKey][$grandKey])) { $greatMap[$greatKey][$grandKey] = []; } if (!isset($greatMap[$greatKey][$grandKey][$parentName])) { $greatMap[$greatKey][$grandKey][$parentName] = []; } $childCode = (string) ($child['code'] ?? ''); $childParentCode = (string) ($child['parent_code'] ?? ''); $isHit = !empty($child['is_hit']) || ($childCode !== '' && isset($examHitKpSet[$childCode])) || ($childParentCode !== '' && isset($examHitKpSet[$childParentCode])); $greatMap[$greatKey][$grandKey][$parentName][] = [ 'code' => $childCode, 'name' => (string) ($child['name'] ?? '未命名知识点'), 'parent_code' => $childParentCode, 'path' => (string) ($child['path'] ?? ''), 'mastery_level' => $childMasteryLevel, 'change' => isset($child['change']) ? (float) $child['change'] : null, 'status' => $status, 'color' => $childStatusColor($status), 'is_hit' => $isHit, ]; $allStagePoints[] = [ 'code' => $childCode, 'name' => (string) ($child['name'] ?? '未命名知识点'), 'parent_code' => $childParentCode, 'mastery_level' => $childMasteryLevel, 'status' => $status, 'change' => isset($child['change']) ? (float) $child['change'] : null, 'is_hit' => $isHit, ]; } $greatGroups = []; foreach ($greatMap as $greatName => $grandMap) { $grandGroups = []; foreach ($grandMap as $grandName => $parentMap) { $parentGroups = []; foreach ($parentMap as $parentName => $points) { usort($points, function ($a, $b) { $am = $a['mastery_level'] ?? -1; $bm = $b['mastery_level'] ?? -1; if ($am === $bm) { return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? '')); } return $bm <=> $am; }); $parentGroups[] = [ 'parent_name' => $parentName, 'points' => $points, 'stats' => $calcStats($points), ]; } // 子模块级过滤:整行没有任何掌握度数字则不显示 $parentGroups = array_values(array_filter($parentGroups, function ($pg) { return (($pg['stats']['learned'] ?? 0) > 0) || (($pg['stats']['hit'] ?? 0) > 0); })); if (empty($parentGroups)) { continue; } usort($parentGroups, function ($a, $b) { $sa = $a['stats']; $sb = $b['stats']; return ($sb['learned'] <=> $sa['learned']) ?: ($sb['total'] <=> $sa['total']); }); $allGrandPoints = []; foreach ($parentGroups as $pg) { $allGrandPoints = array_merge($allGrandPoints, $pg['points']); } $grandGroups[] = [ 'grand_name' => $grandName, 'parent_groups' => $parentGroups, 'stats' => $calcStats($allGrandPoints), ]; } // 大块级过滤:整块没有任何掌握度数字则不显示 $grandGroups = array_values(array_filter($grandGroups, function ($gg) { return (($gg['stats']['learned'] ?? 0) > 0) || (($gg['stats']['hit'] ?? 0) > 0); })); if (empty($grandGroups)) { continue; } usort($grandGroups, function ($a, $b) { $sa = $a['stats']; $sb = $b['stats']; return ($sb['learned'] <=> $sa['learned']) ?: ($sb['total'] <=> $sa['total']); }); $allGreatPoints = []; foreach ($grandGroups as $gg) { foreach ($gg['parent_groups'] as $pg) { $allGreatPoints = array_merge($allGreatPoints, $pg['points']); } } $greatGroups[] = [ 'great_name' => $greatName, 'grand_groups' => $grandGroups, 'stats' => $calcStats($allGreatPoints), ]; } usort($greatGroups, function ($a, $b) { $sa = $a['stats']; $sb = $b['stats']; return ($sb['learned'] <=> $sa['learned']) ?: ($sb['total'] <=> $sa['total']); }); // 严格参考 math.client-pc:扁平化为“grand 层卡片”(展示大块) foreach ($greatGroups as $great) { foreach (($great['grand_groups'] ?? []) as $grand) { $gStats = $grand['stats'] ?? ['learned' => 0, 'total' => 0]; $clusterCards[] = [ 'module_name' => (string) ($moduleItem['name'] ?? '未分组'), 'great_name' => $great['great_name'] ?? '未分组', 'grand_name' => $grand['grand_name'] ?? '未分组', 'parent_groups' => $grand['parent_groups'] ?? [], 'stats' => $gStats, ]; } } } usort($clusterCards, function ($a, $b) { $sa = $a['stats'] ?? ['learned' => 0, 'total' => 0]; $sb = $b['stats'] ?? ['learned' => 0, 'total' => 0]; return (($sb['learned'] ?? 0) <=> ($sa['learned'] ?? 0)) ?: (($sb['total'] ?? 0) <=> ($sa['total'] ?? 0)); }); $kpStatsTotal = [ 'total' => count($allStagePoints), 'mastered' => 0, 'weak' => 0, 'beginner' => 0, 'unlearned' => 0, ]; foreach ($allStagePoints as $p) { $st = (string) ($p['status'] ?? '未学习'); if ($st === '已掌握') { $kpStatsTotal['mastered']++; } elseif ($st === '薄弱') { $kpStatsTotal['weak']++; } elseif ($st === '未入门') { $kpStatsTotal['beginner']++; } else { $kpStatsTotal['unlearned']++; } } $moduleRowsWithStatus = array_values(array_filter($modules, function ($m) { $status = trim((string) ($m['status'] ?? '')); $masteryLevel = $m['mastery_level'] ?? null; $questionCount = (int) ($m['question_count'] ?? 0); if ($masteryLevel !== null) { return true; } return $questionCount > 0 && $status !== '' && ! in_array($status, ['暂无', '-', '未涉及', '未学习'], true); })); $pathTagByModuleName = []; foreach (['keep' => '保分不错', 'boost' => '需要加强', 'key' => '优先加强'] as $bucket => $tagName) { foreach (($paths[$bucket] ?? []) as $item) { $n = trim((string) ($item['name'] ?? '')); if ($n === '') { continue; } $pathTagByModuleName[$n] = $tagName; } } $globalPathTagByMastery = function ($mastery) use ($pcMasteryPercent): string { if ($mastery === null || ! is_numeric($mastery)) { return '待观察'; } $m = $pcMasteryPercent($mastery); if ($m >= 85) { return '保分不错'; } if ($m >= 60) { return '需要加强'; } return '优先加强'; }; $overallPathTag = function (string $tag): string { return match ($tag) { '优先加强' => '整体优先', '需要加强' => '整体加强', '保分不错' => '整体巩固', default => $tag, }; }; $impactedModules = array_values(array_filter($moduleRowsWithStatus, function ($m) { return ((int) ($m['question_count'] ?? 0)) > 0; })); $radarModuleMap = []; foreach ($radar as $moduleItem) { $code = (string) ($moduleItem['code'] ?? ''); if ($code !== '') { $radarModuleMap[$code] = $moduleItem; } } $moduleKpSuggestions = []; foreach ($moduleRowsWithStatus as $m) { $moduleCode = (string) ($m['module_code'] ?? ''); $moduleName = (string) ($m['module_name'] ?? '-'); $moduleChildren = $radarModuleMap[$moduleCode]['children'] ?? []; if (! is_array($moduleChildren) || empty($moduleChildren)) { continue; } $moduleHitCandidates = []; foreach (($mastery['items'] ?? []) as $item) { $hitCode = trim((string) ($item['kp_code'] ?? $item['code'] ?? '')); if ($hitCode === '') { continue; } if (! empty($examHitKpSet) && ! isset($examHitKpSet[$hitCode])) { continue; } $hitLevel = $item['mastery_level'] ?? null; if ($hitLevel === null || ! is_numeric($hitLevel)) { continue; } $matchedChild = null; foreach ($moduleChildren as $child) { $childCode = trim((string) ($child['code'] ?? '')); $parentCode = trim((string) ($child['parent_code'] ?? '')); if ($childCode === $hitCode || $parentCode === $hitCode) { $matchedChild = $child; break; } } if (! is_array($matchedChild)) { continue; } $moduleHitCandidates[$hitCode] = [ 'code' => $hitCode, 'name' => (string) ($item['kp_name'] ?? $item['name'] ?? ($matchedChild['parent_name'] ?? $matchedChild['name'] ?? $hitCode)), 'parent_code' => (string) ($matchedChild['parent_code'] ?? ''), 'parent_name' => (string) ($matchedChild['parent_name'] ?? ''), 'grand_parent_name' => (string) ($matchedChild['grand_parent_name'] ?? ''), 'mastery_level' => (float) $hitLevel, 'is_hit' => true, ]; } $startedByCode = $moduleHitCandidates; foreach (array_values(array_filter($moduleChildren, function ($c) { return isset($c['mastery_level']) && $c['mastery_level'] !== null; })) as $child) { $childCode = trim((string) ($child['code'] ?? '')); if ($childCode !== '' && ! isset($startedByCode[$childCode])) { $startedByCode[$childCode] = $child; } } $started = array_values($startedByCode); usort($started, function ($a, $b) { $am = (float) ($a['mastery_level'] ?? 0); $bm = (float) ($b['mastery_level'] ?? 0); if ($am === $bm) { $ah = !empty($a['is_hit']) ? 0 : 1; $bh = !empty($b['is_hit']) ? 0 : 1; if ($ah !== $bh) { return $ah <=> $bh; } return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? '')); } return $am <=> $bm; }); $weakest = null; if (! empty($started)) { $lowestStarted = $started[0]; $lowestStartedLevel = isset($lowestStarted['mastery_level']) ? (float) $lowestStarted['mastery_level'] : null; if ($lowestStartedLevel !== null && ($pcMasteryPercent($lowestStartedLevel) ?? 0) < 85) { // 规则1:已开始学习中掌握度最低 $weakest = $lowestStarted; } else { // 规则2:若已开始学习均达标(>=85%),取“最近的未学习” $unlearned = array_values(array_filter($moduleChildren, function ($c) { return !isset($c['mastery_level']) || $c['mastery_level'] === null; })); if (! empty($unlearned)) { $anchorParent = (string) ($lowestStarted['parent_name'] ?? ''); $anchorGrand = (string) ($lowestStarted['grand_parent_name'] ?? ''); usort($unlearned, function ($a, $b) use ($anchorParent, $anchorGrand) { $score = function ($node) use ($anchorParent, $anchorGrand) { $parent = (string) ($node['parent_name'] ?? ''); $grand = (string) ($node['grand_parent_name'] ?? ''); if ($anchorParent !== '' && $parent === $anchorParent) { return 0; } if ($anchorGrand !== '' && $grand === $anchorGrand) { return 1; } return 2; }; $sa = $score($a); $sb = $score($b); if ($sa === $sb) { return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? '')); } return $sa <=> $sb; }); $weakest = $unlearned[0]; } } } else { // 没有已开始学习数据时,回退到模块内任一未学习点 $unlearned = array_values(array_filter($moduleChildren, function ($c) { return !isset($c['mastery_level']) || $c['mastery_level'] === null; })); if (! empty($unlearned)) { usort($unlearned, fn ($a, $b) => strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''))); $weakest = $unlearned[0]; } } if (! is_array($weakest)) { $moduleKpSuggestions[] = [ 'module_name' => $moduleName, 'path_tag' => $pathTagByModuleName[$moduleName] ?? '待观察', 'kp_name' => '', 'kp_code' => '', 'mastery_level' => null, 'status' => '当前模块暂无需额外关注知识点', 'is_empty' => true, ]; continue; } $kpName = (string) ($weakest['name'] ?? ''); if ($kpName === '') { continue; } $kpCode = (string) ($weakest['code'] ?? ''); $moduleKpSuggestions[] = [ 'module_name' => $moduleName, 'path_tag' => $pathTagByModuleName[$moduleName] ?? '待观察', 'kp_name' => $kpName, 'kp_code' => $kpCode, 'mastery_level' => $weakest['mastery_level'] ?? null, 'status' => $childMasteryStatus($weakest['mastery_level'] ?? null), 'is_empty' => false, ]; } $moduleSuggestionByName = []; foreach ($moduleKpSuggestions as $sug) { $name = trim((string) ($sug['module_name'] ?? '')); if ($name !== '') { $moduleSuggestionByName[$name] = $sug; } } $focusMarkerByCode = []; foreach ($moduleKpSuggestions as $sug) { if (! empty($sug['is_empty'])) { continue; } $code = trim((string) ($sug['kp_code'] ?? '')); $name = trim((string) ($sug['kp_name'] ?? '')); if ($code === '' || $name === '') { continue; } $focusMarkerByCode[$code] = [ 'name' => $name, 'module_name' => (string) ($sug['module_name'] ?? ''), 'mastery_level' => $sug['mastery_level'] ?? null, ]; } $renderedFocusMarkerCodes = []; $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) { return abs((float) ($b['change'] ?? 0)) <=> abs((float) ($a['change'] ?? 0)); }); $kpPct = function (int $count, int $total): string { if ($total <= 0) { return '0.0%'; } return number_format(($count * 100.0) / $total, 1) . '%'; }; $changeText = function ($change): string { if ($change === null || ! is_numeric($change)) { return ''; } $delta = (float) $change; $points = number_format(abs($delta) * 100, 1); if ($delta > 0.0005) { return '较上次提升' . $points . '个百分点'; } if ($delta < -0.0005) { return '较上次下降' . $points . '个百分点'; } return '较上次基本持平'; }; @endphp 学情分析报告

学情分析报告

一、总体评估
{{ $overallGrade }}
  • 本次诊断得分: @if($scoreObtained !== null && $scoreTotal !== null && $scoreTotal > 0) {{ rtrim(rtrim(number_format((float) $scoreObtained, 1), '0'), '.') }}/{{ rtrim(rtrim(number_format((float) $scoreTotal, 1), '0'), '.') }} @else 暂无得分数据 @endif
  • 平均掌握度:{{ $averageMastery !== null ? number_format((float) $averageMastery * 100, 1) . '%' : '暂无掌握度' }}
  • 难度匹配: @if(!empty($difficultySummary['target_label']) && isset($difficultySummary['actual_average_difficulty'])) 目标 {{ $difficultySummary['target_label'] }} @if(!empty($difficultySummary['target_range'])) ({{ number_format((float)($difficultySummary['target_range']['min'] ?? 0), 2) }}~{{ number_format((float)($difficultySummary['target_range']['max'] ?? 0), 2) }}) @endif ,实际 {{ number_format((float)($difficultySummary['actual_average_difficulty'] ?? 0), 3) }} ({{ $difficultySummary['status'] ?? '暂无' }}) @else 暂无难度匹配数据 @endif
  • @if(!empty($difficultySummary['explain']))
  • 难度说明:{{ $difficultySummary['explain'] }}
  • @endif
  • 与历史自己对比: @if(!empty($historySummary['is_first_exam'])) {{ $historySummary['message'] ?? '这是你的第一次分析报告,先积累样本再看趋势。' }} @elseif(!empty($historySummary['low_baseline_guard'])) {{ $historySummary['message'] ?? '历史基线偏低,建议看连续趋势。' }} @elseif(!empty($historySummary['has_data'])) @php $trendText = (string)($historySummary['trend'] ?? '—'); $tVisual = $trendVisual($trendText); @endphp 近几次均值对比: {{ number_format((float)($historySummary['baseline_score_rate'] ?? 0) * 100, 1) }}%, 本次{{ ($historySummary['delta_score_rate'] ?? 0) >= 0 ? '提升' : '回落' }} {{ number_format(abs((float)($historySummary['delta_score_rate'] ?? 0)) * 100, 1) }}% ({{ $tVisual['icon'] ?? '•' }} {{ $trendText }}) @else {{ $historySummary['message'] ?? '历史样本不足' }} @endif
  • @if(!empty($peerSummary['show_line']))
  • 与同群体对比: {{ $peerSummary['message'] ?? '' }} ({{ $peerSummary['band_icon'] ?? '•' }} {{ $peerSummary['band'] ?? '—' }}
  • @endif
  • 整体水平: @if($overallScore !== null) {{ number_format($overallScore, 1) }} 分({{ $overallGrade }}) @else 待计算 @endif
规则:综合分 = 当前50% + 历史25% + 同群体25% + 难度校正,即:(({{ number_format($scoreRate !== null ? (float)$scoreRate * 100 : 0, 1) }}×70% + {{ number_format($averageMastery !== null ? (float)$averageMastery * 100 : 0, 1) }}×30%)×50%) + {{ number_format($historyPart, 1) }}×25% + {{ number_format($peerPart, 1) }}×25% + {{ number_format($adjustPart, 1) }} = {{ number_format($overallScore ?? $compositeFormulaResult, 1) }}
二、知识点掌握聚类视图
已掌握(深色实心) 薄弱(浅灰实心) 未入门(浅灰虚线框) 未学习(白色) 按“模块 → 子模块 → 知识点”聚类展示
@foreach($clusterCards as $cluster)
@php $clusterModuleName = trim((string) ($cluster['module_name'] ?? '未分组')); $clusterGrandName = trim((string) ($cluster['grand_name'] ?? '')); $clusterTitle = ($clusterGrandName !== '' && $clusterGrandName !== $clusterModuleName) ? ($clusterModuleName . ' / ' . $clusterGrandName) : $clusterModuleName; @endphp
{{ $clusterTitle }}
@if(!empty($cluster['parent_groups'])) @foreach($cluster['parent_groups'] as $parent)
{{ $parent['parent_name'] }}
@foreach($parent['points'] as $point) @php $pointCode = trim((string) ($point['code'] ?? '')); $pointParentCode = trim((string) ($point['parent_code'] ?? '')); $focusMarker = null; $focusMarkerCode = ''; if ($pointCode !== '' && isset($focusMarkerByCode[$pointCode]) && empty($renderedFocusMarkerCodes[$pointCode])) { $focusMarker = $focusMarkerByCode[$pointCode]; $focusMarkerCode = $pointCode; } elseif ($pointParentCode !== '' && isset($focusMarkerByCode[$pointParentCode]) && empty($renderedFocusMarkerCodes[$pointParentCode])) { $focusMarker = $focusMarkerByCode[$pointParentCode]; $focusMarkerCode = $pointParentCode; } if ($focusMarkerCode !== '') { $renderedFocusMarkerCodes[$focusMarkerCode] = true; } $focusName = is_array($focusMarker) ? (string) ($focusMarker['name'] ?? '') : ''; $pointStatusClass = match ((string) ($point['status'] ?? '')) { '已掌握' => 'status-mastered', '薄弱' => 'status-weak', '未入门' => 'status-beginner', default => 'status-unlearned', }; $focusLayoutClass = ''; if ($focusName === '幂与指数') { $focusLayoutClass = 'dense'; } elseif (str_contains($clusterModuleName, '图形变化') || str_contains($clusterModuleName, '图形度量')) { $focusLayoutClass = 'bottom'; } @endphp @if($focusName !== '') @php $focusOffsetClass = match ($loop->index % 3) { 1 => 'focus-offset-b', 2 => 'focus-offset-c', default => 'focus-offset-a', }; @endphp {{ $focusName }} @endif @endforeach
@endforeach @else
当前模块暂无可展示的子知识点。
@endif
@endforeach
总知识点数
{{ $kpStatsTotal['total'] }}
已掌握
{{ $kpStatsTotal['mastered'] }}({{ $kpPct($kpStatsTotal['mastered'], $kpStatsTotal['total']) }})
薄弱
{{ $kpStatsTotal['weak'] }}({{ $kpPct($kpStatsTotal['weak'], $kpStatsTotal['total']) }})
未入门
{{ $kpStatsTotal['beginner'] }}({{ $kpPct($kpStatsTotal['beginner'], $kpStatsTotal['total']) }})
未学习
{{ $kpStatsTotal['unlearned'] }}({{ $kpPct($kpStatsTotal['unlearned'], $kpStatsTotal['total']) }})
三、模块现状与提分路径(全局+本学案影响)
本学案知识点变化情况
@if(!empty($kpChangeItems))
    @foreach($kpChangeItems as $item) @php $delta = (float) ($item['change'] ?? 0); $deltaColor = $delta > 0 ? '#16a34a' : ($delta < 0 ? '#dc2626' : '#64748b'); $deltaText = $changeText($delta); $masteryText = isset($item['mastery_level']) && $item['mastery_level'] !== null ? $formatMasteryPct($item['mastery_level']) : '--'; @endphp
  • {{ $item['name'] ?? '-' }}: 当前掌握度{{ $masteryText }}({{ $item['status'] ?? '未学习' }}) @if($deltaText !== '') ,{{ $deltaText }} @endif
  • @endforeach
@else
@if(!empty($kpWrongStats)) 暂无本学案命中知识点的掌握度数据,以下方知识点错误率作为本学案影响依据。 @else 暂无可用的知识点变化数据 @endif
@endif
@if(!empty($kpWrongStats))
知识点错误率
@foreach($kpWrongStats as $item) {{ $item['kp_name'] }}:{{ $item['wrong'] }}/{{ $item['total'] }}({{ number_format($item['rate'] * 100, 1) }}%) @endforeach
@endif
本次学案影响模块: @if(!empty($impactedModules)) @foreach($impactedModules as $idx => $im) @php $mName = $im['module_name'] ?? '-'; $mQuestionCount = (int) ($im['question_count'] ?? 0); $mScore = $im['exam_obtained_score'] ?? null; @endphp {{ $mName }}({{ $mQuestionCount }}题,得分{{ $mScore !== null ? number_format((float) $mScore, 1) : '-' }}) @endforeach @else 暂无命中模块 @endif
@forelse($moduleRowsWithStatus as $m) @php $status = (string) ($m['status'] ?? '暂无'); $color = $statusColor($status); $qCount = (int) ($m['question_count'] ?? 0); $isImpacted = $qCount > 0; $basePathTag = $pathTagByModuleName[(string) ($m['module_name'] ?? '')] ?? $globalPathTagByMastery($m['mastery_level'] ?? null); $pathTag = $isImpacted ? $basePathTag : $overallPathTag($basePathTag); $pathColor = match ($pathTag) { '优先加强', '整体优先' => '#ef4444', '需要加强', '整体加强' => '#f59e0b', '保分不错', '整体巩固' => '#16a34a', default => '#64748b', }; $moduleName = (string) ($m['module_name'] ?? ''); $focus = $moduleSuggestionByName[$moduleName] ?? null; $focusText = '-'; if (is_array($focus)) { if (!empty($focus['is_empty'])) { $focusText = (string) ($focus['status'] ?? '当前模块暂无需额外关注知识点'); } else { $focusName = (string) ($focus['kp_name'] ?? ''); $focusMastery = isset($focus['mastery_level']) && $focus['mastery_level'] !== null ? $formatMasteryPct($focus['mastery_level']) : '--'; $focusText = $focusName !== '' ? ($focusName . '(' . $focusMastery . ')') : '当前模块暂无需额外关注知识点'; } } @endphp @empty @endforelse
模块 本次影响 当前掌握度 掌握状态 路径建议 关注知识点
{{ $m['module_name'] ?? '-' }} @if($isImpacted) @else @endif {{ isset($m['mastery_level']) && $m['mastery_level'] !== null ? $formatMasteryPct($m['mastery_level']) : '-' }} {{ $status }} {{ $pathTag }} {{ $focusText }}
暂无掌握状态数据