EasyExcel导出excel模板的几种解决方案

EasyExcel导出excel模板的几种解决方案

1、导出含有下拉框的excel

1.1、解决思路

​ excel下拉框包含两种情况,一种是固定内容的下拉框,一种是动态内容的下拉框,所以在设计的时候要考虑这两种情况。实现方式是通过自定义注解和自定义处理器的方式结合easyExcel进行模板下载,自定义注解应用到具体的字段中,在字段中传入相关参数,如果是固定内容的下拉框,则字段的参数直接传具体内容的数组即可,若是动态内容的下拉框则需要具体实现类以及数据类型,在实现类中通过参数来查询动态数据并以数组的方式返回。在注解解析工具类判断该字段是固定内容还是动态内容,并将内容进行解析和返回。在自定义处理器中将解析出的数据写入到一张sheet表中作为下拉框的数据源表,然后再隐藏,将下拉框数据设置到指定行列之后,统一设置下拉框表格除下拉列表意外的所有值都禁止输入。

1.2、核心代码实现

​ 自定义注解

/**
 * 动态下拉框注解
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {
    /**
     * 固定下拉内容
     */
    String[] source() default {};

    /**
     * 动态下拉内容
     */
    Class<? extends ExcelDynamicSelect>[] sourceClass() default {};

    /**
     * 参数
     *
     * @return
     */
    String params() default "";

    /**
     * 设置下拉框的起始行,默认为第二行
     */
    int firstRow() default 1;

    /**
     * 设置下拉框的结束行,默认为最后一行
     */
    int lastRow() default 0x10000;
}

注解解析工具类

/**
 * 解析下拉内容
 */
@Data
@Slf4j
public class ExcelSelectedResolveUtil {
    /**
     * 下拉内容
     */
    private String[] source;

    /**
     * 设置下拉框的起始行,默认为第二行
     */
    private int firstRow;

    /**
     * 设置下拉框的结束行,默认为最后一行
     */
    private int lastRow;

    public String[] resolveSelectedSource(ExcelSelected excelSelected) {
        if (excelSelected == null) {
            return null;
        }

        // 获取固定下拉框的内容
        String[] source = excelSelected.source();
        if (source.length > 0) {
            return source;
        }
        // 获取动态下拉框的内容
        Class<? extends ExcelDynamicSelect>[] classes = excelSelected.sourceClass();
        String params = excelSelected.params();
        if (classes.length > 0) {
            try {
                ExcelDynamicSelect excelDynamicSelect = classes[0].newInstance();
                String[] dynamicSelectSource = excelDynamicSelect.getSource(params);
                if (dynamicSelectSource != null && dynamicSelectSource.length > 0) {
                    return dynamicSelectSource;
                }
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("解析动态下拉框数据异常", e);
            }
        }
        return null;
    }
}

easyExcel处理器

@Data
@AllArgsConstructor
public class SelectedSheetWriteHandler implements SheetWriteHandler {
    private final Map<Integer, ExcelSelectedResolveUtil> selectedMap;

    /**
     * 设置阈值,避免生成的导入模板下拉值获取不到,可自行设置数量大小
     */
    private static final Integer LIMIT_NUMBER = 30;

