EsayExcel-合并表头-并且下拉框动态从数据库获取

EasyExcel是一个由阿里巴巴开源的轻量级Excel处理框架,它以简单易用和节省内存为特点。文章介绍了如何在项目中引入和使用EasyExcel,包括数据导入、分页导出、自定义格式等功能,并展示了如何实现模板导出,以及通过自定义注解处理下拉列表等交互式功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是EasyExcel

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。该项目实现了列表数据的导入,分页列表导出,支持自定义格式,支持模版以及模板和列表的混合导出,小巧、简易、高性能。

 1.pom.xml 

<!--测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>
<!--Lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
<!--easyexcel依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.1.1</version>
    </dependency>


 

以下是实战的导出模板的样式:

 导出的模板Excel拦截器:

@Data
public class CustomTitleWriteHandler implements SheetWriteHandler {


    private final Map<Integer, ExcelSelectedResolve> selectedMap;
    /**
     * 标题
     */
    private final String fileName;

    /**
     * DTO数据类型
     */
    private final Class<?> elementType;

    public CustomTitleWriteHandler(Map<Integer, ExcelSelectedResolve> selectedMap, String fileName, Class<?> elementType) {
        this.selectedMap = selectedMap;
        this.fileName = fileName;
        this.elementType = elementType;
    }

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
         // 获取clazz所有的属性
        Field[] fields = this.elementType.getDeclaredFields();
        Workbook workbook = writeWorkbookHolder.getWorkbook();
        Sheet sheet = workbook.getSheetAt(0);
        Row row1 = sheet.createRow(0);
        row1.setHeight((short) 2100);
        Cell cell = row1.createCell(0);
        //设置标题
        cell.setCellValue(fileName);
        CellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
        cellStyle.setAlignment(HorizontalAlignment.LEFT);//水平左对齐
        cellStyle.setBorderTop(BorderStyle.THIN);
        cellStyle.setWrapText(true);//自动换行
        Font font = workbook.createFont();
        //font.setBold(true); 字体加粗
        font.setFontHeight((short) 250);
        font.setFontName("楷体");
        font.setColor(IndexedColors.RED.getIndex());
        cellStyle.setFont(font);
        cell.setCellStyle(cellStyle);
        sheet.addMergedRegionUnsafe(new CellRangeAddress(0, 0, 0, fields.length-2));
        //自定义注解下拉框配置
        DataValidationHelper helper = sheet.getDataValidationHelper();
        if(selectedMap!=null&&!selectedMap.isEmpty()){
            selectedMap.forEach((k, v) -> {
                // 设置下拉列表的行: 首行,末行,首列,末列
                CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);
                // 设置下拉列表的值
                DataValidationConstraint constraint = helper.createExplicitListConstraint(v.getSource());
                // 设置约束
                DataValidation validation = helper.createValidation(constraint, rangeList);
                // 阻止输入非下拉选项的值
                validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
                validation.setShowErrorBox(true);
                validation.setSuppressDropDownArrow(true);
                validation.createErrorBox("提示", "请输入下拉选项中的内容");
                sheet.addValidationData(validation);
            });
        }
    }

}

Excel工具类:

package com.segi.uhomecp.cmsk.ibatch.utils;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.segi.uhomecp.cmsk.ibatch.handler.CustomTitleWriteHandler;
import com.segi.uhomecp.cmsk.ibatch.handler.ExcelSelected;
import com.segi.uhomecp.cmsk.ibatch.handler.ExcelSelectedResolve;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;

import javax.servlet.http.HttpServletResponse;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;

/**
 * 高性能处理Excel工具类
 *
 */
public class EasyExcelUtil {
    /**
     * 使用 模型 来读取Excel
     *
     * @param inputStream Excel的输入流
     * @param clazz       模型的类
     * @return 返回 模型 的列表
     */
    public static <T> List<T> readExcel(InputStream inputStream, Class<T> clazz) {
        ModelExcelListener<T> listener = new ModelExcelListener<T>();
        EasyExcel.read(inputStream, clazz, listener).sheet().doRead();
        return listener.getDatas();
    }

