前端报表导出成word文档(含echarts图表)

一、问题背景:

前端vue做的各种维度的报表,原来是通过前端整体截屏导出成PDF,但部分报表在遇到跨页时会被截断,客户体验极差。然后又考虑客户可能需要修改报表中的一些内容,因此需要导出成word文档解决跨页截断和满足修改报表内容的问题。前期解决方案预研时试过jacob、poi方案,但jacob只能用于windows平台(要引用一个dll文件),并且jacob和poi都存在样式方面的难题。后来通过其他渠道了解了freemarker,于是通过freemarker的把前端请求的报表数据填充到模板文件,生成word文档(导出功能由后端java实现)

二、效果图

首先上一张效果图,由于数据保密性,故前端页面的报表原样就不展示,导出的word文档的效果图和页面报表几乎一样

#三、功能点

  1. 文档标题
  2. 文档标题下方生成日期
  3. 文档总体情况概述
  4. 每个echarts图表的标题、图片、图注
  5. 水印
    #四、解决方案
    利用freemarker将前端传入的json格式数据填充入事先设计好的模板文件并生成word文档
    #五、实现流程
Created with Raphaël 2.2.0开始新建word文档按照页面报表布局与样式设计文档模板替换内容为占位符另存为xml文件xml模板占位符是否分离占位符完整将图表生成的base64编码手动替换成占位符将文档保存到工程resource/freemarker/template目录编写代码调用freemarker api生成word文档访问swagger接口页面测试下载,打开查看效果yesno

#六、实现步骤
##1. 设计模板
按照前端报表展示样式,设计模板,并将模板中需要动态被参数填充的部分使用占位符代替,如标题使用${title},图表标题使用${title_1}、${title_2}、${title_3},图表总结词用${summary_1},${summary_2},${summary_3},以此类推.下图为使用占位符替换之后的word模板

##2. 另存模板为xml
上一步设计好模板并替换关键内容为占位符后,需要保存成xml模板文件,然后将xml模板文件中的图片base64编码替换成占位符,例如下面模板片段

 <pkg:part pkg:name="/word/media/image16.png" pkg:contentType="image/png" pkg:compression="store"><pkg:binaryData>${base64_11}</pkg:binaryData></pkg:part><pkg:part pkg:name="/word/media/image11.png" pkg:contentType="image/png" pkg:compression="store"><pkg:binaryData>${base64_9_1}</pkg:binaryData></pkg:part><pkg:part pkg:name="/word/media/image9.png" pkg:contentType="image/png" pkg:compression="store"><pkg:binaryData>${base64_8_2}</pkg:binaryData></pkg:part><pkg:part pkg:name="/word/media/image10.png" pkg:contentType="image/png" pkg:compression="store"><pkg:binaryData>${base64_8_3}</pkg:binaryData></pkg:part><pkg:part pkg:name="/word/media/image8.png" pkg:contentType="image/png" pkg:compression="store"><pkg:binaryData>${base64_8_1}</pkg:binaryData></pkg:part>

##3. 新建maven工程
本人使用的开发工具是Idea 2018.1版本,创建maven项目并创建包名,结构如下:

export-doc
└─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─zhuxl
│  │  │          └─exportdoc
│  │  │              │
│  │  │              ├─component
│  │  │              │  └─handler
│  │  │              │
│  │  │              ├─configuration
│  │  │              │
│  │  │              ├─controller
│  │  │              │
│  │  │              ├─entity
│  │  │              │
│  │  │              ├─service
│  │  │              │  │
│  │  │              │  └─impl
│  │  │              │
│  │  │              └─util
│  │  │
│  │  └─resources
│  │
│  └─test
│      └─java
└─pom.xml

##4. 添加相关依赖

  • 添加spring boot依赖
    本demo项目基于spring boot框架,因此需要添加spring-boot-starter-web依赖,并且创建启动类Application.java
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>1.5.13.RELEASE</version><optional>true</optional>
</dependency>
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

