`

将运行时数据装入水晶报表 (Crystal Reports)

阅读更多
引用地址:http://www.ibm.com/developerworks/cn/rational/07/0717_bajaj/index.html


2007 年 9 月 17 日

本系列中的第 4 部分手把手地指导您在 Web 应用程序中,使用 IBM® Rational® 嵌入水晶报表 (Crystal Reports)。在前面的文章中,曾介绍过使用 Report Page Viewer 组件,或者 Java™ Reporting Component (JRC) 代码,设计水晶报表 (Crystal Reports)并将其嵌入到 JavaServer™ Pages。最为重要的是,报表是用真实的数据来设计,并且需要 Crystal Report designer 在设计的时候连接到数据库表。然后编写 JRC 代码,建立真实数据源,以便在运行时装入报表。使用范例数据设计报表的好处是,当不能够连接到数据库,或者环境配置不正确,或者嵌入的设计器不支持连接数据库平台的 Java™ Database Connectivity API (JDBCDatabase Connectivity API (JDBC™)时,您还可以灵活使用嵌入的 Crystal Report。
开发场景范例

本文介绍了使用 Rational Application Developer 在 Web 应用程序中将运行时数据装入 Crystal 报表时所需的主要步骤:

使用 Rational Application Developer 获取范例数据。
使用包含 水晶报表 的 JavaServer Page™ (JSP™) 建立新的 Web 项目。
在 JSP 中使用 Rational Application Developer 的 Java™ Reporting Component (JRC)代码,通过编程的方式把运行时数据装入 水晶报表,然后在服务器上运行。
不使用 Java™ Database Connectivity API (JDBC™),而使 用Java™ Naming and Directory Interface (JNDI)来将运行时数据装入 Crystal Report。
此范例是基于 Rational Application Developer Version 6.0.1.1的,默认安装 Crystal Reports V XI 特性。大多数读者,包括 Rational Application Developer 试用版本的使用者,都可以接触到 IBM® WebSphere® Version 6 测试环境。因而,文章的 Web 应用程序范例运行在 WebSphere V6.0.2.5 测试环境中,而不是 V5.1。另外,它也使用 IBM® DB2® Universal Database V8.1,其中包含了用来生成报表的范例数据库。

想了解水晶报表 (Crystal Reports)的更多信息,请查阅 Rational Application Developer Help 文档(Help > Help Contents > Developing Web Applications > Crystal Reports > Creating Reports))。如果想下载这些产品的免费试用版本,请留意本文末端的参考资源部分。您可以在 Rational Application Developer Support 页面的 Crystal Reports FAQs链接,找到 Rational Application Developer 中水晶报表 (Crystal Reports)工具有关许可、升级等的信息。

步骤1:使用 Rational Application Developer 数据工具获取范例数据

使用 Rational Application Developer 数据库工具建立与后端数据库之间的连接。如步骤 2 所示,设计 Crystal Report。

在 Rational Application Developer 工作空间中,转换到 Data 视图(Window > Open Perspective > Data)。
在 Database Explorer 视图中右键单击,然后选择 New Connection。
选择 Choose a Database Manager and JDBC Driver (如图1)所示。

图1. New Database Connection 对话框




单击 Next。
在 New Database Connection 对话框中,输入如图2所示的值,然后单击 Test Connection。

图2. 指定连接参数的对话框



如果测试连接工作,单击 OK (图3)。

图3. 测试连接工作


JDBC 连接问题错误分析

如果您遇到任何 JDBC 连接问题,以下是一些可采用的解决方案:

检查数据库产品文档。
从参考资源中查看 DB2 InfoCenter 链接。
• 从命令行运行一个Java程序,测试JDBC连接。检查在不使用Rational Application Developer Data透视图的情况下,是否可以连接到数据库(查看参考资源)中的 JDBC Test)
通过查看参考资源中的 Business Objects' Supported Platforms,检查 Business Objects 是否支持您的开发平台(数据库、操作系统,等)。


在 New Database Connection 对话框单击 Finish,完成数据的连接。向导将在 Database Explorer 视图中展示 Con1的连接步骤。
在 Database Explorer 视图中右键单击 Department 表,然后单击 Sample Contents,如图4所示。

图4. Database Explorer 视图



如果您的数据库安装正确,那么,DB Output 视图将会在 Results 选项卡展示 Department 表的内容,如(图5)所示。

图5. DB Output 视图



现在,转换到 Web 透视图(Window > Open Perspective > Web)。在 Web 透视图,打开 DB Output 视图(Window > Show View > Other > Data > DB Output),这样,您就可以在创建 Crystal Report 的时候参考 Department 表的范例数据。
步骤 2:使用包含 Crystal Reports 的 JavaServer Page 创建一个新的 Web 项目。

在这一步中,使用 JSP 页建立一个新的 Web 项目。不需要连接到任何数据库,就可以建立一个新的 Crystal Report,因为这个 Crystal Report 是基于一个数据定义文件的。该数据定义文件是一个以.ttx为扩展名的、有标记界限的文本文件,良好地展现了表结构。TTX 文件的每一行包括字段名、数据类型、长度(如果数据类型是字符串),以及一段范例数据,如上一步 DB Output 视图(图5) 所示。

建立一个新 Web 项目,请按以下步骤进行:

在 Rational Application Developer 工作空间中,选择 File > New > Dynamic Web Project,建立一个新的动态的 Web 项目。
按照 New Dynamic Web Project 向导进行,在 Dynamic Web Project 对话框中,设置如图6所示的值。
说明:
在本范例中,请确保,servlet 版本是 2.4,Target Server 是 IBM® WebSphereIBM® Application Server V6.0,如图6所示。


图6. New Dynamic Web Project 对话框



单击 Next,然后在 Features 对话框中选择 Crystal Reports features,如图7所示。

图7. Web project Features 对话框



单击 Finish。
在您的 Web 项目中,右键单击 WebContent 文件夹,然后选择 New > Crystal Report。
在 New Crystal Report 向导中,输入图8所示的值。

图8. New Crystal Report Wizard



单击 Finish。
在 Crystal Reports Gallery 对话框中,选择 Report Wizard 选项(图9)。

图9. Crystal Reports Gallery 对话框




单击 OK.
在 Standard Report Creation Wizard 对话框中,单击 Create New Connection > Field Definitions Only(如 图10)。

图10. Standard Report Creation Wizard




在 Field Definitions Only 对话框(图11),单击 Create File,建立新的数据定义文件。或许,您也可以单击按钮 labeled... 展开文件系统,选择既有的数据定义文件。本例中,单击 Create File,创建一个新的数据定义文件。

图11. Field Definitions Only 对话框




参照步骤 1的图5,然后在 Database Definition Tool 对话框(如图12)的第一列(DeptNo)提供详细信息,然后单击 Add,将这个字段添加到您的数据定义文件。

图12. Database Definition Tool 对话框



现在,与之前的步骤类似,参照 表1,一步一步,为需要添加的每一列提供详细信息。
然后,在 Database Definition Tool 对话框中单击 Add。

表1:Crystal Report 需要添加的字段
Field Name Field Type Length Sample Data
DeptName String 30 Spiffy Computer Service Div.
MgrNo String 6 000010
AdmrDept String 3 A00


添加完所有列的时候,Database Definition Tool 对话框如图13所示。

图13. 数据库定义工具(Database Definition Tool)显示了所有的列设置



单击 Database Definition Tool 对话框右上方的交叉按钮,关闭该对话框。
然后在对话框(Figure 14)单击 Yes,将所作更改保存在 WebContent 文件夹的 Department_dataDefinition.ttx 文件中(图15)。

图14. 保存数据定义文件的更改



图15. 将更改保存在 WebContent 文件夹。



在上述对话框中单击 Save后,转回到了 Field Definitions Only 对话框,文本中显示出 Department_dataDefinition.ttx 数据定义文件的完整路径(图16),然后单击 Finish。

图16. Field Definition File 对话框



在 Standard Report Creation Wizard 的 Data 对话框,从 Field Definitions Only结点选择您新建立的数据定义文件,然后单击按钮 labeled >,将该数据定义文件移到 Selected Tables(图17)。
然后,单击 Next。

图17. Standard Report Creation Wizard 的 Data 对话框



在 Fields 对话框(图18),选择想要在 Crystal Report 中显示的所有字段,并单击 Next。

图18. Fields 对话框



在接下来的两个对话框,Grouping 和 Record Selection图19 和 图20)保持默认选择,单击 Next。
在 Report Style 对话框(图21)保持默认选择,然后单击 Finish。

图19. Grouping 对话框



图20. Record Selection 对话框



图21. Report Style 对话框



接下来,右键单击 WebContent 文件夹,并单击 Refresh。Department_dataDefinition.ttx 文件出现在了 WebContent 文件夹。
可选的,您可以双击 Department_dataDefinition.ttx 文件,打开并确认其内容与图22是否一致。

图22. Department_dataDefinition.ttx 文件的内容







回页首




步骤 3:通过编程的方式地建立数据 ResultSet,并在运行时将它装入 Crystal Reports。

在这一步中,编写 Java Reporting Component (JRC)代码,以建立数据 ResultSet,并在运行时将它装入 Crystal Reports。

如果需要完整的代码列表,请从下载步骤下载SampleWebApplication.zip 文件,并将其解压:
SampleWebEAR.ear: 包括本文需要的所有文件(JSP、Crystal Report、data definition file 等)。您可以使用 File > Import > EAR file,导入 Rational Application Developer 工作空间,然后根据需要修改代码。
jrc_set_resultset_datasource.jsp: 如果您已经成功完成了文章前面介绍过的两个步骤,那么,您可能就不需要完整的 SampleWebEAR.ear 文件。这个 jrc_set_resultset_datasource.jsp 文件仅包含从步骤 3开始的相关 JRC 源代码。
现在,编辑 jrc_set_resultset_datasource.jsp 文件,使之适用于之前在步骤 2建立的 Crystal Report (setResultsetDatasource.rpt)。
将 reportName 变量值改变为您的 Crystal Report。因为之前建立的 setResultsetDatasource.rpt 文件处于 WebContenty 文件夹中,所以 reportName 的值应当是 setResultsetDatasource.rpt如列表1所示。

列表1. 改变 reportName 变量值
               
final String reportName = "setResultsetDatasource.rpt";




改变 WebProjectName 您的 Web 项目名,如列表2所示。

列表2. 将 WebProjectName 改为您的 Web 项目名
               
final String WebProjectName = "SampleWeb";




jrc_set_resultset_datasource.jsp 文件列表3中代码片段使用 BusinessObjects 的 Java Reporting Component API 建立一个 reportClientDocument 对象,然后指向 setResultsetDatasource.rpt 文件。

列表3. 建立 reportClientDocument 对象的代码
               
//---------- Create a ReportClientDocument -------------
ReportClientDocument reportClientDocument = new ReportClientDocument();
//---------- Set the path to the location of the report soruce -------------
//Open report.
reportClientDocument.open(reportName, 0);




列表4中的代码片段显示了一段 SQL 查询,该 SQL 查询是建立 ResultSet 的基础。

列表4. SQL 查询
               
//SQL query that can be used can be obtained by first creating a report
directly off the desired data source, and then
//in Crystal Reports, open the 'Database' > 'Show SQL Query' to see
the SQL generated for the report. 

String query= "SELECT DEPTNO, DEPTNAME, MGRNO, ADMRDEPT FROM DEPARTMENT ";




调用 getResultSetFromJDBC(...) 函数来获得包含数据的 ResultSet(如列表5所示)。

列表5. 获取包含数据的 ResultSet
               
//Call simple utility function that obtains Java Result set that will be
//pushed into the report. 
ResultSet resultSet = getResultSetFromJDBC(query,
ResultSet.TYPE_SCROLL_INSENSITIVE);




列表6是 getResultSetFromJDBC(...)函数的代码。这是一段简单的连接到 DB2 数据库的 JDBC 代码。参照参考资源的 JDBC Test链接或数据库供应商产品文档,查找您具体数据库提供商的相关设置(特别是 jdbcurl 和 jdbcdriver 的值)。

列表6. getResultSetFromJDBC(...) 函数代码
               
private ResultSet getResultSetFromJDBC(String query, int scrollType)
throws SQLException, ClassNotFoundException {
//provide your username and password here
String username = "<Database-Username>";
String password = "<Password>";
// jdbc driver and jdbc url for db2 versions 7.1 and 8.1
//change the jdbcurl=jdbc:db2:<Database-Name> to point to your
database
String jdbcdriver = "COM.ibm.db2.jdbc.app.DB2Driver";
String jdbcurl = "jdbc:db2:sample";

// loads the jdbcdriver class
Class.forName(jdbcdriver);

//Construct connection to the DSN.
java.sql.Connection connection = DriverManager.getConnection(jdbcurl,
username,password);

//retrieve the Statement from the connection object
Statement statement = connection.createStatement(scrollType,
ResultSet.CONCUR_READ_ONLY);

//Execute query and return result sets.
return statement.executeQuery(query);
}



What the code samples up until next subhead do and what kind they are vs. next subhead

列表7中的代码用于获取数据库表,因为运行时数据将会装入到这个表。然后,通过使用 DatabaseController 对象的 setDataSource() 方法,将 java.sql.ResultSet 数据装入数据库表。


列表7. 需要代码描述标题
               
String tableName = reportClientDocument.getDatabaseController().getDatabase().getTables()
.getTable(0).getName();

//Push the Java Resultset into the report.  This will then become the
//datasource of the report when the report itself is generated.
reportClientDocument.getDatabaseController().setDataSource(resultSet,
tableName , "resultTbl");



说明:
列表7的代码片段将数据装入第一个表。假设名为 resultSet 的 java.sql.ResultSet 对象拥有与第一个表相一致的结构。

然后,在列表8的代码片段中,从 reportClientDocument 对象获得 reportSource 对象,然后将之装入到会话范围中。


列表8. 需要代码描述标题
               
//Cache the report source of the ReportClientDocument in session.
session.setAttribute("reportSource", reportClientDocument.getReportSource());



最后,列表9所示的代码段,将浏览器转到浏览页(CrystalReportViewer_setResultSet.jsp)。


列表9. 需要代码描述标题
               
request.getRequestDispatcher("/CrystalReportViewer_
setResultSet.jsp").forward(request,response);



setResultSet JSP 代码如何工作

现在,让我们看一看 CrystalReportViewer_setResultSet.jsp 代码。

列表10的代码完成了以下活动:

从 session 获取 reportSource。
实例化 CrystalReportViewer,建立 oCrystalReportViewer 对象。
将 oCrystalReportViewer 对象的报表源设置为从 session 中获取到的 reportSource 对象。

列表10. 需要代码描述标题
               
//Get the IReportSource object from sesion and pass it to the viewer
IReportSource reportSource = (IReportSource)session.getAttribute("reportSource");

//---------- Create the viewer and render the report -------------

//create the CrystalReportViewer object
CrystalReportViewer oCrystalReportViewer =
new CrystalReportViewer();

//set the reportsource property of the viewer
oCrystalReportViewer.setReportSource(reportSource);



接下来的三个代码片段设置 viewer (oCrystalReportViewer object)的属性。

列表11所示的代码设置了 viewer 是否控制page。setOwnPage设置为 true 有利于 viewer 完整地处理 HTML 内容。这减少了您需要添加到 JSP 页的代码,并且允许 viewer 自动地确定某些设置。

列表11. 需要代码描述标题
               
oCrystalReportViewer.setOwnPage(true);



列表12代码片段设置了 viewer 是否控制 form,如果 server 控制 form,取得和设置 form 的值。特别是,通过控制 form,viewer 控制可以在客户端缓存状态。

列表12. 需要代码描述标题
               
oCrystalReportViewer.setOwnForm(true);



列表13的代码片段设置当用户单击打印按钮的时候,采用的打印模式是 PDF 还是 ActiveX。在 PDF 打印模式下,先显示出一个 PDF,然后用户可以选择打印。在 ActiveX 打印模式,要将一个小的 ActiveX 控件下载到客户端机器上。如果在当前不支持 ActiveX 控件的系统上选择了 ActiveX 打印模式,则默认的打印模式是 PDF。

列表13. 需要代码描述标题
               
oCrystalReportViewer.setPrintMode(CrPrintMode.PDF);
// the other print mode is CrPrintMode.ACTIVEX



说明:

想了解 CrystalReportViewer 更多的相关信息,请从 Help > Help Contents > Developing Web Applications > Crystal Reports > API Reference > Viewer Java Classes > Crystal Report Viewer,参照 Rational Application Developer Help。
如果您在打印或导出 水晶报表 的时候,遇到了任何问题(特别是以下所示的意外情况),那么,请从参考资源的链接中寻找解决办法:
当导出或打印 水晶报表 时,WebSphere 服务器抛出运行时错误。
当导出或打印 水晶报表 时,如何解决运行时错误。


列表14. 需要代码描述标题
               
java.lang.IllegalStateException: SRVE0199E: OutputStream
already obtained



最后,列表15的代码处理并在 Web 页生成报表。


列表15. 需要代码描述标题
               
oCrystalReportViewer.processHttpRequest(request, response,
getServletConfig().getServletContext(), null);



运行 IBM WebSphere 服务器页面并查看结果:

在 Project Explorer 视图中,右键单击 jrc_set_resultset_datasource.jsp 页,然后选择 Run > Run on Server。
在浏览器中查看包含 Crystal Report 的 JSP,如图23。

图23. 在浏览器中查看包含 Crystal Report 的 JSP



Tip:

请注意,getResultSetFromJDBC() 函数提供了数据库的用户名和密码,这也正是本例中您没有遇到(图24)所示数据库登录登录的原因。


图24. 数据库登录界面



与redirect相比较而言,使用Forward方式执行浏览器的优势:

我们使用 jrc_begin_here.jsp 文件末的代码(列表16)来重定向浏览器到 viewer(CrystalReportViewer.jsp),然后就可以在浏览器里生成报表。

列表16. 需要代码描述标题
               
response.sendRedirect("CrystalReportViewer.jsp");



然而,在这个范例中,我们使用了jrc_set_resultset_datasource.jsp 文件末的代码片段(如列表17所示),来转发浏览器,而不是重定向到 viewer (CrystalReportViewer_setResultSet.jsp)。


列表17. 需要代码描述标题
               
request.getRequestDispatcher("/CrystalReportViewer_setResultSet.jsp")
.forward(request,response);



之所以作这样的改变,是因为如果您重定向浏览器,将会遇到列表18所示的运行时错误。


列表18. 需要代码描述标题
               
com.crystaldecisions.sdk.occa.report.lib.ReportSDKException: JDBC
Error: DSRA9110E: ResultSet is closed.---- Error code:-2147467259
Error code name:failed
at com.crystaldecisions.sdk.occa.report.lib.ReportSDKException.throw
ReportSDKException(Unknown Source)



图25显示的是 Web 页(而不是 Crystal Report)的相应错误。


图25. 当重定向浏览器到 CrystalReportViewer_setResultSet.jsp 时出现的运行时错误



调用 getResultSetFromJDBC() 函数并在其中提供数据库的用户名和密码。尽管 Web 页(包含Crystal Report)的数据源显示在了浏览器,生成的 HTML 代码也并不包括用户名和密码。然而,正如本系列文章第3部分所述,JNDI 可代替 getResultSetFromJDBC() 函数。






回页首




步骤 4:转换使用 JNDI,而不是 JDBC,来将运行时数据装入 水晶报表

如果不使用 JDBC 连接,而使用 JNDI 来查找提前配置好的数据源,以便获取 ResultSet,您可以使用getResultSetFromJNDI() 函数代替 步骤 3中第9个子步骤中的函数(列表19)。


列表19. 需要代码描述标题
               
private ResultSet getResultSetFromJNDI(String query, int scrollType)
throws SQLException, ClassNotFoundException {
//Datasource lookup code
Context ctx, env =null;
DataSource ds = null;
try{
ctx = new InitialContext();
ds = (DataSource) ctx.lookup("java:comp/env/robinDatasource");
}
catch(javax.naming.NamingException ne){
ne.printStackTrace();
}
Connection conn = ds.getConnection();

//Construct connection to the DSN.
//java.sql.Connection connection = DriverManager.getConnection
(jdbcurl,username,password);
Statement statement = conn.createStatement(scrollType, ResultSet.CONCUR_READ_ONLY);

//Execute query and return result sets.
return statement.executeQuery(query);
}



如果您作了这个选择,那么需要按以下几步进行:

首先,要使用 getResultSetFromJNDI() 函数,请完成两个项设置:
数据源:系列教程的第2部分(参照参考资源)中,检查步骤 4从1到12的子步骤,了解如何在您的 Web 应用程序布署描述文件(application.xml file)中建立一个使用 robinAlias JAAS authentication entry 的数据源 jdbc/robinDS。下一步将会用到这个数据源名和 JAAS authentication entry。
参考资源:您需要在 Web 布署描述文件(web.xml file)中建立参考资源,指向 JNDI 数据源。更多信息,请访问参考资源的资源引用。
接下来,为您的 JNDI 数据源建立参考资源:
在 Project Explorer 视图中,双击打开文件 web.xml。
指向 References 选项卡。
点击 Add 创建新的资源引用。
在 Add Reference 对话框(图26)中,选择 Resource Reference,然后单击 Next。

图26. 在 Add Reference 对话框选择 Resource Reference



如图27所示,在 Resource Reference 对话框输入值,然后单击 Finish。

图27. Resource Reference 对话框



如图28)所示,在 WebSphere Bindings 部分的 References 选项卡,输入 robinDatasource 所需要的值。
JNDI Name: jdbc/robinDS 数据源需要的 JNDI 名。
JAAS 登录配置:选择 Use Default Method,从数据源设置(robinAlias)提供 JAAS authentication entry 的名称。

图28. References 标签页中参考资源的其它设置



单击 File > Save,保存 web.xml 文件。
Tip:
请注意列表19中 getResultSetFromJNDI() 函数的代码:

ds = (DataSource) ctx.lookup("java:comp/env/robinDatasource");

现在,请将步骤 3第8个子步骤中的代码由调用 getResultSetFromJDBC()(JDBC)函数改为 getResultSetFromJNDI()(JNDI)函数。
调用 getResultSetFromJNDI()函数无疑要比调用 getResultSetFromJDBC() 安全得多,因为在这个范例中,验证是由 JAAS authentication entry (robinAlias)处理的,而 robinAlias 是使用 JNDI 名(jdbc/robinDS)为数据源 robinDS 配置的。

如何控制运行时期间出现的异常

在运行时过程中可能出现一些异常情况。以下是一些解决方法:

如果遇到 JDBC Driver 运行时错误(图29),请从 DB2 安装目录(\SQLLIB\java)将 DB2 Universal JDBC Driver .jar 文件(db2jcc.jar, db2jcc_license_cu.jar, and db2jcc_license_cisuz.jar)拷贝到 WebSphere Application Server V6.0 安装目录(\lib\ext)。

图29. JDBC Driver 运行时错误



说明:
如果您正在使用 WebSphere V6.0 测试环境,而不是独立的 WebSphere Application Server V6.0。在拷贝.jar 文件之后,请在服务器视图中,右键单击 Servers,然后选择 Restart > Start,重启服务器。

如果您在 Console 视图中遇到了列表20所示的 log4j 错误信息,那么,请参看如何解决 log4j 错误。

列表20. log4j 错误消息
               
[2/14/06 17:38:36:435 EST] 00000031 SystemErr
R log4j:ERROR setFile(null,true) call failed.
[2/14/06 17:38:36:435 EST] 00000031 SystemErr
R java.io.FileNotFoundException: C:\Documents and Settings\Administrator\crystal\jpe.log (The  system cannot
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error:  The previous line is longer than the max of 90 characters ---------|
find the path specified)
at java.io.FileOutputStream.openAppend(Native Method)
at java.io.FileOutputStream.init(FileOutputStream.java:199)
at java.io.FileOutputStream.init(FileOutputStream.java:124)
at org.apache.log4j.FileAppender.setFile(FileAppender.java:272)
at org.apache.log4j.RollingFileAppender.setFile
(RollingFileAppender.java:156)







回页首




本系列教程的下一部分将要介绍的内容

在 Web 应用程序中嵌入水晶报表系列的教程包括5个部分,现在已经完成了第 4 部分。在本文中,您了解了如何在 Rational Application Developer 使用 Java Reporting Component (JRC)将运行时数据装入报表,这里的数据是范例数据而不是真实的数据。使用范例数据设计报表的好处是,当不能够连接到数据库,或者环境配置不正确,或者嵌入的设计器不支持连接到数据库平台的 JDBC 连接的时候,还可以灵活使用嵌入的 Crystal Report。

基于 XML 的数据库越来越流行。系列教程的第 5 部分,介绍了 JRC 的其它特性,比如根据 XML 数据汇报,而不是相关的数据库表。如果您需要根据基于 XML 的数据来生成水晶报表,您会发现那篇文章是非常有用的。

分享到:
评论
1 楼 ydsakyclguozi 2009-12-31  
图怎么没有啊

相关推荐

Global site tag (gtag.js) - Google Analytics