    /**
     * Called before create the sheet
     */
    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    /**
     * Called after the sheet is created
     */
    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        // 这里可以对cell进行任何操作
        Sheet sheet = writeSheetHolder.getSheet();
        DataValidationHelper helper = sheet.getDataValidationHelper();
        selectedMap.forEach((k, v) -> {
            // 设置下拉列表的行: 首行,末行,首列,末列
            CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);
            // 如果下拉值总数大于25,则使用一个新sheet存储,避免生成的导入模板下拉值获取不到
            if (v.getSource().length > LIMIT_NUMBER) {
                //定义sheet的名称
                //1.创建一个隐藏的sheet 名称为 hidden + k
                String sheetName = "hidden" + k;
                Workbook workbook = writeWorkbookHolder.getWorkbook();
                Sheet hiddenSheet = workbook.createSheet(sheetName);
                for (int i = 0, length = v.getSource().length; i < length; i++) {
                    // 开始的行数i,列数k
                    hiddenSheet.createRow(i).createCell(k).setCellValue(v.getSource()[i]);
                }
                Name category1Name = workbook.createName();
                category1Name.setNameName(sheetName);
                String excelLine = getExcelLine(k);
                // =hidden!$H:$1:$H$50  sheet为hidden的 H1列开始H50行数据获取下拉数组
                String refers = "=" + sheetName + "!$" + excelLine + "$1:$" + excelLine + "$" + (v.getSource().length + 1);
                // 将刚才设置的sheet引用到你的下拉列表中
                DataValidationConstraint constraint = helper.createFormulaListConstraint(refers);
                DataValidation dataValidation = helper.createValidation(constraint, rangeList);
                writeSheetHolder.getSheet().addValidationData(dataValidation);
                // 设置存储下拉列值得sheet为隐藏
                int hiddenIndex = workbook.getSheetIndex(sheetName);
                if (!workbook.isSheetHidden(hiddenIndex)) {
                    workbook.setSheetHidden(hiddenIndex, true);
                }
            }
            // 设置下拉列表的值
            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列标A-Z-AA-ZZ
     *
     * @param num 列数
     * @return java.lang.String
     */
    private String getExcelLine(int num) {
        String line = "";
        int first = num / 26;
        int second = num % 26;
        if (first > 0) {
            line = (char) ('A' + first - 1) + "";
        }
        line += (char) ('A' + second) + "";
        return line;
    }
}

easyExcel导出excel工具类

package com.devboy.utils;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.devboy.annotation.ExcelSelected;
import com.devboy.handler.SelectedSheetWriteHandler;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ExcelUtil {
    public static <T> void exportSelectedSheet(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
        try {
            Map<Integer, ExcelSelectedResolveUtil> selectedMap = resolveSelectedAnnotation(clazz);
//            String filename = encodingFilename(sheetName);
//            response.reset();
//            FileUtils.setAttachmentResponseHeader(response, filename);
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
            ServletOutputStream os = response.getOutputStream();
            EasyExcel.write(os, clazz)
                    .autoCloseStream(false)
                    // 自动适配
                    .registerWriteHandler(new SelectedSheetWriteHandler(selectedMap))
                    .inMemory(Boolean.TRUE)
                    .sheet(sheetName).doWrite(list);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    private static <T> Map<Integer, ExcelSelectedResolveUtil> resolveSelectedAnnotation(Class<T> head) {
        Map<Integer, ExcelSelectedResolveUtil> selectedMap = new HashMap<>();
        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) {
                ExcelSelectedResolveUtil excelSelectedResolve = new ExcelSelectedResolveUtil();
                String[] source = excelSelectedResolve.resolveSelectedSource(selected);
                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;
    }
}

使用方式

package com.devboy.domain;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import com.devboy.annotation.ExcelSelected;
import com.devboy.service.impl.DictTypeSelectImpl;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
@HeadRowHeight(30)
@HeadStyle(fillPatternType = FillPatternTypeEnum.NO_FILL, fillForegroundColor = 10)
@ColumnWidth(25)
@ContentRowHeight(25)
public class Student {
    /**
     * 序号
     */
    @ExcelProperty({"序号"})
    @ColumnWidth(10)
    private Long id;

    /**
     * 姓名
     */
    @ExcelProperty({"姓名"})
    @ColumnWidth(10)
    private String name;

    /**
     * 性别 性别为固定下拉框
     */
    @ExcelProperty({"性别"})
    @ColumnWidth(10)
    @ExcelSelected(source = {"男", "女"})
    private String sex;

    /**
     * 年级 这里的年级作为动态数据字典,可动态配置,本demo仅进行数据模拟不再使用数据库
     */
    @ExcelProperty({"年级"})
    @ColumnWidth(10)
    @ExcelSelected(sourceClass = DictTypeSelectImpl.class, params = "grade")
    private String grade;

    /**
     * 角色
     */
    @ExcelProperty({"角色"})
    @ColumnWidth(10)
    @ExcelSelected(sourceClass = DictTypeSelectImpl.class, params = "role")
    private String role;
}

效果图

在这里插入图片描述

2、导出标题带有批注的excel

2.1、解决思路

导出标题带有批注的excel思路同上,都是通过自定义注解和自定义处理器的方式来做,主要是处理方式不同,这里主要解释一下如何处理批注,处理方式是通过在注解中传入批注内容,所在行、列,然后在注解解析工具类中对注解进行解析,返回解析出的批注内容、行、列,然后在自定义处理器中将批注写入对应行列的excel单元格中。

2.2、代码实现

自定义注解

package com.devboy.annotation;

import com.devboy.service.ExcelRichTextSelect;

import java.lang.annotation.*;

/**
 * 自定义批注注解
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelRichText {
    /**
     * 批注内容
     *
     * @return
     */
    String value();

    /**
     * 所在行
     *
     * @return
     */
    int rowNum() default 0;

    /**
     * 所在列
     *
     * @return
     */
    int colNum() default 0;



    String dictCode()default "";

    /**
     * 动态下拉内容
     */
    Class<? extends ExcelRichTextSelect>[] sourceClass() default {};
}

自定义处理器

package com.devboy.handler;

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.devboy.domain.RichText;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;

import java.util.List;

/**
 * 批注处理器
 */
@Data
@AllArgsConstructor
public class RichTextWriterHandler implements CellWriteHandler {
    private final List<RichText> richTextList;

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<com.alibaba.excel.metadata.data.WriteCellData<?>> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        if (aBoolean) { // 判断是否为表头,true则是
            Integer row = cell.getRowIndex();// 行号(从0开始)
            int columnIndex = cell.getColumnIndex();
            for (RichText richText : richTextList) {
                if (richText.getColNum() == columnIndex && richText.getRowNum() == row) {
                    Sheet sheet = writeSheetHolder.getSheet();
                    Drawing draw = sheet.createDrawingPatriarch();
                    //定位批注的显示区域
                    Comment comment = draw.createCellComment(new XSSFClientAnchor(1, 1, 1, 1, (short) columnIndex + 1, (short) columnIndex + 1, (short) columnIndex + 2, (short) columnIndex + 5));
                    // 批注内容
                    comment.setString(new XSSFRichTextString(richText.getValue()));
                    cell.setCellComment(comment);
                }
            }
        }
    }
}

easyExcel工具类以及注解数据解析方法

package com.devboy.utils;

import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.util.StringUtils;
import com.devboy.annotation.ExcelRichText;
import com.devboy.annotation.ExcelSelected;
import com.devboy.domain.RichText;
import com.devboy.handler.RichTextWriterHandler;
import com.devboy.handler.SelectedSheetWriteHandler;
import com.devboy.service.ExcelRichTextSelect;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

public class ExcelUtil {
    public static <T> void exportSelectedSheet(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
        try {
            Map<Integer, ExcelSelectedResolveUtil> selectedMap = resolveSelectedAnnotation(clazz);
            List<RichText> richTexts = resolveRichText(clazz);
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
            ServletOutputStream os = response.getOutputStream();
            EasyExcel.write(os, clazz)
                    .autoCloseStream(false)
                    // 注册下拉框处理器
                    .registerWriteHandler(new SelectedSheetWriteHandler(selectedMap))
                    // 注册自定义批注处理器
                    .registerWriteHandler(new RichTextWriterHandler(richTexts))
                    .inMemory(Boolean.TRUE)
                    .sheet(sheetName).doWrite(list);
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常");
        }
    }

