ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Jasper报表制作与J-WMS上传:从Band结构到PDF中文乱码避坑指南

Jasper报表制作与J-WMS上传:从Band结构到PDF中文乱码避坑指南

简介:Jasper报表制作小技巧是一份面向报表开发与IT运维人员的实战文档,聚焦Jasper/iReport使用中的常见难点。文档首先梳理报表的Title、Page Header、Column Header、Detail、Column Footer、Page Footer、Summary七大区域用途,让读者快速理解报表整体结构;随后按实际开发顺序详解Json数据源接入、中文字段在PDF导出时的字体设置、手工补建字段、明细分组后跨页控制、序号变量生成、自定义纸张大小等高频操作;同时补充交叉报表统计时的字段类型调整、控件按条件打印隐藏,以及已编译模板在J-WMS中的上传与配置示例,覆盖从基础到进阶的完整链路。资源包共1个docx文档,压缩后约599KB,操作说明与界面示例可对照阅读。目前已有242人学习下载,既适合新人快速建立报表框架,也可作为老手排错与细节配置的速查参考,不论制作快递面单、统计报表还是交叉分析,都能找到对应处理思路。

1. 报表预览正常,上传后中文变白:Jasper报表的坑比你想的多

第一次用 Jasper 报表的人,十有八九都遇到过这个邪门情况:本地预览好好的,中文字段一个不少,可模板上传到 J-WMS 系统,点 PDF 下载,中文全没了,只剩一串串方块。很多人第一反应是系统环境缺字体,实际是 PDF 导出时没有告诉报表引擎“中文用什么字体编码”,预览界面用的渲染方式和 PDF 导出根本不是一套逻辑。类似的坑还有:JSON 数据源漏字段、分组后同一店铺被拆到两页、Crosstab 统计值全是 0。这套笔记把 Jasper 的 Band 结构、常用制作技巧和 J-WMS 上传配置串成一个完整流程,新手照着走能避开大半坑,熟手可以直接跳到第 3 章、第 5 章看参数和血泪经验。

2. 先把 Band 结构和数据源理顺:七个段的职责与 JSON 数据源流程

Jasper 的报表画布本质上是一排垂直排列的 Band,预览和编译时引擎按固定顺序渲染它们。很多排版问题,比如合计放错位置、页眉跑到第二页变样,根源都是没搞清楚每个 Band 的出现规则。搞清楚这些规则,再按标准流程接 JSON 数据源,后面做功能才不容易翻车。

2.1 七个 Band 的显示规则:Title、PageHeader、Detail 谁先谁后

Jasper 报表的画布从文档标题、页眉、列头到明细、页脚,由若干带状区域组成,每个 Band 有自己严格的输出位置。项目正文里给出的那套规则,是实际做报表时最常用的七个段,先看表格:

Band显示位置典型用途
Title仅第一个页面的最上方报表总标题,页面再多也只出现一次
Page Header每一页的顶部页码、创建时间、创建人等公共要素
Column Header每一页列头区域放置列名称,注意不是列数据
Detail每一页可重复出现的区域销售记录、明细列表等核心内容
Column Footer每一页列底部列级别的统计值或说明
Page Footer每一页最下端页级统计值或页面说明
Summary最后一页 Detail 之后整个报表的合计、全局统计

记住 Title 只属于第一页,即便报表有几十页,Title 也不会再出现第二次,所以别把每页都要显示的信息放进 Title。Page Header 则相反,它会出现在每一页,第一页时排在 Title 下方,从第二页开始直接顶到页面最上面。实际操作里,页码、创建时间这些公共要素放 Page Header 是默认操作。

Column Header 和 Detail 是最容易被搞混的一对:Column Header 放列名,Detail 放列数据。看起来简单,但做过动态列头的人都知道,一旦把列名和列数据混进同一个 Band,每组明细都会带一次列名,整个报表面目全非。Summary 的作用是在所有 Detail 渲染完之后再输出,比如 50 条销售记录占了 3 页,想统计这 50 条的合计,放 Summary 才是对的——放第一页的 Page Footer 只会算出那一页的统计值。

除了这七个基础段,还有两个分组相关的段:Group Header 和 Group Footer。以月份为单位分组显示销售记录时,Group Header 放组说明或组标志,Group Footer 放该组的小计。还有一个 Last Page Footer,作用于整个报表的最后一页底部,适合放一些只在整个报表末尾出现的补充说明。理解了这些 Band 的边界,再去做分组、跨页统计,思路会清晰很多。

