Explorar o código

fix(exam-sprint): 居中展望报告圆环文案

金逸霄 hai 2 semanas
pai
achega
ea31083b4e

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

@@ -271,9 +271,11 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
                 .append(" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 220' role='img' aria-label='考纲词汇掌握情况'>")
                 .append(" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 220' role='img' aria-label='考纲词汇掌握情况'>")
                 .append("<circle class='chart-track' cx='110' cy='110' r='76' fill='none' stroke='#e8eef7' stroke-width='18'></circle>")
                 .append("<circle class='chart-track' cx='110' cy='110' r='76' fill='none' stroke='#e8eef7' stroke-width='18'></circle>")
                 .append(renderProgressRing("donut-mastered-arc", "donut-mastered-full-circle", 110, 110, 76, masteredPercent, "#448aff"))
                 .append(renderProgressRing("donut-mastered-arc", "donut-mastered-full-circle", 110, 110, 76, masteredPercent, "#448aff"))
-                .append("<text class='chart-percent' x='110' y='106' text-anchor='middle' fill='#2b4c8a' font-size='28' font-weight='700'>")
+                .append("<g class='donut-center-label' transform='translate(110 110)'>")
+                .append("<text class='chart-percent' x='0' y='-6' text-anchor='middle' fill='#2b4c8a' font-size='28' font-weight='700'>")
                 .append(formatTwoDecimals(masteredPercent)).append("%</text>")
                 .append(formatTwoDecimals(masteredPercent)).append("%</text>")
-                .append("<text class='chart-caption' x='110' y='131' text-anchor='middle' fill='#5f6b7a' font-size='14'>掌握率</text>")
+                .append("<text class='chart-caption' x='0' y='19' text-anchor='middle' fill='#5f6b7a' font-size='14'>掌握率</text>")
+                .append("</g>")
                 .append("</svg>")
                 .append("</svg>")
                 .append("</div>")
                 .append("</div>")
                 .append("<div class='data-text'>")
                 .append("<div class='data-text'>")
@@ -533,9 +535,11 @@ public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintRepor
                 .append(" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 260 260' role='img' aria-label='上届学员提分案例图示'>")
                 .append(" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 260 260' role='img' aria-label='上届学员提分案例图示'>")
                 .append("<circle cx='130' cy='130' r='86' fill='none' stroke='#ffe3c7' stroke-width='18'></circle>")
                 .append("<circle cx='130' cy='130' r='86' fill='none' stroke='#ffe3c7' stroke-width='18'></circle>")
                 .append(renderProgressRing("case-progress-arc", "case-progress-full-circle", 130, 130, 86, progressPercent, "#ff7d00"))
                 .append(renderProgressRing("case-progress-arc", "case-progress-full-circle", 130, 130, 86, progressPercent, "#ff7d00"))
-                .append("<text x='130' y='124' text-anchor='middle' fill='#8a5d36' font-size='26' font-weight='700'>")
+                .append("<g class='case-center-label' transform='translate(130 130)'>")
+                .append("<text x='0' y='-6' text-anchor='middle' fill='#8a5d36' font-size='26' font-weight='700'>")
                 .append(formatOneDecimal(progressPercent)).append("%</text>")
                 .append(formatOneDecimal(progressPercent)).append("%</text>")
-                .append("<text x='130' y='152' text-anchor='middle' fill='#8a5d36' font-size='12'>命中率</text>")
+                .append("<text x='0' y='20' text-anchor='middle' fill='#8a5d36' font-size='12'>命中率</text>")
+                .append("</g>")
                 .append("</svg>")
                 .append("</svg>")
                 .append("</div>")
                 .append("</div>")
                 .append("</td>")
                 .append("</td>")

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

@@ -355,6 +355,44 @@ class ClasspathOutlookExamSprintReportRendererTest {
                 .doesNotContain("class='donut-unmastered-arc'");
                 .doesNotContain("class='donut-unmastered-arc'");
     }
     }
 
 
