FreeMarker 的基本使用

后端代码编写

1、导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    <version>2.3.2.RELEASE</version>
</dependency>

2、创建工具类

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.Map;

public class WordUtils { 
    /**
     * 生成 word 文档方法
     *
     * @param dataMap      要填充的数据
     * @param templateName 模版名称
     * @param fileName     要输出的文件路径
     * @throws Exception 抛出的异常
     */
    public static void generateWord(Map<String, Object> dataMap, String templateName, HttpServletResponse response) throws Exception { 
        // 设置FreeMarker的版本和编码格式
        Configuration configuration = new Configuration(new Version("2.3.28"));
        configuration.setDefaultEncoding("UTF-8");
        // 此处把模版文件都放在 resources 下的 templates 中
        configuration.setClassForTemplateLoading(WordUtils.class, "/templates");
        // 设置 FreeMarker 生成 Word 文档所需要的模板
        Template template = configuration.getTemplate(templateName, "UTF-8");
        // 创建文件输出流
        ByteArrayOutputStream fileStream = new ByteArrayOutputStream();
        // 创建一个 Word 文档的输出流
        Writer writer = new OutputStreamWriter(fileStream, "UTF-8");
        // FreeMarker 使用 Word 模板和数据生成 Word 文档
        template.process(dataMap, writer);
        writer.flush();
        writer.close();
        // 返回到前端
        try (OutputStream out = response.getOutputStream()) { 
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/msexcel");
            response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode( "周报.doc", "UTF-8"));
            out.write(fileStream.toByteArray());
            out.flush();
        } 
    } 
} 

3、将内容写入到文档中

Map<String, Object> params = new HashMap<>();
params.put("name", "xiaojiang");
// 生成 word 文档
WordUtils.generateWord(params, "user.ftl", response);

4、转为 pdf

<!-- word 转 pdf -->
<dependency>
    <groupId>com.documents4j</groupId>
    <artifactId>documents4j-local</artifactId>
    <version>1.0.3</version>
</dependency>
<dependency>
    <groupId>com.documents4j</groupId>
    <artifactId>documents4j-transformer-msoffice-word</artifactId>
    <version>1.0.3</version>
</dependency>
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version>
</dependency>
public static void generateWord(Map<String, Object> dataMap, String templateName, HttpServletResponse response, String fileName) throws Exception { 
    // 生成 word 的代码
    // 将 word 输出流转为输入流,转为pdf
    InputStream in = new ByteArrayInputStream(fileStream.toByteArray());
    documents4jWordToPdf(in, "D:/test.pdf");
} 


public static void documents4jWordToPdf(InputStream docxInputStream, String targetPath) { 
    File outputFile = new File(targetPath);
    try  { 
        OutputStream outputStream = new FileOutputStream(outputFile);
        IConverter converter = LocalConverter.builder().build();
        converter.convert(docxInputStream)
            .as(DocumentType.DOCX)
            .to(outputStream)
            .as(DocumentType.PDF).execute();
        outputStream.close();
    }  catch (Exception e) { 
    } 
} 

文档模板的创建

1、创建 word 模板和表格
2、将 word 文件另存为 .xml 格式的文件,然后修改为 .ftl 后缀
3、在 IDEA 中打开该文件并格式化文件,会发现 word 其实就是通过 xml 标签进行设置的
image.png
4、找到需要替换的文字部分,可以直接搜索文字,使用 EL 表达式和变量进行替换
image.png

FreeMarker 常用标签

1、遍历列表,下标通过 k_index 获取

<#list list as k>
    <w:r>
        <w:rPr>
            <w:rFonts w:ascii="宋体" w:eastAsia="宋体" w:hAnsi="宋体" w:cs="宋体"/>
            <w:sz w:val="24"/>
            <w:szCs w:val="24"/>
        </w:rPr>
        <w:t>${ k_index + 1} . ${ k.amount} </w:t>
    </w:r>
</#list>

2、判断

<#if k.amount != 0>
    <w:r>
        <w:rPr>
            <w:rFonts w:ascii="宋体" w:eastAsia="宋体" w:hAnsi="宋体" w:cs="宋体"/>
            <w:sz w:val="24"/>
            <w:szCs w:val="24"/>
        </w:rPr>
        <w:t>(${ k.amount} </w:t>
    </w:r>
<#else>
    <!-- 其他显示内容 -->
</#if>

3、合并上下单元格,一般写在单元格的列头 <w:tcPr> 中,有些版本使用的是 w:vmerge 标签

<!-- 合并单元格的开始 -->
<w:vMerge w:val="restart"/>        

<!-- 被上一个单元格合并 -->
<w:vMerge w:val="continue"/>

4、定义变量

<!-- 定义变量 -->
<#assign pre = "">

<!-- 赋值给变量 -->
<#assign pre = "name">

5、合并内容相同的上下单元格

<#assign pre = "">
<#list tensList as t>
    <w:tr w:rsidR="00B7397A">
        <w:tc>
            <w:tcPr>
                <w:tcW w:w="787" w:type="dxa"/>
                <w:vAlign w:val="center"/>
                <#if t.deptName != pre>
                    <w:vMerge w:val="restart"/>
                <#else>
                    <w:vMerge w:val="continue"/>
                </#if>
                <#assign pre = t.deptName>
            </w:tcPr>
            <w:p w:rsidR="00B7397A" w:rsidRDefault="0057625A">
                <w:pPr>
                    <w:spacing w:line="560" w:lineRule="exact"/>
                    <w:rPr>
                        <w:rFonts w:ascii="黑体" w:eastAsia="黑体" w:hAnsi="黑体" w:cs="仿宋_GB2312"/>
                        <w:sz w:val="24"/>
                        <w:szCs w:val="24"/>
                    </w:rPr>
                </w:pPr>
                <w:r>
                    <w:rPr>
                        <w:rFonts w:ascii="黑体" w:eastAsia="黑体" w:hAnsi="黑体" w:cs="仿宋_GB2312"
                                  w:hint="eastAsia"/>
                        <w:sz w:val="24"/>
                        <w:szCs w:val="24"/>
                    </w:rPr>
                    <w:t>${ t.deptName} </w:t>
                </w:r>
            </w:p>
        </w:tc>
    </w:tr>
</#list>

image.png
6、调整导出 word 页面方向(横向或纵向)
搜索w:sectPr 标签,一般位于内容后面
image.png
用于设置以上页面的方向,纵向设置

<w:sectPr w:rsidR="00334CFE">
    <w:pgSz w:w="11906" w:h="16838"/>
    <w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851"
             w:footer="992" w:gutter="0"/>
    <w:cols w:space="425"/>
    <w:docGrid w:type="lines" w:linePitch="312"/>
</w:sectPr>

横向设置

<w:sectPr w:rsidR="00334CFE">
    <w:pgSz w:w="16838" w:h="11906"/>
    <w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851"
             w:footer="992" w:gutter="0"/>
    <w:cols w:space="425"/>
    <w:docGrid w:type="lines" w:linePitch="312"/>
</w:sectPr>