2.2 JSON 数据源的标准流程:从添加数据源到编译

用 JSON 作为常用数据源时,流程是固定的:添加数据源、新建报表页、关联数据源、添加数据字段、添加并修改字段标签、属性设置、分组、预览、编译。每一步都不能省,但也不用过分紧张,没有数据源也能先生成报表,数据源主要服务于预览。

第一步先添加数据源,这个数据源主要给设计器预览用的,把 JSON 样例喂进去,设计阶段就能看到数据。第二步新建报表页,选好纸张尺寸。第三步关联数据源,让报表编辑器读取到字段结构,这一步做完,左下角报表大纲里会列出 JSON 里的字段。第四步添加数据字段,把需要的字段拖到 Detail 或 Column Header 区域。第五步是修改字段标签,中文标签要重点处理,否则 PDF 导出时中文会丢。第六步属性设置,包括字体、对齐、边框等。第七步分组,需要按某一列归类时用。第八步预览,确认无误后编译成模板。

有一点要特别提醒:JSON 源读取出来的字段默认都是 String 类型,如果后面要用到 Sum、Crosstab 这类计算,记得先检查字段类型,把数值字段改成 Integer 或 BigDecimal,不然后面统计出来的结果会让你怀疑数据源有问题。这个问题的细节我在后文 Crosstab 一节里会展开说。

3. 常用制作技巧:中文字体、手工字段、序号列与分组

这一章是实际做报表时操作频率最高的几个功能点。内容不深,但每一条都是能直接抄作业的,特别是 PDF 中文字体那一段,是绝大多数人遇到的第一个“玄学问题”。

3.1 PDF 中文字段:属性面板和全局 style 两种改法

如果报表只需要在系统界面预览,中文字体问题基本不用管。但一旦走 PDF 打印,不设置中文字体,上传系统后字段就变成空白的。解决方式有两种。

第一种是逐个改属性。选中“收货商品统计标签”这样的中文标签,在属性框里切到 Advanced 面板,设置 PDF 相关属性。核心是三个参数:pdfFontName 设为 STSong-Light,pdfEncoding 设为 UniGB-UCS2-H,isPdfEmbedded 设为 true。STSong-Light 是 PDF 标准内置的简体中文字体,配合 UniGB-UCS2-H 编码,基本上能覆盖绝大多数简体中文场景。

第二种是改源码,插入全局样式,这样整个报表的字段都能生效,不用每个标签单独设置。在报表的 source 源码界面中插入下面这段全局代码:

<style name="pdfStyle" isDefault="true" pdfFontName="STSong-Light" pdfEncoding="UniGB-UCS2-H" isPdfEmbedded="true"/>

这段 style 的作用是把这个字体设置设为默认值,报表里所有文本控件在导出 PDF 时会自动套用。用 STSong-Light 而不是系统字体,是因为这款字体是 Acrobat 自带的 CID 字体,不依赖对方电脑有没有安装中文字体,导出后不管在哪个设备打开都不会变方块。isPdfEmbedded 设为 true,表示字体嵌入 PDF 文件,进一步避免字体替换。

这里有个参数需要说明:pdfEncoding 用的是 UniGB-UCS2-H,这个 H 代表横排文字;如果你的报表里有竖排文本需求,应该用 UniGB-UCS2-V。大多数业务报表都是横排,用 H 足够。

注意:如果全局 style 和单个标签的属性同时设置了,单个标签的高级属性会优先生效。排错时可以先把全局 style 去掉,逐个标签排查,定位到底是哪一处漏配。

3.2 漏字段与序号列:报表大纲右键 fields 和 fx 变量

JSON 数据源使用过程中,偶尔会漏掉个别字段。比如接口后来加了新字段,原来的报表模板没有读到。此时不需要重新建报表,在左下角报表大纲里右键 fields,选择手工新增字段。新增后要在属性框中重命名 Name 和描述,并选择对应字段类型。这里最关键的一点是:Name 必须和数据库查询出来的字段名完全一致,大小写都要一致,否则上传系统后字段取不到值,报表显示空白或者 null。

除了补漏,新增字段还常用于添加序号列。序号列在报表明细里很常见,方便核对行数和引用具体某一行。在 Jaspersoft 报表大纲中右键 fx,选择 Create Variable,在 Object 属性面板中创建一个变量,把计算方式设为计数相关,然后把该变量拖到 Detail 区作为第一列。参考实现如下:

<variable name="seq" class="java.lang.Integer" calculation="Count"> <variableExpression><![CDATA[Boolean.TRUE]]></variableExpression> </variable>

