JAVA下载EXCEL模板文件设置下拉框以及级联下拉框(easyExcel)
添砖_Java_ 2024-08-17 15:35:02 阅读 57
一、概述
当有需求下载EXCEL模板文件时,一些列的数据是想让用户选择而不是输入,此时就需要实现下拉框,当下拉框逐渐多起来之后,有层级关系的时候就涉及到了级联下拉框。
导入导出基于easyExcel实现的,学习跳转--关于Easyexcel | Easy Excel 官网
二、代码实现
(一)、相关依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
(二)、代码实现
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.*;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.*;
@Slf4j
public class CascadeSelectTool {
private final XSSFWorkbook workbook;
private XSSFSheet mainSheet;
/**
* 数据
*/
private Map<String, List<String>> areaList = new LinkedHashMap<>();
/**
* 隐藏页名称
*/
private String hiddenSheetName = "hidden";
/**
* 第一行
*/
private int firstRow = 1;
/**
* 一级名称
*/
private String topName;
/**
* 级联集合
*/
private List<Integer> selectColList;
public CascadeSelectTool(XSSFWorkbook book) {
this.workbook = book;
}
public CascadeSelectTool createSheet(String sheetName) {
Sheet sheet = workbook.getSheet(sheetName);
if (Objects.nonNull(sheet)) {
this.mainSheet = (XSSFSheet) sheet;
} else {
this.mainSheet = (XSSFSheet) workbook.createSheet(sheetName);
}
return this;
}
public CascadeSelectTool createSelectDateList(Map<String, List<String>> areaList) {
this.areaList = areaList;
return this;
}
public CascadeSelectTool createTopName(String topName) {
this.topName = topName;
return this;
}
public CascadeSelectTool createSelectColList(List<Integer> selectColList) {
this.selectColList = selectColList;
return this;
}
public CascadeSelectTool createHiddenName(String hiddenSheetName) {
this.hiddenSheetName = hiddenSheetName;
return this;
}
public CascadeSelectTool createFirstRow(int firstRow) {
this.firstRow = firstRow;
return this;
}
public CascadeSelectTool createHead() {
XSSFRow startRow = this.mainSheet.createRow(0);//新增一行
// 创建单元格风格样式
XSSFCellStyle cellStyle = this.workbook.createCellStyle();
// 设置样式-单元格边框
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
XSSFCell startCell1 = startRow.createCell(0);//行中的第“0+1”列
startCell1.setCellValue("供货日期");//列中放的值
startCell1.setCellStyle(cellStyle);
XSSFCell startCell2 = startRow.createCell(1);
startCell2.setCellValue("供应商");
startCell2.setCellStyle(cellStyle);
XSSFCell startCell3 = startRow.createCell(2);
startCell3.setCellValue("食堂");
startCell3.setCellStyle(cellStyle);
XSSFCell startCell4 = startRow.createCell(3);
startCell4.setCellValue("商品");
startCell4.setCellStyle(cellStyle);
XSSFCell startCell5 = startRow.createCell(4);
startCell5.setCellValue("规格");
startCell5.setCellStyle(cellStyle);
XSSFCell startCell6 = startRow.createCell(5);
startCell6.setCellStyle(cellStyle);
startCell6.setCellValue("计量单位");
XSSFCell startCell7 = startRow.createCell(6);
startCell7.setCellValue("单价(元)");
startCell7.setCellStyle(cellStyle);
XSSFCell startCell8 = startRow.createCell(7);
startCell8.setCellStyle(cellStyle);
startCell8.setCellValue("供货数量");//列中放的值
return this;
}
/**
* 设置二级级联下拉框数据
*/
public CascadeSelectTool setCascadeDropDownBox() {
//获取所有sheet页个数
int sheetTotal = workbook.getNumberOfSheets();
//处理下拉数据
if (areaList != null && areaList.size() != 0) {
//新建一个sheet页
XSSFSheet hiddenSheet = workbook.getSheet(hiddenSheetName);
if (hiddenSheet == null) {
hiddenSheet = workbook.createSheet(hiddenSheetName);
sheetTotal++;
}
int mainStart = 2;
int mainEnd = mainStart;
// 获取数据起始行
int startRowNum = hiddenSheet.getLastRowNum() + 1;
Set<String> keySet = areaList.keySet();
for (String key : keySet) {
XSSFRow fRow = hiddenSheet.createRow(startRowNum++);
fRow.createCell(0).setCellValue(key);
List<String> sons = areaList.get(key);
for (int i = 1; i <= sons.size(); i++) {
fRow.createCell(i).setCellValue(sons.get(i - 1));
}
if (Objects.equals(topName, key)) {
mainEnd = sons.size();
}
// 添加名称管理器
String range = getRange(1, startRowNum, sons.size());
Name name = workbook.getName(key);
if (Objects.isNull(name)) {
name = workbook.createName();
//key不可重复
name.setNameName(key);
String formula = hiddenSheetName + "!" + range;
name.setRefersToFormula(formula);
}
}
//将数据字典sheet页隐藏掉
workbook.setSheetHidden(sheetTotal - 1, true);
// 设置父级下拉
//获取新sheet页内容
String mainFormula = hiddenSheetName + "!$A$" + mainStart + ":$A$" + (mainEnd + 1);
for (int i = 0; i < selectColList.size(); i++) {
Integer col = selectColList.get(i);
if (i == 0) {
// 设置下拉列表值绑定到主sheet页具体哪个单元格起作用
mainSheet.addValidationData(setDataValidation(mainFormula, firstRow, col, col));
} else {
Integer fatherCol = selectColList.get(i - 1);
// 设置子级下拉
// 当前列为子级下拉框的内容受父级哪一列的影响
String indirectFormula = "INDIRECT($" + decimalToTwentyHex(fatherCol + 1) + "" + (firstRow + 1) + ")";
mainSheet.addValidationData(setDataValidation(indirectFormula, firstRow, col, col));
}
}
}
return this;
}
/**
* 计算formula
*
* @param offset 偏移量,如果给0,表示从A列开始,1,就是从B列
* @param rowId 第几行
* @param colCount 一共多少列
* @return 如果给入参 1,1,10. 表示从B1-K1。最终返回 $B$1:$K$1
*/
private String getRange(int offset, int rowId, int colCount) {
char start = (char) ('A' + offset);
if (colCount <= 25) {
char end = (char) (start + colCount - 1);
return "$" + start + "$" + rowId + ":$" + end + "$" + rowId;
} else {
char endPrefix = 'A';
char endSuffix = 'A';
// 26-51之间,包括边界(仅两次字母表计算)
if ((colCount - 25) / 26 == 0 || colCount == 51) {
// 边界值
if ((colCount - 25) % 26 == 0) {
endSuffix = (char) ('A' + 25);
} else {
endSuffix = (char) ('A' + (colCount - 25) % 26 - 1);
}
} else {// 51以上
if ((colCount - 25) % 26 == 0) {
endSuffix = (char) ('A' + 25);
endPrefix = (char) (endPrefix + (colCount - 25) / 26 - 1);
} else {
endSuffix = (char) ('A' + (colCount - 25) % 26 - 1);
endPrefix = (char) (endPrefix + (colCount - 25) / 26);
}
}
return "$" + start + "$" + rowId + ":$" + endPrefix + endSuffix + "$" + rowId;
}
}
/**
* 返回类型 DataValidation
*
* @param strFormula formula
* @param firstRow 起始行
* @param firstCol 起始列
* @param endCol 终止列
* @return 返回类型 DataValidation
*/
private DataValidation setDataValidation(String strFormula, int firstRow, int firstCol, int endCol) {
CellRangeAddressList regions = new CellRangeAddressList(firstRow, 65535, firstCol, endCol);
DataValidationHelper dvHelper = new XSSFDataValidationHelper((XSSFSheet) workbook.getSheet(hiddenSheetName));
DataValidationConstraint formulaListConstraint = dvHelper.createFormulaListConstraint(strFormula);
return dvHelper.createValidation(formulaListConstraint, regions);
}
/**
* 返回类型 DataValidation
* @param strFormula formula
* @param firstRow 起始行
* @param endRow 终止行
* @param firstCol 起始列
* @param endCol 终止列
* @return 返回类型 DataValidation
*/
public DataValidation setTypeListDataValidation(String strFormula, int firstRow, int endRow, int firstCol, int endCol) {
CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol);
DataValidationHelper dvHelper = new XSSFDataValidationHelper((XSSFSheet) workbook.getSheet("typelist"));
DataValidationConstraint formulaListConstraint = dvHelper.createFormulaListConstraint(strFormula);
return dvHelper.createValidation(formulaListConstraint, regions);
}
/**
* 设置下拉框数据
* @param typeName 要渲染的sheet名称
* @param values 下拉框的值
* @param col 下拉列的下标
* @author Hower Wong
* @date 2022年5月27日
*/
public void setDropDownBox(String typeName, String[] values, Integer col) {
//获取所有sheet页个数
int sheetTotal = workbook.getNumberOfSheets();
//处理下拉数据
if (values != null && values.length != 0) {
//新建一个sheet页
XSSFSheet hiddenSheet = workbook.getSheet(hiddenSheetName);
if (hiddenSheet == null) {
hiddenSheet = workbook.createSheet(hiddenSheetName);
sheetTotal++;
}
// 获取数据起始行
int startRowNum = hiddenSheet.getLastRowNum() + 1;
int endRowNum = startRowNum;
//写入下拉数据到新的sheet页中
for (int i = 0; i < values.length; i++){
hiddenSheet.createRow(endRowNum++).createCell(0).setCellValue(values[i]);
}
//将新建的sheet页隐藏掉
workbook.setSheetHidden(sheetTotal - 1, true);
//获取新sheet页内容
String strFormula = hiddenSheetName + "!$A$" + ++startRowNum + ":$A$" + endRowNum;
// 设置下拉
XSSFSheet mainSheet = workbook.getSheet(typeName);
mainSheet.addValidationData(setTypeListDataValidation(strFormula, 1, 65535, col, col));
}
}
/**
* 十进制转二十六进制
*/
private String decimalToTwentyHex(int decimalNum) {
StringBuilder result = new StringBuilder();
while (decimalNum > 0) {
int remainder = decimalNum % 26;
//大写A的ASCII码值为65
result.append((char) (remainder + 64));
decimalNum = decimalNum / 26;
}
return result.reverse().toString();
}
public void writeFile() {
writeFile(workbook);
}
public static void writeFile(Workbook book) {
try {
String storeName = System.currentTimeMillis() + ".xlsx";
String folder = "project//cct/" + cn.hutool.core.date.DateUtil.format(DateUtil.date(), "yyMMdd") + "/";
String attachmentFolder = "E://" + File.separator;
String address = folder + storeName;
FileUtil.mkdir(attachmentFolder + folder);
FileOutputStream fileOut = new FileOutputStream(attachmentFolder + address);
book.write(fileOut);
fileOut.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public XSSFWorkbook getWorkbook() {
return workbook;
}
public void backFlow(HttpServletResponse response) {
try {
// 将工作簿写入输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
// 设置响应头,告诉浏览器返回的是一个Excel文件
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename=.xls");
// 将Excel文件写入响应的输出流
OutputStream outStream = response.getOutputStream();
outputStream.writeTo(outStream);
outStream.flush();
outStream.close();
} catch (Exception e) {
log.error("导出异常-->", e);
}
}
}
三、导出EXCEL设置下拉框
设置下拉框导出示例
/**
* 商品导入模板下载
* @param response
*/
@PostMapping("/template")
@SneakyThrows
public void excelOtherTemplate(HttpServletResponse response){
String nameStr = "商品导入模板";
List<String> heads = Arrays.asList("单号", "姓名", "供应商", "商品", "价格");
XSSFWorkbook book = new XSSFWorkbook();
CascadeSelectTool cascadeSelectTool = new CascadeSelectTool(book)
.createSheet(nameStr)
.createHead(heads)
.createEmptyList(20, heads.size());
List<String> storeList = CollectionUtil.newArrayList("哇哈哈", "可口可乐");
String[] storeStr = storeList.toArray(new String[storeList.size()]);
cascadeSelectTool.setDropDownBox(nameStr, storeStr, 2);
List<String> merList = CollectionUtil.newArrayList("营养快线", "爽歪歪", "AD钙");
String[] merStr = merList.toArray(new String[merList.size()]);
cascadeSelectTool.setDropDownBox(nameStr, merStr, 3);
String fileName = nameStr+".xlsx";
// 将工作簿写入输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
book.write(outputStream);
// 设置响应头,告诉浏览器返回的是一个Excel文件
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 将Excel文件写入响应的输出流
OutputStream outStream = response.getOutputStream();
outputStream.writeTo(outStream);
outStream.flush();
outStream.close();
}
对于新建list的时候用asList方法还是newArrayList,如果新建的list不涉及到新增、删除操作时用asList(),涉及的话用newArrayList(),也可统一用后者。
效果图:
设置级联下拉框导出示例
/**
* 商品模板下载
* @param response
*/
@PostMapping("/template")
@SneakyThrows
public void excelTemplate(HttpServletResponse response){
String fileName = "供货单模板.xlsx";
// 将工作簿写入输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
excelTemplate().write(outputStream);
// 设置响应头,告诉浏览器返回的是一个Excel文件
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 将Excel文件写入响应的输出流
OutputStream outStream = response.getOutputStream();
outputStream.writeTo(outStream);
outStream.flush();
outStream.close();
}
public XSSFWorkbook excelTemplate() {
//业务数据模拟 请根据真实场景替换
Map<Long, String> supNameMap = new HashMap<>();
supNameMap.put(1L, "哇哈哈");
supNameMap.put(2L, "可口可乐");
Map<Long, List<String>> merMap = new HashMap<>();
merMap.put(1L, Arrays.asList("营养快线", "爽歪歪", "AD钙"));
merMap.put(2L, Arrays.asList("美汁源","芬达","水动乐","健怡","零度"));
//示例--供应商与商品的级联下拉框
List<String> supNameList = supNameMap.values().stream().collect(Collectors.toList());
Map<String, List<String>> areaList = new LinkedHashMap<>();
areaList.put("供应商", supNameList);
List<Map<String, List<String>>> twoList = new ArrayList<>();
for (Map.Entry<Long, String> entry : supNameMap.entrySet()) {
String supName = entry.getValue();
Long supId = entry.getKey();
List<String> commodityList = merMap.get(supId);
List<String> merNameList = new ArrayList<>();
Map<String, List<String>> twoMap = new HashMap<>();
for (String merName : commodityList) {
merNameList.add(merName);
}
if (merNameList.isEmpty()) {
merNameList.add("");
}
twoMap.put(supName, merNameList);
twoList.add(twoMap);
}
twoList.forEach(t -> areaList.putAll(t));
List<Integer> selectColList = CollectionUtil.newArrayList(2,3);
XSSFWorkbook book = new XSSFWorkbook();
List<String> heads = Arrays.asList("单号", "姓名", "供应商", "商品", "价格");
CascadeSelectTool cascadeSelectTool = new CascadeSelectTool(book)
.createSheet("供货单")
.createHead(heads)
.createEmptyList(20, heads.size())
.createFirstRow(1)
.createSelectDateList(areaList)
.createTopName("供应商")
.createSelectColList(selectColList)
.setCascadeDropDownBox();
//示例--姓名下拉框
List<String> nameList = Arrays.asList("张三", "李四", "王五");
cascadeSelectTool.setDropDownBox( "供货单", nameList.toArray(new String[nameList.size()]), 1);
return cascadeSelectTool.getWorkbook();
}
参考地址:Java 导出Excel下拉框(多级级联)-CSDN博客
效果图:
正片结束~~
附:关于导入导出的杂记
附上easyExcel导入导出一些小tips:
导入导出遇到时间类型报错处理
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 自定义LocalDateStringConverter
* 用于解决使用easyexcel导出表格时候,默认不支持LocalDateTime日期格式
*
* 在需要的属性上添加注解 @ExcelProperty(value = "创建日期", converter = LocalDateStringConverter.class)
*/
public class LocalDateStringConverter implements Converter<LocalDate> {
@Override
public Class supportJavaTypeKey() {
return LocalDateTime.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public WriteCellData<?> convertToExcelData(LocalDate localDate, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
WriteCellData cellData = new WriteCellData();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String cellValue;
cellValue=formatter.format(localDate);
cellData.setType(CellDataTypeEnum.STRING);
cellData.setStringValue(cellValue);
cellData.setData(cellValue);
return cellData;
}
}
表头的自动行高
设置表头的自动调整行高策略
import com.alibaba.excel.write.style.row.AbstractRowHeightStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import java.util.Iterator;
/**
* 设置表头的自动调整行高策略
*/
public class CellRowHeightStyleStrategy extends AbstractRowHeightStyleStrategy {
/**
* 默认高度
*/
private static final Integer DEFAULT_HEIGHT = 300;
@Override
protected void setHeadColumnHeight(Row row, int relativeRowIndex) {
//设置主标题行高为10
if(relativeRowIndex == 0){
row.setHeight((short) 500);
}
}
@Override
protected void setContentColumnHeight(Row row, int relativeRowIndex) {
Iterator<Cell> cellIterator = row.cellIterator();
while (!cellIterator.hasNext()) {
return;
}
// 默认为 1行高度
int maxHeight = 1;
while (cellIterator.hasNext()) {
Cell cell = cellIterator.next();
if (cell.getCellTypeEnum() == CellType.STRING) {
String value = cell.getStringCellValue();
int len = value.length();
int num = 0;
if (len > 50) {
num = len % 50 > 0 ? len / 50 : len / 2 - 1;
}
if (num > 0) {
for (int i = 0; i < num; i++) {
value = value.substring(0, (i + 1) * 50 + i) + "\n" + value.substring((i + 1) * 50 + i, len + i);
}
}
if (value.contains("\n")) {
int length = value.split("\n").length;
maxHeight = Math.max(maxHeight, length) + 1;
}
}
}
row.setHeight((short) ((maxHeight) * DEFAULT_HEIGHT));
}
}
设置表头和填充内容的样式
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
/**
* 设置表头和填充内容的样式
*/
public class CellStyleStrategy extends HorizontalCellStyleStrategy {
private final WriteCellStyle headWriteCellStyle;
private final WriteCellStyle contentWriteCellStyle;
public CellStyleStrategy(WriteCellStyle headWriteCellStyle, WriteCellStyle contentWriteCellStyle) {
this.headWriteCellStyle = headWriteCellStyle;
this.contentWriteCellStyle = contentWriteCellStyle;
}
//设置头样式
@Override
protected void setHeadCellStyle( CellWriteHandlerContext context) {
// 字体
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontName("宋体");
headWriteFont.setFontHeightInPoints((short)14);
headWriteFont.setBold(true);
headWriteCellStyle.setWriteFont(headWriteFont);
// 样式
headWriteCellStyle.setBorderBottom(BorderStyle.THIN);
headWriteCellStyle.setBottomBorderColor((short) 0);
headWriteCellStyle.setBorderLeft(BorderStyle.THIN);
headWriteCellStyle.setLeftBorderColor((short) 0);
headWriteCellStyle.setBorderRight(BorderStyle.THIN);
headWriteCellStyle.setRightBorderColor((short) 0);
headWriteCellStyle.setBorderTop(BorderStyle.THIN);
headWriteCellStyle.setTopBorderColor((short) 0);
headWriteCellStyle.setWrapped(true);
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
headWriteCellStyle.setShrinkToFit(true);
if (stopProcessing(context)) {
return;
}
WriteCellData<?> cellData = context.getFirstCellData();
WriteCellStyle.merge(headWriteCellStyle, cellData.getOrCreateStyle());
}
//设置填充数据样式
@Override
protected void setContentCellStyle(CellWriteHandlerContext context) {
WriteFont contentWriteFont = new WriteFont();
contentWriteFont.setFontName("宋体");
contentWriteFont.setFontHeightInPoints((short) 11);
//设置数据填充后的实线边框
contentWriteCellStyle.setWriteFont(contentWriteFont);
contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
contentWriteCellStyle.setWrapped(true);
WriteCellData<?> cellData = context.getFirstCellData();
WriteCellStyle.merge(contentWriteCellStyle, cellData.getOrCreateStyle());
}
}
自动设置列宽
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.springblade.core.tool.utils.Func;
import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class CellWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
private final Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>();
@Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) {
boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
if (needSetWidth) {
Map<Integer, Integer> maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap<>());
Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
columnWidth = (int) (Func.toInt(columnWidth) * 1.5);
// 单元格文本长度大于60换行
if (columnWidth >= 0) {
if (columnWidth > 60) {
columnWidth = 60;
}
Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
Sheet sheet = writeSheetHolder.getSheet();
sheet.setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
}
}
}
}
/**
* 计算长度
* @param cellDataList
* @param cell
* @param isHead
* @return
*/
private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
if (isHead) {
return cell.getStringCellValue().getBytes().length;
} else {
CellData<?> cellData = cellDataList.get(0);
CellDataTypeEnum type = cellData.getType();
if (type == null) {
return -1;
} else {
switch (type) {
case STRING:
// 换行符(数据需要提前解析好)
int index = cellData.getStringValue().indexOf("\n");
return index != -1 ?
cellData.getStringValue().substring(0, index).getBytes().length + 1 : cellData.getStringValue().getBytes().length + 1;
case BOOLEAN:
return cellData.getBooleanValue().toString().getBytes().length;
case NUMBER:
return cellData.getNumberValue().toString().getBytes().length;
default:
return -1;
}
}
}
}
}
同列相同数据合并
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* easyExcel 列合并
* 同列相同数据合并
*
*/
public class CustomMergeStrategy extends AbstractMergeStrategy {
/**
* 分组,每几行合并一次
*/
private List<List<Integer>> mergeColDataGroupCountList;
/**
* 目标合并列index
*/
private List<Integer> targetColumnIndex;
/**
* 需要开始合并单元格的首行index
*/
private Integer rowIndex;
/**
* mergeColDataList为待合并目标列的值
*/
public CustomMergeStrategy(List<List<String>> mergeColDataList, List<Integer> targetColumnIndex) {
this.mergeColDataGroupCountList = getGroupCountList(mergeColDataList);
this.targetColumnIndex = targetColumnIndex;
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
if (null == rowIndex) {
rowIndex = cell.getRowIndex();
}
// 仅从首行以及目标列的单元格开始合并,忽略其他
if (cell.getRowIndex() == rowIndex && targetColumnIndex.contains(cell.getColumnIndex())) {
//找到对应的需要合并的列
AtomicInteger i = new AtomicInteger(0);
Optional<Integer> first = targetColumnIndex.stream().filter(col -> {
i.getAndIncrement();
return col == cell.getColumnIndex();
}).findFirst();
mergeGroupColumn(sheet, first.get());
}
}
private void mergeGroupColumn(Sheet sheet, Integer index) {
int rowCount = rowIndex;
for (Integer count : mergeColDataGroupCountList.get(index)) {
if (count == 1) {
rowCount += count;
continue;
}
// 合并单元格
CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCount, rowCount + count - 1,
targetColumnIndex.get(index), targetColumnIndex.get(index));
sheet.addMergedRegionUnsafe(cellRangeAddress);
rowCount += count;
}
}
/**
* 该方法将目标列根据值是否相同连续可合并,存储可合并的行数
*/
private List<List<Integer>> getGroupCountList(List<List<String>> exportDataList) {
if (CollUtil.isEmpty(exportDataList)) {
return new ArrayList<>();
}
List<List<Integer>> groupCountListList = new ArrayList<>();
exportDataList.forEach(dataList->{
List<Integer> groupCountList = new ArrayList<>();
int count = 1;
for (int i = 1; i < dataList.size(); i++) {
if (dataList.get(i).equals(dataList.get(i - 1))) {
count++;
} else {
groupCountList.add(count);
count = 1;
}
}
// 处理完最后一条后
groupCountList.add(count);
groupCountListList.add(groupCountList);
});
return groupCountListList;
}
}
合并参考:EasyExcel合并单元格(同列相同数据合并)_easyexcel合并列单元格-CSDN博客
根据指定列合并参考:JAVA导出EXCEL根据某列自定义单元格合并(easyExcel)-CSDN博客
策略使用示例
根据实际情况使用registerWriteHandler,可以一个都不注册,也可以注册一个或多个。
// 需要合并的列 mergeColDataList
// List<List<String>> mergeColDataList 需要合并的数据
List<List<String>> mergeColDataList = Stream.of(Arrays.asList("张三","李四","王五").stream().collect(Collectors.toList()),
Arrays.asList("哇哈哈","可口可乐").stream().collect(Collectors.toList()),
Arrays.asList("芬达","零度").stream().collect(Collectors.toList())).collect(Collectors.toList());
List<Integer> mergeColIndexList = Stream.of(0,1,2).collect(Collectors.toList());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
String fileName = URLEncoder.encode(name + ".xlsx", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
EasyExcel.write(response.getOutputStream(), ExcelMealStatisticFeeDto.class)
.registerWriteHandler(new CellStyleStrategy(new WriteCellStyle(), new WriteCellStyle()))
.registerWriteHandler(new CellWidthStyleStrategy())
.registerWriteHandler(new CellRowHeightStyleStrategy())
.registerWriteHandler(new CustomMergeStrategy(mergeColDataList, mergeColIndexList))
.sheet(name)
.doWrite(list);
附上模板下载的工具类
exportNoModel方法是下载模板文件,生成文件的表头为传入的headMap。
download方法适用于本地已存在模板文件的场景,fileName为浏览器下载的文件名称(可传中文),filePath为文件所处的位置。
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Slf4j
public class ExcelUtil {
/**
* 下载模板文件
* @param response
* @param headMap
* @param fileName
* @param dataList
*/
public static void exportNoModel(HttpServletResponse response, String[] headMap, String fileName, List<List<Object>> dataList) {
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String name = URLEncoder.encode(fileName, "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + name + ".xlsx");
EasyExcel.write(response.getOutputStream()).head(createdHead(headMap))
.registerWriteHandler(new CellStyleStrategy(new WriteCellStyle(), new WriteCellStyle()))
.registerWriteHandler(new CellWidthStyleStrategy())
.registerWriteHandler(new CellRowHeightStyleStrategy())
.sheet(fileName).doWrite(dataList);
} catch (IOException e) {
log.error("导出异常-->", e);
}
}
/**
* 下载模板文件
* @param response
* @param fileName
* @param filePath
*/
public static void download(HttpServletResponse response, String fileName, String filePath){
try {
response.setContentType("application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setHeader("filename", URLEncoder.encode(fileName, "UTF-8"));
response.setHeader("Access-Control-Expose-Headers", "filename,Content-Disposition");
ClassPathResource resource = new ClassPathResource(filePath);
InputStream input = resource.getInputStream();
OutputStream out = response.getOutputStream();
byte[] b = new byte[2048];
int len;
while ((len = input.read(b)) != -1) {
out.write(b, 0, len);
}
input.close();
} catch (Exception e) {
log.error("下载模板失败 :", e);
}
}
/**
* 处理字段的表头值
* @param headMap
* @return
*/
private static List<List<String>> createdHead(String[] headMap) {
List<List<String>> headList = new ArrayList<List<String>>();
for (String head : headMap) {
List<String> list = new ArrayList<String>();
list.add(head);
headList.add(list);
}
return headList;
}
}
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。