前言

在日常的开发工作中,我们时常会遇到导出Word文档报表的需求,比如公司的财务报表、医院的患者统计报表、电商平台的销售报表等等。

导出Word方式多种多样,通常有以下几种方式:

1. 使用第三方Java工具类库Hutool的Word工具类,参考网址为https://www.hutool.cn/docs/#/poi/Word生成-Word07Writer;

2. 利用Apache POI和FreeMarker模板引擎;

3. 第三方报表工具。

上面的几种方式虽然可以实现Word文档的导出,但有以下缺点

第一种方式操作简单,但也只能生成简单的Word文档,无法生成有表格的Word文档;

第二种方式可以生成复杂的Word文档,但是还要进行Word转xml,xml转ftl的双重转换,不适合内容经常变更的Word文档;

第三种方式有时候不适合对格式要求严格的文档。

那么,有没有既简单又高效的导出Word的方法呢?答案是肯定有的。接下来我就来介绍一种用Java语言实现的,通过XDocReportFreeMarker模板引擎生成Word文档的方法。

准备环境

开发语言:

Java7及以上的版本。

开发工具:

Eclipse/Idea。

第三方依赖库:

XDocReport、POI、Freemarker。

模板语言:

FreeMarker。

Word编辑器:

Microsoft 365或其他版本较高的Word编辑器。

示例Word模板

制作模板

Word模板如上图,可以看到,结构比较简单,包括两个部分,第一部分是纯文字和数字,第二部分主要是表格。我们在实际的开发过程中生成的报表几乎都是动态生成的,所以模板中的数字和表格里的数据都要替换成我们后台的实际数据。

替换Word模板中的动态变量,我们需要掌握两个知识点:

1.Word文档中的Word域,word域是引导Word在文档中自动插入文字、图形、页码或其他信息的一组代码。在这里我们可以把         Word域理解成标识符,这个标识符表示Word文档中要被替换的内容;

2.FreeMarker模板下的变量表达式,比如用${city}替换Word示例模板中的北京市。

了解了以上两个概念后,我们就可以动手编辑Word模板了,步骤如下:

1. 首先在Word模板中选中要替换的文本,在这儿拿标题中的"北京市"为例,然后键盘使用 Ctrl + F9 组合键将其设置为域,此时文本会被"{}"包围,接着鼠标右键选择【编辑域(E)...】:

2. 在弹出的对话框中,类别选择“邮件合并”,域名选择 "MergeField",域属性中的域名填入模版表达式${city},点击【确定】按钮:

3. 编辑后的效果如下:

4. 掌握替换文本的方法后,我们可以把Word模板第一部分需要替换的内容都替换成模板变量:

Word模板中表格数据的处理

表格中的数据实质上就是对集合的遍历。

表格数据的处理其实和上面对文本内容的处理是类似的,只不过要在Word模板中加上集合的变量,Java代码中也要有对集合进行特对的处理(这个在后面的代码展示部分会说)。

具体操作步骤如下:

1. 选定表格中要替换的文本,然后键盘使用 Ctrl + F9 组合键将其设置为域,接着鼠标右键选择【编辑域(E)...】:

2. 在弹出的对话框中,类别选择“邮件合并”,域名选择 "MergeField",域属性中的域名填入模版表达式${goods.num},点击【确定】按钮;

3. 重复步骤2,替换表格中的其他文本内容:

后台代码

添加依赖到pom.xml文件

<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.1</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.1</version>
</dependency>
<dependency><groupId>org.jxls</groupId><artifactId>jxls</artifactId><version>2.6.0</version><exclusions><exclusion><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>org.jxls</groupId><artifactId>jxls-poi</artifactId><version>1.2.0</version>
</dependency>
<dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.core</artifactId><version>2.0.2</version>
</dependency>
<dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.document</artifactId><version>2.0.2</version>
</dependency>
<dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.template</artifactId><version>2.0.2</version>
</dependency>
<dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.document.docx</artifactId><version>2.0.2</version>
</dependency>
<dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId><version>2.0.2</version>
</dependency>
<dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.23</version>
</dependency>
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.5</version>
</dependency>

编写Java代码

