exam-knowledge-explanation.blade.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. {{-- 知识点讲解模板 --}}
  2. <!DOCTYPE html>
  3. <html lang="zh-CN">
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>知识点讲解</title>
  7. <link rel="stylesheet" href="/css/katex/katex.min.css">
  8. @include('pdf.partials.kp-explain-styles')
  9. </head>
  10. <body>
  11. <div class="page">
  12. <div class="kp-explain-header">
  13. <div class="kp-explain-title">知识点讲解</div>
  14. <div class="kp-explain-subtitle">本章节用于梳理本卷涉及的知识点,帮助学生在做题前完成预习/复盘。</div>
  15. </div>
  16. @if(empty($knowledgePoints))
  17. <div class="kp-empty">暂无知识点数据</div>
  18. @else
  19. <div class="kp-list">
  20. @foreach($knowledgePoints as $index => $kp)
  21. <div class="kp-section">
  22. <div class="kp-section-head">
  23. <div class="kp-section-name">{{ $loop->iteration }}、{{ $kp['kp_name'] ?? ($kp['kp_code'] ?? '未命名知识点') }}</div>
  24. </div>
  25. <div class="kp-section-body">
  26. @if(!empty($kp['explanation']))
  27. {{-- 隐藏容器存储原始 Markdown --}}
  28. <div class="kp-markdown-source" style="display:none;">{!! $kp['explanation'] !!}</div>
  29. {{-- 渲染容器 --}}
  30. <div class="kp-markdown-container kp-markdown-content"></div>
  31. @endif
  32. </div>
  33. </div>
  34. @endforeach
  35. </div>
  36. @endif
  37. </div>
  38. {{-- 引入脚本 --}}
  39. <script src="/js/markdown-it.min.js"></script>
  40. <script src="/js/katex.min.js"></script>
  41. <script>
  42. (function() {
  43. 'use strict';
  44. function waitForLibs(callback) {
  45. let attempts = 0;
  46. const maxAttempts = 50;
  47. const interval = setInterval(function() {
  48. attempts++;
  49. if (typeof window.markdownit === 'function') {
  50. clearInterval(interval);
  51. callback();
  52. } else if (attempts >= maxAttempts) {
  53. clearInterval(interval);
  54. console.error('[Render] Libraries failed to load after', maxAttempts, 'attempts');
  55. console.log('[Render] markdownit:', typeof window.markdownit);
  56. callback();
  57. }
  58. }, 100);
  59. }
  60. function renderMarkdown(md, targetEl) {
  61. if (!md) return;
  62. if (typeof window.markdownit !== 'function') {
  63. targetEl.textContent = md;
  64. return;
  65. }
  66. const mdParser = window.markdownit({
  67. html: false,
  68. breaks: false,
  69. linkify: true,
  70. typographer: false
  71. });
  72. let html = mdParser.render(md);
  73. if (typeof window.katex !== 'undefined') {
  74. const katexOptions = {
  75. throwOnError: false,
  76. displayMode: false
  77. };
  78. function decodeEntities(input) {
  79. return input
  80. .replace(/&gt;/g, '>')
  81. .replace(/&lt;/g, '<')
  82. .replace(/&amp;/g, '&')
  83. .replace(/&quot;/g, '"')
  84. .replace(/&#39;/g, "'");
  85. }
  86. // 先渲染块级公式 $$...$$
  87. html = html.replace(/\$\$([\s\S]*?)\$\$/g, function(_, tex) {
  88. try {
  89. const cleaned = decodeEntities(tex.trim());
  90. return window.katex.renderToString(cleaned, { ...katexOptions, displayMode: true });
  91. } catch (e) {
  92. return '<span style="color:red">[KaTeX error]</span>';
  93. }
  94. });
  95. // 再渲染行内公式 $...$
  96. html = html.replace(/\$([^\$\n]+?)\$/g, function(_, tex) {
  97. try {
  98. const cleaned = decodeEntities(tex.trim());
  99. return window.katex.renderToString(cleaned, { ...katexOptions, displayMode: false });
  100. } catch (e) {
  101. return '<span style="color:red">[KaTeX error]</span>';
  102. }
  103. });
  104. }
  105. targetEl.innerHTML = html;
  106. }
  107. function renderAll() {
  108. const containers = document.querySelectorAll('.kp-markdown-container');
  109. containers.forEach((container) => {
  110. const sourceEl = container.previousElementSibling;
  111. let markdown = '';
  112. if (sourceEl && sourceEl.classList.contains('kp-markdown-source')) {
  113. markdown = sourceEl.textContent.trim();
  114. }
  115. if (!markdown) return;
  116. renderMarkdown(markdown, container);
  117. });
  118. }
  119. if (document.readyState === 'loading') {
  120. document.addEventListener('DOMContentLoaded', function() {
  121. waitForLibs(renderAll);
  122. });
  123. } else {
  124. waitForLibs(renderAll);
  125. }
  126. document.addEventListener('livewire:initialized', function() {
  127. waitForLibs(renderAll);
  128. });
  129. document.addEventListener('livewire:navigated', () => setTimeout(function() {
  130. waitForLibs(renderAll);
  131. }, 100));
  132. })();
  133. </script>
  134. </body>
  135. </html>