当前位置: 首页 > news >正文

Spring Boot集成EasyExcel实现数据导出

        在本文中,我们将探讨如何使用Spring Boot集成EasyExcel库来实现数据导出功能。我们将学习如何通过EasyExcel库生成Excel文件,并实现一些高级功能,如支持列下拉和自定义单元格样式,自适应列宽、行高,动态表头 ,以及如何同时导出多个sheet页的数据。

引入依赖

        首先,我们需要在pom.xml文件中添加EasyExcel和相关的依赖项

            <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.2.1</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency>

创建参数类

 动态生成EXCEL参数类

        支持sheet名称、模版类、动态表头、数据集、下拉列、单元格样式定义。


import lombok.Data;import java.io.Serializable;
import java.util.List;
import java.util.Map;/*** <p>  导出动态参数   </p>*/
@Data
public class EasyExcelExportDynamicParam implements Serializable {/*** sheet名称*/private String sheetName;/*** 模版*/private Class<?> template;/*** 数据集*/private List<?> dataList;/*** 动态表头*/private List<List<String>> dynamicHeaderList;/*** 单元格样式map,key为行下标,* Map<Integer,EasyExcelExportDynamicStyleParam> key为列下标*/private Map<Integer, Map<Integer, EasyExcelExportDynamicStyleParam>> styleMap;/*** 下拉选项 key为列下标*/private Map<Integer, ExcelSelectedResolve> selectedMap;
}

单元格样式参数类

        支持字体颜色、背景颜色、字体、字体大小、单元格内容对齐方式。

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.poi.ss.usermodel.HorizontalAlignment;import java.io.Serializable;/*** <p>  EasyExcel导出动态单元格样式   </p>*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EasyExcelExportDynamicStyleParam implements Serializable {/*** 字体颜色 IndexedColors.WHITE.getIndex()*/private Short fontColor;/*** 背景颜色*/private Short bgColor;/*** 字体*/private String fontName;/*** 字体大小*/private Short fontSize;/*** 单元格内容对齐方式*/private HorizontalAlignment alignment;}

 单元格添加下拉列表配置

        支持注解方式设置单元格下拉列表,起始行、结束行、固定下拉内容、动态下拉内容。

import java.lang.annotation.*;/*** <p>  excel动态下拉框数据填充   </p>*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelSelected {/*** 固定下拉内容*/String[] source() default {};/*** 动态下拉内容服务类*/String dynamicData() default "";/*** 动态下拉内容参数** @return*/String dynamicParam() default "";/*** 设置下拉框的起始行,默认为第二行*/int firstRow() default 1;/*** 设置下拉框的结束行,默认10000行*/int lastRow() default 5000;
}/*** <p>  excel动态下拉框数据服务提供者   </p>*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelDynamicData {/*** 提供数据的服务名** @return*/String name();}/*** <p>     </p>*/
public interface ExcelDynamicSelectHandler {/*** 获取动态生成的下拉框可选数据* @return 动态生成的下拉框可选数据*/String[] getSource(String param);
}import com.alibaba.excel.annotation.ExcelProperty;
import com.yt.bi.goods.common.annotation.ExcelSelected;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Field;
import java.util.*;/*** <p>  自定义ExcelSelected注解解析  </p>*/
@Data
@Slf4j
public class ExcelSelectedResolve {/*** 下拉内容*/private String[] source;/*** 设置下拉框的起始行,默认为第二行*/private int firstRow = 1;/*** 设置下拉框的结束行*/private int lastRow = 2000;/*** 解析表头类中的下拉注解** @param head 表头类* @return Map<下拉框列索引, 下拉框内容> map*/public static Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<?> head) {Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();if (Objects.isNull(head)) {return selectedMap;}// 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) {continue;}String[] source = resolveSelectedSource(selected);if (source == null || source.length == 0) {continue;}ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();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;}/*** 解析表头类中的配置注解** @param head 表头类*/public static List<List<String>> resolvePropertyAnnotation(Class<?> head) {List<List<String>> list = new ArrayList<>();if (Objects.isNull(head)) {return list;}// getDeclaredFields(): 返回全部声明的属性;getFields(): 返回public类型的属性Field[] fields = head.getDeclaredFields();for (Field field : fields) {ExcelProperty property = field.getAnnotation(ExcelProperty.class);if (property != null) {list.add(Arrays.asList(property.value()));}}return list;}/*** 获取下拉框选项值** @param excelSelected* @return*/private static String[] resolveSelectedSource(ExcelSelected excelSelected) {if (excelSelected == null) {return null;}// 获取固定下拉框的内容String[] source = excelSelected.source();if (source.length > 0) {return source;}// 获取动态下拉框的内容ExcelDynamicSelectHandler excelDynamicSelectHandler = ExcelDynamicDataStrategyFactory.doStrategy(excelSelected.dynamicData());if (Objects.nonNull(excelDynamicSelectHandler)) {return excelDynamicSelectHandler.getSource(excelSelected.dynamicParam());}return null;}}

创建导出功能工具类

