前言
本文档提供在基于 CUBA 框架构建的应用程序中创建报表的指导。
目标读者
此手册适用于 CUBA 应用程序的开发人员和管理员。要顺利地使用报表生成器,需要对关系数据库原理有一些了解,并且能够编写 SQL 查询。熟悉 JPQL 和 Groovy 也会很有帮助,因为在某些情况下使用这些语言提取报表数据会更容易。
更多阅读材料
可以在 https://www.cuba-platform.com/documentation 找到与 CUBA 框架相关的手册及附属文档。
报表生成器的核心是 YARG 框架,是在免费的 Apache 2.0 许可下发布的。有关更多详细信息,请参阅 blog 中的文章。该框架文档可从 https://github.com/cuba-platform/yarg/wiki 获取。
反馈
如果有任何改进本手册的建议,请随时在 GitHub 上的源码仓库报告问题。如果看到拼写或措辞错误、bug 或不一致,请毫不犹豫地 fork 并修复。谢谢!
2. 快速开始
本章提供了使用报表生成器的实际操作示例。大部分示例都基于示例 Library 应用程序,该应用程序可在 CUBA Studio 示例项目 GitHub 中获取。
首先,在 CUBA Studio 中打开 Project Properties 编辑器:点击 CUBA → Project Properties 主菜单项。在 App components 列表中添加 reports 应用程序组件。
为了快速可以开始,报表生成器附带有报表向导 - 一种用于快速创建报表的可视化工具,包括数据结构和模板设计。使用向导创建报表后,可以再对生成的报表进行进行分析,了解数据集如何创建、查看报表参数、修改模板或输出类型,这些内容在本手册的其它部分描述。
要运行向导,请单击 Reports 界面中的 Create → Using wizard。
使用向导可以创建三种类型的报表:
-
单个实体的报表。
-
给定实体列表的报表。
-
通过查询过滤的实体列表的报表。
报表设计分为三个步骤:
-
创建报表的数据结构。
-
编辑报表区域。
-
保存报表。
2.1. 单实体报表
假设我们想获取有关图书的详细出版信息,即 library$BookPublication
实体的实例。
首先,运行报表向导并指定报表详细信息:
接下来,指定报表类型: Report for single entity。
然后单击 Next 按钮; 将出现 Select attributes for the simple report region 窗口。指定 BookPublication
实体和应该在体现在报表中的相关实体的属性(Publication.Book.Name
、Publication.Publisher.Name
、Publication.Year
和 Publication.City.Name
)。要执行此操作,请在左列中选择它们并通过单击 或双击将它们移动到右侧。
报表中属性的顺序将对应于右侧列表中指定的顺序。要更改显示顺序,请单击 / 上下移动属性。
单击 ОК 进入第二步 - 报表区域编辑。
出现的界面包含一个命名区域列表,这些区域是用来显示相关数据。向导允许向模板添加多个纯文本区域,以显示不同的数据集。
加载到特定区域的实体属性列表可以通过单击选中的属性列表链接来修改。还可以通过单击 Add simple region 来添加新区域。
如果实体包含集合属性,则会出现 Add tabulated region 按钮。它可以添加显示表格数据的区域。
在这两种情况下,选择对话框将显示 library$BookPublication
实体的属性列表,允许添加或删除集合中的属性。
在此步骤,我们已经可以运行报表并查看报表的样式。单击 Run 按钮,选择 library$BookPublication
实例并查看结果。
配置完所有报表区域后,可以进入第三步:保存报表。此时,可以查看完整的报表模板,或将输出文件的名称和格式更改为任意一种可用的类型。
单击 Save 按钮后,将出现标准报表编辑界面。现在,可以按常规方式微调报表。编辑完成后,在报表编辑界面单击 Save and close。
该报表现已添加到报表浏览界面中的 General 报表组,可以通过单击 Run 按钮来运行该报表。
此外,我们可以在出版物浏览界面上调用报表运行功能。为此,我们将在 bookpublication-browse.xml
界面描述添加 Print details 按钮:
<groupTable id="bookPublicationsTable"
...
<buttonsPanel>
...
<button id="printDetails"
caption="msg://printDetails"/>
然后我们需要在控制器中实现 TablePrintFormAction
:
@Inject
private Button printDetails;
@Subscribe
private void onInit(InitEvent event) {
TablePrintFormAction action = new TablePrintFormAction("report", bookPublicationsTable);
bookPublicationsTable.addAction(action);
printDetails.setAction(action);
}
最后,我们应该将报表与 BookPublication
浏览界面关联起来。打开报表编辑界面,切换到 Roles and Screens 标签页,并从下拉列表中将 library$BookPublication.lookup
界面添加到下表:
现在,可以通过在网格中选中出版物并单击 Print details 按钮来运行任何出版物的报表
输出如下:
2.2. 实体列表报表
报表向导允许为实体实例列表创建两种类型的报表:
-
手动选择特定实体实例的报表
-
由特定请求筛选的实体实例报表。
来看看第一种报表类型。假设想要获取图书馆中所有书籍实例(library$BookInstance
实体)的列表,列表项中包含书籍的名称和所属类目。
第一步,指定报表详细信息:
-
Entity - 报表实体 -
library$BookInstance
. -
Template type - 输出格式 - XSLX.
-
Report name - 报表名称 -
Book items location
.
然后,选择报表的类型(Report for list of entities),然后单击 Next。
按任务要求,在属性选择窗口中选择 BookItem.Publication.Book.Name
和 BookItem.LibraryDepartment.Name
。
单击 ОК 并进入第二步,进行报表带区编辑。
用于实体列表的报表模板被限制为只能有一个以表格形式显示数据的区域。虽然不允许添加新区域,但可以通过单击包含属性列表的链接来编辑现有数据集,或者删除现有区域并重新创建。
目前,不需要进行任何更改。单击 Next → Save 保存报表。该报表在报表编辑界面中显示如下:
一旦报表被保存,就可以从通用报表浏览器运行报表。
此外,可以添加一个按钮来从书籍条目浏览界面运行报表,可以通过单击出版物浏览界面中的 Show items 按钮打开该界面。为此,将书籍实例表格的 multiselect
属性设置为 true
,以便能够为报表指定一组记录,然后添加按钮的源代码:
<groupTable id="bookInstancesTable"
multiselect="true">
...
<buttonsPanel>
...
<button id="printList"
caption="msg://printList"/>
之后,在界面控制器中注入 Button
组件:
@Inject
private Button printList;
接下来,订阅 InitEvent
事件并添加以下代码:
@Subscribe
private void onInit(InitEvent event) {
TablePrintFormAction action = new TablePrintFormAction("report", bookInstancesTable);
bookInstancesTable.addAction(action);
printList.setAction(action);
}
最后,应该将 Book items location 报表与图书条目浏览界面相关联。打开报表编辑界面,切换到 Roles and Screens 标签页,然后从界面下拉列表中添加 library$BookInstance.lookup
界面到下表:
现在,可以从书籍条目浏览界面的表格中选中要打印的条目并点击 Print list 按钮来运行报表。Print selected 选项导出所选条目,Print all 选项打印当前过滤器选择的所有实例。
输出如下:
2.3. 查询报表
现在看一下向导提供的最后一种报表类型:通过查询过滤出的实体列表的报表。要演示这种报表类型,可以在前一个示例的基础上进行修改。和前一个示例一样,报表包含一个书籍列表(包括其标题和归属类目),但仅输出特定日期之后添加的书籍。
像上一个示例一样设置报表的详细信息:
-
Entity - 报表实体 -
library$BookInstance
. -
Template type - 输出文件格式 - XSLX.
-
Report name -
Recently added book items
.
然后选择 Report for list of entities, selected by query 报表类型。
选定的报表类型允许我们选择与指定条件匹配的实体列表。要设置查询,请单击下面的 Set query 链接。
出现 Define query 窗口。正如所见,窗口类似于通用过滤器窗口。在此处指定条件,可将多个条件使用 AND/OR 进行组合。
要添加新查询条件,请单击 Add。在出现的窗口中选择 Created at
属性。现在该属性被添加到查询条件树中,右侧面板将显示其属性。
在属性面板中,可以设置默认参数值。如果不允许更改报表逻辑,则可以通过选中 Hidden 复选框来隐藏此属性。在这种情况下,运行报表时不会要求用户输入此参数。
选择一个运算符(>=)。
保存查询后,单击 Next 移至 library$BookInstance
属性选择界面。我们将 BookItem.Publication.Book.Name
和 BookItem.LibraryDepartment.Name
属性移到右边。单击 OK 完成第一步。
点击 Next → Save 以保存报表。
该报表如下所示:
在报表编辑器中,可以通过添加新的带区和数据集,以及配置报表模板、本地化和访问权限来创建更复杂的报表。
例如,可以切换到 Parameters and Formats 标签页并修改 Parameters 列表中的查询参数:使用 Date
替换标准的 CreateTs1
。保存更改并关闭报表编辑界面。
执行完上述操作后,添加可直接从图书馆图书类目浏览界面运行报表的 Report 按钮。
为此,需要在 librarydepartment-browse.xml
界面描述中定义一个按钮:
<table id="libraryDepartmentsTable"
...
<buttonsPanel id="buttonsPanel">
...
<button id="reportBtn"
caption="msg://reportBtn"/>
</buttonsPanel>
</table>
之后,在界面控制器中注入按钮:
@Inject
private Button reportBtn;
并在 onInit()
方法中将 RunReportAction
分配给按钮:
reportBtn.setAction(new RunReportAction("report"));
如同前面的示例,对于每个报表,需要将 library$LibraryDepartment.browse
添加到报表编辑界面的 Roles and Screens 标签页上的界面列表中。
Report 按钮将显示在图书馆图书类目浏览界面中,只需单击一下即可显示系统中所有可用报表的列表。要运行报表,请在列表中选择 Recently added book items,指定日期并单击 Run report。
输出如下:
2.4. 带图表输出的报表
使用报表向导,可以使用跟创建其它类型的报表一样的方式创建带图表输出的报表。唯一的不同是在向导中完成了报表之后需要配置图表模板。
该示例程序是基于 petclinic - 宠物诊所 应用程序,源码可以在 GitHub 找到。
-
如前一章所述开始创建报表。
Figure 25. 向导中的图表模板 - 第一步 -
对于图表报表,要选择可进行数值计算的实体属性,将来用这些属性作为图表的数值轴。
Figure 26. 向导中的图表模板 - 选择属性 -
完成创建报表的下一步。
Figure 27. 向导中的图表模板 - 第二步 -
最后一步中,选择图表类型,饼图或序列图,然后保存报表。
Figure 28. 向导中的图表模板 - 第三步 -
最后,在报表编辑界面的 Templates 标签页配置图表的轴。
Figure 29. 图标模板配置要了解图表配置的更多细节,请参阅 图表文档 。
3. 创建报表
在系统中创建报表涉及两个相关元素:可视化展现模板和为报表提取的数据的描述。使用外部工具以 XLS(X)、DOC(X)、HTML 格式创建模板,并在报表设计界面中创建报表数据的描述。
根据模板和报表参数,生成的报表可以是 PDF、XLS(X)、CSV、DOC(X)、HTML、Chart、Table 或 Pivot table 格式。
报表数据结构既可以在报表设计器中通过创建带区、查询和其它元素来描述,也可以通过实现特定接口的 Java 类来实现。报表可以从用户或调用代码中获取参数。可以指定有权访问报表的用户,以及报表可以出现在哪些系统界面中。
报表生成器的主要组件如下图所示:
-
YARG
- 框架,这是报表生成器的核心。 -
Report Engine
将 YARG 集成到 CUBA 框架中,并提供其它功能,如报表访问权限和界面集成。 -
Report Designer
是用于描述和存储报表的工具。它包括用于存储报表描述和模板的基础设施,以及用于创建和管理报表的界面。 -
Report
- 报表数据结构描述,包括 Band(报表带区) 和 Dataset(输出到带区的数据集)。 -
Report Template
- 报表可视化展示模板。
3.1. 报表数据结构
报表编辑界面的 Report structure 标签页如下所述:
顶部包含用于输入常规报表属性的字段:
-
Name - 报表名称。该名称可以在 Localization 标签页中本地化。
-
Group - 报表组,用于在标准报表浏览界面中进行分组。
-
Default template - 报表输出模板.
-
System code - 可选代码,可用于在应用程序代码中标识报表。
报表数据结构的主要元素是带区树(band hierarchy) - Report bands。
报表区有以下参数:
每个报表带区包括一个或多个数据集。在运行报表时,数据集将转换为行的列表,其中每行都是包含键-值对的 map。报表中出现的带区的次数与其最长数据集中的行数一样多。字段名称在报表模板中指定,并在生成报表时替换为数据集中的相应值。在描述数据集时,可以使用报表的外部参数以及其它报表带区的字段 - 这样可以创建链接带区。
每个报表都有 Root 带区。可以在其中创建数据集并从其它带区引用字段,但不能在报表模板中使用 Root 带区。
Dataset name 列的值仅为方便用户。
Link field 用于合并来自一个带区内的多个数据集的数据。当单个查询或 Groovy 脚本无法满足报表行的整个数据时,可以使用它。
支持的数据集类型如下所述。
3.1.1. SQL 数据集
SQL 数据集是通过执行 SQL 查询而生成的。建议使用 as
运算符为查询结果字段使用别名。也建议将别名用双引号括起来,以防止 DBMS 进行可能的大小写转换:
select u.name as "userName", u.login as "userLogin"
from sec_user u
可以在查询中使用报表输入参数和父带区字段。参数应该用 ${}
中的名称来处理,例如 ${dateFrom}
。父带区字段也可以类似地处理,通过在字段名称前面添加带区名称:${band1.field1}
。
下面是一个使用从 group
父带区获取的 groupId
参数和外部 active
参数的 SQL 查询示例:
select u.name as "userName", u.login as "userLogin"
from sec_user u
where u.group_id = ${group.groupId}
and u.active = ${active}
and u.delete_ts is null
Warning
|
SQL 查询中应手动包含条件以过滤掉软删除的记录。 |
默认情况下,SQL 查询在主数据库上执行。如果要查询 附加数据存储,请在 Data store 字段中设置其名称。
- 报表带区中的查询预处理
-
如果需要根据报表输入参数或父带区中的参数值动态修改 SQL/JPQL 查询,则可以使用 SQL 预处理。模板引擎允许使用 Groovy 修改 SQL/JPQ 查询。要激活它,请在报表带区编辑界面下方选中 Preprocess query as Groovy template 复选框。生成的查询将由
GStringTemplateEngine
处理,它可以访问:-
报表参数:
${<parameter_name>}
, -
父带区中的值:
${<band_name>.<parameter_name>}
.
考虑以下示例:根据是否传递
createTs2
报表参数选择一个查询条件:e.create_ts < ${createTs2}
或e.create_ts < current_timestamp
。在这种情况下,查询应如下所示:
select e.create_ts, e.id, e.vin from ref_car e where e.create_ts >= \${createTs1} and <% out << (createTs2 != null ? 'e.create_ts < ${createTs2}' : 'e.create_ts < current_timestamp')%>
因此,如果未传递
createTs2
参数,则初始查询将转换为以下查询:select e.create_ts, e.id, e.vin from ref_car e where e.create_ts >= \${createTs1} and e.create_ts < current_timestamp
如果传递
createTs2
,则报表带区将使用以下查询:select e.create_ts, e.id, e.vin from ref_car e where e.create_ts >= \${createTs1} and e.create_ts < ${createTs2}
-
3.1.2. JPQL 数据集
JPQL 数据集是通过执行 JPQL 查询而生成的。生成的查询字段必须使用 as
运算符提供别名。可以在 JPQL 查询中使用报表输入参数和父带区字段,类似于 SQL 查询。
下面是一个使用从 group 父带区获取的 groupId 参数和外部 active 参数的 JPQL 查询示例:
select u.name as userName, u.login as userLogin
from sec$User u
where u.group.id = ${group.groupId}
and u.active = ${active}
JPQL 查询自动支持软删除并仅返回未删除的记录。
还可以通过选中报表区编辑界面下方的 Preprocess query as Groovy template 复选框来激活查询预处理。
默认情况下,JPQL 查询使用映射到主数据库的实体。如果要查询 附加数据存储 中的实体,请在 Data store 字段中设置其名称。
3.1.3. Groovy 数据集
Groovy 数据集是通过执行 Groovy 脚本而生成的。该脚本返回 List<Map<String, Object>>
类型的对象。此列表的每个元素都是 Map<String, Object>
类型的对象 - 对应于一个数据集记录。
以下对象将被传递到脚本中:
-
dataManager
- 提供 CRUD 功能的com.haulmont.cuba.core.global.DataManager
类型的对象。例如:def book = dataManager.load(Book.class).id(bookId).view("book.edit").one;
-
metadata
-com.haulmont.cuba.core.global.Metadata
类型的对象,提供对应用程序元数据的访问。例如:def metaClass = metadata.getClassNN('sec$User')
-
params
- 外部报表参数 map。以下是获取参数值的示例:def active = params['active']
-
parentBand
- 父带区是com.haulmont.yarg.structure.BandData
类型的对象。此对象允许通过调用getParameterValue()
方法获取父带区字段值,例如:def groupId = parentBand.getParameterValue('groupId')
-
persistence
-com.haulmont.cuba.core.Persistence
类型的对象,允许获取数据源。默认情况下,使用主数据存储。要使用其它数据存储,请将其名称作为参数传递给
getDataSource()
方法:def sql = new Sql(persistence.getDataSource('myStore')) def rows = sql.rows('select e.name from SEC_GROUP e')
-
security
-com.haulmont.cuba.core.global.Security
类型的对象,用于检查用户对系统中不同对象的访问权限。例如:if (security.isEntityOpPermitted(Book.class, EntityOp.READ) { ... }
-
timeSource
- 用于获取当前时间的com.haulmont.cuba.core.global.TimeSource
类型的对象。例如:def currentDate = timeSource.currentTimestamp()
-
transactional
- 将一个应该在新事务中执行的闭包作为参数的方法。当前的EntityManager
成为闭包参数。例如:transactional { em -> def query = em.createQuery('select g from sec$Group g') ... }
下面是一个 Groovy 脚本的示例,该脚本通过父带区输出的组和
active
外部参数提取用户:def result = [] transactional { em -> def query = em.createQuery('select u from sec$User u where u.group.id = ?1 and u.active = ?2') query.setParameter(1, parentBand.getParameterValue('groupId')) query.setParameter(2, params['active']) query.resultList.each { user -> result.add(['userLogin': user.login, 'userName': user.name]) } } return result
-
userSession
- 与当前通过身份验证的用户关联的com.haulmont.cuba.security.global.UserSession
类型的对象。例如:def user = userSession.currentOrSubstitutedUser
-
userSessionSource
-com.haulmont.cuba.core.global.UserSessionSource
类型的对象,用于获取当前用户会话对象。例如:def locale = userSessionSource.locale
Tip
|
可以使用
|
3.1.4. 实体数据集
Entity 数据集由单行组成,使用单个实体实例的属性和与之相关的实体生成。
数据源由 Entity 类型的外部参数生成,必须在 Parameters and Formats 标签页中进行描述。Entity parameter name 字段中的值必须与参数名称匹配。
报表模板必须包含具有实体属性名称的字段。模板中使用的属性应列在特定的窗口中,使用 Select entity attributes 按钮打开此窗口。
3.1.5. 实体列表数据集
实体列表 数据集是使用实体实例列表生成的。
数据源是使用 实体列表 类型的外部参数生成,必须在 Parameters and Formats 标签页中进行描述。Entity parameter name 字段中的值必须与参数别名匹配。
报表模板必须包含具有实体属性名称的字段。模板中使用的属性应列在特定窗口中,使用 Entity attributes 按钮打开该窗口。
3.1.6. JSON 数据集
JSON 数据集是通过 JSON 数据生成的。可以从以下来源获取此数据:
-
Groovy script
用户提供的脚本应该以字符串形式返回 JSON 数据。
例如:
return ''' { "items": [ { "name": "Java Concurrency in practice", "price": 15000 }, { "name": "Clear code", "price": 13000 }, { "name": "Scala in action", "price": 12000 } ] } '''
-
URL
报表引擎将对 URL 执行 GET HTTP 查询。
例如:
https://jsonplaceholder.typicode.com/users
-
字符串类型的参数
必须在 Parameters and Formats 标签页中描述包含 JSON 数据的 String 类型的报表外部参数。
使用 JsonPath 查询获取 JSON 树。例如,可以使用 $.store.book[*]
JsonPath 返回以下 JSON 树中的所有书籍:
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99,
"isbn": "0-553-21311-3"
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
有关 JsonPath 表达式的更多详细信息,请参阅 http://goessner.net/articles/JsonPath/ 。
Warning
|
报表输出的字段如果是 如需这么做,可以切换到报表编辑器的 Parameters and Formats 标签页并打开formatter编辑器。比如,对于
|
3.2. 报表模板
可以在报表编辑界面的 Templates 标签页中为一个报表创建多个模板。必须在 Report structure 标签页中选择其中一个作为默认模板。
下面是添加模板的表单:
-
Template code - 作为标识的模板代码。
-
Template file - 模板文件,从文件系统加载并与报表结构描述一起保存到数据库中。
-
Output type -报表输出类型。它应该根据输出格式对应表中描述的规则与模板文件类型相一致。
-
Output name pattern - 可选的文件名模板,将用于生成的报表的下载文件名。它可以是常量字符串,也可以包含报表参数变量,例如
${header.authorName}.xlsx
。可以创建具有若干参数和字符串拼接的更复杂的模板,模板中用到的参数可以在任何报表结构带区使用脚本创建,例如,${Root.title}.xlsx
,其中title
是在脚本中定义:[['title' : ('Report for '+params['author'].firstName+' '+params['author'].lastName)]]
-
Is custom - 表示格式化逻辑是自定义的,不是系统提供的格式化器。
-
Defined by - 自定义模板的定义方式:可以是class、脚本或 URL。
-
Custom definition - Java 类的完全限定名称、core 模块中 Groovy 脚本的路径或用于创建模板的 URL。
-
Is alterable output - 定义是否允许用户在运行报表时在对话框窗口中选择报表输出类型。
如果此标志启用,则在运行报表时将显示输出类型选择对话框。如果报表包含多个模板,则还会显示模板选择的下拉列表。
Figure 33. 选择输出类型及模板
3.2.1. XLSX 和 XLS 模板
可以使用 Microsoft Office 或 LibreOffice 创建 XLSX 和 XLS 模板。
每个报表带区必须在模板中具有相应的区域,该区域被命名为带区。例如,报表有两个区 - Header 和 Data。这意味着该模板还应具有 Header 和 Data 命名区域。要创建命名区域,请选择所需的单元格区域,然后在应用程序左上角的字段中输入名称。要编辑现有命名区域,请在 Microsoft Office 中使用 Formulas → Name Manager 菜单命令,在 OpenOffice 中使用 Insert → Names → Manage 命令。 反之亦然,每个要显示的工作表上的区域应该是报表中的一个带区(至少是一个空带区)。
报表带区以报表数据结构中指定的顺序输出。
报表带区可以是水平或垂直的。如果报表区是水平的,相应的命名区域将向下扩展,垂直,将向右扩展。水平报表带区可以以树状结构组织并包含子报表带区(嵌套或子报表带区)。因此,对于子报表带区,需要直接在与父报带表区对应的区域下创建命名区域。 XLSX 格式化器使用以下算法渲染子报表带区:
-
渲染父报表带区的第一行 →
-
渲染第一行的所有子行 →
-
渲染父报表区的下一行。
带区数据集字段以 ${field_name}
格式放置在模板中,其中 field_name
是相应的报表带区字段名称。例如:
可以将变量添加到报表模板。变量应该以格式 ${<BandName>.<variableName>}
插入到 XLSX 模板的工作表名称或的页眉/页脚中。
单元格中可能包含格式以及多个字段。要输出图像或公式,需要将它们完全放入与报表带区链接的相应命名区域。
公式可以引用相同报表带区或另一报表带区的单元格。要由格式化程序处理,公式应该使用报表区中的单元格范围,或直接使用单元格坐标,例如,(A1*B1)
或 ($B:$B)
。
要将数据作为 Excel 图表处理,在报表结构中创建一个空报表区,并在模板中创建一个具有相同名称的命名区域。然后在此命名区域内创建一个图表,并使用图表右键菜单中的 Select data 按钮引用关联的报表数据区域。如果图表数据位于连续的单元格范围内,请选择该范围内的所有单元格。图表将包含该范围内的所有数据。如果数据不在连续范围内,请选择不相邻的单元格或范围。
- 将 XLSX 转换为 PDF 和 CSV
-
XLSX 报表可以自动转换为 CSV 和 PDF 格式。需要为 PDF 转换安装OpenOffice/LibreOffice。
Figure 35. CSV Output
3.2.2. CSV 模板
可以使用 Microsoft Office 或 LibreOffice 创建 CSV 模板。
CSV 模板中的报表区应该是水平方向的,因此相应的命名区域将向下扩展。此外,报表区应属于第一级数据,即根区的第一级子区。在所有其它方面,应使用与XLS/XLSX 模板相同的规则。
Inline editor
CSV 模板支持内联编辑。可以直接在 Template editor 窗口中编辑模板,无需重新上传模板文件即可查看更改。
3.2.3. DOCX 和 DOC 模板
可以使用 Microsoft Office 或 OpenOffice/LibreOffice 创建 DOC 和 DOCX 模板。
这些类型的模板可以包括文档文本和可选的一个或多个表格。文档文本输出任意区的第一行数据。在表格中,可以输出任意数量的报表带区行。
要在文档文本中放置字段,应该使用 ${band_name.field_name}
格式的字符串,其中 band_name
是带区名称,field_name
- 带区字段的名称。
要将数据输出到表格中,应该将其链接到一个报表带区。这是通过在表格的第一个单元格中指定 ##band=band_name
来完成的,其中 band_name
是带区名。表格字段以 ${field_name}
格式被放置,其中 field_name
是与表格关联的带区的字段名称。可以使用带区名称前缀来访问其它带区的字段,与文档文本字段的相同。可以在单个表格单元格中输出多个字段。
DOCX 和 DOC 中的水平报表带区不能包含子区。如果需要子区,请使用 XLS(X)格式。
Warning
|
模板中表格必须包含一行或两行。如果表格有两行,则相应的带区字段必须在第二行。第一行应包含带有相应报表区名称的标记,如果需要,还可以包含静态文本或其它报表带区字段。 |
下面是一个模板示例,它输出一个由两个带区( Book
和 Authors
)组成的报表。第一个带区输出书名和分类,第二个带区输出本书的作者列表。
Warning
|
DOCX 和 DOC 模板不支持单元格数据格式化。要避免由于用户的语言环境而导致的数字或日期格式问题,例如不必要的数字分隔符,请尝试将数据转换为字符串。例如, 将
转换为
|
3.2.4. HTML 模板
HTML 模板在 .html
文件(无 BOM
的 UTF-8
编码)中定义。可以使用 Flying Saucer 库的 HTML/CSS 功能;其主要指南参考 http://flyingsaucerproject.github.io/flyingsaucer/r8/guide/users-guide-R8.html 。
要控制页面尺寸、页眉和页脚,请使用特殊的 CSS 规则和属性。可以在示例报表中找到每页都显示页面/页脚的报表示例。
有两种方法可以在模板中放入数据:
-
使用 FreeMarker 标签。
-
使用 Groovy 模板引擎。
默认情况下,报表向导会生成带有 FreeMarker 标签的 HTML 模板。
在模板编辑器中使用 Template type 单选按钮切换这两种方法。
- Groovy 模板引擎
-
可以将 HTML 报表模板作为 Groovy 模板进行预处理,将使用 GStringTemplateEngine 来处理模板。
模板引擎使用 JSP 格式的
<% %>
脚本和<%= %>
表达式语法或者GString
格式的表达式。out
变量用来绑定模板将要被写入的 writer。因此,如果定义没有问题的话,模板可以使用任何 Groovy 代码。GStringTemplateEngine
能访问:-
外部参数:
BandName.fields.ParamName
; -
区域:
BandName.bands.ChildBandName
; -
字段:
BandName.fields.FieldName
。
方便起见,也可以使用变量,例如:
<% def headerRow = Root.bands.HeaderRow %> <p>Date: ${headerRow.fields.reportDate}</p>
下面是一个模板的示例,输出单一用户的报表。
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru"> <head> <title> Report User </title> <style type="text/css"> body {font-family: 'Charis SIL', sans-serif;} tbody tr {height:20px; min-height:20px} </style> </head> <body> <% def user = Root.bands.User %> <p>Login: ${user.fields.login.first()}</p> <p>Active: ${user.fields.active.first()}</p> </body> </html>
可以在报表示例部分找到使用 Groovy 模板的示例。
-
- FreeMarker
-
FreeMarker 文档可以参阅 https://freemarker.apache.org/docs/ 。
FreeMarker 文档模型的结构如下:
Band { bands [ bandName : [ band, .. ], .. ] fields [ fieldName : fieldValue, .. ] }
例如,应该使用以下表达式访问
band
带区中第一行数据的name
字段:Root.bands.band[0].fields.name
为方便起见,可以使用变量,例如:
<#assign headerRow = Root.bands.Header[0]> <p>Date: ${headerRow.fields.reportDate}</p>
下面是一个模板示例,它输出一个由两个带区(
Book
和Authors
)组成的报表。第一个区输出书名和分类,第二个区输出本书的作者列表。<!doctype html> <html> <head></head> <body> <#assign book = Root.bands.Book[0] /> <#assign authors = Root.bands.Authors /> <p>Name: ${book.fields.name}</p> <p>Genre: ${book.fields.literatureType.name}</p> <table border="1" cellpadding="5" cellspacing="0" width="200"> <thead> <tr> <td>First name</td> <td>Last name</td> </tr> </thead> <tbody> <#list authors as author> <tr> <td>${author.fields.firstName}</td> <td>${author.fields.lastName}</td> </tr> </#list> </tbody> </table> </body> </html>
下面是一个更复杂的例子。假设我们有以下报表带区结构:
Root { HeaderBand { query = return [[ "name" : "Column1" ],[ "name" : "Column2" ]] } Band1 { query = return [ ["field1" : "Value 11", "field2" : "Value 12"], ["field1" : "Value 21" , "field2" : "Value 22"] ] } Band2 { query = return [[ "header" : "Header1" ], [ "header" : "Header2" ]] SubBand1 { query = return [["header" : 'SubHeader1'] , [ "header" : 'SubHeader2' ]] } } }
-
插入字段:
<!doctype html> <html> <head> <title> Simple template </title> </head> <body> <#assign Tree1 = Root.bands.Band2> <h1> Header </h1> <p> ${Tree1[1].bands.SubBand1[0].fields.header} </p> </body> </html>
-
插入列表:
<!doctype html> <html> <head> <title> List </title> </head> <body> <#assign Table1Header = Root.bands.HeaderBand> <#if Table1Header?has_content> <ol> <#list Table1Header as header> <li> ${header.fields.name} </li> </#list> </ol> </#if> </body> </html>
-
插入表格:
<!doctype html> <html> <head> <title> Table </title> </head> <body> <#assign Table1Header = Root.bands.HeaderBand> <#assign Table1 = Root.bands.Band1> <table border="1" cellpadding="5" cellspacing="0" width="200"> <thead> <tr> <#list Table1Header as header> <td> ${header.fields.name} </td> </#list> </tr> </thead> <tbody> <#list Table1 as row> <tr> <td> ${row.fields.field1} </td> <td> ${row.fields.field2} </td> </tr> </#list> </tbody> </table> </body> </html>
-
插入多级列表:
<!doctype html> <html> <head> <title> Multi-level list </title> </head> <body> <#assign Tree1 = Root.bands.Band2> <ul> <#list Tree1 as item> <li> <h2> ${item.fields.header} </h2> <#if item.bands.SubBand1?has_content> <ul> <#list item.bands.SubBand1 as subitem> <li> <h3> ${subitem.fields.header} </h3> </li> </#list> </ul> </#if> </li> </#list> </ul> </body> </html>
-
- 嵌入图片
-
目前,CUBA 报表扩展不提供类似于 DOCX/XLSX 报表将图片插入 HTML 报表的方法。图片仍然可以使用
img
标签并在src
属性中指定图片链接的方式嵌入。有两种方法可以将图片添加到 HTML 报表中:-
通过 URL
图片可以托管在 Tomcat 服务或者其它地方,以便用做本地文件引用。例如,托管在
deploy\tomcat\webapps\ROOT\images
文件夹中的图片可以被插入:
<img src="http://localhost:8080/images/SomeImage.jpg" height="68" width="199" border="0" align="right"/>
-
通过 Bitmap
图片在
src
属性中以字节数组形式添加。此方法可以使用实体的FileDescriptor
属性变量。甚至可以将字节数组直接添加到模板中,虽然不建议使用此方法:
<img alt="SomePicture.png" src="data:image/png;base64,iVBORw0K ..... AcEP9PwxD0hNKK1FCAAAAAElFTkSuQmCC"/>
-
- 内联编辑器
-
HTML 模板支持内联编辑器。可以直接在 Template editor 窗口中编辑模板,无需重新上传模板文件即可查看更改。
Figure 41. HTML 内联编辑器
3.2.4.1. 将 HTML 转换为 PDF
具有 HTML 格式和 PDF 输出格式的模板的报表容易显示出错误的字体。要解决此问题,请将带有所需 .ttf
字体的 cuba/fonts
子目录添加到中间层配置目录(默认部署配置中的 tomcat/conf/app-core
)。此外,可以通过在reporting.fontsDir应用程序属性中指定其路径来使用现有的操作系统字体。
要解决 Ubuntu 服务器上的字体问题,应该执行以下操作:
-
安装
ttf-mscorefonts-installer
包:$ sudo apt-get install ttf-mscorefonts-installer
-
设置 reporting.fontsDir 应用程序属性:
reporting.fontsDir = /usr/share/fonts/truetype/msttcorefonts
-
在 HTML 模板中明确指定字体,例如:
<html>
<head>
<style type="text/css">
* { font-family: Times New Roman; }
</style>
另外要提到的是解析特殊字符。为避免将 HTML 转换为 PDF 时出错,建议将字段封装在 HTML 模板文件的 <![CDATA[ ]]>
标签中:
<tr>
<td> <![CDATA[${(row.fields('book_name'))!?string!}]]> </td>
<td> <![CDATA[${(row.fields('author'))!?string!}]]> </td>
</tr>
3.2.5. JasperReports 模板
JasperReports 格式化器允许使用 JasperReports 模板输出 CUBA 报表提取的信息。模板将由 CUBA 报表引擎处理,支持定义的几种输出类型,请参阅输出格式对应表。
可以使用 JasperReports 工具(例如,Jaspersoft Studio)或在简单的文本编辑器中创建 JRXML 模板。报表数据结构中定义的每个报表带区必须在模板 中有相应的 band
元素,模板中的 band
元素(在 JasperReports 术语中也称为带区)被放置在标准 JasperReports 报表区内:title
、 pageHeader
、 columnHeader
、 detail
等。
报表引擎将所有报表带区数据放在一个数据源中:JRBandDataDataSource
,它以树形结构组织数据,Root
带区为根带区,并将 CubaJRFunction
实例作为主数据源传递给模板,可以通过参数 REPORTING
引用 CubaJRFunction
。在报表模板中可以不声明此参数,这时它会被自动添加,但如果要在 JasperReports IDE 中编译模板,则需要显式声明此参数。
REPORTING
参数提供两个功能:
-
dataset
- 从使用的主数据源获取子数据源,例如,在表格或子报表中作为子数据集。此方法在根带区的子节点中搜索具有指定名称的报表带区,并使用搜索到的报表带区数据作为新的根创建新数据源。例如:<subDataset name="Product"> <field name="name" class="java.lang.String"/> <field name="price" class="java.lang.Long"/> </subDataset> ... <dataSourceExpression><![CDATA[$P{REPORTING}.dataset("Product")]]></dataSourceExpression>
-
bitmap
- 将给定的字节数组转换为ByteArrayInputStream
,可用于将图片嵌入到报表中。例如:
<field name="Main.image" class="java.lang.Object"/> //image from DB as byte array
...
<imageExpression><![CDATA[$P{REPORTING}.bitmap($F{Main.image})]]></imageExpression>
每个报表带区只能在模板中使用一次,因此如果需要在一个报表中以不同的形式表示相同的数据(例如,作为表格和图表),需要创建与模板中 band
元素一样多的报表带区。不支持嵌套报表带区,所有带区都应该是 Root 区的直接子节点。
可以使用以下语法从数据源获取数据: $F{<field name>}
。例如:
<textField>
<textFieldExpression><![CDATA[$F{library_department_name}]]></textFieldExpression>
</textField>
可以在示例报表章节中找到使用 JasperReports 模板的报表示例。
Tip
|
如果应用程序使用 UberJAR 部署选项,请执行以下操作以使用 UberJAR 运行 JasperReports:
|
3.2.6. 类定义模板
当很难或不可能使用 SQL、JPQL 或 Groovy 选择数据时,可以使用类定义模板。例如,当报表是组合其它几个报表的结果时,就可以使用它们。
将定义模板的类放在 core 模块中,并实现 com.haulmont.yarg.formatters.CustomReport
接口。在类中,需要定义 createReport()
方法,该方法返回一个字节数组并接收以下输入参数:
-
report
-com.haulmont.yarg.structure.Report
类型的报表描述。 -
rootBand
-com.haulmont.yarg.structure.BandData
类型的根带区数据。 -
params
- 外部报表参数的 map。
下面是一个简单的类定义模板的示例。它会创建一个 HTML 文档,显示从参数中获取的书籍的名称:
package com.sample.library.report;
import com.haulmont.yarg.formatters.CustomReport;
import com.haulmont.yarg.structure.BandData;
import com.haulmont.yarg.structure.Report;
import com.sample.library.entity.Book;
import java.util.Map;
public class BookReport implements CustomReport {
@Override
public byte[] createReport(Report report, BandData rootBand, Map<String, Object> params) {
Book book = (Book) params.get("book");
String html = "<html><body>";
html += "<p>Name: " + book.getName() + "</p>";
html += "</body></html>";
return html.getBytes();
}
}
在模板编辑界面中,选中 Is custom 复选框,在 Defined by 字段中选择 Class,并设置 custom definition 为 Java 类的完全限定名:
3.2.7. 图表模板
如果应用程序项目包含 charts 组件,则可以使用图表输出类型。生成的图表显示在 Web 应用程序的 Reports → Show Charts 界面中。
支持两种类型的图表:饼图和序列图,每种类型都有自己的一组参数。
饼图:
-
Band name - 为图表提供数据的报表带区。
-
Title field - 将从中获取分段名称的字段。
-
Value field - 将从中获取分段值的字段。
-
Color field - 将从中获取分段颜色的字段。颜色值应在 web 格式中指定。如果未定义,将自动选择颜色。
-
Units -此文本将被追加到图例值后面。
序列图:
-
Band name - 为图表提供数据的报表带区。
-
Category field - 将从中获取类别名称的字段。
-
Category axis caption - 横轴的标题。
-
Value axis caption - 纵轴的标题。
-
Value axis units - 纵轴值的单位。
必须为序列图添加至少一个行定义:
-
Value field - 将从中获取行值的字段。
-
Type - 行显示类型。
-
Color field - 将从中获取分段颜色的字段。颜色值应在 web 格式中指定。如果未定义,将自动选择颜色。
3.2.8. 数据透视表格式化器
如果应用程序项目包含 charts 组件,则可以使用数据透视表输出类型。有关数据透视表的更多信息,请参阅 Charts 手册。
透视表输出只能为报表输出锦上添花,这也就是为什么在报表向导中没有透视表。要使用透视表格式化器,在报表编辑页面切换到 Templates 标签页,点击 Create 然后选择透视表作为新模板的输出类型。之后,按照下面的描述配置模板。
生成的表格会显示在 Web 应用程序的 Reports → Show Pivot Tables 界面中。
报表生成器将获取报表带区数据并绘制具有拖放功能、聚合数据和汇总数据的表格。在数据透视报表中只能使用一个报表带区,不支持嵌套带区。
- 渲染器选项
-
此标签页上列出了可用的渲染器,选择一个默认渲染器。
- 聚合选项
-
Aggregation options 标签页允许定义表格聚合器列表。聚合属性:
-
Mode 可以设置一个预定义的聚合函数,
-
Caption 要在 UI 中显示的本地化名称,
-
Custom function - 如果不为空,则忽略 Mode 值而使用自定义的 JavaScript 代码。
-
- 属性选项
-
数据透视表显示所有类型的数据集的的所有属性。 需要注意的是,SQL、JPQL 和 Groovy 数据集中的引用属性的别名不能包含句点,例如, `select u.name as "userName" `。所选属性应在模板编辑界面的 Properties options 标签页上被设置为数据透视表的属性:
-
Row 、 Column - 都是键值对字典,其中包含要在数据透视表列和行中使用的属性,其中键是数据集中属性的名称,值是其本地化标题。
-
Aggregation - 要对其值进行聚合的属性,
-
Derived property - 可用于向原始数据集添加新属性,这些属性派生自现有数据集。此元素是键值对,其中键是生成的属性的名称,值是生成此属性的 JavaScript 函数。
-
- 自定义选项
-
-
Filter function - 用于过滤的 JavaScript 函数。
-
Sorters function - 用于行和列标题排序的 JavaScript 函数。
-
渲染器配置(可配置内容取决于在渲染器选项中选择的渲染器)- 允许设置用于自定义渲染器渲染效果的 JavaScript 函数。实际上,只有两种类型的渲染器可以定制:
-
各种
heatmap
:可以通过 Javascript 代码设置单元格颜色, -
各种图表:选项可用于设置图表的尺寸。
-
-
3.2.9. 表格格式化器(Formatter)
表格输出不需要报表模板,因为数据将显示在专用的应用程序界面。
要使用表格格式化器,请在报表模板编辑界面中选择 Table 作为输出类型。
报表生成器会获取报表带区数据,并从报表带区树的第一级为每个带区绘制一个可排序表格。
表格在列上显示 SQL、JPQL 数据集的所有属性。如果使用实体/实体列表数据集,则表格仅为选择的属性显示列。
结果表格可通过 Web 应用程序的 Reports > Show Report Table 界面查看。使用 Excel 按钮可以将显示的表格导出为 Excel 文件。
数据透视表显示所有类型的
3.2.10. 输出格式对应表
Template / Output | XLSX | XLS | CSV | DOCX | DOC | HTML | Chart | |
---|---|---|---|---|---|---|---|---|
XLSX |
+ |
+ |
+ 1 |
+ 1 |
||||
XLS |
+ |
+ 1 |
||||||
CSV |
+ |
|||||||
DOCX |
+ |
+ 2 |
+ 2 |
|||||
DOC |
+ |
+ 1 |
||||||
HTML |
+ |
+ |
||||||
Chart |
+ |
|||||||
JRXML |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
1 - 必须安装 OpenOffice/LibreOffice 才能输出。
2 - 根据reporting.openoffice.docx.useOfficeForDocumentConversion应用程序属性,可以选择使用或不使用 OpenOffice/LibreOffice 执行输出。在后一种情况下,需要提供所需的字体,如将 HTML 转换为 PDF中所述。
3.3. 外部报表参数
外部参数在运行报表时从外部传递,可用作数据集中的条件。所有外部参数都会成为每个报表带区的字段,因此在模板中可像数据集字段一样直接使用外部参数。在数据集的字段名与参数名相同时,数据集字段值优先,报表将使用数据集字段值。
可以在报表编辑界面的 Parameters and Formats 标签页中定义外部参数。添加参数的表单如下:
Properties 标签页:
-
Caption - 参数名称,在运行报表时这个名称显示在参数输入表单中。
-
Parameter alias - 用于在数据集中访问的参数别名。
-
Parameter type - 参数类型。
-
Hidden - 用于定义是否对用户隐藏此参数,隐藏后将不提示用户输入此参数。
-
Required parameter? - 是否是必须的参数。
-
Entity - 如果参数类型是 Entity 或 List of entities ,则需要在此字段中选择一个实体类型。
-
Entity selection screen - 可选的界面标识符,用于选择实体实例。如果未指定界面,将使用通用的实体选择界面。
-
Enumeration - 如果指定了 Enumeration 参数类型,则需要在此字段中选择枚举类型。
-
Default value - 定义用户未选择值的情况下将使用的默认参数值。
-
Default date(time) is current - 如果指定了时间参数类型(
Date
、Time
或Date and time
),此字段定义是否将当前时间戳用作默认参数值。
在 Localization 标签页中,可以为不同的区域设置定义参数名称。为此,应该使用 locale_name = parameter_name
键值对,例如:
ru = Книга
- 输入参数转换
-
在 Transformation 标签页中可以定义对参数进行处理的 Groovy 脚本,在报表中使用的将是经过处理的参数。
Groovy 脚本应该返回新的参数值。脚本中的当前参数值可以通过
paramValue
别名获取,参数 map 可以通过别名params
获取。例如:return "%" + paramValue + "%"
还可以使用预定义转换为文本(String)参数添加通配符:
-
Starts with
, -
Ends with
, -
Contains
.
Figure 53. 输入参数转换 -
在 Validation 标签页中,可以使用 Groovy 脚本定义一些参数验证条件,请参阅下面的详细说明。
- 输入参数验证
-
可以验证输入参数和(或)定义交叉参数验证。
-
可以通过选中 Validate 复选框,在参数编辑界面的 Validation 标签页中启用参数验证。验证逻辑由 Groovy 脚本指定。脚本应检查参数值,如果值无效调用就
invalid()
方法。此方法会向用户显示一条警告信息,提示用户参数验证失败。以下变量会传递到脚本中:
-
value
- 用户输入的参数值。 -
dataManager
- 提供 CRUD 功能的DataManager
类型的对象。 -
metadata
- 提供对应用程序元数据的访问的Metadata
类型的对象。 -
security
-Security
类型的对象,用于检查用户对系统中不同对象的访问权限。 -
userSession
- 与当前经过身份验证的用户关联的UserSession
类型的对象。
-
-
输入参数验证 image::report_parameter_validation.png[align="center"]
-
通过选中 Parameters and Formats 标签页的 Cross parameters validation 部分的 Validate 复选框,可以启用交叉参数验证。验证逻辑由 Groovy 脚本指定。这个脚本应检查参数值之间是否合乎逻辑, 检查不通过时调用
invalid()
方法。此方法将向用户显示一条警告信息,其中包含在脚本中指定的提示内容。除了上面列出的变量之外,
params
变量也会传递到脚本中以访问外部报表参数字典。Figure 54. Cross-parameter validation
-
3.4. 字段值格式化
可以在报表编辑界面的 Parameters and Formats 标签页中为报表输出的任何字段指定格式。下面是添加格式的表单:
-
Value name - 带有报表带区前缀的报表字段名称,例如,
Book.year
。 -
Format string - 格式化字符串。对于数字值,根据
java.text.DecimalFormat
的规则指定格式,对于日期值 - 根据java.text.SimpleDateFormat
的规则指定格式。 -
Groovy script - 复选框。可以使用 Groovy 脚本来格式化参数。使用
value
别名,会将当前参数值传递给脚本,可以用来格式化或者转换成需要的格式。Groovy 脚本需要返回字符串类型的新值。
使用格式,可以将图像和 HTML 块插入到文档中。
-
要插入图像,将图像 URL 指定为字段值,格式字符串必须是:
${image:<Width>x<Height>}
,例如${image:200x300}
。要使用
FileDescriptor
,可以使用${imageFileId:WxH}
格式,它接受FileDescriptor
id
和FileDecriptor
实例本身的链接。 -
要插入 HTML 片段,应该在字段中返回 HTML 标记,并选择
${html}
作为格式字符串。在输出值中,可以省略<body>
以外的 的顶层标记。如有必要,将自动添加所有缺少的顶层标记。所有片段都应该用UTF-8
编码。不支持 CSS 和style
属性。
也可以指定自定义的格式。直接在控件中输入新的值而不必打开下拉列表,然后敲回车。也可以从下拉列表中选择任何格式,在控件中编辑名称然后敲回车。两种情况都会保存自定义的格式。
3.5. 报表访问权限
可以在报表编辑界面的 Roles and Screens 标签页中定义哪些用户可以访问报表,以及哪些界面可以使用报表。
如果报表角色列表中含有角色,则该报表仅对具有此角色的用户可用。如果未指定任何角色,则所有人都可以使用该报表。
Tip
|
注意,在报表查看器(Reports→Reports 菜单项)中,能看到所有的系统报表,因为该菜单是管理员菜单。 |
界面列表允许指定在调用 RunReportAction
,TablePrintFormAction
或 EditorPrintFormAction
actions时报表可用的界面。如果未指定任何界面,则报表在任何界面都不可用。
4. 运行报表
本节介绍如何运行创建的报表。
4.1. 从通用报表浏览界面运行
运行报表最简单的方式是使用通用报表浏览界面,通过 Reports → Run Reports 打开该界面。用户必须有权访问此界面。在报表浏览界面列出了对当前用户可用的所有报表。如果报表有外部参数,则在运行报表会显示一个表单来获取这些参数。
4.2. 从界面运行报表
可以使用特定操作(action)及相关的按钮或组件右键菜单从任意界面运行报表。在这种情况下,除了用户角色外,还将检查报表在此界面的可用性。
下面提供操作(action)类型及其使用示例。
-
com.haulmont.reports.gui.actions.RunReportAction
- 在列表中显示所有可用报表的操作。当用户从列表中选择报表时,将显示参数(如果已定义)输入表单并运行报表。示例:在界面 XML 描述声明了一个按钮,在按钮中调用此操作
-
XML-描述
<layout> <groupTable id="booksTable"> ... <buttonsPanel id="buttonsPanel"> ... <button id="reportButton" icon="PRINT"/> </buttonsPanel> </groupTable>
-
控制器
@Inject private Button reportButton; @Subscribe private void onInit(InitEvent event) { reportButton.setAction(new RunReportAction("report")); }
-
messages.properties
report = Report
-
-
com.haulmont.reports.gui.actions.TablePrintFormAction
- 这个操作可用在一个实体列表表格上。该操作仅选择具有 Entity 或 List of entities 类型的外部参数的报表,并且参数的实体类型要与表格中显示的实体类型一样。如果只有一个这样的报表可用,会立即调用这个报表。如果多个报表可用,会显示一个列表供用户选择。使用以下规则将外部参数值传递给报表:
-
如果参数是 List of entities 类型,会将表格中当前选择的实例列表传递给它。
-
如果参数是 Entity 类型,并且在表格选择了单个实例(突出显示一行),则选中实例会传递到报表中。
-
如果是 Entity 类型的参数,并且表中选择了多行,则报表会根据选择的实例数量运行多次。执行之后,用户将得到一个包含所有生成的报表的 ZIP 文件。
下面是在表格按钮和右键菜单中使用操作(action)的示例:
-
XML 描述
<layout> <groupTable id="booksTable"> ... <buttonsPanel id="buttonsPanel"> ... <button id="reportButton" icon="PRINT"/> </buttonsPanel> </groupTable>
-
控制器
@Inject private Button reportButton; @Inject private GroupTable<Book> booksTable; @Subscribe private void onInit(InitEvent event) { TablePrintFormAction action = new TablePrintFormAction("report", booksTable); booksTable.addAction(action); reportButton.setAction(action); }
-
messages.properties
report = Report
-
-
com.haulmont.reports.gui.actions.EditorPrintFormAction
- 与实体编辑器界面关联的操作。该操作仅选择外部参数类型是 Entity 或 List of entities 的报表,并且参数实体类型要与编辑的实体类型一样。如果只有一个这样的报表,则会立即调用它。如果多个,则会显示一个列表供用户选择。外部参数值 - 被编辑的实体实例被传递到报表中。如果参数是 List of entities 类型,则会传递一个包含单个条目的列表。
下面是在一个按钮中使用这个操作的示例,这个按钮位于标准的 OK 和 Cancel 按钮旁边:
-
XML 描述
<layout expand="editActions" spacing="true"> ... <hbox id="editActions" spacing="true"> <button action="windowCommitAndClose"/> <button action="windowClose"/> <button id="reportButton" icon="PRINT"/> </hbox> </layout>
-
控制器
@Inject private Button reportButton; @Subscribe private void onBeforeShow(BeforeShowEvent event) { reportButton.setAction(new EditorPrintFormAction("report", this, null)); }
-
messages.properties
report = Report
-
4.3. 取消报表
如果报表以后台任务的方式运行,那么用户就可以中断报表的执行。
要添加取消选项,请在 Administration > Application Properties 界面中设置 reporting.useBackgroundReportProcessing 属性。
reporting.useBackgroundReportProcessing = true
这样,报表在执行时会显示带有进度条和 Cancel 按钮的窗口:
还可以使用 reporting.backgroundReportProcessingTimeoutMs 属性设置处理超时:
reporting.backgroundReportProcessingTimeoutMs = 30000
时间到了,无论结果如何任务都将被取消,用户会接收到错误信息:
要以编程方式取消报表执行,可以使用 ReportService
接口的 cancelReportExecution()
方法,该方法需要用户会话和报表的标识:
reportService.cancelReportExecution(userSessionId, report.getId());
5. 示例报表
5.1. XLS 报表示例
在本章中,了解 Library 示例应用程序中的一个报表的结构,源代码可从 GitHub 获取。
首先,在 CUBA Studio 中打开 Project Properties 编辑器:点击 CUBA → Project Properties 主菜单项。在页面上的 App components 列表中添加 reports 应用程序组件。然后启动应用。
打开 Reports → Reports 界面,然后单击 Import 按钮导入报表。在项目根目录中选择 Reports.zip。在表格中将出现两个报表,其中一个是 "Books by author"。此报表显示所选作者的书籍出版物列表;书籍将按书名和出版商分组。输出格式为 XLS。
-
我们看看报表带区。
-
header 带区 - 报表标题。它包含带有 Groovy 脚本的数据集,该脚本输出报表外部参数值:
[['authorName' : (params['author'].firstName + ' ' + params['author'].lastName)]]
-
book 带区通过执行以下 SQL 查询输出书籍列表:
select b.name as book_name, b.id as book_id from library_book b join library_book_author_link ba on ba.book_id = b.id join library_author a on a.id = ba.author_id where a.id = ${author}
此查询使用外部报表参数 - author。该参数是 Entity 类型,但在 SQL 查询中,可以直接将其与实体标识符字段进行比较;类型转换会自动完成。
-
publisher 带区是 book 的子带区,通过执行以下 SQL 查询输出图书出版商:
select p.name as publisher, bp.year, p.id as publisher_id from library_book_publication bp join library_publisher p on p.id = bp.publisher_id where bp.book_id = ${book.book_id}
此查询使用父带区字段
book_id
作为参数。以这种方式指定了父带区和子带区之间的依赖关系。-
publication 带区是 publisher 带区的子项,通过执行以下 SQL 查询输出图书出版物:
select ld.name as department, count(bi.id) as amount from library_book_instance bi join library_book_publication bp on bp.id = bi.book_publication_id join library_library_department ld on ld.id = bi.library_department_id where bp.publisher_id = ${publisher.publisher_id} and bp.book_id = ${book.book_id} group by ld.name
此查询使用父带区字段作为参数 -
book_id
和publisher_id
。 -
-
报表参数。
Parameters and Formats 标签页包含一个声明的报表外部参数 -
Author
:运行报表时,用户必须输入此参数。作者是通过应用程序中的
library$Author.lookup
界面进行选择的。 -
报表模板。
Templates 标签页包含一个定义的 XLS 模板,从
BooksByAuthor.xls
加载 -
报表名称本地化。
Localization 标签页包含俄语语言环境的报表名称:
ru = Книги по автору
可以在 Reports → Run Reports 通用浏览界面中运行报表。
5.2. 交叉报表示例
要创建交叉报表,请在报表编辑界面的 Report structure 标签页上选择 Crosstab 带区方向。此方向将自动添加三个数据集到报表带区:
-
<band_name>
_dynamic_header - 这个数据集的数据将向右复制,类似于包含表格列标题的垂直带区。 -
<band_name>
_master_data - 这个数据集的数据向下复制,类似于包含表格行标题的水平带区。 -
<band_name>
- 与其所属带区名称相同的数据集。它是实现单元格矩阵的主内容带区。
例如, Sales 示例应用程序的 Order
实体的交叉报表可能具有以下结构:
-
这里,
orders_dynamic_header
数据集将返回月份名称列表:orders_dynamic_header 数据集import java.text.DateFormatSymbols List result = new ArrayList() DateFormatSymbols dateFormatSymbols = DateFormatSymbols.getInstance(Locale.ENGLISH) for (i in 0..dateFormatSymbols.months.length - 1) { result.add(["header_id" : i + 1, "month_name" : dateFormatSymbols.months[i]]) } return result
-
orders_master_data
数据集返回根据外部 user 参数选择的客户名称和标识符:orders_master_data 数据集select name as name, id as customer_id from SALES_CUSTOMER where id in (${selected_customers})
-
orders
数据集将提供单元格矩阵的数据,这个数据统计出特定用户在特定月份的订单金额总额。它将orders_master_data@customer_id
(客户 id)作为单元格的 Y-坐标,并将orders_dynamic_header@header_id
(月份名称)作为 X-坐标,使用amount
值填充单元格矩阵。在下面的示例中,报表还有两个外部参数:
start_date
和end_date
用于定义订单日期的范围。通过 参数交叉验证 来确保参数值在合理的范围是一个很好的主意。orders datasetselect o.customer_id as orders_master_data@customer_id, month(o.date_) as orders_dynamic_header@header_id, sum(o.amount) as "amount" from sales_order o where o.date_ >= ${start_date} and o.date_ <= ${end_date} and o.customer_id in (${orders_master_data@customer_id}) and month(o.date_) in (${orders_dynamic_header@header_id}) group by o.customer_id, month(o.date_) order by o.customer_id, month(o.date_)
最后可以使用 Microsoft Office 或 LibreOffice 创建报表模板。
报表模板应包含交叉带区的所有三个数据集的命名区域以及列标题的命名区域: <band_name>_header
。在这个例子中,它是 orders_header
。
下面是一个模板示例,它在垂直方向输出 客户
列表,在水平方向输出按下单日期汇总的每个月的 订单
金额。
这样,报表可以纵向和横向扩展,并汇总每个客户和每个月的订单金额:
如果想为报表添加总计,应该在单独带区中执实现,并且为这个带区定义汇总数据集。
5.3. JRXML 报表示例
该示例也是基于示例 Library 应用程序,源代码可在 GitHub 上获取。
首先,在 CUBA Studio 中打开 Project Properties 编辑器:点击 CUBA → Project Properties 主菜单项。在页面上的 App components 列表中添加 reports 应用程序组件。然后启动应用。
打开 Reports → Reports 界面,然后单击 Import 按钮导入报表。在项目根目录中选择 Reports.zip。在表格中将显示两个报表,其中一个报表显示每个图书类目中可用的书籍。这个报表显示选中的类目下的图书出版物列表;默认输出格式为 XLS。我们为此报表创建新的 JasperReports 模板。
-
报表带区:
-
Header 带区 - 报表标题。它是包含 Groovy 脚本的数据集,该脚本输出报表外部参数值:
[['library_department_name' : params['library_department'].name]]
-
Data 带区通过运行以下 Groovy 脚本输出指定类目下的实例列表,这个类目根据传递的参数确定:
import com.haulmont.cuba.core.global.AppBeans def persistence = AppBeans.get('cuba_Persistence') def tx = persistence.createTransaction() try { def result = [] def em = persistence.getEntityManager() def ltList = em.createQuery('select lt from library$LiteratureType lt').getResultList() ltList.each { lt -> def count = em.createQuery(''' select count(bi) from library$BookInstance bi where bi.libraryDepartment = ?1 and bi.bookPublication.book.literatureType = ?2 ''') .setParameter(1, params['library_department']) .setParameter(2, lt) .getSingleResult() def refCount = em.createQuery(''' select count(bi) from library$BookInstance bi where bi.libraryDepartment = ?1 and bi.bookPublication.book.literatureType = ?2 and bi.isReference = true''') .setParameter(1, params['library_department']) .setParameter(2, lt) .getSingleResult() result.add(['literature_type_name': lt.name, 'books_instances_amount': count, 'reference_books_instances_amount': refCount]) } return result } finally { tx.end() }
此查询使用外部报表参数 -
library_department
。这个参数是 Entity 类型,但可以直接将其与实体标识符字段进行比较,类型转换会自动完成。
-
-
报表参数.
Parameters and Formats 标签页包含一个声明的报表外部参数 - Department:
运行报表时,用户必须输入此参数。部门是通过应用程序中的
library$LibraryDepartment.lookup
界面选择的。 -
报表模板。
Templates 标签页包含一个定义好的 XLS 模板,从
BookAvailability.xls
加载。使用以下内容创建新的 JRXML 文件:
BookAvailability.jrxml<?xml version="1.0" encoding="UTF-8"?> <!-- Created with Jaspersoft Studio version 6.4.0.final using JasperReports Library version 6.4.1 --> <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="books" pageWidth="595" pageHeight="842" whenNoDataType="AllSectionsNoDetail" columnWidth="535" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20"> <property name="template.engine" value="tabular_template"/> <property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/> <style name="Table_TH" mode="Opaque" backcolor="#066990"> <box> <topPen lineWidth="0.5" lineColor="#000000"/> <bottomPen lineWidth="0.5" lineColor="#000000"/> </box> </style> <style name="Table_CH" mode="Opaque" forecolor="#FFFFFF" backcolor="#06618F" hTextAlign="Center" fontSize="12"> <box> <topPen lineWidth="0.5" lineColor="#000000"/> <bottomPen lineWidth="0.5" lineColor="#000000"/> </box> </style> <style name="Table_TD" mode="Opaque" backcolor="#FFFFFF" hTextAlign="Center"> <box> <topPen lineWidth="0.5" lineColor="#000000"/> <bottomPen lineWidth="0.5" lineColor="#000000"/> </box> </style> <subDataset name="Data"> <field name="literature_type_name" class="java.lang.String"/> <field name="books_instances_amount" class="java.lang.Long"/> <field name="reference_books_instances_amount" class="java.lang.Long"/> </subDataset> <field name="library_department_name" class="java.lang.String"/> <title> <band height="72"> <frame> <reportElement mode="Opaque" x="-20" y="-20" width="595" height="92" backcolor="#006699"/> <staticText> <reportElement x="20" y="10" width="555" height="30" forecolor="#FFFFFF"/> <textElement textAlignment="Center"> <font size="20" isBold="true"/> </textElement> <text><![CDATA[Book availability in department]]></text> </staticText> <textField> <reportElement x="20" y="50" width="555" height="30" forecolor="#FFFFFF"/> <box> <pen lineWidth="1.0" lineColor="#FFFFFF"/> <topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/> <leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/> <bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/> <rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/> </box> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="SansSerif" size="20" isBold="true"/> </textElement> <textFieldExpression><![CDATA[$F{library_department_name}]]></textFieldExpression> </textField> </frame> </band> </title> <detail> <band height="204"> <componentElement> <reportElement x="0" y="4" width="555" height="200" forecolor="#FFFFFF"> <property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/> <property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/> <property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/> <property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/> <property name="net.sf.jasperreports.export.headertoolbar.table.name" value=""/> <property name="com.jaspersoft.studio.components.autoresize.proportional" value="true"/> </reportElement> <jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd"> <datasetRun subDataset="Data"> <dataSourceExpression><![CDATA[$P{REPORTING}.dataset("Data")]]></dataSourceExpression> </datasetRun> <jr:column width="188"> <jr:columnHeader style="Table_CH" height="30"> <staticText> <reportElement x="0" y="0" width="188" height="30" forecolor="#FFFFFF"/> <box> <pen lineColor="#FFFFFF"/> </box> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="SansSerif" size="12" isBold="true"/> </textElement> <text><![CDATA[Literature Type]]></text> </staticText> </jr:columnHeader> <jr:detailCell style="Table_TD" height="30"> <textField> <reportElement x="0" y="0" width="188" height="30"/> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="SansSerif" size="12"/> </textElement> <textFieldExpression><![CDATA[$F{literature_type_name}]]></textFieldExpression> </textField> </jr:detailCell> </jr:column> <jr:column width="186"> <jr:columnHeader style="Table_CH" height="30"> <staticText> <reportElement x="0" y="0" width="186" height="30" forecolor="#FFFFFF"/> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="SansSerif" size="12" isBold="true"/> </textElement> <text><![CDATA[Book Amount]]></text> </staticText> </jr:columnHeader> <jr:detailCell style="Table_TD" height="30"> <textField> <reportElement x="0" y="0" width="186" height="30"/> <textElement textAlignment="Center" verticalAlignment="Middle"> <font size="12"/> </textElement> <textFieldExpression><![CDATA[$F{books_instances_amount}]]></textFieldExpression> </textField> </jr:detailCell> </jr:column> <jr:column width="181"> <jr:columnHeader style="Table_CH" height="30"> <staticText> <reportElement x="0" y="0" width="181" height="30" forecolor="#FFFFFF"/> <textElement textAlignment="Center" verticalAlignment="Middle"> <font fontName="SansSerif" size="12" isBold="true"/> </textElement> <text><![CDATA[Reference Book Amount]]></text> </staticText> </jr:columnHeader> <jr:detailCell style="Table_TD" height="30"> <textField isBlankWhenNull="false"> <reportElement x="0" y="0" width="181" height="30" forecolor="#000000"/> <textElement textAlignment="Center" verticalAlignment="Middle"> <font size="12"/> </textElement> <textFieldExpression><![CDATA[$F{reference_books_instances_amount}]]></textFieldExpression> </textField> </jr:detailCell> </jr:column> </jr:table> </componentElement> </band> </detail> <pageFooter> <band height="17"> <textField> <reportElement mode="Opaque" x="0" y="4" width="515" height="13" backcolor="#E6E6E6"/> <textElement textAlignment="Right"/> <textFieldExpression><![CDATA["Page "+$V{PAGE_NUMBER}+" of"]]></textFieldExpression> </textField> <textField evaluationTime="Report"> <reportElement mode="Opaque" x="515" y="4" width="40" height="13" backcolor="#E6E6E6"/> <textFieldExpression><![CDATA[" " + $V{PAGE_NUMBER}]]></textFieldExpression> </textField> <textField pattern="M/d/yy"> <reportElement x="0" y="4" width="280" height="13"/> <textFieldExpression><![CDATA[new java.util.Date()]]></textFieldExpression> </textField> </band> </pageFooter> </jasperReport>
这个模板中的表格绑定到子数据集(subDataset)。
title
元素直接使用 Header 区数据。可以在 JasperReports 可视化设计器中打开模板文件来查看报表布局。将新模板上传到应用程序,选择任何一种输出类型,并将其设置为默认值:
运行一下报表以确定报表定义正确:
5.4. 带分页、页眉和页脚的 HTML/PDF 报表示例
假设要创建一个这样的报表:横向展示、每页上都显示页码和固定的页眉页脚,使用特殊的 CSS 规则和属性进行配置。输出格式是 HTML 并可以导出为 PDF。
此报表示例及其演示项目也可在 CUBA GitHub 上找到。
-
数据模型
报表将显示有关
Client
实体的信息。它包含两个 String 属性:title
和summary
,在报表结构中会使用它们。public class Client extends StandardEntity { @NotNull @Column(name = "TITLE", nullable = false) protected String title; @Lob @Column(name = "SUMMARY") protected String summary; ... }
-
创建一个没有参数的简单报表。使用 JPQL 查询所有 Client 实体的本地属性:
title
和summary
。 -
报表模板.
现在创建报表模板文件。在这里定义页眉和页脚块,页眉和页脚会在每页 PDF 都打印 。这需要使用特殊的
page-break-before
:always
CSS 属性。它会在每个 Client 信息块之前生成分页符。如下所示,使用 FreeMarker 语法将数据插入到模板中。请在此处查看完整的 FreeMarker 参考: https://freemarker.apache.org/docs/ 。
<body> <h1>Clients report</h1> <!-- Custom HTML header --> <div class="header"> Annual Report of our Company </div> <!-- Custom HTML footer --> <div class="footer"> Address: William Road </div> <#assign clients = Root.bands.Clients /> <#list clients as client> <div class="custom-page-start" style="page-break-before: always;"> <h2>Client</h2> <p>Name: ${client.fields.title}</p> <p>Summary: ${client.fields.summary}</p> </div> </#list> </body>
-
CSS 规则
将使用以下 CSS 代码来调整 PDF 页面显示:
body { font: 12pt Georgia, "Times New Roman", Times, serif; line-height: 1.3; } @page { /* switch to landscape */ size: landscape; /* set page margins */ margin: 0.5cm; @top-center { content: element(header); } @bottom-center { content: element(footer); } @bottom-right{ content: counter(page) " of " counter(pages); } }
此 CSS 代码将设置页眉/页脚位置:
div.header { display: block; text-align: center; position: running(header); width: 100%; } div.footer { display: block; text-align: center; position: running(footer); width: 100%; }
之后,需要填充主要内容的边距以防止内容和页眉/页脚重叠:
/* Fix overflow of headers and content */ body { padding-top: 50px; } .custom-page-start { margin-top: 50px; }
最终,完整的
paging-template.html
文件如下所示:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Invoice</title> <style type="text/css"> body { font: 12pt Georgia, "Times New Roman", Times, serif; line-height: 1.3; padding-top: 50px; } div.header { display: block; text-align: center; position: running(header); width: 100%; } div.footer { display: block; text-align: center; position: running(footer); width: 100%; } @page { /* switch to landscape */ size: landscape; /* set page margins */ margin: 0.5cm; @top-center { content: element(header); } @bottom-center { content: element(footer); } @bottom-right { content: counter(page) " of " counter(pages); } } .custom-page-start { margin-top: 50px; } </style> </head> <body> <h1>Clients report</h1> <!-- Custom HTML header --> <div class="header"> Annual Report of our Company </div> <!-- Custom HTML footer --> <div class="footer"> Address: William Road </div> <#assign clients = Root.bands.Clients /> <#list clients as client> <div class="custom-page-start" style="page-break-before: always;"> <h2>Client</h2> <p>Name: ${client.fields.title}</p> <p>Summary: ${client.fields.summary}</p> </div> </#list> </body> </html>
-
上传模板文件并运行报表。
正如所见,报表包含标题页、每个 Client 信息前都分页、每页都显示页眉和页脚。
5.5. 使用 Groovy 模板引擎的 HTML 报表
该示例是基于 Library 应用程序,其源码可以在 GitHub 找到。我们创建一个报表展示选中城市的图书出版物。输出格式是 HTML。
-
使用 JPQL 数据集创建报表:
Figure 63. 报表数据结构BookPublications
区域用来输出图书出版物列表,使用如下 JPQL 查询语句:BookPublications 数据集select book.name as "book", publisher.name as "publisher" from library$BookPublication e left join e.book book left join e.publisher publisher where e.town.id = ${city}
这个查询使用了外部的报表参数 -
city
。该参数是 Entity 类型;但是,在 JPQL 查询语句中可以直接用来跟实体标识符进行比较;后台会自动做转换。 -
报表参数描述:
在 Parameters and Formats 标签页声明了一个报表外部参数 –
City
:Figure 64. 报表参数当运行报表时,用户必须输入该参数。城市的选择会通过
library$Town.browse
界面进行,这个界面在应用程序内是可用的。 -
创建一个报表模板
Templates 标签页定义了一个 HTML 模板,使用 FreeMarker 标签默认生成。
使用下面内容创建新的 HTML 文件:
PublicationsTemplate<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru"> <head> <title> Publications by city </title> <style type="text/css"> body {font: 12pt Georgia, "Times New Roman", Times, serif; line-height: 1.3; padding-top: 30px;} tbody tr {height:40px; min-height:20px} </style> </head> <body> <h1>Publications, published in <% out << "${Root.fields.city.name}"%></h1> <% def bookPublications = Root.bands.BookPublications.fields %> <table class="report-table" border="1" cellspacing="2" > <thead> <tr> <th>Book</th> <th>Publisher</th> </tr> </thead> <tbody> <% bookPublications.title.eachWithIndex{elem, index -> out << "<tr><td> ${bookPublications.book[index]} </td><td> ${bookPublications.publisher[index]} </td></tr>"}%> </tbody> </table> </body> </html>
输入参数的值用来生成报表标题:
${Root.fields.city.name}
。bookPublications
变量定义如下:<% def bookPublications = Root.bands.BookPublications.fields %>
该变量在表格体中用来显示报表字段。
<% bookPublications.title.eachWithIndex{elem, index -> out << "<tr><td> ${bookPublications.book[index]} </td><td> ${bookPublications.publisher[index]} </td></tr>"}%>
上传新的模板,然后选择 HTML 输出类型,在 Template type 单选按钮组选择 Groovy template 并设置为默认:
Figure 65. 报表模板编辑器
运行模板,确保其工作正常:
6. 报表 REST API
通用 REST API 为报表扩展提供以下功能:
-
获取报表列表。
-
获取特定报表的详细信息。
-
运行报表并获取结果。
-
获取 Swagger 文档。
REST API 使用 OAuth2 协议进行身份验证,并支持匿名访问。
要通过 REST API 提供报表,请在 Roles and Screens 标签页上选中 Visible for REST API 复选框:
下面提供了一些报表特定功能的一般描述。有关如何获取 OAuth 令牌和其它 REST API 功能的更多信息,请参阅 REST API 扩展文档 。
- 获取现有报表列表
-
可以使用以下 GET 请求获取现有报表列表:
/rest/reports/v1/report
例如:
GET http://localhost:8080/app/rest/reports/v1/report HTTP/1.1 Authorization: Bearer f5a2b4b1-a121-4563-9519-dd3c0b116689 Content-Type: application/json
响应体将包含标记为 Visible for REST API 的报表的简要信息:
{ "id": "2dd27fbf-8830-416a-899f-339543f8f27a", "name": "Books by author" }, { "id": "2f07c9fe-5d6d-48cf-876f-8c02ac1f6c3c", "name": "Book availability in department" }
- 获取报表信息
-
使用以下 GET 请求获取指定报表的详细信息:
/rest/reports/v1/report/{id}
这里查询的最后一部分是报表标识符,例如:
GET http://localhost:8080/app/rest/reports/v1/report/2dd27fbf-8830-416a-899f-339543f8f27a HTTP/1.1
返回的 JSON 对象将包含报表的以下信息:
{ "id": "2dd27fbf-8830-416a-899f-339543f8f27a", "name": "Books by author", "templates": [ { "code": "DEFAULT", "outputType": "XLS" } ], "inputParameters": [ { "name": "Author", "alias": "author", "type": "ENTITY", "required": true, "hidden": false, "entityMetaClass": "library$Author" } ] }
- 运行报表
-
要运行报表,请发送以下 POST 请求:
/rest/reports/v1/run/{id}
这里查询的最后一部分是报表标识符,例如:
POST http://localhost:8080/app/rest/reports/v1/run/2dd27fbf-8830-416a-899f-339543f8f27a HTTP/1.1
报表参数在请求体中被传递:
{parameters: [{name: 'author',value: '4b3a21b0-d6b7-4161-b0b6-55f118fbaac5'}]}
要使用非默认模板打印报表,请在请求体中传递模板代码:
{template: 'Template_1', parameters: [{name: 'author',value: '4b3a21b0-d6b7-4161-b0b6-55f118fbaac5'}]}
- 获取 Swagger 文档
-
可以通过 GET 请求此地址获取报表扩展的完整 Swagger 文档:
http://localhost:8080/app/rest/reports/v1/docs/swagger.json
Appendix A: 安装和配置 OpenOffice
报表生成器使用 OpenOffice / LibreOffice 程序包来输出 PDF 和 DOC 格式的报表。需要在计算机上安装应用服务并进行配置。
在 Windows 上安装和配置 Openoffice
-
在 http://openoffice.org 下载应用程序。
-
安装应用程序。
-
在 core 模块的
app.properties
文件中的reporting.openoffice.path应用程序属性中指定 OpenOffice.org 的路径,例如:
reporting.openoffice.path = C:/Program Files (x86)/OpenOffice.org 3/program
在 Windows 上安装和配置 LibreOffice
-
安装应用程序。
-
在 core 模块的
app.properties
文件中的reporting.openoffice.path应用程序属性中,指定 LibreOffice 的路径,例如:
reporting.openoffice.path = C:/Program Files (x86)/LibreOffice 5/program
在 Ubuntu Server 上安装和配置 LibreOffice
-
通过运行以下命令来安装
libreoffice
包,例如:$ sudo apt-get install libreoffice`
-
在 core 模块的
app.properties
文件中的reporting.openoffice.path应用程序属性中,指定 LibreOffice 的路径:reporting.openoffice.path = /usr/lib/libreoffice/program
-
如果服务不是通过界面操作的方式安装,LibreOffice 启动时将出现错误,
Caused by: java.awt.HeadlessException: No X11 DISPLAY variable was set, but this program performed an operation which requires it
,或者只是停止运行而没有错误消息。要解决此问题,请设置reporting.displayDeviceUnavailable应用程序属性:reporting.displayDeviceUnavailable = true
-
启动 LibreOffice 时,可以运行以下命令来诊断错误:
$ strace -e trace=signal /usr/lib/libreoffice/program/soffice.bin --headless --accept="socket,host=localhost,port=8100;urp" --nologo --nolockcheck
Tip
|
对于使用 之后,应该更改此文件夹的所有者:
|
在 macOS 上安装和配置 LibreOffice
-
在 https://www.libreoffice.org/get-help/install-howto/os-x/ 下载应用程序。
-
安装应用程序。
-
在reporting.openoffice.path应用程序属性中,指定 LibreOffice.app 的路径,例如:
reporting.openoffice.path = /Applications/LibreOffice.app
Appendix B: 应用程序属性
本节按字母顺序描述与报表生成器相关的应用程序属性。
- reporting.backgroundReportProcessingTimeoutMs
-
在 reporting.useBackgroundReportProcessing 设置为
true
的情况下,用该参数定义报表执行过程超时的时限,单位为毫秒。默认值:
10000
保存在数据库
用于 Middleware block。
- reporting.enableTabSymbolInDataSetEditor
-
定义是否应将 TAB 键作为
\t
符号处理,而不是在报表编辑界面的脚本字段中进行焦点切换。默认值:
false
用于客户端 block。
- reporting.fontsDir
-
指定 HTML 转换为 PDF 时使用的字体目录的路径。
例如:
reporting.fontsDir = C:/Windows/Fonts
。用于 Middleware block。
- reporting.docFormatterTimeout
-
设置 LibreOffice 转换 DOCX/XLSX 为 HTML/PDF 的超时时限,单位是秒。
当时间用完的时候,用户会收到一个错误消息。
默认值:
20
用于 Middleware block。
- reporting.openoffice.docx.useOfficeForDocumentConversion
-
开启使用 OpenOffice 将含有 DOCX 模板的报表转换为 HTML/PDF 的功能,这样可以显著提高转换的准确性。
默认值:
false
用于 Middleware block。
- reporting.openoffice.path
-
设置 OpenOffice 的路径。
默认值:
/
用于 Middleware block。
- reporting.openoffice.ports
-
为 OpenOffice/LibreOffice 指定用逗号或竖直分隔符隔开的可用端口列表。
例如:
reporting.openoffice.ports = 8100|8101|8102|8103|8104|8105
.默认值:
8100, 8101, 8102, 8103
.用于 Middleware block。
- reporting.putEmptyRowIfNoDataSelected
-
设置当报表带区的数据集没有返回记录时是否仍然将带区显示一次。
默认值:
true
用于 Middleware block。
- reporting.useBackgroundReportProcessing
-