这个变量的逻辑是:Detail 每渲染一行,Count 就加一,expression 固定为 Boolean.TRUE 保证每行都参与计数。渲染效果就是 1、2、3、4 递增的序号。计算方式用 Count 而不是 Sum,是因为 Count 适合这种不需要实际数值、只需要行号的场景。

如果报表明细做了分组,序号是否按组重置也需要提前想清楚。默认情况下变量按整个报表的渲染过程累计;需要按组重置时,要在变量属性里把 ResetType 设为对应的 Group。另外,这个变量拖到 Detail 区时建议放在最左侧,宽度尽量小,不会挤压后面的明细列。

3.3 明细分组:两种分组方式与 Height 调 0

报表明细按店铺、月份、供应商这类字段分组是高频需求。项目正文里提到了两种操作方式,适用场景略有差异。

第一种方式:在报表大纲中右键报表名,选 Create Group,创建时选中分组字段。创建后,点击分组区域,在右下角属性面板的 Band Properties 中,把 Height 设为 0,并勾选 Start New Column、Start New Page 前面的复选框。Height 设为 0 是让组头不占额外高度,勾选 Start New Page 是让每个新组从新一页开始,Start New Column 对应分栏报表中的换列。两个都勾的效果就是:同一组的数据集中排列,组切到另一组时强制翻页。

第二种方式:针对单个分组字段,直接把 Column Header 的 Height 设为 0,同时把 Column Header 中原有的列名字段全部移到 Group Header 中。分组字段在创建分组时已选择,可以一起放进 Group Header,如果需要显示组名就保留,不需要就用后文提到的控件隐藏方式处理。这种方式的好处是不用额外维护一个组头 Band,把列头直接和分组绑定,实际效果类似按店铺分页,同一个店铺的明细归档在同一页。

两种方式的取舍:第一种适合有多个分组层级、需要精细控制每个分组行为的场景;第二种适合只有一个分组字段、希望列头跟随分组移动的场景。我个人更常用第一种,因为创建分组后可以在 Band Properties 里单独调高度和翻页行为,后续改起来不容易乱。但不管哪种方式,Height 的数值必须检查——如果组头高度不为 0,每个分组前都会空出一大块,页面利用率低,打印时还会浪费纸张。

4. 快递面单与交叉报表:自定义纸张、控件显隐和 Crosstab

这一章讲两个特殊场景:一是快递面单需要的自定义纸张与控件显隐,二是统计类报表常用的交叉表。两者的操作思路完全不同,但有一个共同点——都要先处理布局和类型,再做功能。

4.1 自定义纸张大小与 Detail-only 布局

快递面单或者部分管理报表,默认的 A4 纸满足不了需求,需要自定义纸张尺寸。做法是:新建完报表初始页面后,点击页面最边框外侧部分,在 Report 属性部分选择 Edit Page Format,在 Format 中选择 Custom,手工填写宽高。此时注意在下方 Units 中选择 mm,或者根据实际需求选择其他度量单位。

自定义纸张最容易翻车的点在于单位。设计器默认可能是像素或英寸,而打印机的驱动通常以毫米为单位。如果设计尺寸用的是像素,打印时换算下来位置会偏移,打出来条码或者文字错位。所以一开始就把单位切到 mm,后续所有控件的坐标都以毫米为基准,误差最小。

快递面单的场景比较特殊:一般只需要保留 Detail 区域,其他区域高度设置为 0。Title 不要、Page Header 不要、Page Footer 也不要,因为面单就是一单一条数据,占一张纸,不需要附加信息。保留其他 Band 的结果就是每一单多出来一块空白页眉页脚区域,打印出来既难看又容易干扰扫码。把不需要的 Band 高度全部调成 0,再把 Detail 区域撑满整个纸张,这是面单类报表的标准做法。

4.2 用 EQUALS 函数控制控件的打印显示与隐藏

快递面单上经常有这种业务:同一种面单,有些客户是陆运件,有些是航空件,物流公司要求两种件在面单上有不同的标记。这时可以用 Jasper 的打印条件表达式,判断某个字段值来决定控件显示还是不显示。

下面这个例子,判断运输方式字段值等于 E 时,打印显示该标记控件,否则不显示:

<printWhenExpression><![CDATA[$F{transportType}.equals("E")]]></printWhenExpression>

