【EasyExcel】EasyExcel导出表格包含合计行、自定义样式、自适应列宽

目录

0 EasyExcel简介

在数据处理和报表生成的过程中,Excel是一个非常常用的工具。特别是在Java开发中,EasyExcel库因其简单高效而备受欢迎。

EasyExcel是阿里巴巴开源的一个Excel处理库,特别适用于大数据量的Excel文件的读写。它的优点在于使用简单、性能高效,特别适合在Java项目中处理Excel文件。

1 Excel导出工具类

Excel导出工具类ExcelUtil类代码如下:

java 复制代码
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import lombok.Data;
import org.apache.poi.ss.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.List;

@Data
public class ExcelUtil{

	public static void downloadExcelWithHead(HttpServletResponse response, List<?> list, String sheetName, List<List<String>> rowHead) throws IOException {
	        if (list.isEmpty()) {
	            throw new IllegalArgumentException("列表不能为空!");
	        }
	
	        Class<?> clazz = list.get(0).getClass();
	        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
	        response.setCharacterEncoding("utf-8");
	        String fileName = URLEncoder.encode(sheetName, "UTF-8").replaceAll("\\+", "%20");
	        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
	        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), clazz).excelType(ExcelTypeEnum.XLSX).build();
	
	        // 创建 ExcelWriterBuilder
	        ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream(), clazz)
	                .autoCloseStream(true)
	                .needHead(true);
	
	        excelWriterBuilder.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
	        excelWriterBuilder.useDefaultStyle(false);
	
	        // 创建 WriteSheet 并设置动态标题行
	        WriteSheet writeSheet = EasyExcel.writerSheet(sheetName)
	                .head(rowHead) // 设置标题行
	                .registerWriteHandler(setCellStyle()) // 设置表头样式
	                .registerWriteHandler(new CustomCellWriteUtil()) // 设置列宽自适应
	                .build();
	
	        // 写入数据
	        excelWriter.write(list, writeSheet);
	        excelWriter.finish();
	    }
	}

设置自定义表头样式

其中,downloadExcelWithHead()中调用setCellStyle()方法用于设置表头样式。setCellStyle()方法的代码如下:

java 复制代码
    public static HorizontalCellStyleStrategy setCellStyle() {
        // 表头策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景设置为白色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.index);
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);

        //边框
        headWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        headWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        headWriteCellStyle.setBorderRight(BorderStyle.THIN);
        headWriteCellStyle.setBorderTop(BorderStyle.THIN);
        //自动换行
        headWriteCellStyle.setWrapped(true);
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setBold(true);
        headWriteFont.setFontName("宋体");
        headWriteFont.setFontHeightInPoints((short)12);
        headWriteCellStyle.setWriteFont(headWriteFont);

        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // 背景白色
        contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        //边框
        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
        //自动换行
        contentWriteCellStyle.setWrapped(true);
        //文字
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short)12);
        contentWriteFont.setFontName("宋体");
        contentWriteCellStyle.setWriteFont(contentWriteFont);

        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

设置自适应列宽

自适应列宽通过CustomCellWriteUtil类设置,CustomCellWriteUtil类的代码如下:

java 复制代码
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Cell;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * excel自适应列宽
 */
public class CustomCellWriteUtil extends AbstractColumnWidthStyleStrategy {
    private static final int MAX_COLUMN_WIDTH = 255;
    private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8);

    public CustomCellWriteUtil() {
    }

    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
        if (needSetWidth) {
            Map<Integer, Integer> maxColumnWidthMap = (Map) CACHE.get(writeSheetHolder.getSheetNo());
            if (maxColumnWidthMap == null) {
                maxColumnWidthMap = new HashMap(16);
                CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
            }

            Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
            if (columnWidth >= 0) {
                if (columnWidth > 255) {
                    columnWidth = 255;
                }
                Integer maxColumnWidth = (Integer) ((Map) maxColumnWidthMap).get(cell.getColumnIndex());
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    ((Map) maxColumnWidthMap).put(cell.getColumnIndex(), columnWidth);
                    writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), 7250);
                }
            }
        }
    }

    private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData cellData = (CellData) cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        return cellData.getStringValue().getBytes().length;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }
}

