使用EasyExcel实现导出excel文件时生成多级下拉选

前言

公司有个需求本来只涉及到两个下拉选项,后面就想能不能实现多个下拉选,当然我这里说的多个下拉选是联动的,比如省、地市、区县这种。

实现步骤

1、添加EasyExcel的Maven依赖

 <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
 </dependency>

2、一个具有多级关联的数据项


/**
 * excel下拉框数据项
 * @author lcy
 */
@Data
public class SelectItem {

    public SelectItem(Integer columnIndex) {
        this.columnIndex = columnIndex;
    }

    /**
     * 下拉框所在列的索引,从0开始
     */
    private Integer columnIndex;

    /**
     * 下拉框的值列表
     */
    private List<DataItem> dataItems;


    /**
     * 子级对应的下拉框数据
     */
    private SelectItem subSelect;


    public  void addDataItem(String mappingKey,List<String> values){
        if (this.dataItems == null){
            this.dataItems = new ArrayList<>();
        }
        this.dataItems.add(new DataItem(mappingKey,values));
    }
    public  void addDataItem(List<String> values){
        this.addDataItem("_"+UUID.randomUUID().toString().replaceAll("-",""),values);
    }


    @Data
    public static class DataItem{

        /**
         * 关联上级的key
         */
        private String mappingKey;

        /**
         * 当前下拉框的值
         */
        private List<String> values;

        /**
         * 当前下拉框的引用,隐藏页单元格地址
         */
        private String hiddenFormulaRef;

        public DataItem(String mappingKey, List<String> values) {
            Assert.notBlank(mappingKey,"mappingKey is not blank");
            Assert.notEmpty(values,"values is not empty");
            this.mappingKey = mappingKey;
            this.values = values;
        }
    }

3、定义一个SheetWriteHandler,这是EasyExcel提供的一个组件,允许我们在sheet页生成前后做一些干预动作。


/**
 * @author lcy
 */
public class SelectWriteHandler implements SheetWriteHandler , CellWriteHandler {

    private static final int ROW_SIZE = 10000;

    private final  WriteFont redFont;

    private final  List<SelectItem> selectItems;

    private final String HIDDEN_SHEET_NAME = "hidden_sheet";

    private final Set<Integer> selectColumns = new HashSet<>();

    private boolean isLoadSelectColumns = false;

    private int rowIndex = 0;

    public SelectWriteHandler(List<SelectItem> selectItems) {
        Assert.notEmpty(selectItems, "selectItems can not be empty");
        this.selectItems = selectItems;
        redFont = getRedFont();
    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        Workbook workbook = writeWorkbookHolder.getWorkbook();
        Sheet hiddenSheet = workbook.getSheet(HIDDEN_SHEET_NAME);
        if (hiddenSheet != null){
            return ;
        }
        hiddenSheet = workbook.createSheet(HIDDEN_SHEET_NAME);
        workbook.setSheetHidden(workbook.getSheetIndex(hiddenSheet),true);
        Sheet sheet = writeSheetHolder.getSheet();
        for (SelectItem selectItem : selectItems) {
            buildHiddenSheetSelectRef(workbook,sheet,hiddenSheet,selectItem,null);
        }
        if (!isLoadSelectColumns){
            isLoadSelectColumns = true;
        }
    }

    private void buildHiddenSheetSelectRef(Workbook workbook,Sheet sheet,Sheet hiddenSheet, SelectItem selectItem,String formulaRef ) {
        if (!isLoadSelectColumns){
            selectColumns.add(selectItem.getColumnIndex());
        }
        List<SelectItem.DataItem> dataItems = selectItem.getDataItems();
        for (SelectItem.DataItem dataItem : dataItems) {
            setDataAndName(workbook, hiddenSheet, dataItem);
        }
        // 单元格地址引用
        if (formulaRef == null || formulaRef.isEmpty()){
            formulaRef = dataItems.get(0).getHiddenFormulaRef();
        }
        // 创建检验器
        DataValidation dataValidation = getDataValidation(sheet, selectItem, formulaRef);
        sheet.addValidationData(dataValidation);
        SelectItem subSelect = selectItem.getSubSelect();
        if (subSelect != null){
            buildHiddenSheetSelectRef(workbook,sheet,hiddenSheet,subSelect,getInDirectFormulaRef(selectItem.getColumnIndex()));
        }
    }

