Java 生成「审批归档报告」PDF 示例(Template)
Template2 页 A4 · 44 KB
多页审批归档:流程记录跨页自动带表头,页眉展示审批单号。

输出规格
- PDF 页数
- 2
- 图表类型
- Template
- 模板行数
- 184
- 源码行数
- 106
Java 源码 · JQuickBiz76ApprovalArchive.java
以下代码即生成上方 PDF 的完整测试类,可直接复制运行;Maven 依赖见快速开始章节。
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (c) [2025-2099] Martin ([email protected])
*/
package com.github.paohaijiao.biz.scenario;
import com.github.paohaijiao.config.JPdfConfig;
import com.github.paohaijiao.executor.JQuickPdfFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class JQuickBiz76ApprovalArchive {
private static final String PDF_DIR = "D:/test/pdf/";
private static final String OUT_FILE = PDF_DIR + "76_approval_archive.pdf";
private static final String TEMPLATE = "report/biz/76_approval_archive.txt";
public static void main(String[] args) throws Exception {
new File(PDF_DIR).mkdirs();
// ---------- 1. 组织数据(普通 Map,无实体类)----------
Map<String, Object> data = new LinkedHashMap<String, Object>();
data.put("approvalNo", "OA-2026-09-0247");
data.put("applicant", "刘洋(数据平台部)");
data.put("applyTime", "2026年09月02日 09:12");
data.put("nodeCount", "12 个");
// 流程记录:逐条 bind 为 recN_node / recN_approver / recN_result / recN_opinion / recN_time
List<String[]> records = new ArrayList<String[]>();
records.add(new String[]{"发起申请", "刘洋", "提交", "申请采购 PDF 报表引擎企业授权 5 套", "2026-09-02 09:12"});
records.add(new String[]{"部门经理审批", "王敏", "通过", "业务量增长确实需要扩容,同意采购", "2026-09-02 10:35"});
records.add(new String[]{"财务初审", "赵磊", "驳回", "预算科目填写有误,请调整为「软件购置费-信息化」", "2026-09-02 14:20"});
records.add(new String[]{"申请人修改", "刘洋", "提交", "已按财务意见调整预算科目并补充报价单", "2026-09-03 08:50"});
records.add(new String[]{"财务初审", "赵磊", "通过", "科目已更正,金额与报价单一致", "2026-09-03 09:40"});
records.add(new String[]{"法务审核", "陈静", "通过", "软件许可协议无排他条款,允许签署", "2026-09-03 11:05"});
records.add(new String[]{"IT 架构评审", "孙浩", "通过", "与现有报表平台兼容,无需额外中间件", "2026-09-03 15:30"});
records.add(new String[]{"分管副总审批", "周建国", "转交", "由总经理审批(金额超 20 万)", "2026-09-04 09:00"});
records.add(new String[]{"总经理审批", "李国华", "通过", "同意采购,要求 30 日内完成部署", "2026-09-04 16:45"});
records.add(new String[]{"合同签署", "王敏", "完成", "合同 HT-2026-0918 已用印", "2026-09-08 10:00"});
records.add(new String[]{"付款执行", "财务部", "完成", "首付款 30% 已支付", "2026-09-12 14:00"});
records.add(new String[]{"到货验收", "刘洋", "完成", "5 套授权已激活,验收单 YS-2026-0921", "2026-09-21 11:00"});
String[] keys = {"node", "approver", "result", "opinion", "time"};
for (int i = 0; i < records.size(); i++) {
for (int j = 0; j < keys.length; j++) {
data.put("rec" + (i + 1) + "_" + keys[j], records.get(i)[j]);
}
}
// 申请内容:三段
data.put("applyPara1", "因集团报表业务量增长,现有 3 套 PDF 报表引擎授权已无法满足日均 12 万份报表的生成需求,高峰期排队时间超过 20 分钟,影响业务部门早会数据获取。");
data.put("applyPara2", "本次申请采购企业级授权 5 套,供应商为 ××数字科技有限公司,单价 42,000 元,总价 210,000 元,含一年技术支持与两次现场培训。采购方式为单一来源采购,理由为现有系统基于该引擎开发,更换供应商需重构 300 余个报表模板,成本远高于本次采购。");
data.put("applyPara3", "预计扩容后日均处理能力提升至 30 万份,高峰排队时间可控制在 2 分钟以内。");
// 附件清单
List<String> attachments = Arrays.asList(
"《PDF 报表引擎采购申请单》",
"供应商正式报价单(BJ-2026-0912-0031)",
"单一来源采购论证意见",
"IT 架构评审记录",
"合同 HT-2026-0918 扫描件(已用印)",
"验收单 YS-2026-0921");
for (int i = 0; i < attachments.size(); i++) {
data.put("att" + (i + 1), attachments.get(i));
}
// ---------- 2. 渲染 ----------
JPdfConfig config = new JPdfConfig();
config.getLayoutConfig().setFlexLayout(true);
// 顶部留出 34pt 给页眉
config.getDoc().setMargins(Arrays.asList(58, 50, 55, 50));
config.getHeaderConfig().setEnabled(true);
config.getHeaderConfig().setText("审批单号:" + data.get("approvalNo") + " | OA 审批归档报告");
config.getHeaderConfig().setFontSize(8);
config.getHeaderConfig().setHeight(34);
config.getFooterConfig().setEnabled(true);
config.getFooterConfig().setFontSize(8);
config.getFooterConfig().setPageNumberFormat("第 %d 页");
JQuickPdfFactory factory = new JQuickPdfFactory(config);
factory.bindAll(data);
byte[] pdf = factory.executeResource(TEMPLATE);
FileOutputStream out = new FileOutputStream(OUT_FILE);
out.write(pdf);
out.close();
System.out.println("已生成:" + OUT_FILE);
}
}最小调用(3 行)
不想读完整测试类?核心就是这三行:绑定图表 SVG,执行模板,拿到 PDF 字节流。可直接复制到你的项目里。
// config 可省略;svg 来自 JChartRendererFactory.renderChart(...),见下方完整源码
byte[] pdf = new JQuickPdfFactory(config).executeResource("report/biz/76_approval_archive.txt");模板文件 · 76_approval_archive.txt
类 HTML + 内联 CSS 的模板语法,绑定数据后渲染为 PDF。下方为完整模板,可直接复制,也可下载原始文件。
<pdf>
<body>
<h1 style="fontSize:18pt; bold:true; color:#1F4E79; textAlignment:center; characterSpacing:4pt">'审 批 归 档 报 告'</h1>
<p style="fontSize:8.5pt; color:#5A6B7C; textAlignment:center; marginBottom:8px">'本报告由 OA 系统自动生成,归档后不可修改'</p>
// ---------- 审批概况 ----------
<table style="width:100%; fontSize:9pt">
<tr>
<td style="width:78px; padding:5px; backgroundColor:#E8EDF2; bold:true; border:1px solid #C3CDD8">'审批单号'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${approvalNo}</td>
<td style="width:78px; padding:5px; backgroundColor:#E8EDF2; bold:true; border:1px solid #C3CDD8">'申请人'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${applicant}</td>
</tr>
<tr>
<td style="width:78px; padding:5px; backgroundColor:#E8EDF2; bold:true; border:1px solid #C3CDD8">'申请时间'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${applyTime}</td>
<td style="width:78px; padding:5px; backgroundColor:#E8EDF2; bold:true; border:1px solid #C3CDD8">'流程节点数'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${nodeCount}</td>
</tr>
</table>
// ---------- 一、审批流程记录 ----------
<h2 style="fontSize:11pt; bold:true; color:#1F4E79; borderLeft:3px solid #1F4E79; paddingLeft:6px; marginTop:12px; marginBottom:5px">'一、审批流程记录'</h2>
<table style="width:100%; fontSize:8.5pt">
<tr>
<th style="width:30px; padding:4px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'序号'</th>
<th style="width:100px; padding:4px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'审批节点'</th>
<th style="width:70px; padding:4px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'审批人'</th>
<th style="width:56px; padding:4px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'审批结果'</th>
<th style="width:175px; padding:4px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'审批意见'</th>
<th style="padding:4px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'处理时间'</th>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'1'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec1_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec1_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec1_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec1_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec1_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'2'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec2_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec2_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec2_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec2_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec2_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'3'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec3_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec3_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; color:#B71C1C; border:1px solid #C3CDD8">${rec3_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec3_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec3_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'4'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec4_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec4_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec4_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec4_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec4_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'5'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec5_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec5_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec5_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec5_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec5_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'6'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec6_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec6_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec6_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec6_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec6_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'7'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec7_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec7_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec7_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec7_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec7_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'8'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec8_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec8_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; color:#5A6B7C; border:1px solid #C3CDD8">${rec8_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec8_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec8_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'9'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec9_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec9_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec9_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec9_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec9_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'10'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec10_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec10_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec10_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec10_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec10_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'11'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec11_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec11_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec11_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec11_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec11_time}</td>
</tr>
<tr>
<td style="padding:4px; textAlign:center; border:1px solid #C3CDD8">'12'</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec12_node}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec12_approver}</td>
<td style="padding:4px; textAlign:center; bold:true; border:1px solid #C3CDD8">${rec12_result}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec12_opinion}</td>
<td style="padding:4px; border:1px solid #C3CDD8">${rec12_time}</td>
</tr>
</table>
// ---------- 二、申请内容 ----------
<h2 style="fontSize:11pt; bold:true; color:#1F4E79; borderLeft:3px solid #1F4E79; paddingLeft:6px; marginTop:12px; marginBottom:5px">'二、申请内容'</h2>
<p style="fontSize:9.5pt; color:#333333; textIndent:19pt; marginBottom:4px">${applyPara1}</p>
<p style="fontSize:9.5pt; color:#333333; textIndent:19pt; marginBottom:4px">${applyPara2}</p>
<p style="fontSize:9.5pt; color:#333333; textIndent:19pt; marginBottom:4px">${applyPara3}</p>
// ---------- 三、附件说明 ----------
<h2 style="fontSize:11pt; bold:true; color:#1F4E79; borderLeft:3px solid #1F4E79; paddingLeft:6px; marginTop:12px; marginBottom:5px">'三、附件说明'</h2>
<table style="width:100%; fontSize:9pt">
<tr>
<th style="width:36px; padding:5px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'序号'</th>
<th style="width:300px; padding:5px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'附件名称'</th>
<th style="padding:5px; backgroundColor:#1F4E79; color:white; textAlign:center; border:1px solid #1F4E79">'归档状态'</th>
</tr>
<tr>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'1'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${att1}</td>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'已归档'</td>
</tr>
<tr>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'2'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${att2}</td>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'已归档'</td>
</tr>
<tr>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'3'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${att3}</td>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'已归档'</td>
</tr>
<tr>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'4'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${att4}</td>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'已归档'</td>
</tr>
<tr>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'5'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${att5}</td>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'已归档'</td>
</tr>
<tr>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'6'</td>
<td style="padding:5px; border:1px solid #C3CDD8">${att6}</td>
<td style="padding:5px; textAlign:center; border:1px solid #C3CDD8">'已归档'</td>
</tr>
</table>
<div style="marginTop:14px">
<p style="fontSize:8.5pt; color:#8a939b">'归档信息:本报告与流程审批记录一并存入电子档案系统,保存期限 10 年,调阅需经档案管理员授权。'</p>
</div>
</body>
</pdf>同分组示例
本页 PDF、预览图、模板与源码均由 JQuick-Pdf 真实运行产出,可自由复制到你的项目中使用。