printWhenExpression 是 Jasper 控件自带的打印控制表达式,返回布尔值。表达式的值为 true 时控件正常渲染,false 时整个控件不占用空间、也不显示。把这个条件加到某个标记控件上,就实现了按字段值控制显隐。注意比较时用 equals 而不是双等号,避免空指针问题。

这个思路可以推广到更多场景:比如在明细行里,某个状态字段值等于 1 时显示“已完成”,否则不显示;或者在分组表头里,按供应商类型显示不同的标签。条件表达式支持常见的逻辑运算,多个条件用 && 或 || 组合起来,灵活性很高。

注意:当字段值为 null 时,equals 判断本身会返回 false,控件就不会显示。所以做显隐判断前,先确认字段值在数据源里不会为 null,或者在表达式里先做 null 判断,否则会出现“数据明明有,控件却不见”的情况。

4.3 交叉报表 Crosstab:数据类型先调整,功能区要精简

做收发货绩效这类统计需求时,横向按一个维度、纵向按另一个维度交叉展示数据,要用 Crosstab 控件。很多人在这一步踩坑,最常见的原因就两个:统计字段类型不对,或者放错了 Band。

第一步,在导入数据源或者手工新增 Fields 字段时,对需要参与计算的统计字段,必须调整为 Integer 或 int 类型。比如明细中的 ReceiptCount、TotalQty 这两个字段,从 JSON 读过来默认是 String,如果不改成 int,后面的 Sum 计算不会生效,交叉表里全是 0。字段类型改完后,再进入设计界面。

第二步,去掉多余的功能区域,根据需要保留 Title 和 Summary。注意:Crosstab 只可以存放于 Summary 区,不是 Detail,也不是 Page Footer。这是 Jasper 的硬性规则,交叉表必须等所有明细渲染完才能得出完整统计结果,所以只能放在 Summary。

第三步,拖动 Crosstab 控件到 Summary 区,在向导中选择引用已存在数据源,根据向导里表格区域的粉色阴影提示,按需依次选择数据字段。向导分三步:选择行分组字段、列分组字段、明细统计字段。在添加明细字段时,把 calculation 函数均改为 Sum,这样交叉表才是求和汇总,不是逐行展示。

第四步,点击完成后,把 Crosstab 覆盖到整个 Summary 区域。双击 Crosstab,可以修改深蓝色 Static Text 上部、左部的字段名称。这里有一点经验之谈:大多数时候需要选中 Crosstab 中全部明细字段,统一修改字体大小为 6 号并居中,因为默认字体过大,行列一多就显示不出来,看起来像是报表坏了,其实是尺寸问题。

5. 避坑/常见问题/排查:预览只是第一步,上传配置才是关键

做 Jasper 报表最迷惑的地方就是:本地预览正常,不代表上传到 J-WMS 系统正常。下面五条踩坑记录,全是实际项目中反复出现的问题,按现象、原因、解决的顺序梳理。

现象一:PDF 导出后中文消失,只剩方块或空白。原因:预览界面用的是 Java 的 AWT 字体渲染,PDF 导出是另一套字体机制,没有设置中文字体编码,导出时就无法映射。 解决:在报表源码中插入全局 style,设置 pdfFontName 为 STSong-Light、pdfEncoding 为 UniGB-UCS2-H、isPdfEmbedded 为 true。如果个别标签不生效,检查这个标签的 Advanced 属性,优先级的覆盖会盖掉全局设置。

现象二:手工新增字段后,报表取不到值,显示 null 或空白。原因:新增字段的 Name 和数据库查询出来的字段名不一致,或者字段类型选错,导致映射失败。 解决:报表大纲右键 fields 手工新增后,Name 必须和 SQL 查询列名完全一致,大小写都不能差。JSON 数据源里的数值字段,类型要同步改成 Integer,不要让设计器沿用默认的 String。

现象三:按店铺分组后,同一店铺的明细被拆到两页,分组效果和预想不一致。原因:创建分组时没有勾选 Start New Column 和 Start New Page,或者分组 Band 的 Height 没有设为 0,导致引擎按照默认分页逻辑处理。 解决:创建分组后在 Band Properties 中把 Height 设为 0,勾选 Start New Column、Start New Page。用第二种手写方案时,Column Header 的 Height 也要归零,并把列名字段全部移到 Group Header。

现象四:序号列不是从 1 开始,或者出现跳号。原因:fx 里创建的变量计算方式不对,或者变量被拖错了 Band,导致计数逻辑没有按行触发。 解决:右键 fx 创建 Variable,calculation 设为 Count,variableExpression 固定为 Boolean.TRUE,拖到 Detail 区第一列。分组报表里想按组重置,就要在变量属性里把 ResetType 改成对应的 Group。

