像素级精确报表把数据定义和版式定义分开管理:Report Query 负责提供报表需要的数据,Report Layout 负责用 Word 或 Excel 模板控制最终文档的外观。APEX 会把布局文件和 JSON 数据发送给远程打印服务,由打印服务合成并返回 PDF、Word 或 Excel 等输出文件。

21 生成像素级精确报表#

本章的核心流程是:先准备可用的远程打印服务,再创建报表模板和数据查询,最后从页面或报表区域触发下载。一个 Report Query 可以包含多个数据源;每个数据源可对应模板中的一个循环名称。只要模板能够表达需要的字体、列宽、分页、页眉页脚和图文排版,就可以用同一套机制生成业务所需的固定格式文档。

学习时要同时关注三件事:模板中的标签名称、APEX 中的数据循环名称、实际 JSON 中的属性名称。这三者不一致时,文档仍可能生成,但对应位置会为空或重复结构不正确。

21.1 设置应用的远程打印服务器#

应用可以使用实例级打印服务器配置来生成报表。官方示例使用 Oracle Cloud Infrastructure Document Generator Function。它使用一组标签在 Word 或 Excel 模板中引用报表数据,并支持以 Microsoft Word 或 Excel 文件作为布局模板,输出 PDF、Word 或 Excel 文件。

对应用开发者来说,最简单的设置是在应用属性中把 Print Server Type 设为 Use Instance Settings。这样应用会继承实例管理员已经配置好的远程打印服务。

图 21-1 将应用配置为使用实例级远程打印设置。

实例管理员需要先在实例设置中完成打印服务器配置。以 Oracle Document Generator 为例,管理员会配置数据库云凭据、对象存储桶访问权限、函数相关 OCID 和访问 URL。完成后,各工作区中的应用开发者可以直接使用该打印能力,而不需要在每个应用中处理这些 OCI 细节。

图 21-2 APEX 实例管理员在 Instance SettingsReport Printing 选项卡中配置 Oracle Document Generator。
  • 前提:实例管理员已配置可用的远程打印服务器。
  • 操作位置:应用定义中的打印相关属性,以及管理员侧的 Instance Settings
  • 验证点:应用的 Print Server Type 使用实例设置;后续 Test Report 或页面下载可以成功返回文件。
  • 预期结果:应用开发者不需要在业务页面中直接维护打印服务器凭据和 OCI 访问参数。

21.2 使用 Excel 模板打印报表#

本节用 Woods Clinic 的员工任期报表说明如何把应用数据合并到 Excel 模板中并生成 PDF。业务需求是让 HR 代表按部门生成员工任期报表。实现路径包括:设计 Excel 模板标签、创建 Report Layout、创建 Report Query、设置数据循环名称、检查 JSON、再从页面触发打印下载。

21.2.1 用标签在 Excel 中格式化数据#

模板可以用 Microsoft Excel 创建。设计模板前,先列出报表需要的数据来源、字段和每个来源的短名称。官方示例中的 Tenure Report 使用三组数据:

  • 部门信息,短名称为 dep,字段包括 dnamedeptnoloc
  • 按任期排序的员工信息,短名称为 emp_t,字段包括 enameempnotenurejob
  • 按姓名排序的员工信息,短名称为 emp_n,字段包括 enamehiredatejob