(exclude = {DataSourceAutoConfiguration.class}) 参数表示不自动加载参数连接数据库,因为本demo无数据库连接,仅演示service里调用工具类方法导出word,不需要操作数据库,因此需要添加这个参数,否则启动会报连接数据库异常。

  • 添加swagger依赖
    本demo导出word报表请求参数为json格式,数据量非常大(因为有echarts报表base64编码),请求方式为POST,为了便于测试,因此集成swagger
<dependency><groupId>com.didispace</groupId><artifactId>spring-boot-starter-swagger</artifactId><version>1.4.1.RELEASE</version>
</dependency>
  • 添加lombok依赖
    demo中请求参数使用lombok注解@Data或@Getter,@Setter,可以不用写请求对象的getter和setter方法,在项目编译阶段会自动生成getter和setter方法。
<!-- LOMBOK begin -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.20</version>
</dependency>
<!-- LOMBOK end -->
  • 添加fastjson依赖
    demo可能会使用到JSONObject类来设置异常时接口返回的数据
<!-- FASTJSON begin -->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.31</version>
</dependency>
<!-- FASTJSON end -->
  • 添加freemarker依赖
    该依赖为本次功能实现的核心,主要利用freemarker的api将请求数据构造的map和模板文件作为参数生成word文件,并返回File文件对象,最后使用response的输出流将文件返回
<!-- FREEMARKER begin -->
<dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.28</version>
</dependency>
<!-- FREEMARKER end -->

##5. 创建接口请求参数对象类
使用java类来接收请求的json数据

@Data
@ApiModel(value = "贫困人群报表导出请求对象")
public class ReportExportWordRequest {@ApiModelProperty(value = "区域级别", name = "unitLevel")private Integer unitLevel;@ApiModelProperty(value = "区域编码", name = "unitCode")private String unitCode;@ApiModelProperty(value = "报表类型", name = "type", notes = "poverty:贫困人群报告;disable:残疾人群报告;poverty_disable:贫困且残疾人群报告")private String type;@ApiModelProperty(value = "报表标题", name = "title")private String title;@ApiModelProperty(value = "报告水印", name = "watermark")private String watermark;@ApiModelProperty(value = "报表生成日期", name = "date")private String date;@ApiModelProperty(value = "该区域报表描述第一段", name = "description1")private String description1;@ApiModelProperty(value = "该区域报表描述第二段", name = "description2")private String description2;@ApiModelProperty(value = "报表中每个图表的内容列表", name = "reports")private List<ReportContentRequest> reports;
}
@Data
@ApiModel("单个图表请求对象")
public class ReportContentRequest {@ApiModelProperty(value = "报表中排列序号", name = "serial")private Integer serial;@ApiModelProperty(value = "单个图表标题", name = "title")private String title;@ApiModelProperty(value = "单个图表base64编码值", name = "base64")private String base64;@ApiModelProperty(value = "单个图表内容总结", name = "summary")private String summary;@ApiModelProperty(value = "该标题下存在多个报表", name = "children")private List<ReportContentRequest> children;
}

6. 创建导出word工具类

该工具类是实现导出word功能的核心类,读取模板文件,格式化请求参数,填充模板生成word文档的功能都在此工具类完成

