背景:easyexcel导出excel模板时,标题带下拉框及其下拉值过多不显示问题。
使用的easyexcel版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.4</version>
</dependency>
实现:
1、自定义注解类
import java.lang.annotation.*;
/**
* 标记导出excel的下拉数据集
*
* @return result
* @since 2021-01:15 14:22:09
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DropDownField {
/**
* 固定下拉内容
*
* @return result
* @since 2021-01:15 14:21:35
*/
String[] source() default {};
/**
* 动态下拉内容
*
* @return result
* @since 2021-01:15 14:22:30
*/
Class[] sourceClass() default {};
/**
* 设置字典常量值
*
* @return result
* @since 2021-01:15 14:22:30
*/
String value() default "";
/**
* 设置DB类
*
* @return result
* @since 2021-01:15 14:22:30
*/
Class<?> clazz() default java.lang.Object.class;
/**
* 标识是DICT还是DB或其他;true;DB或其他;false:dict
*
* @return result
* @since 2021-01:15 14:22:30
*/
boolean type() default true;
}
2、实体类
import com.alibaba.excel.annotation.ExcelProperty;
import com.test.constant.Constant;
import com.test.service.TestService;
import lombok.Data;
/**
* Description
*
* @Since 2020-12-07
*/
@Data
public class TestExcel {
/**
* 姓名
*/
@ExcelProperty(value = "姓名")
private String name;
/**
* 部门
*/
@ExcelProperty(value = "部门")
@DropDownField(sourceClass = TestDropDownSetImpl.class, clazz = TestService.class)
private String department;
/**
* 类型
*/
@ExcelProperty(value = "类型")
@DropDownField(sourceClass = TestDropDownSetImpl.class, value = Constant.TYPE, type = false)
private String type;
}
3、获取数据源的接口及其实现类
接口
import com.test.service.DictBizService;
/**
* @since 2021/1/15
*/
public abstract class AbstractDropDown {
/**
* 获取数据源
*
* @param clzz DB 类
* @return result
* @since 2021-01:15 16:20:44
*/
abstract String[] getSourceByDb(Class clzz);
/**
* 获取数据源
*
* @param dictBizService 字典对象
* @param dictConstant 字典常量
* @return result
* @since 2021-01:15 16:20:44
*/
String[] getSourceByDict(DictBizService dictBizService, String dictConstant){
if (ObjectUtil.isNotNull(dictConstant)) {
Map<String, String> map = dictBizService.getMap(dictConstant);
String[] valueArray = new String[map.size()];
return map.values().toArray(valueArray);
}
return new String[0];
}
}
实现类
import com.test.vo.TestVO;
import com.test.service.TestService;
import com.test.service.DictBizService;
import cn.hutool.core.util.ObjectUtil;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @since 2021/1/15
*/
@Component
public class ProductCostDropDownSetImpl implements AbstractDropDown {
@Override
public String[] getSourceByDb(Class clzz) {
if (clzz.isAssignableFrom(TestService.class)) {
TestService testService= new TestService();
List<TestVO> list = testService.getDepartment();
List<String> valueList = list.parallelStream().map(TestVO::getDepartment).collect(Collectors.toList());
String[] valueArray = new String[valueList.size()];
return valueList.toArray(valueArray);
}
return new String[0];
}
}
4、解析注解获取动态或静态下拉框值的接口及其实现类
接口
/**
* @since 2021/1/15
*/
public interface ResolveDropAnnotation {
/**
* 解析注解
*
* @param dropDownField annotation
* @return result
* @since 2021-01:15 18:35:42
*/
String[] resolve(DropDownField dropDownField);
}
实现类
import com.test.service.DictBizService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Optional;
/**
* @since 2021/1/15
*/
@Component
public class TestResolveDropAnnotation implements ResolveDropAnnotation {
@Resource
@Lazy
DictBizService dictBizService;
@Override
public String[] resolve(DropDownField dropDownField) {
if (!Optional.ofNullable(dropDownField).isPresent()) {
return new String[0];
}
// 获取固定下拉信息
String[] source = dropDownField.source();
if (source.length > 0) {
return source;
}
// 获取动态的下拉数据
Class<? extends AbstractDropDown>[] classes = dropDownField.sourceClass();
try {
AbstractDropDown abstractDropDown = Arrays.stream(classes).findFirst().get().newInstance();
boolean type = dropDownField.type();
String[] dynamicSource;
if (type) {
Class clazz = dropDownField.clazz();
dynamicSource = abstractDropDown.getSourceByDb(clazz);
} else {
String dictConstant = dropDownField.value();
dynamicSource = abstractDropDown.getSourceByDict(dictBizService, dictConstant);
}
if (null != dynamicSource && dynamicSource.length > 0) {
return dynamicSource;
}
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return new String[0];
}
}
5、下拉框值处理及映射到相应的行列中
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.Map;
/**
* @since 2021/1/15
*/
public class ExcelCellWriteHandler implements SheetWriteHandler {
private final Map<Integer, String[]> map;
/**
* 设置阈值,避免生成的导入模板下拉值获取不到
*/
private static final Integer LIMIT_NUMBER = 100;
public ExcelCellWriteHandler(Map<Integer, String[]> map) {
this.map = map;
}
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 这里可以对cell进行任何操作
Sheet sheet = writeSheetHolder.getSheet();
DataValidationHelper helper = sheet.getDataValidationHelper();
// k 为存在下拉数据集的单元格下表 v为下拉数据集
map.forEach((k, v) -> {
// 设置下拉单元格的首行 末行 首列 末列
CellRangeAddressList rangeList = new CellRangeAddressList(1, 65536, k, k);
// 如果下拉值总数大于100,则使用一个新sheet存储,避免生成的导入模板下拉值获取不到
if (v.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.length; i < length; i++) {
// 开始的行数i,列数k
hiddenSheet.createRow(i).createCell(k).setCellValue(v[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.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);
// 设置约束
DataValidation validation = helper.createValidation(constraint, rangeList);
// 阻止输入非下拉选项的值
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.setShowErrorBox(true);
validation.setSuppressDropDownArrow(true);
validation.createErrorBox("提示", "此值与单元格定义格式不一致");
// validation.createPromptBox("填写说明:","填写内容只能为下拉数据集中的单位,其他单位将会导致无法入仓");
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;
}
}
6、导出service
import com.test.service.TestService;
@Service
public class TestService {
@Resource
ResolveDropAnnotation resolveDropAnnotation;
public void export(HttpServletResponse response) throws IOException {
try {
// 获取该类声明的所有字段
Field[] fields = TestExcel.class.getDeclaredFields();
// 响应字段对应的下拉集合
Map<Integer, String[]> map = new HashMap<>();
Field field;
// 循环判断哪些字段有下拉数据集,并获取
for (int i = NumberEnum.ZERO.getNumber(); i < fields.length; i++) {
field = fields[i];
// 解析注解信息
DropDownField dropDownField = field.getAnnotation(DropDownField.class);
if (null != dropDownField) {
String[] sources = resolveDropAnnotation.resolve(dropDownField);
if (null != sources && sources.length > NumberEnum.ZERO.getNumber()) {
map.put(i, sources);
}
}
}
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ProductCostExcel.class)
.registerWriteHandler(new ExcelCellWriteHandler(map)).build();
WriteSheet sheet = EasyExcel.writerSheet(NumberEnum.ZERO.getNumber(), "导出明细").build();
excelWriter.write(null, sheet);
excelWriter.finish();
} catch (Throwable var6) {
log.error("导出模板异常!");
throw var6;
}
}
public List<TestVO> getDepartment(){
System.out.println("获取部门的方法...");
}
}
7、controller
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import com.test.service.TestService;
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
TestService testService;
/**
* 下载模板
*/
@GetMapping("/export")
@ApiOperationSupport(order = 1)
@ApiOperation(value = "下载模板", notes = "下载模板")
public void export(HttpServletResponse response) {
try {
test.Service.export(response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
在做导出时,需要用到下拉框值做值选入,避免出现手动输入危险。
reference link:
https://blog.csdn.net/qq_44605317/article/details/107376876?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-3.control