pdf-report-v3.blade.php 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. @php
  2. $v3 = $v3 ?? [];
  3. $summary = $v3['summary'] ?? [];
  4. $radar = $v3['radar'] ?? [];
  5. $modules = $v3['modules'] ?? [];
  6. $paths = $v3['paths'] ?? ['keep' => [], 'boost' => [], 'key' => []];
  7. $overallPlan = $v3['overall_plan'] ?? [];
  8. $rawPaperId = $paper['id'] ?? $paper['paper_id'] ?? 'unknown';
  9. preg_match('/paper_(\d{15})/', $rawPaperId, $matches);
  10. $reportCode = $matches[1] ?? preg_replace('/[^0-9]/', '', (string) $rawPaperId);
  11. $generateDateTime = now()->format('Y年m月d日 H:i:s');
  12. $scoreObtained = $summary['score_obtained'] ?? null;
  13. $scoreTotal = $summary['score_total'] ?? null;
  14. $scoreRate = $summary['score_rate'] ?? null;
  15. $averageMastery = $summary['average_mastery'] ?? null;
  16. $overallLabel = $summary['overall_label'] ?? '待评估';
  17. $difficultySummary = $summary['difficulty'] ?? [];
  18. $comparisonSummary = $summary['comparison'] ?? [];
  19. $overallLabelDetail = $summary['overall_label_detail'] ?? [];
  20. $historySummary = $comparisonSummary['history'] ?? [];
  21. $peerSummary = $comparisonSummary['peers'] ?? [];
  22. $overallScore = isset($overallLabelDetail['composite_score']) ? (float) $overallLabelDetail['composite_score'] : null;
  23. $overallGrade = (string) ($overallLabelDetail['grade'] ?? 'D');
  24. $currentPart = (float) ($overallLabelDetail['current_score'] ?? 0);
  25. $historyPart = (float) ($overallLabelDetail['history_score'] ?? 0);
  26. $peerPart = (float) ($overallLabelDetail['peer_score'] ?? 0);
  27. $adjustPart = (float) ($overallLabelDetail['difficulty_adjust'] ?? 0);
  28. $compositeFormulaResult = (0.50 * $currentPart) + (0.25 * $historyPart) + (0.25 * $peerPart) + $adjustPart;
  29. $overallBadge = function (string $grade): array {
  30. return match ($grade) {
  31. 'S' => ['bg' => '#f5f3ff', 'border' => '#6d28d9', 'text' => '#6d28d9', 'class' => 'badge-s'],
  32. 'A' => ['bg' => '#ecfdf3', 'border' => '#22c55e', 'text' => '#166534', 'class' => 'badge-excellent'],
  33. 'B' => ['bg' => '#eff6ff', 'border' => '#3b82f6', 'text' => '#1d4ed8', 'class' => 'badge-good'],
  34. 'C' => ['bg' => '#fff7ed', 'border' => '#f59e0b', 'text' => '#b45309', 'class' => 'badge-average'],
  35. default => ['bg' => '#fef2f2', 'border' => '#ef4444', 'text' => '#b91c1c', 'class' => 'badge-weak'],
  36. };
  37. };
  38. $overallVisual = $overallBadge((string) $overallGrade);
  39. $trendVisual = function (string $trend): array {
  40. return match ($trend) {
  41. '显著提升' => ['icon' => '▲', 'color' => '#16a34a'],
  42. '小幅提升' => ['icon' => '↗', 'color' => '#0ea5e9'],
  43. '基本持平' => ['icon' => '•', 'color' => '#64748b'],
  44. '小幅回落' => ['icon' => '↘', 'color' => '#f59e0b'],
  45. '明显回落' => ['icon' => '▼', 'color' => '#ef4444'],
  46. default => ['icon' => '•', 'color' => '#64748b'],
  47. };
  48. };
  49. $statusColor = function (string $status): string {
  50. return match ($status) {
  51. '良好' => '#16a34a',
  52. '一般' => '#f97316',
  53. '薄弱' => '#e11d48',
  54. default => '#64748b',
  55. };
  56. };
  57. $n = max(1, count($radar));
  58. $cx = 210;
  59. $cy = 155;
  60. $r = 108;
  61. $outer = [];
  62. $inner = [];
  63. for ($i = 0; $i < $n; $i++) {
  64. $angle = -M_PI / 2 + (2 * M_PI * $i / $n);
  65. $ox = $cx + $r * cos($angle);
  66. $oy = $cy + $r * sin($angle);
  67. $outer[] = [$ox, $oy];
  68. $value = isset($radar[$i]['value']) ? (float) $radar[$i]['value'] : 0.0;
  69. $ratio = max(0.0, min(1.0, $value / 5));
  70. $ix = $cx + $r * $ratio * cos($angle);
  71. $iy = $cy + $r * $ratio * sin($angle);
  72. $inner[] = [$ix, $iy];
  73. }
  74. $outerPoints = implode(' ', array_map(fn ($p) => round($p[0], 2).','.round($p[1], 2), $outer));
  75. $innerPoints = implode(' ', array_map(fn ($p) => round($p[0], 2).','.round($p[1], 2), $inner));
  76. $insightMap = [];
  77. foreach (($question_insights ?? []) as $insight) {
  78. $no = $insight['question_number'] ?? $insight['question_id'] ?? null;
  79. if ($no !== null) {
  80. $insightMap[$no] = $insight;
  81. }
  82. }
  83. $analysisWrongMap = [];
  84. foreach (($analysis_data['question_analysis'] ?? []) as $qa) {
  85. $qid = $qa['question_bank_id'] ?? $qa['question_id'] ?? null;
  86. if ($qid === null || $qid === '') {
  87. continue;
  88. }
  89. $rawCorrect = $qa['is_correct'] ?? null;
  90. $isWrongFromAnalysis = false;
  91. if (is_array($rawCorrect)) {
  92. $isWrongFromAnalysis = in_array(0, $rawCorrect, true);
  93. } elseif ($rawCorrect !== null) {
  94. $isWrongFromAnalysis = !boolval($rawCorrect);
  95. }
  96. if ($isWrongFromAnalysis) {
  97. $analysisWrongMap[(string) $qid] = true;
  98. }
  99. }
  100. $wrongQuestions = [];
  101. foreach (($questions ?? []) as $qItem) {
  102. $isCorrectProbe = $qItem['is_correct'] ?? null;
  103. $studentAnswerProbe = $qItem['student_answer'] ?? null;
  104. $correctAnswerProbe = $qItem['answer'] ?? ($qItem['correct_answer'] ?? null);
  105. if ($isCorrectProbe === null && !empty($studentAnswerProbe) && !empty($correctAnswerProbe)) {
  106. $isCorrectProbe = (trim((string) $studentAnswerProbe) === trim((string) $correctAnswerProbe)) ? 1 : 0;
  107. }
  108. $normalizedCorrect = $isCorrectProbe;
  109. if ($isCorrectProbe !== null) {
  110. $normalizedCorrect = is_bool($isCorrectProbe) ? ($isCorrectProbe ? 1 : 0) : intval($isCorrectProbe);
  111. }
  112. $qidProbe = (string) ($qItem['question_bank_id'] ?? $qItem['question_id'] ?? '');
  113. $isWrongByAnalysis = ($qidProbe !== '' && isset($analysisWrongMap[$qidProbe]));
  114. if ($normalizedCorrect === 0 || $isWrongByAnalysis) {
  115. $wrongQuestions[] = $qItem;
  116. }
  117. }
  118. $kpStats = [];
  119. foreach (($questions ?? []) as $qItem) {
  120. $kpName = trim((string) ($qItem['knowledge_point_name'] ?? $qItem['knowledge_point'] ?? '未标注知识点'));
  121. $kpName = $kpName === '' ? '未标注知识点' : $kpName;
  122. if (!isset($kpStats[$kpName])) {
  123. $kpStats[$kpName] = ['total' => 0, 'wrong' => 0];
  124. }
  125. $kpStats[$kpName]['total']++;
  126. }
  127. foreach ($wrongQuestions as $qItem) {
  128. $kpName = trim((string) ($qItem['knowledge_point_name'] ?? $qItem['knowledge_point'] ?? '未标注知识点'));
  129. $kpName = $kpName === '' ? '未标注知识点' : $kpName;
  130. if (!isset($kpStats[$kpName])) {
  131. $kpStats[$kpName] = ['total' => 0, 'wrong' => 0];
  132. }
  133. $kpStats[$kpName]['wrong']++;
  134. }
  135. $kpWrongStats = [];
  136. foreach ($kpStats as $kpName => $stat) {
  137. if (($stat['wrong'] ?? 0) <= 0) {
  138. continue;
  139. }
  140. $total = max(1, intval($stat['total'] ?? 0));
  141. $wrong = intval($stat['wrong'] ?? 0);
  142. $kpWrongStats[] = [
  143. 'kp_name' => $kpName,
  144. 'wrong' => $wrong,
  145. 'total' => $total,
  146. 'rate' => $wrong / $total,
  147. ];
  148. }
  149. usort($kpWrongStats, function ($a, $b) {
  150. if ($a['rate'] === $b['rate']) {
  151. return $b['wrong'] <=> $a['wrong'];
  152. }
  153. return $b['rate'] <=> $a['rate'];
  154. });
  155. @endphp
  156. <!DOCTYPE html>
  157. <html lang="zh-CN">
  158. <head>
  159. <meta charset="UTF-8">
  160. <title>学情分析报告</title>
  161. <link rel="stylesheet" href="/css/katex/katex.min.css">
  162. <style>
  163. @page {
  164. size: A4;
  165. margin: 2.2cm 2cm 2.3cm 2cm;
  166. @top-left { content: "知了数学·{{ $generateDateTime }}"; font-size: 13px; color: #666; }
  167. @top-center { content: "{{ $student['name'] ?? '-' }}"; font-size: 13px; color: #666; }
  168. @top-right {
  169. content: "{{ $reportCode }}";
  170. font-size: 19px;
  171. font-weight: 600;
  172. font-family: "Noto Sans", "Liberation Sans", "Nimbus Sans", sans-serif;
  173. color: #222;
  174. }
  175. @bottom-left { content: "{{ $reportCode }}"; font-size: 11px; color: #666; }
  176. @bottom-right { content: counter(page) "/" counter(pages); font-size: 13px; color: #666; }
  177. }
  178. * { box-sizing: border-box; }
  179. body { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; margin: 0; color: #0f172a; font-size: 13px; line-height: 1.65; }
  180. .page { page-break-after: auto; }
  181. .header { text-align: left; margin-bottom: 16px; }
  182. .paper-title { font-size: 30px; font-weight: 700; margin-bottom: 8px; color: #0b3a75; letter-spacing: 1px; }
  183. .section { margin-bottom: 14px; page-break-inside: auto; break-inside: auto; }
  184. .section-title { font-size: 20px; margin-bottom: 10px; font-weight: 700; color: #0b3a75; border-left: 5px solid #3b82f6; padding-left: 10px; line-height: 1.3; }
  185. .card { border: 1px solid #dbeafe; border-radius: 12px; padding: 14px; background: #f8fbff; position: relative; }
  186. .summary-list { margin: 0; padding-left: 18px; }
  187. .summary-list li { margin: 6px 0; font-size: 13px; }
  188. .overall-badge {
  189. position: absolute;
  190. right: 14px;
  191. top: 12px;
  192. border-radius: 12px;
  193. border: 0;
  194. padding: 9px 16px;
  195. min-width: 0;
  196. width: auto;
  197. text-align: center;
  198. position: absolute;
  199. overflow: hidden;
  200. display: inline-block;
  201. white-space: nowrap;
  202. background: transparent !important;
  203. }
  204. .overall-badge .level { font-size: 28px; font-weight: 800; line-height: 1.05; letter-spacing: 1px; }
  205. .overall-badge .score { font-size: 13px; margin-top: 3px; }
  206. .overall-badge.badge-s {
  207. border: 5px solid #6d28d9;
  208. border-radius: 14px;
  209. box-shadow: none;
  210. transform: rotate(-7deg);
  211. }
  212. .overall-badge.badge-s::before {
  213. content: "";
  214. position: absolute;
  215. inset: 4px;
  216. border: 2px dashed rgba(109, 40, 217, 0.65);
  217. border-radius: 10px;
  218. pointer-events: none;
  219. }
  220. .overall-badge.badge-s .level {
  221. letter-spacing: 2px;
  222. text-shadow: 0 1px 0 rgba(109, 40, 217, 0.24);
  223. }
  224. .overall-badge.badge-excellent {
  225. border: 3px double #16a34a;
  226. border-radius: 999px;
  227. box-shadow: none;
  228. }
  229. .overall-badge.badge-good {
  230. border: 2px solid #2563eb;
  231. border-radius: 10px;
  232. clip-path: polygon(6% 0, 94% 0, 100% 50%, 94% 100%, 6% 100%, 0 50%);
  233. box-shadow: none;
  234. }
  235. .overall-badge.badge-average {
  236. border: 2px dashed #d97706;
  237. border-radius: 14px;
  238. box-shadow: none;
  239. }
  240. .overall-badge.badge-weak {
  241. border-left: 3px solid #ef4444;
  242. border-right: 0;
  243. border-top: 0;
  244. border-bottom: 2px solid #ef4444;
  245. border-radius: 0 10px 10px 0;
  246. box-shadow: none;
  247. }
  248. .overall-meta { margin-top: 8px; font-size: 9px; color: #64748b; line-height: 1.6; white-space: nowrap; }
  249. .radar-center { text-align: center; }
  250. .legend { margin-top: 8px; font-size: 12px; color: #475569; }
  251. .legend span { margin: 0 8px; }
  252. .dot { display: inline-block; width: 10px; height: 10px; border-radius: 999px; margin-right: 4px; vertical-align: middle; }
  253. /* PDF 版优先上下结构,避免左右分栏导致拥挤 */
  254. .radar-split { display: block; width: 100%; }
  255. .radar-left { width: 100%; text-align: center; }
  256. .radar-right { width: 100%; padding-left: 0; margin-top: 10px; }
  257. .radar-desc { border: 1px solid #dbeafe; background: #f8fbff; border-radius: 12px; padding: 12px; text-align: left; }
  258. .radar-item { display: block; margin: 6px 0; font-size: 12px; }
  259. .kp-burst-card { margin-top: 10px; border: 1px solid #dbeafe; border-radius: 12px; padding: 10px; background: #fff; }
  260. .kp-burst-title { font-size: 13px; font-weight: 700; margin-bottom: 6px; color: #0b3a75; }
  261. .kp-burst-meta { font-size: 12px; color: #334155; margin-top: 6px; line-height: 1.6; }
  262. .kp-burst-list { margin-top: 6px; font-size: 11px; color: #334155; line-height: 1.5; }
  263. .kp-burst-list span { display: inline-block; margin-right: 10px; margin-bottom: 3px; }
  264. table { width: 100%; border-collapse: collapse; font-size: 12px; background: #fff; }
  265. th, td { border: 1px solid #d0d7e2; padding: 8px 10px; text-align: left; vertical-align: top; }
  266. th { background: #f1f5f9; color: #1e293b; font-weight: 700; }
  267. .badge { display: inline-block; padding: 2px 8px; border-radius: 999px; color: #fff; font-size: 11px; font-weight: 600; }
  268. .path-stack { display: block; }
  269. .path-box { border: 1px solid #e5e7eb; border-radius: 12px; padding: 12px 14px; margin-bottom: 10px; }
  270. .path-box.keep { background: #f0fdf4; border-color: #86efac; }
  271. .path-box.boost { background: #fff7ed; border-color: #fdba74; }
  272. .path-box.key { background: #fff1f2; border-color: #fda4af; }
  273. .path-title { font-size: 16px; font-weight: 700; margin-bottom: 6px; color: #111827; }
  274. .path-box ul { margin: 0; padding-left: 16px; }
  275. .path-box li { font-size: 13px; margin: 2px 0; }
  276. .plan { background: #eef4ff; border-left: 4px solid #3b82f6; border-radius: 12px; padding: 12px 14px; }
  277. .plan ol { margin: 0; padding-left: 18px; }
  278. .tag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; color: #334155; background: #e5e7eb; }
  279. .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; }
  280. .error-kp-tag.high-risk { color: #b91c1c; border-color: #fca5a5; background: #fff; font-weight: 600; }
  281. .question-card { border:1px solid #e5e7eb; border-radius:8px; padding:6px 9px; margin-bottom:5px; background:#fff; page-break-inside:auto; break-inside:auto; }
  282. .question-block { margin-bottom: 5px; padding: 5px; border-radius: 4px; page-break-inside: auto; break-inside: auto; }
  283. .question-card,
  284. .question-card .math-content,
  285. .question-card .solution-content { font-size: 12px; line-height: 1.7; }
  286. .question-card .question-stem svg,
  287. .question-card .math-content svg { max-width: 100%; height: auto; display: block; shape-rendering: geometricPrecision; text-rendering: geometricPrecision; }
  288. .question-card .question-stem svg text {
  289. font-family: "Noto Serif", "Noto Serif CJK SC", "Noto Sans CJK SC", "Noto Sans", "STSongti-SC", "PingFang SC", "Songti SC", serif !important;
  290. font-size: 13px !important;
  291. font-weight: bold;
  292. dominant-baseline: middle;
  293. text-anchor: middle;
  294. }
  295. .question-card .question-stem svg circle,
  296. .question-card .question-stem svg line,
  297. .question-card .question-stem svg polygon,
  298. .question-card .question-stem svg polyline { shape-rendering: geometricPrecision; }
  299. .question-card .question-stem img,
  300. .question-card .question-main img {
  301. display: block;
  302. max-width: 220px;
  303. max-height: 60mm;
  304. width: auto;
  305. height: auto;
  306. margin: 6px auto;
  307. box-sizing: border-box;
  308. object-fit: contain;
  309. -webkit-print-color-adjust: exact;
  310. print-color-adjust: exact;
  311. image-rendering: -webkit-optimize-contrast;
  312. }
  313. .question-card .question-stem .katex { font-size: 1em !important; vertical-align: 0; }
  314. .question-card .question-stem .katex-display { margin: 0.35em 0 !important; }
  315. .question-card .solution-content img,
  316. .question-card .report-answer-meta img {
  317. display: block;
  318. max-width: 220px;
  319. max-height: 60mm;
  320. width: auto;
  321. height: auto;
  322. margin: 6px auto;
  323. object-fit: contain;
  324. -webkit-print-color-adjust: exact;
  325. print-color-adjust: exact;
  326. }
  327. .solution-content {
  328. display: block;
  329. line-height: 1.75;
  330. white-space: normal;
  331. word-break: break-word;
  332. overflow-wrap: anywhere;
  333. page-break-inside: auto;
  334. break-inside: auto;
  335. }
  336. .report-options { margin-top: 6px; page-break-inside: auto; break-inside: auto; }
  337. .report-options.options-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px 12px; }
  338. .report-options.options-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 20px; }
  339. .report-options.options-grid-1 { display: grid; grid-template-columns: 1fr; gap: 8px; }
  340. .report-options .option { display: flex; align-items: baseline; font-size: 12px; line-height: 1.6; page-break-inside: auto; break-inside: auto; }
  341. .report-options .option strong { margin-right: 4px; flex: 0 0 auto; }
  342. .report-options .option-value.option-short { white-space: nowrap; }
  343. .report-options .option-value.option-long { white-space: normal; word-break: break-word; }
  344. .report-options .option p, .report-options .option div { margin: 0; display: inline; }
  345. .report-options .option img { max-width: 100%; height: auto; vertical-align: middle; }
  346. .report-answer-meta { font-size: 12px; color: #2f2f2f; line-height: 1.75; margin-top: 6px; page-break-inside: auto; break-inside: auto; }
  347. .report-answer-meta .answer-line + .answer-line { margin-top: 4px; }
  348. .report-answer-meta .solution-content { display: inline; line-height: 1.75; }
  349. .muted { color: #6b7280; font-size: 12px; }
  350. </style>
  351. </head>
  352. <body>
  353. <div class="page">
  354. <div class="header">
  355. <h1 class="paper-title">学情分析报告</h1>
  356. </div>
  357. <div class="section">
  358. <div class="section-title">一、总体评估</div>
  359. <div class="card">
  360. <div class="overall-badge {{ $overallVisual['class'] ?? '' }}"
  361. style="border-color:{{ $overallVisual['border'] }}; color:{{ $overallVisual['text'] }};">
  362. <div class="level">{{ $overallGrade }}</div>
  363. </div>
  364. <ul class="summary-list">
  365. <li>本次诊断得分:
  366. @if($scoreObtained !== null && $scoreTotal !== null && $scoreTotal > 0)
  367. {{ rtrim(rtrim(number_format((float) $scoreObtained, 1), '0'), '.') }}/{{ rtrim(rtrim(number_format((float) $scoreTotal, 1), '0'), '.') }}
  368. @else
  369. 暂无得分数据
  370. @endif
  371. </li>
  372. <li>得分率:{{ $scoreRate !== null ? number_format((float) $scoreRate * 100, 1) . '%' : '暂无得分率' }}</li>
  373. <li>平均掌握度:{{ $averageMastery !== null ? number_format((float) $averageMastery * 100, 1) . '%' : '暂无掌握度' }}</li>
  374. <li>
  375. 难度匹配:
  376. @if(!empty($difficultySummary['target_label']) && isset($difficultySummary['actual_average_difficulty']))
  377. 目标 {{ $difficultySummary['target_label'] }}
  378. @if(!empty($difficultySummary['target_range']))
  379. ({{ number_format((float)($difficultySummary['target_range']['min'] ?? 0), 2) }}~{{ number_format((float)($difficultySummary['target_range']['max'] ?? 0), 2) }})
  380. @endif
  381. ,实际 {{ number_format((float)($difficultySummary['actual_average_difficulty'] ?? 0), 3) }}
  382. ({{ $difficultySummary['status'] ?? '暂无' }})
  383. @else
  384. 暂无难度匹配数据
  385. @endif
  386. </li>
  387. @if(!empty($difficultySummary['explain']))
  388. <li>难度说明:{{ $difficultySummary['explain'] }}</li>
  389. @endif
  390. <li>
  391. 与历史自己对比:
  392. @if(!empty($historySummary['is_first_exam']))
  393. {{ $historySummary['message'] ?? '这是你的第一次分析报告,先积累样本再看趋势。' }}
  394. @elseif(!empty($historySummary['low_baseline_guard']))
  395. {{ $historySummary['message'] ?? '历史基线偏低,建议看连续趋势。' }}
  396. @elseif(!empty($historySummary['has_data']))
  397. @php
  398. $trendText = (string)($historySummary['trend'] ?? '—');
  399. $tVisual = $trendVisual($trendText);
  400. @endphp
  401. 近几次均值对比:
  402. {{ number_format((float)($historySummary['baseline_score_rate'] ?? 0) * 100, 1) }}%,
  403. 本次{{ ($historySummary['delta_score_rate'] ?? 0) >= 0 ? '提升' : '回落' }}
  404. {{ number_format(abs((float)($historySummary['delta_score_rate'] ?? 0)) * 100, 1) }}%
  405. (<span style="color:{{ $tVisual['color'] ?? '#64748b' }}; font-weight:600;">{{ $tVisual['icon'] ?? '•' }} {{ $trendText }}</span>)
  406. @else
  407. {{ $historySummary['message'] ?? '历史样本不足' }}
  408. @endif
  409. </li>
  410. @if(!empty($peerSummary['show_line']))
  411. <li>
  412. 与同群体对比:
  413. {{ $peerSummary['message'] ?? '' }}
  414. (<span style="color:{{ $peerSummary['band_color'] ?? '#64748b' }}; font-weight:600;">{{ $peerSummary['band_icon'] ?? '•' }} {{ $peerSummary['band'] ?? '—' }}</span>)
  415. </li>
  416. @endif
  417. <li>
  418. 整体水平:
  419. @if($overallScore !== null)
  420. {{ number_format($overallScore, 1) }} 分({{ $overallGrade }})
  421. @else
  422. 待计算
  423. @endif
  424. </li>
  425. </ul>
  426. <div class="overall-meta">
  427. 规则:综合分 = 当前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) }}
  428. </div>
  429. </div>
  430. </div>
  431. <div class="section">
  432. <div class="section-title">二、知识点掌握雷达图</div>
  433. <div class="radar-split">
  434. <div class="radar-left">
  435. <svg width="430" height="320" viewBox="0 0 430 320">
  436. <polygon points="{{ $outerPoints }}" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1"/>
  437. <polygon points="{{ $innerPoints }}" fill="rgba(59,130,246,0.28)" stroke="#3b82f6" stroke-width="2"/>
  438. @foreach($outer as $i => $p)
  439. <line x1="{{ $cx }}" y1="{{ $cy }}" x2="{{ round($p[0],2) }}" y2="{{ round($p[1],2) }}" stroke="#e2e8f0" stroke-width="1"/>
  440. @endforeach
  441. @foreach($outer as $i => $p)
  442. @php
  443. $name = $radar[$i]['name'] ?? '';
  444. $children = $radar[$i]['children'] ?? [];
  445. $labelX = $p[0] + (($p[0] >= $cx) ? 9 : -9);
  446. $labelY = $p[1] + (($p[1] >= $cy) ? 12 : -8);
  447. $anchor = $p[0] >= $cx ? 'start' : 'end';
  448. $dotColor = $statusColor((string) ($radar[$i]['status'] ?? '暂无'));
  449. $value = number_format((float) ($radar[$i]['value'] ?? 0), 2);
  450. $axisAngle = atan2(($p[1] - $cy), ($p[0] - $cx));
  451. @endphp
  452. <text x="{{ round($labelX,2) }}" y="{{ round($labelY,2) }}" font-size="11" fill="#334155" text-anchor="{{ $anchor }}">{{ $name }} {{ $value }}</text>
  453. <circle cx="{{ round($inner[$i][0],2) }}" cy="{{ round($inner[$i][1],2) }}" r="4" fill="{{ $dotColor }}" />
  454. @if(!empty($children))
  455. @foreach($children as $cIdx => $child)
  456. @php
  457. $childN = max(1, count($children));
  458. $depth = max(1, intval($child['depth'] ?? 1));
  459. $offset = ($cIdx - (($childN - 1) / 2)) * 0.04;
  460. $childAngle = $axisAngle + $offset;
  461. // 子知识点必须从父轴外圈向外发散,避免父轴值低时挤在中心
  462. $axisOuterR = sqrt(pow(($outer[$i][0] - $cx), 2) + pow(($outer[$i][1] - $cy), 2));
  463. $startR = max($axisOuterR + 4, 112 + (($depth - 1) * 10));
  464. $endR = $startR + 12 + (($depth - 1) * 4);
  465. $sx = $cx + $startR * cos($childAngle);
  466. $sy = $cy + $startR * sin($childAngle);
  467. $ex = $cx + $endR * cos($childAngle);
  468. $ey = $cy + $endR * sin($childAngle);
  469. $changed = !empty($child['changed']);
  470. $cColor = $changed ? '#e11d48' : '#94a3b8';
  471. $cWidth = $changed ? 1.5 : 0.8;
  472. $masteryPct = isset($child['mastery_level']) ? max(0, min(100, (float) $child['mastery_level'] * 100)) : null;
  473. $label = $masteryPct !== null ? number_format($masteryPct, 1) . '%' : '—';
  474. $tx = $cx + ($endR + 5 + (($depth - 1) * 2)) * cos($childAngle);
  475. $ty = $cy + ($endR + 5 + (($depth - 1) * 2)) * sin($childAngle);
  476. $anchor = cos($childAngle) >= 0 ? 'start' : 'end';
  477. @endphp
  478. <line x1="{{ round($sx,2) }}" y1="{{ round($sy,2) }}" x2="{{ round($ex,2) }}" y2="{{ round($ey,2) }}"
  479. stroke="{{ $cColor }}" stroke-width="{{ $cWidth }}" opacity="{{ $changed ? 0.95 : 0.85 }}"/>
  480. <circle cx="{{ round($ex,2) }}" cy="{{ round($ey,2) }}" r="{{ $changed ? 1.6 : 1.0 }}" fill="{{ $cColor }}" />
  481. <text x="{{ round($tx,2) }}" y="{{ round($ty,2) }}" font-size="9" fill="{{ $cColor }}" text-anchor="{{ $anchor }}">{{ $label }}</text>
  482. @endforeach
  483. @endif
  484. @endforeach
  485. </svg>
  486. <div class="legend">
  487. <span><i class="dot" style="background:#16a34a"></i>良好(4.0-5.0)</span>
  488. <span><i class="dot" style="background:#f97316"></i>一般(2.0-3.9)</span>
  489. <span><i class="dot" style="background:#e11d48"></i>薄弱(0-1.9)</span>
  490. <span><i class="dot" style="background:#64748b"></i>未涉及</span>
  491. <span><i class="dot" style="background:#94a3b8"></i>子知识点</span>
  492. <span><i class="dot" style="background:#e11d48"></i>子知识点变化</span>
  493. <span>外圈越远表示层级越深</span>
  494. </div>
  495. </div>
  496. <div class="radar-right">
  497. <div class="radar-desc">
  498. <strong>雷达图解读</strong>
  499. @foreach($radar as $item)
  500. @php $color = $statusColor((string) ($item['status'] ?? '暂无')); @endphp
  501. <span class="radar-item">
  502. <i class="dot" style="background:{{ $color }}"></i>
  503. {{ $item['name'] }}:{{ $item['status'] }}
  504. ({{ !empty($item['has_mastery']) ? number_format((float) ($item['value'] ?? 0), 2) . '/5' : '—' }})
  505. @if(!empty($item['children']))
  506. ,子知识点 {{ count($item['children']) }} 个
  507. @endif
  508. </span>
  509. @endforeach
  510. </div>
  511. </div>
  512. </div>
  513. </div>
  514. <div class="section">
  515. <div class="section-title">三、模块能力分析表</div>
  516. <table>
  517. <thead>
  518. <tr>
  519. <th style="width: 16%;">模块</th>
  520. <th style="width: 13%;">掌握分值</th>
  521. <th style="width: 12%;">掌握状态</th>
  522. <th style="width: 10%;">样本数</th>
  523. <th style="width: 12%;">得分率</th>
  524. <th>学生当前能力</th>
  525. </tr>
  526. </thead>
  527. <tbody>
  528. @foreach($modules as $m)
  529. @php
  530. $status = (string) ($m['status'] ?? '暂无');
  531. $color = $statusColor($status);
  532. $rate = $m['exam_score_rate'] ?? null;
  533. @endphp
  534. <tr>
  535. <td>{{ $m['module_name'] ?? '-' }}</td>
  536. <td>{{ $m['mastery_score_5'] !== null ? number_format((float) $m['mastery_score_5'], 2) . '/5' : '-' }}</td>
  537. <td><span class="badge" style="background:{{ $color }}">{{ $status }}</span></td>
  538. <td>{{ $m['kp_count'] ?? 0 }}</td>
  539. <td>{{ $rate !== null ? number_format((float) $rate * 100, 1) . '%' : '-' }}</td>
  540. <td>{{ $m['ability_text'] ?? '-' }}</td>
  541. </tr>
  542. @endforeach
  543. </tbody>
  544. </table>
  545. </div>
  546. <div class="section">
  547. <div class="section-title">四、分模块提分路径</div>
  548. <div class="path-stack">
  549. <div class="path-box keep">
  550. <div class="path-title">保分模块(保持优势)</div>
  551. <ul>
  552. @foreach(($paths['keep'] ?? []) as $item)
  553. <li>{{ $item['name'] }}:掌握度 {{ number_format((float) ($item['mastery_level'] ?? 0) * 100, 1) }}%</li>
  554. @endforeach
  555. </ul>
  556. @if(empty($paths['keep']))
  557. <div class="muted">暂无数据</div>
  558. @endif
  559. </div>
  560. <div class="path-box boost">
  561. <div class="path-title">涨分模块(重点突破)</div>
  562. <ul>
  563. @foreach(($paths['boost'] ?? []) as $item)
  564. <li>{{ $item['name'] }}:掌握度 {{ number_format((float) ($item['mastery_level'] ?? 0) * 100, 1) }}%</li>
  565. @endforeach
  566. </ul>
  567. @if(empty($paths['boost']))
  568. <div class="muted">暂无数据</div>
  569. @endif
  570. </div>
  571. <div class="path-box key">
  572. <div class="path-title">提分关键(优先补短)</div>
  573. <ul>
  574. @foreach(($paths['key'] ?? []) as $item)
  575. <li>{{ $item['name'] }}:掌握度 {{ number_format((float) ($item['mastery_level'] ?? 0) * 100, 1) }}%</li>
  576. @endforeach
  577. </ul>
  578. @if(empty($paths['key']))
  579. <div class="muted">暂无数据</div>
  580. @endif
  581. </div>
  582. </div>
  583. </div>
  584. <div class="section">
  585. <div class="section-title">五、整体提升方案</div>
  586. <div class="plan">
  587. <ol>
  588. @foreach($overallPlan as $line)
  589. <li>{{ $line }}</li>
  590. @endforeach
  591. </ol>
  592. </div>
  593. </div>
  594. @if(!empty($wrongQuestions))
  595. <div class="section" style="page-break-inside:auto; break-inside:auto;">
  596. <div class="section-title">六、这次错题记录</div>
  597. @if(!empty($kpWrongStats))
  598. <div style="margin-bottom:8px; padding:8px; border:1px solid #e5e7eb; border-radius:6px; background:#f8fafc;">
  599. <div style="font-size:12px; font-weight:600; margin-bottom:6px;">知识点错误率</div>
  600. <div style="font-size:12px; color:#475569; line-height:1.7;">
  601. @foreach($kpWrongStats as $item)
  602. <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>
  603. @endforeach
  604. </div>
  605. </div>
  606. @endif
  607. @foreach($wrongQuestions as $q)
  608. @php
  609. $studentAnswer = $q['student_answer'] ?? null;
  610. $correctAnswer = $q['answer'] ?? $q['correct_answer'] ?? null;
  611. $isCorrect = $q['is_correct'] ?? null;
  612. if ($isCorrect === null && !empty($studentAnswer) && !empty($correctAnswer)) {
  613. $isCorrect = (trim($studentAnswer) === trim($correctAnswer)) ? 1 : 0;
  614. }
  615. $statusText = '';
  616. $statusColorValue = '';
  617. if ($isCorrect === 1) {
  618. $statusText = '正确';
  619. $statusColorValue = '#10b981';
  620. } elseif ($isCorrect === 0) {
  621. $statusText = '错误';
  622. $statusColorValue = '#ef4444';
  623. }
  624. $showStatus = $statusText !== '';
  625. $insight = $insightMap[$q['question_number']] ?? ($insightMap[$q['display_number'] ?? null] ?? []);
  626. $fullScore = $insight['full_score'] ?? ($q['score'] ?? null);
  627. if ($isCorrect === 1) {
  628. $score = $fullScore;
  629. } elseif ($isCorrect === 0) {
  630. $score = $q['score_obtained'] ?? 0;
  631. } else {
  632. $score = null;
  633. }
  634. $analysisRaw = $insight['analysis']
  635. ?? $insight['thinking_process']
  636. ?? $insight['feedback']
  637. ?? $insight['suggestions']
  638. ?? $insight['reason']
  639. ?? ($insight['correct_solution'] ?? null);
  640. if (empty($analysisRaw) && !empty($insight['next_steps'])) {
  641. $analysisRaw = '后续建议:' . (is_array($insight['next_steps']) ? implode(';', $insight['next_steps']) : $insight['next_steps']);
  642. }
  643. $analysis = is_array($analysisRaw) ? json_encode($analysisRaw, JSON_UNESCAPED_UNICODE) : $analysisRaw;
  644. if ($analysis === null || $analysis === '') {
  645. $analysis = '暂无解题思路,待补充';
  646. }
  647. if (is_string($analysis)) {
  648. $analysis = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $analysis);
  649. }
  650. $formatSolutionLikeGrading = function ($text) {
  651. if (!is_string($text) || trim($text) === '') {
  652. return $text;
  653. }
  654. $normalized = preg_replace('/\s*;\s*步骤\s*(\d+)/u', ";\n步骤$1", $text);
  655. $normalized = preg_replace('/\s*。\s*步骤\s*(\d+)/u', "。\n步骤$1", $normalized);
  656. $normalized = preg_replace('/(?<!^)(步骤\s*\d+\s*[::])/u', "\n$1", $normalized);
  657. $normalized = preg_replace('/(?<!^)(第\s*\d+\s*步\s*[::]?)/u', "\n$1", $normalized);
  658. $normalized = preg_replace('/\n{3,}/u', "\n\n", $normalized);
  659. $normalized = preg_replace('/^[\h\x{3000}]+/mu', '', $normalized);
  660. return trim($normalized);
  661. };
  662. $stepsRaw = $insight['steps'] ?? $insight['solution_steps'] ?? $insight['analysis_steps'] ?? null;
  663. $steps = [];
  664. if (is_array($stepsRaw)) {
  665. $steps = $stepsRaw;
  666. } elseif (is_string($stepsRaw) && trim($stepsRaw) !== '') {
  667. $steps = preg_split('/[\r\n]+/', trim($stepsRaw));
  668. }
  669. $typeMap = ['choice' => '选择题', 'fill' => '填空题', 'answer' => '解答题'];
  670. $typeLabel = $typeMap[$q['question_type'] ?? ''] ?? ($q['question_type'] ?? '题型未标注');
  671. $questionText = is_string($q['question_text']) ? $q['question_text'] : json_encode($q['question_text'], JSON_UNESCAPED_UNICODE);
  672. $solution = $q['solution'] ?? null;
  673. if (is_string($solution)) {
  674. $solution = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $solution);
  675. }
  676. $solution = $formatSolutionLikeGrading($solution);
  677. $analysis = $formatSolutionLikeGrading($analysis);
  678. $renderLikeGrading = function ($text) {
  679. if (is_array($text)) {
  680. $text = json_encode($text, JSON_UNESCAPED_UNICODE);
  681. }
  682. $text = is_string($text) ? trim($text) : '';
  683. if ($text === '') {
  684. return '';
  685. }
  686. // 兼容题库里常见的转义写法:\$x\$、\$\frac{...}\$
  687. $text = preg_replace('/\\\\\\$/u', '$', $text);
  688. return \App\Services\MathFormulaProcessor::processFormulas($text);
  689. };
  690. $questionTextRendered = $renderLikeGrading($questionText);
  691. $displayCorrectAnswer = is_array($correctAnswer) ? json_encode($correctAnswer, JSON_UNESCAPED_UNICODE) : (string) $correctAnswer;
  692. $questionTypeRaw = strtolower(trim((string) ($q['question_type'] ?? '')));
  693. $isChoiceQuestion = in_array($questionTypeRaw, ['choice', 'multiple_choice', 'single_choice', '选择题', 'select'], true);
  694. $normalizedOptions = [];
  695. $correctAnswerLetters = [];
  696. if ($isChoiceQuestion) {
  697. $rawOptions = $q['options'] ?? [];
  698. if (is_string($rawOptions)) {
  699. $decodedOptions = json_decode($rawOptions, true);
  700. $rawOptions = is_array($decodedOptions) ? $decodedOptions : [];
  701. }
  702. if (is_array($rawOptions)) {
  703. foreach ($rawOptions as $optKey => $optValue) {
  704. $letter = null;
  705. if (is_string($optKey) && preg_match('/([A-H])/i', $optKey, $m)) {
  706. $letter = strtoupper($m[1]);
  707. } elseif (is_array($optValue)) {
  708. $candidate = $optValue['label'] ?? $optValue['key'] ?? $optValue['option'] ?? null;
  709. if (is_string($candidate) && preg_match('/([A-H])/i', $candidate, $m)) {
  710. $letter = strtoupper($m[1]);
  711. }
  712. }
  713. if ($letter === null) {
  714. continue;
  715. }
  716. $content = is_array($optValue) ? ($optValue['content'] ?? $optValue['text'] ?? $optValue['value'] ?? '') : $optValue;
  717. if (!is_string($content)) {
  718. $content = json_encode($content, JSON_UNESCAPED_UNICODE);
  719. }
  720. $content = trim((string) $content);
  721. if ($content !== '') {
  722. $normalizedOptions[$letter] = $content;
  723. }
  724. }
  725. }
  726. if (trim((string) $correctAnswer) !== '') {
  727. preg_match_all('/[A-H]/i', strtoupper((string) $correctAnswer), $answerMatches);
  728. $correctAnswerLetters = array_values(array_unique($answerMatches[0] ?? []));
  729. }
  730. if (!empty($normalizedOptions) && !empty($correctAnswerLetters)) {
  731. $mappedAnswers = [];
  732. foreach ($correctAnswerLetters as $letter) {
  733. if (isset($normalizedOptions[$letter])) {
  734. $mappedAnswers[] = $letter . '. ' . $normalizedOptions[$letter];
  735. }
  736. }
  737. if (!empty($mappedAnswers)) {
  738. $displayCorrectAnswer = implode(';', $mappedAnswers);
  739. }
  740. }
  741. }
  742. $choiceOptionLetters = !empty($normalizedOptions) ? array_keys($normalizedOptions) : [];
  743. sort($choiceOptionLetters);
  744. $choiceLayoutClass = 'options-grid-1';
  745. $layoutDecider = app(\App\Support\OptionLayoutDecider::class);
  746. if (! empty($normalizedOptions) && ! empty($choiceOptionLetters)) {
  747. $optValuesForLayout = [];
  748. foreach ($choiceOptionLetters as $L) {
  749. $optValuesForLayout[] = $normalizedOptions[$L];
  750. }
  751. $layoutMeta = $layoutDecider->decide($optValuesForLayout, 'grading');
  752. $choiceLayoutClass = $layoutMeta['class'] ?? 'options-grid-1';
  753. }
  754. @endphp
  755. <div class="question-card">
  756. <div style="display:flex; justify-content:space-between; align-items:center; gap:8px; margin-bottom:4px;">
  757. <div style="display:flex; align-items:center; gap:8px; font-weight:600;">
  758. <span class="tag">题号 {{ $q['display_number'] ?? $q['question_number'] }} · {{ $typeLabel }}</span>
  759. @php
  760. $kpName = $q['knowledge_point_name'] ?? $q['knowledge_point'] ?? null;
  761. if (!empty($kpName) && $kpName !== '-' && $kpName !== '未标注') {
  762. echo '<span class="tag" style="background: #eef2ff; color:#4338ca;">' . e($kpName) . '</span>';
  763. }
  764. @endphp
  765. @if($showStatus)
  766. <span class="tag" style="background: {{ $statusColorValue }}; color:#fff;">{{ $statusText }}</span>
  767. @endif
  768. </div>
  769. @if($score !== null && $fullScore !== null)
  770. <div class="muted">得分 {{ $score }} / {{ $fullScore }}</div>
  771. @endif
  772. </div>
  773. <div class="question-stem math-content" style="margin-bottom:6px;">{!! $questionTextRendered !!}</div>
  774. @if(!empty($isChoiceQuestion) && !empty($normalizedOptions))
  775. <div class="report-options {{ $choiceLayoutClass }}">
  776. @foreach($choiceOptionLetters as $optLetter)
  777. @php
  778. $isCorrectOpt = in_array($optLetter, $correctAnswerLetters ?? [], true);
  779. $rawOpt = (string) ($normalizedOptions[$optLetter] ?? '');
  780. $normalizedOpt = str_replace('\\dfrac', '\\frac', $rawOpt);
  781. $normalizedOpt = str_replace('\\displaystyle', '', $normalizedOpt);
  782. $normalizedOpt = $layoutDecider->normalizeCompactMathForDisplay($normalizedOpt);
  783. $rawOptPlain = html_entity_decode(strip_tags($rawOpt), ENT_QUOTES | ENT_HTML5, 'UTF-8');
  784. $rawOptPlain = preg_replace('/\s+/u', '', $rawOptPlain ?? '');
  785. $isShortOption = mb_strlen((string) $rawOptPlain, 'UTF-8') <= 8;
  786. $valClass = $isShortOption ? 'option-short' : 'option-long';
  787. $renderedOpt = $renderLikeGrading($normalizedOpt);
  788. @endphp
  789. <div class="option option-compact">
  790. <strong>{{ $optLetter }}.</strong>
  791. <span class="option-value {{ $valClass }}">{!! $renderedOpt !!}</span>
  792. @if($isCorrectOpt)
  793. <span style="margin-left:4px; font-size:13px; color:#15803d; font-weight:700;">✅</span>
  794. @endif
  795. </div>
  796. @endforeach
  797. </div>
  798. @endif
  799. @if(!empty($correctAnswer) && (!$isChoiceQuestion || empty($normalizedOptions)))
  800. <div class="question-block" style="background:#f0fdf4; border-left:3px solid #10b981;">
  801. <div style="font-weight:600; font-size:12px; color:#111827; margin-bottom:3px;">正确答案</div>
  802. <div class="math-content" style="line-height:1.7; color:#374151;">
  803. {!! $renderLikeGrading($displayCorrectAnswer) !!}
  804. </div>
  805. </div>
  806. @endif
  807. @if(!empty($solution))
  808. <div class="report-answer-meta">
  809. <div class="answer-line">
  810. <strong>解题思路:</strong>
  811. <span class="solution-content">{!! $renderLikeGrading($solution) !!}</span>
  812. </div>
  813. </div>
  814. @elseif(!empty($analysis) && $analysis !== '暂无解题思路记录')
  815. <div class="report-answer-meta">
  816. <div class="answer-line">
  817. <strong>解题思路:</strong>
  818. <span class="solution-content">{!! $renderLikeGrading($analysis) !!}</span>
  819. </div>
  820. </div>
  821. @endif
  822. @if(!empty($steps))
  823. <div style="margin-top:6px; font-size:12px;">
  824. <div style="font-weight:600; margin-bottom:3px;">解题步骤</div>
  825. <ol style="margin:0; padding-left:18px;">
  826. @foreach($steps as $s)
  827. @php
  828. $stepText = is_array($s) ? json_encode($s, JSON_UNESCAPED_UNICODE) : (string) $s;
  829. @endphp
  830. <li style="margin-bottom:2px;">{!! nl2br($renderLikeGrading($stepText)) !!}</li>
  831. @endforeach
  832. </ol>
  833. </div>
  834. @endif
  835. </div>
  836. @endforeach
  837. </div>
  838. @endif
  839. </div>
  840. <script src="/js/katex.min.js"></script>
  841. <script src="/js/auto-render.min.js"></script>
  842. <script>
  843. document.addEventListener('DOMContentLoaded', function() {
  844. try {
  845. renderMathInElement(document.body, {
  846. delimiters: [
  847. {left: "$$", right: "$$", display: true},
  848. {left: "$", right: "$", display: false},
  849. {left: "\\(", right: "\\)", display: false},
  850. {left: "\\[", right: "\\]", display: true}
  851. ],
  852. throwOnError: false,
  853. strict: false,
  854. trust: true
  855. });
  856. } catch (e) {}
  857. });
  858. </script>
  859. </body>
  860. </html>