    private static <T> Map<Integer, ExcelSelectedResolveUtil> resolveSelectedAnnotation(Class<T> head) {
        Map<Integer, ExcelSelectedResolveUtil> selectedMap = new HashMap<>();
        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) {
                ExcelSelectedResolveUtil excelSelectedResolve = new ExcelSelectedResolveUtil();
                String[] source = excelSelectedResolve.resolveSelectedSource(selected);
                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;
    }

    private static <T> List<RichText> resolveRichText(Class<T> head) {
        List<RichText> res = new ArrayList<>();
        Field[] fields = head.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            ExcelRichText richText = field.getAnnotation(ExcelRichText.class);
            if (richText != null) {
                RichText text = new RichText();
                String s = richText.dictCode();
                if (StringUtils.isNotBlank(s)) {
                    Class<? extends ExcelRichTextSelect>[] classes = richText.sourceClass();
                    try {
                        ExcelRichTextSelect excelRichTextSelect = classes[0].newInstance();
                        String[] source = excelRichTextSelect.getSource(s);
                        String sourceLabel = Arrays.stream(source).collect(Collectors.joining("、"));
                        String a = StrUtil.format(richText.value(), sourceLabel);
                        text.setValue(a);
                    } catch (InstantiationException | IllegalAccessException e) {
                        e.printStackTrace();
                        text.setValue(richText.value());
                    }
                } else {
                    text.setValue(richText.value());
                }
                text.setColNum(richText.colNum());
                text.setRowNum(richText.rowNum());
                res.add(text);
            }
        }
        return res;
    }
}

具体实体类

package com.devboy.domain;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import com.devboy.annotation.ExcelRichText;
import com.devboy.service.impl.DictTypeRichTextImpl;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
@HeadRowHeight(30)
@HeadStyle(fillPatternType = FillPatternTypeEnum.NO_FILL, fillForegroundColor = 10)
@ColumnWidth(25)
@ContentRowHeight(25)
public class Computer {
    /**
     * 序号
     */
    @ExcelProperty({"序号"})
    @ColumnWidth(10)
    private Long id;

    /**
     * 名称
     */
    @ExcelProperty({"名称"})
    @ColumnWidth(10)
    private String name;

    /**
     * 类型
     */
    @ExcelProperty({"类型"})
    @ColumnWidth(10)
    @ExcelRichText(value = "请从一下系统中存在的类型,多个类型通过、进行分割。可选类型:{}", rowNum = 0, colNum = 2, dictCode = "computer", sourceClass = DictTypeRichTextImpl.class)
    private String type;
}

效果截图

num.NO_FILL, fillForegroundColor = 10)
@ColumnWidth(25)
@ContentRowHeight(25)
public class Computer {
/**
* 序号
*/
@ExcelProperty({“序号”})
@ColumnWidth(10)
private Long id;

/**
 * 名称
 */
@ExcelProperty({"名称"})
@ColumnWidth(10)
private String name;

/**
 * 类型
 */
@ExcelProperty({"类型"})
@ColumnWidth(10)
@ExcelRichText(value = "请从一下系统中存在的类型,多个类型通过、进行分割。可选类型:{}", rowNum = 0, colNum = 2, dictCode = "computer", sourceClass = DictTypeRichTextImpl.class)
private String type;

}
``
效果截图
在这里插入图片描述
具体代码可参考:https://gitee.com/liu-ye-ly/some-solutions-for-easy-excel

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值