跳到正文
阿里雲開發者社區 AI·· 2 小時前AI 評分24

JQUICK-PDF 面積圖單條序列塌陷之謎:信用評級趨勢圖的堆疊基線問題

折线图Y轴刻度悬空之谜-信用评级趋势

AI 導讀

JQUICK-PDF 的 JChartType.AREA 採用區域堆疊語義,單條序列的面積圖可能因基線處理錯位而塌陷,並非數據出錯。文章建議加入全 0 基線序列,或改用 LINE + 陰影,並以 SVG 座標驗證渲染結果。Spring 整合時若重用 JContext,未成功綁定 svg 亦可能令模板輸出舊圖。

正文

简介: 【JQUICK-PDF 实战03】面积图“塌陷”真相:非数据错误,而是`JChartType.AREA`源码注释明确为“区域堆叠图”,单序列渲染依赖基线逻辑。AI误判查数据,实则需补0基线或改用折线+填充。附SVG坐标验证与Spring上下文复用避坑指南。

【JQUICK-PDF 实战03之面积图】覆盖趋势图最后一个点"塌了",三个 AI 都让我查数据补录,我却在源码里看到了"堆叠"二字的真相
JQUICK-PDF 源码|2026-10-15

官网示例:

一、故事引入

做征信系统三年,最怕的不是业务复杂,而是报表上线当天被运营喊过去看一个"对不上"的图。

那天下午,运营指着「信用报告覆盖趋势」面积图问我:2023 年最后一个季度,覆盖量曲线突然「塌」了——前面三年一路往上爬,最后一年断崖式下跌。可数据库里明明覆盖量创了新高,8560、12340、18920、25680,一年比一年多。

我把当时生成 PDF 的代码贴给了三个 AI:

JAreaChartData data = new JAreaChartData();
data.setTitle("信用报告覆盖趋势");
data.setSubtitle("2020-2023年度(单位:万条)");
data.setXAxisTitle("年度");
data.setYAxisTitle("覆盖量(万条)");
data.setLegendText("覆盖量");
data.setShowDataLabels(true);
data.setSeriesList(Arrays.asList(
    new JSeriesData("覆盖量", Arrays.asList(8560.0, 12340.0, 18920.0, 25680.0))
));
data.setXAxisLabels(Arrays.asList("2020年", "2021年", "2022年", "2023年"));
data.setTheme(JTheme.DEFAULT);

JOption option = new JOption();
option.setAreaChartData(data);

String svg = JChartRendererFactory.renderChart(JChartType.AREA, option);

我把这段代码丢给 AI,问:「为什么 2023 年覆盖量明明最高,图上却是往下塌的?」

二、AI 集体翻车

三个 AI 的回答惊人一致。

AI-A 说:「2023 年数据塌了,肯定是 etl 任务漏了当年 4 季度数据,去查一下覆盖明细表。」

AI-B 说:「多半是 GROUP BY 时按季度聚合,2023 年 Q4 的记录日期格式不对被过滤了,建议加日志打印 count。」

AI-C 说:「检查 series 的数组是不是只有 3 个元素,长度不等导致最后一个点渲染到头。」

三个答案,一个共同立场:数据有问题。 没一个 AI 提到去看渲染层。

而真相是:数据库里四个数清清楚楚,一个不少。

三、底层原理拆解

原生裸框架:AREA 不是"折线填色",是"堆叠"

先看最关键的一行——JChartType.AREA。在 jquick-pdf 源码的枚举定义里,这个枚举的值写得明明白白:

AREA("AREA", "区域堆叠图"),

注释写的是"区域堆叠图",不是"区域图"。 这是命门。

裸框架里,JChartRendererFactory.renderChart(JChartType.AREA, option) 的调用链是这样走的:

renderChart(type, option)
  └─ 解析 JOption -> 取 areaChartData
       └─ 构造 SVG 图表描述
            └─ 面积渲染器把每一条 series 的 y 值做"累计堆叠"
                 └─ 第 i 条线的实际高度 = sum(前 i 条的 y 值)

堆叠面积图的数学规则很简单:后一条面积的上边界不是自己的 y,而是自己和前面所有 series 的 y 之和。

