本文主要讲解如何通过ftl模版语言实现数据表格数据合并,跨行跨列展示
废话不多说,直接上代码
1.数据说明:
1.会有多个检测房间
2.每个检测房间有多个检测区域
3.每个区域有多个检测点
4.每个检测点有多个指标
{
"dataMap":{
"title":"xx医疗检测报告",
"approvalList":[],
"list":[
{
"index":1,
"room":"洁净室1",
"roomArea":"洁净间2",
"address":"124_测量点1",
"name":"温度",
"value1":18,
"value2":"18~28",
"result":"合格",
"unit":"°C"
},
{
"index":2,
"room":"洁净室1",
"roomArea":"洁净间2",
"address":"124_测量点1",
"name":"相对湿度",
"value1":46,
"value2":"45~65",
"result":"合格",
"unit":"%"
},
{
"index":3,
"room":"洁净室1",
"roomArea":"洁净间2",
"address":"124_测量点2",
"name":"温度",
"value1":0,
"value2":"18~28",
"result":"不合格",
"unit":"°C"
},
{
"index":4,
"room":"洁净室1",
"roomArea":"洁净间2",
"address":"124_测量点2",
"name":"相对湿度",
"value1":0,
"value2":"45~65",
"result":"不合格",
"unit":"%"
},
{
"index":5,
"room":"洁净室1",
"roomArea":"洁净间3",
"address":"1201_测量点2",
"name":"温度",
"value1":"61.48",
"value2":"18~28",
"result":"不合格",
"unit":"°C"
},
{
"index":6,
"room":"洁净室1",
"roomArea":"洁净间3",
"address":"1201_测量点2",
"name":"相对湿度",
"value1":"27.12",
"value2":"45~65",
"result":"不合格",
"unit":"%"
},
{
"index":7,
"room":"洁净室1",
"roomArea":"洁净间3",
"address":"1201_测量点1",
"name":"温度",
"value1":"61.31",
"value2":"18~28",
"result":"不合格",
"unit":"°C"
},
{
"index":8,
"room":"洁净室1",
"roomArea":"洁净间3",
"address":"1201_测量点1",
"name":"相对湿度",
"value1":"27.16",
"value2":"45~65",
"result":"不合格",
"unit":"%"
}
],
"date":"2023年09月19日",
"time":"19:06:33",
"device":"",
"address":"朝阳区",
"room":"洁净室1"
}
}
2.效果展示
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/dbb8c03924585c98422b366d13cd4262.png)
3.模版文件test.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="Content-Style-Type" content="text/css"/>
<title></title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
html, body, #main {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
#main{
background-color: ${backgroundColor};
}
table {
border-collapse: collapse;
table-layout: fixed;
}
td {
border: 1px solid;
padding: 5px auto;
text-align: center;
word-break: break-word;
}
.fontRed{
color:red
}
.footer {
margin-top: 20px;
width: 50%;
float: right;
}
.footer div {
margin-bottom: 5px;
height: 120px;
}
.footer div span{
display: inline-block;
vertical-align: middle;
margin-right: 15px;
line-height: 120px;
}
.footer div img{
display: inline-block;
vertical-align: middle;
line-height: 120px;
}
img {
width: 100px;
height: 100px;
}
.subtitle{
text-align: right;
margin-bottom: 5px;
}
.subtitle div{
display:inline-block;
margin-right: 20px;
}
</style>
</head>
<body style="background:red">
<div id="main">
<h1 style="text-align: center; margin-top:50px;margin-bottom: 50px">
<span class="ql-bold-700">${title}</span></h1>
<br>
<div class="subtitle">
<div>
<#if startUser??>
<span >发起人:</span> <span style="display:inline-block;width: 100px !important;text-align: center" >${startUser} </span>
<#else>
<span style="margin-right:150px" >发起人:</span>
</#if>
</div>
<div>
<span>报告日期:</span> <span style="display:inline-block;width: 100px !important;" >${startTime} </span>
</div>
</div>
<table style="border-collapse: collapse;border:1px solid #ffffff; width: 100.065%;" border="0">
<tbody>
<tr>
<td style="width: 5%;">序号</td>
<td style="width: 10%;">检测房间</td>
<td style="width: 10%;">检测房间区域</td>
<td style="width: 15%;">检测地址</td>
<td style="width: 15%;">检测项</td>
<td style="width: 15%;">检测日期</td>
<td style="width: 10%;">国际值</td>
<td style="width: 10%;">实际值</td>
<td style="width: 10%;">检测结果</td>
</tr>
<#if list?? && (list?size > 0)>
<#assign rowmap={}>
<#list list as item>
<#assign roomkey = item.room?string>
<#assign roomAreakey = roomkey+"##"+item.roomArea>
<#assign addresskey = roomAreakey+"##"+item.address>
<#if item.room?has_content && item.roomArea?has_content && item.address?has_content>
<#if rowmap[roomkey]??>
<#assign rowmap+={roomkey:rowmap[roomkey] + 1}>
<#else>
<#assign rowmap+={roomkey: 1}>
</#if>
<#if rowmap[roomAreakey]??>
<#assign rowmap+={roomAreakey:rowmap[roomAreakey] + 1}>
<#else>
<#assign rowmap+={roomAreakey:1}>
</#if>
<#if rowmap[addresskey]??>
<#assign rowmap+={addresskey:rowmap[addresskey] + 1}>
<#else>
<#assign rowmap+={addresskey:1}>
</#if>
</#if>
</#list>
<#list list as itemx>
<#assign roomKeyX = itemx.room>
<#assign roomAreaKeyX = roomKeyX+"##"+itemx.roomArea>
<#assign addressKeyX = roomAreaKeyX+"##"+itemx.address>
<tr>
<td >${itemx.index}</td>
<#if rowmap[roomKeyX]!=-1>
<td rowspan="${rowmap[roomKeyX]}">${itemx.room}</td>
</#if>
<#if rowmap[roomAreaKeyX]!=-1>
<td rowspan="${rowmap[roomAreaKeyX]}">${itemx.roomArea}</td>
</#if>
<#if rowmap[addressKeyX]!=-1>
<td rowspan="${rowmap[addressKeyX]}">${itemx.address}</td>
</#if>
<td>${itemx.name}</td>
<td>${itemx.value1}</td>
<td>${itemx.value2}</td>
<td>${itemx.result}</td>
<td>${itemx.unit}</td>
</tr>
<#assign rowmap+={roomKeyX: -1}>
<#assign rowmap+={roomAreaKeyX: -1}>
<#assign rowmap+={addressKeyX: -1}>
</#list>
</#if>
</tbody>
</table>
<div class="footer">
<#if approvalList?? && (approvalList?size > 0)>
<#list approvalList as node>
<div>
<span>审核人:</span>
<span>${node.name!}</span>
<span>盖章:</span>
<img src="${node.url!}" />
<span>审核日期:</span>
<span>${node.time!}</span>
</div>
</#list>
<#else>
<div>
<span style="margin-right: 150px">签名(盖章):</span>
<span >审核日期:</span>
</div>
</#if>
</div>
<div style="clear:both;"></div>
</div>
</body>
</html>
4.java工具类
import cn.hutool.core.util.ObjectUtil;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.io.font.otf.GlyphLine;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.BlockElement;
import com.itextpdf.layout.element.IElement;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.layout.property.Property;
import com.itextpdf.layout.splitting.DefaultSplitCharacters;
import com.itextpdf.text.DocumentException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
public class HtmlToPdfUtils {
public static final float topMargin = 114f;
public static final float H_TOP_MARGIN = 10f;
public static final float bottomMargin = 156f;
public static final float H_BOTTOM_MARGIN = 10f;
public static final float leftMargin = 10f;
public static final float rightMargin = 10f;
private static final String SHIPPED_FONT_RESOURCE_PATH = Objects.requireNonNull(HtmlToPdfUtils.class.getResource("/fonts/simhei.ttf")).toString();
public static byte[] convert(String html, PrintDirection printDirection) throws IOException, DocumentException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(outputStream);
PdfDocument pdfDocument = new PdfDocument(writer);
try {
ConverterProperties props = new ConverterProperties();
FontProvider fp = new FontProvider();
fp.addStandardPdfFonts();
FontProgram fontProgram = FontProgramFactory.createFont(SHIPPED_FONT_RESOURCE_PATH);
fp.addFont(fontProgram);
props.setFontProvider(fp);
List<IElement> iElements = HtmlConverter.convertToElements(html, props);
PageSize pageSize = new PageSize(PageSize.A4);
if (ObjectUtil.isNotEmpty(printDirection) && printDirection == PrintDirection.HORIZONTAL) {
pageSize = pageSize.rotate();
}
Document document = new Document(pdfDocument, pageSize, true);
if (ObjectUtil.isNotEmpty(printDirection) && printDirection == PrintDirection.HORIZONTAL) {
document.setMargins(H_TOP_MARGIN, rightMargin, H_BOTTOM_MARGIN, leftMargin);
} else {
document.setMargins(topMargin, rightMargin, bottomMargin, leftMargin);
}
document.setProperty(Property.SPLIT_CHARACTERS, new DefaultSplitCharacters() {
@Override
public boolean isSplitCharacter(GlyphLine text, int glyphPos) {
return true;
}
});
for (IElement iElement : iElements) {
BlockElement blockElement = (BlockElement) iElement;
blockElement.setMargins(1, 0, 1, 0);
document.add(blockElement);
}
document.close();
return outputStream.toByteArray();
} catch (Exception e) {
throw e;
} finally {
writer.close();
pdfDocument.close();
outputStream.close();
}
}
}