⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7241171237603147831 「抢老婆酸奶的小肥仔」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前段时间做了一个需求:需要快速生成一份数据报告,里面包含了文字、图片和数据报表,同时生成的图形数据也可以随意修改。之前想着使用Apache POI来进行实现,在翻阅一些资料后,发现poi-tl更适合我们的业务,也更容易上手,于是对其进行了研究、也对其中的一些标签进行了封装,形成一个快速实现的工具类。

废话不多说,直接开撸。

1、Poi-tl简介

poi-tl是一个免费开源的Java类库,是基于Apache POI的模板引擎,纯Java组件,跨平台,代码短小精悍,通过插件机制使其具有高度扩展性。

因此在使用的时候需要实现设置好模板,就像Freemarker一样,但是比其简单,也易操作。

2、常用标签

2.1 文本

“

格式:{{var}}

数据模型:

类型 描述
String 纯文本
TextRenderData 带有样式的文本
HyperLinkTextRenderData 超链接文本
Object 调用toString()方法转化成文本

例如:

put("name", "Sayi");
put("author", new TextRenderData("000000", "Sayi"));
// 超链接
put("link",
new HyperLinkTextRenderData("website", "http://deepoove.com"));

2.2 图片

“

格式:以@开始,{{@var}}

数据模型:

类型 描述
PictureRenderData 可以支持本地图片、也可以支持远程的URL地址(主要讲URL图片转成BufferedImage)

例如:

// 本地图片
put("local", new PictureRenderData(80, 100, "./sayi.png"));

// 图片流
put("localbyte", new PictureRenderData(80, 100, ".png", new FileInputStream("./logo.png")));

// 网络图片(注意网络耗时对系统可能的性能影响)
put("urlpicture", new PictureRenderData(50, 50, ".png", BytePictureUtils.getUrlBufferedImage("http://deepoove.com/images/icecream.png")));

2.3 表格

poi-tl默认实现了N行N列的样式(如下图),同时提供了当数据为空时,展示一行空数据的文案。

“

格式:以#开头,{{#var}}

数据模型:

类型 描述
MiniTableRenderData 该类主要也是调用TextRenderData,提供样式等

2.4 列表

“

格式:以*开头,{{*var}}

数据模型:

类型 描述
NumbericRenderData 该类主要也是调用TextRenderData,提供样式等

NumbericRenderData中支持列表样式,主要有罗马字符、有序无序等。

FMT_DECIMAL //1. 2. 3.
FMT_DECIMAL_PARENTHESES //1) 2) 3)
FMT_BULLET //● ● ●
FMT_LOWER_LETTER //a. b. c.
FMT_LOWER_ROMAN //i ⅱ ⅲ
FMT_UPPER_LETTER //A. B. C.

2.5 单系列图表

单系列图标,是指在图形中只展示一列数据,例如:单数据的柱状图,饼图等。

格式:先创建单系列图,然后在图表区格式 ->可选文字->标题。与文字一样,以{{val}},如图:

数据模型:

类型 描述
ChartSingleSeriesRenderData 该类提供了设置标题、类别等

例如:

