记录工作中使用easyExcel实现复杂一对多excel表格导出及多sheet页导出

该文章介绍了如何在一个工单对应多个项目,项目又对应多个配件信息的业务场景下,利用EasyExcel库进行数据导出。通过配置控制器、服务层实现以及自定义的导出类模板和合并策略处理器,实现了工单级联信息的Excel表格导出,包括工单号、车牌号、客户名称等详细信息,并支持多行合并。

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

业务场景

一个工单对应多个项目,一个项目对应多个配件信息。这样形成了三层级联的一对多的业务场景。

实现效果如下:
在这里插入图片描述

功能实现

1、引入maven

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

2、controller

    @ApiOperation("客户账单信息导出")
    @PostMapping("/exportWorkOrderCascadeCustomerInfo")
    public void exportWorkOrderCascadeCustomerInfo(@RequestBody WorkOrderCascadeCustomerDto param, HttpServletResponse response) throws Exception {
        workOrderService.exportWorkOrderCascadeCustomerInfo(param, response);
    }

3、serviceImpl

需要注意result的数据结构如下:
在这里插入图片描述
最后通过合并策略处理器将上面的数据接口合并如下:
在这里插入图片描述

 @Override
 public void exportWorkOrderCascadeCustomerInfo(WorkOrderCascadeCustomerDto param, HttpServletResponse response) throws Exception {
	List<WorkOrderCustomerCascade> result = workOrderMapper.getWorkOrderCascadeCustomerPageInfo(param);
	String fileName = java.net.URLEncoder.encode("工单级联信息" + DateFormatUtils.format(org.apache.commons.lang3.time.DateUtils.addDays(new Date(), -1), "yyyyMMdd") + ".xlsx", "UTF-8");
	response.setCharacterEncoding("UTF-8");
	response.setHeader("content-Type", "application/vnd.ms-excel");
	response.setHeader("Content-disposition", "attachment; filename=" + fileName);
	// 设置表头样式
	WriteCellStyle headStyle = new WriteCellStyle();
	headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
	// 设置表格内容样式
	WriteCellStyle bodyStyle = new WriteCellStyle();
	bodyStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
	bodyStyle.setVerticalAlignment(VerticalAlignment.CENTER);
	// 拿到表格处理对象
	ExcelWriter writer = EasyExcel.write(response.getOutputStream())
	        .needHead(true)
	        .excelType(ExcelTypeEnum.XLSX)
	        // 设置需要待合并的行和列。参数1:数值数组,指定需要合并的列;参数2:数值,指定从第几行开始合并
	        .registerWriteHandler(new ExcelMergeCustomerCellHandler(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,20,21,22}, 1))
	        .registerWriteHandler(new HorizontalCellStyleStrategy(headStyle, bodyStyle))
	        .build();
	// 设置表格sheet样式,并写入excel
	WriteSheet sheet = EasyExcel.writerSheet("工单级联信息").head(WorkOrderCustomerCascade.class).sheetNo(1).build();
	writer.write(result, sheet);
	
	writer.finish();
 }

4、自定义导出类模板(WorkOrderCustomerCascade)

package com.zdft.bhdcm.dispatch.entity.vo.excel;

import com.alibaba.excel.annotation.ExcelIgnore;
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 io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.Date;


@Data
@EqualsAndHashCode
@HeadRowHeight(60)
@ContentRowHeight(25)
@ColumnWidth(15)
@HeadStyle(fillForegroundColor = 44)
@NoArgsConstructor
@AllArgsConstructor
public class WorkOrderCustomerCascade {

    @ColumnWidth(20)
    @ExcelProperty({"工单号"})
    @ApiModelProperty(value = "工单号")
    private String orderCode;

    @ExcelProperty({"车牌号"})
    @ApiModelProperty(value = "车牌号")
    private String carNumber;

    @ColumnWidth(25)
    @ExcelProperty({"客户名称"})
    @ApiModelProperty(value = "客户名称")
    private String customerName;

    @ColumnWidth(25)
    @ExcelProperty({"修理厂名称"})
    @ApiModelProperty(value = "修理厂名称")
    private String factoryName;

    @ExcelProperty({"订单类型"})
    @ApiModelProperty(value = "订单类型")
    private String workOrderType;

    @ExcelProperty({"工单状态"})
    @ApiModelProperty(value = "工单状态")
    private String workOrderStatus;

    @ExcelProperty({"故障提报类型"})
    @ApiModelProperty(value = "故障提报类型")
    private String faultSubmitType;

    @ColumnWidth(25)
    @ExcelProperty({"故障提报描述"})
    @ApiModelProperty(value = "故障提报描述")
    private String faultSubmitDesc;

