某业务可以根据Excel模板的方式新增数据,其导入模板中包含组织机构信息,人员信息。以往的做法是用户在导入时自己填写模板。
针对如上弊端,产品提出如下需求
1.学习Excel如何制作下拉框
2.基于Easyexcel实现Excel下拉框选择数据
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExcelCascade {
/**
*下拉选项
*/
private List<String> options;
/**
* 级联模板
*/
private String parentColTpl;
/**
* 当前下拉列索引
*/
private Integer curColIndex;
/**
* 下拉列表首行
*/
private Integer firstRow;
/**
* 下拉列表末行
*/
private Integer lastRow;
/**
* 下拉列表名称
*/
private String title;
}
public static List<ExcelCascade> init() {
List<ExcelCascade> excelCascades = new ArrayList<>();
List<OrganizationVO> organizationVOS = new ArrayList<>() {{
add(OrganizationVO.builder().id("1").code("1").name("公司").parentId("0").type("10").build());
add(OrganizationVO.builder().id("2").code("2").name("处室").parentId("1").type("20").build());
add(OrganizationVO.builder().id("3").code("3").name("科室").parentId("2").type("30").build());
add(OrganizationVO.builder().id("4").code("4").name("班组").parentId("3").type("30").build());
}};
Map<String, OrganizationVO> orgIdOrgMap = organizationVOS.stream().collect(Collectors.toMap(OrganizationVO::getId, Function.identity()));
Map<String, List<OrganizationVO>> orgGroupingByParentIdMap = organizationVOS.stream().filter(o -> !StrUtil.equals(o.getType(), "10")).collect(Collectors.groupingBy(OrganizationVO::getParentId));
orgGroupingByParentIdMap.forEach((k, v) -> {
OrganizationVO parentOrganizationVO = orgIdOrgMap.get(k);
if (Objects.nonNull(parentOrganizationVO)) {
int curColIndex = 0;
String parentColTpl = "";
String type = parentOrganizationVO.getType();
if (StrUtil.equals(type, "10")) {
curColIndex = 1;
}
if (StrUtil.equals(type, "20")) {
curColIndex = 2;
parentColTpl = "INDIRECT($B${})";
}
if (StrUtil.equals(type, "30")) {
curColIndex = 3;
parentColTpl = "INDIRECT($C${})";
}
ExcelCascade tmp = ExcelCascade.builder()
//考虑名称中不能包含特殊字符 TODO
.title(parentOrganizationVO.getName() + "_" + parentOrganizationVO.getCode())
.options(v.stream().map(o -> o.getName() + "_" + o.getCode()).collect(Collectors.toList()))
.curColIndex(curColIndex)
.parentColTpl(parentColTpl)
.firstRow(1)
.lastRow(999)
.build();
excelCascades.add(tmp);
}
});
List<UserVO> userVOS = new ArrayList<>() {{
add(UserVO.builder().id("1").name("处室的人").code("1").orgId("2").build());
add(UserVO.builder().id("2").name("科室的人").code("2").orgId("3").build());
add(UserVO.builder().id("3").name("班组的人").code("3").orgId("4").build());
add(UserVO.builder().id("3").name("班组的人").code("4").orgId("4").build());
}};
Map<String, List<String>> userGroupByOrgIdMap = userVOS.stream().collect(Collectors.groupingBy(UserVO::getOrgId, Collectors.mapping(o -> o.getName() + "_" + o.getCode(), Collectors.toList())));
userGroupByOrgIdMap.forEach((k, v) -> {
OrganizationVO organizationVO = orgIdOrgMap.get(k);
if (Objects.nonNull(organizationVO)) {
ExcelCascade tmp = ExcelCascade.builder()
//考虑名称中不能包含特殊字符 TODO
.title(organizationVO.getName() + "_" + organizationVO.getCode() + "_成员")
.options(v)
.curColIndex(4)
.parentColTpl("INDIRECT((IF(ISBLANK($D${}),IF(ISBLANK($C${}),IF(ISBLANK($B${}),$B${},$B${}),$C${}),$D${}))&\"_成员\")")
.firstRow(1)
.lastRow(999)
.build();
excelCascades.add(tmp);
}
});
return excelCascades;
}
注意
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.example.demo.entity.ExcelCascade;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public class CascadeWriteHandler implements SheetWriteHandler {
/**
* 下拉框信息
*/
private List<ExcelCascade> selectData;
public CascadeWriteHandler(List<ExcelCascade> selectData) {
this.selectData = selectData;
}
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
//获取工作簿
Sheet sheet = writeSheetHolder.getSheet();
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
Workbook book = writeWorkbookHolder.getWorkbook();
//创建一个专门用来存放地区信息的隐藏sheet页
//因此不能在现实页之前创建,否则无法隐藏。
String hideSheetName = "hideSheet";
Sheet hideSheet = book.createSheet(hideSheetName);
//设置隐藏 建议在构建模板时注释掉改行
//book.setSheetHidden(book.getSheetIndex(hideSheet), true);
// 将具体的数据写入到每一行中,行开头为父级区域即公式名称,后面是子区域,即下拉选项。
AtomicInteger rowId = new AtomicInteger(1);
List<Integer> hasSet = new CopyOnWriteArrayList<>();
selectData.forEach(sd -> {
int rowIdNumber = rowId.getAndIncrement();
Row row = hideSheet.createRow(rowIdNumber);
String title = sd.getTitle();
row.createCell(0).setCellValue(title);
List<String> options = sd.getOptions();
for (int i = 0; i < options.size(); i++) {
Cell cell = row.createCell(i + 1);
cell.setCellValue(options.get(i));
}
// 添加名称管理器
String range = getRange(1, rowIdNumber + 1, options.size());
Name name = book.createName();
name.setNameName(title);
String formula = hideSheetName + "!" + range;
name.setRefersToFormula(formula);
//设置下拉框
if (StringUtils.isBlank(sd.getParentColTpl()) && !hasSet.contains(sd.getCurColIndex())) {
hasSet.add(sd.getCurColIndex());
DataValidationConstraint expConstraint = dvHelper.createFormulaListConstraint(title);
CellRangeAddressList expRangeAddressList = new CellRangeAddressList(sd.getFirstRow(), sd.getLastRow(), sd.getCurColIndex(), sd.getCurColIndex());
setValidation(sheet, dvHelper, expConstraint, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
} else {
if (!hasSet.contains(sd.getCurColIndex())) {
hasSet.add(sd.getCurColIndex());
for (int i = sd.getFirstRow() + 1; i < sd.getLastRow(); i++) {
DataValidationConstraint expConstraint = dvHelper.createFormulaListConstraint(StrUtil.format(sd.getParentColTpl(), i, i, i, i, i, i, i));
CellRangeAddressList expRangeAddressList = new CellRangeAddressList(i - 1, i - 1, sd.getCurColIndex(), sd.getCurColIndex());
setValidation(sheet, dvHelper, expConstraint, expRangeAddressList, "提示", "你输入的值未在备选列表中,请下拉选择合适的值");
}
}
}
});
}
/**
* 设置验证规则
*
* @param sheet sheet对象
* @param helper 验证助手
* @param constraint createExplicitListConstraint
* @param addressList 验证位置对象
* @param msgHead 错误提示头
* @param msgContext 错误提示内容
*/
private void setValidation(Sheet sheet, DataValidationHelper helper, DataValidationConstraint constraint, CellRangeAddressList addressList, String msgHead, String msgContext) {
DataValidation dataValidation = helper.createValidation(constraint, addressList);
dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
dataValidation.setShowErrorBox(true);
dataValidation.setSuppressDropDownArrow(true);
dataValidation.createErrorBox(msgHead, msgContext);
sheet.addValidationData(dataValidation);
}
/** 获取数据范围
* @param offset 偏移量,如果给0,表示从A列开始,1,就是从B列
* @param rowId 第几行
* @param colCount 一共多少列
* @return 数据范围
*/
public String getRange(int offset, int rowId, int colCount) {
char start = (char) ('A' + offset);
return "$" + start + "$" + rowId + ":$" + numberToColumn(colCount+offset) + "$" + rowId;
}
/**
* 10 进制转 26进制
* @param number
* @return
*/
String numberToColumn(int number) {
StringBuilder column = new StringBuilder();
while (number > 0) {
int remainder = (number - 1) % 26;
column.insert(0, (char) ('A' + remainder));
number = (number - 1) / 26;
}
return column.toString();
}
}
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExportExcel {
@ExcelProperty(value = "业务名称", index = 0)
private String name;
@ExcelProperty(value = "责任部门", index = 1)
private String org;
@ExcelProperty(value = "责任科室", index = 2)
private String dept;
@ExcelProperty(value = "责任班组", index = 3)
private String team;
@ExcelProperty(value = "责任人", index = 4)
private String rspUserName;
@ExcelProperty(value = "业务开始时间", index = 5)
private String startTime;
@ExcelProperty(value = "业务完成时间", index = 6)
private String endTime;
@ExcelProperty(value = "备注", index = 7)
private String goal;
}
public static void main(String[] args) {
List<ExcelCascade> excelCascades = init();
// 写出数据
EasyExcel.write(new File("业务导入模板.xlsx"), ExportExcel.class)
.sheet("sheet1")
.registerWriteHandler(new CascadeWriteHandler(excelCascades))
.doWrite(new ArrayList<>());
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrganizationVO {
/**
* 部门Id
*/
private String id;
/**
* 部门名称
*/
private String name;
/**
* 部门代码
*/
private String code;
/**
* 父级部门Id
*/
private String parentId;
/**
* 部门类型
*/
private String type;
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserVO {
/**
* 用户Id
*/
private String id;
/**
* 用户名称
*/
private String name;
/**
* 用户代码
*/
private String code;
/**
* 归属部门Id
*/
private String orgId;
}
使用EasyExcel导出模板并设置级联下拉及其原理分析
十分感谢参考文档提供的示例,以及对Excel设置下拉框步骤的讲解。同时也感谢十分强大的Excel处理工具Easyexcel。