最近项目中需要将查询出来的表格数据以PDF形式导出,并且表格的形式包含横向行与纵向列的单元格合并操作,导出的最终效果如图所示:

首先引入操作依赖

com.itextpdf

itextpdf

5.5.10

com.itextpdf

itext-asian

5.2.0

最上面的基本信息是固定死的就是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 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> 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 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> doList = new ArrayList<>();

这个是我构建底下表格数据的格式,举个例子,类似于:

List> 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, Font fontChinese, PdfPTable table2) {

List> aa = new ArrayList<>();

int length = list.get(0).size();

//循环在外层,这里代表的是有几列(其实是固定的,因为上面构建的时候,列数就是三列)

//下面的循环不用管,是将你组装的数据设置到单元格里

for (int i = 0; i < list.size(); i++) {

List strings = list.get(i);

int colNum = 1;

List 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 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的类很强大,基本上你想怎么导出,都可以进行调整

好文推荐

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。