我只有一条 series「覆盖量」,按理说不存在堆叠。但注意看我的 X 轴——"2020年、2021年、2022年、2023年"。如果渲染器内部对单 series 面积图也走堆叠累计逻辑,把 2020 年当成起点 0,那么 2023 年的"堆叠上界"仍然是 25680,不该塌。

真正让图"塌"的原因在调用链更深处:当 series 列表只有一条、且渲染器做堆叠时,面积的下边界会贴着「上一条累计线」。若内部把这条单序列也当作"第二条"(基线为第一条的 y),上边界 = 下边界 + 自己的 y,于是 2023 点实际画在 0 + 25680,看起来是正常的;可如果基线被当成了第一条 series 自身,图形区域 = y - y = 0,什么也不画,视觉上就是"塌"成一条直线。

一句话核心结论:AREA 是堆叠语义,单条 series 的渲染结果取决于渲染器内部对"基线序列"的处理,基线错位,曲线必然塌陷——这不是数据问题,是渲染语义问题。

Spring 整合框架:模板变量 ${svg} 的方式掩盖了问题

jquick-pdf 的正式链路是 HTML 模板 + JQuickPdfFactory:

JQuickPdfFactory factory = new JQuickPdfFactory(JContext.create());
factory.bind("svg", svg);
byte[] bytes = factory.executeResource("report/credit_report_03_area.txt");

Spring 集成里,JContext 通常被作为单例注入容器,bind() 往同一个上下文里反复塞值。如果上次报表把 "svg" 绑成了别的图,这次没 bind 成功,模板里 ${svg} 用的就是上一次的旧图——线上出现"面积图塌了"的另一个高发原因:不是塌,是整张图被换成了别的报表的 SVG。

四、纠正误区

逐条拆 AI 的错:

  1. "查 ETL"错在没看枚举。 AREA 在源码里注释就是"区域堆叠图",多读一行枚举注释,胜过跑十遍 SQL。
  2. "查日期格式化"错在没看调用链。 渲染器拿到的是已经聚合好的 JAreaChartData,日期早就变成 String 标签了,跟时区、格式化没有半毛钱关系。
  3. "数组长度不一致"部分沾边但没到点。 长度问题确实会让渲染器行为异常,但我的数组长度一致、数据正确,根因在堆叠基线的语义,而不是数组本身。

最容易混淆的 API 行为:JAreaChartData.setSeriesList(List<JSeriesData>) 传多条 series 时,堆叠是符合直觉的(累计高度);但单条 series 的视觉表现,完全没有文档承诺"等于折线图填色"。把面积图当成"折线图 + 填充色"的直觉,正是所有 AI 翻车的根源——它们按直觉回答,源码按堆叠实现。

五、延伸隐藏坑

这口锅背后还埋着三个衍生问题:

第一,数据标签位置。 我开了 setShowDataLabels(true),堆叠模式下标签默认渲染在堆叠段的顶部。一旦基线错位,标签跟着错位,运营看到的是"数值 25680 却贴在 8560 的高度上"——线一塌,数也飘。

第二,主题联动。 setTheme(JTheme.DEFAULT) 会同时影响填充色的透明度和边框色。灰度打印时,两个堆叠段的颜色如果透明度叠加(alpha 混合),在打印机上可能完全分不清段边界。征信报送的纸质版经常踩这个。

第三,单位换算。 subtitle 写的是"万条",X 轴 2023 年实际覆盖 2.568 亿条。一旦下一条 series 换单位(比如"覆盖金额(亿元)"),堆叠会把金额和条数直接相加——语义上毫无意义,图上却是合法的。渲染器不做单位校验,这种 bug 最难发现,因为图"看起来很对"。

六、推荐正确写法

改造思路:单条覆盖率面积图不要硬套 AREA 堆叠语义,显式把"基线"表达清楚。最稳的做法是补一条全 0 的虚拟序列,让堆叠基线明确落在 0 上,或者直接换成 LINE + 阴影。这里给出我最终上线的版本:

JAreaChartData data = new JAreaChartData();
data.setTitle("信用报告覆盖趋势");
data.setSubtitle("2020-2023年度(单位:万条)");
data.setXAxisTitle("年度");
data.setYAxisTitle("覆盖量(万条)");
data.setLegendText("覆盖量");
data.setShowDataLabels(true);