        为了实现高内聚和低耦合的设计,我们可以创建一个导出功能的工具类EasyExcelUtil,支持动态表头生成、多sheet、下拉列等功能

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import lombok.extern.slf4j.Slf4j;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;@Slf4j
public class EasyExcelUtil {/*** 生成多个sheet** @param response* @param paramList* @param fileName* @throws IOException*/public static void exportExcel(HttpServletResponse response, List<EasyExcelExportDynamicParam> paramList, String fileName) throws IOException {response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build()) {for (EasyExcelExportDynamicParam param : paramList) {ExcelWriterSheetBuilder writerSheetBuilder = EasyExcel.writerSheet(param.getSheetName()).head(param.getDynamicHeaderList()).head(param.getTemplate());// 样式Map<Integer, Map<Integer, EasyExcelExportDynamicStyleParam>> styleMap = param.getStyleMap();if (CollectionUtil.isNotEmpty(styleMap)) {writerSheetBuilder.registerWriteHandler(new CellStyleSheetWriteHandler(styleMap));}// 下拉选择Map<Integer, ExcelSelectedResolve> selectedMap =CollectionUtil.isNotEmpty(param.getSelectedMap()) ? param.getSelectedMap() : ExcelSelectedResolve.resolveSelectedAnnotation(param.getTemplate());if (CollectionUtil.isNotEmpty(selectedMap)) {writerSheetBuilder.registerWriteHandler(new SelectedSheetWriteHandler(selectedMap));}excelWriter.write(param.getDataList(), writerSheetBuilder.build());}excelWriter.finish();}}}

使用CellWriteHandler实现自定义单元格样式

        EasyExcel提供了CellWriteHandler接口,其中的afterCellDispose方法在单元格写操作完成并销毁后被调用。我们可以通过实现该接口并重写afterCellDispose方法来实现自定义单元格样式。

