You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在JRXML端获取JasperReports合并报表的总页数?

如何在Jasper Reports中合并多子报表并显示全局连续页码与总页数

问题回顾

你现在面临的场景是:后端传入List<List<?>>类型的Bean集合,每个子列表对应一个独立的Jasper报表,最终合并成一份报表后,每个子报表的页码都从1开始计数,而且无法获取全局总页数——每个子报表都显示自己的页码范围,而你需要的是跨所有子报表的连续页码和统一总页数。另外由于Jasper打印逻辑不在你的权限范围内,没法从后端计算页数,想知道JRXML端能不能解决这个问题。

先看你当前使用的页码文本框代码:

<textField evaluationTime="Master">
 <reportElement x="660" y="14" width="58" height="14" forecolor="#1A75B4" uuid="24876562-c6ab-424d-9ac6-769ef9b54079">
 <property name="com.jaspersoft.studio.unit.width" value="pixel"/>
 <property name="com.jaspersoft.studio.unit.height" value="pixel"/>
 <property name="com.jaspersoft.studio.unit.y" value="pixel"/>
 </reportElement>
 <textElement textAlignment="Right">
 <font fontName="Albany WT" size="10"/>
 </textElement>
 <textFieldExpression><![CDATA["Page " + $V{MASTER_CURRENT_PAGE}]]></textFieldExpression>
 </textField>
 <textField evaluationTime="Master">
 <reportElement x="725" y="14" width="50" height="14" forecolor="#1A75B4" uuid="5c06c90b-79f2-450b-9f43-7eb00676871b">
 <property name="com.jaspersoft.studio.unit.width" value="pixel"/>
 <property name="com.jaspersoft.studio.unit.height" value="pixel"/>
 <property name="com.jaspersoft.studio.unit.y" value="pixel"/>
 </reportElement>
 <textElement textAlignment="Left">
 <font fontName="Albany WT" size="10"/>
 </textElement>
 <textFieldExpression><![CDATA[" of " + $V{MASTER_TOTAL_PAGES}]]></textFieldExpression>
 </textField>

还有后端生成JasperPrint的代码:

List<JasperPrint> prints = new ArrayList<JasperPrint>();
helperReturnObject.getTemPlatepaths().forEach(t -> {
 try {
 int index = helperReturnObject.getTemPlatepaths().indexOf(t);
 JasperReport jasperReport = null;
 if (!developMentFlag) {
 jasperReport = (JasperReport) JRLoader.loadObject(JasperGatewayClass.class.getResourceAsStream(t));
 } else {
 try {
 jasperReport = (JasperReport) JRLoader.loadObject(new FileInputStream(new File("path")));
 } catch (FileNotFoundException e) {
 e.printStackTrace();
 }
 }
 JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(
 helperReturnObject.getBeanCollections().get(index));
 JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, helperReturnObject.getParameters().get(index), dataSource);
 prints.add(jasperPrint);
 } catch (/*JRException | NullPointerException*/ Exception e) {
 System.out.println(e.getMessage() + "----------------------ERROR----------------");
 e.printStackTrace();
 }
});

JRXML端的可行性分析

先直接给结论:单纯靠JRXML很难实现这个需求。原因是每个子报表都是独立填充的,Jasper内置的MASTER_CURRENT_PAGEMASTER_TOTAL_PAGES变量都是绑定单个报表实例的,没办法跨多个JasperPrint实例共享全局的页码计数。

如果硬要尝试JRXML方向的思路,你可以考虑把所有子报表的数据合并成一个大的数据源,然后用报表分组(Group)来划分原来的子报表内容,但这种方法有很大局限性:如果你的每个子报表模板差异很大(比如不同的页眉、字段布局),这种方法几乎行不通,而且还要大幅调整数据结构和报表设计,成本很高。

已验证的可行方案(适配页眉带)

既然JRXML端走不通,我找到了一个不需要修改打印逻辑的替代方案:在所有子报表填充完成后,遍历所有JasperPrint的页面,手动替换页码文本。这个方案你已经实现了,我再整理一下细节方便其他人参考:

步骤说明

  1. 先统计所有子报表的总页数
  2. 维护一个全局的连续页码计数器
  3. 遍历每个JasperPrint的每一页,深入到页眉的Frame元素中,找到页码对应的文本框,替换成全局的页码和总页数

完整实现代码

// 第一步:计算所有子报表的总页数
int totPageNumber = 0;
for (JasperPrint jp : prints) {
    totPageNumber += jp.getPages().size();
}

int currentPage = 1;
// 定义两个标记,用来在JRXML中做占位(需要对应修改JRXML里的页码表达式)
final String CURRENT_PAGE_MARKER = "CURRENT_PAGE_MARKER";
final String TOTAL_PAGE_MARKER = "TOTAL_PAGE_MARKER";

// 遍历所有子报表的页面,替换页码
for (JasperPrint jp : prints) {
 List<JRPrintPage> pages = jp.getPages(); 
 for (JRPrintPage jpp : pages) {
 List<JRPrintElement> elements = jpp.getElements(); 
 for (JRPrintElement jpe : elements) {
 // 页眉元素通常嵌套在Frame里,所以先判断是否为Frame类型
 if (jpe instanceof JRTemplatePrintFrame) {
 JRTemplatePrintFrame jpf = (JRTemplatePrintFrame) jpe;
 List<JRPrintElement> frameElements = jpf.getElements();
 for (JRPrintElement element : frameElements) {
 if (element instanceof JRTemplatePrintText) {
 JRTemplatePrintText textElement = (JRTemplatePrintText) element;
 // 替换当前页码标记
 if (CURRENT_PAGE_MARKER.equals(textElement.getValue())) {
 textElement.setText("Page " + currentPage + " of");
 continue;
 }
 // 替换总页数标记
 if (TOTAL_PAGE_MARKER.equals(textElement.getValue())) {
 textElement.setText(" " + totPageNumber);
 }
 }
 }
 }
 }
 currentPage++;
 }
}

对应的JRXML修改

你需要把原来的页码文本框表达式改成对应的标记:

<!-- 当前页码文本框 -->
<textFieldExpression><![CDATA["CURRENT_PAGE_MARKER"]]></textFieldExpression>
<!-- 总页数文本框 -->
<textFieldExpression><![CDATA["TOTAL_PAGE_MARKER"]]></textFieldExpression>

这个方案的好处是完全不需要触碰打印逻辑,只需要在填充完所有报表后做一次全局的页码替换,完美适配了页眉带的结构,而且支持子报表数量动态扩展的场景,刚好符合你的需求。

内容的提问来源于stack exchange,提问作者HeartATech

火山引擎 最新活动