public class WordGeneratorUtils {private static Configuration configuration = null;private static Map<String, Template> allTemplates = null;private static class FreemarkerTemplate {public static final String POVERTY = "poverty";}static {configuration = new Configuration(Configuration.VERSION_2_3_28);configuration.setDefaultEncoding("utf-8");configuration.setClassForTemplateLoading(WordGeneratorUtils.class, "/freemarker/template");allTemplates = new HashMap();try {allTemplates.put(FreemarkerTemplate.POVERTY, configuration.getTemplate(FreemarkerTemplate.POVERTY + ".ftl"));} catch (IOException e) {e.printStackTrace();throw new RuntimeException(e);}}private WordGeneratorUtils() {throw new AssertionError();}public static File createDoc(Map<String, String> dataMap) {try {String name = dataMap.get("title") + dataMap.get("date") + ".doc";File f = new File(name);Template t = allTemplates.get(dataMap.get("template"));// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");t.process(dataMap, w);w.close();return f;} catch (Exception ex) {ex.printStackTrace();throw new RuntimeException("生成word文档失败");}}public static Map<String, String> parseToMap(ReportExportWordRequest request) {Map<String, String> datas = new HashMap(32);//主标题datas.put("title", request.getTitle());datas.put("date", request.getDate());datas.put("watermark", request.getWatermark());datas.put("description1", request.getDescription1());datas.put("description2", request.getDescription2());//遍历设置报表List<ReportContentRequest> contents = request.getReports();datas.put("template", request.getType());for (ReportContentRequest c : contents) {if (c.getChildren() == null || c.getChildren().size() == 0) {//无子报表datas.put("title_" + c.getSerial(), c.getTitle());datas.put("base64_" + c.getSerial(), c.getBase64());datas.put("summary_" + c.getSerial(), c.getSummary());} else {//有多个子报表datas.put("title_" + c.getSerial(), c.getTitle());for (ReportContentRequest subc : c.getChildren()) {datas.put("title_" + c.getSerial() + "_" + subc.getSerial(), subc.getTitle());datas.put("base64_" + c.getSerial() + "_" + subc.getSerial(), subc.getBase64());datas.put("summary_" + c.getSerial() + "_" + subc.getSerial(), subc.getSummary());}}}return datas;}
}

##7. 创建业务接口与实现类
ReportService接口类

public interface ReportService {File exportWord(ReportExportWordRequest exportWordRequest);
}

ReportServiceImpl实现类

@Service
public class ReportServiceImpl implements ReportService {@Overridepublic File exportWord(ReportExportWordRequest exportWordRequest) {//解析参数Map<String, String> datas = WordGeneratorUtils.parseToMap(exportWordRequest);//导出File word = WordGeneratorUtils.createDoc(datas);return word;}
}

8. 创建Controller类

@Slf4j
@RestController
@RequestMapping("/api/v1/report")
public class ReportController {@Autowiredprivate ReportService reportService;@ApiOperation(value = "贫困人群综合分析报告导出word文档", notes = "贫困人群综合分析报告导出word文档")@PostMapping("/poverty_export_word.ajax")public void povertyExportWord(HttpServletRequest request, HttpServletResponse response,@Valid @RequestBody ReportExportWordRequest exportWordRequest) {File file = reportService.exportWord(exportWordRequest);InputStream fin = null;OutputStream out = null;try {// 调用工具类WordGeneratorUtils的createDoc方法生成Word文档fin = new FileInputStream(file);response.setCharacterEncoding("utf-8");response.setContentType("application/msword");// 设置浏览器以下载的方式处理该文件// 设置文件名编码解决文件名乱码问题response.addHeader("Content-Disposition", "attachment;filename=" + new String(file.getName().getBytes(), "iso-8859-1"));out = response.getOutputStream();byte[] buffer = new byte[512];int bytesToRead = -1;// 通过循环将读入的Word文件的内容输出到浏览器中while ((bytesToRead = fin.read(buffer)) != -1) {out.write(buffer, 0, bytesToRead);}} catch (Exception e) {throw new RuntimeException("导出失败", e);} finally {try {if (fin != null) {fin.close();}if (out != null) {out.close();}if (file != null) {file.delete();}} catch (IOException e) {throw new RuntimeException("导出失败", e);}}}}

##9. 创建spring boot 启动类与yml配置
启动类在前面已经创建,此处只贴出application.yml基本配置

server:port: 8080context-path: /zhuxl

##10. 创建swagger配置

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {@Beanpublic Docket api() {ParameterBuilder parameterBuilder = new ParameterBuilder();parameterBuilder.name("Access-Token").description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();List<Parameter> parameters = new ArrayList<>();parameters.add(parameterBuilder.build());return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.regex("/api/v1/.*")).build().globalOperationParameters(parameters).apiInfo(apiInfo());}
}

11. 运行,访问swagger页面测试

执行Applicaton类的main方法运行demo,访问swagger接口页面,在本demo中访问地址为:http://localhost:8080/zhuxl/swagger-ui.html

12. 构造参数测试获得报表word文件

构造json参数,点击try it out按钮,即可进行测试并将文件下载,由于请求参数中base64编码内容过于复杂,因此贴出的参数中图片base64编码省略

{"unitLevel":"4","unitCode":"513429100000","type":"poverty","title":"XXXX地区贫困人群总体情况报告","watermark":"张三13800138000","date":"2018年6月","description1":"报告正文开始的第一部分增加对该区域整体描述,描述中加入总关注人群数等信息","description2":"报告正文开始的第一部分增加对该区域整体描述,描述中加入总关注人群数等信息,报告正文开始的第一部分增加对该区域整体描述,描述中加入总关注人群数等信息,报告正文开始的第一部分增加对该区域整体描述,描述中加入总关注人群数等信息,报告正文开始的第一部分增加对该区域整体描述,描述中加入总关注人群数等信息,报告正文开始的第一部分增加对该区域整体描述,描述中加入总关注人群数等信息,报告正文开始的第一部分增加对该行政区域整体描述,描述中加入总关注人群数等信息,报告正文开始的第一部分增加对该行政区域整体描述,描述中加入总关注人群数等信息,报告正文开始的第一部分增加对该行政区域整体描述,描述中加入总关注人群数等信息","reports":[{"serial":1,"title":"一、贫困人口占比排名","base64":"xxxx","summary":"截止2018年6月,贫困人口占比最高的是XXX,占比达到60.8233%;占比最低的是XXXX,占比为11.6273%。","children":[]},{"serial":2,"title":"二、贫困人口关注等级分析","base64":"xxxxxxxx","summary":"截止2018年6月,总贫困人口中,一般关注等级0人,中度关注等级68156人,重点关注等级1人。三类人群分别占总人口的0%,34.2872%,0.0005%;占贫困总人口的0%,99.9985%,0.0014%。","children":[]},{"serial":3,"title":"三、致贫原因分析","base64":"xxxxx","summary":"截止2018年6月,贫困人口中,自身发展动力不足原因致贫的人数最多,占总贫困人口的37.0583%;其它原因致贫的人数最少,占总贫困人口的0.0161%。","children":[]},{"serial":4,"title":"四、贫困人群性别分析","base64":"xxxx","summary":"xxxxxxxxxxxxxxxxxxxxx","children":[]},{"serial":5,"title":"五、贫困人群年龄分析","base64":"xxxxx","summary":"","children":[]},{"serial":6,"title":"六、贫困人群学历分析","base64":"xxxxxx","summary":"","children":[]},{"serial":7,"title":"七、贫困人群民族分析","base64":"xxxxx","summary":"","children":[]},{"serial":8,"title":"八、贫困人群脱贫能力分析","base64":"","summary":"","children":[{"serial":1,"title":"1、文化程度分析","base64":"xxxxx","summary":"截止2018年6月,贫困人口中,中度关注等级中的文盲或半文盲,学龄前学历人数最多,占总贫困人口的30.7232%;重度关注等级中的大专及以上学历人数最少,占总贫困人口的0%。"},{"serial":2,"title":"2、劳动能力分析","base64":"xxxxxx","summary":"截止2018年6月,贫困人口中,中度关注等级中的丧失劳动力劳动力人数最多,占总贫困人口的51.1466%;重度关注等级中的技能劳动力劳动力人数最少,占总贫困人口的0%。"},{"serial":3,"title":"3、健康情况分析","base64":"xxxxx","summary":"截止2018年6月,贫困人口中,中度关注等级中的健康健康状况人数最多,占总贫困人口的96.5653%;重度关注等级中的残疾健康状况人数最少,占总贫困人口的0%。"}]},{"serial":9,"title":"九、资产和收入分析","base64":"","summary":"","children":[{"serial":1,"title":"1、家庭收入分析","base64":"xxxxx","summary":"截止2018年6月,贫困人口中,中度关注等级中的5k-10k收入人数最多,占总贫困人口的9.0365%;重度关注等级中的15k以上收入人数最少,占总贫困人口的0%"},{"serial":2,"title":"2、房产情况分析","base64":"xxxxx","summary":"截止2018年6月,贫困人口中,中度关注等级中的房屋面积50-100房产人数最多,占总贫困人口的18.8565%;重度关注等级中的房屋面积100平以上房产人数最少,占总贫困人口的0%"},{"serial":3,"title":"3、耕地林地情况分析","base64":"xxxxx","summary":"截止2018年6月,贫困人口中,中度关注等级中拥有耕地面积5.32亩以上亩的人数最多,占总贫困人口的7.7556%;重度关注等级中拥有耕地面积5.32亩以上亩的人数最少,占总贫困人口的0%"},{"serial":4,"title":"4、新农合、养老保险情况分析","base64":"xxxx","summary":"截止2018年6月,贫困人口中,中度关注等级中办理已参加新农合保险的人数最多,占总贫困人口的99.9956%;重度关注等级中办理已办理养老保险保险的人数最少,占总贫困人口的0%"}]},{"serial":10,"title":"十、预脱贫分析","base64":"xxxx","summary":"截止2018年6月,预脱贫人口中,2020年预脱贫的人数最多,占总贫困人口的2.4134%","children":[]},{"serial":11,"title":"十一、生活状况分析","base64":"xxxx","summary":"截止2018年6月,贫困人口家庭中,没有实现卫生厕所的贫困家庭数量占比最高,占比为80.7526%","children":[]}]
}

##13. 打开文件验证
将swagger接口页面Response Body请求返回的doc文档下载并打开,效果图见文章顶部
#七、问题排查

