paper-body.blade.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. @php
  2. $choiceQuestions = $questions['choice'] ?? [];
  3. $fillQuestions = $questions['fill'] ?? [];
  4. $answerQuestions = $questions['answer'] ?? [];
  5. $gradingMode = $grading ?? false;
  6. // 检查是否有数学公式处理标记,避免重复处理
  7. $mathProcessed = false;
  8. // 检查所有题型中是否有任何题目包含 math_processed 标记
  9. foreach ([$choiceQuestions, $fillQuestions, $answerQuestions] as $questionType) {
  10. foreach ($questionType as $q) {
  11. if (isset($q->math_processed) && $q->math_processed) {
  12. $mathProcessed = true;
  13. break 2; // 找到标记就退出两层循环
  14. }
  15. }
  16. }
  17. // 计算填空空格数量
  18. $countBlanks = function($text) {
  19. $count = 0;
  20. $count += preg_match_all('/_{2,}/u', $text, $m);
  21. $count += preg_match_all('/(\s*)/u', $text, $m);
  22. $count += preg_match_all('/\(\s*\)/', $text, $m);
  23. return max(1, $count);
  24. };
  25. // 计算步骤数量
  26. $countSteps = function($text) {
  27. $matches = [];
  28. $cnt = preg_match_all('/第\s*\d+\s*步/u', $text ?? '', $matches);
  29. return max(1, $cnt);
  30. };
  31. $renderBoxes = function($num) {
  32. // 判卷方框放大 1.2 倍,保持单行布局
  33. return str_repeat('<span style="display:inline-block;width:17px;height:17px;line-height:17px;border:1px solid #333;margin-right:4px;vertical-align:middle;"></span>', $num);
  34. };
  35. @endphp
  36. <!-- 一、选择题 -->
  37. <div class="section-title">一、选择题
  38. @if(count($choiceQuestions) > 0)
  39. @php
  40. $choiceTotal = array_sum(array_map(fn($q) => $q->score ?? 5, $choiceQuestions));
  41. @endphp
  42. (本大题共 {{ count($choiceQuestions) }} 小题,共 {{ $choiceTotal }} 分)
  43. @else
  44. (本大题共 0 小题,共 0 分)
  45. @endif
  46. </div>
  47. @if(count($choiceQuestions) > 0)
  48. @foreach($choiceQuestions as $index => $q)
  49. @php
  50. $questionNumber = $index + 1;
  51. $cleanContent = preg_replace('/^\d+[\.、]\s*/', '', $q->content);
  52. $cleanContent = trim($cleanContent);
  53. $options = $q->options ?? [];
  54. if (empty($options)) {
  55. $pattern = '/([A-D])[\.、:.:]\s*(.+?)(?=\s*[A-D][\.、:.:]|$)/su';
  56. if (preg_match_all($pattern, $cleanContent, $matches, PREG_SET_ORDER)) {
  57. foreach ($matches as $match) {
  58. $optionText = trim($match[2]);
  59. if (!empty($optionText)) {
  60. $options[] = $optionText;
  61. }
  62. }
  63. }
  64. }
  65. $stemLine = $cleanContent;
  66. if (!empty($options)) {
  67. if (preg_match('/^(.+?)(?=[A-D][\.、:.:])/su', $cleanContent, $stemMatch)) {
  68. $stemLine = trim($stemMatch[1]);
  69. }
  70. }
  71. // 将题干中的空括号/下划线替换为短波浪线;如无占位符,则在末尾追加短波浪线
  72. $blankSpan = '<span style="display:inline-block; min-width:80px; border-bottom:1.2px dashed #444; vertical-align:bottom;">&nbsp;</span>';
  73. $renderedStem = preg_replace(['/((\s*))/u', '/\(\s*\)/', '/_{2,}/'], $blankSpan, $stemLine);
  74. if ($renderedStem === $stemLine) {
  75. $renderedStem .= ' ' . $blankSpan;
  76. }
  77. $renderedStem = $mathProcessed ? $renderedStem : \App\Services\MathFormulaProcessor::processFormulas($renderedStem);
  78. @endphp
  79. <div class="question">
  80. <div class="question-grid">
  81. <div class="question-lead">
  82. @if($gradingMode)
  83. <span class="grading-boxes">{!! $renderBoxes(1) !!}</span>
  84. @endif
  85. <span class="question-number">{{ $gradingMode ? '题目 ' : '' }}{{ $questionNumber }}.</span>
  86. </div>
  87. <div class="question-main">
  88. <span class="question-stem">{!! $renderedStem !!}</span>
  89. </div>
  90. @if(!empty($options))
  91. @php
  92. // 计算选项长度并动态选择布局
  93. $optCount = count($options);
  94. $maxOptionLength = 0;
  95. foreach ($options as $opt) {
  96. $optText = strip_tags(\App\Services\MathFormulaProcessor::processFormulas($opt));
  97. $maxOptionLength = max($maxOptionLength, mb_strlen($optText, 'UTF-8'));
  98. }
  99. // 根据最长选项长度和选项数量动态选择布局
  100. // 短选项(≤15字符)且选项数≤4:4列布局
  101. // 中等选项(16-30字符)或选项数>4:2列布局
  102. // 长选项(>30字符):1列布局
  103. if ($maxOptionLength <= 15 && $optCount <= 4) {
  104. $optionsClass = 'options-grid-4';
  105. $layoutDesc = '4列布局';
  106. } elseif ($maxOptionLength <= 30) {
  107. $optionsClass = 'options-grid-2';
  108. $layoutDesc = '2列布局';
  109. } else {
  110. $optionsClass = 'options-grid-1';
  111. $layoutDesc = '1列布局';
  112. }
  113. \Illuminate\Support\Facades\Log::debug('选择题布局决策', [
  114. 'question_number' => $questionNumber,
  115. 'opt_count' => $optCount,
  116. 'max_length' => $maxOptionLength,
  117. 'selected_class' => $optionsClass,
  118. 'layout' => $layoutDesc
  119. ]);
  120. @endphp
  121. <div class="question-lead spacer"></div>
  122. <div class="{{ $optionsClass }}">
  123. @foreach($options as $optIndex => $opt)
  124. @php $label = chr(65 + (int)$optIndex); @endphp
  125. <div class="option option-compact">
  126. <strong>{{ $label }}.</strong>&nbsp;{!! $mathProcessed ? $opt : \App\Services\MathFormulaProcessor::processFormulas($opt) !!}
  127. </div>
  128. @endforeach
  129. </div>
  130. @endif
  131. @if($gradingMode)
  132. @php
  133. $solutionText = trim($q->solution ?? '');
  134. // 去掉前置的"解题思路"标签,避免出现"解题思路:【解题思路】"重复
  135. $solutionText = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $solutionText);
  136. $solutionHtml = $solutionText === ''
  137. ? '<span style="color:#999;font-style:italic;">(暂无解题思路)</span>'
  138. : ($mathProcessed ? $solutionText : \App\Services\MathFormulaProcessor::processFormulas($solutionText));
  139. @endphp
  140. <div class="question-lead spacer"></div>
  141. <div class="answer-meta">
  142. <div class="answer-line"><strong>正确答案:</strong><span class="solution-content">{!! $mathProcessed ? ($q->answer ?? '') : \App\Services\MathFormulaProcessor::processFormulas($q->answer ?? '') !!}</span></div>
  143. <div class="answer-line"><strong>解题思路:</strong><span class="solution-content">{!! $solutionHtml !!}</span></div>
  144. </div>
  145. @endif
  146. </div>
  147. </div>
  148. @endforeach
  149. @else
  150. <div class="question">
  151. <div class="question-content" style="font-style: italic; color: #999; padding: 20px; border: 1px dashed #ccc; background: #f9f9f9;">
  152. 该题型正在生成中或暂无题目,请稍后刷新页面查看
  153. </div>
  154. </div>
  155. @endif
  156. <!-- 二、填空题 -->
  157. <div class="section-title">二、填空题
  158. @if(count($fillQuestions) > 0)
  159. @php
  160. $fillTotal = array_sum(array_map(fn($q) => $q->score ?? 5, $fillQuestions));
  161. @endphp
  162. (本大题共 {{ count($fillQuestions) }} 小题,共 {{ $fillTotal }} 分)
  163. @else
  164. (本大题共 0 小题,共 0 分)
  165. @endif
  166. </div>
  167. @if(count($fillQuestions) > 0)
  168. @foreach($fillQuestions as $index => $q)
  169. @php
  170. $questionNumber = count($choiceQuestions) + $index + 1;
  171. $blankSpan = '<span style="display:inline-block; min-width:80px; border-bottom:1.2px dashed #444; vertical-align:bottom;">&nbsp;</span>';
  172. $renderedContent = preg_replace(['/((\s*))/u', '/\(\s*\)/', '/_{2,}/'], $blankSpan, $q->content);
  173. if ($renderedContent === $q->content) {
  174. $renderedContent .= ' ' . $blankSpan;
  175. }
  176. $renderedContent = $mathProcessed ? $renderedContent : \App\Services\MathFormulaProcessor::processFormulas($renderedContent);
  177. @endphp
  178. <div class="question">
  179. <div class="question-grid">
  180. <div class="question-lead">
  181. @if($gradingMode)
  182. <span class="grading-boxes">{!! $renderBoxes($countBlanks($q->content ?? '')) !!}</span>
  183. @endif
  184. <span class="question-number">{{ $gradingMode ? '题目 ' : '' }}{{ $questionNumber }}.</span>
  185. </div>
  186. <div class="question-main">
  187. <span class="question-stem">{!! $renderedContent !!}</span>
  188. </div>
  189. @if($gradingMode)
  190. @php
  191. $solutionText = trim($q->solution ?? '');
  192. // 去掉前置的“解题思路”标签,避免出现“解题思路:【解题思路】”重复
  193. $solutionText = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $solutionText);
  194. $solutionHtml = $solutionText === ''
  195. ? '<span style="color:#999;font-style:italic;">(暂无解题思路)</span>'
  196. : ($mathProcessed ? $solutionText : \App\Services\MathFormulaProcessor::processFormulas($solutionText));
  197. @endphp
  198. <div class="question-lead spacer"></div>
  199. <div class="answer-meta">
  200. <div class="answer-line"><strong>正确答案:</strong><span class="solution-content">{!! $mathProcessed ? ($q->answer ?? '') : \App\Services\MathFormulaProcessor::processFormulas($q->answer ?? '') !!}</span></div>
  201. <div class="answer-line"><strong>解题思路:</strong><span class="solution-content">{!! $solutionHtml !!}</span></div>
  202. </div>
  203. @endif
  204. </div>
  205. </div>
  206. @endforeach
  207. @else
  208. <div class="question">
  209. <div class="question-content" style="font-style: italic; color: #999; padding: 20px; border: 1px dashed #ccc; background: #f9f9f9;">
  210. 该题型正在生成中或暂无题目,请稍后刷新页面查看
  211. </div>
  212. </div>
  213. @endif
  214. <!-- 三、解答题 -->
  215. <div class="section-title">三、解答题
  216. @if(count($answerQuestions) > 0)
  217. (本大题共 {{ count($answerQuestions) }} 小题,共 {{ array_sum(array_column($answerQuestions, 'score')) }} 分。解答应写出文字说明、证明过程或演算步骤)
  218. @else
  219. (本大题共 0 小题,共 0 分)
  220. @endif
  221. </div>
  222. @if(count($answerQuestions) > 0)
  223. @foreach($answerQuestions as $index => $q)
  224. @php
  225. $questionNumber = count($choiceQuestions) + count($fillQuestions) + $index + 1;
  226. @endphp
  227. <div class="question">
  228. <div class="question-grid">
  229. <div class="question-lead">
  230. <span class="question-number">{{ $gradingMode ? '题目 ' : '' }}{{ $questionNumber }}.</span>
  231. </div>
  232. <div class="question-main">
  233. @unless($gradingMode)
  234. <span class="question-score-inline">(本小题满分 {{ $q->score ?? 10 }} 分)</span>
  235. @endunless
  236. <span class="question-stem">{!! $mathProcessed ? $q->content : \App\Services\MathFormulaProcessor::processFormulas($q->content) !!}</span>
  237. </div>
  238. @unless($gradingMode)
  239. <div class="question-lead spacer"></div>
  240. <div class="answer-area boxy">
  241. <span class="answer-label">作答</span>
  242. </div>
  243. @endunless
  244. @if($gradingMode)
  245. @php
  246. $solutionRaw = $q->solution ?? '';
  247. $solutionProcessed = $mathProcessed ? $solutionRaw : \App\Services\MathFormulaProcessor::processFormulas($solutionRaw);
  248. // 去掉分步得分等分值标记
  249. $solutionProcessed = preg_replace('/(\s*\d+\s*分\s*)/u', '', $solutionProcessed);
  250. // 优化解析分段格式
  251. $solutionProcessed = preg_replace('/【(解题思路|详细解答|最终答案)】/u', "\n\n【$1】\n\n", $solutionProcessed);
  252. $solutionProcessed = preg_replace('/^【(解题思路|详细解答|最终答案)】\n\n/u', '<div class="solution-section"><strong>【$1】</strong><br>', $solutionProcessed);
  253. $solutionProcessed = preg_replace('/\n\n【(解题思路|详细解答|最终答案)】\n\n/u', '</div><div class="solution-section"><strong>【$1】</strong><br>', $solutionProcessed);
  254. $solutionProcessed = preg_replace('/\n\n/u', '<br>', $solutionProcessed);
  255. // 为每个"第 N 步"前添加方框;若没有,则在【详细解答】段落开头添加一个方框
  256. if (preg_match('/第\s*\d+\s*步/u', $solutionProcessed)) {
  257. $solutionProcessed = preg_replace_callback('/第\s*\d+\s*步/u', function($m) use ($renderBoxes) {
  258. return '<br><span class="solution-step"><span class="step-box">' . $renderBoxes(1) . '</span><span class="step-label">' . $m[0] . '</span></span>';
  259. }, $solutionProcessed);
  260. } else {
  261. // 在【详细解答】标题后追加方框;若无标题则在正文最前补一个
  262. $injected = false;
  263. $count = 0;
  264. $solutionProcessed = preg_replace(
  265. '/(<strong>【详细解答】<\/strong><br>)/u',
  266. '$1' . '<span class="solution-step"><span class="step-box">' . $renderBoxes(1) . '</span><span class="step-label">&nbsp;</span></span> ',
  267. $solutionProcessed,
  268. 1,
  269. $count
  270. );
  271. if (!empty($count)) {
  272. $injected = true;
  273. }
  274. if (!$injected) {
  275. $solutionProcessed = '<span class="solution-step"><span class="step-box">' . $renderBoxes(1) . '</span><span class="step-label">&nbsp;</span></span> ' . ltrim($solutionProcessed);
  276. }
  277. }
  278. @endphp
  279. <div class="question-lead spacer"></div>
  280. <div class="answer-meta">
  281. <div class="answer-line"><strong>正确答案:</strong><span class="solution-content">{!! $mathProcessed ? ($q->answer ?? '') : \App\Services\MathFormulaProcessor::processFormulas($q->answer ?? '') !!}</span></div>
  282. <div class="answer-line solution-parsed">{!! $solutionProcessed !!}</div>
  283. </div>
  284. @endif
  285. </div>
  286. </div>
  287. @endforeach
  288. @else
  289. <div class="question">
  290. <div class="question-content" style="font-style: italic; color: #999; padding: 20px; border: 1px dashed #ccc; background: #f9f9f9;">
  291. 该题型正在生成中或暂无题目,请稍后刷新页面查看
  292. </div>
  293. </div>
  294. @endif