java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java easyexcel主子表报表打印

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 提供了灵活的方式来实现主子表结构的报表导出,主要有两种方式:

  1. 使用多个 sheet 分别展示主表和子表数据
  2. 在单个 sheet 中使用合并单元格的方式展示主子表关系

第二种方式更符合传统的报表展示形式,但实现起来稍复杂,需要自定义合并策略。根据实际需求选择合适的方式即可。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文