ChartSingleSeriesRenderData singleSeriesRenderData = new ChartSingleSeriesRenderData();
singleSeriesRenderData.setCategories(new String[] { "俄罗斯", "加拿大", "美国", "中国" }");
singleSeriesRenderData.setChartTitle("测试");
pie.setSeriesData(new SeriesRenderData("countries", new Integer[] { 17098242, 9984670, 9826675, 9596961 }));

2.6 多系列图表

在报表应用中,很多时候使用的是多系列组合,例如:柱状图与折线图组合等。

格式:与单系列一致。

数据模型:

类型 描述
ChartMultiSeriesRenderData 该类提供了设置标题、类别等

例如:

ChartMultiSeriesRenderData chart = new ChartMultiSeriesRenderData();
chart.setChartTitle("MyChart");
chart.setCategories(new String[] { "中文", "English" });
List<SeriesRenderData> seriesRenderData = new ArrayList<>();
seriesRenderData.add(new SeriesRenderData("countries", new Double[] { 15.0, 6.0 }));
seriesRenderData.add(new SeriesRenderData("speakers", new Double[] { 223.0, 119.0 }));
chart.setSeriesDatas(seriesRenderData);

3、代码封装

上述我们介绍了几种常用标签,更多的标签大家可以参考官方网站。

既然我们已经知道标签,那我们来进行代码的整合,主要是封装一个工具类,快速实现多标签一起生成报表。

引入jar包,我们以1.8.2版本为例:

<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.8.2</version>
</dependency>

3.1 创建标签类型

标签类型指程序中支持哪些标签,例如:文字,图片等。使用一个枚举来实现,方便后期进行扩展。

/**
* @author: jiangjs
* @description: 标签类型
**/
public enum WordContentTypeEnum {
/**
* 文本
*/
TEXT,
/**
* 图片
*/
PICTURE,
/**
* 表格
*/
TABLE,
/**
* 列表
*/
LIST,
/**
* 图表
*/
CHART;
}

3.2 创建公共实体

该实体提供替换的标签名称及标签类型。标签名称及put使用的名称,与word中定义的名称一致。

/**
* @author: jiangjs
* @description: 公共实体
* @date: 2022/11/24 15:05
**/
@Data
@Accessors(chain = true)
public class LabelData {
/**
* 标签名称,即put使用到的名称
*/
private String labelName;
/**
* 文件内容类型
*/
private WordContentTypeEnum typeEnum;
}

3.3 封装统一数据生成接口

该接口只提供各标签数据生成的封装,返回一个Object。

/**
* @author: jiangjs
* @description: 封装统一各标签数据生成接口
**/
public interface GenerateWord {
Object generateWord(LabelData data);
}

3.4 创建工厂

主要是便于对各标签生成的数据进行管理。

/**
* @author: jiangjs
* @description: 生成word工厂
**/
public class GenerateWordFactory {

private static final Map<WordContentTypeEnum, GenerateWord> TYPE_BACK_DATA = new HashMap<>();

public static void register(WordContentTypeEnum typeEnum, GenerateWord word){
if (Objects.nonNull(typeEnum)){
TYPE_BACK_DATA.put(typeEnum,word);
}
}

public static GenerateWord getBackData(WordContentTypeEnum typeEnum){
return TYPE_BACK_DATA.get(typeEnum);
}
}
  • TYPE_BACK_DATA:表示各标签封装数据的类与标签的一一对应。
  • GenerateWord getBackData(WordContentTypeEnum typeEnum) :根据标签类型获取对应生成的数据。

3.5 封装word生成类

这个类的主要作用就是将封装的各标签数据写入到word模板中,并形成最终的报表。

/**
* @author: jiangjs
* @description: 操作word内容
**/
public class OperateWordManage {

private final static Logger log = LoggerFactory.getLogger(OperateWordManage.class);

public static void generateWordContent(File tempFileFile, String destFilePath,List<LabelData> contents){
FileOutputStream fos = null;
XWPFTemplate template = null;
try {
template = XWPFTemplate.compile(tempFileFile).render(new HashMap<String,Object>(contents.size()){{
contents.forEach(content ->{
GenerateWord backData = GenerateWordFactory.getBackData(content.getTypeEnum());
put(content.getLabelName(),backData.generateWord(content));
});
}});
fos = new FileOutputStream(destFilePath);
template.write(fos);
fos.flush();
}catch (Exception e){
log.error("替换生成图表报错:{}",e.getMessage());
e.printStackTrace();
}finally {
try{
if (Objects.nonNull(fos)){
fos.close();
}
if (Objects.nonNull(template)){
template.close();
}
}catch (Exception e){
log.error("关闭数据流报错:{}",e.getMessage());
e.printStackTrace();
}
}
}
}
  • tempFilePath:模板文件的地址。
  • destFilePath:生成后的文件地址。
  • List<LabelData> contents:各标签封装后的数据集合。

3.6 各标签生成数据封装

3.6.1 文本封装

3.6.1.1 创建文本实体

文本实体包括了纯文本、带样式文本、超链接文本。

/**
* @author: jiangjs
* @description: 文本
**/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class TextContentData extends LabelData {

/**
* 纯文本内容
*/
private String content;
/**
* 带样式文本
*/
private TextRenderData renderData;
/**
* 超链接文本
*/
private HyperLinkTextRenderData linkData;
}

3.6.1.2 创建数据封装类

该封装类会根据不同的属性值来返回不同类型。优先返回超链接,其次是带样式的文本,最后是纯文本。

/**
* @author: jiangjs
* @description: 文本内容实现
**/
@Component
public class TextGenerateWord implements GenerateWord {

@PostConstruct
public void init(){
GenerateWordFactory.register(WordContentTypeEnum.TEXT,this);
}

@Override
public Object generateWord(LabelData data) {
TextContentData contentData = (TextContentData) data;
return Objects.nonNull(contentData.getLinkData()) ? contentData.getLinkData() :
Objects.nonNull(contentData.getRenderData()) ? contentData.getRenderData() : contentData.getContent();
}
}

每一个封装数据方法都有一个初始化方法,主要是调用工厂方法将类型与当前数据生成方法进行绑定。

3.6.2 图片封装

3.6.2.1 创建图片实体

/**
* @author: jiangjs
* @description: 图片
**/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class PictureContentData extends LabelData {
/**
* 图片宽度
*/
private Integer width;
/**
* 图片高度
*/
private Integer height;
/**
* 图片类型
*/
private PicTypeEnum picType;
/**
* 图片地址(网络图片插入时使用)
*/
private String picUrl;
/**
* 图片文件
*/
private File file;
}

PicTypeEnum:图片类型。

/**
* @author: jiangjs
* @description: 图片类型
**/
public enum PicTypeEnum {
/**
* png图片
*/
PNG(".png"),
/**
* JPG图片
*/
JPG(".jpg"),
/**
* jpeg
*/
JPEG(".jpeg");

private final String picName;

PicTypeEnum(String picName) {
this.picName = picName;
}

public String getPicName() {
return picName;
}

}

3.6.2.2 创建数据封装类

/**
* @author: jiangjs
* @description:
**/
@Component
public class PictureGenerateWord implements GenerateWord {

@PostConstruct
private void init(){
GenerateWordFactory.register(WordContentTypeEnum.PICTURE,this);
}

@Override
public Object generateWord(LabelData data) {
PictureContentData picture = (PictureContentData) data;
return StringUtils.isNotBlank(picture.getPicUrl()) ? new PictureRenderData(picture.getWidth(),picture.getHeight(),picture.getPicType().getPicName(),
BytePictureUtils.getUrlBufferedImage(picture.getPicUrl()))
: new PictureRenderData(picture.getWidth(),picture.getHeight(),picture.getFile());
}
}

该封装会根据图片实体中是否有图片链接来创建数据。

3.6.3 表格封装

3.6.3.1 创建表格实体

/**
* @author: jiangjs
* @description: 表格
**/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class TableSeriesRenderData extends LabelData {

/**
* 表头
*/
private TextRenderData[] header;
/**
* 表内容
*/
private List<TextRenderData[]> contents;
}

将表头与表格内容进行分开赋值,使其更加清晰。

3.6.3.2 创建数据封装类

/**
* @author: jiangjs
* @description:
**/
@Component
public class TableGenerateWord implements GenerateWord {
@PostConstruct
private void init(){
GenerateWordFactory.register(WordContentTypeEnum.TABLE,this);
}
@Override
public Object generateWord(LabelData data) {
TableSeriesRenderData tableData = (TableSeriesRenderData) data;
RowRenderData header = RowRenderData.build(tableData.getHeader());
List<RowRenderData> contentData = new ArrayList<>();
tableData.getContents().forEach(con ->{
contentData.add(RowRenderData.build(con));
});
return new MiniTableRenderData(header,contentData);
}
}

3.6.4 列表封装

3.6.4.1 创建列表实体

/**
* @author: jiangjs
* @description: 列表
**/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class ListRenderData extends LabelData{
/**
* 列表数据集
*/
private List<TextRenderData> list;

/**
* 列表样式,支持罗马字符、有序无序等,默认为点
*/
private Pair<STNumberFormat.Enum, String> pair = NumbericRenderData.FMT_BULLET;
}

3.6.4.2 创建数据封装类

/**
* @author: jiangjs
* @description:
**/
@Component
public class ListGenerateWord implements GenerateWord {
@PostConstruct
private void init(){
GenerateWordFactory.register(WordContentTypeEnum.LIST,this);
}
@Override
public Object generateWord(LabelData data) {
ListRenderData listData = (ListRenderData) data;
return new NumbericRenderData(listData.getPair(),listData.getList());
}
}

3.6.5 图表封装

3.6.5.1 创建图表实体

/**
* @author: jiangjs
* @description: 图表
**/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
public class ChartSeriesRenderData extends LabelData {
/**
* 横轴数据
*/
private String[] categories;

/**
* 图表名称
*/
private String title;

/**
* 图表类型 组合
*/
private CharCombinationType charType = CharCombinationType.MULTI;

/**
* 系列对应数据
*/
private List<RenderData> senderData;

@Data
public static class RenderData{
/**
* 系列名称
*/
private String renderTitle;
/**
* 系列对应的数据
*/
private Number[] data;
/**
* 该系列对应生成的图表类型
*/
private SeriesRenderData.ComboType comboType = null;
}
}

CharCombinationType:表示图表中系列的类型,只有单系列或多系列。

/**
* @author: jiangjs
* @description: 图表系列类型
**/
public enum CharCombinationType {

/**
* 多组合
*/
MULTI("Multi"),
/**
* 单图形
*/
Single("Single");

private final String type;

CharCombinationType(String type){
this.type = type;
}
public String getType(){
return type;
}
}

3.6.5.2 创建数据封装类

/**
* @author: jiangjs
* @description: 图表类型
**/
@Component
public class ChartGenerateWord implements GenerateWord {
@PostConstruct
private void init(){
GenerateWordFactory.register(WordContentTypeEnum.CHART,this);
}
@Override
public Object generateWord(LabelData obj) {
ChartSeriesRenderData renderData = (ChartSeriesRenderData) obj;
if (Objects.nonNull(renderData.getCharType()) && Objects.equals("Single",renderData.getCharType().getType())){
ChartSingleSeriesRenderData singleSeriesRenderData = new ChartSingleSeriesRenderData();
singleSeriesRenderData.setCategories(renderData.getCategories());
singleSeriesRenderData.setChartTitle(renderData.getTitle());
ChartSeriesRenderData.RenderData seriesData = renderData.getSenderData().get(0);
SeriesRenderData srd = new SeriesRenderData(seriesData.getRenderTitle(),seriesData.getData());
if (Objects.nonNull(seriesData.getComboType())){
srd.setComboType(seriesData.getComboType());
}
singleSeriesRenderData.setSeriesData(srd);
return singleSeriesRenderData;
} else {
ChartMultiSeriesRenderData seriesRenderData = new ChartMultiSeriesRenderData();
seriesRenderData.setCategories(renderData.getCategories());
seriesRenderData.setChartTitle(renderData.getTitle());
List<ChartSeriesRenderData.RenderData> renderDataList = renderData.getSenderData();
List<SeriesRenderData> groupData = new ArrayList<>();
renderDataList.forEach(data -> {
SeriesRenderData srd = new SeriesRenderData(data.getRenderTitle(),data.getData());
if (Objects.nonNull(data.getComboType())){
srd.setComboType(data.getComboType());
}
groupData.add(srd);
});
seriesRenderData.setSeriesDatas(groupData);
return seriesRenderData;
}
}
}

代码中会根据系列类型的不同创建数据类型进行返回。

上述就是我对常用标签生成数据的封装,既然已经封装完成,那我们来进行下测试。

4、测试

4.1 创建文件模板

根据介绍的常用标签,我们在word中创建文件模板,如图:

我们将模板放置在项目的resources目录下。如图:

4.2 封装各标签数据

根据封装后的标签数据生成类来进行数据封装。

private static final String TEMPLATE_PATH = "static/template/demo_template.docx";

public void generateCharts() {
File templateFile = null;
try {
templateFile = new ClassPathResource(TEMPLATE_PATH).getFile();
} catch (IOException e) {
e.printStackTrace();
}
List<LabelData> generates = new ArrayList<>();
//文本
TextContentData contentData = new TextContentData();
contentData.setContent("2022年月通报函生成报告").setLabelName("title").setTypeEnum(WordContentTypeEnum.TEXT);
generates.add(contentData);

//带样式文本
TextContentData typeData = new TextContentData();
typeData.setRenderData(new TextRenderData("cc0000","这是带样式的内容")).setLabelName("typeContent").setTypeEnum(WordContentTypeEnum.TEXT);
generates.add(typeData);

//插入图片
PictureContentData picData = new PictureContentData();
picData.setWidth(200).setHeight(160).setPicType(PicTypeEnum.JPG).setFile(new File("D:\down\java.jpg"))
.setLabelName("picture").setTypeEnum(WordContentTypeEnum.PICTURE);
generates.add(picData);

//插入表格
TableSeriesRenderData tableData = new TableSeriesRenderData();
List<TextRenderData[]> contents = Arrays.asList(new TextRenderData[]{new TextRenderData("科教1班"),
new TextRenderData("1")},new TextRenderData[]{new TextRenderData("幼儿3班"),new TextRenderData("6")});
tableData.setHeader(new TextRenderData[]{new TextRenderData("班级"),new TextRenderData("排名")})
.setContents(contents).setLabelName("showTable").setTypeEnum(WordContentTypeEnum.TABLE);
generates.add(tableData);

//插入列表
ListRenderData listRenderData = new ListRenderData();
List<TextRenderData> listData = Arrays.asList(new TextRenderData("排序1"),new TextRenderData("排序2"),new TextRenderData("排序3"));
listRenderData.setList(listData).setPair(NumbericRenderData.FMT_LOWER_ROMAN).setTypeEnum(WordContentTypeEnum.LIST).setLabelName("numList");
generates.add(listRenderData);

//折线
ChartSeriesRenderData lineData = new ChartSeriesRenderData();
List<ChartSeriesRenderData.RenderData> lineRenderData = new ArrayList<>();
ChartSeriesRenderData.RenderData numRenderData = new ChartSeriesRenderData.RenderData();
ChartSeriesRenderData.RenderData moneyRenderData = new ChartSeriesRenderData.RenderData();
numRenderData.setRenderTitle("项目数量").setData(new Double[] {-11.02,-19.42,-10.61,-11.41,-7.91,-5.44,-5.30,-2.75,-1.24,0.35});
moneyRenderData.setRenderTitle("投资额").setData(new Number[]{-12.66,-19.41,-15.16,-19.72,-17.05,-15.92,-15.10,-13.04,-10.65,-9.15});
lineRenderData.add(numRenderData);
lineRenderData.add(moneyRenderData);
lineData.setTitle("1-10月份全国新开工项目数量、投资额增速")
.setCategories(new String[] {"1月","2月","3月","4月","5月","6月","7月","8月","9月","10月"})
.setSenderData(lineRenderData).setTypeEnum(WordContentTypeEnum.CHART).setLabelName("speedLine");
generates.add(lineData);

//柱状图
ChartSeriesRenderData barData = new ChartSeriesRenderData();
List<ChartSeriesRenderData.RenderData> barRenderData = new ArrayList<>();
ChartSeriesRenderData.RenderData openRenderData = new ChartSeriesRenderData.RenderData();
ChartSeriesRenderData.RenderData moneyData = new ChartSeriesRenderData.RenderData();
openRenderData.setRenderTitle("开工数量").setData(new Number[]{40,50,45,12,21,18,21,28,21,18,28,18,20,19,-10,-9,-10,19,39,31,20,19,-10,-9,-10,19,39,31,-10,19,39});
moneyData.setRenderTitle("投资额").setData(new Number[]{20,-22,-12,8,-10,-14,-10,-10,-8,-2,-8,-1,-9,-21,-9,-7,-21,-10,21,-29,-50,-21,-9,-7,-21,-10,21,-29,-21,-10,21});
barRenderData.add(openRenderData);
barRenderData.add(moneyData);
barData.setTitle("各省(自治区)直辖市新开项目数量、投资额同比情况")
.setCategories(new String[] {"贵州","西藏","黑龙江","浙江","湖北","江苏","四川","福建","安徽","海南","山西","广西","青海","广东","甘肃",
"云南","宁夏","新疆","湖南","北京","河北","山西","山东","内蒙古","天津","江西","吉林","河南","重庆","上海","辽宁"})
.setSenderData(barRenderData).setTypeEnum(WordContentTypeEnum.CHART).setLabelName("investmentRatio");
generates.add(barData);
//生成饼图
ChartSeriesRenderData areaData = new ChartSeriesRenderData();
List<ChartSeriesRenderData.RenderData> areaRenderDatas = new ArrayList<>();
ChartSeriesRenderData.RenderData areaRenderData = new ChartSeriesRenderData.RenderData();
areaRenderData.setData(new Number[]{17098242, 9984670, 9826675, 9596961}).setRenderTitle("投资额")
.setComboType(SeriesRenderData.ComboType.AREA);
areaRenderDatas.add(areaRenderData);
areaData.setTitle("国家投资额").setSenderData(areaRenderDatas).setCharType(CharCombinationType.Single)
.setCategories(new String[]{"俄罗斯", "加拿大", "美国", "中国"})
.setLabelName("areaShow").setTypeEnum(WordContentTypeEnum.CHART);
generates.add(areaData);

//横向柱状图
ChartSeriesRenderData lateralData = new ChartSeriesRenderData();
List<ChartSeriesRenderData.RenderData> lateralRenderData = new ArrayList<>();
ChartSeriesRenderData.RenderData lateralYearData = new ChartSeriesRenderData.RenderData();
ChartSeriesRenderData.RenderData lateralMoneyData = new ChartSeriesRenderData.RenderData();
lateralYearData.setRenderTitle("2021年").setData(new Number[]{400,200});
lateralMoneyData.setRenderTitle("2022年").setData(new Number[]{456,255});
lateralRenderData.add(lateralYearData);
lateralRenderData.add(lateralMoneyData);
lateralData.setTitle("工程建设项目建设周期同比情况")
.setCategories(new String[] {"从立项到开工的用时","从开工到验收的用时"})
.setSenderData(lateralRenderData).setTypeEnum(WordContentTypeEnum.CHART).setLabelName("cycleRadio");
generates.add(lateralData);

//组合图表
ChartSeriesRenderData groupData = new ChartSeriesRenderData();
List<ChartSeriesRenderData.RenderData> groupRenderData = new ArrayList<>();
ChartSeriesRenderData.RenderData unOpenData = new ChartSeriesRenderData.RenderData();
ChartSeriesRenderData.RenderData openRadioData = new ChartSeriesRenderData.RenderData();
unOpenData.setComboType(SeriesRenderData.ComboType.BAR).setRenderTitle("未开工项目数(个)")
.setData(new Number[]{55, 35, 23, 76, 60, 65.1, 70.2, 75.3, 80.4, 85.5, 90.6, 95.7, 26,
76, 60, 65.1, 70.2, 75.3, 80.4, 95.7, 26, 76, 60, 65.1, 70.2, 75.3, 95.7, 26, 76, 60, 65.1});
openRadioData.setComboType(SeriesRenderData.ComboType.LINE).setRenderTitle("开工率(%)")
.setData(new Number[]{34,45,23,67,34,45,23,67,34,45,23,67,23,67,34,45,23,45,23,67,23,67,34,45,23,45,67,23,67,34,45});
groupRenderData.add(unOpenData);
groupRenderData.add(openRadioData);
groupData.setTitle("各省(区、市)签约项目开工情况")
.setCategories(new String[] {"北京","吉林","云南","上海","安徽","浙江","江西","四川","陕西","甘肃","江苏","广西","内蒙古","福建","天津","海南","黑龙江",
"贵州","山东","河北","辽宁","湖北","宁夏","广东","重庆","河南","新疆","山西","湖南","青海","兵团"})
.setSenderData(groupRenderData).setTypeEnum(WordContentTypeEnum.CHART).setLabelName("openCondition");
generates.add(groupData);

//生成word
OperateWordManage.generateWordContent(templateFile,"D:\down\output.docx",generates);
}

4.3 生成报表

从生成的word中我们看到,数据已经被替换,在word中生成。

上述就是我使用poi-tl生成word报表,也对其进行了封装,便于我们使用。

工欲善其事,必先利其器,有时候封装还是有必要的,希望我的封装对大家有所启发。

源码地址:

“

https://github.com/lovejiashn/generate_report.git

文章目录
  1. 1. 1、Poi-tl简介
  2. 2. 2、常用标签
    1. 2.1. 2.1 文本
    2. 2.2. 2.2 图片
    3. 2.3. 2.3 表格
    4. 2.4. 2.4 列表
    5. 2.5. 2.5 单系列图表
    6. 2.6. 2.6 多系列图表
  3. 3. 3、代码封装
    1. 3.1. 3.1 创建标签类型
    2. 3.2. 3.2 创建公共实体
    3. 3.3. 3.3 封装统一数据生成接口
    4. 3.4. 3.4 创建工厂
    5. 3.5. 3.5 封装word生成类
    6. 3.6. 3.6 各标签生成数据封装
      1. 3.6.1. 3.6.1 文本封装
        1. 3.6.1.1. 3.6.1.1 创建文本实体
        2. 3.6.1.2. 3.6.1.2 创建数据封装类
      2. 3.6.2. 3.6.2 图片封装
        1. 3.6.2.1. 3.6.2.1 创建图片实体
        2. 3.6.2.2. 3.6.2.2 创建数据封装类
      3. 3.6.3. 3.6.3 表格封装
        1. 3.6.3.1. 3.6.3.1 创建表格实体
        2. 3.6.3.2. 3.6.3.2 创建数据封装类
      4. 3.6.4. 3.6.4 列表封装
        1. 3.6.4.1. 3.6.4.1 创建列表实体
        2. 3.6.4.2. 3.6.4.2 创建数据封装类
      5. 3.6.5. 3.6.5 图表封装
        1. 3.6.5.1. 3.6.5.1 创建图表实体
        2. 3.6.5.2. 3.6.5.2 创建数据封装类
  4. 4. 4、测试
    1. 4.1. 4.1 创建文件模板
    2. 4.2. 4.2 封装各标签数据
    3. 4.3. 4.3 生成报表