pdf-report.blade.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>学情报告 - {{ $paper['name'] ?? '试卷' }}</title>
  6. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
  7. <style>
  8. * { box-sizing: border-box; }
  9. body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; margin: 24px; color: #1f2937; background: #f9fafb; }
  10. h1, h2, h3 { margin: 0; color: #111827; }
  11. .card { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px 18px; margin-bottom: 16px; box-shadow: 0 6px 20px rgba(15, 23, 42, 0.06); }
  12. .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
  13. .tag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 12px; color: #374151; background: #e5e7eb; }
  14. .section-title { font-size: 16px; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
  15. .pill { padding: 4px 10px; border-radius: 999px; font-size: 12px; }
  16. .pill.green { background: #ecfdf3; color: #15803d; }
  17. .pill.amber { background: #fef3c7; color: #b45309; }
  18. .pill.red { background: #fef2f2; color: #b91c1c; }
  19. table { width: 100%; border-collapse: collapse; font-size: 13px; }
  20. th, td { padding: 8px 10px; border-bottom: 1px solid #e5e7eb; text-align: left; vertical-align: top; }
  21. th { background: #f3f4f6; color: #111827; }
  22. .muted { color: #6b7280; font-size: 12px; }
  23. .progress-wrap { background: #f3f4f6; border-radius: 999px; overflow: hidden; height: 10px; }
  24. .progress-bar { height: 100%; background: linear-gradient(90deg, #4f46e5, #10b981); }
  25. .recommend-card { border: 1px dashed #cbd5e1; border-radius: 10px; padding: 10px 12px; margin-bottom: 8px; background: #f8fafc; }
  26. </style>
  27. </head>
  28. <body>
  29. <div class="card" style="display:flex; justify-content:space-between; align-items:flex-start; gap:16px;">
  30. <div>
  31. <h1>学情报告</h1>
  32. <div class="muted" style="margin-top:6px;">卷子:{{ $paper['name'] ?? '-' }} | 学生:{{ $student['name'] ?? '-' }}</div>
  33. <div class="muted">年级:{{ $student['grade'] ?? '-' }} | 班级:{{ $student['class'] ?? '-' }}</div>
  34. </div>
  35. <div style="text-align:right;">
  36. <div class="pill {{ ($mastery['average'] ?? 0) >= 0.7 ? 'green' : (($mastery['average'] ?? 0) >= 0.5 ? 'amber' : 'red') }}">
  37. 平均掌握度 {{ isset($mastery['average']) ? number_format($mastery['average'] * 100, 1) . '%' : '无数据' }}
  38. </div>
  39. <div class="muted" style="margin-top:6px;">题目数:{{ is_array($questions ?? null) ? count($questions) : ($paper['total_questions'] ?? '-') }}</div>
  40. </div>
  41. </div>
  42. <div class="card">
  43. <div class="section-title">知识点掌握度</div>
  44. @if(!empty($mastery['items']))
  45. @foreach($mastery['items'] as $item)
  46. @php
  47. $pct = min(100, max(0, $item['mastery_level'] * 100));
  48. $barColor = $pct >= 80 ? '#10b981' : ($pct >= 60 ? '#f59e0b' : '#ef4444');
  49. $delta = $item['mastery_change'] ?? null;
  50. @endphp
  51. <div style="margin-bottom:10px;">
  52. <div style="display:flex; justify-content:space-between; align-items:center;">
  53. <div><strong>{{ $item['kp_name'] }}</strong></div>
  54. <div>
  55. {{ number_format($pct, 1) }}%
  56. @if($delta !== null)
  57. <span class="muted" style="margin-left:6px;">{{ $delta > 0 ? '↑' : ($delta < 0 ? '↓' : '→') }} {{ number_format(abs($delta) * 100, 1) }}%</span>
  58. @endif
  59. </div>
  60. </div>
  61. <div class="progress-wrap">
  62. <div class="progress-bar" style="width: {{ $pct }}%; background: {{ $barColor }};"></div>
  63. </div>
  64. </div>
  65. @endforeach
  66. @else
  67. <div class="muted">暂无掌握度数据</div>
  68. @endif
  69. </div>
  70. <div class="card">
  71. <div class="section-title">解题思路与题目表现</div>
  72. @php
  73. $insightMap = [];
  74. foreach (($question_insights ?? []) as $insight) {
  75. $no = $insight['question_number'] ?? $insight['question_id'] ?? null;
  76. if ($no !== null) {
  77. $insightMap[$no] = $insight;
  78. }
  79. }
  80. @endphp
  81. @foreach($questions as $q)
  82. @php
  83. $insight = $insightMap[$q['question_number']] ?? ($insightMap[$q['display_number'] ?? null] ?? []);
  84. $score = $insight['score'] ?? ($insight['student_score'] ?? null);
  85. $fullScore = $insight['full_score'] ?? ($q['score'] ?? null);
  86. $analysisRaw = $insight['analysis']
  87. ?? $insight['thinking_process']
  88. ?? $insight['feedback']
  89. ?? $insight['suggestions']
  90. ?? $insight['reason']
  91. ?? ($insight['correct_solution'] ?? null);
  92. // 若有下一步建议,追加
  93. if (empty($analysisRaw) && !empty($insight['next_steps'])) {
  94. $analysisRaw = '后续建议:' . (is_array($insight['next_steps']) ? implode(';', $insight['next_steps']) : $insight['next_steps']);
  95. }
  96. $analysis = is_array($analysisRaw) ? json_encode($analysisRaw, JSON_UNESCAPED_UNICODE) : $analysisRaw;
  97. if ($analysis === null || $analysis === '') {
  98. $analysis = '暂无解题思路,待补充';
  99. }
  100. $stepsRaw = $insight['steps'] ?? $insight['solution_steps'] ?? $insight['analysis_steps'] ?? null;
  101. $steps = [];
  102. if (is_array($stepsRaw)) {
  103. $steps = $stepsRaw;
  104. } elseif (is_string($stepsRaw) && trim($stepsRaw) !== '') {
  105. $steps = preg_split('/[\r\n]+/', trim($stepsRaw));
  106. }
  107. $isCorrect = $insight['is_correct'] ?? $insight['correct'] ?? null;
  108. $badgeColor = $isCorrect === true ? '#10b981' : ($isCorrect === false ? '#ef4444' : '#6b7280');
  109. $badgeText = $isCorrect === true ? '答对' : ($isCorrect === false ? '答错' : '待判');
  110. $typeMap = ['choice' => '选择题', 'fill' => '填空题', 'answer' => '解答题'];
  111. $typeLabel = $typeMap[$q['question_type'] ?? ''] ?? ($q['question_type'] ?? '题型未标注');
  112. $questionText = is_string($q['question_text']) ? $q['question_text'] : json_encode($q['question_text'], JSON_UNESCAPED_UNICODE);
  113. $solution = $q['solution'] ?? null;
  114. @endphp
  115. <div style="border:1px solid #e5e7eb; border-radius:10px; padding:12px 14px; margin-bottom:10px; background:#fff; page-break-inside: avoid;">
  116. <div style="display:flex; justify-content:space-between; align-items:center; gap:8px; margin-bottom:6px;">
  117. <div style="display:flex; align-items:center; gap:8px; font-weight:600;">
  118. <span class="tag">题号 {{ $q['display_number'] ?? $q['question_number'] }}</span>
  119. <span class="tag" style="background: #eef2ff; color:#4338ca;">{{ $q['knowledge_point_name'] ?? $q['knowledge_point'] ?? '-' }}</span>
  120. <span class="tag" style="background: {{ $badgeColor }}; color:#fff;">{{ $badgeText }}</span>
  121. </div>
  122. <div class="muted">
  123. @if($score !== null && $fullScore !== null)
  124. 得分 {{ $score }} / {{ $fullScore }}
  125. @else
  126. 待评分
  127. @endif
  128. </div>
  129. </div>
  130. <div class="math-content" style="margin-bottom:6px;">{!! $questionText !!}</div>
  131. <div class="muted" style="margin-bottom:6px;">题型:{{ $typeLabel }}</div>
  132. <div style="font-size:13px; line-height:1.6;">{!! nl2br(e($analysis ?? '暂无解题思路记录')) !!}</div>
  133. @if(!empty($steps))
  134. <div style="margin-top:6px; font-size:13px;">
  135. <div style="font-weight:600; margin-bottom:4px;">解题步骤</div>
  136. <ol style="margin:0; padding-left:18px;">
  137. @foreach($steps as $s)
  138. <li>{!! nl2br(e(is_array($s) ? json_encode($s, JSON_UNESCAPED_UNICODE) : $s)) !!}</li>
  139. @endforeach
  140. </ol>
  141. </div>
  142. @elseif(!empty($solution))
  143. <div style="margin-top:8px; padding:8px; background:#f9fafb; border-left:3px solid #4f46e5; border-radius:4px;">
  144. <div style="font-weight:600; font-size:13px; color:#111827; margin-bottom:6px;">解析</div>
  145. <div class="math-content" style="font-size:13px; line-height:1.6; color:#374151;">
  146. {!! is_array($solution) ? json_encode($solution, JSON_UNESCAPED_UNICODE) : nl2br(e($solution)) !!}
  147. </div>
  148. </div>
  149. @endif
  150. </div>
  151. @endforeach
  152. </div>
  153. <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
  154. <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
  155. <script>
  156. document.addEventListener('DOMContentLoaded', function() {
  157. try {
  158. renderMathInElement(document.body, {
  159. delimiters: [
  160. {left: "$$", right: "$$", display: true},
  161. {left: "$", right: "$", display: false},
  162. {left: "\\(", right: "\\)", display: false},
  163. {left: "\\[", right: "\\]", display: true}
  164. ],
  165. throwOnError: false,
  166. strict: false,
  167. trust: true
  168. });
  169. } catch (e) {}
  170. });
  171. </script>
  172. </body>
  173. </html>