    /**
     * 使用 模型 来导出到WEB
     *
     * @param response      web的响应
     * @param data         要写入的以 模型 为单位的数据
     * @param fileName     配置Excel的表名
     * @param sheetName    配置Excel的页签名
     * @param clazz        模型的类
     * @throws IOException
     */
    public static <T> void writeExcel(HttpServletResponse response, List<T> data, Class<T> clazz, String fileName, String sheetName) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);
    }

    /**
     * 使用 模型 来写入Excel
     * <br/>注意,如果是web输出流,需要设置头
     *
     * @param outputStream Excel的输出流
     * @param data         要写入的以 模型 为单位的数据
     * @param sheetName    配置Excel的表名字
     * @param clazz        模型的类
     */
    public static <T> void writeExcel(OutputStream outputStream, List<T> data, Class<T> clazz, String sheetName) {
        EasyExcel.write(outputStream, clazz).sheet(sheetName).doWrite(data);
    }

    /**
     * 模型 解析监听器
     */
    private static class ModelExcelListener<T> extends AnalysisEventListener<T> {
        private List<T> datas = new ArrayList<>();

        @Override
        public void invoke(T object, AnalysisContext context) {
            datas.add(object);
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
        }

        public List<T> getDatas() {
            return datas;
        }
    }

    /**
     * 将 Map对象转化为JavaBean
     * @param map
     * @param T
     * @return
     * @throws Exception
     */
    public static <T> T convertMap2Bean(Map<String, Object> map, Class<T> T)
            throws Exception
    {
        if (map == null || map.size() == 0)
        {
            return null;
        }
        //获取map中所有的key值,全部更新成大写,添加到keys集合中,与mybatis中驼峰命名匹配
        Object mvalue = null;
        Map<String, Object> newMap = new HashMap<>();
        Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
        while(it.hasNext()){
            String key = it.next().getKey();
            mvalue = map.get(key);
          /*  if (key.indexOf(CharacterConstant.UNDERLINE) != -1)
            {
                key = key.replaceAll(CharacterConstant.UNDERLINE, "");
            }*/
            newMap.put(key.toUpperCase(Locale.US), mvalue);
        }

        BeanInfo beanInfo = Introspector.getBeanInfo(T);
        T bean = T.newInstance();
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (int i = 0, n = propertyDescriptors.length; i < n; i++)
        {
            PropertyDescriptor descriptor = propertyDescriptors[i];
            String propertyName = descriptor.getName();
            String upperPropertyName = propertyName.toUpperCase();

            if (newMap.keySet().contains(upperPropertyName))
            {
                Object value = newMap.get(upperPropertyName);
                //这个方法不会报参数类型不匹配的错误。
                BeanUtils.copyProperty(bean, propertyName, value);
            }
        }
        return bean;
    }

    /**
     * 设置样式
     */
    public static HorizontalCellStyleStrategy getCellStyle(){
        //表头样式
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        //设置表头居中对齐
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //内容样式
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //设置内容垂直居中对齐
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        //设置内容水平居中对齐
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        return new HorizontalCellStyleStrategy(headWriteCellStyle,contentWriteCellStyle);
    }


    /**
     * Excel导出模板write
     * @param response
     * @param fileName sheet标题
     * @param exegesis 注释
     * @param name class类名
     * @param dataList 数据列表
     * @param id 统一客户是一级物业ID,项目客户是项目ID
     * @throws Exception
     */
    public static <T> void EasyExcelInit(HttpServletResponse response, String fileName, String exegesis, Class<T> name, List dataList,String id) throws Exception{
        //动态获取下拉属性,将动态数据下发到Excel
        Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(name,id);
        String fileName2 = URLEncoder.encode(fileName + "模板.xlsx", "utf-8");
        response.setHeader("Content-disposition", "attachment; filename=" + fileName2);
        response.setContentType("application/vnd.ms-excel");
        EasyExcel.write(response.getOutputStream(), name)
                .inMemory(true) // 富文本
                .autoCloseStream(true)//自动关闭流
                .relativeHeadRowIndex(1)//空格一行。用来添加注释
                .useDefaultStyle(false)
                .sheet(fileName)//设置sheet页的名称
                .registerWriteHandler(new CustomTitleWriteHandler(selectedMap,exegesis,name))//合并+添加样式
                .doWrite(dataList);
    }


    /**
     * 创建即将导出的sheet页(sheet页中含有带下拉框的列)
     * @param head 导出的表头信息和配置
     * @param sheetNo sheet索引
     * @param sheetName sheet名称
     * @param <T> 泛型
     * @return sheet页
     */
    public static <T> WriteSheet writeSelectedSheet(Class<T> head, Integer sheetNo, String sheetName) {
        Map<Integer, ExcelSelectedResolve> selectedMap = resolveSelectedAnnotation(head,null);
        return EasyExcel.writerSheet(sheetNo, sheetName)
                .head(head)
                .registerWriteHandler(new CustomTitleWriteHandler(selectedMap,null,null))
                .build();
    }

    /**
     * 解析表头类中的下拉注解
     * @param head 表头类
     * @param <T> 泛型
     * @return Map<下拉框列索引, 下拉框内容> map
     */
    private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head,String id) {
        Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();

        // getDeclaredFields(): 返回全部声明的属性;getFields(): 返回public类型的属性
        Field[] fields = head.getDeclaredFields();
        for (int i = 0; i < fields.length; i++){
            Field field = fields[i];
            // 解析注解信息
            ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (selected != null) {
                ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
                String[] source = excelSelectedResolve.resolveSelectedSource(selected,id);
                if (source != null && source.length > 0){
                    excelSelectedResolve.setSource(source);
                    excelSelectedResolve.setFirstRow(selected.firstRow());
                    excelSelectedResolve.setLastRow(selected.lastRow());
                    if (property != null && property.index() >= 0){
                        selectedMap.put(property.index(), excelSelectedResolve);
                    } else {
                        selectedMap.put(i, excelSelectedResolve);
                    }
                }
            }
        }

        return selectedMap;
    }
}


    @RequestMapping(value = "/downLoadTemplate.json", method = {RequestMethod.POST})
    public void downLoadTemplate(@RequestBody  Map<String, Object> reqMap, HttpServletResponse response) throws IOException {
        if(reqMap.get("type")==null&&"".equals(reqMap.get("type"))){
            throw new RuntimeException("模板类型不能为空!");
        }
        String type=reqMap.get("type").toString();
        String topOrganId=null;
        String organId=null;
        if("1".contains(type)){
            if(reqMap.get("topOrganId")==null&&"".equals(reqMap.get("topOrganId"))){
                throw new RuntimeException("一级物业不能为空!");
            }
             topOrganId=reqMap.get("topOrganId").toString();
        }else if("2".contains(type)||"3".equals(type)){
            if(reqMap.get("organId")==null&&"".equals(reqMap.get("organId"))){
                throw new RuntimeException("项目机构不能为空!");
            }
            organId=reqMap.get("organId").toString();
        }
        try {
            if("1".equals(type)){
                String fileName = "模板一";
                EasyExcelInit(response,fileName, IbatchUtil.exegesis, CustMainAllTempleExcelDto.class,null,topOrganId);
            }else if("2".equals(type)){
                String fileName ="模板二";
                EasyExcelInit(response,fileName,IbatchUtil.exegesis2, CustomerTempleExcelDto.class,null,organId);
            }else if("3".equals(type)){
                String fileName ="模板三";
                List<CustomerInvoiceExcelDto>  customerInvoiceExcelDtoList=commonService.queryCustomerListByOrganId(organId);
                EasyExcelInit(response,fileName,IbatchUtil.exegesis3, CustomerInvoiceExcelDto.class,customerInvoiceExcelDtoList,organId);
            }else if("4".equals(type)){
                String fileName = "模板四";
                EasyExcelInit(response,fileName, IbatchUtil.exegesis4, CustMainInvoiceExcelDto.class,null,topOrganId);
            }
        } catch (Exception e) {
            Map<String, String> resMap = Maps.newHashMap();
            logger.error("下载模板异常:", e);
            resMap.put("code", "-1");
            resMap.put("message", "下载模板异常!"+e.getMessage());
            response.setHeader("errorMsg", URLEncoder.encode("下载模板异常!", "utf-8"));
            response.reset();
            response.setContentType("application/json;charset=utf-8");
            response.setCharacterEncoding("utf-8");
            response.getWriter().println(JSON.toJSONString(resMap));
        }
    }

