當前位置: 妍妍網 > 碼農

SpringBoot + POI-TL 操作 Word,快速生成報表,短小精悍!

2024-03-22碼農

來源|juejin.cn/post/7241171237603147831

👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 贈書福利

全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 http://116.62.199.48/ ,新計畫正在醞釀中。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了239小節,累計38w+字,講解圖:1645張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,

前段時間做了一個需求:需要快速生成一份數據報告,裏麵包含了文字、圖片和數據報表,同時生成的圖形數據也可以隨意修改。之前想著使用Apache POI來進行實作,在翻閱一些資料後,發現poi-tl更適合我們的業務,也更容易上手,於是對其進行了研究、也對其中的一些標簽進行了封裝,形成一個快速實作的工具類。

廢話不多說,直接開擼。

1、Poi-tl簡介

poi-tl是一個免費開源的Java類別庫,是基於Apache POI的樣版引擎,純Java元件,跨平台,程式碼短小精悍,透過外掛程式機制使其具有高度擴充套件性。

因此在使用的時候需要實作設定好樣版,就像Freemarker一樣,但是比其簡單,也易操作。

2、常用標簽

2.1 文本

格式:{{var}}

數據模型:

圖片

例如:

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

2.2 圖片

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

數據模型:

圖片

例如:

// 本地圖片
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}}

數據模型:

圖片

2.4 列表

格式:以 * 開頭,{{*var}}

數據模型:

圖片

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 singleSeriesRenderData = new ChartSingleSeriesRenderData();
singleSeriesRenderData.setCategories(new String[] { "俄羅斯""加拿大""美國""中國" }");
singleSeriesRenderData.setChartTitle("
測試");
pie.setSeriesData(new SeriesRenderData("
countries", new Integer[] { 17098242, 9984670, 9826675, 9596961 }));

2.6 多系列圖表

在報表套用中,很多時候使用的是多系列組合,例如:柱狀圖與折線圖組合等。

格式:與單系列一致。

圖片

數據模型:

圖片

例如:

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(){
    returntype;
    }
    }

    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

    👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 贈書福利

    全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 http://116.62.199.48/ ,新計畫正在醞釀中。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了239小節,累計38w+字,講解圖:1645張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,


    1. 

    2. 

    3. 

    4. 

    最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。

    獲取方式:點「在看」,關註公眾號並回復 Java 領取,更多內容陸續奉上。

    PS:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下在看,加個星標,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。

    「在看」支持小哈呀,謝謝啦