在模板中,用 {#shortname}{/shortname} 包围循环区域,用 {fieldname} 引用字段值。字段名称应与报表源列名的小写形式匹配。

部门信息可以写成:

{#dep}Department {dname} ({deptno}) in {loc}{/dep}

Employees by Tenure 区域使用横向重复行。竖线表示 Excel 单元格边界,Document Generator 会对 {#emp_t}{/emp_t} 之间的单元格行按数据行重复。

{#emp_t}{ename}({empno}) | {tenure} | {job}{/emp_t}

Employees by Name 区域使用纵向重复列。Document Generator 会对 {:emp_n}{/emp_n} 之间的单元格列按数据行重复。

{:emp_n}{ename}
-
{hiredate}
-
{job}{/emp_n}
图 21-3 用 Microsoft Excel 创建带有 Document Generator 标签的报表布局。
  • 前提:已明确报表需要的字段、排序方式和分组方式。
  • 操作位置:Microsoft Excel 模板文件。
  • 验证点:短名称和字段标签与后续 Report QueryData Loop Name、列名一致。
  • 预期结果:模板能描述部门标题、按任期的员工列表,以及按姓名排列的员工信息区域。

21.2.2 为文档模板创建报表布局#

Excel 模板文件需要作为共享组件中的 Report Layout 保存。示例中布局名称为 Tenure Report,并设置 Static ID。这个静态 ID 可在业务逻辑中调用 APEX_PRINT.GENERATE_DOCUMENT 时引用,用来生成 PDF。

上传模板时,可以把 woods_clinic_tenure_report.xlsx 拖放到 File 区域,也可以点击该区域从系统文件对话框中选择文件。

图 21-4 定义用于 Excel 文档模板的 Report Layout
  • 前提:Excel 模板文件已保存并包含正确的 Document Generator 标签。
  • 操作位置:共享组件中的 Report Layouts
  • 验证点:布局名称、静态 ID、模板文件类型和上传文件都正确。
  • 预期结果:APEX 中出现一个可被 Report Query 或打印流程引用的布局定义。

21.2.3 定义报表查询来源#

Report Query 用来提供报表数据。示例中的 Department Employee Tenure 定义包含三个报表源,并引用 Tenure Report 布局。报表源可以来自本地 SQL、远程查询、表、视图、REST Data Source 等;示例的三个来源都使用本地数据库 SQL 查询。

部门来源使用应用项 G_DEPTNO_FOR_REPORT 作为绑定变量:

select deptno, dname, loc
  from dept
 where deptno = :G_DEPTNO_FOR_REPORT

Set Bind Values 按钮可以为查询中的绑定变量设置示例值。点击 Download 时,APEX 会用这些示例值生成代表报表数据的 JSON 文件;点击 Test Report 时,同一份 JSON 会与布局一起发送到远程打印服务器,用于验证输出结果。

图 21-5 为任期报表配置 Report Query 数据来源。
  • 前提:布局已创建,查询需要的表和绑定变量可用。
  • 操作位置:共享组件中的 Report Queries
  • 验证点:每个报表源都能单独返回数据;绑定变量示例值可以生成样例 JSON。
  • 预期结果:报表查询可向模板提供部门信息、按任期排序的员工信息和按姓名排序的员工信息。

21.2.4 为来源指定数据循环名称#

每个报表源的 Data Loop Name 必须与模板标签中的短名称匹配。它就是模板中用来“遍历”该来源每一行数据的名称。示例中,部门来源使用 dep,因此 Excel 模板可以引用:

{#dep}Department {dname} ({deptno}) in {loc}{/dep}
图 21-6 让报表源的数据循环名称与模板标签保持一致。
  • 前提:模板中已经定义了短名称,例如 depemp_temp_n
  • 操作位置:Report Query 中每个 Report Source 的属性。
  • 验证点:下载 JSON 后,顶层属性名称与模板循环名称一致。
  • 预期结果:模板中的循环标签能正确找到对应数据数组。

21.2.5 在报表来源中使用 SQL 能力#

报表源查询可以使用 SQL 的常规能力,也可以调用自定义函数来把数据塑造成模板需要的形式。示例中的 Employees in Department (by Tenure) 来源使用 emp_t 作为数据循环名称,并调用 EBA_DEMO_WOODSHR_REPORT.TENURE_YEARS_MONTHS 把入职日期格式化成类似 1 year, 5 months 的任期文本。

select empno,
       ename,
       job,
       eba_demo_woodshr_report.tenure_years_months(hiredate) as tenure
  from emp
 where deptno = :G_DEPTNO_FOR_REPORT
 order by hiredate

辅助函数示例:

-- In package eba_demo_woodshr_report
function tenure_years_months (p_date in date)
  return varchar2
is
  l_months      number;
  l_years       number;
  l_rem_months  number;
begin
  if p_date is null then
    return null;
  end if;

  -- Whole months completed between the dates
  l_months := floor(months_between(trunc(sysdate), trunc(p_date)));

  -- If p_date is in the future, clamp to 0
  if l_months < 0 then
    l_months := 0;
  end if;

  l_years       := trunc(l_months / 12);
  l_rem_months  := mod(l_months, 12);

  return  l_years || ' ' ||
          case when l_years = 1 then 'year' else 'years' end || ', ' ||
          l_rem_months || ' ' ||
          case when l_rem_months = 1 then 'month' else 'months' end;
end;
  • 前提:报表源查询可访问需要的包函数、表和绑定变量。
  • 操作位置:Report Query 的 SQL 来源定义。
  • 验证点:查询列名与模板字段标签一致,格式化后的列值已经适合直接显示。
  • 预期结果:模板不需要承担业务计算,只负责呈现已经整理好的数据。

21.2.6 检查报表查询 JSON#

Report Query 定义页上的 Download 按钮可以下载报表使用的 JSON。每个 Data Loop Name 会成为顶层 JSON 属性,其值是该来源行对象组成的数组。每个行对象中的小写属性名对应报表源列名。

{
  "dep": [
    {
      "deptno": 20,
      "dname": "RESEARCH",
      "loc": "DALLAS"
    }
  ],
  "emp_t": [
    {
      "empno": 7566,
      "ename": "JONES",
      "job": "MANAGER",
      "tenure": "44 years, 6 months"
    },
    {
      "empno": 7788,
      "ename": "SCOTT",
      "job": "ANALYST",
      "tenure": "1 year, 2 months"
    }
  ],
  "emp_n": [
    {
      "empno": 7876,
      "ename": "ADAMS",
      "job": "CLERK",
      "hiredate": "12-JAN-1983"
    },
    {
      "empno": 7788,
      "ename": "SCOTT",
      "job": "ANALYST",
      "hiredate": "14-JUL-2024"
    }
  ]
}
  • 前提:每个报表源已经设置正确的绑定值和数据循环名称。
  • 操作位置:Report Query 定义页。
  • 验证点:JSON 顶层名称、字段名称、数组结构与 Excel 模板标签一致。
  • 预期结果:在测试 PDF 前,可以先用 JSON 明确判断数据契约是否正确。

21.2.7 从页面打印报表#

页面可以通过按钮、应用项和 Print Report 页面进程触发报表生成与下载。示例中,PRINT 按钮与 P35_DEPTNO 选择列表放在同一行,HR 代表先选择部门,再点击 Print and Download Report

图 21-7 用 Universal Theme CSS 类让按钮与同一行的页面项垂直居中。

用户提交页面后,After Submit 计算会把应用项 G_DEPTNO_FOR_REPORT 设置为 P35_DEPTNO 的值。这样报表源查询用绑定变量引用该应用项时,就能拿到用户选择的部门编号。

图 21-8 将用户选择的部门编号写入应用项。

应用项赋值后,Print Report 页面进程使用 Department Employee Tenure 报表查询生成并下载 PDF。文件名可以使用替换字符串,例如把 &P35_DEPTNO. 作为下载文件名的一部分。APEX 也提供原生 Print Report 动态动作,功能相同,只是由页面事件触发,而不是页面提交触发。

图 21-9 使用 Report Query 打印任期报表。
  • 前提:页面上已有部门选择项,报表查询使用的应用项可接收该部门编号。
  • 操作位置:页面设计器中的按钮、计算、页面进程或动态动作。
  • 验证点:提交后应用项值正确,报表查询使用预期部门,文件名替换值生效。
  • 预期结果:用户点击按钮后直接下载指定部门的像素级 PDF。

21.2.8 以用户身份体验报表生成#

从最终用户视角看,流程很短:HR 代表 Susan 打开只对 HR Representatives 可见的 Tenure Report 页面,在列表中选择 RESEARCH 部门,然后点击 Print and Download Report

图 21-10 HR 代表 Susan 为 RESEARCH 部门打印任期报表。

按钮触发后,APEX 会执行报表查询定义中的所有来源查询,把结果格式化为 JSON,把 JSON 和关联布局发送给远程打印服务器,然后下载返回的 PDF 文件。

Susan 打开 PDF 后,可以看到 Woods Clinic Tenure Report 的成品。该 PDF 包含 Employees by Tenure 区域,员工按行列出;也包含 Employees by Name 区域,员工信息按列横向排布。

图 21-11 RESEARCH 部门任期报表的 PDF 输出结果。
  • 前提:页面授权、按钮、计算、报表查询、布局和远程打印服务都已配置完成。
  • 操作位置:运行时应用页面。
  • 验证点:所选部门影响 JSON 和 PDF 内容,下载文件正常打开。
  • 预期结果:最终用户无需理解模板和报表源,只需选择条件并下载成品 PDF。

21.3 使用 Word 模板定制报表#

Report Layout 也可以用来定制交互式报表或经典报表区域的 PDF 下载格式。Excel 示例强调从页面触发自定义文档;Word 示例则强调覆盖报表区域默认的 PDF 输出布局。

21.3.1 用标签在 Word 中格式化数据#

本例用 Word 文档定制 Employee Directory 页上交互式报表的 PDF 下载格式。与 Excel 模板一样,Word 模板使用 Oracle Document Generator 标签,把报表区域的数据映射到文档版式中。

Employee Directory 布局需要一组数据:员工目录信息,短名称为 dir,字段包括 enamejobmgrdeptno

模板中同样使用 {#shortname}{/shortname} 包围数据来源,用 {fieldname} 引用字段值。字段标签名是与布局关联的报表区域列名的小写形式。

Word 文档中的表格可以只保留一行,四个字段分别放在四个单元格中。竖线表示单元格边界:

{#dir}{ename} | {job} | {mgr} | {deptno}{/dir}
图 21-12 Employee Directory 的 Microsoft Word 报表布局文档。
  • 前提:交互式报表区域已有稳定列集合,且 PDF 布局依赖的列不会随意删除。
  • 操作位置:Microsoft Word 模板文件。
  • 验证点:短名称 dir 和字段标签都与后续布局或报表区域数据一致。
  • 预期结果:下载 PDF 时,报表行会按 Word 表格样式重复输出。

21.3.2 设置布局的数据循环名称#

当布局与报表区域配对时,短名称设置在布局定义自身上,而不是在多个 Report Query 来源中逐一设置。示例中的 Employee Directory 布局把 Data Loop Name 设为 dir,以匹配 Word 模板中的标签,并上传 emp_directory.docx 作为模板文件。

图 21-13 在布局定义中指定与模板标签匹配的数据循环名称。
  • 前提:Word 模板已经使用 {#dir}{/dir} 包围重复区域。
  • 操作位置:共享组件中的 Report Layout 定义。
  • 验证点:布局的 Data Loop Namedir,模板文件为预期的 Word 文档。
  • 预期结果:报表区域导出的 JSON 可以被 Word 模板正确遍历。

21.3.3 覆盖报表区域的 PDF 布局#

Employee Directory 页面中,选择交互式报表区域,然后在属性编辑器的 Printing 选项卡中把 Layout 设为 Employee Directory,即可覆盖默认 PDF 格式。

图 21-14 为交互式报表配置自定义 PDF 打印布局。
  • 前提:自定义 Word 布局已经上传,数据循环名称与模板一致。
  • 操作位置:页面设计器中报表区域的 Printing 属性。
  • 验证点:区域 PDF 下载使用自定义布局,而不是默认报表 PDF 样式。
  • 预期结果:用户从报表区域下载 PDF 时得到企业统一格式的文档。

21.3.4 下载定制后的报表 PDF#

配置完成后,用户在交互式报表的 Actions 菜单中选择 Download,再选择 PDF 格式,就会得到像素级精确的 Employee Directory。用户下载前对 Job 列进行筛选、对 Deptno 列进行排序,这些筛选和排序也会影响 PDF 中出现的行以及排序方式。

图 21-15 交互式报表当前筛选和排序会影响自定义 PDF 输出。

用户打开 PDF 后,会看到经过筛选、排序的 Employee Directory 内容,并且这些内容已经套用了自定义的像素级布局。

图 21-16 从 Employee Directory 交互式报表下载的 PDF 成品。
  • 前提:报表区域启用了下载,PDF 格式使用自定义布局。
  • 操作位置:运行时交互式报表的 Actions 菜单。
  • 验证点:当前筛选、排序会反映到下载的 PDF;模板依赖列未被隐藏或移除。
  • 预期结果:用户得到与当前报表视图一致、且采用企业自定义版式的 PDF。