    private  DataValidation getDataValidation(Sheet sheet, SelectItem selectItem, String formulaRef) {
        DataValidationHelper helper = sheet.getDataValidationHelper();
        DataValidationConstraint constraint = helper.createFormulaListConstraint(formulaRef);
        CellRangeAddressList rangeAddressList = new CellRangeAddressList(1,ROW_SIZE, selectItem.getColumnIndex(), selectItem.getColumnIndex());
        DataValidation dataValidation = helper.createValidation(constraint, rangeAddressList);
        dataValidation.setShowErrorBox(true);
        return dataValidation;
    }

    private void setDataAndName(Workbook workbook, Sheet hiddenSheet, SelectItem.DataItem dataItem) {
        // 构建隐藏数据
        Row row = hiddenSheet.createRow(rowIndex);
        List<String> values = dataItem.getValues();
        for (int i = 0; i < values.size(); i++) {
            row.createCell(i).setCellValue(values.get(i));
        }
        // 创建名称命名器
        Name name = workbook.createName();
        name.setNameName(dataItem.getMappingKey());
        name.setRefersToFormula(getFormulaRef(row));
        dataItem.setHiddenFormulaRef(name.getRefersToFormula());
        rowIndex++;
    }

    private String getInDirectFormulaRef(Integer columnIndex){
        CellReference slectCellReference = new CellReference(1, columnIndex);
        return  "INDIRECT("+joinFormulaRef(slectCellReference, false)+")";
    }

    @Override
    public void afterCellDispose(CellWriteHandlerContext context) {
        if (!context.getHead()){
            Integer columnIndex = context.getColumnIndex();
            if (selectColumns.contains(columnIndex)){
                // 设置红色字体
                context.getFirstCellData().getOrCreateStyle().setWriteFont(redFont);
            }
        }
        CellWriteHandler.super.afterCellDispose(context);
    }

    private   String getFormulaRef(Row prvRow) {
        Cell startCell = prvRow.getCell(prvRow.getFirstCellNum());
        Cell endCell = prvRow.getCell(prvRow.getLastCellNum() - 1);
        return HIDDEN_SHEET_NAME + "!" + joinFormulaRef(new CellReference(startCell),true) + ":" + joinFormulaRef(new CellReference(endCell),true);
    }

    public  String joinFormulaRef(CellReference cellReference,boolean isAbsolute){
        StringBuilder sb = new StringBuilder();
        String[] refs = cellReference.getCellRefParts();
        for (int i = refs.length -1 ; i >= 1; i--) {
            if (isAbsolute){
                sb.append("$");
            }
            sb.append(refs[i]);
        }
        return sb.toString();
    }

    /**
     * 返回一个红色字体
     * @return
     */
    private WriteFont getRedFont() {
        WriteFont redFont =  new WriteFont();
        redFont.setColor(IndexedColors.RED.getIndex());
        return redFont;
    }
}


4、准备数据

       // 准备数据
        SelectItem selectItem = new SelectItem(0);
        selectItem.addDataItem(List.of("浙江省","河南省"));

        SelectItem subSelectItem = new SelectItem(1);
        subSelectItem.addDataItem("浙江省",List.of("杭州市","宁波市"));
        subSelectItem.addDataItem("河南省",List.of("郑州市","洛阳市","开封市"));
        selectItem.setSubSelect(subSelectItem);

        SelectItem selectItem3 = new SelectItem(2);
        selectItem3.addDataItem("杭州市",List.of("滨江区","西湖区"));
        selectItem3.addDataItem("宁波市",List.of("宁波市1","宁波市2"));
        selectItem3.addDataItem("郑州市",List.of("金水区","二七区"));
        selectItem3.addDataItem("洛阳市",List.of("洛阳市1","洛阳市2"));
        selectItem3.addDataItem("开封市",List.of("开封市1","开封市2"));
        subSelectItem.setSubSelect(selectItem3);

5、测试

EasyExcel.write("d:\\5555.xlsx")
                .registerWriteHandler(new SelectWriteHandler(List.of(selectItem)))
                .sheet()
                .doWrite(Collections.emptyList());


完整的测试代码

public class SelectExcelTest {