    @ColumnWidth(20)
    @ExcelProperty({"工单创建时间"})
    @ApiModelProperty(value = "工单创建时间")
    private String createTime;

    @ColumnWidth(20)
    @ExcelProperty({"派单日期"})
    @ApiModelProperty(value = "派单日期")
    private String dispatchTime;

    @ColumnWidth(20)
    @ExcelProperty({"开始施工时间"})
    @ApiModelProperty(value = "开始施工时间")
    private String workStartTime;

    @ColumnWidth(20)
    @ExcelProperty({"完成施工时间"})
    @ApiModelProperty(value = "完成施工时间")
    private String workEndTime;

    @ColumnWidth(20)
    @ExcelProperty({"工单关单时间"})
    @ApiModelProperty(value = "工单关单时间")
    private String closeTime;

    @ExcelProperty({"工时费合计"})
    @ApiModelProperty(value = "工时费合计")
    private BigDecimal itemPriceSum;


    @ExcelProperty({"配件合计[元]"})
    @ApiModelProperty(value = "配件合计[元]")
    private BigDecimal partsTotalSum;


    @ExcelProperty({"总合计"})
    @ApiModelProperty(value = "总合计")
    private BigDecimal allTotalSum;


    @ExcelProperty({"海容供件[元]"})
    @ApiModelProperty(value = "海容供件[元]")
    private BigDecimal companyPartsTotalSum;


    @ExcelProperty({"修理厂供件[元]"})
    @ApiModelProperty(value = "修理厂供件[元]")
    private BigDecimal factoryPartsTotalSum;

    @ColumnWidth(25)
    @ExcelProperty({"工单描述"})
    @ApiModelProperty(value = "工单描述")
    private String remark;

    @ExcelIgnore
    @ApiModelProperty(value = "项目ID")
    private String itemId;

    @ExcelIgnore
    @ApiModelProperty(value = "项目编码")
    private String itemCode;

    @ColumnWidth(40)
    @ExcelProperty({"项目信息", "项目名称"})
    @ApiModelProperty(value = "项目名称")
    private String itemName;

    @ExcelProperty({"项目信息", "工时费单价[元]"})
    @ApiModelProperty(value = "工时费单价[元]")
    private BigDecimal repairUnitPrice;

    @ExcelProperty({"项目信息", "项目数量"})
    @ApiModelProperty(value = "维修项目次数")
    private Integer itemCount;

    @ExcelProperty({"项目信息", "工时费小计[元]"})
    @ApiModelProperty(value = "工时费小计[元]")
    private BigDecimal itemPrice;

    @ExcelProperty({"配件信息", "供货渠道"})
    @ApiModelProperty(value = "供货渠道")
    private String goodsFlag;

    @ExcelProperty({"配件信息", "配件编码"})
    @ApiModelProperty(value = "配件编码")
    private String productCode;

    @ColumnWidth(40)
    @ExcelProperty({"配件信息", "配件名称"})
    @ApiModelProperty(value = "配件名称")
    private String goodsName;

    @ExcelProperty({"配件信息", "配件数量"})
    @ApiModelProperty(value = "配件数量")
    private Integer number;



    @ExcelProperty({"配件信息", "生态价[元]"})
    @ApiModelProperty(value = "生态价[元]")
    private BigDecimal costPrice;


    @ExcelProperty({"配件信息", "销售价[元]"})
    @ApiModelProperty(value = "销售价[元]")
    private BigDecimal salesPrice;


    @ExcelProperty({"配件信息", "配件小计[元]"})
    @ApiModelProperty(value = "配件小计[元]")
    private BigDecimal partsTotal;

}

5、自定义合并策略处理类(ExcelMergeCustomerCellHandler)

package com.zdft.bhdcm.dispatch.service.impl;

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;


@Data
@NoArgsConstructor
@AllArgsConstructor
public class ExcelMergeCustomerCellHandler implements CellWriteHandler {
    /**
     * 一级合并的列,从0开始算
     */
    private int[] mergeColIndex;

    /**
     * 从指定的行开始合并,从0开始算
     */
    private int mergeRowIndex;

