answer-detail-page.blade.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. @php
  2. $choiceQuestions = $questions['choice'] ?? [];
  3. $fillQuestions = $questions['fill'] ?? [];
  4. $answerQuestions = $questions['answer'] ?? [];
  5. $stepPattern = '(步骤\s*[0-9一二三四五六七八九十百零两]+\s*[::]?|第\s*[0-9一二三四五六七八九十百零两]+\s*步\s*[::]?)';
  6. $allQuestions = collect()
  7. ->concat(collect($choiceQuestions)->map(fn ($q) => ['q' => $q, 'detail_type' => 'choice']))
  8. ->concat(collect($fillQuestions)->map(fn ($q) => ['q' => $q, 'detail_type' => 'fill']))
  9. ->concat(collect($answerQuestions)->map(fn ($q) => ['q' => $q, 'detail_type' => 'answer']))
  10. ->sortBy(fn ($item) => (int) (($item['q']->question_number ?? 0)))
  11. ->values();
  12. $normalizeQuickAnswer = function (?string $answer): string {
  13. $answer = trim((string) $answer);
  14. if ($answer === '') {
  15. return '—';
  16. }
  17. $answer = preg_replace('/\s+/u', ' ', $answer) ?? $answer;
  18. $answer = str_replace([';', ';'], ' / ', $answer);
  19. return trim($answer);
  20. };
  21. $quickAnswers = $allQuestions->map(function ($item) use ($normalizeQuickAnswer) {
  22. $q = $item['q'];
  23. $answerText = $normalizeQuickAnswer($q->answer ?? '');
  24. $isLong = mb_strlen(strip_tags($answerText)) > 20
  25. || str_contains($answerText, "\n")
  26. || str_contains($answerText, "\r")
  27. || str_contains($answerText, '{')
  28. || str_contains($answerText, '\\frac')
  29. || str_contains($answerText, '见解析');
  30. $processedAnswer = \App\Services\MathFormulaProcessor::processFormulas($answerText);
  31. // 速查统一为流式:移除显式换行
  32. $processedAnswer = preg_replace('/<br\s*\/?>/iu', ' ', $processedAnswer) ?? $processedAnswer;
  33. return [
  34. 'no' => (int) ($q->question_number ?? 0),
  35. 'answer' => $processedAnswer,
  36. 'long' => $isLong,
  37. ];
  38. })->values();
  39. $normalizeDetailHtml = function (?string $text): string {
  40. $text = trim((string) $text);
  41. if ($text === '') {
  42. return '';
  43. }
  44. // 兼容历史/AI返回的非标准图片标签,统一转为标准 <img>
  45. $text = preg_replace('/<\s*image\b/iu', '<img', $text) ?? $text;
  46. $text = preg_replace('/<\s*\/\s*image\s*>/iu', '', $text) ?? $text;
  47. // 兼容被裁掉 "<" 的图片标签文本:img src="..."/>
  48. $text = preg_replace('/(^|[\s>])img\s+src\s*=\s*([\'"][^\'"]+[\'"])\s*\/?>/iu', '$1<img src=$2 />', $text) ?? $text;
  49. $text = preg_replace('/font-size\s*:[^;"]+;?/iu', '', $text) ?? $text;
  50. $text = preg_replace('/line-height\s*:[^;"]+;?/iu', '', $text) ?? $text;
  51. $text = preg_replace('/style\s*=\s*([\'"])\s*\1/iu', '', $text) ?? $text;
  52. return $text;
  53. };
  54. $formatDetailForReadability = function (?string $text) use ($stepPattern): string {
  55. $text = trim((string) $text);
  56. if ($text === '') {
  57. return '';
  58. }
  59. // 仅做轻量分行:遇到“步骤X”时换行,避免整段拥挤
  60. $text = preg_replace('/\s*(' . $stepPattern . ')/u', '<br>$1', $text) ?? $text;
  61. // 软换行点:分号后允许优先换行,缓解长公式串拥挤
  62. $text = str_replace([';', ';'], [';<wbr>', ';<wbr>'], $text);
  63. // 轻量语义断点:在“则/故/所以”前给换行机会,避免整段公式堆在一行
  64. $text = preg_replace('/,\s*(则|故|所以)/u', ',<br>$1', $text) ?? $text;
  65. // 清理重复换行
  66. $text = preg_replace('/(?:<br>\s*){2,}/u', '<br>', $text) ?? $text;
  67. // 仅移除前导 <br> 标签,避免把 <img> 这类标签的起始 "<" 误删
  68. return preg_replace('/^(?:\s*<br\s*\/?>\s*)+/iu', '', $text) ?? $text;
  69. };
  70. $renderSolutionLikeGrading = function (string $solutionHtml, bool $withStepBoxes = false) use ($stepPattern): string {
  71. $solution = trim($solutionHtml);
  72. if ($solution === '' || $solution === '(无详解)') {
  73. return $solutionHtml;
  74. }
  75. // 去掉分步得分等分值标记(与判卷页一致)
  76. $solution = preg_replace('/(\s*\d+\s*分\s*)/u', '', $solution) ?? $solution;
  77. // 与判卷页一致:先做“解题思路/解题过程”等分段
  78. $solution = preg_replace('/【(解题思路|详细解答|最终答案)】/u', "\n\n===SECTION_START===\n【$1】\n===SECTION_END===\n\n", $solution) ?? $solution;
  79. $solution = preg_replace('/(解题过程\s*[^:\n]*:)/u', "\n\n===SECTION_START===\n【解题过程】\n===SECTION_END===\n\n", $solution) ?? $solution;
  80. $sections = explode('===SECTION_START===', $solution);
  81. $processedSections = [];
  82. foreach ($sections as $section) {
  83. if (trim((string) $section) === '') {
  84. continue;
  85. }
  86. $section = str_replace('===SECTION_END===', '', $section);
  87. if (preg_match('/【(解题思路|详细解答|最终答案|解题过程)】/u', $section, $matches)) {
  88. $sectionTitle = $matches[0];
  89. $sectionContent = preg_replace('/【(解题思路|详细解答|最终答案|解题过程)】/u', '', $section) ?? $section;
  90. if ($withStepBoxes) {
  91. if (preg_match('/' . $stepPattern . '/u', $sectionContent)) {
  92. $parts = preg_split('/(?=' . $stepPattern . ')/u', $sectionContent, -1, PREG_SPLIT_NO_EMPTY) ?: [];
  93. $processed = '';
  94. foreach ($parts as $index => $part) {
  95. $stepText = trim((string) $part);
  96. if ($stepText === '') {
  97. continue;
  98. }
  99. $prefix = $index > 0 ? '<br>' : '';
  100. $isStep = preg_match('/^' . $stepPattern . '/u', $stepText);
  101. if ($isStep) {
  102. $processed .= $prefix
  103. . '<span class="solution-step"><span class="step-box"><span class="detail-grade-box"></span></span><span class="step-label">'
  104. . $stepText
  105. . '</span></span>';
  106. } else {
  107. $processed .= $prefix . '<span class="step-label">' . $stepText . '</span>';
  108. }
  109. }
  110. $sectionContent = $processed;
  111. } else {
  112. $sectionContent = '<span class="solution-step"><span class="step-box"><span class="detail-grade-box"></span></span><span class="step-label">&nbsp;</span></span> ' . trim((string) $sectionContent);
  113. }
  114. }
  115. $processedSections[] = '<div class="solution-section"><strong>' . $sectionTitle . '</strong><br>' . $sectionContent . '</div>';
  116. } else {
  117. $processedSections[] = $section;
  118. }
  119. }
  120. $solution = implode('', $processedSections);
  121. if ($withStepBoxes && (empty($processedSections) || (count($processedSections) === 1 && !str_contains($processedSections[0] ?? '', 'step-box')))) {
  122. if (preg_match('/' . $stepPattern . '/u', $solution)) {
  123. $parts = preg_split('/(?=' . $stepPattern . ')/u', $solution, -1, PREG_SPLIT_NO_EMPTY) ?: [];
  124. $processed = '';
  125. foreach ($parts as $index => $part) {
  126. $stepText = trim((string) $part);
  127. if ($stepText === '') {
  128. continue;
  129. }
  130. $prefix = $index > 0 ? '<br>' : '';
  131. $isStep = preg_match('/^' . $stepPattern . '/u', $stepText);
  132. if ($isStep) {
  133. $processed .= $prefix . '<span class="solution-step"><span class="step-box"><span class="detail-grade-box"></span></span><span class="step-label">' . $stepText . '</span></span>';
  134. } else {
  135. $processed .= $prefix . '<span class="step-label">' . $stepText . '</span>';
  136. }
  137. }
  138. $solution = $processed;
  139. } else {
  140. $solution = '<span class="solution-step"><span class="step-box"><span class="detail-grade-box"></span></span><span class="step-label">' . trim((string) $solution) . '</span></span>';
  141. }
  142. }
  143. $solution = preg_replace('/\n{3,}/u', "\n\n", $solution) ?? $solution;
  144. return nl2br($solution);
  145. };
  146. @endphp
  147. <div class="answer-detail-page">
  148. <div class="answer-quick">
  149. <div class="answer-quick-label">答案速查</div>
  150. <div class="answer-quick-flow answer-quick-flow-ordered">
  151. @foreach($quickAnswers as $item)
  152. <span class="answer-quick-item {{ $item['long'] ? 'answer-quick-item-long' : 'answer-quick-item-compact' }}">
  153. <strong>{{ $item['no'] }}.&nbsp;</strong>{!! $item['answer'] !!}
  154. </span>
  155. @endforeach
  156. </div>
  157. </div>
  158. <div class="answer-detail-two-cols">
  159. @foreach($allQuestions as $item)
  160. @php
  161. $q = $item['q'];
  162. $no = (int) ($q->question_number ?? 0);
  163. $detailType = strtolower((string) ($item['detail_type'] ?? ''));
  164. $isAnswerType = $detailType === 'answer';
  165. $rawAnswer = trim((string) ($q->answer ?? ''));
  166. $rawSolution = $normalizeDetailHtml($q->solution ?? '');
  167. $isSeeSolutionAnswer = (bool) preg_match('/^见\s*解析[。\.]?$|^详见解析/u', $rawAnswer);
  168. $hasImageLikeSolution = (bool) preg_match('/<\s*img\b|<\s*image\b|(^|[\s>])img\s+src\s*=/iu', $rawSolution);
  169. $showAnswer = !$isSeeSolutionAnswer;
  170. $renderAnswer = $rawAnswer !== ''
  171. ? \App\Services\MathFormulaProcessor::processFormulas($rawAnswer)
  172. : '—';
  173. if ($rawSolution === '') {
  174. $renderSolution = '(无详解)';
  175. } elseif ($hasImageLikeSolution) {
  176. // 与判卷页保持一致:图片型解析优先保留原始HTML结构,避免标签被公式处理链打散为纯文本
  177. $renderSolution = $formatDetailForReadability($rawSolution);
  178. } else {
  179. $renderSolution = \App\Services\MathFormulaProcessor::processFormulas($formatDetailForReadability($rawSolution));
  180. }
  181. if ($isAnswerType) {
  182. $renderSolution = $renderSolutionLikeGrading($renderSolution, true);
  183. } else {
  184. $renderSolution = $renderSolutionLikeGrading($renderSolution, false);
  185. }
  186. // 版式规则:
  187. // 1) 选择/填空同行
  188. // 2) 简答默认解析另起一行
  189. // 3) 若答案为“见解析”被隐藏,则题号与【解析】同行
  190. $useSplitAnalysisLine = $isAnswerType && $showAnswer;
  191. @endphp
  192. <div class="answer-detail-item">
  193. @if($useSplitAnalysisLine)
  194. <div class="entry-line entry-answer-line">
  195. <span class="qno">{{ $no }}.</span>
  196. <span class="answer-only">{!! $renderAnswer !!}</span>
  197. </div>
  198. <div class="entry-line entry-analysis-line">
  199. <span class="analysis-tag">【解析】</span>
  200. <span class="solution-only">{!! $renderSolution !!}</span>
  201. </div>
  202. @else
  203. <div class="entry-line entry-inline-line">
  204. <span class="qno">{{ $no }}.</span>
  205. @if($showAnswer)
  206. <span class="answer-only">{!! $renderAnswer !!}</span>
  207. @endif
  208. <span class="analysis-tag">【解析】</span>
  209. <span class="solution-only">{!! $renderSolution !!}</span>
  210. </div>
  211. @endif
  212. </div>
  213. @endforeach
  214. </div>
  215. </div>