  1. doc模板设计保存成xml模板文件占位符分离,如${title_1}可能被分离成$、title_、1、}或者${title_、1}或者其他情况

方案一:手动修改xml中被分离的占位符,但缺点是如果模板需要做一点改动,保存的xml又需要手动修改,增加无谓的工作量

方案二:将整个占位符的样式设置成一样,但事实上同样存在被分离的情况
方案三:该方案可完美解决占位符分离情况,避免修改doc模板保存时重复修改占位符,点击查看详细方案
#八、git clone
传送门:去star

git clone https://github.com/v5zhu/export-doc.git

九、联系方式

QQ:2810010108

前端报表导出成word文档(含echarts图表)相关推荐

  1. 只涉及前端,将html页面导出成word文档

    用docxtemplater插件,前端实现html导出word文档(可导出文字.表格.图片) 下面我用的是vue框架,用其他框架同理,只要这个插件能兼容就行 官方文档https://docxtempl ...

  2. PHP导出成word文档

    文章转载:脚本之家 链接:https://www.jb51.net/article/157904.htm 作者:cywu https://mp.weixin.qq.com/s/OV33WGPgnjx7 ...

  3. CAD图纸如何免费导出成Word文档?

    我们在日常操作CAD图纸的过程中经常会遇到格式转换的问题,比如将图纸转成Word?有哪些转换方法可以操作呢?需要再安装其他转换工具吗? 如果我们日常需要查看或编辑CAD图纸,那么一定已经安装过相关的C ...

