效果图
poi-tl文档地址
1、导入依赖
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.29</version>
</dependency>
2、准备word模板
word模板中内容如下,放在resource目录下templates文件夹下(跟第五步导出接口中的模板路径对应)
DataTemplate.docx
3、准备数据
DataClass
@Data
public class DataClass {
/**
* 班级名称
*/
private String className;
/**
* 性别数据列表
*/
private List<DataSex> sexList;
}
DataSex
@Data
public class DataSex {
/**
* 性别
*/
private String sex;
/**
* 学生数据
*/
private List<DataStudent> dataStudentList;
}
DataStudent
@Data
public class DataStudent {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 分数
*/
private BigDecimal score;
}
测试数据mock方法
/**
* 模拟数据
*
* @return
*/
public static Map<String, Object> mockData() {
Map<String, Object> map = new HashMap<>();
// 创建一班的学生数据
DataStudent student1 = new DataStudent();
student1.setName("小明");
student1.setAge(16);
student1.setScore(new BigDecimal("89.5"));
DataStudent student2 = new DataStudent();
student2.setName("小红");
student2.setAge(17);
student2.setScore(new BigDecimal("92.0"));
DataStudent student3 = new DataStudent();
student3.setName("小刚");
student3.setAge(16);
student3.setScore(new BigDecimal("85.0"));
DataStudent student4 = new DataStudent();
student4.setName("小丽");
student4.setAge(17);
student4.setScore(new BigDecimal("90.0"));
// 创建二班的学生数据
DataStudent student5 = new DataStudent();
student5.setName("张伟");
student5.setAge(16);
student5.setScore(new BigDecimal("88.0"));
DataStudent student6 = new DataStudent();
student6.setName("王芳");
student6.setAge(17);
student6.setScore(new BigDecimal("91.0"));
DataStudent student7 = new DataStudent();
student7.setName("李强");
student7.setAge(16);
student7.setScore(new BigDecimal("86.5"));
DataStudent student8 = new DataStudent();
student8.setName("赵敏");
student8.setAge(17);
student8.setScore(new BigDecimal("89.0"));
// 创建一班的性别数据
DataSex maleSexClass1 = new DataSex();
maleSexClass1.setSex("男");
maleSexClass1.setDataStudentList(Arrays.asList(student1));
DataSex femaleSexClass1 = new DataSex();
femaleSexClass1.setSex("女");
femaleSexClass1.setDataStudentList(Arrays.asList(student2, student4));
// 创建二班的性别数据
DataSex maleSexClass2 = new DataSex();
maleSexClass2.setSex("男");
maleSexClass2.setDataStudentList(Arrays.asList(student3, student5, student7));
DataSex femaleSexClass2 = new DataSex();
femaleSexClass2.setSex("女");
femaleSexClass2.setDataStudentList(Arrays.asList(student6, student8));
// 创建班级数据
DataClass class1 = new DataClass();
class1.setClassName("一班");
class1.setSexList(Arrays.asList(maleSexClass1, femaleSexClass1));
DataClass class2 = new DataClass();
class2.setClassName("二班");
class2.setSexList(Arrays.asList(maleSexClass2, femaleSexClass2));
// 创建班级列表
List<DataClass> classList = new ArrayList<>();
classList.add(class1);
classList.add(class2);
// 输出模拟数据
for (DataClass dataClass : classList) {
System.out.println("班级名称: " + dataClass.getClassName());
for (DataSex dataSex : dataClass.getSexList()) {
System.out.println(" 性别: " + dataSex.getSex());
for (DataStudent dataStudent : dataSex.getDataStudentList()) {
System.out.println(" 学生姓名: " + dataStudent.getName() + ", 年龄: " + dataStudent.getAge() + ", 分数: " + dataStudent.getScore());
}
}
}
map.put("dataList", classList);
return map;
}
4、编写DataPolicy
继承DynamicTableRenderPolicy后,实现抽象方法render中对表格数据进行渲染数据和处理
import com.deepoove.poi.data.*;
import com.deepoove.poi.data.style.Style;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.TableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import com.zhou.entity.DataClass;
import com.zhou.entity.DataSex;
import com.zhou.entity.DataStudent;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import java.util.List;
/**
* @Author: zhoujinchuan
* @Description: (请添加描述)
* @CreateTime: 2024/6/18 16:25
*/
public class DataPolicy extends DynamicTableRenderPolicy {
/**
*
* @param xwpfTable 数据的表格对象
* @param tableData 传入的数据对象
* @throws Exception
*/
@Override
public void render(XWPFTable xwpfTable, Object tableData) throws Exception {
if (null == tableData) {
return;
}
List<DataClass> list = (List<DataClass>)tableData;
//这里是表格的起始行数,行数从0开始,除去表头是1行
int startRow = 1;
//加粗样式
Style style = new Style();
style.setBold(true);
// 行从中间插入, 因此采用倒序渲染数据
for (int i = list.size() - 1; i >= 0; i--) {
DataClass oneVo = list.get(i);
RowRenderData serverData;
// 第一列数据
TextRenderData textRenderData = new TextRenderData();
textRenderData.setText(oneVo.getClassName());
textRenderData.setStyle(style);
CellRenderData cellRenderData = new CellRenderData().addParagraph(new ParagraphRenderData().addText(textRenderData));
//获取第二层数据
List<DataSex> twoList = oneVo.getSexList();
for (int twoI = twoList.size() - 1; twoI >= 0; twoI--) {
DataSex twoVo = twoList.get(twoI);
// 第二列数据
TextRenderData textRenderData2 = new TextRenderData();
textRenderData2.setStyle(style);
textRenderData2.setText(String.valueOf(twoVo.getSex()));
CellRenderData cellRenderData2 = new CellRenderData().addParagraph(new ParagraphRenderData().addText(textRenderData2));
//获取第三层的数据
List<DataStudent> threeList = twoVo.getDataStudentList();
int threeSize = threeList.size();
for (int threeI = threeSize - 1; threeI >= 0; threeI--) {
XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
//添加一行的单元格个数为5个
for (int j = 0; j < 5; j++) {
newRow.createCell();
}
//这就是完整的一行数据
serverData = Rows.of(cellRenderData)
.addCell(cellRenderData2)
.addCell(new CellRenderData().addParagraph(new ParagraphRenderData().addText(threeList.get(threeI).getName() == null ? "" : threeList.get(threeI).getName())))
.addCell(new CellRenderData().addParagraph(new ParagraphRenderData().addText(threeList.get(threeI).getAge() == null ? "" : String.valueOf(threeList.get(threeI).getAge()))))
.addCell(new CellRenderData().addParagraph(new ParagraphRenderData().addText(threeList.get(threeI).getScore() == null ? "" : threeList.get(threeI).getScore().toString())))
.center().create();
//渲染这一行数据
TableRenderPolicy.Helper.renderRow(newRow, serverData);
}
}
}
//渲染完数据后合并对应单元格
int startOneLevel = startRow;
int startTwoLevel = startRow;
for (DataClass oneVo : list) {
int threeTotal = 0;
for (DataSex twoVo : oneVo.getSexList()) {
int threeSize = twoVo.getDataStudentList().size();
//二级---性别----如果需要合并,合并对应第二列对应行数
if (threeSize > 1){
//参数,表格对象,列数,开始行,结束行
TableTools.mergeCellsVertically(xwpfTable, 1, startTwoLevel, (startTwoLevel + threeSize - 1));
}
startTwoLevel += threeSize;
threeTotal += threeSize;
}
//一级---班级----如果需要合并,合并对应第一列对应行数
if (threeTotal > 1){
//参数,表格对象,列数,开始行,结束行
TableTools.mergeCellsVertically(xwpfTable, 0, startOneLevel, (startOneLevel + threeTotal - 1));
}
startOneLevel += threeTotal ;
}
}
}
XWPFTableRow newRow = xwpfTable.insertNewTableRow(1);
这行代码体现了从中间插入,采用倒序渲染数据的原因,参数1代表插入第二行,序号从0开始
这个代码操作类似在word中表头下方插入行操作
5、导出接口编写
@GetMapping("test")
public void test(HttpServletResponse response) {
InputStream inputStream = null;
XWPFTemplate template = null;
try {
// 获取数据映射
Map<String, Object> dataMap = mockData();
String templateName = "DataTemplate.docx";
String fileName = "数据.docx";
// 设置响应头,指定生成的文档名称
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 配置 FreeMarker
Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
//用于设置经典兼容模式。在这种模式下,如果模板中有未定义的变量或空值,FreeMarker 不会抛出异常,而是直接显示一个空字符串。
configuration.setClassicCompatible(true);
//模板的字符编码为 UTF-8
configuration.setEncoding(Locale.CHINA, "UTF-8");
// 设置模板加载路径
configuration.setClassForTemplateLoading(DemoController.class, "/templates/plan");
// 构建配置构造器
ConfigureBuilder builder = Configure.builder();
// 绑定数据策略
builder.bind("dataList", new DataPolicy()).build();
// 获取模板输入流
inputStream = getClass().getResourceAsStream("/templates/" + templateName);
// 编译并渲染模板
template = XWPFTemplate.compile(inputStream, builder.build()).render(dataMap);
// 渲染模板并输出到响应流
template.write(response.getOutputStream());
response.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
// 处理异常,例如返回一个错误的响应状态码
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
// 关闭输入流
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 关闭模板
if (template != null) {
try {
template.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 关闭输出流
try {
response.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}