springboot集成Freemarker实现表格自动跨行跨列

本文主要讲解如何通过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.效果展示

在这里插入图片描述

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); // immediateFlush设置true和false都可以,false 可以使用 relayout
            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;//解决word-break: break-all;不兼容的问题,解决纯英文或数字不自动换行的问题
                }
            });
            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();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot_Freemarker生成Word_多个表格+两层嵌套循环; 步骤说明: 1.用Microsoft Office Word打开word原件;将文档中需要动态生成的内容,替换为属性名 ${name} 2.另存为,选择保存类型Word 2003 XML 文档(*.xml) 3.用Firstobject free XML editor打开文件,选择Tools下的Indent【或者按快捷键F8】格式化文件内容。左边是文档结构,右边是文档内容; 4. 文档生成后有时需要手动修改,查找第一步中设置的属性名,可能会产生类似${n.....ame}类似的样子,我们将将名字中间的标签删掉,恢复为${name} 5. word模板中有表格,需要循环的位置, 用 标签将第二对 标签(即除表头的w:tr标签后的一对)包围起来 同时表格内的属性例如${name},在这里需要修改为${user.name} (userList是集合在dataMap中的key, user是集合中的每个元素, 类似), 如图: PLUS:若表格之外还有嵌套的循环,也需要用,注意这里的标签不要和某对其他标签交叉,不可以出现这种 6. 标识替换完之后,另存为.ftl后缀文件即可。 代码里是相对有一丢丢复杂的,两层嵌套循环; 总(dataMap) deptName 部门名 list(Table)表的集合 table1(map) table-名字 ${map.table} tableName-中文名 ${map.tableName} columnCount-字段数 ${map.columnCount} recordCount-记录数 ${map.recordCount} listA-List--表格1 map.listA column Model属性——字段名 ${model.column} columnName Model属性——字段中文名 ${model.column} rate Model属性——字段占比 ${model.rate} nullValueCount Model属性——字段空值数 ${model.nullValueCount} listB-List--表格2 map.listB …… listC-List--表格3 map.listC …… table2 table-名字 ${map.table} tableName-中文名 ${map.tableName} columnCount-字段数 ${map.columnCount} recordCount-记录数 ${map.recordCount} listA-List--表格1 map.listA column Model属性——字段名 ${model.column} columnName Model属性——字段中文名 ${model.column} rate Model属性——字段占比 ${model.rate} nullValueCount Model属性——字段空值数 ${model.nullValueCount} listB-List--表格2 map.listB …… listC-List--表格3 map.listC …… table3 ……
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值