package com.tzsj.test;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
import io.renren.entity.Goods;@Controller
@RequestMapping("/word")
public class WordTest {@Testpublic void test() throws IOException, XDocReportException {generateWord();}public void generateWord() throws IOException, XDocReportException {//获取Word模板,模板存放路径在项目的resources目录下InputStream ins = this.getClass().getResourceAsStream("/模板.docx");//注册xdocreport实例并加载FreeMarker模板引擎IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins,     TemplateEngineKind.Freemarker);//创建xdocreport上下文对象IContext context = report.createContext();//创建要替换的文本变量context.put("city", "北京市");context.put("startDate", "2020-09-17");context.put("endDate", "2020-10-16");context.put("totCnt", 3638763);context.put("totAmt", "6521");context.put("onCnt", 2874036);context.put("onAmt", "4768");context.put("offCnt", 764727);context.put("offAmt", "1753");context.put("typeCnt", 36);List<Goods> goodsList = new ArrayList<Goods>();Goods goods1 = new Goods();goods1.setNum(1);goods1.setType("臭美毁肤");goods1.setSv(675512);goods1.setSa("589");Goods goods2 = new Goods();goods2.setNum(2);goods2.setType("女装");goods2.setSv(602145);goods2.setSa("651");Goods goods3 = new Goods();goods3.setNum(3);goods3.setType("手机");goods3.setSv(587737);goods3.setSa("866");Goods goods4 = new Goods();goods4.setNum(4);goods4.setType("家具家装");goods4.setSv(551193);goods4.setSa("783");Goods goods5 = new Goods();goods5.setNum(5);goods5.setType("食物饮品");goods5.setSv(528604);goods5.setSa("405");goodsList.add(goods1);goodsList.add(goods2);goodsList.add(goods3);goodsList.add(goods4);goodsList.add(goods5);context.put("goods", goodsList);//创建字段元数据FieldsMetadata fm = report.createFieldsMetadata();//Word模板中的表格数据对应的集合类型fm.load("goods", Goods.class, true);//输出到本地目录FileOutputStream out = new FileOutputStream(new File("D://商品销售报表.docx"));report.process(context, out);}}

Word模板中生成序号

给表格数据添加序号是通过后台代码生成的,比如上面的"goods1.setNum(1)"这段代码,其实也可以在Word模板中设置对应的域变量来实现序号的填充。

语法如下:

@before-row[#list sequence as item]item?index
@after-row[/#list]

在表格中添加上面的表达式,XDocReport就会自动解析并生成序号,表格中的其他字段也需要进行相应的改动:

提示:

1. 序号的表达式要拆成三个域,如下图,要把这三部分分别设置成域;

     设置完成的结果参考上面表格中的序号表达式,表达式中"item?index+1"是因为序号是从0开始的,所以要加1

2. 表格中除序号的列需要改成item.xxx而不是之前的goods.xxx:

3. 生成效果如下:

建议:序号最好在后台生成,用序号表达式生成的序号列会占用比较大的空间,对资源有所浪费。

补充

1. JavaWeb项目中通常是通过浏览器下载的方式来下载Word文档,此时只需要把之前下载到本地的代码改成浏览器端下载的代码即可:

//输出到本地目录
//FileOutputStream out = new FileOutputStream(new File("D://商品销售报表.docx"));
//report.process(context, out);//浏览器端下载
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
String fileName = "商品销售报表.docx";
response.setHeader("Content-Disposition", "attachment;filename="  .concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
report.process(context, response.getOutputStream());

2. Word模板中的表格的长度最好充满Word文档的左右两边,否则如果表格下面还有其他文本内容,下面的文本内容会自动填充到表格的缝隙处,而且会对下面的文本内容进行覆盖。

加餐

其实,导出Word模板,上面的模板和代码已经够用了,但也有少数模板需要添加图片和图形(比如饼状图)。

制作图片

图片的生成不使用编辑域,使用模板图片和Word的书签功能,而且需要在元数据中加入图片类型的代码,以下为具体步骤:

1. 在Word模版中需要插入图片的位置插入一张模版图片,然后鼠标单击模板图片插入一个书签,设置书签名称,比如img1, 最后点击【添加】按钮:

2. 如果需要插入多个图片,就在需要插入图片的位置插入多个模板图片并插入书签设置对应的书签名称即可,后台代码如下:

package com.tzsj.test;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;public class ImgTest {@Testpublic void test() throws IOException, XDocReportException {generateWordForImg();}public void generateWordForImg() throws IOException, XDocReportException {//获取Word模板,模板存放路径在项目的resources目录下InputStream ins = this.getClass().getResourceAsStream("/图片.docx");//注册xdocreport实例并加载FreeMarker模板引擎IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins, TemplateEngineKind.Freemarker);//创建xdocreport上下文对象IContext context = report.createContext();FieldsMetadata fm = report.createFieldsMetadata();//元数据中加入图片fm.addFieldAsImage("img1");fm.addFieldAsImage("img2");//获取图片InputStream img1 = this.getClass().getResourceAsStream("/11.jpg");InputStream img2 = this.getClass().getResourceAsStream("/33.jpg");//把图片添加到上下文对象context.put("img1", img1);context.put("img2", img2);//输出到本地目录FileOutputStream out = new FileOutputStream(new File("D://图片报表.docx"));report.process(context, out);}}

3. 导出效果如下:

制作图形

要在Word文档中生成柱状图、饼状图等图形,需要在项目中引入第三方绘图工具,在这里使用xchart来演示在Word中生成饼状图图形。

生成饼状图和生成图片的方法很类似,具体步骤如下:

1. 在Word模版中需要插入图片的位置插入一张模版图片,然后鼠标单击模板图片插入一个书签,设置书签名称,比如chart,最后点击【添加】按钮:

2. 编写代码:

2.1 在pom.xml文件中添加xchart的依赖:

<dependency><groupId>org.knowm.xchart</groupId><artifactId>xchart</artifactId><version>3.5.4</version>
</dependency>

2.2 后台代码:

package com.tzsj.test;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
import org.knowm.xchart.BitmapEncoder;
import org.knowm.xchart.PieChart;
import org.knowm.xchart.PieChartBuilder;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.images.ByteArrayImageProvider;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;public class ChartTest {@Testpublic void test() throws IOException, XDocReportException {generateWordForChart();}public void generateWordForChart() throws IOException, XDocReportException {//获取Word模板,模板存放路径在项目的resources目录下InputStream ins = this.getClass().getResourceAsStream("/饼图.docx");//注册xdocreport实例并加载FreeMarker模板引擎IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins,                 TemplateEngineKind.Freemarker);//创建xdocreport上下文对象IContext context = report.createContext();FieldsMetadata fm = report.createFieldsMetadata();//元数据中加入图片fm.addFieldAsImage("chart");PieChart chart = new PieChartBuilder().width(800).height(620).title("销售饼图").build();//给饼图设置对应的值chart.addSeries("臭美毁肤", 589);chart.addSeries("女装", 651);chart.addSeries("手机", 866);chart.addSeries("家居家装", 783);chart.addSeries("食物饮品", 405);//生成饼图ByteArrayOutputStream baos = new ByteArrayOutputStream();BitmapEncoder.saveBitmap(chart, baos, BitmapEncoder.BitmapFormat.JPG);//把饼图添加到上下文对象context.put("chart", new ByteArrayImageProvider(baos.toByteArray()));//输出到本地目录FileOutputStream out = new FileOutputStream(new File("D://饼图报表.docx"));report.process(context, out);}}

3. 导出效果如下:

总结

这就是用Java语言实现,结合XDocReportFreeMarker模板引擎生成Word文档的方法。希望能给致力于开发的小伙伴带来一丝丝帮助。

如果您在浏览过程中遇到什么问题,请在下方评论区给我留言。

本人入驻的其他平台:

  • 微信公众号:弹指时间 。
  • 网易音乐人弹指时间

Java导出Word文档的实现相关推荐

  1. 【Java用法】使用Java导出word文档的解决方案(适用于从服务器上下载到本地电脑)

    本文目录 一.Controller 二.Service 接口类 三.ServiceImpl 实现类 四.Content-Type 类型与MIME Type类型对照表 最近在做一个word导出功能,需求 ...

  2. 【Java用法】使用Java导出word文档的解决方案(适用于Windows电脑)

    目录 实现方式一.通过原生的POI 实现方式二.通过Hutool工具包 步骤1.添加pom依赖 步骤2.编写几行代码 步骤3.启动项目,大功告成 实现的效果 最近在做一个word导出功能,需求非常简单 ...

  3. JAVA导出Word文档工具EasyWord

    介绍 基于Apache poi封装,在上层做了模型转换的封装,让使用者更加简单方便 只支持docx的导出,不支持doc 下面废话少说 让我们以最快的方式学会用java导出word文档 组件依赖 依赖 ...

  4. 【day22】java导出word文档(包含导出图片)

    1.[Java]使用freemarker模板技术导出word main方法测试 package com.havenliu.document;import java.io.UnsupportedEnco ...

  5. java导出word文档组件_java导出word文档(转)

    导出word文档有两种方式:第一种是使用POI,第二种是使用itext组件,下面逐渐介绍: 1.使用POI的方式:本方式目前没法改变字体样式,只能输出纯文本. /** * 试卷导出word文档 * @ ...

  6. Java导出word文档

    需求 在开发中,经常会碰到需要导出word文档的时候,比如一些审批表.财务报表等等,接下来介绍一种基于java的简单高效的转换方法 环境 依赖 Apache的POI.FreeMarker和XDocre ...

  7. Java导出Word文档的几种方法

    前言 在日常的开发工作中,我们时常会遇到导出Word文档报表的需求,比如公司的财务报表.医院的患者统计报表.电商平台的销售报表等等. 导出Word方式多种多样,通常有以下几种方式: 使用第三方Java ...

  8. Java 导出Word文档(含图片)doc格式 (保姆级)

    1.maven仓库 <!-- freemarker jar --><dependency><groupId>org.freemarker</groupId&g ...

  9. Java 导出word文档,遍历表格数据,导出图片

    引用:https://www.cnblogs.com/pxblog/p/13072711.html 1.引入maven依赖: <dependency><groupId>cn.a ...

  10. java导出word文档(easypoi)

    导出word,引用的是easypoi的jar 第一步:导入jar <dependency><groupId>cn.afterturn</groupId><ar ...

最新文章

  1. mysql 自动管理内存_MySQL内存管理,内存分配器和操作系统
  2. 【Windows工具】BBDown.exe B站视频下载工具详细使用说明(https://github.com/nilaoda/BBDown)
  3. 嵌套函数,匿名函数,高阶函数
  4. python 进程池不足_python 进程池pool简单使用
  5. 展示面--存储学习总结于2021年
  6. 一摞烙饼最上面一个的另一面为焦的概率
  7. SQL server sysobjects表说明
  8. php 图片轮换 代码,jQuery实现图片轮播特效代码分享
  9. RapidXml的使用
  10. RT-Thread:RW007-连接WIFI
  11. linux没有cpufreq目录,Linux系统的Cpufreq
  12. 权力的下沉:去中心化的真正本质
  13. js和html:周岁年龄计算器
  14. 记录一次Golang逃逸分析
  15. PDF压缩文件怎么压缩?两分钟让你学会三种方法
  16. Python、Cython、CPython的简单对比
  17. 2019肖秀荣考研政治3件套
  18. 发言稿开场白范文_演讲稿开场白范文集锦
  19. Python爬虫的urllib.error.HTTPError: HTTP Error 418错误
  20. 对千变万化数据的处理 12work

热门文章

  1. Pandas数据分析与处理补充习题
  2. Merged region B8 must contain 2 or more cells
  3. matlab fft 历程,MATLAB利用FFT分析不足一个完整周期的信号时的问题
  4. python从键盘上输入10个数、求其平均值_从键盘上循环输入10个数,求其平均值,并打印输出....
  5. 计算机学科a类排名,哈工大17个学科排名位列A类
  6. @自我怀疑的开发者:你够优秀吗?
  7. 计算机自我检测方法,电脑问题的自我检测方法有哪些?
  8. 空间信息产业的八大极客技术
  9. python 自动解析外文时间日期
  10. vue移动端登录页面(包含国际区号选择、账号密码登录、手机登录和忘记密码)