        在重写的afterCellDispose方法中,我们可以获取到已经创建好的单元格,并添加自定义的样式。这个方法在每个单元格写操作完成后都会被调用,因此我们可以根据需要对特定的单元格或整个表格进行样式处理。

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.StyleUtil;
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.alibaba.excel.write.metadata.style.WriteCellStyle;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.springframework.util.CollectionUtils;import java.util.List;
import java.util.Map;
import java.util.Objects;/*** <p>  excel设置动态列样式处理器   </p>*/
@Data
@AllArgsConstructor
public class CellStyleSheetWriteHandler implements CellWriteHandler {private static final short DEFAULT_FONT_SIZE = 14;private static final String DEFAULT_FONT_NAME = "宋体";private static final short DEFAULT_FONT_COLOR = 8;private static final short DEFAULT_BG_COLOR = 22;private Map<Integer, Map<Integer, EasyExcelExportDynamicStyleParam>> styleMap;@Overridepublic void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex,Boolean isHead) {int rowIndex = cell.getRowIndex();Sheet sheet = cell.getSheet();Workbook workbook = sheet.getWorkbook();Row row = sheet.getRow(rowIndex);int columnIndex = cell.getColumnIndex();if (isHead) {// 表头设置自适应列宽// 获取单元格内容长度(以字符为单位)String stringCellValue = cell.getStringCellValue();int contentLength = stringCellValue.length();// 计算自动调整后的列宽(加上一些额外空间)int newWidth = contentLength > 10 ? (contentLength + 35) * 256 : (contentLength + 12) * 256;sheet.setColumnWidth(columnIndex, newWidth);// 表头设置自适应行高String[] split = stringCellValue.split("\\n");if (split != null && split.length > 0) {setRowHeight(row, (short) ((split.length + 1.2) * 256));}}if (CollectionUtil.isEmpty(styleMap)) {return;}Map<Integer, EasyExcelExportDynamicStyleParam> indexes = styleMap.get(rowIndex);if (CollectionUtils.isEmpty(indexes)) {return;}// 自定义样式setCellStyle(row, cell, workbook, indexes.get(columnIndex));}/*** 自定义样式** @param cell* @param workbook* @param styleParam*/private void setCellStyle(Row row, Cell cell, Workbook workbook, EasyExcelExportDynamicStyleParam styleParam) {if (Objects.isNull(styleParam)) {return;}// 字体Font font = workbook.createFont();font.setFontName(StringUtils.isNotBlank(styleParam.getFontName()) ? styleParam.getFontName() : DEFAULT_FONT_NAME);font.setFontHeightInPoints(Objects.nonNull(styleParam.getFontSize()) ? styleParam.getFontSize() : DEFAULT_FONT_SIZE);font.setBold(true);font.setColor(Objects.nonNull(styleParam.getFontColor()) ? styleParam.getFontColor() : DEFAULT_FONT_COLOR);WriteCellStyle writeCellStyle = new WriteCellStyle();writeCellStyle.setFillForegroundColor(Objects.nonNull(styleParam.getBgColor()) ? styleParam.getBgColor() : DEFAULT_BG_COLOR);writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);writeCellStyle.setWrapped(true);CellStyle cellStyle = workbook.createCellStyle();// 克隆原有样式属性cellStyle.cloneStyleFrom(cell.getCellStyle());CellStyle newCellStyle = StyleUtil.buildCellStyle(workbook, cellStyle, writeCellStyle);newCellStyle.setFont(font);if (Objects.nonNull(styleParam.getAlignment())) {newCellStyle.setAlignment(styleParam.getAlignment());}// 设置新样式cell.setCellStyle(newCellStyle);}/*** 设置行高** @param row* @param height*/private void setRowHeight(Row row, short height) {if (row != null) {row.setHeight(height);}}/*** 写入器排序问题AbstractCellWriteHandler使用的默认序号是0,* EasyExcel自己的样式填充器FillStyleCellWriteHandler使用序号是50000(可在OrderConstant类中查到),* 也就是说我们在这个类中重写样式时又被easy excel重写回去了。* 解决方法是重写order方法使其大于50000 即可。** @return*/@Overridepublic int order() {return 1000000;}

 使用SheetWriteHandler实现自定义下拉列表处理