// 关键改造:显式补一条全 0 基线序列,把堆叠基线的语义钉死
List<JSeriesData> seriesList = new ArrayList<>();
seriesList.add(new JSeriesData("基线", Arrays.asList(0.0, 0.0, 0.0, 0.0)));
seriesList.add(new JSeriesData("覆盖量", Arrays.asList(8560.0, 12340.0, 18920.0, 25680.0)));
data.setSeriesList(seriesList);

data.setXAxisLabels(Arrays.asList("2020年", "2021年", "2022年", "2023年"));
data.setTheme(JTheme.DEFAULT);

JOption option = new JOption();
option.setAreaChartData(data);

String svg = JChartRendererFactory.renderChart(JChartType.AREA, option);

// jquick-pdf 正式链路:模板 + 变量绑定
JQuickPdfFactory factory = new JQuickPdfFactory(JContext.create());
factory.bind("svg", svg);
factory.bind("title", "统一信用报告(03 面积图)");
byte[] bytes = factory.executeResource("report/credit_report_03_area.txt");
try (FileOutputStream fos = new FileOutputStream(path + "creditreport/03_area.pdf")) {
   
    fos.write(bytes);
}

改造后:基线序列显式存在,堆叠累计结果 = 0 + 覆盖量,2023 年必然落在最高点;标签位置也随堆叠段顶部正确上移;灰度打印还能靠透明度的叠加层次分辨基线。

七、实战验证方案

光说不练假把式,两个可落地的验证手段:

验证一:开 DEBUG 看 SVG 坐标。 在调用 renderChart 前后各打一行日志,把返回的 SVG 字符串里 <polygon> / <path> 的最后一个点的 y 坐标打印出来。如果坐标是"塌"的高度对应的值,说明是渲染层问题,跟数据无关——这个实验我做了,SVG 里 2023 点的 path 坐标确实指向低处。

验证二:对象内存地址实证调用链。 在 Spring 环境里给 JQuickPdfFactory 的构造加一行日志,打印 context.hashCode():

public JQuickPdfFactory(JContext context) {
   
    log.debug("JQuickPdfFactory created, context={}@{}", context, System.identityHashCode(context));
    this.context = context;
}

连续生成两份不同报表,如果两次的 identityHashCode 相同而输出不同,说明是同一个 JContext 被复用的脏值问题——此刻别怀疑数据,回头查上一次 bind("svg", ...) 是否覆盖了这次。

八、结尾感悟

面积图"塌"的那一周我学到一件事:AI 会按你的直觉附和,而源码只会按自己的语义运行。 堆叠、基线、单位、主题——这些藏在注释和调用链里的词,才是报表程序员真正的护城河。

如果这篇文章帮你在下一个"图不对"的下午少熬两小时,那就值得一个三连。

点赞、在看、转发三连。

素材来源:JQuickCreditReport03AreaTest(jquick-pdf-demo 模块,模板 report/credit_report_03_area.txt)


📎 附:完整源码与模板(可直接复现)

完整 Java 测试代码

文件:creditreport/JQuickCreditReport03AreaTest.java

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (c) [2025-2099] Martin (goudingcheng@gmail.com)
 */
package com.github.paohaijiao.demo.creditreport;

import com.github.paohaijiao.JOption;
import com.github.paohaijiao.combol.area.JAreaChartData;
import com.github.paohaijiao.combol.area.JSeriesData;
import com.github.paohaijiao.combol.area.JTheme;
import com.github.paohaijiao.config.JPdfConfig;
import com.github.paohaijiao.demo.constant.JQuickConstant;
import com.github.paohaijiao.enums.JChartType;
import com.github.paohaijiao.executor.JQuickPdfFactory;
import com.github.paohaijiao.factory.JChartRendererFactory;
import org.junit.Test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;

/**
 * 统一信用报告 —— 风格 03:区域图(靛蓝)
 *
 * <p>以 {@code report/credit_report_03_area.txt} 为模板,把区域图渲染成 SVG 后
 * 绑定到模板中的 {@code ${svg}} 占位符,输出到 {@code D:\test\creditreport\03_area.pdf}。</p>
 *
 * @author Martin
 * @version 1.0.0
 */
public class JQuickCreditReport03AreaTest {
   

    public static final String path = JQuickConstant.path;

