关于easypoi模板导出时多个$fe标签的使用问题及解决方案参考
业务需求
先上业务需求,生产需求有一个新的模板,模板大概长这个样子

遇到的问题
但是easypoi的模板导出在多个遍历导出的时候会存在以下几个问题
多个$fe标签的使用会提示"for each存在空字符串"
$fe标签在多行遍历工作中合并单元格存在样式问题
这个问题是easypoi的bug,目前只有两条路,改源码或规避bug,本文采用第二种方案,第一种可参考:http://t.csdnimg.cn/PDMLU
解决方案
通过观察模板,发现其实可以把所有数据都放在一条下插遍历list中,如小计、房费、门票这种,难点是如何处理房费的合并,以及小计的样式,我的做法是用poi+自定义正则关键字。
定义正则
@AllArgsConstructor
public enum CellPatternEnum {
/**
* excel定制化导出正则枚举 【排列顺序为关键字处理顺序】
*/
// 红色文字样式
RED_FONT_STYLE("RED_FONT_STYLE", "样式","###redFontStyle###"),
// 合并单元格 ###merge_style(占用列(52);;占用行(1);;展示文字)###
MERGE_STYLE("MERGE_STYLE", "合并单元格", "###merge_style\\((.+?)\\)###"),
// 隐藏该行
HIDDEN_ROW("HIDDEN_ROW", "若数据为空则隐藏该行", "###hiddenRow###"),
;
@Getter
private final String code;
@Getter
private final String name;
@Getter
private final String pattern;
}数据中加入自定义关键字
代码中的###merge_style(%s;;%s;;%s)###和###redFontStyle###即为自定义关键字,后续可以通过poi遍历处理该格子
// 遍历每种类型的费用
beanMap.forEach((type, items) -> {
// 初始化预算金额和实际金额
BigDecimal budgetAmount = BigDecimal.ZERO;
BigDecimal totalRealAmount = BigDecimal.ZERO;
// 遍历各项费用
for (int i = 0, size = items.getItems().size(); i < size; i++) {
Bean item = items.getItems().get(i);
// 将费用信息转换为Map
Map<String, Object> itemMap = BeanUtil.beanToMap(item);
if (i == 0) {
// 设置合并样式
String format = String.format("###merge_style(%s;;%s;;%s)###", 0, size, type);
itemMap.put("typeName", format);
} else {
itemMap.put("typeName", "");
}
// 累加预算金额和实际金额
budgetAmount = NumberUtil.add(budgetAmount, item.getBudgetAmount());
totalRealAmount = NumberUtil.add(totalRealAmount, item.getTotalRealAmount());
// 添加到列表
itemsList.add(itemMap);
}
// 创建小计项
Bean bean = new Bean();
bean.setItemTitle("小计" + "###redFontStyle###");
bean.setBudgetAmount(budgetAmount);
bean.setTotalRealAmount(totalRealAmount);
// 将小计项转换为Map
Map<String, Object> sumColumnItemMap = BeanUtil.beanToMap(bean);
sumColumnItemMap.put("typeName", "");
// 添加到列表
itemsList.add(sumColumnItemMap);
});使用poi遍历easypoi处理后的Workbook
核心方法
for (Sheet sheet : workbook) {
for (Row row : sheet) {
for (Cell cell : row) {
Object valueObj = CommonExcelUtil.getCellValue(cell);
if (!Objects.nonNull(valueObj)) {
continue;
}
String value;
for (CellPatternEnum cellPatternEnum : CellPatternEnum.values()) {
//每次都获取最新值
value = String.valueOf(CommonExcelUtil.getCellValue(cell));
CommonExcelUtil.handleMatchedCell(workbook, sheet, cell, value, cellPatternEnum);
}
}
}
}
/**
* 根据自定义关键字处理excel模板导出
*/
public static void handleMatchedCell(Workbook workbook, Sheet sheet, Cell cell, String value, CellPatternEnum cellPatternEnum) {
Pattern pattern = Pattern.compile(cellPatternEnum.getPattern());
Matcher matcher = pattern.matcher(value);
//改为循环,修复一行字符串中有多次匹配正则问题
int matcherStart = 0;
while (matcher.find(matcherStart)) {
//重新获取最新值
value = String.valueOf(CommonExcelUtil.getCellValue(cell));
if (CellPatternEnum.RED_FONT_STYLE.equals(cellPatternEnum)) {
String s = StringUtils.replace(value, matcher.group(0), "");
// 复制新的style
CellStyle cellStyle = cell.getCellStyle();
CellStyle newCellStyle = workbook.createCellStyle();
newCellStyle.cloneStyleFrom(cellStyle);
// 设置新的style字体大小
Font font = workbook.getFontAt(newCellStyle.getFontIndexAsInt());
Font newFont = workbook.createFont();
BeanUtil.copyProperties(font, newFont);
newFont.setFontName(font.getFontName());
//字体样式
newFont.setColor(Font.COLOR_RED);
//是否加粗
newFont.setBold(true);
newFont.setUnderline(font.getUnderline());
newFont.setFontHeightInPoints(font.getFontHeightInPoints());
newCellStyle.setFont(newFont);
cell.setCellStyle(newCellStyle);
cell.setCellValue(s);
return;
}
if (CellPatternEnum.HIDDEN_ROW.equals(cellPatternEnum)) {
Row row = cell.getRow();
row.setZeroHeight(true);
row.setHeight((short) 0);
return;
}
if (CellPatternEnum.MERGE_STYLE.equals(cellPatternEnum)) {
String funcArgs = matcher.group(1);
String[] funcConfigSplits = funcArgs.split(";;");
// 合并单元格 ###merge_style(占用列(52);;占用行(1);;展示文字)###
int colspan = NumberUtil.parseInt(funcConfigSplits[0]);
int rowspan = NumberUtil.parseInt(funcConfigSplits[1]);
String s = StringUtils.isBlank(funcConfigSplits[2]) ? "" : funcConfigSplits[2];
// 清空内容 替换成 展示文字
String content = StringUtils.replace(value, matcher.group(0), s);
cell.setCellValue(content);
CellAddress address = cell.getAddress();
// 指定合并开始行、合并结束行 合并开始列、合并结束列
CellRangeAddress rangeAddress = new CellRangeAddress(address.getRow(), address.getRow() + rowspan, address.getColumn(), address.getColumn() + colspan);
// 先把限定范围内的合并格子取消
CommonExcelUtil.removeMergedRegion(sheet, rangeAddress);
// 添加要合并地址到表格
sheet.addMergedRegion(rangeAddress);
return;
}
}
}一些基本方法
/**
* 获取单元格 值
*
* @param cell cell
* @return Object
*/
public static Object getCellValue(Cell cell) {
CellType cellType = cell.getCellType();
switch (cellType) {
case STRING:
return cell.getStringCellValue();
case BOOLEAN:
return cell.getBooleanCellValue();
case FORMULA:
return cell.getCellFormula();
case NUMERIC:
// 防止double科学计数法
return BigDecimal.valueOf(cell.getNumericCellValue()).toPlainString();
default:
try {
return cell.getStringCellValue();
} catch (Exception ignored) {
return null;
}
}
}
/**
* 删除合并行
*
* @param sheet sheet
* @param cellAddresses 限定单元格范围
*/
public static void removeMergedRegion(Sheet sheet, CellRangeAddress cellAddresses) {
// 获取所有的合并单元格
int sheetMergeCount = sheet.getNumMergedRegions();
// 用于保存要移除的那个单元格序号
List<Integer> indexList = new ArrayList<>();
for (int i = 0; i < sheetMergeCount; i++) {
//获取第i个合并单元格
CellRangeAddress ca = sheet.getMergedRegion(i);
// 开始列 开始行
int caFirstColumn = ca.getFirstColumn();
int caFirstRow = ca.getFirstRow();
// 只需要判断 开始列和开始行 有没有在限定坐标内
if (cellAddresses.containsRow(caFirstRow) && cellAddresses.containsColumn(caFirstColumn)) {
indexList.add(i);
}
}
if (CollUtil.isNotEmpty(indexList)) {
//移除合并单元格
sheet.removeMergedRegions(indexList);
}
}重新定义模板

导出效果
完美解决需求!
总结
自定义关键字+poi其实可以扩展更多关键字搭配easypoi原生的关键字可以实现更多复杂的业务需求,如:###hiddenRow###,可以搭配原生的三目判断,来判断是否隐藏当前行,如:
{{le:(items) > 0 ? '': '###hiddenRow###'}}

本文链接:
/archives/9s7ckqvs
版权声明:
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自
飞的博客!
喜欢就支持一下吧