添加合计行

合计行用于展示数据的总和或其他统计信息。在Controller中调用downloadExcelWithHead()方法导出Excel时,添加合计行和自定义表头形式。

2 调用导出工具类导出Excel表

此处以导出产品明细表为例,新建产品表如下:

entity、mapper、service层省略,在Controller层中添加一个导出产品明细表接口,代码如下:

java 复制代码
import com.z.entity.Product;
import com.z.service.ProductService;
import com.z.util.ConvertUtil;
import com.z.util.ExcelUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    private ProductService productService;

    @PostMapping("/export")
    public void export(HttpServletResponse response) throws IOException {
        List<Product> dataList = productService.list();
        if(null == dataList || dataList.isEmpty()){
            throw new RuntimeException("数据为空,导出失败!");
        }
        // 计算合计
        double totalProductCost = dataList.stream()
                .mapToDouble(data -> data.getProductCost() != null ? data.getProductCost() : 0.0)
                .sum();
        int totalProductNum = dataList.stream()
                .mapToInt(data -> data.getProductNum() != null ? data.getProductNum() : 0)
                .sum();
        // 创建合计行
        Product totalRow = new Product();
        totalRow.setId("合计");
        totalRow.setProductCost(totalProductCost);
        totalRow.setProductNum(totalProductNum);

        // 添加合计行到列表
        dataList.add(totalRow);

        // 创建自定义表头
        List<List<String>> rowHead = createHead();

        // 导出 Excel(带标题列,带合计列)
        ExcelUtil.downloadExcelWithHead(response, dataList, "产品明细表", rowHead);
    }

    /**
     * 设置Excel头
     * @return
     */
    private List<List<String>> createHead() {
        List<List<String>> list = new ArrayList<>();

        // 第一列表头
        List<String> headRow1 = new ArrayList<>();
        headRow1.add("产品明细表报表");
        headRow1.add("test");
        headRow1.add("产品序号");
        list.add(headRow1);

        // 第二列表头
        List<String> headRow2 = new ArrayList<>();
        headRow2.add("产品明细表报表");
        headRow2.add("备注:"+"此处可调用方法取值");
        headRow2.add("产品编号");
        list.add(headRow2);

        // 第三列表头
        List<String> headRow3 = new ArrayList<>();
        headRow3.add("产品明细表报表");
        headRow3.add("备注:"+"此处可调用方法取值");
        headRow3.add("产品名称");
        list.add(headRow3);

        // 第四列表头
        List<String> headRow4 = new ArrayList<>();
        headRow4.add("产品明细表报表");
        headRow4.add("无");
        headRow4.add("产品进价");
        list.add(headRow4);

        // 第五列表头
        List<String> headRow5 = new ArrayList<>();
        headRow5.add("产品明细表报表");
        headRow5.add("测试");
        headRow5.add("产品库存");
        list.add(headRow5);

        return list;
    }

其中,在使用createHead()方法设置Excel头时,可以调用service层中的方法,自定义显示特定值。

3 测试结果

最终使用postman调用该导出接口,导出的表格结果如下:

相关推荐
Lojarro6 分钟前
【Spring】Spring框架之-AOP
java·mysql·spring
莫名其妙小饼干9 分钟前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
isolusion21 分钟前
Springboot的创建方式
java·spring boot·后端
zjw_rp1 小时前
Spring-AOP
java·后端·spring·spring-aop
Oneforlove_twoforjob1 小时前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
TodoCoder1 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
向宇it1 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
小蜗牛慢慢爬行1 小时前
Hibernate、JPA、Spring DATA JPA、Hibernate 代理和架构
java·架构·hibernate
星河梦瑾2 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富2 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua