前端报表导出成word文档(含echarts图表) - Go语言中文社区

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


前端报表导出成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.
                        
                        
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/musuny/article/details/80783891
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