| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <title>数学知识点讲解</title>
- <link rel="stylesheet" href="/css/katex/katex.min.css">
- @include('pdf.partials.kp-explain-styles')
- <style>
- :root {
- --pdf-space-xxs: 2px;
- --pdf-space-xs: 4px;
- --pdf-space-sm: 6px;
- --pdf-space-md: 10px;
- --pdf-border-light: #d8d8d8;
- --pdf-border-strong: #777;
- --pdf-text-primary: #000;
- --pdf-text-secondary: #555;
- --pdf-radius-soft: 2px;
- --pdf-line-normal: 1.75;
- --pdf-line-relaxed: 1.9;
- }
- /* 与 exam-paper 一致:题干网格、选项网格、判卷答案区基础样式 */
- @include('pdf.partials.paper-body-core-styles')
- .case-list .option-compact { line-height: inherit; }
- .case-list .option .katex {
- font-size: 1em !important;
- vertical-align: 0;
- }
- .case-list .option .katex .mfrac {
- font-size: 1em !important;
- }
- .case-list .option .katex .mfrac .mtight {
- font-size: 1em !important;
- }
- .case-list .option .katex .frac-line {
- border-bottom-width: 0.055em !important;
- }
- .case-list .option .katex .mfrac .vlist > span:nth-child(1) {
- transform: translateY(0.24em) !important;
- }
- .case-list .option .katex .mfrac .vlist > span:nth-child(3) {
- transform: translateY(-0.16em) !important;
- }
- .case-list .option .katex-display {
- display: inline;
- margin: 0 !important;
- vertical-align: baseline;
- }
- .case-list .answer-meta {
- font-size: 12px;
- color: #2f2f2f;
- line-height: var(--pdf-line-normal);
- margin-top: var(--pdf-space-xs);
- page-break-inside: auto;
- break-inside: auto;
- }
- .case-list .answer-line + .answer-line { margin-top: var(--pdf-space-xs); }
- .case-list .solution-content { display: inline-block; line-height: var(--pdf-line-normal); }
- /* ========== 案例区整体 ========== */
- .case-list { margin-top: 14px; }
- .case-list .case-item {
- margin: 10px 0 14px;
- padding: 8px var(--pdf-space-md);
- border: none;
- border-left: 1.5px solid var(--pdf-border-light);
- border-radius: var(--pdf-radius-soft);
- background: #fff;
- line-height: var(--pdf-line-normal);
- break-inside: auto;
- page-break-inside: auto;
- orphans: 2;
- widows: 2;
- }
- .case-list .case-item + .case-item { margin-top: 9px; }
- /* ========== 题干区域 ========== */
- .case-list .kp-case-row {
- font-size: 14.5px;
- line-height: 1.8;
- margin-bottom: 3px;
- break-inside: auto;
- page-break-inside: auto;
- break-after: auto;
- page-break-after: auto;
- orphans: 2;
- widows: 2;
- }
- .case-list .kp-case-head-inline {
- display: inline;
- }
- .case-list .kp-case-prefix {
- font-weight: 700;
- white-space: nowrap;
- }
- .case-list .kp-case-title {
- font-size: 15px;
- font-weight: 700;
- color: var(--pdf-text-primary);
- margin-right: var(--pdf-space-xxs);
- white-space: nowrap;
- }
- .case-list .kp-case-source {
- font-size: 13px;
- color: var(--pdf-text-secondary);
- margin-right: 3px;
- font-weight: 700;
- }
- .case-list .kp-case-head-content {
- display: inline;
- }
- .case-list .kp-case-stem {
- font-size: 14.5px;
- font-weight: 400;
- line-height: 1.85;
- word-break: normal;
- overflow-wrap: break-word;
- break-inside: auto;
- page-break-inside: auto;
- orphans: 2;
- widows: 2;
- }
- /* ========== 解析区 ========== */
- .case-list .kp-case-meta-block { margin-top: 3px; line-height: 1.85; }
- .case-list .kp-case-meta-row {
- margin-top: 3px;
- text-indent: 0;
- page-break-inside: auto;
- break-inside: auto;
- }
- .case-list .kp-case-content {
- display: block;
- font-size: 14px;
- line-height: var(--pdf-line-relaxed);
- color: #222;
- white-space: normal;
- word-break: break-word;
- overflow-wrap: anywhere;
- orphans: 2;
- widows: 2;
- }
- .case-list .kp-case-answer-row {
- margin: var(--pdf-space-xs) 0 var(--pdf-space-sm);
- padding: 3px var(--pdf-space-sm);
- background: #f6f6f6;
- border-radius: var(--pdf-radius-soft);
- }
- .case-list .kp-case-solution-row .solution-content {
- display: block;
- margin-top: 2px;
- }
- /* 仅提升案例区 display 公式呼吸感,不动全局 KaTeX 行高 */
- .case-list .kp-case-content .katex-display,
- .case-list .kp-case-stem .katex-display {
- margin-top: 0.45em !important;
- margin-bottom: 0.5em !important;
- }
- .case-list .kp-case-content .complex-display-math,
- .case-list .kp-case-stem .complex-display-math {
- margin-top: 0.6em !important;
- margin-bottom: 0.65em !important;
- }
- /* ========== 小标题 ========== */
- .case-list .case-subtitle {
- display: block;
- font-weight: 700;
- font-size: 14px;
- margin-top: 8px;
- margin-bottom: var(--pdf-space-xxs);
- padding-left: 5px;
- border-left: 2px solid var(--pdf-border-strong);
- line-height: 1.35;
- color: var(--pdf-text-primary);
- break-after: avoid;
- page-break-after: avoid;
- }
- /* 标题+首段语义绑定:只对真正短小的纯文本节 avoid;含图/大公式自动分页,减少大面积空白 */
- .case-list .case-section {
- margin-top: var(--pdf-space-xs);
- break-inside: auto;
- page-break-inside: auto;
- }
- .case-list .case-section + .case-section { margin-top: 5px; }
- .case-list .case-section.case-section-keep {
- break-inside: avoid;
- page-break-inside: avoid;
- }
- .case-list .case-section.case-section-long {
- break-inside: auto;
- page-break-inside: auto;
- }
- .case-list .case-section-content { margin-top: 1px; }
- /* ========== 分页控制 ========== */
- .case-list .case-analysis {
- break-inside: auto;
- page-break-inside: auto;
- }
- .case-list .case-detail {
- break-inside: auto;
- page-break-inside: auto;
- }
- /* ========== 多小题 ========== */
- .case-list .sub-question {
- display: block;
- margin: 4px 0;
- padding-left: 2em;
- text-indent: -2em;
- line-height: 1.8;
- }
- /* 图片:与 exam-paper / 判卷一致 */
- @include('pdf.partials.paper-exam-shared-image-styles')
- /* 案例区优先节省纸张:不要让图和后续小题/答案强绑定到下一页 */
- .case-list .pdf-figure {
- break-inside: auto !important;
- page-break-inside: auto !important;
- -webkit-column-break-inside: auto !important;
- break-before: auto !important;
- break-after: auto !important;
- page-break-before: auto !important;
- page-break-after: auto !important;
- margin: 4px 0 !important;
- min-height: 0 !important;
- max-height: none !important;
- }
- .case-list .pdf-figure img,
- .case-list .kp-case-stem img {
- margin-top: 4px !important;
- margin-bottom: 4px !important;
- max-height: 48mm !important;
- }
- </style>
- </head>
- <body>
- <div class="page">
- <div class="kp-explain-header">
- <div class="kp-explain-title">数学知识点讲解</div>
- <div class="kp-explain-subtitle">围绕目标知识点生成讲解与案例,帮助学生高效复习。</div>
- </div>
- {{-- 知识点正文:与 pdf.exam-knowledge-explanation(知识点组卷前置梳理)同一套容器与数据(buildExplanations + normalizeKpExplanation) --}}
- @if(empty($knowledgePoints))
- <div class="kp-empty">暂无知识点数据</div>
- @else
- <div class="kp-list">
- @foreach($knowledgePoints as $point)
- <div class="kp-section">
- <div class="kp-section-head">
- <div class="kp-section-name">{{ $loop->iteration }}、{{ $point['kp_name'] ?? ($point['kp_code'] ?? '未命名知识点') }}</div>
- </div>
- <div class="kp-section-body">
- @if(!empty($point['explanation']))
- {!! $point['explanation'] !!}
- @endif
- </div>
- @if(!empty($point['cases']))
- @php
- // 与卷子题干一致的小题号断行逻辑(仅在至少2个小题编号时生效)
- $formatStemLikePaper = function (?string $text): string {
- $stem = trim((string) $text);
- if ($stem === '') {
- return '—';
- }
- preg_match_all('/[((][1-9][0-9]*[))]/u', $stem, $subQuestionMatches);
- $subQuestionCount = count($subQuestionMatches[0] ?? []);
- if ($subQuestionCount >= 2) {
- $stem = preg_replace('/^\s*([((][1-9][0-9]*[))])\s*/u', '$1 ', $stem) ?? $stem;
- // 断行后保留两个中文空格缩进
- $stem = preg_replace('/([。;;!?!?::.])\s*([((][1-9][0-9]*[))])\s*/u', '$1<br> $2 ', $stem) ?? $stem;
- $stem = preg_replace('/(?:(?:\\\\r\\\\n|\\\\n)|(?:\r?\n)|(?:<br\s*\/?>)|\s)+\s*([((][1-9][0-9]*[))])\s*/u', '<br> $1 ', $stem) ?? $stem;
- $stem = preg_replace('/(求出|求解|求|写出|计算|证明|判断|化简)\s*([((][1-9][0-9]*[))])\s*/u', '$1<br> $2 ', $stem) ?? $stem;
- }
- return $stem;
- };
- $markComplexDisplayMath = function (string $html): string {
- return preg_replace_callback(
- '/<span\b([^>]*\bclass="[^"]*\bkatex-display\b[^"]*"[^>]*)>(.*?)<\/span>/isu',
- static function (array $matches): string {
- $attrs = $matches[1] ?? '';
- $content = $matches[2] ?? '';
- $isComplex = str_contains($content, 'mfrac') || str_contains($content, 'vlist');
- if (!$isComplex || str_contains($attrs, 'complex-display-math')) {
- return $matches[0];
- }
- $attrs = preg_replace('/\bclass="([^"]*)"/u', 'class="$1 complex-display-math"', $attrs, 1) ?? $attrs;
- return '<span' . $attrs . '>' . $content . '</span>';
- },
- $html
- ) ?? $html;
- };
- // 解析:移除“讲解/解析”前缀,结构化分析/详解小标题,并保守处理步骤换行
- $formatSolution = function (?string $text) use ($markComplexDisplayMath): string {
- $solution = trim((string) $text);
- if ($solution === '') {
- return '—';
- }
- $solution = preg_replace('/^\s*[【\[]?\s*(讲解|解析)\s*[】\]]?\s*[::]\s*/u', '', $solution) ?? $solution;
- $solution = preg_replace('/\s*【\s*(分析|详解|点睛)\s*】\s*/u', '<div class="case-subtitle">$1</div>', $solution) ?? $solution;
- $solution = preg_replace('/\s*\[\s*(分析|详解|点睛)\s*\]\s*/u', '<div class="case-subtitle">$1</div>', $solution) ?? $solution;
- $solution = preg_replace('/\s*(步骤\s*[0-9一二三四五六七八九十百零两]+\s*[::]?)/u', '<br>$1', $solution) ?? $solution;
- $solution = preg_replace('/\s*(第\s*[0-9一二三四五六七八九十百零两]+\s*步\s*[::]?)/u', '<br>$1', $solution) ?? $solution;
- $plainLength = mb_strlen(strip_tags($solution), 'UTF-8');
- if ($plainLength > 160) {
- $solution = preg_replace('/([。;])\s*/u', '$1<br>', $solution) ?? $solution;
- }
- $solution = preg_replace('/(<\/div>)\s*<br>\s*(步骤\s*[0-9一二三四五六七八九十百零两]+\s*[::]?)/u', '$1$2', $solution) ?? $solution;
- $solution = preg_replace('/(<\/div>)\s*<br>\s*(第\s*[0-9一二三四五六七八九十百零两]+\s*步\s*[::]?)/u', '$1$2', $solution) ?? $solution;
- $solution = preg_replace('/^(?:\s*<br\s*\/?>\s*)+/iu', '', $solution) ?? $solution;
- $solution = preg_replace('/(?:<br>\s*){2,}/u', '<br>', $solution) ?? $solution;
- $solution = preg_replace('/(?:\s*<br>\s*)*(<div class="case-subtitle">)/u', '$1', $solution) ?? $solution;
- $solution = $markComplexDisplayMath($solution);
- // 将「分析/详解」转换为可分页语义块:标题 + 内容。
- // 仅短小纯文本节绑定;含图/display 公式的节允许自然分页,减少整块推页空白。
- $caseSectionClass = static function (string $body, bool $hasTitle = true): string {
- $plainLength = mb_strlen(trim(strip_tags($body)), 'UTF-8');
- $containsImage = str_contains($body, '<img') || str_contains($body, 'pdf-figure');
- $containsDisplayMath = str_contains($body, 'katex-display');
- $isShortSection = $plainLength < 120 && !$containsImage && !$containsDisplayMath;
- if ($hasTitle) {
- return $isShortSection ? 'case-section case-section-keep' : 'case-section case-section-long';
- }
- return $isShortSection ? 'case-section case-section-keep case-section-plain' : 'case-section case-section-long case-section-plain';
- };
- $chunks = preg_split('/<div class="case-subtitle">(.*?)<\/div>/u', $solution, -1, PREG_SPLIT_DELIM_CAPTURE);
- if (is_array($chunks) && count($chunks) > 1) {
- $sectionHtml = '';
- $lead = trim((string) ($chunks[0] ?? ''));
- if ($lead !== '') {
- $leadClass = $caseSectionClass($lead, false);
- $sectionHtml .= '<div class="' . $leadClass . '"><div class="case-section-content">' . $lead . '</div></div>';
- }
- for ($i = 1; $i < count($chunks); $i += 2) {
- $title = trim((string) ($chunks[$i] ?? ''));
- $body = trim((string) ($chunks[$i + 1] ?? ''));
- if ($title === '' && $body === '') {
- continue;
- }
- $sectionClass = $caseSectionClass($body, true);
- $sectionHtml .= '<div class="' . $sectionClass . '"><div class="case-subtitle">' . $title . '</div><div class="case-section-content">' . $body . '</div></div>';
- }
- if ($sectionHtml !== '') {
- $solution = $sectionHtml;
- }
- } else {
- $plainClass = $caseSectionClass($solution, false);
- $solution = '<div class="' . $plainClass . '"><div class="case-section-content">' . $solution . '</div></div>';
- }
- return $solution;
- };
- @endphp
- <div class="kp-markdown"><h3>案例分析</h3></div>
- <div class="case-list">
- @foreach($point['cases'] as $case)
- @php
- $sourceText = '';
- if (!empty($case['child_kp_name'])) {
- // 只展示子知识点来源,不展示父知识点名称
- $sourceText = trim((string) $case['child_kp_name']);
- $parentName = trim((string) ($point['kp_name'] ?? ''));
- if ($parentName !== '') {
- $escapedParent = preg_quote($parentName, '/');
- $sourceText = preg_replace('/^' . $escapedParent . '\s*[-—-\/]\s*/u', '', $sourceText) ?? $sourceText;
- }
- if (preg_match('/[-—-\/]/u', $sourceText)) {
- $parts = preg_split('/\s*[-—-\/]\s*/u', $sourceText);
- if (is_array($parts) && !empty($parts)) {
- $sourceText = trim((string) end($parts));
- }
- }
- } elseif (!empty($case['is_wrong_case'])) {
- $sourceText = '错题讲解';
- } elseif (($case['source_type'] ?? '') === 'reviewed') {
- $sourceText = '已做题';
- } elseif (($case['source_type'] ?? '') === 'fallback') {
- $sourceText = '补充题';
- }
- @endphp
- <div class="case-item">
- <div class="kp-case-row">
- @php
- $stemLine = trim((string) ($case['stem'] ?? ''));
- if ($stemLine === '') {
- $renderedStemHtml = '—';
- } else {
- [$renderedStemHtml] = \App\Support\BlankPlaceholderRenderer::replaceToBlankSpan($stemLine, null, true, false);
- $renderedStemHtml = \App\Support\BlankPlaceholderRenderer::normalizeTerminalPunctuation($renderedStemHtml, 'remove');
- $renderedStemHtml = $formatStemLikePaper($renderedStemHtml);
- $renderedStemHtml = \App\Services\MathFormulaProcessor::processFormulas($renderedStemHtml);
- $renderedStemHtml = $markComplexDisplayMath($renderedStemHtml);
- }
- @endphp
- <div class="kp-case-head-inline">
- <span class="kp-case-prefix">
- <span class="kp-case-title">例{{ $loop->iteration }}.</span>
- @if($sourceText !== '')
- <span class="kp-case-source">({{ $sourceText }})</span>
- @endif
- </span>
- <span class="kp-case-head-content">
- <span class="kp-case-stem">{!! $renderedStemHtml !!}</span>
- </span>
- </div>
- @php
- $options = (array) ($case['options'] ?? []);
- @endphp
- @if(!empty($options))
- @include('pdf.partials.exam-choice-options', [
- 'options' => $options,
- 'gradingMode' => false,
- 'mathProcessed' => false,
- 'logQuestionNumber' => 'kp-' . ($case['question_id'] ?? $loop->iteration),
- 'showLeadSpacer' => false,
- ])
- @endif
- </div>
- <div class="kp-case-meta-block">
- @php
- $layoutDeciderService = app(\App\Support\OptionLayoutDecider::class);
- $choiceAnswerRaw = $layoutDeciderService->normalizeCompactMathForDisplay(trim((string) ($case['answer'] ?? '')));
- $solutionText = trim((string) ($case['solution'] ?? ''));
- $solutionText = preg_replace('/^【?\s*解题思路\s*】?\s*[::]?\s*/u', '', $solutionText) ?? $solutionText;
- $solutionProcessed = $solutionText === ''
- ? ''
- : \App\Services\MathFormulaProcessor::processFormulas($solutionText);
- $solutionHtml = $solutionProcessed === ''
- ? '<span style="color:#999;font-style:italic;">(暂无解题思路)</span>'
- : $formatSolution($solutionProcessed);
- $answerLineHtml = $choiceAnswerRaw === ''
- ? '—'
- : \App\Services\MathFormulaProcessor::processFormulas($choiceAnswerRaw);
- @endphp
- <div class="answer-meta">
- <div class="answer-line kp-case-answer-row"><strong>正确答案:</strong><span class="solution-content">{!! $answerLineHtml !!}</span></div>
- <div class="answer-line kp-case-solution-row"><strong>解题思路:</strong><span class="solution-content kp-case-content">{!! $solutionHtml !!}</span></div>
- </div>
- </div>
- </div>
- @endforeach
- </div>
- @endif
- </div>
- @endforeach
- </div>
- @endif
- </div>
- </body>
- </html>
|