模版导出咨询
能否在 TemplateSheet 中添加, 只复制原表表头数据,并且支持根据程序生成的字段对复制原表表头 实行定位追加功能, 同时支持,传入字段 绑定的模板 key, 传入最终导出数据,实现模板绑定导出。 列如 我有一个初始化模板,但是可以实行一定规则的追加数据,比如订单导入商品信息时,sku 可以不设限制追加, 然后给用户数据错误信息列
excel 文件
先读到模板的样式放到Map里,Key是表头名,Value是样式,写的时候根据表头名拿到原来的样式并添加到新Excel里这样是不是满足你的需求?
先读到模板的样式放到Map里,Key是表头名,Value是样式,写的时候根据表头名拿到原来的样式并添加到新Excel里这样是不是满足你的需求?
这样是可以满足需求, 我想说的是, 通过 TemplateSheet copy 原表的功能, 然后添加绑定数据的 key,最后传入导出的数据,写到 excel , 这个能在一个动作完成吗?
先读到模板的样式放到Map里,Key是表头名,Value是样式,写的时候根据表头名拿到原来的样式并添加到新Excel里这样是不是满足你的需求?
中间 我修改过 TemplateSheet 代码, 通过 判断, row index 实现了复制原表的功能,这样实现的我需求, 但是要拆分成三步
- 复制原表 (包含样式, 数据验证) 到新表, 2. 读取新表 写入 绑定的 key 3. 读取新生成的模板表 导出错误数据
没太理解需求,你是想数据上传做内容校验,NG的数据需要原样导出,是这样的需求吗?大概需要添加一列在最后并附带异常信息?
没太理解需求,你是想数据上传做内容校验,NG的数据需要原样导出,是这样的需求吗?大概需要添加一列在最后并附带异常信息?
对,大概是这样的, 目前是我通过实现 自定义的 sheet 实现了 代码 /*
- Copyright (c) 2017-2024, [email protected] All Rights Reserved.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License. */
package org.ttzero.excel.entity;
import org.dom4j.Document; import org.dom4j.Element; import org.ttzero.excel.entity.e7.XMLWorksheetWriter; import org.ttzero.excel.entity.style.Border; import org.ttzero.excel.entity.style.Fill; import org.ttzero.excel.entity.style.Font; import org.ttzero.excel.entity.style.NumFmt; import org.ttzero.excel.entity.style.Styles; import org.ttzero.excel.manager.Const; import org.ttzero.excel.reader.Cell; import org.ttzero.excel.reader.CellType; import org.ttzero.excel.reader.Col; import org.ttzero.excel.reader.CrossDimension; import org.ttzero.excel.reader.Dimension; import org.ttzero.excel.reader.ExcelReader; import org.ttzero.excel.reader.FullSheet; import org.ttzero.excel.util.FileUtil; import org.ttzero.excel.util.SAXReaderUtil; import org.ttzero.excel.util.StringUtil; import org.ttzero.excel.validation.Validation;
import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.BiFunction;
import org.ttzero.excel.entity.TemplateCopySheet.HeaderStyleModifier;
/**
-
复制表头模板工作表,它支持从已有的Excel文件中复制表头及样式,然后通过Map映射表头名字和数据key,
-
如果key不在表头中存在则在末尾新建一个表头列,支持修改表头样式,最后传入导出数据输出到新表。
-
使用示例:
-
-
// 表头名称到数据key的映射
-
Map<String, String> headerMapping = new LinkedHashMap<>();
-
headerMapping.put("姓名", "name");
-
headerMapping.put("年龄", "age");
-
headerMapping.put("新增列", "newColumn"); // 不存在的列会自动添加
-
// 导出数据
-
List<Map<String, Object>> data = new ArrayList<>();
-
Map<String, Object> row = new HashMap<>();
-
row.put("name", "张三");
-
row.put("age", 25);
-
row.put("newColumn", "新数据");
-
data.add(row);
-
new Workbook()
-
.addSheet(new TemplateCopySheet(Paths.get("template.xlsx")) -
.setHeaderMapping(headerMapping) -
.setData(data)) -
.writeTo(Paths.get("output.xlsx")); -
@author guanquan.wang at 2024-12-19 / public class TemplateCopySheet extends ListMapSheet<Object> { /*
- 模板路径 / protected Path templatePath; /*
- 模板流 / protected InputStream templateStream; /*
- 读取模板用 / protected ExcelReader reader; /*
- 源工作表索引 / protected int originalSheetIndex; /*
- 源工作表名 / protected String originalSheetName; /*
- 表头名称到数据key的映射
- Key: 表头名称, Value: 数据key / protected Map<String, String> headerMapping; /*
- 样式映射,缓存源样式索引映射到目标样式索引 / protected Map<Integer, Integer> styleMap; /*
- 源表头信息
- Key: 表头名称, Value: 列索引 / protected Map<String, Integer> originalHeaders; /*
- 源表头样式
- Key: 列索引, Value: 样式索引 / protected Map<Integer, Integer> originalHeaderStyles; /*
- 源列宽 / protected Map<Integer, Double> originalColWidths; /*
- 表头行号 / protected int headerRowNum = 1; /*
- 是否已初始化 / protected boolean initialized; /*
- 以Excel格式输出 / protected boolean writeAsExcel; /*
- Delete the temp file if close buffer / protected boolean shouldDeleteTemp; /*
- 自定义表头样式修改器 */ protected HeaderStyleModifier headerStyleModifier;
/**
- 表头样式修改器接口
/
@FunctionalInterface
public interface HeaderStyleModifier {
/*
- 修改表头样式
- @param styles 样式表,可用于创建新的字体、填充、边框等
- @param headerName 表头名称
- @param style 原始样式
- @return 新样式 */ int apply(Styles styles, String headerName, int style); }
/**
- 实例化模板复制工作表,默认以第一个工作表做为模板
- @param templatePath 模板路径 */ public TemplateCopySheet(Path templatePath) { this(templatePath, 0); }
/**
- 实例化模板复制工作表,默认以第一个工作表做为模板
- @param name 指定工作表名称
- @param templatePath 模板路径 */ public TemplateCopySheet(String name, Path templatePath) { this(name, templatePath, 0); }
/**
- 实例化模板复制工作表并指定模板工作表索引
- @param templatePath 模板路径
- @param originalSheetIndex 指定源工作表索引(从0开始) */ public TemplateCopySheet(Path templatePath, int originalSheetIndex) { this(null, templatePath, originalSheetIndex); }
/**
- 实例化模板复制工作表并指定模板工作表索引
- @param name 指定工作表名称
- @param templatePath 模板路径
- @param originalSheetIndex 指定源工作表索引(从0开始) */ public TemplateCopySheet(String name, Path templatePath, int originalSheetIndex) { super(name); this.templatePath = templatePath; this.originalSheetIndex = originalSheetIndex; }
/**
- 实例化模板复制工作表并指定模板工作表名
- @param templatePath 模板路径
- @param originalSheetName 指定源工作表名 */ public TemplateCopySheet(Path templatePath, String originalSheetName) { this(null, templatePath, originalSheetName); }
/**
- 实例化模板复制工作表并指定模板工作表名
- @param name 指定工作表名称
- @param templatePath 模板路径
- @param originalSheetName 指定源工作表名 */ public TemplateCopySheet(String name, Path templatePath, String originalSheetName) { super(name); this.templatePath = templatePath; this.originalSheetName = originalSheetName; }
/**
- 实例化模板复制工作表,默认以第一个工作表做为模板
- @param templateStream 模板输入流 */ public TemplateCopySheet(InputStream templateStream) { this(templateStream, 0); }
/**
- 实例化模板复制工作表,默认以第一个工作表做为模板
- @param name 设置工作表名
- @param templateStream 模板输入流 */ public TemplateCopySheet(String name, InputStream templateStream) { this(name, templateStream, 0); }
/**
- 实例化模板复制工作表并指定模板工作表索引
- @param templateStream 模板输入流
- @param originalSheetIndex 指定源工作表索引 */ public TemplateCopySheet(InputStream templateStream, int originalSheetIndex) { this(null, templateStream, originalSheetIndex); }
/**
- 实例化模板复制工作表并指定模板工作表名
- @param templateStream 模板输入流
- @param originalSheetName 指定源工作表名 */ public TemplateCopySheet(InputStream templateStream, String originalSheetName) { this(null, templateStream, originalSheetName); }
/**
- 实例化模板复制工作表并指定模板工作表索引
- @param name 设置工作表名
- @param templateStream 模板输入流
- @param originalSheetIndex 指定源工作表索引 */ public TemplateCopySheet(String name, InputStream templateStream, int originalSheetIndex) { super(name); this.templateStream = templateStream; this.originalSheetIndex = originalSheetIndex; }
/**
- 实例化模板复制工作表并指定模板工作表名
- @param name 设置工作表名
- @param templateStream 模板输入流
- @param originalSheetName 指定源工作表名 */ public TemplateCopySheet(String name, InputStream templateStream, String originalSheetName) { super(name); this.templateStream = templateStream; this.originalSheetName = originalSheetName; }
/**
- 设置表头映射
- Key: 表头名称, Value: 数据Map中的key
- @param headerMapping 表头映射
- @return 当前工作表 */ public TemplateCopySheet setHeaderMapping(Map<String, String> headerMapping) { this.headerMapping = headerMapping; return this; }
/**
- 设置表头行号,默认为1
- @param headerRowNum 表头行号(从1开始)
- @return 当前工作表 */ public TemplateCopySheet setHeaderRowNum(int headerRowNum) { this.headerRowNum = headerRowNum; return this; }
/**
- 设置表头样式修改器,用于自定义修改表头样式
-
使用示例:
-
- .setHeaderStyleModifier((styles, headerName, style) -> {
-
if ("备注".equals(headerName)) { -
// 创建红色字体 -
Font redFont = new Font("微软雅黑", 12, Font.Style.BOLD, Color.RED); -
// 创建灰色填充 -
Fill greyFill = new Fill(PatternType.solid, new Color(217, 217, 217)); -
// 组合样式 -
return styles.addFont(redFont) | styles.addFill(greyFill) | Horizontals.CENTER; -
} -
return style; - })
- @param headerStyleModifier 样式修改器,参数1为样式表,参数2为表头名称,参数3为原始样式,返回新样式
- @return 当前工作表 */ public TemplateCopySheet setHeaderStyleModifier(HeaderStyleModifier headerStyleModifier) { this.headerStyleModifier = headerStyleModifier; return this; }
@Override public TemplateCopySheet setData(List<Map<String, Object>> data) { super.setData(data); return this; }
@Override public TemplateCopySheet setData(BiFunction<Integer, Map<String, Object>, List<Map<String, Object>>> dataSupplier) { super.setData(dataSupplier); return this; }
/**
- 获取表头列 */ @Override protected Column[] getHeaderColumns() { if (!headerReady) { try { initTemplate(); } catch (IOException e) { throw new ExcelWriteException("Failed to init template", e); } } return super.getHeaderColumns(); }
/**
-
初始化模板 */ protected void initTemplate() throws IOException { if (initialized) return; initialized = true;
// 实例化ExcelReader if (templateStream != null) { templatePath = Files.createTempFile("eec-", null); if (templatePath == null) throw new IOException("Create temp directory error. Please check your permission"); java.io.OutputStream os = Files.newOutputStream(templatePath); FileUtil.cp(templateStream, os); FileUtil.close(os); FileUtil.close(templateStream); templateStream = null; shouldDeleteTemp = true; } if (templatePath != null) reader = ExcelReader.read(templatePath);
// 查找源工作表 org.ttzero.excel.reader.Sheet[] sheets = reader.all(); if (StringUtil.isNotBlank(originalSheetName)) { int index = 0; for (; index < sheets.length && !originalSheetName.equals(sheets[index].getName()); index++) ; if (index >= sheets.length) throw new IOException("The original worksheet [" + originalSheetName + "] does not exist in template file."); originalSheetIndex = index; } else if (originalSheetIndex < 0 || originalSheetIndex >= sheets.length) { throw new IOException("The original worksheet index [" + originalSheetIndex + "] is out of range in template file[0-" + sheets.length + "]."); }
// 加载模板工作表 FullSheet sheet = reader.sheet(originalSheetIndex).asFullSheet(); writeAsExcel = sheetWriter != null && XMLWorksheetWriter.class.isAssignableFrom(sheetWriter.getClass());
// 样式缓存 styleMap = writeAsExcel ? new HashMap<>() : Collections.emptyMap();
// 解析表头和样式 parseHeaderAndStyles(sheet);
// 构建列信息 buildColumns(); }
/**
-
解析表头和样式 */ protected void parseHeaderAndStyles(FullSheet sheet) { originalHeaders = new LinkedHashMap<>(); originalHeaderStyles = new HashMap<>(); originalColWidths = new HashMap<>();
// 获取列宽 List<Col> cols = sheet.getCols(); if (cols != null && !cols.isEmpty()) { for (Col col : cols) { for (int c = col.min; c <= col.max; c++) { originalColWidths.put(c - 1, col.width); } } }
Styles styles0 = reader.getStyles();
// 遍历表头行 for (Iterator<org.ttzero.excel.reader.Row> iter = sheet.iterator(); iter.hasNext(); ) { org.ttzero.excel.reader.Row row = iter.next(); if (row.getRowNum() == headerRowNum) { for (int i = row.getFirstColumnIndex(), end = row.getLastColumnIndex(); i < end; i++) { Cell cell = row.getCell(i); if (row.getCellType(cell) == CellType.STRING) { String headerName = row.getString(cell); if (StringUtil.isNotBlank(headerName)) { originalHeaders.put(headerName.trim(), i); originalHeaderStyles.put(i, cell.xf);
// 复制样式 if (writeAsExcel && !styleMap.containsKey(cell.xf)) { styleMap.put(cell.xf, copyStyle(styles0, workbook.getStyles(), cell.xf)); } } } } break; }}
// 复制数据验证(包括级联列表) copyValidations(sheet); }
/**
-
复制数据验证 */ protected void copyValidations(FullSheet sheet) { if (!writeAsExcel) return;
List<Validation> validations = sheet.getValidations(); if (validations == null || validations.isEmpty()) return;
// 构建原列索引到新列索引的映射 Map<Integer, Integer> colIndexMapping = new HashMap<>(); if (headerMapping != null && !headerMapping.isEmpty()) { int newColIndex = 0; for (String headerName : headerMapping.keySet()) { Integer originalColIndex = originalHeaders.get(headerName); if (originalColIndex != null) { colIndexMapping.put(originalColIndex, newColIndex); } newColIndex++; } } else { // 无映射时,列索引保持不变 for (Map.Entry<String, Integer> entry : originalHeaders.entrySet()) { colIndexMapping.put(entry.getValue(), entry.getValue()); } }
// 处理验证 List<Validation> newValidations = new ArrayList<>(); Map<String, String> refererSheetMap = new HashMap<>();
for (Validation val : validations) { // 调整验证的维度范围 Validation newVal = adjustValidationDimension(val, colIndexMapping); if (newVal != null) { // 处理跨工作表引用 if (newVal.referer != null && newVal.referer.isCrossSheet()) { refererSheetMap.computeIfAbsent(newVal.referer.sheetName, k -> "__St" + getId() + "_val" + (System.currentTimeMillis() % 1000)); newVal.referer.sheetName = refererSheetMap.get(newVal.referer.sheetName); } newValidations.add(newVal); } }
// 添加引用的工作表 if (!refererSheetMap.isEmpty()) { for (Map.Entry<String, String> entry : refererSheetMap.entrySet()) { workbook.addSheet(new TemplateSheet(templatePath, entry.getKey()).setName(entry.getValue()).hidden()); } // 读取defined_name try { Document document = SAXReaderUtil.createDefault().read(reader.getEntryStream("xl/workbook.xml")); Element definedNameEl = document.getRootElement().element("definedNames"); if (definedNameEl != null) { List<Element> sub = definedNameEl.elements(); Map<String, String> definedNames = new HashMap<>(sub.size()); for (Element e : sub) { String txt = e.getTextTrim(); int vt = Validation.testValueType(txt); CrossDimension cd; if (vt == 3 && (cd = CrossDimension.of(txt)).isCrossSheet() && refererSheetMap.containsKey(cd.sheetName)) { cd.sheetName = refererSheetMap.get(cd.sheetName); definedNames.put(e.attributeValue("name"), cd.toString()); } } if (!definedNames.isEmpty()) putExtProp(Const.ExtendPropertyKey.DEFINED_NAME, definedNames); } } catch (Exception ex) { // ignore } }
if (!newValidations.isEmpty()) { putExtProp(Const.ExtendPropertyKey.DATA_VALIDATION, newValidations); } }
/**
-
调整验证的维度范围 */ protected Validation adjustValidationDimension(Validation val, Map<Integer, Integer> colIndexMapping) { if (val.sqrefList == null || val.sqrefList.isEmpty()) return val;
List<Dimension> newSqrefList = new ArrayList<>(); for (Dimension dim : val.sqrefList) { // 获取新的列索引 Integer newFirstCol = colIndexMapping.get(dim.firstColumn - 1); Integer newLastCol = colIndexMapping.get(dim.lastColumn - 1);
if (newFirstCol != null) { // 如果找到映射,使用新的列索引 int fc = newFirstCol + 1; int lc = newLastCol != null ? newLastCol + 1 : fc; // 行号从表头行之后开始 Dimension newDim = new Dimension(headerRowNum + 1, (short) fc, dim.lastRow, (short) lc); newSqrefList.add(newDim); } else { // 保持原维度(可能是全列验证) newSqrefList.add(dim); }}
if (!newSqrefList.isEmpty()) { val.sqrefList = newSqrefList; return val; } return null; }
/**
- 复制样式 */ protected int copyStyle(Styles srcStyles, Styles distStyle, int copyXf) { int style = srcStyles.getStyleByIndex(copyXf), xf = 0; // 字体 Font font = srcStyles.getFont(style); if (font != null) xf |= distStyle.addFont(font.clone()); // 填充 Fill fill = srcStyles.getFill(style); if (fill != null) xf |= distStyle.addFill(fill.clone()); // 边框 Border border = srcStyles.getBorder(style); if (border != null) xf |= distStyle.addBorder(border.clone()); // 格式化 NumFmt numFmt = srcStyles.getNumFmt(style); if (numFmt != null) xf |= distStyle.addNumFmt(numFmt.clone()); // 水平对齐 xf |= srcStyles.getHorizontal(style); // 垂直对齐 xf |= srcStyles.getVertical(style); // 自动折行 xf |= srcStyles.getWrapText(style); return distStyle.of(xf); }
/**
-
构建列信息 */ protected void buildColumns() { List<Column> columnList = new ArrayList<>();
if (headerMapping == null || headerMapping.isEmpty()) { // 没有映射,使用原表头作为key for (Map.Entry<String, Integer> entry : originalHeaders.entrySet()) { String headerName = entry.getKey(); int colIndex = entry.getValue(); Column col = createColumnFromTemplate(headerName, headerName, colIndex); columnList.add(col); } } else { // 根据映射构建列 for (Map.Entry<String, String> entry : headerMapping.entrySet()) { String headerName = entry.getKey(); String dataKey = entry.getValue();
Integer colIndex = originalHeaders.get(headerName); Column col; if (colIndex != null) { // 表头存在,使用原样式 col = createColumnFromTemplate(headerName, dataKey, colIndex); } else { // 表头不存在,在末尾新建 col = createNewColumn(headerName, dataKey); } columnList.add(col); }}
columns = columnList.toArray(new Column[0]); }
/**
-
从模板创建列 */ protected Column createColumnFromTemplate(String headerName, String dataKey, int colIndex) { Column col = new Column(headerName, dataKey);
// 设置列宽 Double width = originalColWidths.get(colIndex); if (width != null && width > 0) { col.setWidth(width); }
// 设置表头样式 Integer originalStyleXf = originalHeaderStyles.get(colIndex); if (originalStyleXf != null && writeAsExcel) { Integer newStyleIndex = styleMap.get(originalStyleXf); if (newStyleIndex != null) { int style = workbook.getStyles().getStyleByIndex(newStyleIndex); // 应用样式修改器 if (headerStyleModifier != null) { style = headerStyleModifier.apply(workbook.getStyles(), headerName, style); } col.setHeaderStyle(style); } }
return col; }
/**
-
创建新列 */ protected Column createNewColumn(String headerName, String dataKey) { Column col = new Column(headerName, dataKey);
// 使用默认表头样式或第一个表头的样式 if (!originalHeaderStyles.isEmpty() && writeAsExcel) { Integer firstStyleXf = originalHeaderStyles.values().iterator().next(); Integer newStyleIndex = styleMap.get(firstStyleXf); if (newStyleIndex != null) { int style = workbook.getStyles().getStyleByIndex(newStyleIndex); // 应用样式修改器 if (headerStyleModifier != null) { style = headerStyleModifier.apply(workbook.getStyles(), headerName, style); } col.setHeaderStyle(style); } }
return col; }
@Override public void close() throws IOException { super.close(); if (reader != null) { reader.close(); reader = null; } if (templateStream != null) { templateStream.close(); templateStream = null; } if (shouldDeleteTemp && templatePath != null) { FileUtil.rm(templatePath); } } }
没太理解需求,你是想数据上传做内容校验,NG的数据需要原样导出,是这样的需求吗?大概需要添加一列在最后并附带异常信息?
很多都是原代码的 copy ,所以想着 能否在原有 模板导出做增强处理
没太理解需求,你是想数据上传做内容校验,NG的数据需要原样导出,是这样的需求吗?大概需要添加一列在最后并附带异常信息?
很多都是原代码的 copy ,所以想着 能否在原有 模板导出做增强处理
增加addColumn(int startRow, int colIndex, Column column)方法是不是可以,在指定位置添加列,可以通过Column指定样式,未指定时使用前一个单元格的样式
没太理解需求,你是想数据上传做内容校验,NG的数据需要原样导出,是这样的需求吗?大概需要添加一列在最后并附带异常信息?
很多都是原代码的 copy ,所以想着 能否在原有 模板导出做增强处理
增加
addColumn(int startRow, int colIndex, Column column)方法是不是可以,在指定位置添加列,可以通过Column指定样式,未指定时使用前一个单元格的样式
嗯,这个是复制了全部数据,可以操作, 但需求是, 正确的数据落库, 错误的数据返回, 此时相当于原表数据是丢弃的, 给了一个新的 数据集合进去
没太理解需求,你是想数据上传做内容校验,NG的数据需要原样导出,是这样的需求吗?大概需要添加一列在最后并附带异常信息?
很多都是原代码的 copy ,所以想着 能否在原有 模板导出做增强处理
增加
addColumn(int startRow, int colIndex, Column column)方法是不是可以,在指定位置添加列,可以通过Column指定样式,未指定时使用前一个单元格的样式
测试用例
@Test public void testTemplateCopySheet() throws IOException {
Path templatePath = Paths.get("./parcel_outbound_import_template_1765422494020.xlsx");
// 如果模板文件不存在则跳过测试
if (!Files.exists(templatePath)) {
System.out.println("Template file not found, skip test: " + templatePath);
return;
}
Map<String, String> headerMapping = new LinkedHashMap<>();
final String fileName = "template copy sheet test.xlsx";
try (ExcelReader read = ExcelReader.read(templatePath)) {
List<Map<String, Object>> maps = read.sheet(0).dataRows().map(Row::toMap).collect(Collectors.toList());
Map<String, Object> map = maps.get(0);
map.forEach((k,v) -> headerMapping.put(k, k));
}
// 新增一个原表不存在的列
headerMapping.put("备注", "备注");
// 准备测试数据
List<Map<String, Object>> data = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Map<String, Object> row = new HashMap<>();
row.put("参考单号", "REF" + (1000 + i));
row.put("平台单号", "PLT" + (2000 + i));
row.put("销售平台", "淘宝");
row.put("仓库", "深圳仓");
row.put("物流渠道", "标准快递");
row.put("物流承运商", "顺丰速运");
row.put("物流追踪号", "SF" + System.currentTimeMillis() + i);
row.put("国家", "中国");
row.put("州省", "广东省");
row.put("城市", "深圳市");
row.put("详细地址", "南山区科技园" + i + "号");
row.put("邮编", "518000");
row.put("收件人", "张三" + i);
row.put("电话", "1380000" + String.format("%04d", i));
row.put("SKU1", "SKU-A" + (3000 + i));
row.put("出库数量1", i + 1);
row.put("SKU2", "SKU-B" + (4000 + i));
row.put("出库数量2", i + 2);
row.put("备注", "备注信息" + i);
data.add(row);
}
new Workbook()
.addSheet(new TemplateCopySheet(templatePath)
.setHeaderMapping(headerMapping)
.setData(data)
// 修改表头样式
.setHeaderStyleModifier((styles, headerName, style) -> {
// 新增列使用红色字体 + 灰色背景
if ("备注".equals(headerName)) {
Font redFont = new Font("微软雅黑", 12, Font.Style.BOLD, Color.RED);
Fill greyFill = new Fill(PatternType.solid, new Color(217, 217, 217));
return styles.addFont(redFont) | styles.addFill(greyFill) | Horizontals.CENTER;
}
return style;
}))
.writeTo(defaultTestPath.resolve(fileName));
// 验证导出结果
try (ExcelReader reader = ExcelReader.read(defaultTestPath.resolve(fileName))) {
FullSheet sheet = reader.sheet(0).asFullSheet();
Iterator<Row> iter = sheet.header(1).iterator();
// 验证数据行数
int count = 0;
while (iter.hasNext()) {
Row row = iter.next();
if (!row.isBlank()) {
count++;
}
}
assertEquals(data.size(), count);
}
}
厉害👍,之前有一个ISSUE在导入异常时添加TIPS的需求,一边检查一边写异常数据:https://github.com/wangguanquan/eec/issues/383
可以考虑这种场景还是比较多的,年底开发任务比较重,短期内不会实现