answer-detail-page.blade.php 12 KB

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