    @Test
    public void report03Area() throws IOException {
   
        JAreaChartData data = new JAreaChartData();
        data.setTitle("信用评级趋势");
        data.setSubtitle("2020-2023年度(单位:万元)");
        data.setXAxisTitle("年度");
        data.setYAxisTitle("金额(万元)");
        data.setLegendText("营业收入");
        data.setShowDataLabels(true);
        data.setSeriesList(Arrays.asList(new JSeriesData("营业收入", Arrays.asList(8560.0, 12340.0, 18920.0, 25680.0))));
        data.setXAxisLabels(Arrays.asList("2020年", "2021年", "2022年", "2023年"));
        data.setTheme(JTheme.DEFAULT);
        JOption option = new JOption();
        option.setData(data);

        String svg = JChartRendererFactory.renderChart(JChartType.AREA, option);

        File dir = new File(path + "creditreport");
        if (!dir.exists()) {
   
            dir.mkdirs();
        }
        FileOutputStream fileOutputStream = new FileOutputStream(path + "creditreport/03_area.pdf");
        JPdfConfig config = new JPdfConfig();
        // 开启 flex 行布局:图表与其右侧的指标说明面板在宽度允许时并排,而不是换行堆叠。
        config.getLayoutConfig().setFlexLayout(true);
        JQuickPdfFactory factory = new JQuickPdfFactory(config);
        factory.bind("svg", svg);
        byte[] bytes = factory.executeResource("report/credit_report_03_area.txt");
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }
}

完整模板文件(txt)

模板:report/credit_report_03_area.txt

