diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/CellUtil.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/CellUtil.java
index aebb6a086..feac78ff6 100644
--- a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/CellUtil.java
+++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/CellUtil.java
@@ -39,6 +39,7 @@ import org.dromara.hutool.poi.excel.style.StyleSet;
public class CellUtil {
// region ----- getCellValue
+
/**
* 获取单元格值
*
@@ -103,6 +104,7 @@ public class CellUtil {
// endregion
// region ----- setCellValue
+
/**
* 设置单元格值
* 根据传入的styleSet自动匹配样式
@@ -169,8 +171,8 @@ public class CellUtil {
* 根据传入的styleSet自动匹配样式
* 当为头部样式时默认赋值头部样式,但是头部中如果有数字、日期等类型,将按照数字、日期样式设置
*
- * @param cell 单元格
- * @param value 值或{@link CellSetter}
+ * @param cell 单元格
+ * @param value 值或{@link CellSetter}
* @since 5.6.4
*/
public static void setCellValue(final Cell cell, final Object value) {
@@ -191,10 +193,11 @@ public class CellUtil {
// endregion
// region ----- getCell
+
/**
* 获取指定坐标单元格,如果isCreateIfNotExist为false,则在单元格不存在时返回{@code null}
*
- * @param sheet {@link Sheet}
+ * @param sheet {@link Sheet}
* @param x X坐标,从0计数,即列号
* @param y Y坐标,从0计数,即行号
* @param isCreateIfNotExist 单元格不存在时是否创建
@@ -249,6 +252,7 @@ public class CellUtil {
// endregion
// region ----- merging 合并单元格
+
/**
* 判断指定的单元格是否是合并单元格
*
@@ -287,7 +291,7 @@ public class CellUtil {
for (int i = 0; i < sheetMergeCount; i++) {
ca = sheet.getMergedRegion(i);
if (y >= ca.getFirstRow() && y <= ca.getLastRow()
- && x >= ca.getFirstColumn() && x <= ca.getLastColumn()) {
+ && x >= ca.getFirstColumn() && x <= ca.getLastColumn()) {
return true;
}
}
@@ -297,7 +301,7 @@ public class CellUtil {
/**
* 合并单元格,可以根据设置的值来合并行和列
*
- * @param sheet 表对象
+ * @param sheet 表对象
* @param cellRangeAddress 合并单元格范围,定义了起始行列和结束行列
* @return 合并后的单元格号
*/
@@ -308,9 +312,9 @@ public class CellUtil {
/**
* 合并单元格,可以根据设置的值来合并行和列
*
- * @param sheet 表对象
+ * @param sheet 表对象
* @param cellRangeAddress 合并单元格范围,定义了起始行列和结束行列
- * @param cellStyle 单元格样式,只提取边框样式,null表示无样式
+ * @param cellStyle 单元格样式,只提取边框样式,null表示无样式
* @return 合并后的单元格号
*/
public static int mergingCells(final Sheet sheet, final CellRangeAddress cellRangeAddress, final CellStyle cellStyle) {
@@ -360,8 +364,8 @@ public class CellUtil {
return null;
}
return ObjUtil.defaultIfNull(
- getCellIfMergedRegion(cell.getSheet(), cell.getColumnIndex(), cell.getRowIndex()),
- cell);
+ getCellIfMergedRegion(cell.getSheet(), cell.getColumnIndex(), cell.getRowIndex()),
+ cell);
}
/**
@@ -376,8 +380,8 @@ public class CellUtil {
*/
public static Cell getMergedRegionCell(final Sheet sheet, final int x, final int y) {
return ObjUtil.defaultIfNull(
- getCellIfMergedRegion(sheet, x, y),
- () -> SheetUtil.getCell(sheet, y, x));
+ getCellIfMergedRegion(sheet, x, y),
+ () -> SheetUtil.getCell(sheet, y, x));
}
// endregion
@@ -420,13 +424,25 @@ public class CellUtil {
// 修正在XSSFCell中未设置地址导致错位问题
comment.setAddress(cell.getAddress());
comment.setString(factory.createRichTextString(commentText));
- if(null != commentAuthor){
+ if (null != commentAuthor) {
comment.setAuthor(commentAuthor);
}
cell.setCellComment(comment);
}
+ /**
+ * 移除指定单元格
+ *
+ * @param cell 单元格
+ */
+ public static void remove(final Cell cell) {
+ if (null != cell) {
+ cell.getRow().removeCell(cell);
+ }
+ }
+
// -------------------------------------------------------------------------------------------------------------- Private method start
+
/**
* 获取合并单元格,非合并单元格返回{@code null}
* 传入的x,y坐标(列行数)可以是合并单元格范围内的任意一个单元格
diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/VirtualCell.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/VirtualCell.java
new file mode 100644
index 000000000..0d5c570c1
--- /dev/null
+++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/VirtualCell.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2024 Hutool Team and hutool.cn
+ *
+ * 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.dromara.hutool.poi.excel.cell;
+
+import org.apache.poi.ss.SpreadsheetVersion;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.dromara.hutool.poi.excel.cell.values.FormulaCellValue;
+
+import java.time.LocalDateTime;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 虚拟单元格,表示一个单元格的位置、值和样式,但是并非实际创建的单元格
+ * 注意:虚拟单元格设置值和样式均不会在实际工作簿中生效
+ *
+ * @author Looly
+ * @since 6.0.0
+ */
+public class VirtualCell extends CellBase {
+
+ private final Row row;
+ private final int columnIndex;
+ private final int rowIndex;
+
+ private CellType cellType;
+ private Object value;
+ private CellStyle style;
+
+ /**
+ * 构造
+ *
+ * @param cell 参照单元格
+ * @param x 新的列号,从0开始
+ * @param y 新的行号,从0开始
+ */
+ public VirtualCell(final Cell cell, final int x, final int y) {
+ this(cell.getRow(), x, y);
+ this.cellType = cell.getCellType();
+ this.value = CellUtil.getCellValue(cell);
+ this.style = cell.getCellStyle();
+ this.comment = cell.getCellComment();
+ }
+
+ private Comment comment;
+
+ /**
+ * 构造
+ *
+ * @param row 行
+ * @param y 行号,从0开始
+ * @param x 列号,从0开始
+ */
+ public VirtualCell(final Row row, final int x, final int y) {
+ this.row = row;
+ this.rowIndex = y;
+ this.columnIndex = x;
+ }
+
+ @Override
+ protected void setCellTypeImpl(final CellType cellType) {
+ this.cellType = cellType;
+ }
+
+ @Override
+ protected void setCellFormulaImpl(final String formula) {
+ this.value = new FormulaCellValue(formula);
+ }
+
+ @Override
+ protected void removeFormulaImpl() {
+ if (this.value instanceof FormulaCellValue) {
+ this.value = null;
+ }
+ }
+
+ @Override
+ protected void setCellValueImpl(final double value) {
+ this.value = value;
+ }
+
+ @Override
+ protected void setCellValueImpl(final Date value) {
+ this.value = value;
+ }
+
+ @Override
+ protected void setCellValueImpl(final LocalDateTime value) {
+ this.value = value;
+ }
+
+ @Override
+ protected void setCellValueImpl(final Calendar value) {
+ this.value = value;
+ }
+
+ @Override
+ protected void setCellValueImpl(final String value) {
+ this.value = value;
+ }
+
+ @Override
+ protected void setCellValueImpl(final RichTextString value) {
+ this.value = value;
+ }
+
+ @Override
+ protected SpreadsheetVersion getSpreadsheetVersion() {
+ return SpreadsheetVersion.EXCEL2007;
+ }
+
+ @Override
+ public int getColumnIndex() {
+ return this.columnIndex;
+ }
+
+ @Override
+ public int getRowIndex() {
+ return this.rowIndex;
+ }
+
+ @Override
+ public Sheet getSheet() {
+ return this.row.getSheet();
+ }
+
+ @Override
+ public Row getRow() {
+ return this.row;
+ }
+
+ @Override
+ public CellType getCellType() {
+ return this.cellType;
+ }
+
+ @Override
+ public CellType getCachedFormulaResultType() {
+ if (this.value instanceof FormulaCellValue) {
+ return ((FormulaCellValue) this.value).getResultType();
+ }
+ return null;
+ }
+
+ @Override
+ public String getCellFormula() {
+ if (this.value instanceof FormulaCellValue) {
+ return ((FormulaCellValue) this.value).getValue();
+ }
+ return null;
+ }
+
+ @Override
+ public double getNumericCellValue() {
+ return (double) this.value;
+ }
+
+ @Override
+ public Date getDateCellValue() {
+ return (Date) this.value;
+ }
+
+ @Override
+ public LocalDateTime getLocalDateTimeCellValue() {
+ return (LocalDateTime) this.value;
+ }
+
+ @Override
+ public RichTextString getRichStringCellValue() {
+ return (RichTextString) this.value;
+ }
+
+ @Override
+ public String getStringCellValue() {
+ return (String) this.value;
+ }
+
+ @Override
+ public void setCellValue(final boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public void setCellErrorValue(final byte value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean getBooleanCellValue() {
+ return (boolean) this.value;
+ }
+
+ @Override
+ public byte getErrorCellValue() {
+ return (byte) this.value;
+ }
+
+ @Override
+ public void setCellStyle(final CellStyle style) {
+ this.style = style;
+ }
+
+ @Override
+ public CellStyle getCellStyle() {
+ return this.style;
+ }
+
+ @Override
+ public void setAsActiveCell() {
+ throw new UnsupportedOperationException("Virtual cell cannot be set as active cell");
+ }
+
+ @Override
+ public void setCellComment(final Comment comment) {
+ this.comment = comment;
+ }
+
+ @Override
+ public Comment getCellComment() {
+ return this.comment;
+ }
+
+ @Override
+ public void removeCellComment() {
+ this.comment = null;
+ }
+
+ @Override
+ public Hyperlink getHyperlink() {
+ return (Hyperlink) this.value;
+ }
+
+ @Override
+ public void setHyperlink(final Hyperlink link) {
+ this.value = link;
+ }
+
+ @Override
+ public void removeHyperlink() {
+ if (this.value instanceof Hyperlink) {
+ this.value = null;
+ }
+ }
+
+ @Override
+ public CellRangeAddress getArrayFormulaRange() {
+ return null;
+ }
+
+ @Override
+ public boolean isPartOfArrayFormulaGroup() {
+ return false;
+ }
+}
diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/values/FormulaCellValue.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/values/FormulaCellValue.java
index 64d1d6e12..66d1a582f 100644
--- a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/values/FormulaCellValue.java
+++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/cell/values/FormulaCellValue.java
@@ -16,6 +16,7 @@
package org.dromara.hutool.poi.excel.cell.values;
+import org.apache.poi.ss.usermodel.CellType;
import org.dromara.hutool.poi.excel.cell.setters.CellSetter;
import org.apache.poi.ss.usermodel.Cell;
@@ -40,6 +41,7 @@ public class FormulaCellValue implements CellValue, CellSetter {
* 结果,使用ExcelWriter时可以不用
*/
private final Object result;
+ private final CellType resultType;
/**
* 构造
@@ -57,8 +59,20 @@ public class FormulaCellValue implements CellValue, CellSetter {
* @param result 结果
*/
public FormulaCellValue(final String formula, final Object result) {
+ this(formula, result, null);
+ }
+
+ /**
+ * 构造
+ *
+ * @param formula 公式
+ * @param result 结果
+ * @param resultType 结果类型
+ */
+ public FormulaCellValue(final String formula, final Object result, final CellType resultType) {
this.formula = formula;
this.result = result;
+ this.resultType = resultType;
}
@Override
@@ -73,12 +87,22 @@ public class FormulaCellValue implements CellValue, CellSetter {
/**
* 获取结果
+ *
* @return 结果
*/
public Object getResult() {
return this.result;
}
+ /**
+ * 获取结果类型
+ *
+ * @return 结果类型,{@code null}表示未明确
+ */
+ public CellType getResultType() {
+ return this.resultType;
+ }
+
@Override
public String toString() {
return getResult().toString();
diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/ExcelWriter.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/ExcelWriter.java
index c37c342e2..e86b4d1cc 100644
--- a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/ExcelWriter.java
+++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/ExcelWriter.java
@@ -91,7 +91,7 @@ public class ExcelWriter extends ExcelBase {
/**
* 构造
* 此构造不传入写出的Excel文件路径,只能调用{@link #flush(OutputStream)}方法写出到流
- * 若写出到文件,需要调用{@link #flush(File)} 写出到文件
+ * 若写出到文件,需要调用{@link #flush(File, boolean)} 写出到文件
*
* @param isXlsx 是否为xlsx格式
* @since 3.2.1
@@ -112,7 +112,7 @@ public class ExcelWriter extends ExcelBase {
/**
* 构造
* 此构造不传入写出的Excel文件路径,只能调用{@link #flush(OutputStream)}方法写出到流
- * 若写出到文件,需要调用{@link #flush(File)} 写出到文件
+ * 若写出到文件,需要调用{@link #flush(File, boolean)} 写出到文件
*
* @param isXlsx 是否为xlsx格式
* @param sheetName sheet名,第一个sheet名并写出到此sheet,例如sheet1
@@ -152,7 +152,7 @@ public class ExcelWriter extends ExcelBase {
if (!FileUtil.exists(targetFile)) {
this.targetFile = targetFile;
- } else{
+ } else {
// 如果是已经存在的文件,则作为模板加载,此时不能写出到模板文件
// 初始化模板
this.templateContext = new TemplateContext(this.sheet);
@@ -549,6 +549,7 @@ public class ExcelWriter extends ExcelBase {
}
// region ----- merge
+
/**
* 合并当前行的单元格
*
@@ -640,6 +641,7 @@ public class ExcelWriter extends ExcelBase {
// endregion
// region ----- write
+
/**
* 写出数据,本方法只是将数据写入Workbook中的Sheet,并不写出到文件
* 写出的起始行为当前行号,可使用{@link #getCurrentRow()}方法调用,根据写出的的行数,当前行号自动增加
@@ -1010,21 +1012,16 @@ public class ExcelWriter extends ExcelBase {
// region ----- fill
- public ExcelWriter fillRow(final Map, ?> rowMap){
- rowMap.forEach((key, value)->{
-
- });
+ /**
+ * 填充模板行
+ *
+ * @param rowMap 行数据
+ * @return this
+ */
+ public ExcelWriter fillRow(final Map, ?> rowMap) {
+ rowMap.forEach((key, value) -> this.templateContext.fillAndPointToNext(StrUtil.toStringOrNull(key), rowMap));
return this;
}
-
- public ExcelWriter fillCell(final String name, final Object value){
- final Cell cell = this.templateContext.getCell(name);
- if(null != cell){
- CellUtil.setCellValue(cell, value, this.config.getCellEditor());
- }
- return this;
- }
-
// endregion
// region ----- writeCol
@@ -1282,21 +1279,39 @@ public class ExcelWriter extends ExcelBase {
* @throws IORuntimeException IO异常
*/
public ExcelWriter flush() throws IORuntimeException {
- return flush(this.targetFile);
+ return flush(false);
+ }
+
+ /**
+ * 将Excel Workbook刷出到预定义的文件
+ * 如果用户未自定义输出的文件,将抛出{@link NullPointerException}
+ * 预定义文件可以通过{@link #setTargetFile(File)} 方法预定义,或者通过构造定义
+ *
+ * @param override 是否覆盖已有文件
+ * @return this
+ * @throws IORuntimeException IO异常
+ */
+ public ExcelWriter flush(final boolean override) throws IORuntimeException {
+ Assert.notNull(this.targetFile, "[targetFile] is null, and you must call setTargetFile(File) first.");
+ return flush(this.targetFile, override);
}
/**
* 将Excel Workbook刷出到文件
* 如果用户未自定义输出的文件,将抛出{@link NullPointerException}
*
- * @param destFile 写出到的文件
+ * @param targetFile 写出到的文件
+ * @param override 是否覆盖已有文件
* @return this
* @throws IORuntimeException IO异常
* @since 4.0.6
*/
- public ExcelWriter flush(final File destFile) throws IORuntimeException {
- Assert.notNull(destFile, "[destFile] is null, and you must call setDestFile(File) first or call flush(OutputStream).");
- return flush(FileUtil.getOutputStream(destFile), true);
+ public ExcelWriter flush(final File targetFile, final boolean override) throws IORuntimeException {
+ Assert.notNull(targetFile, "targetFile is null!");
+ if (FileUtil.exists(targetFile) && !override) {
+ throw new IORuntimeException("File to write exist: " + targetFile);
+ }
+ return flush(FileUtil.getOutputStream(targetFile), true);
}
/**
@@ -1321,6 +1336,7 @@ public class ExcelWriter extends ExcelBase {
*/
public ExcelWriter flush(final OutputStream out, final boolean isCloseOut) throws IORuntimeException {
Assert.isFalse(this.isClosed, "ExcelWriter has been closed!");
+
try {
this.workbook.write(out);
out.flush();
diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/TemplateContext.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/TemplateContext.java
index 066d022cf..676d78f98 100644
--- a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/TemplateContext.java
+++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/writer/TemplateContext.java
@@ -20,11 +20,14 @@ import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Sheet;
import org.dromara.hutool.core.collection.CollUtil;
+import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.regex.ReUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.poi.excel.SheetUtil;
+import org.dromara.hutool.poi.excel.cell.CellUtil;
+import org.dromara.hutool.poi.excel.cell.VirtualCell;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@@ -50,7 +53,7 @@ public class TemplateContext {
private static final Pattern ESCAPE_VAR_PATTERN = Pattern.compile("\\\\\\{([.$_a-zA-Z]+\\d*[.$_a-zA-Z]*)\\\\}");
// 存储变量对应单元格的映射
- private final Map varMap = new HashMap<>();
+ private final Map varMap = new LinkedHashMap<>();
/**
* 构造
@@ -62,7 +65,7 @@ public class TemplateContext {
}
/**
- * 获取变量对应的单元格,列表变量以.开头
+ * 获取变量对应的当前单元格,列表变量以开头
*
* @param varName 变量名
* @return 单元格
@@ -71,6 +74,45 @@ public class TemplateContext {
return varMap.get(varName);
}
+ /**
+ * 填充变量名name指向的单元格,并将变量指向下一列
+ *
+ * @param name 变量名
+ * @param rowData 一行数据的键值对
+ * @since 6.0.0
+ */
+ public void fillAndPointToNext(final String name, final Map, ?> rowData) {
+ Cell cell = varMap.get(name);
+ if (null == cell) {
+ // 没有对应变量占位
+ return;
+ }
+
+ final String templateStr = cell.getStringCellValue();
+
+ // 指向下一列的单元格
+ final Cell next = new VirtualCell(cell, cell.getColumnIndex(), cell.getRowIndex() + 1);
+ next.setCellValue(templateStr);
+ varMap.put(name, next);
+
+ if(cell instanceof VirtualCell){
+ // 虚拟单元格,转换为实际单元格
+ final Cell newCell = CellUtil.getCell(cell.getSheet(), cell.getColumnIndex(), cell.getRowIndex(), true);
+ Assert.notNull(newCell, "Can not get or create cell at {},{}", cell.getColumnIndex(), cell.getRowIndex());
+ newCell.setCellStyle(cell.getCellStyle());
+ cell = newCell;
+ }
+
+ // 模板替换
+ if(StrUtil.equals(name, StrUtil.unWrap(templateStr, "{", "}"))){
+ // 一个单元格只有一个变量
+ CellUtil.setCellValue(cell, rowData.get(name));
+ } else {
+ // 模板中存在多个变量或模板填充
+ CellUtil.setCellValue(cell, StrUtil.format(templateStr, rowData));
+ }
+ }
+
/**
* 初始化,提取变量及位置,并将转义的变量回填
*
diff --git a/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/ExcelWriteTest.java b/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/ExcelWriteTest.java
index 61d0e9f7f..3667c6ade 100644
--- a/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/ExcelWriteTest.java
+++ b/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/ExcelWriteTest.java
@@ -143,7 +143,7 @@ public class ExcelWriteTest {
writer.writeRow(row2);
// 生成文件或导出Excel
- writer.flush(FileUtil.file("d:/test/writeWithSheetTest.xlsx"));
+ writer.flush(FileUtil.file("d:/test/writeWithSheetTest.xlsx"), true);
writer.close();
}
@@ -868,7 +868,7 @@ public class ExcelWriteTest {
writer.writeImg(file, 0, 0, 5, 10);
- writer.flush(new File("C:\\Users\\zsz\\Desktop\\2.xlsx"));
+ writer.flush(new File("C:\\Users\\zsz\\Desktop\\2.xlsx"), true);
writer.close();
}
diff --git a/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateContextTest.java b/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateContextTest.java
index def486b64..95c45e216 100644
--- a/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateContextTest.java
+++ b/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateContextTest.java
@@ -22,6 +22,6 @@ public class TemplateContextTest {
final ExcelWriter writer = ExcelUtil.getWriter("template.xlsx");
final TemplateContext templateContext = new TemplateContext(writer.getSheet());
Assertions.assertNotNull(templateContext.getCell("date"));
- Assertions.assertNotNull(templateContext.getCell(".month"));
+ Assertions.assertNotNull(templateContext.getCell("month"));
}
}
diff --git a/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateWriterTest.java b/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateWriterTest.java
new file mode 100644
index 000000000..fe653e2fc
--- /dev/null
+++ b/hutool-poi/src/test/java/org/dromara/hutool/poi/excel/writer/TemplateWriterTest.java
@@ -0,0 +1,34 @@
+package org.dromara.hutool.poi.excel.writer;
+
+import org.dromara.hutool.core.io.file.FileUtil;
+import org.dromara.hutool.core.map.MapUtil;
+import org.dromara.hutool.poi.excel.ExcelUtil;
+import org.junit.jupiter.api.Test;
+
+public class TemplateWriterTest {
+ @Test
+ void writeRowTest() {
+ final ExcelWriter writer = ExcelUtil.getWriter("d:/test/template.xlsx");
+
+ // 单个替换的变量
+ writer.fillRow(MapUtil
+ .builder("date", (Object)"2024-01-01")
+ .build());
+
+ // 列表替换
+ for (int i = 0; i < 10; i++) {
+ writer.fillRow(MapUtil
+ .builder("user.name", (Object)"张三")
+ .put("user.age", 18)
+ .put("year", 2024)
+ .put("month", 8)
+ .put("day", 24)
+ .put("day", 24)
+ .put("user.area123", "某某市")
+ .put("invalid", "不替换")
+ .build());
+ }
+
+ writer.flush(FileUtil.file("d:/test/templateResult.xlsx"), true);
+ }
+}
diff --git a/hutool-poi/src/test/resources/template.xlsx b/hutool-poi/src/test/resources/template.xlsx
index b78abfddc..357afbdd1 100644
Binary files a/hutool-poi/src/test/resources/template.xlsx and b/hutool-poi/src/test/resources/template.xlsx differ