    /**
     * 在单元格上的所有操作完成后调用,遍历每一个单元格,判断是否需要向上合并
     */
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        // 获取当前单元格行下标
        int currRowIndex = cell.getRowIndex();
        // 获取当前单元格列下标
        int currColIndex = cell.getColumnIndex();
        // 判断是否大于指定行下标,如果大于则判断列是否也在指定的需要的合并单元列集合中
        if (currRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColIndex.length; i++) {
                if (currColIndex == mergeColIndex[i]) {
                    if(currColIndex <= 18){
                        // 一级合并唯一标识
                        Object currLevelOneCode = cell.getRow().getCell(0).getStringCellValue();
                        Object preLevelOneCode = cell.getSheet().getRow(currRowIndex - 1).getCell(0).getStringCellValue();
                        // 判断两条数据的是否是同一集合,只有同一集合的数据才能合并单元格
                        if(preLevelOneCode.equals(currLevelOneCode)){
                            // 如果都符合条件,则向上合并单元格
                            mergeWithPrevRow(writeSheetHolder, cell, currRowIndex, currColIndex);
                            break;
                        }
                    }else{
                        // 一级合并唯一标识
                        Object currLevelOneCode = cell.getRow().getCell(0).getStringCellValue();
                        Object preLevelOneCode = cell.getSheet().getRow(currRowIndex - 1).getCell(0).getStringCellValue();
                        // 二级合并唯一标识
                        Object currLevelTwoCode = cell.getRow().getCell(19).getStringCellValue();
                        Object preLevelTwoCode = cell.getSheet().getRow(currRowIndex - 1).getCell(19).getStringCellValue();
                        if(preLevelOneCode.equals(currLevelOneCode)&&preLevelTwoCode.equals(currLevelTwoCode)){
                            // 如果都符合条件,则向上合并单元格
                            mergeWithPrevRow(writeSheetHolder, cell, currRowIndex, currColIndex);
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * 当前单元格向上合并
     *
     * @param writeSheetHolder 表格处理句柄
     * @param cell             当前单元格
     * @param currRowIndex     当前行
     * @param currColIndex     当前列
     */
    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int currRowIndex, int currColIndex) {
        // 获取当前单元格数值
        Object currData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        // 获取当前单元格正上方的单元格对象
        Cell preCell = cell.getSheet().getRow(currRowIndex - 1).getCell(currColIndex);
        // 获取当前单元格正上方的单元格的数值
        Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
        // 将当前单元格数值与其正上方单元格的数值比较
        if (preData.equals(currData)) {
            Sheet sheet = writeSheetHolder.getSheet();
            List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
            // 当前单元格的正上方单元格是否是已合并单元格
            boolean isMerged = false;
            for (int i = 0; i < mergeRegions.size() && !isMerged; i++) {
                CellRangeAddress address = mergeRegions.get(i);
                // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
                if (address.isInRange(currRowIndex - 1, currColIndex)) {
                    sheet.removeMergedRegion(i);
                    address.setLastRow(currRowIndex);
                    sheet.addMergedRegion(address);
                    isMerged = true;
                }
            }
            // 若上一个单元格未被合并,则新增合并单元
            if (!isMerged) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(currRowIndex - 1, currRowIndex, currColIndex, currColIndex);
                sheet.addMergedRegion(cellRangeAddress);
            }
        }
    }
}

多sheet页导出

String userAgent = request.getHeader("User-Agent");
 String fileFix = reportType == 1?"日报客户商品报表":"月报客户商品报表";
 String fileFmt = fileFix + DateFormatUtils.format(DateUtils.addDays(new Date(), -1), "yyyyMMdd") + ".xlsx";
 // 针对IE或者以IE为内核的浏览器:
 String fileName = null;
 if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
     fileName = java.net.URLEncoder.encode(fileFmt, "UTF-8");
 } else {
     // 非IE浏览器的处理:
     fileName = new String(fileFmt.getBytes("UTF-8"), "ISO-8859-1");
 }
 
