最近项目中需要将查询出来的表格数据以PDF形式导出,并且表格的形式包含横向行与纵向列的单元格合并操作,导出的最终效果如图所示:
首先引入操作依赖
<!--导出pdf所需包-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
最上面的基本信息是固定死的就是4*4的表格,这个创建起来 就比较简单,主要是下面这个表格,需要从数据库查出数据并循环进行展示,并且内容相同的列要进行合并。直接展示代码:
主类对外调用方法:
@Operation(summary = "导出PDF")
@PostMapping("/download")
@SneakyThrows(Exception.class)
public void download(Long id,HttpServletResponse response, HttpServletRequest request) {
// 防止日志记录获取session异常
request.getSession();
// 设置编码格式
response.setContentType("application/pdf;charset=UTF-8");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("调试PDF", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".pdf");
contractDemandThreeRequestBaseService.download(id,response);
}
具体实现类:
@Override
public void download(Long id, HttpServletResponse response) {
//要下载的数据查询数据部分我去掉了有需要自己根据业务取
ContractDemandThreeRequestBaseDO baseDO = contractDemandThreeRequestBaseMapper.selectById(id);
ContractDemandThreeRequestBaseRespVO base = ContractDemandThreeRequestBaseConvert.INSTANCE.convert(baseDO);
base.setDeptName(ObjectUtil.isEmpty(deptService.getDept(base.getDeptId())) ? null : deptService.getDept(base.getDeptId()).getName());
base.setProjectLeaderName(ObjectUtil.isEmpty(userService.getUser(base.getProjectLeaderId())) ? null : userService.getUser(base.getProjectLeaderId()).getNickname());
//子表数据
List<ContractDemandThreeQuestionDO> details = contractDemandThreeQuestionMapper.
selectList(Wrappers.lambdaQuery(ContractDemandThreeQuestionDO.class).eq(ContractDemandThreeQuestionDO::getDemandId, id));
//下面进行表格的创建、字体设置、合并单元格
// 定义全局的字体静态变量
Font titlefont;
Font headfont;
Font keyfont = null;
Font textfont = null;
Font content = null;
BaseFont bfChinese = null;
// 最大宽度
try {
// 不同字体(这里定义为同一种字体:包含不同字号、不同style)这里我用的最后一个 content字体
bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
titlefont = new Font(bfChinese, 16, Font.BOLD);
headfont = new Font(bfChinese, 14, Font.BOLD);
keyfont = new Font(bfChinese, 10, Font.BOLD);
textfont = new Font(bfChinese, 15, Font.NORMAL);
content = new Font(bfChinese, 10, Font.NORMAL);
} catch (Exception e) {
e.printStackTrace();
}
BaseFont bf;
Font font = null;
try {
//创建字体
bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",
BaseFont.NOT_EMBEDDED);
//使用字体并给出颜色
font = new Font(bf, 20, Font.BOLD, BaseColor.BLACK);
} catch (Exception e) {
e.printStackTrace();
}
Document document = new Document();
try {
PdfWriter pdfWriter = PdfWriter.getInstance(document, response.getOutputStream());
//打开生成的pdf文件
document.open();
//设置内容
Paragraph paragraph = new Paragraph("采购需求三问", font);
paragraph.setAlignment(1);
//引用字体
document.add(paragraph);
// 设置表格的列宽和列数
float[] widths = {25f, 25f, 25f, 25f};
PdfPTable table = new PdfPTable(widths);
table.setSpacingBefore(20f);
// 设置表格宽度为100%
table.setWidthPercentage(100.0F);
table.setHeaderRows(1);
table.getDefaultCell().setHorizontalAlignment(1);
PdfPCell cell = null;
//第一行:表格的名字
cell = new PdfPCell(new Paragraph("采购项目名称", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(30);
table.addCell(cell);
//表格里面的值(下面都是同样的操作)
cell = new PdfPCell(new Paragraph(base.getProjectName(), content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
//第二行
cell = new PdfPCell(new Paragraph("项目编号", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(30);
table.addCell(cell);
cell = new PdfPCell(new Paragraph(base.getProjectCode(), content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell = new PdfPCell(new Paragraph("资金类型", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell = new PdfPCell(new Paragraph(getDitcValue("FundType", base.getFundType()), content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
//第三行
cell = new PdfPCell(new Paragraph("支出类别", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(30);
table.addCell(cell);
cell = new PdfPCell(new Paragraph(getDitcValue("ExpenditureCategory", base.getExpenditureCategory()), content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell = new PdfPCell(new Paragraph("预算含税总金额(万元)", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell = new PdfPCell(new Paragraph(String.valueOf(base.getBudgetIncludingTaxTotalMoney()), content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
//第三行
cell = new PdfPCell(new Paragraph("采购内容", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(30);
table.addCell(cell);
cell = new PdfPCell(new Paragraph(base.getProcureContent(), content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
//第四行
cell = new PdfPCell(new Paragraph("需求部门", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(30);
table.addCell(cell);
cell = new PdfPCell(new Paragraph(base.getDeptName(), content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
cell = new PdfPCell(new Paragraph("项目负责人", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(30);
table.addCell(cell);
cell = new PdfPCell(new Paragraph(base.getProjectLeaderName(), content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(cell);
// 设置表格的列宽和列数
float[] widths2 = {25f, 25f, 25f};
PdfPTable table2 = new PdfPTable(widths2);
table2.setSpacingBefore(20f);
// 设置表格宽度为100%
table2.setWidthPercentage(100.0F);
table2.setHeaderRows(1);
table2.getDefaultCell().setHorizontalAlignment(1);
//需求三问详情信息标题栏
cell = new PdfPCell(new Paragraph("需求三问", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(20);
table2.addCell(cell);
cell = new PdfPCell(new Paragraph("选择项", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table2.addCell(cell);
cell = new PdfPCell(new Paragraph("内容项", content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
table2.addCell(cell);
//下面的代码就是样图中底下的表格,需要动态构建数据
//人员列表数据-第五行
List<List<String>> doList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(details)) {
//组装数据
for (ContractDemandThreeQuestionDO detail : details) {
String natureType = "项目" + getDitcValue("QuestionType", detail.getDemandNatureType());
String deviceGoodsType = getDitcValue(baseDO.getModelType(), detail.getItemValue());
doList.add(Arrays.asList(natureType, deviceGoodsType, detail.getItemContent()));
}
makeData(doList, content, table2);
//为两个表格添加标题
document.add(new Paragraph("\n"));
document.add(new Paragraph("▋ 基本信息", content));
document.add(new Paragraph("\n"));
document.add(table);
document.add(new Paragraph("\n"));
document.add(new Paragraph("▋ 采购需求三问内容", content));
document.add(new Paragraph("\n"));
//将table2中数据相同的列合并单元格(横向合并)
document.add(table2);
// 加水印(水印组成:下载人姓名-手机号-部门)
PdfContentByte waterMar = pdfWriter.getDirectContentUnder();
AtomicReference<String> text = new AtomicReference<>("");
Long loginUserId = getLoginUserId();
AdminUserRedisVO adminUserRedisVO = adminUserRedisDAO.get(loginUserId);
Optional.ofNullable(adminUserRedisVO).ifPresent(vo -> {
DeptDO dept = deptService.getDept(adminUserRedisVO.getDeptId());
String deptName = Objects.nonNull(dept) ? dept.getName() : "";
text.set(vo.getNickname() + "-" + vo.getMobile() + "-" + deptName);
});
addTextFullWaterMark(waterMar, text.get(), bfChinese);
//关闭文档
document.close();
} catch (DocumentException | IOException e) {
e.printStackTrace();
log.error("导出pdf失败:{}", e);
}
}
补充说明:List<List<String>> doList = new ArrayList<>();
这个是我构建底下表格数据的格式,举个例子,类似于:
List<List<String>> headList = new ArrayList<>(); headList.add(Arrays.asList(new String[]{"1", "2", "3"})); headList.add(Arrays.asList(new String[]{"2", "6", "10"})); headList.add(Arrays.asList(new String[]{"1", "12", "13"})); headList.add(Arrays.asList(new String[]{"2", "9", "11"})); headList.add(Arrays.asList(new String[]{"1", "8", "12"}));
根据自己的实际业务构建,我底下的表格是一个n*3的表格,只有三列,所以数据结构就相当于上面的两层List结构,最外层的集合代表有多少行,嵌套的结合相当于多少列(这里就是三列),我将对象集合查询出来后使用循环,将我所用到的数据组装成这个示例的样子。
重点来了,以下是合并单元格的方法:
makeData(doList, content, table2);
/**
* 合并单元格方法
*
* @param list 表头数据 list中相连下标位置内容如果相同自动合并 上下位置内容相同自动合并
* @param fontChinese 支持转换中文的Font对象
* @return
*/
private void makeData(List<List<String>> list, Font fontChinese, PdfPTable table2) {
List<List<PdfPCell>> aa = new ArrayList<>();
int length = list.get(0).size();
//循环在外层,这里代表的是有几列(其实是固定的,因为上面构建的时候,列数就是三列)
//下面的循环不用管,是将你组装的数据设置到单元格里
for (int i = 0; i < list.size(); i++) {
List<String> strings = list.get(i);
int colNum = 1;
List<PdfPCell> bb = new ArrayList<>();
for (int j = 0; j < strings.size(); j++) {
if (j + 1 < strings.size()) {
if (strings.get(j).equals(strings.get(j + 1))) {
colNum++;
} else {
PdfPCell cell = new PdfPCell();
//合并列
cell.setColspan(colNum);
Paragraph elements = new Paragraph(strings.get(j), fontChinese);
elements.setAlignment(1);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(30);
cell.addElement(elements);
bb.add(cell);
for (int a = 1; a < colNum; a++) {
bb.add(null);
}
colNum = 1;
}
} else {
PdfPCell cell = new PdfPCell();
cell.setColspan(colNum);
Paragraph elements = new Paragraph(strings.get(j), fontChinese);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
elements.setAlignment(1);
cell.setFixedHeight(30);
cell.addElement(elements);
bb.add(cell);
for (int a = 1; a < colNum; a++) {
bb.add(null);
}
colNum = 1;
}
}
aa.add(bb);
}
//合并算法
for (int i = 0; i < length; i++) {
int rowSpan = 1;
for (int j = 0; j < aa.size(); j++) {
if (aa.get(j).get(i) == null) {
continue;
}
if (j + 1 < aa.size()) {
if (aa.get(j + 1).get(i) != null
&& aa.get(j).get(i).getCompositeElements().get(0).toString()
.equals(aa.get(j + 1).get(i).getCompositeElements().get(0).toString())
) {
rowSpan++;
} else {
aa.get(j - rowSpan + 1).get(i).setRowspan(rowSpan);
for (int a = 1; a < rowSpan; a++) {
aa.get(j - rowSpan + 1 + a).set(i, null);
}
rowSpan = 1;
}
} else {
aa.get(j - rowSpan + 1).get(i).setRowspan(rowSpan);
for (int a = 1; a < rowSpan; a++) {
aa.get(j - rowSpan + 1 + a).set(i, null);
}
rowSpan = 1;
}
}
break;
}
for (List<PdfPCell> a : aa) {
for (PdfPCell pCell : a) {
if (pCell != null) {
table2.addCell(pCell);
}
}
}
}
这里将合并算法进行一个说明:外层循环其实还就是表示列,我这里是三列,他会依次循环三列,并将横向与纵向的表格中有相同数据的都进行合并,也就是值相同的行进行合并,值相同的列也进行合并。但我的业务要求就是只合并第一列,所以我这里也没有改算法,偷了个懒,直接在第一次循环后加了break,直接终止后面的循环。这个自己看自己的业务要求。代码里的getDictValue方法是我自己获取字典值的方法,可自行定义
加水印:
public static void addTextFullWaterMark(PdfContentByte waterMar, String text, BaseFont bf) {
waterMar.beginText();
PdfGState gs = new PdfGState();
// 设置填充字体不透明度为0.2f
gs.setFillOpacity(0.2f);
waterMar.setFontAndSize(bf, 20);
// 设置透明度
waterMar.setGState(gs);
// 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度
for (int x = 0; x <= 900; x += 200) {
for (int y = -50; y <= 800; y += 200) {
waterMar.showTextAligned(Element.ALIGN_RIGHT, text, x, y, 35);
}
}
// 设置水印颜色
waterMar.setColorFill(BaseColor.GRAY);
//结束设置
waterMar.endText();
waterMar.stroke();
}
这个操作PDF的类很强大,基本上你想怎么导出,都可以进行调整