  4. phpword 利用phpword将信息导出成word文档进行下载

    前几天需要一个功能,就是把填写得信息(文字图片)导出到word,对于我这个小白来说无比的艰难,幸好有前辈得帮助.也希望能帮助更多的人~ 首先下载phpword  https://download.cs ...

  5. 前端实现HTML导出为word文档

    需求:将页面或者页面上所需要的部分导出为word文档 基本导出 修改样式 修改图片大小 修改导出文档名称 修改导出默认方式 准备工作: jquery FileSaver.js jquery.worde ...

  6. HTML导出生成Word文档

    前言 在某某夜黑风高的一天即将下班的时候,老板召集公司大神们,进行了一个紧急会议,此会议主要目的是老板的客户提出了一些小需求, 有一个前端小 需求,需要将前端HTML导出为Word文档,因为没有做过此 ...

  7. js将html转为word文档,js将html导出到word文档(含echarts图表)

    需求 在开发项目途中遇到了一个需求,就是将一个整个HTML界面导出到word文档,其中包含了echarts图表,经过一番折腾,终于完成了~~~(鸡肋),过程中用到了几个插件,总结了一下几个步骤,希望可 ...

  8. Java项目中利用Freemarker模板引擎导出--生成Word文档

    应邀写的一篇文章:Java项目中利用Freemarker模板引擎导出--生成Word文档 资源下载:https://download.csdn.net/download/weixin_41367523 ...