现象五:Crosstab 统计结果全是 0,或者行列显示不全。原因:统计字段从 JSON 读过来是 String,Sum 对字符串不生效;另外默认字体过大,交叉表行列多时数值直接溢出。 解决:先回字段定义把 ReceiptCount、TotalQty 这类字段改成 Integer,再回设计界面把明细统计字段的 calculation 改成 Sum。最后选中交叉表所有明细字段,字体调到 6 号、居中。

6. 从模板到上线:J-WMS 打印配置与 View JSON 查询条件

模板编译完不等于报表上线,J-WMS 系统里还需要完成上传和配置两步。作业报表和管理报表的挂载方式不同,配置时的 JSON 模板和 SQL 条件也有一些细节要处理。

6.1 打印配置:JSON 条件模板的字段规范

在系统里选择“配置—打印配置—报表”,新增报表,填写代码标识、描述、报表类型,然后编写特定的条件和 SQL 语句模板。代码标识尽量与原始模板名称保持一致,后续挂载时容易对应。条件部分的 JSON 模板这样写:

[{"field":"id","id":"id","name":"装载号","required":true,"type":"integer"}]

这里 field、id、name、type 四个字段是必填项,field 与 id 尽量保持一致。type 决定下一步 SQL 查询中条件字段的类型,比如装载号是 integer。name 是对外显示的名称。对于作业报表,除 name 外其它都不需要改动;管理报表如果要支持期间、自定义条件等查询,就要按需扩展字段定义。举个例子,入库商品明细表需要按起止日期、商品编码、供应商等字段查询,对应的 JSON 是这段:

[{"field":"","id":"h1_basic","name":"基本信息","type":"h1"}, {"field":"beg","id":"beg","name":"起始日期","type":"date"}, {"field":"end","id":"end","name":"结束日期","type":"date"}, {"field":"itemCode","id":"itemCode","name":"商品编码","type":"string"}, {"field":"code","id":"code","name":"入库单号","type":"string"}, {"field":"shipFromName","id":"shipFromName","name":"供应商名","type":"string"}]

type=h1 的那一行只是分组标题,用于界面把查询条件分组展示,不参与 SQL。beg 和 end 定义为 date,是让界面弹出日期选择器;itemCode、code、shipFromName 定义为 string,是让界面渲染成文本输入框。

6.2 View JSON 挂载与 T-SQL 参数绑定

模板上传完成后,通过“开发—菜单配置”找到报表打印界面的类型和名称,在 View JSON 中打开对应加载项,在打印区域添加作业报表。文档名称对应模板名称,id 对应打印配置中的代码名称。更新完成后,整套报表的制作和配置才算结束。

打印时系统会把前一步 JSON 中定义的查询条件转换成 SQL 参数,和模板文件里的 SQL 语句绑定。以入库商品明细表为例,T-SQL 条件部分是这样:

WHERE rh.leadingSts=900 AND rh.trailingSts=900 AND (rh.`code` = :code OR rh.`code` IS NULL) AND (rd.itemCode = :itemCode OR rd.itemCode IS NULL) AND (rh.shipFromName = :shipFromName OR rh.shipFromName IS NULL) AND (rh.closedAt >= :beg OR rh.closedAt IS NULL) AND (rh.closedAt <= :end OR rh.closedAt IS NULL) ORDER BY rh.closedAt DESC

冒号开头的 :code、:itemCode 这些参数,对应前面 JSON 模板里定义的 field。每个条件都写成“参数等于值 OR 参数 IS NULL”的形式,这样用户不填该条件时,参数为 null,OR 子句让该条件不参与过滤;用户填了值,才精确匹配。日期条件同理,beg/end 是闭区间。注意 closedAt 为 null 的历史数据,同样靠 OR 子句保住,否则会漏单。

6.3 管理报表的预览查询:不走菜单配置

前文提到的 View JSON 打印配置只适用于一般作业报表。管理报表不需要走菜单挂载,可以在“管理—报表查询”中直接双击预览查询。比如入库商品明细表,界面里选择自定义期间、添加完其他字段后,点击“PDF 下载”即可完成预览和打印。这背后的 SQL 用的就是 6.1 中自定义条件后半段生成的模板。

从那以后我每次在 Jasper 里做完一个报表,都会强制走一遍“预览 → 上传 → PDF 下载”的完整流程,核心字段绝不停留在“本地能跑”的阶段,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进