6.5、自定义报表方案
现在越来越多的应用系统中都要求提供自定义报表功能,即用户可以自行设计、修改报表。除了要求打印操作之外,还要求可以导入多种文件格式,例如:Excel、Word、PDF等文件格式。然而自定义报表不仅仅功能强大,同时设计起来也比较复杂。
1.方案分析
自定义报表实现的功能是比较强大的,自定义报表主旨在于根据用户的选择动态生成报表,这里笔者为用户提供了表格字段选择,报表标题动态化,报表标题字体、报表正文字体、报表正文颜色、报表正文字体大小的选择,除此之外,根据用户选择的字段动态生成查询条件下拉列表,用户可以根据选择的字段填写查询条件进行查询,可以动态定义打印分页页码,预置了“打印预览”、“打印”、“打印设置”按钮,可以将报表分别导出到Excel、Word、PDF格式进行显示。为了更好的理解本方案,请读者看流程图,如图6.90所示。

图6.90 自定义报表打印方案流程图
2.实施过程
在开发图书馆管理系统时,要求系统对图书借阅情况进行报表打印,而为了得到更为详尽的信息,用户需要自定义图书借阅情况报表,并且能打印,可以根据不同情况将报表导入到Excel、Word、PDF等文件格式中。在这种情况下,就可以应用到自定义报表打印,如图6.91、6.92所示。

图6.91 自定义报表

图6.92 自定义报表打印