        建一个名为SelectedSheetWriteHandler的类,并实现com.alibaba.excel.write.handler.SheetWriteHandler接口。这个接口中定义了一些回调方法,允许你在生成Excel文件的过程中进行自定义处理。
        重写afterSheetCreate方法:在SelectedSheetWriteHandler类中,实现afterSheetCreate方法。这个方法会在每个Sheet创建完成后被调用,我们可以在这里进行下拉列表的处理。我们可以在每个Sheet创建完成后,为指定的单元格添加下拉列表,并设置数据源。这样,我们就能更好地控制用户在Excel中输入的数据,提高数据的准确性和一致性。

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;import java.util.Map;
import java.util.Objects;/*** <p>  excel设置下拉选项处理器   </p>*/@Data
@AllArgsConstructor
public class SelectedSheetWriteHandler implements SheetWriteHandler {private final Map<Integer, ExcelSelectedResolve> selectedMap;@Overridepublic void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {if (CollectionUtil.isEmpty(selectedMap)) {return;}// 这里可以对cell进行任何操作Sheet sheet = writeSheetHolder.getSheet();DataValidationHelper helper = sheet.getDataValidationHelper();selectedMap.forEach((k, v) -> {if (Objects.isNull(v)) {return;}// 设置下拉列表的行: 首行,末行,首列,末列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);});}
}

使用示例

示例一(多sheet页固定表头,支持动态下拉列表)

1:定义模版类

import com.alibaba.excel.annotation.ExcelProperty;
import com.yt.bi.goods.common.annotation.ExcelSelected;
import com.yt.bi.goods.common.constant.ExcelConstants;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.io.Serializable;@Data
public class ProductSkuUpdateBasicsTemplateDTO implements Serializable {private static final long serialVersionUID = 1L;// 字符串的头背景设置成黄色 IndexedColors.PINK.getIndex()@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*SKU"}, index = 0)private String sku;@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "产品名称"}, index = 1)private String productName;@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "末级品类编码"}, index = 2)private String categoryCodeLast;@ExcelSelected(dynamicData = "bi_dict", dynamicParam = "product_origin_receiving", firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "头程方式"}, index = 3)private String originReceiving;@ApiModelProperty("是否有配件 1是 0否")@ExcelSelected(source = {"是", "否"}, firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "是否包含配件"}, index = 4)private String haveParts;@ApiModelProperty("是否反倾销 1=是; 0=否;")@ExcelSelected(source = {"是", "否"}, firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "是否反倾销"}, index = 5)private String antiDumpingFlag;@ApiModelProperty("是否带电 1是 0否")@ExcelSelected(source = {"是", "否"}, firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "是否带电"}, index = 6)private String electrifyFlag;@ApiModelProperty("主项目组")@ExcelSelected(dynamicData = "erp_dict", dynamicParam = "main_project_team", firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "主项目组"}, index = 7)private String mainProjectTeam;@ApiModelProperty("输入电压")@ExcelSelected(dynamicData = "sku_voltage", firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "输入电压"}, index = 8)private String voltage;@ApiModelProperty("产品开发人员")@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "产品开发人员工号(多个人员请用&隔开)"}, index = 9)private String productDeveloper;}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.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.enums.BooleanEnum;
import lombok.Data;import java.io.Serializable;@HeadFontStyle(fontName = "宋体",color = Short.MAX_VALUE,fontHeightInPoints = 14
)
@HeadRowHeight(value = 30)
@ContentRowHeight(value = 20)
@ColumnWidth(value = 15)
@Data
public class DeveloperTemplateDTO implements Serializable {@ExcelProperty({"工号"})private String accountName;@ExcelProperty({"人员名称"})private String userName;
}
其中ProductSkuUpdateBasicsTemplateDTO模版类种的haveParts等字段为固定值下拉列表,mainProjectTeam等字段为动态值下拉列表,动态下拉列表数据提供示例如下:
/*** ERP数据字典处理类*/
@Slf4j
@Component
@ExcelDynamicData(name = "erp_dict")
public class ErpDictDataRpcHandle implements ExcelDynamicSelectHandler {/*** 查询字典信息** @param param* @return*/@Overridepublic String[] getSource(String dictType) {if (StringUtils.isBlank(param)) {return new String[0];}ErpDictDataQuery query = new ErpDictDataQuery();query.setTopFlag(Constants.ZERO);query.setDictType(dictType);List<ErpDictDataDTO> dictDataDTOList = erpDictList(query);// 查询数据库或其他方式获取数据if (CollectionUtils.isNotEmpty(dictDataDTOList)) {return dictDataDTOList.stream().map(ErpDictDataDTO::getDictLabel).toArray(String[]::new);}return new String[0];}
}

2:构建导出参数

public void batchUpdateSkuImportTemplate(HttpServletResponse response) throws IOException {// 导入数据页List<EasyExcelExportDynamicParam> paramList = new ArrayList<>();EasyExcelExportDynamicParam param = new EasyExcelExportDynamicParam();param.setSheetName("导入数据页");param.setTemplate(ProductSkuUpdateBasicsTemplateDTO.class);param.setDataList(new ArrayList<>());// 构建样式,第三行,第一列背景色黄色,字体红色buildStyle(param);paramList.add(param);// 人员对照表EasyExcelExportDynamicParam developerParam = new EasyExcelExportDynamicParam();developerParam.setSheetName("人员对照表");developerParam.setTemplate(DeveloperTemplateDTO.class);List<DeveloperTemplateDTO> templateDTOList = new ArrayList<>();developerParam.setDataList(templateDTOList);paramList.add(developerParam);EasyExcelUtil.exportExcel(response, paramList, "多sheet页导出");}/*** 样式** @param param*/private void buildStyle(EasyExcelExportDynamicParam param) {Map<Integer, Map<Integer, EasyExcelExportDynamicStyleParam>> rowStyleMap = new HashMap<>();Map<Integer, EasyExcelExportDynamicStyleParam> oneRowMap = new HashMap<>();Map<Integer, EasyExcelExportDynamicStyleParam> twoRowMap = new HashMap<>();Map<Integer, EasyExcelExportDynamicStyleParam> threeRowMap = new HashMap<>();EasyExcelExportDynamicStyleParam oneRowParam = EasyExcelExportDynamicStyleParam.builder().bgColor(IndexedColors.WHITE.getIndex()).alignment(HorizontalAlignment.LEFT).build();oneRowMap.put(0, oneRowParam);EasyExcelExportDynamicStyleParam twoRowParam = EasyExcelExportDynamicStyleParam.builder().bgColor(IndexedColors.PALE_BLUE.getIndex()).build();twoRowMap.put(0, twoRowParam);EasyExcelExportDynamicStyleParam threeRowParam = EasyExcelExportDynamicStyleParam.builder().bgColor(IndexedColors.YELLOW.getIndex()).fontColor(IndexedColors.RED.getIndex()).build();threeRowMap.put(0, threeRowParam);rowStyleMap.put(0, oneRowMap);rowStyleMap.put(1, twoRowMap);rowStyleMap.put(2, threeRowMap);param.setStyleMap(rowStyleMap);}

 3:导出结果示例

 示例二(多sheet页固定+动态表头,支持动态下拉列表,动态设置单元格格式)

1:定义固定表头模版类

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.BooleanEnum;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
import com.yt.bi.goods.common.annotation.ExcelSelected;
import com.yt.bi.goods.common.constant.ExcelConstants;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.io.Serializable;@Data
public class ProductAddNormalSkuBasicsTemplateDTO implements Serializable {private static final long serialVersionUID = 1L;@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*国家"}, index = 0)private String country;@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*序列"}, index = 1)private String series;@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*产品名称"}, index = 2)private String productName;@ExcelSelected(dynamicData = "bi_dict", dynamicParam = "product_origin_receiving", firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*头程方式"}, index = 3)private String originReceiving;@ApiModelProperty("是否有配件 1是 0否")@ExcelSelected(source = {"是", "否"}, firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*是否包含配件"}, index = 4)private String haveParts;@ApiModelProperty("是否反倾销 1=是; 0=否;")@ExcelSelected(source = {"是", "否"}, firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*是否反倾销"}, index = 5)private String antiDumpingFlag;@ApiModelProperty("主项目组")@ExcelSelected(dynamicData = "erp_dict", dynamicParam = "main_project_team", firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*主项目组"}, index = 6)private String mainProjectTeam;@ApiModelProperty("是否带电 1是 0否")@ExcelSelected(source = {"是", "否"}, firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "*是否带电"}, index = 7)private String electrifyFlag;@ExcelSelected(dynamicData = "sku_voltage", firstRow = 3)@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "输入电压"}, index = 8)private String voltage;@ApiModelProperty("产品开发人员")@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "产品开发人员工号(多个人员请用&隔开)"}, index = 9)private String productDeveloper;@ApiModelProperty("备注")@ExcelProperty(value = {"导入说明:\n" +, "基础信息", "备注"}, index = 10)private String remark;
}@HeadFontStyle(fontName = "宋体",color = Short.MAX_VALUE,fontHeightInPoints = 14
)
@HeadRowHeight(value = 30)
@ContentRowHeight(value = 20)
@ColumnWidth(value = 15)
@Data
public class DeveloperTemplateDTO implements Serializable {@ExcelProperty({"工号"})private String accountName;@ExcelProperty({"人员名称"})private String userName;
}
其中ProductAddNormalSkuBasicsTemplateDTO模版类种的haveParts等字段为固定值下拉列表,mainProjectTeam等字段为动态值下拉列表,动态下拉列表数据提供示例如下:
/*** ERP数据字典处理类*/
@Slf4j
@Component
@ExcelDynamicData(name = "erp_dict")
public class ErpDictDataRpcHandle implements ExcelDynamicSelectHandler {/*** 查询字典信息** @param param* @return*/@Overridepublic String[] getSource(String dictType) {if (StringUtils.isBlank(param)) {return new String[0];}ErpDictDataQuery query = new ErpDictDataQuery();query.setTopFlag(Constants.ZERO);query.setDictType(dictType);List<ErpDictDataDTO> dictDataDTOList = erpDictList(query);// 查询数据库或其他方式获取数据if (CollectionUtils.isNotEmpty(dictDataDTOList)) {return dictDataDTOList.stream().map(ErpDictDataDTO::getDictLabel).toArray(String[]::new);}return new String[0];}
}

2:构建导出参数

public void batchAddSkuImportTemplate(HttpServletResponse response) {List<EasyExcelExportDynamicParam> paramList = new ArrayList<>();EasyExcelExportDynamicParam param = new EasyExcelExportDynamicParam();// 生成基础信息表头List<List<String>> listList = ExcelSelectedResolve.resolvePropertyAnnotation(ProductAddNormalSkuBasicsTemplateDTO.class);// 查询品类属性ProductCategoryAttributeValueDTO attributeValueDTO = productCategoryAttributeService.queryCategoryAttributeByCategoryCode(categoryCodeLast);// 生成规格属性表头List<ProductCategoryAttributeDTO> specAttributeList = attributeValueDTO.getSpecAttributeList();if (CollectionUtil.isNotEmpty(specAttributeList)) {List<List<String>> attributeNameList = specAttributeList.stream().map(x -> Arrays.asList("导入说明:\n" +, "产品属性-规格属性", attributeNameRequiredFlag(x))).collect(Collectors.toList());listList.addAll(attributeNameList);}// 生成销售属性表头List<ProductCategoryAttributeDTO> salesAttributeList = attributeValueDTO.getSalesAttributeList();if (CollectionUtil.isNotEmpty(salesAttributeList)) {List<List<String>> attributeNameList = salesAttributeList.stream().map(x -> Arrays.asList("导入说明:\n" +, "产品属性-销售属性", attributeNameRequiredFlag(x))).collect(Collectors.toList());listList.addAll(attributeNameList);}// 生成标签属性表头List<ProductCategoryAttributeDTO> tagAttributeList = attributeValueDTO.getTagAttributeList();if (CollectionUtil.isNotEmpty(tagAttributeList)) {List<List<String>> attributeNameList = tagAttributeList.stream().map(x -> Arrays.asList("导入说明:\n" +, "产品属性-标签属性", attributeNameRequiredFlag(x))).collect(Collectors.toList());listList.addAll(attributeNameList);}// 设置颜色Map<Integer, Map<Integer, EasyExcelExportDynamicStyleParam>> styleMap = new HashMap<>();Map<Integer, EasyExcelExportDynamicStyleParam> paramMap = new HashMap<>();// 判断第三行带*号列明都加上颜色Integer num = Constants.ZERO;for (List<String> line : listList) {String secondLineName = line.get(2);if (secondLineName.startsWith("*")) {EasyExcelExportDynamicStyleParam styleParam = EasyExcelExportDynamicStyleParam.builder().bgColor(IndexedColors.YELLOW.getIndex()).fontColor(IndexedColors.RED.getIndex()).build();paramMap.put(num, styleParam);}num++;}styleMap.put(2, paramMap);Map<Integer, EasyExcelExportDynamicStyleParam> one = new HashMap<>();EasyExcelExportDynamicStyleParam oneParam = EasyExcelExportDynamicStyleParam.builder().bgColor(IndexedColors.WHITE.getIndex()).alignment(HorizontalAlignment.LEFT).build();one.put(0, oneParam);styleMap.put(0, one);param.setStyleMap(styleMap);param.setDataList(new ArrayList<>());param.setDynamicHeaderList(listList);// 生成基础信息下拉Map<Integer, ExcelSelectedResolve> head = ExcelSelectedResolve.resolveSelectedAnnotation(clazz);param.setSelectedMap(head);param.setSheetName("导入数据页");paramList.add(param);// 人员对照表EasyExcelExportDynamicParam developerParam = new EasyExcelExportDynamicParam();developerParam.setSheetName("人员对照表");developerParam.setTemplate(DeveloperTemplateDTO.class);List<DeveloperTemplateDTO> templateDTOList = new ArrayList<>();developerParam.setDataList(templateDTOList);paramList.add(developerParam);EasyExcelUtil.exportExcel(response, paramList, "多sheet页导出");}

 3:导出结果示例

注意:当使用动态表头和固定表头组合生成时,需要统一把表头单元格字段内容写入到List<List<String>> 当中。 

相关文章:

Spring Boot集成EasyExcel实现数据导出

在本文中&#xff0c;我们将探讨如何使用Spring Boot集成EasyExcel库来实现数据导出功能。我们将学习如何通过EasyExcel库生成Excel文件&#xff0c;并实现一些高级功能&#xff0c;如支持列下拉和自定义单元格样式&#xff0c;自适应列宽、行高&#xff0c;动态表头 &#xff…...

EasyExcel3.0读(日期、数字或者自定义格式转换)

EasyExcel 3.0读(日期、数字或者自定义格式转换) 依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.2.1</version> </dependency>对象 package com.xiaobu.entity.vo;import …...

浅谈C++|STL之vector篇

一.vector的基本概念 vector是C标准库中的一种动态数组容器&#xff0c;提供了动态大小的数组功能&#xff0c;能够在运行时根据需要自动扩展和收缩。vector以连续的内存块存储元素&#xff0c;可以快速访问和修改任意位置的元素。 以下是vector的基本概念和特点&#xff1a; 动…...

微信、支付宝修改步数【小米运动】

简介 小米运动是一款流行的健身应用,可以记录用户的步数和运动数据。然而,有些用户希望能够修改步数,以达到一些特定的目的。本文将介绍一个Python脚本,可以帮助用户实现修改小米运动步数的功能。 正文 脚本介绍: 本脚本是一个Python脚本,用于修改小米运动步数。通过模…...

stu02-初识HTML

1.HTML概述 &#xff08;1&#xff09;HTML是Hyper Text Mark-up Language的首字母缩写。 &#xff08;2&#xff09;HTML是一种超文本标记语言。 &#xff08;3&#xff09; 超文本&#xff1a;指除了文字外&#xff0c;页面内还可以包含图片、链接、甚至音乐、视频等非文字元…...

软件测试7大误区

随着软件测试对提高软件质量重要性的不断提高&#xff0c;软件测试也不断受到重视。但是&#xff0c;国内软件测试过程的不规范&#xff0c;重视开发和轻视测试的现象依旧存在。因此&#xff0c;对于软件测试的重要性、测试方法和测试过程等方面都存在很多不恰当的认识&#xf…...

【深度学习】 Python 和 NumPy 系列教程(十二):NumPy详解:4、数组广播;5、排序操作

目录 一、前言 二、实验环境 三、NumPy 0、多维数组对象&#xff08;ndarray&#xff09; 多维数组的属性 1、创建数组 2、数组操作 3、数组数学 4、数组广播 5、排序操作 1. np.sort() 函数 2. np.argsort() 函数 3. ndarray.sort() 方法 4. 按列或行排序 5. n…...

CSS宽度问题

一、魔法 为 DOM 设置宽度有哪些方式呢&#xff1f;最常用的是配置width属性&#xff0c;width属性在配置时&#xff0c;也有多种方式&#xff1a; widthmin-widthmax-width 通常当配置了 width 时&#xff0c;不会再配置min-width max-width&#xff0c;如果将这三者混合使…...

浅谈C++|STL之string篇

一.string的基本概念 本质 string是C风格的字符串&#xff0c;而string本质是一个字符串 string和char * 区别 char * 是一个指针string是一个类&#xff0c;类内部封装了char *&#xff0c;管理这个字符串&#xff0c;是一个char * 型容器。 特点 string类内部封装了很多成…...

Kubernetes Dashboard安装部署

Kubernetes Dashboard安装部署 1. 下载Dashboard 部署文件2. 修改yaml配置文件3. 应用安装&#xff0c;查看pod和svc4. 创建dashboard服务账户5. 创建admin-user用户的登录密钥6. 登录6.1 使用token登录(1) 短期token(2) token长期有效 6.2 使用 Kubeconfig 文件登录 7.安装met…...

在Qt的点云显示窗口中添加坐标轴C++

通过摸索整理了三个方法&#xff1a; 一、方法1&#xff1a;//不推荐&#xff0c;但可以参考 1、通过pcl的compute3DCentroid()方法计算点云的中心点坐标&#xff1b; 函数原型如下&#xff1a; compute3DCentroid (const pcl::PointCloud<PointT> &cloud, Eigen…...

[密码学入门]凯撒密码(Caesar Cipher)

密码体质五元组&#xff1a;P,C,K,E,D P&#xff0c;plaintext&#xff0c;明文空间 C&#xff0c;ciphertext&#xff0c;密文空间 K&#xff0c;key&#xff0c;密钥空间 E&#xff0c;encrypt&#xff0c;加密算法 D&#xff0c;decrypt&#xff0c;解密算法 单表代换…...

uboot 顶层Makefile-make xxx_deconfig过程说明三

一. uboot 的 make xxx_deconfig配置 本文接上一篇文章的内容。地址如下&#xff1a;uboot 顶层Makefile-make xxx_deconfig过程说明二_凌肖战的博客-CSDN博客 本文继续来学习 uboot 源码在执行 make xxx_deconfig 这个配置过程中&#xff0c;顶层 Makefile有关的执行思路。 …...

c++中的多线程通信

信息传递 #include <iostream> #include <thread> #include <chrono> #include <mutex> #include <condition_variable> #include <queue> // 用于存储和同步数据的结构 struct Data {std::queue<std::string> messag…...

IO day7

1->x.mind 2-> A进程 B进程...

C语言之指针进阶篇(3)

目录 思维导图 回调函数 案例1—计算器 案例2—qsort函数 关于qsort函数 演示qsort函数的使用 案例3—冒泡排序 整型数据冒泡排序 回调函数搞定各类型冒泡排序 cmp_int比较大小 cmp传参数 NO1. NO2. 解决方案 交换swap 总代码 今天我们学习指针难点之回调函数…...

SQL7 查找年龄大于24岁的用户信息

描述 题目&#xff1a;现在运营想要针对24岁以上的用户开展分析&#xff0c;请你取出满足条件的设备ID、性别、年龄、学校。 用户信息表&#xff1a;user_profile iddevice_idgenderageuniversityprovince12138male21北京大学Beijing23214male复旦大学Shanghai36543female20…...

vite搭建vue3项目

参考视频 1.使用npm搭建vite项目,会自动搭建vue3项目 npm create vitelatest yarn create vite2.手动搭建vue3项目 创建一个项目名称的文件夹执行命令&#xff1a;npm init -y 快速的创建一个默认的包信息安装vite: npm i vite -D -D开发环境的依赖 安装vue,现在默认是vue3.…...

Qt中表格属性相关操作,调整表格宽度高度自适应内容等

1 表格列宽设置 利用Qt designer设计&#xff0c;可以通过改变表头的列宽从而保证内容不会被遮盖&#xff0c;输入空格的方式增加表头的长度&#xff0c;比如表头为"Value"&#xff0c;则改成"Value "&#xff0c;可以扩展列默认的宽度&#xff0c;保证后面…...

NLP机器翻译全景:从基本原理到技术实战全解析

目录 一、机器翻译简介1. 什么是机器翻译 (MT)?2. 源语言和目标语言3. 翻译模型4. 上下文的重要性 二、基于规则的机器翻译 (RBMT)1. 规则的制定2. 词典和词汇选择3. 限制与挑战4. PyTorch实现 三、基于统计的机器翻译 (SMT)1. 数据驱动2. 短语对齐3. 评分和选择4. PyTorch实现…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

【Veristand】Veristand环境安装教程-Linux RT / Windows

首先声明&#xff0c;此教程是针对Simulink编译模型并导入Veristand中编写的&#xff0c;同时需要注意的是老用户编译可能用的是Veristand Model Framework&#xff0c;那个是历史版本&#xff0c;且NI不会再维护&#xff0c;新版本编译支持为VeriStand Model Generation Suppo…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

在城市的某个角落&#xff0c;一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延&#xff0c;滚滚浓烟弥漫开来&#xff0c;周围群众的生命财产安全受到严重威胁。就在这千钧一发之际&#xff0c;消防救援队伍迅速行动&#xff0c;而豪越科技消防一体化安全管控平台构建的消防“…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟

2025年4月29日&#xff0c;在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上&#xff0c;可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞&#xff0c;强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...

AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)

Name&#xff1a;3ddown Serial&#xff1a;FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名&#xff1a;Axure 序列号&#xff1a;8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...

如何通过git命令查看项目连接的仓库地址?

要通过 Git 命令查看项目连接的仓库地址&#xff0c;您可以使用以下几种方法&#xff1a; 1. 查看所有远程仓库地址 使用 git remote -v 命令&#xff0c;它会显示项目中配置的所有远程仓库及其对应的 URL&#xff1a; git remote -v输出示例&#xff1a; origin https://…...

用鸿蒙HarmonyOS5实现国际象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码&#xff0c;使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...