poi tl 导出word
1. 核心概念与模板语法
在使用
poi-tl 进行 Word 模板渲染时,针对循环列表生成表格行的场景,有以下特殊语法规则:- 循环开始标识:使用
{{#listName}}标记循环的开始(通常位于表格的第一行或表头下方)。 - 属性引用:在循环体内部,引用集合中对象的属性时,必须使用方括号
[propertyName],而不是普通的双花括号{{propertyName}}。 - 循环结束:不需要显式的结束标签,引擎会自动根据表格结构判断循环范围。
2. 后端实现 (Java)
2..1 依赖准备
确保项目中引入了 poi-tl 依赖:
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.4</version> <!-- 请使用最新稳定版 -->
</dependency>
2.2 业务调用层
构建数据模型,区分普通文本、静态表格数据和动态循环数据。
public void exportWord(DealAfterReport dealAfterReport, HttpServletResponse response) throws Exception { Map<String, Object> dataMap = new HashMap<>(); // 1. 基础对象数据 (对应模板 {{dealBase.dealName}}) DealBase dealBase = new DealBase(); dealBase.setDealName("dealName*************"); dataMap.put("dealBase", dealBase); // 2. 普通文本 (对应模板 {{title}}) dataMap.put("title", "年度财务报告"); // 3. 静态表格数据 (对应模板 {{tableTest}}) // 这种表格行数固定,直接渲染 RowRenderData row0 = Rows.of("姓名", "学历").textColor("FFFFFF").bgColor("4472C4").center().create(); RowRenderData row1 = Rows.create("李四", "博士"); dataMap.put("tableTest", Tables.create(row0, row1)); // 4. 动态循环数据 (对应模板 {{#aaa}}) // 这里的 Key "aaa" 必须与模板中的 {{#aaa}} 一致 List<Map<String, Object>> risks = new ArrayList<>(); risks.add(Map.of("name", "技术风险", "level", "高")); risks.add(Map.of("name", "市场风险", "level", "中")); dataMap.put("aaa", risks); // 5. 定义需要循环渲染的标签名列表 List<String> loopTags = Arrays.asList("aaa"); // 6. 调用通用工具 exportWordUtils(dataMap, response, "季度投后管理报告模板.docx", "年度财务报告.docx", loopTags); }
2.3 通用导出工具类
封装了配置构建、流处理和 HTTP 响应头设置。
/** * 通用 Word 导出工具方法 */ public void exportWordUtils(Map<String, Object> dataMap, HttpServletResponse response, String templateName, String outFileName, List<String> loopTags) throws Exception { // 1. 设置 HTTP 响应头 (处理中文文件名乱码) String encodedFileName = URLEncoder.encode(outFileName, StandardCharsets.UTF_8.name()) .replaceAll("\\+", "%20"); response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName); // 2. 读取模板文件 String templatePath = "/template/" + templateName; try (InputStream templateIs = this.getClass().getResourceAsStream(templatePath)) { if (templateIs == null) { throw new RuntimeException("模板文件未找到:" + templatePath); } // 3. 动态构建 Configure 配置 // HackLoopTableRenderPolicy 是处理复杂循环表格的关键策略 ConfigureBuilder builder = Configure.builder(); HackLoopTableRenderPolicy policy = new HackLoopTableRenderPolicy(); if (loopTags != null && !loopTags.isEmpty()) { for (String tag : loopTags) { // 将标签名绑定到策略上 builder.bind(tag, policy); } } Configure config = builder.build(); // 4. 编译、渲染并写出 XWPFTemplate template = XWPFTemplate.compile(templateIs, config).render(dataMap); template.write(response.getOutputStream()); template.close(); // 务必关闭流 } }
3. 前端实现 (Vue.js)
handleExport(row) { const fileName = this.reportQuarterFormat(row) + '投后管理报告'; const baseURL = process.env.VUE_APP_BASE_API || ''; axios({ method: 'get', url: baseURL + '/afterReport/dealAfterReport/exportWord', params: { id: row.id }, responseType: 'blob' // 必须指定返回类型为 blob }) .then((response) => { // 【重要修正】 // 原代码 type: 'text/html;charset=UTF-8' 是错误的! // 必须使用 application/vnd.openxmlformats-officedocument.wordprocessingml.document // 或者简写为 application/octet-stream const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = fileName + '.docx'; // 建议后缀使用 .docx 以匹配真实格式 document.body.appendChild(link); link.click(); // 清理 DOM 和内存 document.body.removeChild(link); window.URL.revokeObjectURL(link.href); }) .catch((error) => { console.error('导出失败:', error); this.$message.error('导出失败'); }); }

浙公网安备 33010602011771号