为了更好的演示本方案,首先建立一个图书借阅数据表,数据结构如表6.19所示。
表6.19 表格 tb_zdbb
字段名称
|
数据类型
|
描述
|
是否为空
|
id
|
int(4)
|
序列号
|
not null
|
tsmc
|
varchar(50)
|
图书名称
|
null
|
jysj
|
varchar(20)
|
借阅时间
|
null
|
yhsj
|
varchar(20)
|
应还时间
|
null
|
cbs
|
varchar(20)
|
出版社
|
null
|
sj
|
varchar(20)
|
书架
|
null
|
dj
|
money
|
单价
|
null
|
下面详细介绍各个部分实现原理。
为了实现表字段动态选择,可以使用JavaScript来实现,在一个下拉中菜单选择一些字段名称后,在另一个下拉菜单中即时显示这些字段,可以在这个下拉菜单中删除不需要显示的字段名称,选好字段名称后,使用JavaScript做表单提交,为了取得下拉菜单中的值,在表单中使用一个Hidden类型的文本框,把JavaScript中提交上来的字段值赋给这个文本框,部分程序代码如下:
例程6-103 代码位置:光盘/mr/6/6.5/6.5.1/20/zdbbnew.jsp
if (count == 0) {
strValues = document.choiceForm.choiceBox.options[i].value;
}
else {
strValues = strValues + "," + document.choiceForm.choiceBox.options[i].value;
}
count++;
if (strValues.length == 0) {
alert("您没有选择字段值");
}
else {
choiceForm.hidd.value=strValues;
choiceForm.submit();
alert("您已经选择的字段为:/r/n" + strValues);
}
取出文本框中的值后,由于这个字符串的值以逗号分割,所以这里笔者使用String类中的split()方法,使这个数值以逗号分割返回到数组中,部分程序代码如下:
例程6-104 代码位置:光盘/mr/6/6.5/6.5.1/20/table.jsp
if(request.getParameter("hidd")!=""&&request.getParameter("hidd")!=null){
String available=request.getParameter("hidd");
String sub[]=available.split(",");
然后根据数组然后通过用户选择的表格字体、颜色、字体大小等使用CSS动态设置表格样式,部分程序代码如下:
例程6-105 代码位置:光盘/mr/6/6.5/6.5.1/20/table.jsp
<table width="737" style="font:<%=s.tranC(btzt)%>; font-size:14px" >
<tr>
<td><div align="center" >
<%=s.tranC(biaoti) %>
</div></td>
</tr>
</table>
<table width="737" border="1" bordercolor="#CCCCCC" style="font:<%=s.tranC(zwzt)%>; color:<%=ys%>; font-size: <%=zwztdx%> " >
当用户没有根据字段进行查询的情形下,页面显示表格中的所有数据,为了美化页面,本方案中使用了分页技术,使用分页需要一个计算页码的类DownTable.java,DownTable.java的主要功能是计算总页码,设置每页显示的行数,取当前页数,取表格的总行数。为了实现DownTable.java中的方法,需要一个连接数据库的类ConnDB.java,此类与上述章节中的连接数据库类相同,所以在此笔者不再赘述。
在DownTable.java类中,计算表格总行数的方法无参数,返回值类型为int型,为表格总行数,此方法的具体实现实际上就是一条SQL语句,使用语句"select count(*) from tb_zdbb",结果集中返回第一个字段的值即为数据表的总行数,程序代码如下:
例程6-106 代码位置:光盘/mr/6/6.5/6.5.1/20/src/com/wsy/DownTable.java
public int getRows()
{
try{
ConnDB conn=new ConnDB();
rs=conn.executeQuery("select count(*) from tb_zdbb");
if(rs.next())
{
totalRows=rs.getInt(1);
}
s.close();
conn.close();
}
catch(Exception e)
{
}
return totalRows;
}
得到数据表总行数后,由于DownTable.java中有一个setXXX()、getXXX()方法,用于用户控制每页显示的行数传值使用,所以可以使用getPageSize()取出用户希望每页显示的行数,在这里使用总行数除以每页显示的行数便可以得到总页数,程序代码如下:
例程6-107 代码位置:光盘/mr/6/6.5/6.5.1/20/src/com/wsy/DownTable.java
public int getTotalPage(){
if(getRows()%getPageSize()==0)
return getRows()/getPageSize();
else
return getRows()/getPageSize()+1;
}
分页除了一个计算页码的DownTable.java类之外,还需要一个“首页”、“上一页”、“下一页”、“尾页”连接,在连接中笔者设置一个累计变量current,用于取得当前用户浏览的页数,“首页”的连接中current值赋为1,“上一页”的连接current值赋为current+1,“下一页”的连接current值赋为current-1,“尾页”的连接中current值赋为DownTable.java类中的getTotalPage()方法中返回的值。程序代码如下:
例程6-108 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
<div align="center">
<table width="65%">
<tr class="style4">
<td><a href=table2.jsp?current=1>首页</a></td>
<td><a href=table2.jsp?current=<%=current-1 %>>上一页</a></td>
<td><a href=table2.jsp?current=<%=current+1 %>>下一页</a></td>
<td><a href=table2.jsp?current=<%=down.getTotalPage() %>>尾页</a></td>
</tr>
</table></div>
当用户没有输入查询条件时,通过取出current的值,便可以使用SQL语句进行分页处理,分页SQL语句为: "select top "+down.getPageSize()+" * from tb_zdbb where(id not in(select top "+down.getPageSize()*(current-1)+" id from tb_zdbb order by id)) order by id",然后根据用户选择的字段值循环表格列,根据列名,也就是字段名循环显示数据表中的数据,在此有一个问题,下拉菜单中的显示字段名称为中文,但下拉菜单中的value值却是英文,但在表格中显示字段时需要中文显示,根据字段取数据数据表中数据的时候则需要英文显示,此时,为了解决这个问题,笔者引用Map数据结构,把中文字段和英文字段对应起来,程序代码如下:
例程6-109 代码位置:光盘/mr/6/6.5/6.5.1/20/table.jsp
Map a=new HashMap();
a.put("id","id");
a.put("tsmc","图书名称");
a.put("jysj","借阅时间");
a.put("yhsj","应还时间");
a.put("cbs","出版社");
a.put("sj","书架");
a.put("dj","定价");
当遇到下拉菜单时,可以使用如下代码,数组sub为用户选择的字段值,a.get(sub[i])为在Map中取key对应中文值。
例程6-110 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
<select name="cxtj" id="aa" >
<%
for(int i=0;i<sub.length;i++){
%>
<option value=<%=sub[i] %>><%=a.get(sub[i])%></option>
<%} %>
</select>
在页面显示表格时,字段名称需要中文显示,程序代码如下:
例程6-111 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
for(int i=0;i<sub.length;i++){
%>
<td><%=a.get(sub[i]) %></td>
<%} %>
当显示数据时,根据结果集循环表格的行,根据用户选择的字段个数循环列,此时涉及到分页打印,根据用户输入的行数使用CSS控制打印分页,使用变量i累计变量,当i是用户输入行数的倍数时,使用CSS进行表格打印分页,在<tr>中使用"style='page-break-after:always'"样式,可以达到分页打印功能,程序代码如下:
例程6-112 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
<%
int i=1;
while(rs.next()){
%>
<tr <%if(i%fyRow==0){ %> style="page-break-after:always;"<%}%>>
<%
for(int j=0;j<sub.length;j++){
%>
<td><%=rs.getString(sub[j]) %></td>
<%
} %>
</tr>
<%
i++;
} %>
当用户选择查询条件提交时,String类型的SQL变量被赋值为"select * from tb_zdbb where "+cxtj+" like '%"+s.tranC(input)+"%' order by id",使用like进行模糊查询,tranC()是StringTrans.java类方法,是一个转码方法,把字符转为UTF-8编码,这个类曾经在以上章节中使用过,所以在此不再过多赘述。
结果显示到Web页面后,需要进行打印操作,本方案采用WebBrowser方式实现打印操作,需要在HTML中建立Object标签,调用WebBrowser控件,程序代码如下:
例程6-113 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
<OBJECT classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2 height=0 id=WB width=0>
</OBJECT>
使用JavaScript设置打印设置、打印预览函数,代码如下:
例程6-114 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
function previewPrint(){
WB.ExecWB(7,1)
}
function setPrint(){
WB.ExecWB(8,1);
}
建立相关的打印超连接,并调用WebBrowser控件的相应参数实现打印预览、打印等功能。代码如下:
例程6-115 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
<a href="#" class="style4" οnclick="previewPrint();">打印预览 </a><a href="#" class="style4" οnclick="window.print();"> 打印 </a> <a href="#" class="style4" οnclick="setPrint();"> 打印设置</a>
当用户单击打印预览连接时,由于打印分页值默认为二,所以打印以两行进行分页,如图6.93所示。

图6.93 自定义报表打印预览
除了打印操作外,系统向用户提供了将报表导入到Excel、Word、PDF中,导出连接如下:
例程6-116 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
<a href="#" class="style4" onClick="outExcel();"> 导出到Excel</a><a href="#" class="style4" onClick="outDoc();"> 导出到Word</a><a href="pdfOutput.jsp" class="style4"> 导出到PDF</a></div>
首先来看将报表导出到Excel文件格式,使用JavaScript的ActiveXObject()构造函数创建一个Excel.Application对象的实例来实现此功能,程序代码如下:
例程6-117 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
<script language="javascript">
function outExcel(){
var table=document.all.book;
row=table.rows.length;
column=table.rows(1).cells.length;
var excelapp=new ActiveXObject("Excel.Application");
excelapp.visible=true;
objBook=excelapp.Workbooks.Add(); //添加新的工作簿
var objSheet = objBook.ActiveSheet;
title=objSheet.Range("D1").MergeArea; //合并单元格
title.Cells(1,1).Value ="书籍借还列表";
title.Cells(1,1).Font.Size =16;
for(i=1;i<row+1;i++){
for(j=0;j<column;j++){
objSheet.Cells(i+1,j+1).value=table.rows(i-1).cells(j).innerHTML.replace(" ","");
}
}
objBook.SaveAs("C:/bookList.xls");
excelapp.UserControl = true;
}
</script>
用户单击导出到Excel连接,结果如图6.94所示。

图6.94 自定义报表导出到Excel
将报表导出到Word格式文件中,同样使用JavaScript的ActiveXObject(),只是此时ActiveXObject()构造函数创建一个Word.Application对象的实例来实现导出到Word功能,程序代码如下:
例程6-118 代码位置:光盘/mr/6/6.5/6.5.1/20/table2.jsp
<script language="javascript">
function outDoc(){
var table=document.all.book;
row=table.rows.length;
column=table.rows(1).cells.length;
var wdapp=new ActiveXObject("Word.Application");
wdapp.visible=true;
wddoc=wdapp.Documents.Add(); //添加新的文档
thearray=new Array();
//将页面中表格的内容存放在数组中
for(i=0;i<row;i++){
thearray[i]=new Array();
for(j=0;j<column;j++){
thearray[i][j]=table.rows(i).cells(j).innerHTML;
}
}
var range = wddoc.Range(0,0);
range.Text="书籍借还自定义列表"+"/n";
wdapp.Application.Activedocument.Paragraphs.Add(range);
wdapp.Application.Activedocument.Paragraphs.Add();
rngcurrent=wdapp.Application.Activedocument.Paragraphs(3).Range;
var objTable=wddoc.Tables.Add(rngcurrent,row,column) //插入表格
for(i=0;i<row;i++){
for(j=0;j<column;j++){
objTable.Cell(i+1,j+1).Range.Text = thearray[i][j].replace(" ","");
}
}
wdapp.Application.ActiveDocument.SaveAs("bookList.doc",0,false,"",true,"",false,false,false,false,false);
wdapp.Application.Printout();
wdapp=null;
}
</script>
当用户单击导出到Word连接时,结果如图6.95所示。

图6.95 自定义报表导出到Word
将报表导入到PDF中过程较以上两种导入操作要繁琐一些,本方案使用iText组件实现导入PDF,使用iText组件中com.lowagie.text.pdf.pdfPTable类来实现,iText组件详细介绍请读者参看6.1.1。
首先来看表题部分,创建列数为用户选择的字段个数的表格,创建单元格,把用户输入的标题添加到单元格中,此时使用PDFParagraph.java控制中文输出,单元格向右扩展,扩展单元格的个数为用户选择的字段的个数,程序代码如下:
例程6-119 代码位置:光盘/mr/6/6.5/6.5.1/20/pdfOutput.jsp
PdfPTable table=new PdfPTable(sub.length);
PdfPCell cellh=new PdfPCell(new PDFParagraph(s.tranC(biaoti)));
cellh.setColspan(sub.length);
table.addCell(cellh);
然后是表体部分,首先是添加字段名称,根据用户选择的字段个数,创建单元格,在单元格中添加用户选择的字段名称,注意利用亚洲包类库使字段名称转化为中文显示,程序代码如下:
例程6-120 代码位置:光盘/mr/6/6.5/6.5.1/20/ pdfOutput.jsp
PdfPCell cell=null;
for(int i=0;i<sub.length;i++){
cell=new PdfPCell();
cell.addElement(new PDFParagraph((String)a.get(sub[i])));
table.addCell(cell);
}
表体部分除了字段名称之外还有表格数据,首先连接数据库,根据字段名称取出数据表中的值,添加到单元格中,然后将单元格添加到表格中。程序代码如下:
例程6-121 代码位置:光盘/mr/6/6.5/6.5.1/20/ pdfOutput.jsp
ConnDB conn=new ConnDB();
ResultSet rs=conn.executeQuery(sql);
while(rs.next()){
for(int j=0;j<sub.length;j++){
table.addCell(new PDFParagraph(rs.getString(sub[j])));
}
}
最后以PDF流的形式输出。
当用户单击导出到PDF连接时,结果如图6.96所示。

图6.96 自定义报表导出到PDF
3.补充说明
本方案中笔者采用WebBrowser方式进行打印操作,WebBrowser是IE内置的浏览器控件,该控件的具体参数为:
document.all.WebBrowser.Execwb(7,1):表示打印预览。
document.all.WebBrowser.Execwb(6,1):表示打印。
document.all.WebBrowser.Execwb(6,6):表示直接打印。
document.all.WebBrowser.Execwb(8,1):表示页面设置。
document.all.WebBrowser.Execwb(4,1):表示另存为。
document.all.WebBrowser.Execwb(1,1):表示打开。
document.all.WebBrowser.Execwb(17,1):表示全选。
document.all.WebBrowser.Execwb(10,1):表示属性。
document.all.WebBrowser.Execwb(8,1):表示页面设置。
document.all.WebBrowser.Execwb(22,1):表示刷新页面。
document.all.WebBrowser.Execwb(45,1):表示关闭窗体时无提示。