Poi导出动态表格-数据渲染(支持单元格合并)-Word

全文介绍

poi-tl(poi template language)是Word模板引擎,使用Word模板和数据创建很棒的Word文档 。
常用标签介绍(官网):

  1. 文本:{{var}} 2. 图片:{{@var}}
  2. 表格:{{#var}} 4. 列表:{{*var}}
  3. 区块对:{{?sections}}{{/sections}} 6. 嵌套:{{+var}}

本次制作的Demo,简写代码、多注释,只为容易理解,读者可根据自己需求进行重构代码、优化代码。

  1. 使用到{{#var}}单个表格渲染(无模板)
  2. 使用到{{+var}}多个表格渲染-嵌套模板(无模板)
  3. 使用到{{var}}}单个表格渲染-标记表(有模板)
  4. 使用到{{?sections}}{{/sections}}多个表格渲染-标记位置(有模板)

功能需求

导出的word中存在单个表格, 或动态的多个表格

Demo: boot项目实现业务===>服务器信息表格

环境配置

项目依赖引入poi-tl依赖

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

<!--*的lombok插件库 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<!--*的hutool工具包 -->
<dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
     <version>5.7.14</version>
 </dependency>

表格渲染

根据模板

单个多个表格模板如下:
单个
多个

poi-tl提供了抽象表格策略类 DynamicTableRenderPolicy
我们可以自定义模板渲染策略类,继承即可,从而动态渲染的部分单元格,实现我们需求
-- 注意点: 
	{{serverListTable}}可以放到表格中任意处,可识别到该表格即可

代码实现

  1. 新建数据存储实体类-ServerTableData-
@Data
public class ServerTableData {
    /*
       携带表格中真实数据
     */
    private List<RowRenderData> serverDataList;

    /*
        携带要分组的信息
     */
    private List<Map<String, Object>> groupDataList;
}
  1. 新建自定义表格渲染策略类-ServerTablePolicy-
public class ServerTablePolicy extends DynamicTableRenderPolicy {

    @Override
    public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
        if (null == tableData) {
            return;
        }
        
		// 参数数据声明
        ServerTableData serverTableData = (ServerTableData) tableData;
        List<RowRenderData> serverDataList = serverTableData.getServerDataList();
        List<Map<String, Object>> groupDataList = serverTableData.getGroupDataList();

        if (CollUtil.isNotEmpty(serverDataList)) {
            // 先删除一行, demo中第一行是为了调整 三线表 样式
            xwpfTable.removeRow(1);

            // 行从中间插入, 因此采用倒序渲染数据
            for (int i = serverDataList.size() - 1; i >= 0; i--) {
                XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
                newRow.setHeight(400);
                for (int j = 0; j < 4; j++) {
                    newRow.createCell();
                }
                // 渲染一行数据
                TableRenderPolicy.Helper.renderRow(newRow, serverDataList.get(i));
            }

            // 处理合并
            for (int i = 0; i < serverDataList.size(); i++) {
                // 获取要合并的名称那一列数据 (样本采用第一列)
                String typeNameData = serverDataList.get(i).getCells().get(0).getParagraphs().get(0).getContents().get(0).toString();
                for (int j = 0; j < groupDataList.size(); j++) {
                    String typeNameTemplate = String.valueOf(groupDataList.get(j).get("typeName"));
                    int listSize = Integer.parseInt(String.valueOf(groupDataList.get(j).get("listSize")));

                    // 若匹配上 就直接合并
                    if (typeNameTemplate.equals(typeNameData)) {
                        TableTools.mergeCellsVertically(xwpfTable, 0, i + 1, i + listSize);
                        groupDataList.remove(j);
                        break;
                    }
                }

                // 生成行数据时候已经设置水平, 在此处进行垂直居中
                for (int j = 0; j < 4; j++){
                    XWPFTableCell cell = xwpfTable.getRow(i + 1).getCell(j);
                    cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
                }
            }
        }
    }
}
  1. 新建业务实现类(数据来源)-ExportWordServiceImpl-
@Service("exportWord")
public class ExportWordServiceImpl implements ExportWordService {

    /**
    * @Description: 单个表格数据渲染(测试 合并单元格)
    */
    @Override
    public XWPFTemplate oneTable() {
        // 获取模板文件流
        InputStream resourceAsStream =
                this.getClass().getResourceAsStream("/templates/serverInfo.docx");
        // word数据集合
        Map<String, Object> params = new HashMap<>();

        // 伪造一个表格数据
        ServerTableData tableData = getServerTableData();

        // 赋值, word解析
        params.put("serverListTable", tableData);
        Configure configure = Configure.builder()
                .bind("serverListTable", new ServerTablePolicy())
                .build();
        return XWPFTemplate.compile(Objects.requireNonNull(resourceAsStream), configure).render(params);
    }

    /**
    * @Description: 动态多个表格
    */
    @Override
    public XWPFTemplate dynamicTable() {
        // 获取模板文件流
        InputStream resourceAsStream =
                this.getClass().getResourceAsStream("/templates/dynamicServerInfo.docx");
        // word数据集合
        Map<String, Object> params = new HashMap<>();

        List<Map<String, Object>> dynamicFlag = new ArrayList<>();
        // 伪造3个表格数据
        for (int i = 0; i < 3; i++) {
            ServerTableData tableData = getServerTableData();
            Map<String, Object> dynamicTableMap = new HashMap<>();
            dynamicTableMap.put("serverListTable", tableData);
            dynamicTableMap.put("firstTitle", "一级标题");
            dynamicTableMap.put("secondTitle", "二级标题");
            dynamicTableMap.put("tableName", "表名");
            dynamicFlag.add(dynamicTableMap);
        }

        // 赋值, word解析
        params.put("dynamicFlag", dynamicFlag);
        Configure configure = Configure.builder()
                .bind("serverListTable", new ServerTablePolicy())
                .build();
        return XWPFTemplate.compile(Objects.requireNonNull(resourceAsStream), configure).render(params);
    }

	/**
    * @Description: 模拟表格中的真实数据
    */
    private ServerTableData getServerTableData() {
        ServerTableData serverTableData = new ServerTableData();
        List<RowRenderData> serverDataList = new ArrayList<>();
        for (int j = 0; j < 4; j++) {
            String typeName = "数据库服务器";
            if (j > 1) {
                typeName = "应用服务器";
            }

            RowRenderData serverData = Rows.of(typeName, "4c处理器", "Win11", "127.0.0.1").center().create();
            serverDataList.add(serverData);
        }

        List<Map<String, Object>> groupDataList = new ArrayList<>();
        Map<String, Object> groupData1 = new HashMap<>();
        groupData1.put("typeName", "数据库服务器");
        groupData1.put("listSize", "2");
        Map<String, Object> groupData2 = new HashMap<>();
        groupData2.put("typeName", "应用服务器");
        groupData2.put("listSize", "2");
        groupDataList.add(groupData1);
        groupDataList.add(groupData2);

        serverTableData.setServerDataList(serverDataList);
        serverTableData.setGroupDataList(groupDataList);
        return serverTableData;
    }
}
  1. 新建接口类,实现下载word-ExportWordController-
@Slf4j
@RestController
@RequestMapping("/poi")
public class ExportWordController {

    @Resource(name = "exportWord")
    private ExportWordService exportWordService;

    /**
    * @Description: 合并单元格
    */
    @GetMapping("one-table")
    public void oneTable() {
        poiExportWord("单个表格数据渲染.docx", exportWordService.oneTable());
    }

    /**
    * @Description: 多表格渲染
    */
    @GetMapping("dynamic-table")
    public void dynamicTable() {
        poiExportWord("多表格数据渲染.docx", exportWordService.dynamicTable());
    }

    // 导出文档Header信息
    public static final String CONTENT_DISPOSITION_KEY = "Content-disposition";
    public static final String CONTENT_DISPOSITION_VALUE = "attachment; filename={};filename*=utf-8''{}";
    public static final String URL_ENCODER_CHARSET = "UTF-8";

    @Resource
    private HttpServletResponse response;

    /**
     * @Description: POI-导出word文档
     * @Params: [fileName, xwpfTemplate] 文件名称, poi-word解析模板
     */
    @SneakyThrows(value = Exception.class)
    private void poiExportWord(String fileName, XWPFTemplate xwpfTemplate) {
        String encodeFileName = URLEncoder.encode(fileName, URL_ENCODER_CHARSET);
        // 弹出下载框并且回填文件名
        response.setHeader(CONTENT_DISPOSITION_KEY,
                StrUtil.format(CONTENT_DISPOSITION_VALUE, encodeFileName, encodeFileName));
        xwpfTemplate.write(response.getOutputStream());
        xwpfTemplate.close();
    }
}

接口调用:

  1. GET: http://localhost:8001/poi/one-table
  2. GET: http://localhost:8001/poi/dynamic-table
    效果图:
    单个
    多个

无需模板

单表

模板如下:单个

代码实现
	下面代码依旧在上面的接口类,实现类中
	ExportWordController、ExportWordServiceImpl
private static final String[] SERVER_HEADER  = new String[]{"设备名称","硬件配置","软件版本","IP和网关地址"};

@Override
public XWPFTemplate oneTableNo() {
    // 获取模板文件流
    InputStream resourceAsStream =
            this.getClass().getResourceAsStream("/templates/oneTableWithout.docx");

    // word数据集合
    Map<String, Object> params = new HashMap<>();

    // 表格样式
    BorderStyle borderStyle = new BorderStyle();
    borderStyle.setColor("000000");
    borderStyle.setSize(5);
    borderStyle.setType(XWPFTable.XWPFBorderType.SINGLE);

    // 单元格合并声明
    MergeCellRule.MergeCellRuleBuilder mergeCellRuleBuilder = MergeCellRule.builder();
    mergeCellRuleBuilder.map(MergeCellRule.Grid.of(1, 0), MergeCellRule.Grid.of(2, 0));
    mergeCellRuleBuilder.map(MergeCellRule.Grid.of(3, 0), MergeCellRule.Grid.of(4, 0));

    RowRenderData serverHeader = getTableHeaderRow(SERVER_HEADER);
    Tables.TableBuilder tableBuilder = Tables.ofA4MediumWidth().addRow(serverHeader);

    RowRenderData serverRow1 = Rows.of("数据库服务器", "4c处理器", "Win11", "127.0.0.1")
            .center().textFontSize(10).create();
    RowRenderData serverRow2 = Rows.of("数据库服务器", "4c处理器", "Win11", "127.0.0.1")
            .center().textFontSize(10).create();
    RowRenderData serverRow3 = Rows.of("应用服务器", "4c处理器", "Win11", "127.0.0.1")
            .center().textFontSize(10).create();
    RowRenderData serverRow4 = Rows.of("应用服务器", "4c处理器", "Win11", "127.0.0.1")
            .center().textFontSize(10).create();

    TableRenderData serverTable = tableBuilder.addRow(serverRow1).addRow(serverRow2).addRow(serverRow3)
            .addRow(serverRow4).border(borderStyle).center().create();
	serverTable.setMergeRule(mergeCellRuleBuilder.build());
    params.put("serverListTable", serverTable);

    return XWPFTemplate.compile(Objects.requireNonNull(resourceAsStream)).render(params);
}

/**
 * 表头行 统一样式
 */
private RowRenderData getTableHeaderRow(String[] header) {
    return Rows.of(header).center()
            .textColor("000000").textFontSize(10).textBold().create();
}
/**
* @Description: 单个表格-无需模板
*/
@GetMapping("one-table-no")
public void oneTableNo() {
	poiExportWord("无模板单个表格渲染.docx", exportWordService.oneTableNo());
}

接口调用:

  1. GET: http://localhost:8001/poi/one-table-no
    效果图:
    单个

多表

模板: 两个,一个主模板,一个是内嵌模板(用于生成多个表格)
多个

// 代码基本与单个是一致的, 下面只展示差异部分

// 将子模板流赋值给主模板参数
InputStream innerInputStream =
     this.getClass().getResourceAsStream("/templates/inner.docx");
DocxRenderData innerData = new DocxRenderData(innerInputStream, dataList);
params.put("innerTable", innerData);

/*
	注意这里的dataList为 List<Map<String, Object>>参数传递而来
	该参数为整理好的内嵌模板数据(List集合)
	比如举例说明动态生成了一个:
	
	Map<String, Object> map1 = new HashMap<>();
    // 收集表格数据
    TableRenderData tableData1 = new TableRenderData();
    map1.put("firstTitle", "一级标题");
    map1.put("secondTitle", "二级标题");
    map1.put("tableName", "表格名称");
    map1.put("serverListTable", tableData1);
    dataList.add(map1);
*/
        
// 获取主模板文件流(后续代码真正使用的流)
InputStream resourceAsStream =
     this.getClass().getResourceAsStream("/templates/dynamicTableWithout.docx");

文末说明

如若需要本人项目代码-可评论联系me.
根据模板-渲染表格数据是参考该大牛写的文章实现的: https://blog.csdn.net/qq_26383975/article/details/112238802

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值