java easyexcel主子表報(bào)表打印實(shí)踐
Java EasyExcel 主子表報(bào)表打印
EasyExcel 是阿里巴巴開源的一個基于 Java 的簡單、省內(nèi)存的讀寫 Excel 的工具,特別適合大數(shù)據(jù)量的 Excel 操作。
下面我將介紹如何使用 EasyExcel 實(shí)現(xiàn)主子表結(jié)構(gòu)的報(bào)表打印。
1. 添加依賴
首先需要在項(xiàng)目中添加 EasyExcel 依賴:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version> <!-- 使用最新版本 -->
</dependency>2. 主子表數(shù)據(jù)結(jié)構(gòu)
假設(shè)我們有一個訂單(主表)和訂單項(xiàng)(子表)的結(jié)構(gòu):
// 主表數(shù)據(jù)
public class Order {
private String orderNo;
private Date orderDate;
private String customerName;
private List<OrderItem> items;
// getters and setters
}
// 子表數(shù)據(jù)
public class OrderItem {
private String productName;
private Integer quantity;
private BigDecimal price;
private BigDecimal amount;
// getters and setters
}3. 實(shí)現(xiàn)主子表導(dǎo)出
方法一:使用合并單元格方式
public class OrderExcelExport {
public static void exportOrders(List<Order> orders, HttpServletResponse response) throws IOException {
String fileName = "訂單報(bào)表_" + 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, "訂單明細(xì)")
.head(OrderItem.class)
.build();
// 寫入主表數(shù)據(jù)
excelWriter.write(orders, mainSheet);
// 寫入子表數(shù)據(jù)
for (Order order : orders) {
excelWriter.write(order.getItems(), itemSheet);
}
excelWriter.finish();
}
}方法二:單個sheet中展示主子表關(guān)系
public class OrderExcelExport {
public static void exportOrdersInOneSheet(List<Order> orders, HttpServletResponse response) throws IOException {
String fileName = "訂單報(bào)表_" + 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("訂單及明細(xì)")
.head(OrderExcelData.class)
.build();
// 轉(zhuǎn)換數(shù)據(jù)
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數(shù)據(jù)模型
public class OrderExcelData {
@ExcelProperty("訂單編號")
private String orderNo;
@ExcelProperty("訂單日期")
@DateTimeFormat("yyyy-MM-dd")
private Date orderDate;
@ExcelProperty("客戶名稱")
private String customerName;
@ExcelProperty("產(chǎn)品名稱")
private String productName;
@ExcelProperty("數(shù)量")
private Integer quantity;
@ExcelProperty("單價")
private BigDecimal price;
@ExcelProperty("金額")
private BigDecimal amount;
// getters and setters
}4. 控制器調(diào)用
@RestController
@RequestMapping("/api/order")
public class OrderController {
@GetMapping("/export")
public void exportOrders(HttpServletResponse response) throws IOException {
// 模擬數(shù)據(jù)
List<Order> orders = getMockOrders();
// 導(dǎo)出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("產(chǎn)品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("產(chǎn)品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("產(chǎn)品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();
// 標(biāo)題樣式
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);
}
// 數(shù)據(jù)行樣式
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) {
}
}然后在導(dǎo)出時注冊這個樣式策略:
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.registerWriteHandler(new OrderMergeStrategy(orders))
.registerWriteHandler(new CustomCellStyleStrategy())
.build();總結(jié)
EasyExcel 提供了靈活的方式來實(shí)現(xiàn)主子表結(jié)構(gòu)的報(bào)表導(dǎo)出,主要有兩種方式:
- 使用多個 sheet 分別展示主表和子表數(shù)據(jù)
- 在單個 sheet 中使用合并單元格的方式展示主子表關(guān)系
第二種方式更符合傳統(tǒng)的報(bào)表展示形式,但實(shí)現(xiàn)起來稍復(fù)雜,需要自定義合并策略。根據(jù)實(shí)際需求選擇合適的方式即可。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot根據(jù)實(shí)體類生成表的實(shí)現(xiàn)方法
本文介紹了如何通過SpringBoot工程引入SpringDataJPA,并通過實(shí)體類自動生成數(shù)據(jù)庫表的過程,包括常見問題解決方法,感興趣的可以了解一下2024-09-09
Spring Validation數(shù)據(jù)校驗(yàn)詳解
SpringValidation是Spring框架的數(shù)據(jù)校驗(yàn)抽象層,支持注解、分組、嵌套校驗(yàn)及國際化,默認(rèn)集成HibernateValidator,可自定義校驗(yàn)器滿足復(fù)雜需求,適用于Web參數(shù)校驗(yàn)和業(yè)務(wù)邏輯驗(yàn)證2025-09-09
IntelliJ IDEA的build path設(shè)置方法
這篇文章主要介紹了IntelliJ IDEA的build path設(shè)置方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
java中動態(tài)代理如何實(shí)現(xiàn)詳解
動態(tài)代理是基于接口實(shí)現(xiàn)的代理,mybatis就是用這個技術(shù)實(shí)現(xiàn)的,下面這篇文章主要給大家介紹了關(guān)于java中動態(tài)代理如何實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2024-01-01
java實(shí)現(xiàn)ssh登錄linux并執(zhí)行命令的三種實(shí)現(xiàn)方式
文章介紹了三種在Java中實(shí)現(xiàn)SSH登錄Linux并執(zhí)行命令的方法,包括使用ganymed-ssh2、jsch和sshd-core,由于ganymed-ssh2和jsch的最新版本較舊,可能無法與較新的Linux系統(tǒng)兼容,而sshd-core一直在更新,推薦使用2024-11-11
SpringBoot接收數(shù)組參數(shù)和集合參數(shù)方式
這篇文章主要介紹了SpringBoot接收數(shù)組參數(shù)和集合參數(shù)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03
Java SpringBoot在RequestBody中高效的使用枚舉參數(shù)原理案例詳解
這篇文章主要介紹了Java SpringBoot在RequestBody中高效的使用枚舉參數(shù)原理案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
解決SpringMVC同時接收J(rèn)son和Restful時Request里有Map的問題
今天小編就為大家分享一篇解決SpringMVC同時接收J(rèn)son和Restful時Request里有Map的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
java多線程應(yīng)用實(shí)現(xiàn)方法
以前沒有寫筆記的習(xí)慣,現(xiàn)在慢慢的發(fā)現(xiàn)及時總結(jié)是多么的重要了,呵呵。雖然才大二,但是也快要畢業(yè)了,要加油2012-11-11