 response.setCharacterEncoding("UTF-8");
 response.setHeader("content-Type", "application/vnd.ms-excel");
 response.setHeader("Content-disposition", "attachment; filename=" + fileName);
 ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
 for (int i = 0; i < result.size(); i++) {
     CustomerResultModel customerResultModel = result.get(i);
     String customerId = customerResultModel.getBindId();
     String customerName = customerResultModel.getName();
     List<GoosInfo> goods = customerMapper.queryCustomerGoodsInfo(customerId,reportType);
     goods = goods.stream().map(t -> {
         if ("2".equals(t.getIsConfirmContractPrice())) {
             t.setIsConfirmContractPrice("已确认供货");
         }
         if ("0".equals(t.getIsConfirmContractPrice()) ) {
             t.setIsConfirmContractPrice("待确认");
         }
         String goodItem = t.getGoodsCode();
         if(StringUtils.isNotBlank(goodItem)){
             Integer salesNumber = customerMapper.queryCustomerGoodsNumSum(customerId,goodItem);
             t.setSalesNumber(String.valueOf(salesNumber));
         }else{
             t.setSalesNumber("0");
         }
         return t;
     }).collect(Collectors.toList());
     WriteSheet writeSheet = EasyExcel.writerSheet(i, customerName).head(GoosInfo.class).build();
     excelWriter.write(goods, writeSheet);
 }
 excelWriter.finish();
### 回答1: Easyexcel是一个Java的excel操作工具,可以轻松地实现excel的读写操作。而一对导出指的是在一个excel导出sheet的数据。 要实现一对导出,可以按照以下步骤进行操作: 1. 创建一个Workbook对象,用于保存导出excel数据。 2. 遍历需要导出的数据源,每个数据源对应一个sheet。 3. 在每个数据源对应的sheet中,创建表头,并设置样式。 4. 写入数据到表格中,可以使用循环或者遍历方式将数据逐行写入。 5. 设置列宽,根据需要调整各列的宽度。 6. 格式化数据,根据需要对数据进行格式化,如日期格式、数值格式等。 7. 导出excel,将Workbook对象写入到输出流或者保存到文件中。 8. 关闭资源,确保所有资源被正确释放,如关闭Workbook、关闭输出流等。 使用Easyexcel,可以简化以上操作,提供了一些方便的方法来实现excel导出。例如,可以使用`EasyExcel.write()`来创建Workbook对象,使用`EasyExcel.sheet()`来创建sheet使用`EasyExcel.write().sheet().head()`来设置表头,使用`EasyExcel.write().sheet().doWrite()`来进行数据写入,等等。 总之,使用Easyexcel可以方便地实现一对导出,通过简洁的API和丰富的功能,能够满足大部分excel导出的需求。 ### 回答2: EasyExcel是一个Java开发的简单易用的Excel操作工具库,它提供了方便的API来导入和导出Excel文件。对于一对导出操作,可以使用EasyExcel的嵌套对象功能来实现。 首先,需要创建一个包含一对关系数据的对象模型。例如,我们有一个学校对象,每个学校下面有个班级。那么,我们可以创建一个School对象,其中包含一个List<Class>属性,用于存放个班级对象。 接下来,需要准备好数据,并将数据填充到创建的对象模型中。可以通过数据库查询等方式获取到需要导出一对关系数据,然后将数据逐个填充到School对象的List<Class>属性中。 最后,使用EasyExcel导出功能将填充好数据的对象写入到Excel文件中。可以通过创建一个ExcelWriter对象,然后调用write方法来实现导出。在write方法中,可以指定需要导出的对象模型和Excel文件的路径,同时还可以设置一些导出的配置,如表格样式、表头等。 在导出过程中,EasyExcel会根据对象模型的结构,自动创建Excel表头和对应的数据列,并将数据填充到相应的位置。对于一对的关系,EasyExcel会在Excel中嵌套展示个对象数据。 综上所述,使用EasyExcel进行一对导出操作,只需要创建包含一对关系的对象模型,填充数据并调用导出方法即可。EasyExcel的简单易用特性能够很好地满足一对导出的需求。 ### 回答3: EasyExcel是一个开源的Java库,用于简化Excel文件的导入和导出操作。而一对导出是指在Excel导出一张表格,其中某些列的数据是一对的关系,即一个主实体对应个子实体的数据。 在EasyExcel实现一对导出,可以按照以下步骤进行: 1. 创建Excel模板:首先,我们需要创建一个Excel模板,其中包含主实体的列和子实体的列。可以使用EasyExcel提供的注解(如@ExcelProperty)来标识每个列的数据。 2. 准备数据:接下来,我们需要准备要导出的数据。主实体的数据可以直接从数据库或其他数据源中获取,而子实体的数据通常是通过主实体的ID关联查询获得。 3. 构建Excel导出逻辑:使用EasyExcel提供的API,通过遍历主实体的数据,将主实体的字段值填充到Excel中对应的列中。对于子实体的数据,可以通过在遍历主实体时,再嵌套一个循环来遍历子实体数据,并将子实体的字段值填充到子实体的列中。 4. 导出Excel文件:完成所有数据填充后,使用EasyExcel提供的API将数据导出Excel文件。可以指定导出的文件名、文件格式等参数。 通过以上步骤,我们就可以使用EasyExcel实现一对导出。这样,我们可以方便地将一对关系的数据导出Excel,便于数据展示和使用。同时,EasyExcel提供了可扩展的功能,如数据转换、样式设置等,可以满足更导出需求的定制化操作。总的来说,使用EasyExcel可以简化一对导出的开发流程,提高开发效率。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值