如何动态的给报表添加目录

需求描述:

在做一些类似 word 报告格式报表时,为了有更好的阅读效果,经常需要在首页增加一个目录导航页,这样就能够快速的知道关心的数据在第几页,但是报表不同于 word 文档,word 文档中格式都是固定的,所以能够方便快速的生成目录页,在报表中数据都是动态变化的,并且通常会涉及到动态扩展,这样很难固定目录项,并且由于数据动态扩展,对应的页码也很难精确获得,下面通过一个实例看下,如果在报表中动态增加目录。

解决方案:

报表数据是动态的,所以设计时就不能用固定的目录,需要报表计算后才能知道数据在第几页,润乾报表提供了一个报表计算侦听类,在 java 程序中可以动态获取报表计算后的结果,并能够在程序中动态更改单元格的值。

首先,看下报表模板的设计界面:

1jpg

这个报表要对订单数据按照地区进行汇总分析,并展示详细数据,要求目录中以地区为导航进行设置,报表前 5 行是目录页,第三行中,概要分析一般是固定的,这里就写了固定一行,页码设置 2 就行。

A4:=ds1.group(货主地区; 货主地区:1),表达式按照地区进行分组,也就是目录这块按照地区进行展示,对应 E4 单元格后续要设置成目录页,此处暂时为空。

A5:目录通常在第一页,所以此处设置一个行后分页

A7:就是一个固定的汇总描述,里边可以用字符串拼接方式将固定文本和动态数据拼接在一起展示。

A8:=ds1.group(货主地区; 货主地区:1),按照地区进行分组

A9:对该地区数据做一个汇总说明

A10:=ds1.select(订单 ID),取数订单数据,B10 往后依次类推。

将 A9,A10,A11,A12 单元格的左主格设置成 A8,此片数据根据 A8 进行纵向扩展,这样报表展示结果为:

2jpg

这里可以看到,目录项处列出了对应地区,接下来看下,如果给地区增加对应页码。

从报表结果中看到,第一页中的目录名称和报表中的地区名称相同,这样就可以根据这两个名称做匹配,判断如果名称相同获取数据区域的名称所在的页码,放到对应目录行就行,如数据区域的“东北”在第 2 页,那么目录中东北的页码应该为 2,接下来看下,如何给目录设置动态的页码。

这里就用到了之前说到的报表侦听类的使用,源码如下:

import com.raqsoft.common.Area;
import com.raqsoft.report.usermodel.Context;
import com.raqsoft.report.usermodel.IPagerListener;
import com.raqsoft.report.usermodel.IReport;
import com.raqsoft.report.usermodel.IReportListener;
import com.raqsoft.report.usermodel.PageBuilder;
import com.raqsoft.report.util.ReportUtils;

public class createmulu implements IReportListener {
	 public void afterCalc(Context arg0, IReport arg2) { 
		 PageBuilder arg1 = null;
		try {
			arg1 = new PageBuilder(arg2);
		} catch (Throwable e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
			System.out.println("报表总页数"+arg1.getPageCount());
			IReport p1;
			try {
				 p1=arg1.getPage(1);//目录通常在第一页,所以获取第一页为目录页
				 int page=2;//设置数据初始循环页,通常为第二页
				 for(int i=3;i<=p1.getRowCount();i++){//目录行从第三行开始
					String muluName=(String)p1.getCell(i, 2).getValue() ;//报表中设置第二页为目录名称
					for(int j=page;j<=arg1.getPageCount();j++){//按照页数进行循环,分别取分页后每页报表对象
						for(int k=1;k<=arg1.getPage(j).getRowCount();k++){//每页中按照每行进行循环		
						String mName=arg1.getPage(j).getCell(k,1).getValue() != null ? arg1.getPage(j).getCell(k,1).getValue().toString() : "";//本例中目录项在报表中的第一列,也就是A8单元格
						if(mName!="" && mName==muluName){//判断每页中的目录项和第一页中的目录名称是否相同
							arg2.getCell(i, 5).setValue(j);//如果相同,则设置目录页第5列对应的值为对应页码,注意,此处是arg2对象
							arg2.getCell(i, 5).setHyperlink("javaScript:toPage('report1',"+j+")");//设置超链接
							page=j;//为提高计算效率,下次在循环时,不用从第2页开始,从上次终端的页码开始就行
							break;//找到页码,跳出此处循环,提高效率
						}	
						}
					} 
				 }
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
	   } 
	   public void beforeCalc(Context arg0, IReport arg1) { 
	   } 
	
}

核心思路就是在类中根据名称进行匹配,获取页码,放到对应单元格中,将编译好的类放到报表类路径中,如:应用的 WEB-INF\classes 下,注意如果有包路径,此处要要带相应的路径,放置过去后重启应用,在页面端访问报表,结果如下:

3jpg

可以看到,目录后边会生成对应的页码,word 中目录有个功能点是,点击页码,能够快速跳转到对应的页数,报表中同样可以增加对应的功能,报表提供了一个跳转页数的 js 函数,toPage,在 java 类中,在对应的页码单元格设置了一个超链接,调用这个 js 就行:

arg2.getCell(i, 5).setHyperlink("javaScript:toPage('report1',"+j+")");

这样,在页面端点击页码,就能够快速跳转到对应页数,当然,此功能要求报表在页面端分页后才有效,否则无法跳转,并且如果导出到 word 的话,只能显示页数,无法跳转。

通过这个例子可以看到,通过拿目录项中的单元格数据和数据表中的单元格进行匹配,来获取页码,但是在实际使用中,目录页中的目录名称和报表中的数据名称可能并不完全一致,比如目录页中叫东北,报表数据中叫东北地区,很难严格匹配,这样可以换种变通的方法,报表单元格属性栏中有个注释属性,可以在这个里边写上和目录项匹配的值,然后 java 类中可以根据这个属性的值做匹配,这样能够实现更加灵活的效果,更多 api 接口可以参考报表帮助文档《程序员参考》,后续带来更多 api 在报表实际需求中的应用。

更多报表样式类问题请看:报表样式分类导航
* 按段分组报表制作
* 如何开发主从报表
* 报告式报表的制作
* 报表中如何实现不规则布局
* 报表工具轻松搞定卡片式报表