preview-tool.blade.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>题目预览验证工具 - Math CMS</title>
  7. <!-- KaTeX CSS -->
  8. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
  9. <!-- Tailwind CSS CDN -->
  10. <script src="https://cdn.tailwindcss.com"></script>
  11. <!-- KaTeX JS -->
  12. <script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
  13. <style>
  14. [x-cloak] { display: none !important; }
  15. .math-preview {
  16. font-family: "SimSun", "Songti SC", serif;
  17. line-height: 1.8;
  18. }
  19. .math-preview .katex {
  20. font-size: 1.1em;
  21. }
  22. .math-preview .katex-display {
  23. margin: 0.5em 0;
  24. }
  25. /* 题目样式 */
  26. .question-stem {
  27. margin-bottom: 1rem;
  28. }
  29. .question-options {
  30. margin-left: 1rem;
  31. }
  32. .question-option {
  33. margin: 0.5rem 0;
  34. }
  35. .question-answer {
  36. margin-top: 1rem;
  37. padding-top: 0.5rem;
  38. border-top: 1px dashed #ccc;
  39. }
  40. .question-solution {
  41. margin-top: 1rem;
  42. padding: 0.75rem;
  43. background: #f9f9f9;
  44. border-radius: 4px;
  45. }
  46. </style>
  47. <!-- Livewire Styles -->
  48. <style>
  49. [wire\:loading], [wire\:loading\.delay], [wire\:loading\.inline-block], [wire\:loading\.inline], [wire\:loading\.block], [wire\:loading\.flex], [wire\:loading\.table], [wire\:loading\.grid], [wire\:loading\.inline-flex] {
  50. display: none;
  51. }
  52. [wire\:loading\.delay\.shortest], [wire\:loading\.delay\.shorter], [wire\:loading\.delay\.short], [wire\:loading\.delay\.long], [wire\:loading\.delay\.longer], [wire\:loading\.delay\.longest] {
  53. display: none;
  54. }
  55. [wire\:offline] {
  56. display: none;
  57. }
  58. [wire\:dirty]:not(textarea):not(input):not(select) {
  59. display: none;
  60. }
  61. </style>
  62. @livewireStyles
  63. </head>
  64. <body class="bg-gray-100 min-h-screen">
  65. {{ $slot }}
  66. <!-- Livewire Scripts -->
  67. @livewireScripts
  68. <script>
  69. /**
  70. * 数学公式渲染器
  71. * 与前端 MathText.tsx 逻辑保持一致
  72. */
  73. const MathRenderer = {
  74. /**
  75. * 预处理文本:处理双反斜杠、换行符等
  76. */
  77. preprocessText(text) {
  78. let result = text;
  79. // 1. 处理公式内的双反斜杠 -> 单反斜杠
  80. // 块级公式 $$...$$
  81. result = result.replace(/\$\$([\s\S]*?)\$\$/g, (_, tex) => {
  82. return '$$' + tex.replace(/\\\\/g, '\\') + '$$';
  83. });
  84. // 行内公式 $...$
  85. result = result.replace(/\$([^$\n]+?)\$/g, (_, tex) => {
  86. return '$' + tex.replace(/\\\\/g, '\\') + '$';
  87. });
  88. // 2. 换行符处理:\n -> <br>,但保护 LaTeX 命令如 \neq, \nu
  89. result = result.replace(/\\n(?![a-zA-Z])/g, '<br>');
  90. // 3. 统一 LaTeX 分隔符格式
  91. result = result.replace(/\\\[([\s\S]*?)\\\]/g, (_, tex) => `$$${tex}$$`);
  92. result = result.replace(/\\\(([\s\S]*?)\\\)/g, (_, tex) => `$${tex}$`);
  93. return result;
  94. },
  95. /**
  96. * 渲染数学公式
  97. */
  98. render(text) {
  99. if (!text) return '';
  100. let result = this.preprocessText(text);
  101. // 渲染块级公式 $$...$$
  102. result = result.replace(/\$\$([\s\S]*?)\$\$/g, (_, tex) => {
  103. try {
  104. return katex.renderToString(tex.trim(), {
  105. displayMode: true,
  106. throwOnError: false,
  107. strict: false,
  108. });
  109. } catch (e) {
  110. console.error('KaTeX render error:', e);
  111. return `<span class="text-red-500">$$${tex}$$</span>`;
  112. }
  113. });
  114. // 渲染行内公式 $...$
  115. result = result.replace(/\$([^$\n]+?)\$/g, (_, tex) => {
  116. try {
  117. return katex.renderToString(tex.trim(), {
  118. displayMode: false,
  119. throwOnError: false,
  120. strict: false,
  121. });
  122. } catch (e) {
  123. console.error('KaTeX render error:', e);
  124. return `<span class="text-red-500">$${tex}$</span>`;
  125. }
  126. });
  127. return result;
  128. },
  129. /**
  130. * 渲染指定元素内的所有数学公式
  131. */
  132. renderElement(element) {
  133. const mathElements = element.querySelectorAll('[data-math]');
  134. mathElements.forEach(el => {
  135. const rawText = el.getAttribute('data-math');
  136. if (rawText) {
  137. el.innerHTML = this.render(rawText);
  138. }
  139. });
  140. }
  141. };
  142. // 监听 Livewire 事件
  143. document.addEventListener('livewire:init', () => {
  144. Livewire.on('render-math', () => {
  145. setTimeout(() => {
  146. const previewArea = document.getElementById('web-preview-area');
  147. if (previewArea) {
  148. MathRenderer.renderElement(previewArea);
  149. }
  150. }, 100);
  151. });
  152. });
  153. // 页面加载完成后渲染
  154. document.addEventListener('DOMContentLoaded', () => {
  155. const previewArea = document.getElementById('web-preview-area');
  156. if (previewArea) {
  157. MathRenderer.renderElement(previewArea);
  158. }
  159. });
  160. </script>
  161. </body>
  162. </html>