java easyexcel主子表报表打印实践
作者:leijmdas
EasyExcel支持主子表报表导出,可通过多sheet分开展示或单sheet合并单元格方式实现,后者需自定义策略,适合传统报表形式,根据需求选择合适方案即可
Java EasyExcel 主子表报表打印
EasyExcel 是阿里巴巴开源的一个基于 Java 的简单、省内存的读写 Excel 的工具,特别适合大数据量的 Excel 操作。
下面我将介绍如何使用 EasyExcel 实现主子表结构的报表打印。
1. 添加依赖
首先需要在项目中添加 EasyExcel 依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version> <!-- 使用最新版本 -->
</dependency>2. 主子表数据结构
假设我们有一个订单(主表)和订单项(子表)的结构:
// 主表数据
public class Order {
private String orderNo;
private Date orderDate;
private String customerName;
private List<OrderItem> items;
// getters and setters
}
// 子表数据
public class OrderItem {
private String productName;
private Integer quantity;
private BigDecimal price;
private BigDecimal amount;
// getters and setters
}3. 实现主子表导出
方法一:使用合并单元格方式
public class OrderExcelExport {
public static void exportOrders(List<Order> orders, HttpServletResponse response) throws IOException {
String fileName = "订单报表_" + System.currentTimeMillis() + ".xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
// 主表sheet
WriteSheet mainSheet = EasyExcel.writerSheet(0, "订单信息")
.head(Order.class)
.build();
// 子表sheet
WriteSheet itemSheet = EasyExcel.writerSheet(1, "订单明细")
.head(OrderItem.class)
.build();
// 写入主表数据
excelWriter.write(orders, mainSheet);
// 写入子表数据
for (Order order : orders) {
excelWriter.write(order.getItems(), itemSheet);
}
excelWriter.finish();
}
}方法二:单个sheet中展示主子表关系
public class OrderExcelExport {
public static void exportOrdersInOneSheet(List<Order> orders, HttpServletResponse response) throws IOException {
String fileName = "订单报表_" + System.currentTimeMillis() + ".xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 自定义合并策略
HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy();
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.registerWriteHandler(new OrderMergeStrategy(orders))
.registerWriteHandler(styleStrategy)
.build();
WriteSheet sheet = EasyExcel.writerSheet("订单及明细")
.head(OrderExcelData.class)
.build();
// 转换数据
List<OrderExcelData> dataList = convertToExcelData(orders);
excelWriter.write(dataList, sheet);
excelWriter.finish();
}
private static List<OrderExcelData> convertToExcelData(List<Order> orders) {
List<OrderExcelData> result = new ArrayList<>();
for (Order order : orders) {
boolean isFirstItem = true;
for (OrderItem item : order.getItems()) {
OrderExcelData data = new OrderExcelData();
if (isFirstItem) {
data.setOrderNo(order.getOrderNo());
data.setOrderDate(order.getOrderDate());
data.setCustomerName(order.getCustomerName());
isFirstItem = false;
}
data.setProductName(item.getProductName());
data.setQuantity(item.getQuantity());
data.setPrice(item.getPrice());
data.setAmount(item.getAmount());
result.add(data);
}
}
return result;
}
}
// 合并策略
public class OrderMergeStrategy implements CellWriteHandler {
private List<Order> orders;
private int itemCount = 0;
public OrderMergeStrategy(List<Order> orders) {
this.orders = orders;
for (Order order : orders) {
itemCount += order.getItems().size();
}
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
// 合并主表信息单元格
if (cell.getRowIndex() == 1 && !isHead) {
for (int i = 0; i < orders.size(); i++) {
Order order = orders.get(i);
int itemSize = order.getItems().size();
if (itemSize > 1) {
// 合并订单号、日期、客户名等主表信息
if (cell.getColumnIndex() == 0) { // 订单号
writeSheetHolder.getSheet().addMergedRegionUnsafe(
new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + itemSize - 1, 0, 0));
} else if (cell.getColumnIndex() == 1) { // 订单日期
writeSheetHolder.getSheet().addMergedRegionUnsafe(
new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + itemSize - 1, 1, 1));
} else if (cell.getColumnIndex() == 2) { // 客户名
writeSheetHolder.getSheet().addMergedRegionUnsafe(
new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + itemSize - 1, 2, 2));
}
}
}
}
}
}
// Excel数据模型
public class OrderExcelData {
@ExcelProperty("订单编号")
private String orderNo;
@ExcelProperty("订单日期")
@DateTimeFormat("yyyy-MM-dd")
private Date orderDate;
@ExcelProperty("客户名称")
private String customerName;
@ExcelProperty("产品名称")
private String productName;
@ExcelProperty("数量")
private Integer quantity;
@ExcelProperty("单价")
private BigDecimal price;
@ExcelProperty("金额")
private BigDecimal amount;
// getters and setters
}4. 控制器调用
@RestController
@RequestMapping("/api/order")
public class OrderController {
@GetMapping("/export")
public void exportOrders(HttpServletResponse response) throws IOException {
// 模拟数据
List<Order> orders = getMockOrders();
// 导出Excel
OrderExcelExport.exportOrdersInOneSheet(orders, response);
}
private List<Order> getMockOrders() {
List<Order> orders = new ArrayList<>();
// 订单1
Order order1 = new Order();
order1.setOrderNo("ORD20230001");
order1.setOrderDate(new Date());
order1.setCustomerName("客户A");
List<OrderItem> items1 = new ArrayList<>();
OrderItem item1 = new OrderItem();
item1.setProductName("产品A");
item1.setQuantity(2);
item1.setPrice(new BigDecimal("100.00"));
item1.setAmount(new BigDecimal("200.00"));
items1.add(item1);
OrderItem item2 = new OrderItem();
item2.setProductName("产品B");
item2.setQuantity(1);
item2.setPrice(new BigDecimal("50.00"));
item2.setAmount(new BigDecimal("50.00"));
items1.add(item2);
order1.setItems(items1);
orders.add(order1);
// 订单2
Order order2 = new Order();
order2.setOrderNo("ORD20230002");
order2.setOrderDate(new Date());
order2.setCustomerName("客户B");
List<OrderItem> items2 = new ArrayList<>();
OrderItem item3 = new OrderItem();
item3.setProductName("产品C");
item3.setQuantity(3);
item3.setPrice(new BigDecimal("30.00"));
item3.setAmount(new BigDecimal("90.00"));
items2.add(item3);
order2.setItems(items2);
orders.add(order2);
return orders;
}
}5. 高级功能
自定义样式
// 自定义样式策略
public class CustomCellStyleStrategy implements CellWriteHandler {
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
// 标题样式
if (isHead) {
CellStyle cellStyle = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
font.setColor(IndexedColors.WHITE.getIndex());
cellStyle.setFont(font);
cellStyle.setFillForegroundColor(IndexedColors.BLUE.getIndex());
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cell.setCellStyle(cellStyle);
}
// 数据行样式
else {
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
// 金额列右对齐
if (cell.getColumnIndex() >= 5) {
cellStyle.setAlignment(HorizontalAlignment.RIGHT);
}
cell.setCellStyle(cellStyle);
}
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
}然后在导出时注册这个样式策略:
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.registerWriteHandler(new OrderMergeStrategy(orders))
.registerWriteHandler(new CustomCellStyleStrategy())
.build();总结
EasyExcel 提供了灵活的方式来实现主子表结构的报表导出,主要有两种方式:
- 使用多个 sheet 分别展示主表和子表数据
- 在单个 sheet 中使用合并单元格的方式展示主子表关系
第二种方式更符合传统的报表展示形式,但实现起来稍复杂,需要自定义合并策略。根据实际需求选择合适的方式即可。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
