基于java8
引入依赖
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>8.3.1</version> <!-- Java 8 兼容版本 -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version> <!-- 日志依赖 -->
</dependency>
package com.inspur.plugins.project.export;
import com.inspur.plugins.bill.dto.ProjectBillOutDTO;
import com.inspur.plugins.partner.entity.PartnerFile;
import org.docx4j.XmlUtils;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.*;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import java.io.*;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.docx4j.dml.wordprocessingDrawing.Inline; // 新增:图片内联对象类
import org.docx4j.wml.Drawing; // 新增:绘图对象类
/**
* Java 8 兼容的 Word 模板填充工具类
* 支持文本内容控件和表格数据填充
*/
public class WordTemplateWriter {
/**
* 填充Word模板并输出文件
* @param templatePath 模板路径(支持类路径或绝对路径)
* @param outputPath 输出文件路径
* @param dataMap 填充数据(key: 内容控件标签, value: 填充值)
* @throws Exception 处理异常
*/
public static void fillTemplate(String templatePath, String outputPath, Map<String, Object> dataMap) throws Exception {
// 加载模板
WordprocessingMLPackage wordMLPackage = loadTemplate(templatePath);
MainDocumentPart mainDocPart = wordMLPackage.getMainDocumentPart();
// 新增:判断是否为保证金模板并填充表头
boolean isDepositTemplate = templatePath.contains("(保证金)");
if (isDepositTemplate && dataMap.containsKey("payRate")) {
fillTableHeader(mainDocPart, dataMap);
}
// 新增:替换文本占位符(<<tag>>格式)
replaceTextPlaceholders(mainDocPart, dataMap);
// 新增:替换图片占位符([[image:tag]]格式)
replaceImagePlaceholders(mainDocPart, dataMap);
// 填充表格数据(如果存在表格数据)
if (dataMap.containsKey("tableData")) {
fillTableData(mainDocPart, dataMap.get("tableData")); // 直接传递原始对象
}
// 保存文件
saveDocument(wordMLPackage, outputPath);
}
/**
* 填充表格表头数据(针对保证金模板特殊处理)
*/
private static void fillTableHeader(MainDocumentPart mainDocPart, Map<String, Object> headerData) throws Docx4JException, JAXBException {
// Map<String, String> headerData = convertToMap(headerDataObj);
if (headerData == null || headerData.isEmpty()) return;
// 获取文档中第一个表格
List<Object> tables = mainDocPart.getJAXBNodesViaXPath("//w:tbl", false);
if (tables.isEmpty()) return;
Object tableObj = tables.get(0);
if (tableObj instanceof JAXBElement) {
tableObj = ((JAXBElement<?>) tableObj).getValue();
}
Tbl table = (Tbl) tableObj;
List<Tr> rows = table.getContent().stream()
.filter(Tr.class::isInstance)
.map(Tr.class::cast)
.collect(Collectors.toList());
if (rows.isEmpty()) return; // 确保表格至少有一行
// 获取表头行(第一行)
Tr headerRow = rows.get(0);
List<Tc> headerCells = getCellsFromRow(headerRow);
// 替换表头单元格中的 {{tag}} 占位符
replacePlaceholdersInTextNodes(extractTextNodesFromContents(headerRow.getContent()), headerData);
}
/**
* 保证金特殊处理支持表格单元格、段落等嵌套结构)保证金特殊处理
*/
private static List<Object> extractTextNodesFromContents(List<Object> content) {
List<Object> textNodes = new ArrayList<>();
for (Object obj : content) {
// 处理JAXBElement包装的元素
if (obj instanceof JAXBElement) {
obj = ((JAXBElement<?>) obj).getValue();
}
// 递归处理所有内容容器(替换原有的P和R类型判断)
if (obj instanceof ContentAccessor) { // ContentAccessor是docx4j中容器元素的通用接口
textNodes.addAll(extractTextNodesFromContents(((ContentAccessor) obj).getContent()));
} else if (obj instanceof Text) { // 文本节点
textNodes.add(obj);
}
}
return textNodes;
}
/**
* 将任意对象转换为字段名-值字符串映射(支持基本类型、String、BigDecimal等)
*/
private static Map<String, String> convertToMap(Object obj) {
if (obj == null) return null;
// 若已是Map则直接转换(兼容旧逻辑)
if (obj instanceof Map) {
Map<?, ?> rawMap = (Map<?, ?>) obj;
Map<String, String> strMap = new HashMap<>();
rawMap.forEach((k, v) -> strMap.put(k.toString(), v != null ? v.toString() : ""));
return strMap;
}
// 反射提取对象字段值(支持DTO对象)
Map<String, String> fieldMap = new HashMap<>();
Class<?> clazz = obj.getClass();
// 获取所有字段(包括父类字段)
List<Field> fields = new ArrayList<>();
while (clazz != null) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass();
}
// 提取字段值
for (Field field : fields) {
try {
field.setAccessible(true);
Object value = field.get(obj);
fieldMap.put(field.getName(), value != null ? value.toString() : "");
} catch (Exception e) {
}
}
return fieldMap;
}
/**
* 加载Word模板
*/
private static WordprocessingMLPackage loadTemplate(String templatePath) throws Docx4JException, IOException {
// 尝试从类路径加载模板
try (InputStream is = WordTemplateWriter.class.getClassLoader().getResourceAsStream(templatePath)) {
if (is != null) {
return WordprocessingMLPackage.load(is);
}
}
// 类路径加载失败则尝试绝对路径
return WordprocessingMLPackage.load(new File(templatePath));
}
/**
* 填充表格数据
* 模板要求:表格需包含表头行和至少一行"模板行"(内容控件作为占位符)
*/
private static void fillTableData(MainDocumentPart mainDocPart, Object tableDataObj) throws Docx4JException, JAXBException {
// 1. 校验输入是否为列表
if (!(tableDataObj instanceof List)) {
System.out.println("tableData 不是有效列表类型,跳过表格填充");
return;
}
List<?> tableData = (List<?>) tableDataObj;
if (tableData.isEmpty()) return;
// 获取文档中第一个表格
List<Object> tables = mainDocPart.getJAXBNodesViaXPath("//w:tbl", false);
if (tables.isEmpty()) return;
Object tableObj = tables.get(0);
if (tableObj instanceof JAXBElement) {
tableObj = ((JAXBElement<?>) tableObj).getValue();
}
Tbl table = (Tbl) tableObj;
List<Tr> rows = table.getContent().stream()
.filter(Tr.class::isInstance)
.map(Tr.class::cast)
.collect(Collectors.toList());
if (rows.size() < 2) return; // 至少需要表头行 + 1行模板行
// 获取模板行(假设第二行为模板行)
Tr templateRow = rows.get(1);
// 移除模板行(后续会替换为实际数据行)
rows.remove(templateRow);
// 新增:计算表格总列数(从模板行获取)
List<Tc> templateCells = getCellsFromRow(templateRow);
int totalColumns = templateCells.size();
// 填充数据行并添加合并行
for (Object rowObj : tableData) {
// 将对象转换为字段名-值映射(核心适配)
Map<String, String> rowData = convertToMap(rowObj);
if (rowData == null) continue;
// 创建数据行
Tr dataRow = (Tr) XmlUtils.deepCopy(templateRow);
fillTableRow(dataRow, rowData);
// 创建要合并的第二行
Tr mergedRow = (Tr) XmlUtils.deepCopy(templateRow);
clearTableRowContent(mergedRow); // 清空合并行内容
// 添加两行到表格
rows.add(dataRow);
rows.add(mergedRow);
// 合并两行的第一列(垂直合并)
Tc dataRowFirstCell = getCellByIndex(dataRow, 0);
setVerticalMerge(dataRowFirstCell, "restart");
Tc mergedRowFirstCell = getCellByIndex(mergedRow, 0);
setVerticalMerge(mergedRowFirstCell, "continue");
// 给第二列赋值并横向合并后续所有列
Tc mergedRowSecondCell = getCellByIndex(mergedRow, 1);
if (mergedRowSecondCell != null) {
setCellContent(mergedRowSecondCell, rowData.get("calculationFormula"));
// 新增:设置横向合并(合并剩余所有列)
setCellGridSpan(mergedRowSecondCell, totalColumns - 1);
// 新增:删除合并行中多余的单元格(从第3列开始)
List<Tc> mergedRowCells = getCellsFromRow(mergedRow);
if (mergedRowCells.size() > 2) {
// 只保留前两列单元格,删除后续多余单元格
mergedRow.getContent().subList(2, mergedRow.getContent().size()).clear();
}
}
}
// 更新表格内容
table.getContent().clear();
table.getContent().addAll(rows);
}
/**
* 从行中获取所有单元格
*/
private static List<Tc> getCellsFromRow(Tr row) {
return row.getContent().stream()
.map(obj -> {
if (obj instanceof JAXBElement) {
return (Tc) ((JAXBElement<?>) obj).getValue();
} else if (obj instanceof Tc) {
return (Tc) obj;
}
return null;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
* 设置单元格横向合并(跨列)
* @param cell 单元格
* @param span 合并列数(>=2表示合并后续span-1列)
*/
private static void setCellGridSpan(Tc cell, int span) {
if (cell == null || span <= 1) return; // 无需合并或参数无效
TcPr tcPr = cell.getTcPr();
if (tcPr == null) {
tcPr = new TcPr();
cell.setTcPr(tcPr);
}
// 设置横向合并属性(gridSpan表示当前单元格跨越的列数)
TcPrInner.GridSpan gridSpan = new TcPrInner.GridSpan();
gridSpan.setVal(BigInteger.valueOf(span));
tcPr.setGridSpan(gridSpan);
}
/**
* 填充表格行数据
*/
private static void fillTableRow(Tr row, Map<String, String> rowData) {
List<Tc> cells = row.getContent().stream()
// 新增:处理 JAXBElement 包装的单元格
.map(obj -> {
if (obj instanceof JAXBElement) {
return ((JAXBElement<?>) obj).getValue();
}
return obj;
})
.filter(Tc.class::isInstance)
.map(Tc.class::cast)
.collect(Collectors.toList());
for (Tc cell : cells) {
// 提取单元格内所有文本节点
List<Object> cellTextNodes = extractTextNodesFromContent(cell.getContent());
// 新增:合并多个文本节点为一个(若存在)
if (cellTextNodes.size() > 1) {
// 1. 合并所有文本节点内容
StringBuilder mergedText = new StringBuilder();
for (Object node : cellTextNodes) {
if (node instanceof Text) {
mergedText.append(((Text) node).getValue());
}
}
// 2. 清空单元格原有内容,添加合并后的文本节点
cell.getContent().clear();
P paragraph = new P();
R run = new R();
Text mergedTextNode = new Text();
mergedTextNode.setValue(mergedText.toString());
run.getContent().add(mergedTextNode);
paragraph.getContent().add(run);
cell.getContent().add(paragraph);
// 3. 更新文本节点列表为合并后的单个节点
cellTextNodes = Collections.singletonList(mergedTextNode);
}
// 替换单元格内的 {{tag}} 占位符
replacePlaceholdersInTextNodes(cellTextNodes, rowData);
}
}
/**
* 通用占位符替换工具(供文本和表格共用)
* 替换文本节点列表中的 {{tag}} 占位符
*/
private static void replacePlaceholdersInTextNodes(List<Object> textNodes, Map<String, ?> dataMap) {
Pattern placeholderPattern = Pattern.compile("\\[\\[(.+?)\\]\\]");
for (Object obj : textNodes) {
Text textNode = null;
// 处理JAXBElement包装的文本节点
if (obj instanceof JAXBElement) {
JAXBElement<?> jaxbElement = (JAXBElement<?>) obj;
if (jaxbElement.getValue() instanceof Text) {
textNode = (Text) jaxbElement.getValue();
}
} else if (obj instanceof Text) {
textNode = (Text) obj;
}
if (textNode != null) {
String originalText = textNode.getValue();
Matcher matcher = placeholderPattern.matcher(originalText);
StringBuffer replacedText = new StringBuffer();
// 替换所有匹配的占位符
while (matcher.find()) {
String tag = matcher.group(1);
Object value = dataMap.get(tag);
String replacement = value != null ? value.toString() : "";
matcher.appendReplacement(replacedText, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(replacedText);
textNode.setValue(replacedText.toString());
}
}
}
/**
* 从内容中递归提取所有文本节点(支持表格单元格、段落等嵌套结构)
*/
private static List<Object> extractTextNodesFromContent(List<Object> content) {
List<Object> textNodes = new ArrayList<>();
for (Object obj : content) {
// 处理JAXBElement包装的元素
if (obj instanceof JAXBElement) {
obj = ((JAXBElement<?>) obj).getValue();
}
// 递归处理段落(P)和文本块(R)
if (obj instanceof P) { // 段落
textNodes.addAll(extractTextNodesFromContent(((P) obj).getContent()));
} else if (obj instanceof R) { // 文本块
textNodes.addAll(extractTextNodesFromContent(((R) obj).getContent()));
} else if (obj instanceof Text) { // 文本节点
textNodes.add(obj);
}
}
return textNodes;
}
/**
* 替换文档中的文本占位符(格式:<<tag>>)
* 支持占位符前后有其他文本的场景
*/
private static void replaceTextPlaceholders(MainDocumentPart mainDocPart, Map<String, Object> dataMap) throws Docx4JException, JAXBException {
// 1. 获取文档中所有文本节点(<w:t>元素)
List<Object> textNodes = mainDocPart.getJAXBNodesViaXPath("//w:t", false);
// 2. 定义占位符匹配模式({{tag}}格式)
Pattern placeholderPattern = Pattern.compile("\\<\\<(.+?)\\>\\>");
for (Object obj : textNodes) {
Text textNode = null;
// 处理可能的JAXBElement包装节点
if (obj instanceof JAXBElement) {
JAXBElement<?> jaxbElement = (JAXBElement<?>) obj;
if (jaxbElement.getValue() instanceof Text) {
textNode = (Text) jaxbElement.getValue();
}
} else if (obj instanceof Text) {
textNode = (Text) obj;
}
if (textNode != null) {
String originalText = textNode.getValue();
Matcher matcher = placeholderPattern.matcher(originalText);
StringBuffer replacedText = new StringBuffer();
// 3. 匹配并替换所有占位符
while (matcher.find()) {
String tag = matcher.group(1); // 获取占位符中的tag(如{{projectName}}中的projectName)
Object value = dataMap.get(tag);
String replacement = value != null ? value.toString() : ""; // 无数据时替换为空字符串
matcher.appendReplacement(replacedText, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(replacedText); // 拼接剩余文本
// 4. 更新文本节点值
textNode.setValue(replacedText.toString());
}
}
}
/**
* 从行中获取指定索引的单元格
*/
private static Tc getCellByIndex(Tr row, int index) {
List<Tc> cells = row.getContent().stream()
.map(obj -> {
if (obj instanceof JAXBElement) {
return (Tc) ((JAXBElement<?>) obj).getValue();
} else if (obj instanceof Tc) {
return (Tc) obj;
}
return null;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
return cells.size() > index ? cells.get(index) : null;
}
/**
* 设置单元格垂直合并属性
* @param cell 单元格
* @param val "restart" 开始合并, "continue" 继续合并
*/
private static void setVerticalMerge(Tc cell, String val) {
if (cell == null) return;
TcPr tcPr = cell.getTcPr();
if (tcPr == null) {
tcPr = new TcPr();
cell.setTcPr(tcPr);
}
TcPrInner.VMerge vMerge = new TcPrInner.VMerge();
vMerge.setVal(val);
tcPr.setVMerge(vMerge);
}
/**
* 清空行中所有单元格的内容
*/
private static void clearTableRowContent(Tr row) {
List<Tc> cells = row.getContent().stream()
.map(obj -> {
if (obj instanceof JAXBElement) {
return (Tc) ((JAXBElement<?>) obj).getValue();
} else if (obj instanceof Tc) {
return (Tc) obj;
}
return null;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
for (Tc cell : cells) {
cell.getContent().clear();
}
}
/**
* 设置单元格内容
*/
private static void setCellContent(Tc cell, String content) {
if (cell == null) return;
cell.getContent().clear();
P paragraph = new P();
R run = new R();
Text text = new Text();
text.setValue(content != null ? content : "");
run.getContent().add(text);
paragraph.getContent().add(run);
cell.getContent().add(paragraph);
}
/**
* 保存文档到指定路径
*/
private static void saveDocument(WordprocessingMLPackage wordMLPackage, String outputPath) throws Docx4JException, IOException {
File outputFile = new File(outputPath);
// 确保父目录存在
if (outputFile.getParentFile() != null) {
outputFile.getParentFile().mkdirs();
}
try (FileOutputStream fos = new FileOutputStream(outputFile)) {
wordMLPackage.save(fos);
}
}
// 新增:图片插入相关方法开始
/**
* 替换文档中的图片占位符(格式:[[image:tag]])
*/
private static void replaceImagePlaceholders(MainDocumentPart mainDocPart, Map<String, Object> dataMap) throws Exception {
// 获取所有段落
List<Object> paragraphs = mainDocPart.getJAXBNodesViaXPath("//w:p", false);
for (Object paraObj : paragraphs) {
P paragraph = (P) paraObj;
List<Object> content = paragraph.getContent();
// 遍历段落内容查找图片占位符
for (int i = 0; i < content.size(); i++) {
Object obj = content.get(i);
if (obj instanceof JAXBElement) {
obj = ((JAXBElement<?>) obj).getValue();
}
if (obj instanceof R) { // 文本块
R run = (R) obj;
List<Object> runContent = run.getContent();
for (int j = 0; j < runContent.size(); j++) {
Object runObj = runContent.get(j);
// 新增:处理 JAXBElement 包装的文本节点(关键修复)
if (runObj instanceof JAXBElement) {
runObj = ((JAXBElement<?>) runObj).getValue(); // 提取包装的实际值
}
if (runObj instanceof Text) { // 现在能正确识别被包装的 Text 节点
Text textNode = (Text) runObj;
String text = textNode.getValue();
// 匹配图片占位符格式 {{image:tag}}
Pattern pattern = Pattern.compile("\\[\\[image:(.+?)\\]\\]");
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
String imageTag = matcher.group(1);
Object imageObj = dataMap.get(imageTag); // 不再强制转换为String,支持List
if (imageObj == null) continue;
// 1. 移除原始占位符文本节点
runContent.remove(j);
// 2. 统一处理单图片(String)和多图片(List<String>)
List<String> imagePaths = new ArrayList<>();
if (imageObj instanceof List) {
imagePaths = (List<String>) imageObj; // 多图片列表
} else if (imageObj instanceof String) {
imagePaths.add((String) imageObj); // 兼容单图片字符串
}
// 3. 循环插入所有图片
for (String imgPath : imagePaths) {
Drawing drawing = createImageDrawing(mainDocPart, imgPath, 400, 300);
runContent.add(j++, drawing); // 逐个添加图片,索引递增
}
// 4. 处理占位符前后的剩余文本(移至所有图片之后)
String remainingText = text.replace(matcher.group(0), "");
if (!remainingText.isEmpty()) {
Text remainingTextNode = new Text();
remainingTextNode.setValue(remainingText);
runContent.add(j, remainingTextNode);
}
}
}
}
}
}
}
}
/**
* 创建图片Drawing对象(适配 docx4j-core-8.3.1)
*/
private static Drawing createImageDrawing(MainDocumentPart mainDocPart, String imagePath, int width, int height) throws Exception {
// 1. 读取图片文件(Java 8 兼容方式)
byte[] imageBytes;
try (InputStream fis = getInputStreamByPath(imagePath);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
imageBytes = bos.toByteArray();
}
// 2. 使用 docx4j 8.3.1 推荐的工具类创建图片部件
BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(
mainDocPart.getPackage(), // 当前文档包
mainDocPart, // 源部件(主文档)
imageBytes // 图片字节数组
);
// 3. 计算图片尺寸(1px = 1440 EMUs,Word 内部单位)
long emuWidth = width * 1440L;
long emuHeight = height * 1440L;
// 4. 创建内联图片对象(适配 8.3.1 版本参数要求)
Inline inline = imagePart.createImageInline(
"image", // 文件名提示
"alt text", // 替代文本
0, // 图片ID(整数)
1, // 图片索引(整数)
emuWidth, // 宽度(EMU)
emuHeight, // 高度(EMU)
false // 是否链接(false=嵌入)
);
// 5. 包装为Drawing对象返回
Drawing drawing = new Drawing();
drawing.getAnchorOrInline().add(inline);
return drawing;
}
private static InputStream getInputStreamByPath(String path) throws IOException, MalformedURLException {
if (path.startsWith("http://") || path.startsWith("https://")) {
// 网络URL:使用URL.openStream()
URL url = new URL(path);
return url.openStream();
} else {
// 本地文件:使用FileInputStream(保持原逻辑)
return new FileInputStream(path);
}
}
/**
* 生成Word文档
* @param dataMap 数据模型
* @param outPath 输出路径
* @throws Exception
*/
public static void outPutWordFile(Map<String, Object> dataMap, String outPath) throws Exception {
try {
// 根据条件动态确定模板文件名(resources/template目录下的文件名)
String templateFileName = "";
String cooperationWay = (String) dataMap.get("cooperationWay");
Integer isDeposit = (Integer) dataMap.get("isDeposit");
// 拼接模板文件名(根据实际模板文件命名调整)
if ("渠道合作".equals(cooperationWay)) {
templateFileName = isDeposit == 1 ? "【充电渠道费】计算说明(保证金).docx" : "【充电渠道费】计算说明.docx";
} else if ("设施租赁".equals(cooperationWay)) {
templateFileName = "【充电设施租赁费】计算说明.docx";
} else {
templateFileName = "【充电场地管理服务费】计算说明.docx";
}
// 构建 resources/template 目录下的模板资源路径(类路径相对路径)
String templateResourcePath = "template/" + templateFileName;
// 校验模板是否存在于 resources/template 目录
ClassLoader classLoader = WordTemplateWriter.class.getClassLoader();
if (classLoader.getResource(templateResourcePath) == null) {
throw new FileNotFoundException("模板文件不存在于 resources/template: " + templateResourcePath);
}
// 2. 填充模板(直接传入类路径下的模板资源路径)
// ... 现有代码 ...
// 2. 填充模板(直接传入类路径下的模板资源路径)
fillTemplate(
templateResourcePath, // 类路径下的模板路径:resources/template/xxx.docx
outPath + File.separator + templateFileName, // 使用跨平台路径分隔符
dataMap
);
// ... 现有代码 ...
System.out.println("Word文档生成成功!");} catch (Exception e) {
e.printStackTrace();
}
}
// 测试方法
// public static void main(String[] args) {
// try {
// // 1. 准备数据
// Map<String, Object> dataMap = new HashMap<>();
// // 文本数据
// dataMap.put("cooperationWay","设施租赁");
// // isDeposit 1涉及保证金 0不涉及保证金
// dataMap.put("isDeposit",1);
// // 最终支付比例,只有涉及保证金才有这个字段
// dataMap.put("payRate","95%");
// dataMap.put("contractCode","contractCode1");
// dataMap.put("calculationBaseName","测试计算基准名称");
// dataMap.put("billMonth","2025-09、2025-08、2025-07");
// dataMap.put("billingModel","用户消费金额(不含税)*考核利用率系数*考评得分系数*(1-保证金预留比例)");
// String outPath = "C:\\Users\\sombo\\Desktop\\";
// // 根据条件动态确定模板文件名(resources/template目录下的文件名)
// String templateFileName = "";
// String cooperationWay = (String) dataMap.get("cooperationWay");
// Integer isDeposit = (Integer) dataMap.get("isDeposit");
//
// // 拼接模板文件名(根据实际模板文件命名调整)
// if ("渠道合作".equals(cooperationWay)) {
// templateFileName = isDeposit == 1 ? "【充电渠道费】计算说明(保证金).docx" : "【充电渠道费】计算说明.docx";
// } else if ("设施租赁".equals(cooperationWay)) {
// templateFileName = "【充电设施租赁费】计算说明.docx";
// } else {
// templateFileName = "【充电场地管理服务费】计算说明.docx";
// }
//
// // 构建 resources/template 目录下的模板资源路径(类路径相对路径)
// String templateResourcePath = "template/" + templateFileName;
//
// // 校验模板是否存在于 resources/template 目录
// ClassLoader classLoader = WordTemplateWriter.class.getClassLoader();
// if (classLoader.getResource(templateResourcePath) == null) {
// throw new FileNotFoundException("模板文件不存在于 resources/template: " + templateResourcePath);
// }
//
// // 表格数据(需与模板中表格控件标签对应)
// List<ProjectBillOutDTO> tableData = new ArrayList<>();
// ProjectBillOutDTO row1 = new ProjectBillOutDTO();
// row1.setStaMonth("2023-10");
// row1.setCalculationBaseAmount(new BigDecimal("1000000"));
// row1.setCalculationBaseName("端口");
// row1.setCalculationFormula("1000000*95%*100%*(1-0.1)");
// tableData.add(row1);
// ProjectBillOutDTO row2 = new ProjectBillOutDTO();
// row2.setStaMonth("2023-11");
// row2.setCalculationBaseAmount(new BigDecimal("1000000"));
// row2.setCalculationBaseName("端口");
// row2.setCalculationFormula("1000*95%*100%*(1-0.1)");
// tableData.add(row2);
// dataMap.put("tableData", tableData);
// // 新增:图片数据(key对应模板中的[[image:tag]])
// dataMap.put("projectLogo", Arrays.asList(
// "http://10.110.141.88:8855/scap/others/2025/7/23/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20250430104053_1753232919287.png",
// "http://10.110.141.88:8855/scap/others/2025/7/23/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20250430104053_1753232919287.png",
// "http://10.110.141.88:8855/scap/others/2025/7/23/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20250430104053_1753232919287.png"
// ));
// fillTemplate(
// templateResourcePath, // 类路径下的模板路径:resources/template/xxx.docx
// outPath+templateFileName, // 输出路径
// dataMap
// );
// System.out.println("Word文档生成成功!");
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
}