  9. 用 Python 将微信热文转换成Word文档 | 神级操作

    不得不说微信公众号已经成为了一个开放平台,每天数以万计的微信公众号文章在这产生,我们关注一个微信公众号每天便可以看到新的文章,我们同时也不知不觉的将好的文章分享到给朋友. 那么如何保存一个好的文章呢? ...

最新文章

  1. 终于把XGBoost总结写出来了!
  2. 开心庄园页面HTML素材,练习2:制作开心庄园页面.html
  3. Android实现侧滑抽屉菜单,android studio自带的抽屉侧滑菜单怎么设置点击事件?还头一回遇到,汗!...
  4. 一款jQuery立体感动态下拉导航菜单特效
  5. 深度学习(五十一)变分贝叶斯自编码器(上)
  6. 华为云PB级数据库GaussDB(for Redis)解析第二期:Redis消息队列Stream的应用探讨
  7. java 序列化 写入mysql_java 序列化到mysql数据库中
  8. 基于SSM框架的高校实验室管理系统PPT模板
  9. 【PostgreSQL-9.3.17】CentOS-6.7安装PostgreSQL-9.3.17
  10. 使用内部类或者外部类
  11. 从零基础入门Tensorflow2.0 ----一、3.4 实战深度神经网络(dropout)
  12. 【HTTP】Fiddler(一) - Fiddler简介和使用
  13. 堆栈宽度学习Stacked BLS的简单python代码实现
  14. C语言链表详解附实例
  15. indy-sdk tutorials数字身份认证(一)
  16. umts是移动还是联通_网络模式中的UMTS是什么意思?
  17. Python编写的com组件大全与解决对策
  18. inv如何用计算机计算,计算器INV是用那个键表示的
  19. 魔兽在副本里服务器维护了,魔兽世界11月19日维护服务器状态查询地址 6.2.3补丁上线更新一览...
  20. ADOBE AIR是什么?

热门文章

  1. windows定时关机命令 取消定时关机命令 查看DNS缓存命令 清除DNS缓存命令
  2. html做成小程序,微信小程序——简单静态网页的制作
  3. 适合Java初学入门的几本图书
  4. JavaWeb——JSP技术
  5. Android中高级进阶开发面试题冲刺合集(四)
  6. Oracle数据库设计(开放式基金交易平台)
  7. 2021年个人学习生活总结和2022年学习生活计划
  8. speedoffice(Excel)如何设置纸张大小
  9. 荣耀note10无缘鸿蒙,赵明确认荣耀NOTE10 真机参数疑似全曝光!
  10. 增加购物车商品数量【项目 商城】