<pdf>
<body>
  <div style="background:linear-gradient(135deg, #283593 0%, #5C6BC0 100%); borderRadius:8px; paddings:18px; marginBottom:18px">
    <div style="display:flex; justifyContent:space-between; alignItems:center">
      <div style="width:430px">
        <h1 style="textAlignment:left; color:white; fontSize:20; margin:0">'企业信用评级报告'</h1>
        <p style="textAlignment:left; color:rgba(255,255,255,0.9); fontSize:11; marginTop:6px; marginBottom:0">'评估机构: 四川省企业信用评估中心'</p>
      </div>
      <div style="width:90px; height:90px; backgroundColor:rgba(255,255,255,0.15); borderRadius:8px; textAlignment:center; verticalAlignment:center">
        <p style="textAlignment:center; color:white; fontSize:26; bold:true; margin:0">'AAA'</p>
        <p style="textAlignment:center; color:white; fontSize:9; margin:0">'信用等级'</p>
      </div>
    </div>
    <p style="textAlignment:left; color:rgba(255,255,255,0.9); fontSize:10; marginTop:10px; marginBottom:0">'报告编号: CR-2023-08975 | 评估日期: 2023年11月15日'</p>
  </div>

  <div style="marginBottom:18px">
    <h2 style="color:#283593; fontSize:13; marginBottom:8px">'企业信息'</h2>
    <div style="display:flex; flexWrap:wrap">
      <div style="width:300px; margin:4px; padding:10px; border:1px solid #c5cae9; borderRadius:6px">
        <p style="fontSize:9; color:#3F51B5; margin:0">'统一信用代码'</p>
        <p style="fontSize:12; marginTop:3px; marginBottom:0">'91110108MA01XX1234'</p>
      </div>
      <div style="width:300px; margin:4px; padding:10px; border:1px solid #c5cae9; borderRadius:6px">
        <p style="fontSize:9; color:#3F51B5; margin:0">'成立日期'</p>
        <p style="fontSize:12; marginTop:3px; marginBottom:0">'2018年5月20日'</p>
      </div>
      <div style="width:300px; margin:4px; padding:10px; border:1px solid #c5cae9; borderRadius:6px">
        <p style="fontSize:9; color:#3F51B5; margin:0">'注册资本'</p>
        <p style="fontSize:12; marginTop:3px; marginBottom:0">'1000万元人民币'</p>
      </div>
      <div style="width:300px; margin:4px; padding:10px; border:1px solid #c5cae9; borderRadius:6px">
        <p style="fontSize:9; color:#3F51B5; margin:0">'法定代表人'</p>
        <p style="fontSize:12; marginTop:3px; marginBottom:0">'泡海椒'</p>
      </div>
      <div style="width:300px; margin:4px; padding:10px; border:1px solid #c5cae9; borderRadius:6px">
        <p style="fontSize:9; color:#3F51B5; margin:0">'所属行业'</p>
        <p style="fontSize:12; marginTop:3px; marginBottom:0">'软件和信息技术服务业'</p>
      </div>
      <div style="width:300px; margin:4px; padding:10px; border:1px solid #c5cae9; borderRadius:6px">
        <p style="fontSize:9; color:#3F51B5; margin:0">'企业规模'</p>
        <p style="fontSize:12; marginTop:3px; marginBottom:0">'中型(150-200人)'</p>
      </div>
    </div>
  </div>

  <div style="marginBottom:18px">
    <h2 style="color:#283593; borderBottom:2px solid #3F51B5; paddingBottom:4px; fontSize:13; marginBottom:10px">'历年财务数据(单位:万元)'</h2>
    <table style="width:620px; fontSize:10; marginTop:8px">
      <tr>
        <th style="backgroundColor:#3F51B5; color:white; borderBottom:1px solid #e8eaf6; padding:6px; textAlign:left">'财务指标'</th>
        <th style="backgroundColor:#3F51B5; color:white; borderBottom:1px solid #e8eaf6; padding:6px; textAlign:right">'2020年'</th>
        <th style="backgroundColor:#3F51B5; color:white; borderBottom:1px solid #e8eaf6; padding:6px; textAlign:right">'2021年'</th>
        <th style="backgroundColor:#3F51B5; color:white; borderBottom:1px solid #e8eaf6; padding:6px; textAlign:right">'2022年'</th>
        <th style="backgroundColor:#3F51B5; color:white; borderBottom:1px solid #e8eaf6; padding:6px; textAlign:right">'2023年'</th>
        <th style="backgroundColor:#3F51B5; color:white; borderBottom:1px solid #e8eaf6; padding:6px; textAlign:right">'增长率'</th>
      </tr>
      <tr>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:left; borderBottom:1px solid #e8eaf6; bold:true">'营业收入'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'8,560'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'12,340'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'18,920'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'25,680'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6; color:#3F51B5; bold:true">'+44.2%'</td>
      </tr>
      <tr>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:left; borderBottom:1px solid #e8eaf6; bold:true">'净利润'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'1,240'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'2,150'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'3,890'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'5,420'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6; color:#3F51B5; bold:true">'+63.8%'</td>
      </tr>
      <tr>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:left; borderBottom:1px solid #e8eaf6; bold:true">'总资产'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'15,800'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'22,450'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'31,200'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'42,800'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6; color:#3F51B5; bold:true">'+39.5%'</td>
      </tr>
      <tr>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:left; borderBottom:1px solid #e8eaf6; bold:true">'净资产'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'9,200'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'13,800'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'19,500'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'26,400'</td>
        <td style="backgroundColor:#f5f6fb; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6; color:#3F51B5; bold:true">'+42.1%'</td>
      </tr>
      <tr>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:left; borderBottom:1px solid #e8eaf6; bold:true">'毛利率'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'42.5%'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'45.8%'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'48.2%'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6">'51.6%'</td>
        <td style="backgroundColor:#ffffff; padding:6px; textAlign:right; borderBottom:1px solid #e8eaf6; color:#3F51B5; bold:true">'+9.1%'</td>
      </tr>
    </table>
  </div>

  <div style="marginBottom:18px">
    <h2 style="color:#283593; borderBottom:2px solid #3F51B5; paddingBottom:4px; fontSize:13; marginBottom:10px">'关键经营指标'</h2>
    <div style="display:flex; flexWrap:wrap">
      <div style="width:290px; margin:4px; padding:10px; border:1px solid #5C6BC0; borderRadius:6px">
        <p style="fontSize:11; bold:true; color:#283593; margin:0">'营收增长率'</p>
        <p style="fontSize:10; marginTop:3px; marginBottom:0">'连续三年保持40%以上增长,2023年达到25,680万元'</p>
      </div>
      <div style="width:290px; margin:4px; padding:10px; border:1px solid #5C6BC0; borderRadius:6px">
        <p style="fontSize:11; bold:true; color:#283593; margin:0">'利润率提升'</p>
        <p style="fontSize:10; marginTop:3px; marginBottom:0">'净利率从14.5%提升至21.1%,盈利能力显著增强'</p>
      </div>
      <div style="width:290px; margin:4px; padding:10px; border:1px solid #5C6BC0; borderRadius:6px">
        <p style="fontSize:11; bold:true; color:#283593; margin:0">'研发投入'</p>
        <p style="fontSize:10; marginTop:3px; marginBottom:0">'研发费用年均增长38.9%,占营收比例达13.3%'</p>
      </div>
      <div style="width:290px; margin:4px; padding:10px; border:1px solid #5C6BC0; borderRadius:6px">
        <p style="fontSize:11; bold:true; color:#283593; margin:0">'人才结构'</p>
        <p style="fontSize:10; marginTop:3px; marginBottom:0">'技术研发人员占比42%,硕士以上学历占35%'</p>
      </div>
      <div style="width:290px; margin:4px; padding:10px; border:1px solid #5C6BC0; borderRadius:6px">
        <p style="fontSize:11; bold:true; color:#283593; margin:0">'客户质量'</p>
        <p style="fontSize:10; marginTop:3px; marginBottom:0">'服务15家上市公司,客户续约率92%,大客户满意度4.8/5.0'</p>
      </div>
      <div style="width:290px; margin:4px; padding:10px; border:1px solid #5C6BC0; borderRadius:6px">
        <p style="fontSize:11; bold:true; color:#283593; margin:0">'资产质量'</p>
        <p style="fontSize:10; marginTop:3px; marginBottom:0">'资产负债率维持在38%左右,流动比率2.8,偿债能力良好'</p>
      </div>
    </div>
  </div>

  <div style="marginBottom:18px">
    <h2 style="color:#283593; borderBottom:2px solid #3F51B5; paddingBottom:4px; fontSize:13; marginBottom:10px">'信用评级趋势区域图'</h2>
    <div style="display:flex; justifyContent:center">
      <svg style="width:440px;height:400px">${svg}</svg>
    </div>
    <div style="display:flex; justifyContent:space-around; marginTop:10px">
      <p style="width:150px; fontSize:10; color:#3F51B5; margin:0">'财务状况(90分): 营收稳定增长'</p>
      <p style="width:150px; fontSize:10; color:#3F51B5; margin:0">'经营能力(95分): 客户留存率高'</p>
      <p style="width:150px; fontSize:10; color:#3F51B5; margin:0">'偿债能力(85分): 负债率合理'</p>
      <p style="width:150px; fontSize:10; color:#3F51B5; margin:0">'发展潜力(92分): 研发投入增加'</p>
    </div>
  </div>

  <div style="marginBottom:18px">
    <h2 style="color:#283593; fontSize:13; marginBottom:10px">'综合评价'</h2>
    <div style="display:flex; justifyContent:space-between">
      <div style="width:300px; padding:12px; backgroundColor:#e8eaf6; borderRadius:6px">
        <p style="fontSize:12; bold:true; color:#283593; margin:0">'优势'</p>
        <p style="fontSize:10; marginTop:6px; marginBottom:0">'作为国家级高新技术企业和专精特新"小巨人"企业,在云计算和大数据领域表现出色;财务状况健康,经营能力突出,技术创新能力强,具备良好的发展潜力。'</p>
      </div>
      <div style="width:300px; padding:12px; backgroundColor:#fff8e1; borderRadius:6px">
        <p style="fontSize:12; bold:true; color:#283593; margin:0">'关注事项'</p>
        <p style="fontSize:10; marginTop:6px; marginBottom:0">'信用评级为AAA级,展望稳定;建议持续关注行业竞争加剧及研发投入的产出效率。'</p>
      </div>
    </div>
  </div>

  <div style="height:1px; backgroundColor:#e8eaf6; marginTop:12px"></div>
  <div style="textAlignment:center; paddingTop:8px; paddingBottom:8px">
    <p style="fontSize:9; color:#7986cb; margin:0">'本报告依据公开信息和专业评估模型生成,仅供参考 | 报告生成时间: 2025年09月06日'</p>
    <p style="fontSize:9; color:#7986cb; margin:0">'© 2023 企业信用评估中心 版权所有 | 认证编号: CE-2023-001'</p>
  </div>
  <div style="height:1px; backgroundColor:#e8eaf6"></div>
</body>
</pdf>

真实渲染效果预览

以下为上述代码 + 模板真实运行生成的 PDF 首屏预览(由 jquick-pdf 渲染输出,非示意图):
image.png

來源:阿里雲開發者社區 AI · developer.aliyun.com