+    /**
+     * 覆盖考纲圆环中心文案渲染时,应将百分比与“掌握率”包进围绕圆心(110,110)定位的标签组,而不是继续输出两个绝对 baseline 文本。
+     */
+    @Test
+    void renderSyllabusDonutAnchorsCenterCopyAsCircleCenteredLabelGroup() throws Exception {
+        ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);
+
+        String html = renderer.render(unmodeledOutlookContent(callerVocabularyPayload()), Instant.parse("2026-01-03T08:00:00Z"));
+
+        String syllabusSvg = extractSvgByClass(html, "syllabus-donut-chart");
+
+        assertThat(syllabusSvg)
+                .contains("<g class='donut-center-label' transform='translate(110 110)'>")
+                .contains("<text class='chart-percent' x='0' y='-6' text-anchor='middle' fill='#2b4c8a' font-size='28' font-weight='700'>40.00%</text>")
+                .contains("<text class='chart-caption' x='0' y='19' text-anchor='middle' fill='#5f6b7a' font-size='14'>掌握率</text>")
+                .doesNotContain("class='chart-percent' x='110' y='106'")
+                .doesNotContain("class='chart-caption' x='110' y='131'");
+    }
+
+    /**
+     * 覆盖学员案例命中率圆环中心文案渲染时,应将百分比与“命中率”包进围绕圆心(130,130)定位的标签组。
+     */
+    @Test
+    void renderCaseStudyRingAnchorsCenterCopyAsCircleCenteredLabelGroup() throws Exception {
+        ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);
+
+        String html = renderer.render(unmodeledOutlookContent(callerVocabularyPayload()), Instant.parse("2026-01-03T08:00:00Z"));
+
+        String caseStudySvg = extractSvgByAriaLabel(html, "上届学员提分案例图示");
+
+        assertThat(caseStudySvg)
+                .contains("<g class='case-center-label' transform='translate(130 130)'>")
+                .contains("<text x='0' y='-6' text-anchor='middle' fill='#8a5d36' font-size='26' font-weight='700'>33.8%</text>")
+                .contains("<text x='0' y='20' text-anchor='middle' fill='#8a5d36' font-size='12'>命中率</text>")
+                .doesNotContain("<text x='130' y='124'")
+                .doesNotContain("<text x='130' y='152'");
+    }
+
     private static final class TrackingObjectMapper extends ObjectMapper {
     private static final class TrackingObjectMapper extends ObjectMapper {
         private boolean treeToValueCalled;
         private boolean treeToValueCalled;
 
 
@@ -471,6 +509,15 @@ class ClasspathOutlookExamSprintReportRendererTest {
         throw new AssertionError("svg should exist for class '" + cssClass + "' in HTML fragment:\n" + html);
         throw new AssertionError("svg should exist for class '" + cssClass + "' in HTML fragment:\n" + html);
     }
     }
 
 
+    private String extractSvgByAriaLabel(String html, String ariaLabel) {
+        Pattern svgPattern = Pattern.compile("<svg\\b(?=[^>]*aria-label='" + Pattern.quote(ariaLabel) + "')[\\s\\S]*?</svg>");
+        Matcher matcher = svgPattern.matcher(html);
+        if (matcher.find()) {
+            return matcher.group();
+        }
+        throw new AssertionError("svg should exist for aria-label '" + ariaLabel + "' in HTML fragment:\n" + html);
+    }
+
     private List<String> gridLineElements(String svg) {
     private List<String> gridLineElements(String svg) {
         Pattern gridLinePattern = Pattern.compile("<line class='chart-grid-line chart-grid-line-y'[^>]*/>");
         Pattern gridLinePattern = Pattern.compile("<line class='chart-grid-line chart-grid-line-y'[^>]*/>");
         Matcher matcher = gridLinePattern.matcher(svg);
         Matcher matcher = gridLinePattern.matcher(svg);