Răsfoiți Sursa

fix(exam-sprint): 修复展望报告进度条尖头

金逸霄 2 săptămâni în urmă
părinte
comite
749a5cbc96

+ 21 - 2
abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.java

@@ -458,8 +458,7 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
             }
 
             builder.append("</div>")
-                    .append("<div class='freq-progress'><span style='width:")
-                    .append(card.winRatePercent()).append("%'></span></div>")
+                    .append(renderFrequencyPlanProgressBar(card.winRatePercent(), columnNumber))
                     .append("<div class='freq-data'>提升 <strong>")
                     .append(escape(card.scoreGainLabel()))
                     .append("</strong> · 胜率 <strong>")
@@ -488,6 +487,26 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
         return builder.toString();
     }
 
+    private String renderFrequencyPlanProgressBar(int percent, int columnNumber) {
+        int boundedPercent = Math.max(0, Math.min(100, percent));
+        String clipId = "freq-progress-clip-" + columnNumber;
+        return new StringBuilder()
+                .append("<svg class='freq-progress-svg'").append(SVG_CJK_FONT_FAMILY)
+                .append(" xmlns='http://www.w3.org/2000/svg' width='100%' height='8' viewBox='0 0 100 8' preserveAspectRatio='none' role='img' aria-label='胜率进度")
+                .append(boundedPercent)
+                .append("%'>")
+                .append("<defs><clipPath id='").append(clipId).append("' clipPathUnits='userSpaceOnUse'>")
+                .append("<rect x='0' y='0' width='100' height='8' rx='4' ry='4'/>")
+                .append("</clipPath></defs>")
+                .append("<rect x='0' y='0' width='100' height='8' rx='4' ry='4' fill='#dfe6ef'/>")
+                .append("<rect x='0' y='0' width='").append(boundedPercent)
+                .append("' height='8' fill='#2c8cff' clip-path='url(#")
+                .append(clipId)
+                .append(")'/>")
+                .append("</svg>")
+                .toString();
+    }
+
     private String renderScoreImprovementCaseStudy(ScoreImprovementCaseStudy caseStudy) {
         double progressPercent = Math.max(0d, Math.min(100d, caseStudy.hitRatePercent()));
         double endAngle = -90 + (progressPercent * 3.6);

+ 3 - 10
abilities/exam-sprint/infrastructure/src/main/resources/templates/outlook-exam-sprint-report-template.html

@@ -197,20 +197,13 @@
             font-size: 20px;
         }
 
-        .freq-progress {
-            background: #dfe6ef;
-            border-radius: 999px;
+        .freq-progress-svg {
+            display: block;
+            width: 100%;
             height: 8px;
-            overflow: hidden;
             margin-bottom: 12px;
         }
 
-        .freq-progress > span {
-            display: block;
-            height: 100%;
-            background: #2c8cff;
-        }
-
         .freq-data {
             font-family: MiSans, ReportFont, sans-serif;
             color: #4a5568;

+ 21 - 1
abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.java

@@ -169,6 +169,25 @@ class ClasspathOutlookExamSprintReportRendererTest {
                 .containsPattern("<text class='chart-axis-tick-label chart-axis-tick-label-y' x='26' y='54' text-anchor='end' fill='#7f8b97' font-size='11'>5</text>");
     }
 
+    /**
+     * 覆盖科学备考建议频率卡片的线性进度条时,应使用 PDF 友好的 SVG 几何图形,避免 CSS 圆角裁剪在 PDF 中变尖。
+     */
+    @Test
+    void renderUsesSvgLinearProgressBarsForFrequencyPlanCards() throws Exception {
+        ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);
+
+        String html = renderer.render(unmodeledOutlookContent(callerVocabularyPayload()), Instant.parse("2026-01-03T08:00:00Z"));
+
+        assertThat(html)
+                .contains("<svg class='freq-progress-svg'")
+                .contains("viewBox='0 0 100 8'")
+                .contains("preserveAspectRatio='none'")
+                .contains("<clipPath id='freq-progress-clip-1' clipPathUnits='userSpaceOnUse'>")
+                .contains("<rect x='0' y='0' width='100' height='8' rx='4' ry='4' fill='#dfe6ef'/>")
+                .contains("<rect x='0' y='0' width='72' height='8' fill='#2c8cff' clip-path='url(#freq-progress-clip-2)'/>")
+                .doesNotContain("<div class='freq-progress'><span style='width:");
+    }
+
     /**
      * 覆盖官方上游词汇 payload 渲染所有内联 SVG 时,每个 SVG 起始标签都应声明 Batik 可识别的 CJK 字体族。
      */
@@ -183,8 +202,9 @@ class ClasspathOutlookExamSprintReportRendererTest {
                 .contains("<svg class='past-paper-column-chart' font-family=\"'MiSans VF', MiSans, ReportFont, sans-serif\"")
                 .contains("<svg class='high-frequency-column-chart' font-family=\"'MiSans VF', MiSans, ReportFont, sans-serif\"")
                 .contains("<svg class='frequency-band-column-chart' font-family=\"'MiSans VF', MiSans, ReportFont, sans-serif\"")
+                .contains("<svg class='freq-progress-svg' font-family=\"'MiSans VF', MiSans, ReportFont, sans-serif\"")
                 .contains("<svg font-family=\"'MiSans VF', MiSans, ReportFont, sans-serif\" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 260 260'");
-        assertThat(svgStartTags(html)).hasSize(5).allSatisfy(svg -> assertThat(svg).contains(SVG_CJK_FONT_FAMILY));
+        assertThat(svgStartTags(html)).hasSize(8).allSatisfy(svg -> assertThat(svg).contains(SVG_CJK_FONT_FAMILY));
     }
 
     /**