fillForegroundColor:前景色,类型是 short,使用的org.apache.poi.ss.usermodel.IndexedColors枚举的 idx 值(数字)。(两个类型不一致,一个是 short,一个是 int,没有办法直接引用。)

  实体类(需要写入excel的类)模板类:

@ContentRowHeight(15)
@HeadRowHeight(15)
@Data
//表头居中
@ContentStyle (horizontalAlignment= HorizontalAlignment.CENTER)
//实线表格
@HeadStyle(borderTop = BorderStyle.THIN,borderBottom =BorderStyle.THIN,borderLeft = BorderStyle.THIN,borderRight = BorderStyle.THIN)
public class CustMainAllTempleExcelDto implements Serializable {
    private static final long serialVersionUID = 1L;

    @ColumnWidth(13)
    @ExcelProperty(value = {"客户信息","客户性质"}, index = 0)
    @ExcelSelected(sourceClass = CustPubTypeSelected.class)
    //表头标黄
    @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND,fillForegroundColor=13)
    @HeadFontStyle(color =10)
    private String custPubTypeName;

    @ColumnWidth(13)
    //合并表头,相同合并
    @ExcelProperty(value = {"客户信息","客户名称"}, index = 1)
    @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND,fillForegroundColor=13)
    @HeadFontStyle(color =10)
    private String custName;

    ......//其他字段

    @ColumnWidth(20)
    @ExcelProperty(value = {"开票信息", "纳税人名称"}, index = 12)
    //表头标黄
    @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND,fillForegroundColor=13)
    //表头内容标红
    @HeadFontStyle(color =10)
    private String taxpayerName;

    @ColumnWidth(20)
    @ExcelProperty(value = {"开票信息", "纳税人税号"}, index = 13)
    private String taxpayerNum;
}

 下拉数据动态获取

/**
 * 下拉框-动态字段-自定义证件类型下拉数据
 */
public class CustPubTypeSelected implements ExcelDynamicSelect {

    @Override
    public String[] getSource(String id) {
        List<AttrDictDto> dictCustList = CommonDicts.getAttrByCode(Integer.valueOf(id), "CUST_PUB_TYPE");
        List<String> collect = dictCustList.stream().map(AttrDictDto::getDictName).collect(Collectors.toList());
        return collect.toArray(new String[dictCustList.size()]);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值