    public static void main(String[] args) {

        // 准备数据
        SelectItem selectItem = new SelectItem(0);
        selectItem.addDataItem(List.of("浙江省","河南省"));

        SelectItem subSelectItem = new SelectItem(1);
        subSelectItem.addDataItem("浙江省",List.of("杭州市","宁波市"));
        subSelectItem.addDataItem("河南省",List.of("郑州市","洛阳市","开封市"));
        selectItem.setSubSelect(subSelectItem);

        SelectItem selectItem3 = new SelectItem(2);
        selectItem3.addDataItem("杭州市",List.of("滨江区","西湖区"));
        selectItem3.addDataItem("宁波市",List.of("宁波市1","宁波市2"));
        selectItem3.addDataItem("郑州市",List.of("金水区","二七区"));
        selectItem3.addDataItem("洛阳市",List.of("洛阳市1","洛阳市2"));
        selectItem3.addDataItem("开封市",List.of("开封市1","开封市2"));
        subSelectItem.setSubSelect(selectItem3);



        EasyExcel.write("d:\\5555.xlsx")
                .registerWriteHandler(new SelectWriteHandler(List.of(selectItem)))
                .sheet()
                .doWrite(Collections.emptyList());
    }

}

6、结果


 

1. 引入easyExcel依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.9</version> </dependency> ``` 2. 编写导出excel的实体类 例如,我们要导出一个学生信息表格,那么我们可以定义一个Student类来表示学生信息,如下所示: ``` public class Student { private String name; // 姓名 private int age; // 年龄 private String gender; // 性别 private String major; // 专业 // 省略getter和setter方法 } ``` 3. 编写导出excel的服务类 我们可以定义一个ExcelService类来实现导出excel文件的功能,例如: ``` @Service public class ExcelService { public void export(List<Student> studentList, HttpServletResponse response) throws IOException { OutputStream out = response.getOutputStream(); try { response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("学生信息表.xlsx", "UTF-8")); response.setContentType("application/vnd.ms-excel;charset=UTF-8"); ExcelWriter writer = EasyExcelFactory.getWriter(out); Sheet sheet = new Sheet(1, 0, Student.class); sheet.setSheetName("学生信息表"); writer.write(studentList, sheet); writer.finish(); } finally { out.close(); } } } ``` 在这个服务类中,我们定义了一个export方法来实现导出excel文件的功能。它接受一个学生信息列表和一个HttpServletResponse对象作为参数,其中HttpServletResponse对象用于设置响应头和输出流。 在方法中,我们首先获取输出流,然后设置响应头和响应类型。接着,我们使用EasyExcelFactory的getWriter方法获取一个ExcelWriter对象,并创建一个Sheet对象来表示Excel中的一个表格。我们将学生信息列表和Sheet对象传给ExcelWriter的write方法来写入数据,最后调用ExcelWriter的finish方法来完成导出操作。 4. 调用导出excel的服务方法 我们可以在Controller中调用ExcelService的export方法来实现导出excel文件的功能,例如: ``` @RestController public class ExcelController { @Autowired private ExcelService excelService; @GetMapping("/export") public void export(HttpServletResponse response) throws IOException { List<Student> studentList = new ArrayList<>(); studentList.add(new Student("张三", 18, "男", "计算机科学与技术")); studentList.add(new Student("李四", 19, "女", "软件工程")); studentList.add(new Student("王五", 20, "男", "信息安全")); excelService.export(studentList, response); } } ``` 在这个Controller中,我们定义了一个export方法来处理导出excel文件的请求。它调用了ExcelService的export方法来实现导出操作,并将学生信息列表和HttpServletResponse对象作为参数传递给ExcelService的export方法。 5. 测试导出excel文件的功能 我们可以启动应用程序,并在浏览器中访问http://localhost:8080/export来测试导出excel文件的功能。当我们点击链接时,浏览器会下载一个名为“学生信息表.xlsx”的excel文件,其中包含了我们之前定义的学生信息。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会很甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值