6.3 样式报表打印方案
6.3.1 业务报表
业务报表这个概念比较笼统,也很繁杂,在这里笔者只选取了库存明细表这样一个业务进行演示业务报表,库存信息表在企业应用中非常广泛,几乎所有系统中都会有库存信息表业务逻辑的实施。库存管理是信息管理系统中很重要的模块,而库存信息打印则是报表中很常见的环节。
1.方案分析
本例程使用JavaScript+CSS做打印演示。在报表中使用JavaScript+CSS是很常见的一种形式,JavaScript通过<OBJECT classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2 height=0 id=WB width=0/>调用IE本身的打印操作,再使用CSS控制页面的某些部分是否显示,同时使用CSS控制分页打印,为了更好地理解打印业务报表方案,下面给出流程图,如图6.25所示。

图6.25 业务报表流程图
2.实施过程
在开发库存管理系统时,通常要求使用库存明细表显示数据,同时具备打印功能,用户需要以相同类别的数据按照用户需要输出的行数进行打印操作,这样方便用户分类查询报表中的数据,在这种情况下,就可以应用到业务报表打印,如图6.26所示。

图6.26 打印库存明细表

图6.27 分页打印库存明细表
首先建立一个库存信息表,名为tb_ywbb,数据结构如表6.5所示。
表6.5 表格tb_ywbb
|
字段名称 |
数据类型 |
描述 |
是否为空 |
|
id |
varchar(30) |
序列号 |
not null |
|
spname |
varchar(50) |
商品名称 |
null |
|
cd |
varchar(50) |
商品产地 |
null |
|
gg |
varchar(50) |
商品重量 |
null |
|
dw |
varchar(10) |
商品单位 |
null |
|
dj |
money |
商品单价 |
null |
|
kcsl |
int(4) |
库存数量 |
null |
实例位置:光盘\mr\6\6.3\6.3.1\08
为了取得数据表中的值,创建JavaBean名为Ywbb.java,Ywbb.java除了基本的setXXX(),getXXX()方法之外,还有一个根据条件取数据的getReport()方法,此类的参数是用户输入的产地, getReport()方法返回值类型为集合Collection,方便JSP取值时使用,Ywbb.java部分程序代码如下:
例程6-41 代码位置:光盘\mr\6\6.3\6.3.1\08\src\com\wsy\Ywbb.java
public Collection getReport(){
Collection ret=new ArrayList();
ConnDB conn=new ConnDB();
String sql="select * from tb_ywbb";
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Ywbb y=new Ywbb();
y.setId(rs.getString(1));
y.setSpname(rs.getString(2));
y.setCd(rs.getString(3));
y.setGg(rs.getString(4));
y.setDw(rs.getString(5));
y.setDj(rs.getString(6));
y.setKcsl(rs.getString(7));
//System.out.println(rs.getString(3));
ret.add(y);
}
conn.close();
}catch(Exception e){
e.printStackTrace();
if(conn!=null||rs!=null){
try{
conn.close();
rs.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
}
return ret;
}
在ywbb.jsp中有一个需要用户输入的分页打印的行数变量为rsRow,此时需要使用JavaScript判断用户输入的值是否为数字,同时需要限制用户输入1~9的数字,具体程序代码如下:
例程6-42 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
function deal(){
var Expression=/^[1-9]+(\d*$)/;
var objExp=new RegExp(Expression);
if(objExp.test(form1.rsRow.value)==false){
alert("您输入的值有误!");return false;
}else{
return true;
}
}
在JSP的form中引用上述JavaScript方法。程序代码如下:
例程6-43 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
<form name="form1" action="ywbb.jsp" method="post" onSubmit="return deal()">
在此例程中笔者并没有使用传统的<form>提交方式,而是使用JavaScript提交方式,当用户输入完数字后,单击回车即可提交表单,程序代码如下:
例程6-44 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
function enter(){
if(event.keyCode==13){
var Expression=/^[1-9]+(\d*$)/;
var objExp=new RegExp(Expression);
if(objExp.test(form1.rsRow.value)==true){
form1.submit()
}
}
}
ywbb.jsp应用了thread标记、tfoot标记,thread标记用于设置表格的表头,tfoot标记用于设置表格的表尾。程序代码如下:
例程6-45 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
<thead style="display:table-header-group;font-weight:bold">
<tr>
<td width="35" height="27" align="center" bgcolor="#efefef">编号</td>
<td width="140" align="center" bgcolor="#efefef">商品名称</td>
<td width="103" align="center" bgcolor="#efefef">产地</td>
<td width="82" align="center" bgcolor="#efefef">规格</td>
<td width="61" align="center" bgcolor="#efefef">计量单位</td>
<td width="75" align="center" bgcolor="#efefef">单价</td>
<td width="65" align="center" bgcolor="#efefef">库存数量</td>
<td width="65" align="center" bgcolor="#efefef">库存金额</td>
</tr>
</thead>
除了在JSP显示数据库中的数据外,还需要计算每种商品的库存金额,方法是单价乘以数量,为了方便操作,将单价转型为Float浮点型。totall代表累加本页的总金额,totalh代码累加表中全部数据的总金额。程序代码如下:
例程6-46 代码位置:光盘\mr\6\.6.3\6.3.1\08\ywbb.jsp
Float total=Float.parseFloat(yw.getDj())*Float.parseFloat(yw.getKcsl());
totall=totall+total;
totalh=totalh+total;
本例程如何分页是一个难点,取数据表中的id值,转型为int型,看此变量是否能整除用户输入的分页数字,如果能整除,使用CSS控制分页打印显示,置totall=0f,以便下一页数据从零开始计算。程序代码如下:
例程6-47 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
<%
if(Integer.parseInt(id)%intRsRow==0){%>
<tr style="page-break-after:always;">
<td align="right" height="25" colspan="8" bgcolor="#efefef"> 本页合计金额:<%=totall %>(元)</td>
</tr>
<%
totall=0f;
}
设置打印机属性,media类型是CSS媒体类型,用于直接引入媒体属性。@media print:指用于打印机的不透明介质,程序代码如下:
例程6-48 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
<style>
@media print{
div{display:none}
}
</style>
使用JavaScript设置打印按钮,打印预览按钮,需要在<head></head>之间加入下述代码。
例程6-49 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
function previewPrint(){
WB.ExecWB(7,1)
}
function setPrint(){
WB.ExecWB(8,1);
}
在JSP中引用打印按钮,打印预览按钮,程序代码如下:
例程6-50 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
<INPUT type="button" value="打印设置" id=button1 name=button1 onclick="setPrint();">
<INPUT type="button" value="打印预览" id=button2 name=button2 onclick="previewPrint();">
<INPUT type="button" value="打印" id=button3 name=button3 onclick="window.print();">
为了使用打印按钮,还需要在body中定义WB,程序代码如下:
例程6-51 代码位置:光盘\mr\6\6.3\6.3.1\08\ywbb.jsp
<OBJECT classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2 height=0 id=WB width=0>
</OBJECT>
制作查询条件界面,为了引用表头文件,用到JSP中的<jsp:include page="top.jsp"/>,将top.jsp包含到ywbb.jsp中,在top.jsp中,设计表单提交到ywbbselect.jsp,作为显示查询条件界面使用。这个时候调用JavaBean的方法getReport(String cds),此时用到了Java中的重载技术。SQL语句使用"like%name%"方式,"%"代表任意零个或多个字符。程序代码如下:
例程6-52 代码位置:光盘\mr\6\6.3\6.3.1\08\src\com\wsy\Ywbb.java
public Collection getReport(String cds){
Collection ret=new ArrayList();
ConnDB conn=new ConnDB();
String sql="select * from tb_ywbb where cd like '%"+cds+"%'";
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Ywbb y=new Ywbb();
y.setId(rs.getString(1));
y.setSpname(rs.getString(2));
y.setCd(rs.getString(3));
y.setGg(rs.getString(4));
y.setDw(rs.getString(5));
y.setDj(rs.getString(6));
y.setKcsl(rs.getString(7));
//System.out.println(rs.getString(3));
ret.add(y);
}
conn.close();
}catch(Exception e){
e.printStackTrace();
if(conn!=null||rs!=null){
try{
conn.close();
rs.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
}
return ret;
}
下面看一下查询分页,可以输入查询条件和打印记录数,例如在“每页显示”文本框中输入2,单击键盘的回车键,表示每页打印两行,结果如图6.28所示。

图6.28 分页查询打印明细报表
单击“打印设置”,结果如图6.29所示。

图6.29 打印设置
单击“打印预览”结果如图6.30所示。

图6.30 打印预览第一页

图6.31 打印预览第二页
3.补充说明
由于输入条件为中文,所以在request.getParameter 取值时会相应出现乱码,这时需要转码,使用StringTrans.java类,读者也可以配置过滤器完成转码工作。由于笔者设置MyEclipse中的JSP文件编码为UTF-8,于是在这里将取出的查询条件转成UTF-8。转码部分程序代码如下:
temp=chB.getBytes("iso-8859-1");
result=new String(temp,"UTF-8");
6.3.2 统计报表
统计报表在企业系统中应用广泛,也是报表中比较常见的形式,统计报表便利了用户对动态数据的统计,使数据一目了然。
1.方案分析
在这里笔者采用iReport+JasperReport组件形式实现统计报表,读者也可以根据实际情况应用其他方式实现统计报表。
本例程实现的原理是根据Employee(员工表)中的员工所属的部门号与deprtment(部门表)中的部门号的使用左外连接语句取出数据,即是取出各个部门的人数,做数字统计。得到数据后,在iReport组件中制作报表,将数据字段放入detail区域,制作完成报表后使用JasperReport组件将报表使用JSP界面控制显示。为了使读者更好的理解本方案,下面给出流程图,如图6.32所示。

图6.32 统计报表流程图
2.实施过程
在开发杰森特企业内部管理系统中,需要统计各个部门员工个数以及各个部门实发工资,而且需要以报表的形式表示,同时需要有打印操作,在这种情况下,就可以应用到统计报表,如图6.33所示。

图6.33 员工统计表
首先在数据库中创建两个表,员工信息表tb_Employee和部门信息表tb_Department表,数据结构如表6.6、表6.7所示。
表6.6 表格tb_Employee
|
字段名称 |
数据类型 |
描述 |
是否为空 |
|
empId |
int(4) |
员工序列号 |
not null |
|
depId |
int(4) |
部门序列号 |
null |
|
empName |
varchar(20) |
员工名称 |
null |
|
job |
varchar(20) |
职务 |
null |
|
mgr |
varchar(20) |
直接领导工号 |
null |
|
sal |
int(4) |
工资 |
null |
|
hiredate |
varchar(50) |
入职时间 |
null |
表6.7 表格tb_Department
|
字段名称 |
数据类型 |
描述 |
是否为空 |
|
deptId |
int(4) |
序列号 |
not null |
|
depName |
varchar(45) |
部门名称 |
null |
实例位置:光盘\mr\6\6.3\6.3.2\09
如果使用iReport组件设计报表,首先需要设置数据源,根据6.1.3节介绍,依次“新建报表”,命名为totalnew.jrxml,然后依此单击“创建数据连结”→“输入sql语句”,由于部门名称和员工名称在两个表内,所以需要用到左外连接。SQL程序代码如下:
select t2.depName,t1.empId,t1.empName from tb_Employee t1 left join tb_Deprtment t2 on t1.deptId=t2.deptId。
单击快捷键
,新建报表群组,单击“new”输入分组名称depName,即以某个字段进行分组,以及分组字段$F{depName},如图6.34所示。

图6.34 设置分组名称
将Fileds区域中的depName拖入depNameHeader栏中,empId、empName拖入detail栏中。根据个人设计,可以在title区加入修饰,如图6.35所示。

图6.35 iReport设计分组报表
需要统计个数的变量,新建一个Variable,右键单击“Variables”,单击“add”,选择variable,依次设置名称、类型、统计类型、统计的位置,本例统计depName这个组的人员个数,填入variable名称,variable类型,注意calculation Type,取count运算,其中包括sum,average等运算,Reset Type为运算的作用范围,Group针对组运算,Reset Group说明针对“depName”这个组进行统计,Variable Expression描述根据这个组中的哪个字段进行统计。设置如图6.36所示。

图6.36 iReport创建count变量
然后将名为count的variable拖入组区域。同理设计一个统计部门员工总工资的变量sum,设计如图6.37所示。

图6.37 iReport创建sum变量
为了在报表中显示图片,需要设计一个传图片路径的参数,右键单击parameter,创建一个名为BaseDir的参数,注意勾选"Use as a Prompt",具体设计如图6.38所示。

图6.38 iReport创建BaseDir参数
在使用iReport组件添加图片时,主要是填写图片路径,使系统知道需要显示的图片所在的位置,在这里笔者将图片放入Tomcat安装路径下\Webapps\JasperReportproject\reports路径中,为了便于JSP调用,图片路径设置为new File($P{BaseDir}, "*.jpg"),参数BaseDir作为一个变量,在JSP调用时将Tomcat安装路径下\Webapps\JasperReportproject\reports路径赋予参数BaseDir,这样做的好处是,图片放置在任何位置,只要在JSP中赋予不同的值,系统便可以找到,并显示。具体设置如图6.39所示。

图6.39 iReport设置图片路径
虽然iReport组件完成了报表的制作,但是在实际的开发中,非专业人员不可能自己亲自动手用iReport制作报表,于是还要借助JasperReport类库,可以轻松在JSP或Servlet中使用制作完成的报表。
首先在MyEclipse中新建Web项目,起名为JasperReportproject,解压JasperReport,将JaperReport路径中的lib以及dist目录下的jar包复制到JasperReportproject项目WEB-INF\lib中,iReport组件制作的报表格式是jrxml,通过单击iReport组件设计窗口的菜单栏中
按钮后可得到此文件,JasperReport正是应用此类格式的文件。
但多数时候程序不会使用这种方式进行编译,而是使用JasperCompileManager.compileReportToFile(String sourcedir,String destdir)这个方法,第一个参数是jrxml文件所在的目录,第二个参数是编译成jasper后放入的目录。
使用JasperReport类库首先将上面制作好的totalnew.jrxml放入Tomcat安装路径下\Webapps\JasperReportproject\reports中,通过JasperReport提供的API完成*.jrxml文件转换为*.jasper文件。程序代码如下:
例程6-53 代码位置:光盘\mr\6\6.3\6.3.2\09\Compile.jsp
JasperCompileManager.compileReportToFile(application.getRealPath("/reports/total2.jrxml"),application.getRealPath("/reports/total2.jasper"));
%>
编译成功后,在JSP中读取totalnew.jasper,首先需要连接数据库,名为conn,然后建立一个名为parameters的Map类型对象,将reportFile.getParentFile()赋予BaseDir这个参数,其实reportFile.getParentFile()的结果正是Tomcat安装路径下\Webapps\JasperReportproject\reports这个路径,将reportFile.getParentFile()赋予BaseDir这个参数,系统便可以找到图片所在的位置。实例化JasperCompileManager对象,对象引用JasperCompileManager中的runReportToPdf()方法,作用是将报表转换成PDF格式输出,此方法参数有3个,分别为*.jasper的路径、parameters和conn,创建一个输出流, runReportToPdf()返回值为byte,设置好以PDF流方式输出。程序代码如下:
例程6-54 代码位置:光盘\mr\6\6.3\6.3.2\09\total2.jsp
<%@ page language="Java" import="Java.util.*,Java.sql.*,Java.io.*,net.sf.JasperReports.engine.*" pageEncoding="UTF-8"%>
<%
File reportFile = new File(application.getRealPath("/reports/totalnew.jasper"));
Map parameters = new HashMap();
parameters.put("BaseDir", reportFile.getParentFile());
JasperRunManager run=new JasperRunManager();
Connection conn=null;
try { Class.forName("com.microsoft.jdbc.SQL Server 2000.SQLServerDriver");
conn=DriverManager.getConnection("jdbc:microsoft:SQL Server 2000://localhost:1433;DatabaseName=db_FABD","sa","");
}catch(Exception e) {
e.printStackTrace();
}
byte[] bytes = run.runReportToPdf(reportFile.getPath(), parameters, conn);//把报表内容放入流中。
response.setContentType("application/pdf");
response.setContentLength(bytes.length);
ServletOutputStream ouputStream = response.getOutputStream();
ouputStream.write(bytes, 0, bytes.length);
ouputStream.flush();
out.clear();
out=pageContext.pushBody();
ouputStream.close();
%>
运行JasperReportproject项目,如图6.40所示。

图6.40 JasperReportPDF格式输出首页
单击图6.40中的compile连接,进入报表编译界面,如图6.41所示。

图6.41 JasperReport编译界面
单击图6.41中的execute超连接,实现*.jrxml编译成*.jasper。读者应注意,单击执行成功后方可显示报表。单击source超连接,可显示compile.jsp的源代码,如图6.42所示。

图6.42 查看源代码
单击PDF output,进入报表输出界面,如图6.43所示。

图6.43 JasperReport以PDF格式输出界面
单击图6.43中execute超连接,可以输出PDF报表。
3.补充说明
读者也许会发现此时中文输出为空白,方法很简单,应用6.1.1节中提到的iTextAsian.jar包,将此包放入iReport安装目录下\lib中,回到iReport界面,选择所要显示中文区域,在iReport窗口的右面属性栏中找到"PDF font name"选项更改为"STSong_Light",找到"PDF Embeded PDF Encoding"选项更改为"UniGB-UCS2-H(Chinese simplified)"。这样即可在生成PDF文档时正常显示中文。
JasperReport1.0.1解压缩后JasperReport1.0.1安装路径下\demo\samples中包括很多报表的例子,读者有兴趣可以参看,虽然iReport和JasperReport为开源项目,但官方网站上的相关技术文档也是需要付一些费用的。
6.3.3 财务报表
财务报表应用广泛,且相当专业,作为程序员来讲,深入了解财务知识显然不现实,所以当做类似这种专业性很强的项目时,一面需要积极向客户了解需求,一面需要请教财务方面的专业人士才可以达到预期的效果。
1.方案分析
财务报表笔者选择JavaScript+Excel方式实现,即将Web页面中的数据信息导出到Excel文件中并打印。在Excel中有很多财务报表的函数,通过JavaScript方式使数据导入到Excel文件后,便可以使用相应的函数处理导入后的数据并打印。
本例程实现的功能是取出用户输入的时间与数据库中时间的年差,导出到Excel中,再通过Excel的功能计算出此差额年数用户所得金额。具体实现过程,如图6.44所示。

图6.44 财务报表流程图
2.实施过程
在开发财务系统时,由于财务人员一般使用Excel处理财务数据,所以通常要求将数据导出到Excel中进行操作并打印,如图6.45所示。

图6.45 财务报表
为了更好的演示财务报表,首先创建个人财务信息表,名称为tb_cwbb,结构如表6.8所示。
表6.8 表格tb_cwbb
|
字段名称 |
数据类型 |
描述 |
是否为空 |
|
id |
int(4) |
表序列号 |
not null |
|
nlv |
varchar(50) |
年利率 |
null |
|
je |
varchar(50) |
每月应付金额 |
null |
|
ckrq |
varchar(20) |
存款集体时间 |
null |
|
name |
varchar(20) |
存款人姓名 |
null |
实例位置:光盘\mr\6\6.3\6.3.3\10
为了取得数据表中的值,创建JavaBean名为Cwbb.java,Cwbb.java除了基本的setXXX()、getXXX()方法之外,还有一个根据条件取数据的getReport()方法,此方法的参数是用户输入的产地, getReport()方法返回值类型为集合Collection,方便JSP取值时候使用。在此方法中的SQL语句datediff(year,ckrq,'"+time+"')as year的意思是取字段ckrq和time相差的是年数,差额以year为字段名打印。 ConnDB.java是连接数据库程序,由于与以上章节代码相同,所以本节不再赘述,部分程序代码如下:
例程6-55 代码位置:光盘\mr\6\6.3\6.3.3\10\src\com\wsy\Cwbb.java
public Collection getReport(String time){
Collection ret=new ArrayList();
ConnDB conn=new ConnDB();
String sql="select id,nlv,je,name,datediff(year,ckrq,'"+time+"') as year,ckrq from tb_cwbb";
ResultSet rs=conn.executeQuery(sql);
try{
while(rs.next()){
Cwbb c=new Cwbb();
c.setId(rs.getString("id"));
c.setNlv(rs.getString("nlv"));
c.setJe(rs.getString("je"));
c.setCkrq(rs.getString("ckrq"));
c.setName(rs.getString("name"));
c.setYear(rs.getString("year"));
ret.add(c);
}
conn.close();
}catch(Exception e){
e.printStackTrace();
if(conn!=null||rs!=null){
try{
conn.close();
rs.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
}
return ret;
}
使用JavaScript打印主要是应用ActiveXObject()构造函数创建一个Excel.Application对象实例,如var objectVar=new ActiveXObject(Excel.Application),创建Word对象,如:Var objectVar=new ActiveXObject(Word.Application)。数据以迭代函数借助JavaBean方式取出,设置打印连接,调用outExcel()事件。部分程序代码如下:
例程6-56 代码位置:光盘\mr\6\6.3\6.3.3\10\src\com\wsy\Cwbb.java
<table width="760" height="50" border="0" cellpadding="0" cellspacing="0" >
<tr>
<td><form name="form1" method="post" action="cwbb.jsp">
<p><span class="zi">请填写要计算总金额的月份</span>:
<input type="text" name="time" readOnly onClick="setDay(this);">
<input type="submit" name="Submit" value="提交"></p>
<p class="zi">您输入的年月为:<%=time%></p>
</form>
<div align="center">
<table width="500" height="30" border="1" cellpadding="0" cellspacing="0" id="pay">
<tr class="zi">
<td width="69">编号</td>
。。。//表头设置
<td width="120">存款人姓名</td>
</tr>
<%
if(time!=null){
Collection ret=c.getReport(time);
Iterator it=ret.iterator();
while(it.hasNext()){
Cwbb cw=(Cwbb)it.next();
%>
<tr class="zi">
<td ><%=cw.getId() %></td>
<td ><%=cw.getNlv() %></td>
<td><%=cw.getJe() %></td>
<td><%=cw.getCkrq() %></td>
<td><%=cw.getYear() %></td>
<td><%=cw.getName() %></td>
</tr>
<%
}
}
%>
</table>
</div>
<p align="center"><a href="cwbb.jsp#" onClick="outExcel();">打印</a></p></td>
<script language="Javascript">
function outExcel(){
var table=document.all.pay;
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%>";
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(" ","");
}
}
excelapp.UserControl = true;
}
</script>
</tr>
</table>
同时设置要打印表格的id为"pay",具体代码如下:
例程6-57 代码位置:光盘\mr\6\6.3\6.3.3\10\cwbb.jsp
<table width="500" height="30" border="1" cellpadding="0" cellspacing="0" id="pay">
在图6.46中单击文本框,会弹出日期选择控件,选择日期后单击提交按钮后显示所有结果,如图6.47所示,单击“打印”连接,将结果导出到Excel中。

图6.46 财务报表

图6.47 财务报表
3.补充说明
Excel财务函数众多,只要取出Excel需要的数值,就可以相应的计算出财务结果,由于程序员并不是专业的财会人员,所以做项目时,程序员提供数据,专业人士提供计算公式,这样项目才可以适应实际需求,并达到预期目的。






