`
log_cd
  • 浏览: 1098686 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

使用jasperreport生成报表

阅读更多
1.ReportUtils.java通用类
/**
 * 使用jasperReport做报表时的工具支持类.有两个用途,生成jasperPrint对象,和设置导出时的session
 */
public class ReportUtils {
	private HttpServletRequest request;
	private HttpServletResponse response;
	private HttpSession session;

	public ReportUtils(HttpServletRequest request, HttpServletResponse response) {
		this.request = request;
		this.session = request.getSession();
		this.response = response;
	}

	/**
	 * 获得JasperPrint对象;自定义填充报表时的parameter和dataSource. 参数说明和动态表头的用法参考上一方法
	 */
	public JasperPrint getJasperPrint(String filePath, Map parameter,
			JRDataSource dataSource) throws JRException {
		JasperReport jasperReport = null;
		try {
			jasperReport = (JasperReport) JRLoader.loadObject(filePath);
			return JasperFillManager.fillReport(jasperReport, parameter,
					dataSource);
		} catch (JRException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 获得JasperPrint对象;自定义填充报表时的parameter和connection
	 */
	public JasperPrint getJasperPrint(String filePath, Map parameter,
			Connection  conn) throws JRException {
		JasperReport jasperReport = null;
		try {
			jasperReport = (JasperReport) JRLoader.loadObject(filePath);
			return JasperFillManager.fillReport(jasperReport, parameter,
					conn);
		} catch (JRException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 通过传入List类型数据源获取JasperPrint实例
	 */
	public JasperPrint getPrintWithBeanList(String filePath, Map parameter,
			List list) throws JRException {
		JRDataSource dataSource = new JRBeanCollectionDataSource(list);
		return getJasperPrint(filePath, parameter, dataSource);
	}

	/**
	 * 传入类型,获取输出器
	 * 
	 * @param docType
	 * @return
	 */
	public JRAbstractExporter getJRExporter(DocType docType) {
		JRAbstractExporter exporter = null;
		switch (docType) {
		case PDF:
			exporter = new JRPdfExporter();
			break;
		case HTML:
			exporter = new JRHtmlExporter();
			break;
		case XLS:
			exporter = new JExcelApiExporter();
			break;
		case XML:
			exporter = new JRXmlExporter();
			break;
		case RTF:
			exporter = new JRRtfExporter();
			break;
		case CSV:
			exporter = new JRCsvExporter();
			break;
		case TXT:
			exporter = new JRTextExporter();
			break;
		}
		return exporter;
	}

	/**
	 * 获得相应类型的Content type
	 * @param docType
	 * @return
	 */
	public String getContentType(DocType docType){
		String contentType="text/html";
		switch(docType){
		case PDF:
			contentType = "application/pdf";
			break;
		case XLS:
			contentType = "application/vnd.ms-excel";
			break;
		case XML:
			contentType = "text/xml";
			break;
		case RTF:
			contentType = "application/rtf";
			break;
		case CSV:
			contentType = "text/plain";
			break;
		}
		return contentType;
	}

	public void setAttrToPage(JasperPrint jasperPrint, String report_fileName,
			String report_type) {
		session.setAttribute("REPORT_JASPERPRINT", jasperPrint);
		session.setAttribute("REPORT_FILENAME", report_fileName);
		session.setAttribute("REPORT_TYPE", report_type);
	}

	/**
	 * 定义了报表输出类型,固定了可输出类型
	 */
	public static enum DocType {
		PDF, HTML, XLS, XML, RTF, CSV, TXT
	}

	/**
	 * 编译报表模板文件jrxml,生成jasper二进制文件
	 * 
	 * @param jrxmlPath
	 * @param jrsperPath
	 * @throws JRException
	 */
	public void complieJrxml(String jrxmlPath, String jasperPath)
			throws JRException {
		JasperCompileManager.compileReportToFile(jrxmlPath, jasperPath);
	}

	/**
	 * 输出html静态页面,必须注入request和response
	 * 
	 * @param jasperPath
	 * @param params
	 * @param sourceList
	 * @param imageUrl
	 *            报表文件使用的图片路径,比如 ../servlets/image?image=
	 * @throws JRException
	 * @throws IOException
	 * @throws ServletException
	 */
	public void servletExportHTML(String jasperPath, Map params,
			List sourceList, String imageUrl) throws JRException, IOException,
			ServletException {
		response.setContentType("text/html");
		response.setCharacterEncoding("UTF-8");
		JRAbstractExporter exporter = getJRExporter(DocType.HTML);

		JasperPrint jasperPrint = getPrintWithBeanList(jasperPath, params,
				sourceList);

		session.setAttribute(
				ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE,
				jasperPrint);

		PrintWriter out = response.getWriter();

		exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
		exporter.setParameter(JRExporterParameter.OUTPUT_WRITER, out);
		exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, imageUrl);
		exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN,false);//不显示px
		exporter.setParameter(JRHtmlExporterParameter.IS_OUTPUT_IMAGES_TO_DIR, Boolean.FALSE);
		exporter.setParameter(JRHtmlExporterParameter.BETWEEN_PAGES_HTML, "<br style='page-break-before:always;'>"); 

		exporter.exportReport();
	}

	/**
	 * 输出html静态页面,必须注入request和response
	 * 
	 * @param jasperPath
	 * @param params
	 * @param sourceList
	 * @param imageUrl
	 *            报表文件使用的图片路径,比如 ../servlets/image?image=
	 * @throws JRException
	 * @throws IOException
	 * @throws ServletException
	 */
	public void servletExportHTML(String jasperPath, Map params,
			Connection conn, String imageUrl) throws JRException, IOException,
			ServletException {
		response.setContentType("text/html");
		response.setCharacterEncoding("UTF-8");
		JRAbstractExporter exporter = getJRExporter(DocType.HTML);

		JasperPrint jasperPrint = getJasperPrint(jasperPath, params,
				conn);

		session.setAttribute(
				ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE,
				jasperPrint);

		PrintWriter out = response.getWriter();

		exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
		exporter.setParameter(JRExporterParameter.OUTPUT_WRITER, out);
		exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, imageUrl);
		exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN,false);//不显示px
		exporter.setParameter(JRHtmlExporterParameter.IS_OUTPUT_IMAGES_TO_DIR, Boolean.FALSE);

		exporter.exportReport();
	}

	/**
	 * 生成不同格式报表文档
	 * 
	 * @param docType
	 *            文档类型
	 * @param jasperPath
	 */
	public void servletExportDocument(DocType docType, String jasperPath,
			Map params, List sourceList, String fileName) throws JRException,
			IOException, ServletException {

		if (docType == DocType.HTML) {
			servletExportHTML(jasperPath, params, sourceList, fileName);
			return;
		}

		JRAbstractExporter exporter = getJRExporter(docType);
		// 获取后缀
		String ext = docType.toString().toLowerCase();

		if (!fileName.toLowerCase().endsWith(ext)) {
			fileName += "." + ext;
		}
		// 判断资源类型
		if (ext.equals("xls")) {
			// 要想获得更好的视觉效果,可以添加以下代码
			 exporter.setParameter(
			 JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,
			 Boolean.TRUE); // 删除记录最下面的空行
			
			 exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET,
			 Boolean.FALSE);// 删除多余的ColumnHeader
			
			 exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND
			 , Boolean.FALSE);// 显示边框
		}

		response.setContentType(getContentType(docType));
		response.setHeader("Content-Disposition", "attachment; filename=\""
				+ URLEncoder.encode(fileName, "UTF-8") + "\"");

		exporter.setParameter(JRExporterParameter.JASPER_PRINT,
				getPrintWithBeanList(jasperPath, params, sourceList));
		
		OutputStream outStream = null;
		PrintWriter outWriter = null;
		
		if(ext.equals("csv")){//解决中文乱码问题
			response.setCharacterEncoding("GBK");
			outWriter = response.getWriter();
			exporter.setParameter(JRExporterParameter.OUTPUT_WRITER,outWriter);
		}else{
			outStream = response.getOutputStream();
			exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, outStream);
			if(ext.equals("txt")){
				exporter.setParameter(JRTextExporterParameter.CHARACTER_WIDTH, new Integer(80));
			    exporter.setParameter(JRTextExporterParameter.CHARACTER_HEIGHT, new Integer(25));
			    exporter.setParameter(JRTextExporterParameter.CHARACTER_ENCODING,"GBK");
			}
		}
		try {
			exporter.exportReport();
		} catch (JRException e) {
			throw new ServletException(e);
		} finally {
			if (outStream != null) {
				try {
					outStream.close();
				} catch (IOException ex) {
				}
			}
		}
	}

	/**
	 * 生成不同格式报表文档
	 * 
	 * @param docType
	 *            文档类型
	 * @param jasperPath
	 */
	public void servletExportDocument(DocType docType, String jasperPath,
			Map params, Connection conn, String fileName) throws JRException,
			IOException, ServletException {

		if (docType == DocType.HTML) {
			servletExportHTML(jasperPath, params, conn, fileName);
			return;
		}

		JRAbstractExporter exporter = getJRExporter(docType);
		// 获取后缀
		String ext = docType.toString().toLowerCase();

		if (!fileName.toLowerCase().endsWith(ext)) {
			fileName += "." + ext;
		}
		// 判断资源类型
		if (ext.equals("xls")) {
			// 要想获得更好的视觉效果,可以添加以下代码
			 exporter.setParameter(
			 JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS,
			 Boolean.TRUE); // 删除记录最下面的空行
			
			 exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET,
			 Boolean.FALSE);// 删除多余的ColumnHeader
			
			 exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND
			 , Boolean.FALSE);// 显示边框
		}

		response.setContentType(getContentType(docType));
		response.setHeader("Content-Disposition", "attachment; filename=\""
				+ URLEncoder.encode(fileName, "UTF-8") + "\"");

		exporter.setParameter(JRExporterParameter.JASPER_PRINT,
				getJasperPrint(jasperPath, params, conn));
		
		OutputStream outStream = null;
		PrintWriter outWriter = null;
		
		if(ext.equals("csv")){//解决中文乱码问题
			response.setCharacterEncoding("GBK");
			outWriter = response.getWriter();
			exporter.setParameter(JRExporterParameter.OUTPUT_WRITER,outWriter);
		}else{
			outStream = response.getOutputStream();
			exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, outStream);
			if(ext.equals("txt")){
				exporter.setParameter(JRTextExporterParameter.CHARACTER_WIDTH, new Integer(80));
			    exporter.setParameter(JRTextExporterParameter.CHARACTER_HEIGHT, new Integer(25));
			    exporter.setParameter(JRTextExporterParameter.CHARACTER_ENCODING,"GBK");
			}
		}
		try {
			exporter.exportReport();
		} catch (JRException e) {
			throw new ServletException(e);
		} finally {
			if (outStream != null) {
				try {
					outStream.close();
				} catch (IOException ex) {
				}
			}
		}
	}

}


2.用Servlet导出生成文件
public class JapserReportServlet extends HttpServlet{

	private static final long serialVersionUID = 1L;
	public void init(){}
	
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException{
		String docType = request.getParameter("docType");
		String jasperFile = request.getParameter("jasperFile");
		String isBean = request.getParameter("isBean");//数据提供方式
		
		String fileName = new String(request.getParameter("fileName").getBytes("ISO-8859-1"),"UTF-8");
		//jasper文件放在应用根目录的reports中
		String jasperPath = request.getSession().getServletContext().getRealPath("/") + "/reports/"+ jasperFile;
		
		if(StringUtils.isNotEmpty(jasperPath)){

			if(StringUtils.isEmpty(fileName)){
				fileName = "报表";
			}
			
			ReportUtils jasperReport = new ReportUtils(request,response);

			//传递报表中(SQL)用到的参数值:$P{ProjectName}
			Map params = new HashMap();
		    //"Name"是报表中定义过的一个参数名称,其类型为String 型 
		    //params.put("ProjectName", new String("Project1"));

			try {
				if(isBean.equals("1")){
					jasperReport.servletExportDocument(getEnumDocType(docType), jasperPath, params, getSourceList(), fileName);
				}else{
					jasperReport.servletExportDocument(getEnumDocType(docType), jasperPath, params, getConnection(), fileName);
				}
			
			} catch (JRException e) {
				e.printStackTrace();
			} catch (ServletException e) {
				e.printStackTrace();
			}
		}else{
			response.setCharacterEncoding("UTF-8");
			response.sendError(-1, "出错:jasperPath参数是必须的!");
		}
	}
	
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{       
		this.doGet(request, response);              
	} 
	
    public void destroy() {       
        super.destroy();        
    }
    
    public static DocType getEnumDocType(String docType){
    	DocType type = DocType.HTML;
    	docType = docType.toUpperCase();
    	if(docType.equals("PDF")){
    		type = DocType.PDF;
    	}else if(docType.equals("XLS")){
    		type = DocType.XLS;
    	}else if(docType.equals("XML")){
    		type = DocType.XML;
    	}else if(docType.equals("RTF")){
    		type = DocType.RTF;
    	}else if(docType.equals("CSV")){
    		type = DocType.CSV;
    	}else if(docType.equals("TXT")){
    		type = DocType.TXT;
    	}
    	return type;
    }
    
    /***数据对象****/
    public static List getSourceList(){
    	List<TUser> sourceList = new ArrayList<TUser>();// 测试数据源
		for (int i = 0; i < 15; i++) {
			TUser user = new TUser();
			user.setId(1000 + i);
			user.setUsername("user_" + i);
			user.setPassword("*******");
			sourceList.add(user);
		}
    	return sourceList;
    }
    
    /****数据库连接****/
	public static Connection getConnection() {
		Connection conn = null;
		String driver = "com.mysql.jdbc.Driver";
		String url = "jdbc:mysql://127.0.0.1/springapp?useUnicode=true&characterEncoding=gb2312";
		try {
			Class.forName(driver);
			conn = DriverManager.getConnection(url,"root", "root");
			return conn;
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException ex) {
			ex.printStackTrace();
		}
		return null;
	}
}


3.web.xml中的Servlet配置
	<servlet>
        <servlet-name>JapserReportServlet</servlet-name>
        <servlet-class>
            com.logcd.servlet.JapserReportServlet
        </servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>JapserReportServlet</servlet-name>
        <url-pattern>/reports</url-pattern>
    </servlet-mapping>


4.调用
function jasperReport(docType){
	var jasperFile = "Regist_User_Group.jasper";
	var paramStr = "?docType=" + docType + "&jasperFile="+jasperFile + "&fileName=" +'人员名单列表' + "&isBean=0";
	var url = getCurrentDirectory() + "/reports" + paramStr;
	window.open(url);
}

/***获取当前路径***/
function getCurrentDirectory(){
	var locHref = location.href;
	var locArray = locHref.split("/");
    delete locArray[locArray.length-1];
    var dirStr = locArray.join("/");
    return dirStr;
}


		报表类型
		<select name="reportType" id="reportType">
			 <option value="html">HTML</option>
			 <option value="pdf">PDF</option>
			 <option value="rtf">RTF</option>
			 <option value="xls">XLS</option>
			 <option value="xml">XML</option>
			 <option value="csv">CSV</option>
			 <option value="txt">TXT</option>
	    </select>&nbsp;
		<button onclick="jasperReport($('reportType').value);">Reports</button>

5.直接生成文件
public class FirstJasperReports {
	 static String fileName="E:/workspace/js_test/jrxml/HelloWorld.jrxml";
	    public static void main(String[] args)throws Exception{
	        long startTime=System.currentTimeMillis();
	        //将报表的定义文件HelloWorld.jrxml编译成HelloWorld.jasper文件
	        String jasperFile=JasperCompileManager.compileReportToFile(fileName);
	        //向HelloWorld.jasper文件中填充数据,这一步将生产出HelloWorld .jrprint文件
	        String jrprintFile=JasperFillManager.fillReportToFile(jasperFile,null,new JREmptyDataSource());
	        //将.jrprint文件转换成HTML格式
	        JasperExportManager.exportReportToHtmlFile(jrprintFile);
	        //将.jrprint文件转换成PDF格式
	        
	        //JasperExportManager.exportReportToPdfFile(jrprintFile);
	        //将.jrprint文件转换成XML格式
	        JasperExportManager.exportReportToXmlFile(jrprintFile,false);
	        //将.jrprint文件转换成XLS格式(即Excel文件),需要用到POI类库.
	        File sourceFile = new File(jrprintFile);
	        JasperPrint jasperPrint = (JasperPrint)JRLoader.loadObject(sourceFile);
	        File destFile = new File(sourceFile.getParent(), jasperPrint.getName() + ".xls");
	        JRXlsExporter exporter = new JRXlsExporter();
	        exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
	        exporter.setParameter(JRExporterParameter.OUTPUT_FILE_NAME, destFile.toString());
	        exporter.setParameter(JRXlsExporterParameter.IS_ONE_PAGE_PER_SHEET, Boolean.TRUE);
	        exporter.exportReport();
	        long endTime=System.currentTimeMillis();
	        long time=(endTime-startTime)/1000;
	        System.out.println("success with "+time+" s");
	     }

}
  • jxl.jar (708.2 KB)
  • 下载次数: 140
分享到:
评论
3 楼 log_cd 2011-12-13  
,当时只是研究一下下。正式应用肯定要根据实际情况了。。。
2 楼 tianhaoleng 2011-12-12  
楼主代码写的不错。

不过我觉得应该把 servlet的api从 utils里解耦,那么应用面会更宽一些
1 楼 shermenn 2011-07-12  
   

相关推荐

    jasperReport5.6.1编码以及使用

    JasperReport编码是指在Java应用程序中使用JasperReport生成报表的过程。这个过程主要包括以下几个步骤: 1. 加入Jar包:在项目中添加JasperReport的Jar包,包括jasperreports-5.6.1.jar、commons-logging-1.1.1....

    JasperReport动态生成报表

    **JasperReport动态生成报表** JasperReport是一款强大的开源报表工具,主要用于生成各种复杂的静态和动态报表。它基于Java,可以很好地与Java应用程序、Web应用和企业级应用集成,提供丰富的报表设计和灵活的数据...

    使用JavaBean构造JasperReport子报表

    综上所述,要使用JavaBean构建JasperReport子报表,你需要理解JasperReport的工作原理,掌握JavaBean数据绑定,了解子报表的概念及其用法,同时确保项目中导入了所有必要的库文件。通过实践和参考相关教程,可以逐步...

    jasperreport生成多种格式的报表

    以下是使用Jasperreport生成多种格式的报表的详细知识点: 一、生成HTML格式的报表 要生成HTML格式的报表,需要使用JRHtmlExporter类,通过设置response的Content-Type和Content-Disposition头来控制报表的下载和...

    jasperreport一个子报表的例子

    标题、描述和标签提示我们,这个例子将围绕如何使用JasperReport创建并集成子报表展开。 首先,我们要理解JasperReport的基本工作流程。设计报表通常在iReport或Jaspersoft Studio这样的可视化工具中完成,这些工具...

    jasperreport 6.4.1报表动态列,以及生成导出html

    JasperReport是一款强大的开源报告生成库,主要用于设计和打印各种复杂的报表。在6.4.1版本中,它提供了丰富的功能,包括支持动态列的报表设计,这使得开发者可以根据数据的实际情况灵活调整列的数量和内容。这个...

    jasperreport 生成pdf实现下载与打印

    标题中的“jasperreport生成pdf实现下载与打印”是指使用JasperReports库来创建PDF报告,并提供下载和打印功能。JasperReports是一个开源的Java报表工具,它允许开发者设计、生成和导出各种类型的报表,包括PDF、...

    使用JasperReport输出image图像

    标题“使用JasperReport输出image图像”涉及到的关键技术点是JasperReport的图像生成和导出功能。在描述中提到的场景是,一个项目需要在文档中添加可识别的元素,比如条形码,然后将包含这些元素的文档作为图像保存...

    jasperReport+ireport制作pdf报表教程

    9. **使用jasperReport生成报表**:在Java项目中,加载JRXML文件,设置数据源,调用jasperReport API生成PDF或其他格式的报表。 10. **集成到应用**:将报表生成功能集成到你的Java应用中,如Web应用的后台服务。 ...

    JasperReport动态报表归并行数据

    通过以上分析,我们可以看出“JasperReport动态报表归并行数据”涉及到的核心概念包括动态报表设计、数据源处理、数据归并、自定义脚本let以及开发工具的使用。掌握这些知识点,能帮助开发者创建出适应各种业务需求...

    jasperreport学习 之 javabean封装成list作为数据源.pdf

    3. 将List作为JasperReport的数据源:最后,将封装好的List作为JasperReport的数据源,就可以使用JasperReport生成报表了。 使用JavaBean封装成List作为JasperReport的数据源有很多优点,例如可以灵活地定义报表的...

    自己搜集的jasperreport和ireport的一些资源

    这通常涉及到在服务器端使用JasperReport生成报表,并通过HTTP响应传递给客户端浏览器。文档可能详细解释了如何配置项目、设置数据源、编写JRXML报表定义以及如何在iReport中设计报表布局。 "使用JasperReport与...

    JasperReport报表设计总结

    JasperReport生成的报表文件可以直接展示,也可以先保存为文件再进行打印。在B/S环境下,通常需要将报表集成到Web应用中。为此,需要在WEB-INF/lib目录下添加必要的依赖库,如Apache Commons库、iText、...

    struts2+Ireport+Jasperreport实现报表导出

    总结来说,这个项目通过Struts2作为控制器处理用户请求,iReport用于设计报表模板,JasperReport负责生成报表,而Highcharts Export Server则提供了额外的图表导出能力。这种组合提供了一套完整的报表系统,可以满足...

    jasperreport5.6.0生成pdf

    System.out.println("ireport 生成报表出错!"); }finally{ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } return null; } ``` 这个示例代码中,我们首先生成.jasper 文件,...

    JasperReport 中交叉报表指南

    数据源名称是报表生成时使用的名称,而 SQL 语句则是用于获取数据的查询语句。在报表设计中,可以点击“ Query” 按钮添加 SQL 语句。 3. 创建交叉报表 在报表设计中,需要点击“ Crosstab” 按钮,然后在报表的...

    jasperreport 生成pdf,html,xml,csv,xls报表,myeclipse源码

    - Java源代码:演示了如何在MyEclipse环境中集成jasperreport,填充数据并生成报表。 - 数据库配置文件:可能包含用于连接数据库的配置信息,供报表获取数据。 - 示例数据:可能有CSV或XML文件,用于测试报表数据...

    jasperreport 与ireport 的配置与使用

    ### 使用JasperReport生成报表 1. **编译报表模板**:在Java代码中,使用JasperCompileManager类的compileReport方法将.IR文件编译为.JASPER文件。 2. **填充数据**:使用JasperFillManager的fillReport方法,传入...

    JasperReport 水晶报表

    JasperReport是一款开源的报表工具,广泛应用于Java环境中,为开发者提供了设计、生成和展示复杂报表的功能。它以其灵活性、强大的数据处理能力和丰富的图表选项而备受赞誉。水晶报表(Crystal Reports)是另一款...

Global site tag (gtag.js) - Google Analytics