在最近的工作中,遇到了一个这样的需求,就是用户要求在导出数据时,用户可以选择一条或者多条数据,并且连带着附件及其他信息同时导出到word文档中。
需求描述:
a、业务主数据

b、业务数据附属模块

此时主数据页面有导出功能,要求导出时,一个项目为一个word文档,然后填充所有的信息数据,包括附件,最终打包成zip压缩包返回给用户,实现效果如下:
![]()



实现过程
1、添加pom依赖
<dependency> <groupId>org.docx4j</groupId> <artifactId>docx4j-JAXB-MOXy</artifactId> <version>8.3.13</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.5</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.13.0</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.32</version> <scope>compile</scope> </dependency>
2、导出数据格式
@Data public class ProjectData { @ApiModelProperty(value = "项目名称") private String projectName; @ApiModelProperty(value = "执行机构名称") private String bsoName; @ApiModelProperty(value = "机构简介") private String synopsis; @ApiModelProperty(value = "项目联系人") private String personName; @ApiModelProperty(value = "联系电话") private String personPhone; @ApiModelProperty(value = "项目开始时间") @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai") private String projectStartTime; @ApiModelProperty(value = "项目结束时间") @JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai") private String projectEndTime; @ApiModelProperty(value = "实施区域【Y】") @AreaField(AreaFieldType.COLLABORATE_PROJECT_AREA) @Area(cascade = true) private List<String> admsAreaList; @ApiModelProperty(value = "实施区域集合【Y】") private String admsAreaListName; @ApiModelProperty(value = "经费预算(万元)") private String budget; @ApiModelProperty(value = "项目性质:1-公益创投;2-组织自有;3-组织间协作【Y】PROJECTNATURE") private String projectQuality; @ApiModelProperty(value = "项目状态:草稿箱、未开始、进行中、已结束【Y】") private String projectStatus; @ApiModelProperty(value = "服务领域") private String serviceArea; @ApiModelProperty(value = "项目背景") private String projectBackground; @ApiModelProperty(value = "预计产出") private String projectedOutputs; @ApiModelProperty(value = "服务对象集合") @MultiCode(CpServeObjectMapper.class) private List<String> cpServeObjectPoList; @ApiModelProperty(value = "服务对象") private String cpServeObjectPoListName; @ApiModelProperty(value = "服务人数") private String serveNum; @ApiModelProperty(value = "资助方") private String funderName; @ApiModelProperty(value = "资金来源") private String fundSource; @ApiModelProperty(value = "项目分类【Y】") @MultiCode(CpTypeMapper.class) private List<String> cpTypePoList; @ApiModelProperty(value = "项目分类集合") private String cpTypePoListName; @ApiModelProperty(value = "项目创新性") private String projectInnovation; @ApiModelProperty(value = "可遇见风险及应对措施") private String riskMeasure; @ApiModelProperty(value = "附件") @Attachment(AttachUseType.COLLABORATE_PROJECT_ATTACH) List<AttachmentVO> ossIdList; @ApiModelProperty(value = "项目评估报告") @Attachment(AttachUseType.COLLABORATE_PROJECT_ASS_REPORT) List<AttachmentVO> assReportList; @ApiModelProperty(value = "附件url集合") List<String> filePathList01; @ApiModelProperty(value = "项目评估报告url集合") List<String> filePathList02; @ApiModelProperty(value = "实施计划") private List<Plan> plans; @ApiModelProperty(value = "成果公开") private List<Achievement> achievements; @ApiModelProperty(value = "占位符") private String titlePlaceholder; }@Data public class Plan { @ApiModelProperty(value = "实施计划") private String planName; @ApiModelProperty(value = "活动时间 开始时间") @JsonFormat(pattern = "yyyy-MM-dd") private String startTime; @ApiModelProperty(value = "活动时间 结束") @JsonFormat(pattern = "yyyy-MM-dd") private String endTime; @ApiModelProperty(value = "参与人数") private String numPeople; @ApiModelProperty(value = "活动形式") private String activityForm; @ApiModelProperty(value = "活动地点") private String location; @ApiModelProperty(value = "活动频次") private String activeFrequency; @ApiModelProperty(value = "活动目的") private String activeGoal; @ApiModelProperty(value = "备注") private String notes; }@Data public class Achievement { @ApiModelProperty(value = "项目名称") private String projectName; @ApiModelProperty(value = "活动名称") private String title; @ApiModelProperty(value = "活动类型") private String activityType; @ApiModelProperty(value = "活动时间") private String activityTime; @ApiModelProperty(value = "详细地址") private String address; @ApiModelProperty(value = "活动区划【Y】") @AreaField(AreaFieldType.FEED_BACK_AREA) @Area(cascade = true) List<String> admsAreaList; @ApiModelProperty(value = "活动区划集合名称") private String admsAreaListName; @ApiModelProperty(value = "服务人次") private String personNumber; @ApiModelProperty(value = "捐赠金额") private String donationAmount; @ApiModelProperty(value = "服务时长") private String duration; @ApiModelProperty(value = "活动参与对象满意度") private String satisfaction; @ApiModelProperty(value = "活动效果说明") private String implementContent; @ApiModelProperty(value = "审核人") private String updateUser; @ApiModelProperty(value = "审核时间") private String updateTime; @ApiModelProperty(value = "佐证材料") @Attachment(AttachUseType.FEED_BACK_TYPE) List<AttachmentVO> attachmentPOList; @ApiModelProperty(value = "其他材料") @Attachment(AttachUseType.FEED_BACK_TYPE2) List<AttachmentVO> attachmentPOList2; @ApiModelProperty(value = "佐证材料附件url集合") private List<String> filePathList1; @ApiModelProperty(value = "其他材料附件url集合") private List<String> filePathList2; }
3、文件生成工具类
package com.yinhai.kq.common.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ZipUtil;
import org.apache.poi.xwpf.usermodel.*;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.PartName;
import org.docx4j.openpackaging.parts.WordprocessingML.AlternativeFormatInputPart;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.relationships.Relationship;
import org.docx4j.wml.CTAltChunk;
import javax.xml.bind.JAXBException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import com.yinhai.kq.business.domain.export.ProjectData;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @date 2025/1/6 14:11
*/
public class GenerateOnWordTemplateKit {
public static final String TEMPLATE_PATH = "项目地址+/src/main/resources/template/";
public static final String FILE_PATH = TEMPLATE_PATH + "file/";
public static final String OUTPUT_PATH = TEMPLATE_PATH + "output/";
public static void main(String[] args) throws Exception {
// 程序入口,主要作用是为多个项目分别根据模版组装数据后合并成一个word
// 1. 准备数据(模拟从数据库中查询,key为项目名称,value为项目数据)
Map<String, ProjectData> dataMap = queryData();
// 2. 为每个项目生成一个以项目名为名称的文件夹,文件夹内包括word文档、附件
// 2.1 创建一个临时文件夹
String tempDirPath = FileUtil.mkParentDirs("temp/" + UUID.randomUUID()).getAbsolutePath();
// 2.2 在这个临时文件夹下面,根据数据生成多个项目文件夹及word文档、附件
generateAllWordDocument(dataMap, tempDirPath);
// 3. 打包整个临时文件夹为zip压缩包
File projectZipFile = compressIntoAZipFile(tempDirPath);
System.out.println("压缩包路径保存:" + projectZipFile.getAbsolutePath());
// 4. 删除所有临时文件
deleteAllTempFiles(tempDirPath);
}
/**
* 打包整个临时文件夹为zip压缩包
*
* @param tempDirPath 需要打包的文件夹目录
* @return 压缩包文件
*/
public static File compressIntoAZipFile(String tempDirPath) {
return ZipUtil.zip(tempDirPath, OUTPUT_PATH + "慈善项目_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN) + ".zip");
}
/**
* 删除所有临时文件
*
* @param tempDirPath 临时文件夹路径
*/
public static void deleteAllTempFiles(String tempDirPath) {
FileUtil.del(tempDirPath);
}
/**
* 生成所有项目文档
*
* @param dataMap 所有项目信息,key为项目名称,value为项目数据
* @param tempDirPath 临时目录,用于存储多个临时Word文档
* @throws Exception 异常
*/
public static void generateAllWordDocument(Map<String, ProjectData> dataMap, String tempDirPath) throws Exception {
for (Map.Entry<String, ProjectData> entry : dataMap.entrySet()) {
// 2.2 创建一个以项目名为名称的文件夹
File projectDir = new File(tempDirPath + "/" + entry.getKey());
if (!projectDir.exists() && !projectDir.mkdirs()) {
throw new RuntimeException("文件夹[" + projectDir.getAbsolutePath() + "]创建失败");
}
// 2.3 为每个项目生成一个word文档
ProjectData projectData = entry.getValue();
generateWordDocument(projectData, projectDir);
}
}
/**
* 生成项目word文档
*
* @param projectData 项目信息
* @param projectDir 临时目录,用于存储多个临时Word文档
* @throws Exception 异常
*/
private static void generateWordDocument(ProjectData projectData, File projectDir) throws Exception {
// 创建一个列表,用于存储多个临时Word文档地址
List<String> tempWordList = new ArrayList<>();
tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_1.docx", projectData, projectDir));
if (CollUtil.isNotEmpty(projectData.getFilePathList01())) {
tempWordList.addAll(simulateInsertingAttachments(projectData.getFilePathList01(), projectDir));
}
if (CollUtil.isNotEmpty(projectData.getFilePathList02())) {
tempWordList.addAll(simulateInsertingAttachments(projectData.getFilePathList02(), projectDir));
}
projectData.setTitlePlaceholder("二、实施计划");
tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_3.docx", projectData, projectDir));
if (CollUtil.isNotEmpty(projectData.getPlans())) {
for (Plan plan : projectData.getPlans()) {
tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_2.docx", plan, projectDir));
}
}
projectData.setTitlePlaceholder("三、成果公开");
tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_3.docx", projectData, projectDir));
if (CollUtil.isNotEmpty(projectData.getAchievements())) {
for (Achievement achievement : projectData.getAchievements()) {
tempWordList.add(createWordMLPackage(TEMPLATE_PATH + "kqcs_project/template_4.docx", achievement, projectDir));
if (CollUtil.isNotEmpty(achievement.getFilePathList1())) {
tempWordList.addAll(simulateInsertingAttachments(achievement.getFilePathList1(), projectDir));
}
if (CollUtil.isNotEmpty(achievement.getFilePathList2())) {
tempWordList.addAll(simulateInsertingAttachments(achievement.getFilePathList2(), projectDir));
}
}
}
mergeDocuments(projectData.getProjectName(), tempWordList, projectDir);
// 删除临时文件
tempWordList.forEach(FileUtil::del);
}
/**
* 模拟从数据库中查询数据
*
* @return 返回一个模拟数据
*/
private static Map<String, ProjectData> queryData() {
Map<String, ProjectData> dataMap = new HashMap<>();
for (int i = 1; i <= 2; i++) {
// 创建模拟项目数据
ProjectData project = new ProjectData();
project.setProjectName("“友邻有爱”社区养老互助服务项目-" + i);
List<Plan> plans = new ArrayList<>();
for (int j = 1; j <= 3; j++) {
Plan plan = new Plan();
plan.setPlanName("计划-" + j);
plans.add(plan);
}
project.setPlans(plans);
List<Achievement> achievements = new ArrayList<>();
for (int j = 1; j <= 2; j++) {
Achievement achievement = new Achievement();
achievement.setTitle("成果-" + j);
// 附件地址,这里可以是文件key,配合download方法能下载到本地即可
achievement.setFilePathList1(Arrays.stream(FileUtil.ls(FILE_PATH)).map(File::getAbsolutePath).collect(Collectors.toList()));
achievement.setFilePathList2(Arrays.stream(FileUtil.ls(FILE_PATH)).map(File::getAbsolutePath).collect(Collectors.toList()));
achievements.add(achievement);
}
project.setAchievements(achievements);
dataMap.put(project.getProjectName(), project);
}
return dataMap;
}
/**
* 生成word文档
*
* @param templatePath word模版路径
* @param dataObj 数据对象
* @param tempDirFile 临时目录
* @return 临时文件路径
* @throws Docx4JException 异常
* @throws JAXBException 异常
* @throws IOException 异常
*/
private static String createWordMLPackage(String templatePath, Object dataObj, File tempDirFile) throws Docx4JException, JAXBException, IOException {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File(templatePath));
// 填充数据到模板
Map<String, String> dataMap = objectToMap(dataObj);
wordMLPackage.getMainDocumentPart().variableReplace(dataMap);
// 保存新的Word文档
String templateFileExtension = templatePath.substring(templatePath.lastIndexOf(".") + 1);
File tempFile = new File(tempDirFile.getAbsolutePath() + "/" + UUID.randomUUID() + "." + templateFileExtension);
if (!tempFile.exists() && !tempFile.createNewFile()) {
throw new RuntimeException("文件[" + tempFile.getAbsolutePath() + "]创建失败");
}
wordMLPackage.save(tempFile);
return tempFile.getAbsolutePath();
}
/**
* 将对象转换为Map
*
* @param dataObj 对象
* @return 返回一个Map
*/
private static Map<String, String> objectToMap(Object dataObj) {
if (null == dataObj) {
return MapUtil.empty();
}
Map<String, String> dataMap = new HashMap<>();
// 使用反射获取dataObj的所有字段
Field[] fields = dataObj.getClass().getDeclaredFields();
for (Field field : fields) {
// 设置访问权限,以便可以访问私有字段
field.setAccessible(true);
try {
// 获取字段值
Object value = field.get(dataObj);
// 如果字段值是String类型,则添加到dataMap中
if (value instanceof String) {
dataMap.put(field.getName(), (String) value);
}
} catch (IllegalAccessException e) {
// 忽略无法访问的字段
}
}
return dataMap;
}
/**
* 合并文档
*
* @param fileName 合并之后的word文档名称
* @param wordList word列表
* @param tempDirFile 临时目录
* @throws Exception 异常
*/
public static void mergeDocuments(String fileName, List<String> wordList, File tempDirFile) throws Exception {
File tempFile = new File(tempDirFile.getAbsolutePath() + "/" + fileName + ".docx");
if (!tempFile.exists() && !tempFile.createNewFile()) {
throw new RuntimeException("文件[" + tempFile.getAbsolutePath() + "]创建失败");
}
// 保存合并后的文档
mergeDoc(wordList, tempFile.getAbsolutePath());
}
/**
* 模拟插入附件
*
* @param fileList 文件地址列表
* @param tempDirFile 临时目录
* @return 返回一个word文档列表地址
* @throws Exception 异常
*/
private static List<String> simulateInsertingAttachments(List<String> fileList, File tempDirFile) throws Exception {
List<String> wordList = new ArrayList<>();
// 创建一个新的Word文档
XWPFDocument document = new XWPFDocument();
// 创建一个段落
XWPFParagraph paragraph = document.createParagraph();
for (int i = 0; i < fileList.size(); i++) {
String filePath = fileList.get(i);
String fileName = getFileName(filePath);
InputStream inputStream = downloadFile(filePath);
String hyperlinkUrl = "./附件/" + fileName;
FileUtil.writeFromStream(inputStream, new File(tempDirFile + hyperlinkUrl));
// 超链接地址,可以是网络地址、本地文件相对路径
XWPFHyperlinkRun hyperlinkRun = paragraph.createHyperlinkRun(hyperlinkUrl);
hyperlinkRun.setText(fileName);
hyperlinkRun.setColor("0000FF");
// 设置下划线
hyperlinkRun.setUnderline(UnderlinePatterns.SINGLE);
// 插入一个换行,除了最后一次循环
if (i < fileList.size() - 1) {
paragraph.createRun().addBreak(BreakType.TEXT_WRAPPING);
}
}
// 将文档保存到文件
String tempFilePath = tempDirFile.getAbsolutePath() + "/" + UUID.randomUUID() + ".docx";
try (FileOutputStream out = new FileOutputStream(tempFilePath)) {
document.write(out);
}
// 关闭文档
document.close();
wordList.add(tempFilePath);
return wordList;
}
/**
* 从文件路径中提取文件名(包括扩展名)
*
* @param filePath 文件路径
* @return 文件名(包括扩展名)
*/
private static String getFileName(String filePath) {
if (filePath == null || filePath.isEmpty()) {
return null;
}
// 使用 Paths 来解析文件路径
return Paths.get(filePath).getFileName().toString();
}
/**
* 下载文件
*
* @param filePath 文件路径
* @return 输入流
* @throws IOException 异常
*/
private static InputStream downloadFile(String filePath) throws IOException {
// 这里应该是下载附件的逻辑,为了模拟效果直接从本地读取
return Files.newInputStream(Paths.get(filePath));
}
/**
* 下载图片到本地
*
* @param imageUrl 图片的URL
* @param savePath 本地保存路径(包括文件名)
* @throws IOException 如果发生I/O错误
*/
public static void downloadImage(String imageUrl, String savePath) throws IOException {
// 创建URL对象
URL url = new URL(imageUrl);
// 打开连接
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
// 允许输入流(默认为true)
httpConn.setDoInput(true);
// 不允许输出流(因为我们只是下载)
httpConn.setDoOutput(false);
// 连接服务器
httpConn.connect();
// 获取响应码
int responseCode = httpConn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) { // 成功
// 获取输入流
try (InputStream inputStream = httpConn.getInputStream();
OutputStream outputStream = new FileOutputStream(savePath)) {
// 创建一个缓冲区
byte[] buffer = new byte[4096];
int bytesRead;
// 读取数据并写入文件
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
} else {
throw new IOException("Failed to download image, HTTP response code: " + responseCode);
}
// 断开连接
httpConn.disconnect();
}
/**
* 合并word文件
*
* @param wordList 待合并文件集合
* @param outFilePath 输出文件路径
*/
public static void mergeDoc(List<String> wordList, String outFilePath) throws IOException, Docx4JException {
List<InputStream> streamList = new ArrayList<>();
for (String wordPath : wordList) {
streamList.add(Files.newInputStream(Paths.get(wordPath)));
}
mergeDocStream(streamList, new FileOutputStream(outFilePath));
}
/**
* 合并word文件
*
* @param streamList 文件流列表
* @param out 输出流
* @throws Docx4JException 异常
* @throws IOException 异常
*/
private static void mergeDocStream(List<InputStream> streamList, FileOutputStream out) throws Docx4JException, IOException {
// 创建一个空的临时Word文档作为基础
WordprocessingMLPackage target = WordprocessingMLPackage.createPackage();
int chunkId = 0;
// 将streamList中的每个文档插入到target中
for (InputStream inputStream : streamList) {
if (inputStream != null) {
insertDoc(target.getMainDocumentPart(), inputStream, chunkId++);
}
}
target.save(out);
out.close();
}
/**
* 插入文档
*
* @param mainDocumentPart mainDocumentPart
* @param inputStream inputStream
* @param chunkId chunkId
* @throws InvalidFormatException 异常
*/
private static void insertDoc(MainDocumentPart mainDocumentPart, InputStream inputStream, int chunkId) throws InvalidFormatException {
PartName partName = new PartName("/part" + chunkId + ".docx");
AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart(partName);
afiPart.setBinaryData(inputStream);
Relationship relationship = mainDocumentPart.addTargetPart(afiPart);
CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk();
chunk.setId(relationship.getId());
mainDocumentPart.addObject(chunk);
}
}
4.导出接口
@ApiOperation(value = "慈善项目基本信息、实施计划、成果公开一本账导出") @GetMapping("export") public void export(HttpServletResponse response, String id) throws Exception { if (StrUtil.isBlank(id)) { throw new FlowException("请选择要导出的慈善项目!"); } List<String> list = Arrays.asList(id.split(",")); String filePath = collaborateProjectService.export(response, list); File file = new File(filePath); // 检查文件是否存在 if (!file.exists()) { response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found"); return; } // 设置响应头 String fileName = file.getName(); // "慈善项目_20250110135423.zip" String encodedFileName = URLEncoder.encode(fileName, "UTF-8"); // 现代浏览器应该能够处理这个编码 response.setContentType("application/zip"); response.setHeader("Content-Disposition", "attachment; filename*=utf-8''" + encodedFileName); System.out.println("file.getName() =================== " + file.getName()); response.setContentLengthLong(file.length()); // 获取响应输出流 try (OutputStream outputStream = response.getOutputStream(); FileInputStream fileInputStream = new FileInputStream(file)) { // 将文件内容写入响应输出流 byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = fileInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); } }## service业务层 String export(HttpServletResponse response, List<String> list) throws Exception; ## serviceImpl业务层 @Override public String export(HttpServletResponse response, List<String> list) throws Exception { // 要导出的慈善项目id集合 // List<String> list = new ArrayList<>(Arrays.asList("1876092587459440641", "1876105476132007938")); if (CollUtil.isEmpty(list)) { throw new FlowException("请选择您要导出的慈善项目!"); } // 程序入口,主要作用是为多个项目分别根据模版组装数据后合并成一个word // 1. 准备数据(模拟从数据库中查询,key为项目名称,value为项目数据) Map<String, ProjectData> dataMap = queryData(list); // 2. 为每个项目生成一个以项目名为名称的文件夹,文件夹内包括word文档、附件 // 2.1 创建一个临时文件夹 String tempDirPath = FileUtil.mkParentDirs("temp/" + UUID.randomUUID()).getAbsolutePath(); // 2.2 在这个临时文件夹下面,根据数据生成多个项目文件夹及word文档、附件 GenerateOnWordTemplateKit.generateAllWordDocument(dataMap, tempDirPath); // 3. 打包整个临时文件夹为zip压缩包 File projectZipFile = GenerateOnWordTemplateKit.compressIntoAZipFile(tempDirPath); System.out.println("压缩包路径保存:" + projectZipFile.getAbsolutePath()); // 5. 删除所有临时文件 GenerateOnWordTemplateKit.deleteAllTempFiles(tempDirPath); return projectZipFile.getAbsolutePath(); }/** * 模拟从数据库中查询数据 * List<String> list 慈善项目主键id * @return 返回一个模拟数据 */ public Map<String, ProjectData> queryData(List<String> list) throws IOException { Map<String, ProjectData> dataMap = new HashMap<>(); for (String s : list) { // 创建模拟项目数据 ProjectData project = new ProjectData(); CollaborateProjectVO projectVO = getCollaborateProjectVO(s); if (ValidateUtil.isEmpty(projectVO)) { continue; } project.setProjectName(projectVO.getProjectName()); project.setBsoName(projectVO.getProjectExecutingPartyList().get(0).getBsoName()); project.setSynopsis(projectVO.getProjectExecutingPartyList().get(0).getSynopsis()); project.setPersonName(projectVO.getProjectExecutingPartyList().get(0).getPersonName()); project.setPersonPhone(projectVO.getProjectExecutingPartyList().get(0).getPersonPhone()); project.setProjectStartTime(DateUtil.format(projectVO.getProjectStartTime(), "yyyy-MM-dd")); project.setProjectEndTime(DateUtil.format(projectVO.getProjectEndTime(), "yyyy-MM-dd")); // 区划处理 List<String> areaNameList = areaFieldMapper.queryAreaNameByAreaCodeList(projectVO.getAdmsAreaList()); if (CollUtil.isNotEmpty(areaNameList)) { project.setAdmsAreaListName(String.join(", ", areaNameList)); } else { project.setAdmsAreaListName(null); } project.setBudget(String.valueOf(projectVO.getBudget())); // 项目性质 List<String> projectQualities = cpServeObjectMapper.queryServeNameByTypeAndValue(projectVO.getProjectQuality(), "PROJECTNATURE"); if (CollUtil.isNotEmpty(projectQualities)) { project.setProjectQuality(projectQualities.get(0)); } else { project.setProjectQuality(null); } // 项目状态 List<String> projectStatus = cpServeObjectMapper.queryServeNameByTypeAndValue(projectVO.getProjectQuality(), "PROJECTSTATUS"); if (CollUtil.isNotEmpty(projectStatus)) { project.setProjectStatus(projectStatus.get(0)); } else { project.setProjectStatus(null); } // 服务领域 List<String> demandTypes = cpServeObjectMapper.queryServeNameByTypeAndValue(projectVO.getDemandType(), "SERVICEAREA"); if (CollUtil.isNotEmpty(demandTypes)) { project.setServiceArea(demandTypes.get(0)); } else { project.setServiceArea(null); } project.setProjectBackground(projectVO.getProjectBackground()); project.setProjectedOutputs(projectVO.getProjectedOutputs()); // 服务对象处理 List<String> types = cpServeObjectMapper.queryServeNameByType(projectVO.getCpServeObjectPoList(), "SERVICEOBJECT"); if (CollUtil.isNotEmpty(types)) { project.setCpServeObjectPoListName(String.join(", ", types)); } else { project.setCpServeObjectPoListName(null); } project.setServeNum(String.valueOf(projectVO.getServeNum())); project.setFunderName(projectVO.getFunderName()); // 资金来源处理 List<String> sourceFunds = cpServeObjectMapper.queryServeNameByTypeAndValue(projectVO.getFundSource(), "SOURCEFUNDS"); if (CollUtil.isNotEmpty(sourceFunds)) { project.setFundSource(String.join(", ", sourceFunds)); } else { project.setFundSource(null); } // 项目分类处理 List<String> projectClasses = cpServeObjectMapper.queryServeNameByType(projectVO.getCpTypePoList(), "PROJECTCLASS"); if (CollUtil.isNotEmpty(projectClasses)) { project.setCpTypePoListName(String.join(", ", projectClasses)); } else { project.setCpTypePoListName(null); } project.setProjectInnovation(projectVO.getProjectInnovation()); project.setRiskMeasure(projectVO.getRiskMeasure()); List<String> filePathList01 = new ArrayList<>(); List<String> filePathList02 = new ArrayList<>(); // 下载附件到本地 if (CollUtil.isNotEmpty(projectVO.getOssIdList())) { for (AttachmentVO attachmentVO : projectVO.getOssIdList()) { GenerateOnWordTemplateKit.downloadImage(attachmentVO.getUrl(), GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName()); filePathList01.add(GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName()); } } // 下载评估报告到本地 if (CollUtil.isNotEmpty(projectVO.getAssReportList())) { for (AttachmentVO attachmentVO : projectVO.getAssReportList()) { GenerateOnWordTemplateKit.downloadImage(attachmentVO.getUrl(), GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName()); filePathList01.add(GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName()); } } project.setFilePathList01(filePathList01); // 附件 project.setFilePathList02(filePathList02); // 评估报告 // 实施计划 List<ImplementPlanPO> implementPlanPOList = planMapper.selectList(new LambdaQueryWrapper<ImplementPlanPO>() .eq(ImplementPlanPO::getEffective, "1") .eq(ImplementPlanPO::getProjectId, s)); if (CollUtil.isNotEmpty(implementPlanPOList)) { List<Plan> plans = new ArrayList<>(); for (int j = 0; j < implementPlanPOList.size(); j++) { Plan plan = new Plan(); plan.setPlanName("实施计划-" + (j + 1)); plan.setStartTime(DateUtil.format(implementPlanPOList.get(j).getStartTime(), "yyyy-MM-dd")); plan.setEndTime(DateUtil.format(implementPlanPOList.get(j).getEndTime(), "yyyy-MM-dd")); plan.setNumPeople(String.valueOf(implementPlanPOList.get(j).getNumPeople())); plan.setActivityForm(implementPlanPOList.get(j).getActivityForm()); plan.setLocation(implementPlanPOList.get(j).getLocation()); plan.setActiveFrequency(implementPlanPOList.get(j).getActiveFrequency()); plan.setActiveGoal(implementPlanPOList.get(j).getActiveGoal()); plan.setNotes(implementPlanPOList.get(j).getNotes()); plans.add(plan); } project.setPlans(plans); } // 成果公开 List<FeedBackPO> feedBackPOList = feedBackMapper.selectList(new LambdaQueryWrapper<FeedBackPO>() .eq(FeedBackPO::getEffective, "1") .eq(FeedBackPO::getProjectId, s)); if (CollUtil.isNotEmpty(feedBackPOList)) { List<Achievement> achievements = new ArrayList<>(); for (FeedBackPO feedBackPO : feedBackPOList) { Achievement achievement = new Achievement(); FeedBackVO feedBackVO = feedBackService.detail(feedBackPO.getId()); if (ValidateUtil.isEmpty(feedBackVO)) { continue; } achievement.setProjectName(feedBackVO.getProjectName()); achievement.setTitle(feedBackVO.getTitle()); // 活动类型处理 List<String> activityTypeList = cpServeObjectMapper.queryServeNameByTypeAndValue(feedBackVO.getActivityType(), "ACTIVITYTYPE"); if (CollUtil.isNotEmpty(activityTypeList)) { achievement.setActivityType(String.join(", ", activityTypeList)); } else { achievement.setActivityType(null); } achievement.setActivityTime(feedBackVO.getTime()); achievement.setAddress(feedBackVO.getAddress()); // 区划处理 List<String> areaNameByAreaCodeList = areaFieldMapper.queryAreaNameByAreaCodeList(feedBackVO.getAdmsAreaList()); if (CollUtil.isNotEmpty(areaNameByAreaCodeList)) { achievement.setAdmsAreaListName(String.join(", ", areaNameByAreaCodeList)); } else { achievement.setAdmsAreaListName(null); } achievement.setPersonNumber(feedBackVO.getPersonNumber()); achievement.setDonationAmount(feedBackVO.getDonationAmount()); achievement.setDuration(feedBackVO.getDuration()); achievement.setSatisfaction(String.valueOf(feedBackVO.getSatisfaction())); achievement.setImplementContent(feedBackVO.getImplementContent()); List<TaUserPO> taUserPOS = taUserMapper.selectList(new LambdaQueryWrapper<TaUserPO>() .eq(TaUserPO::getDestroy, "0") .eq(TaUserPO::getEffective, "1") .eq(StrUtil.isNotBlank(feedBackVO.getUpdateUser()), TaUserPO::getLoginId, feedBackVO.getUpdateUser())); if (CollUtil.isNotEmpty(taUserPOS)) { achievement.setUpdateUser(taUserPOS.get(0).getName()); } else { achievement.setUpdateUser(feedBackVO.getUpdateUser()); } achievement.setUpdateTime(DateUtil.format(feedBackVO.getUpdateTime(), "yyyy-MM-dd hh:mm:ss")); List<String> attachmentPOList = new ArrayList<>(); List<String> attachmentPOList2 = new ArrayList<>(); // 附件地址,这里可以是文件key,配合download方法能下载到本地即可 // 佐证材料 if (CollUtil.isNotEmpty(feedBackVO.getAttachmentPOList())) { for (AttachmentVO attachmentVO : feedBackVO.getAttachmentPOList()) { GenerateOnWordTemplateKit.downloadImage(attachmentVO.getUrl(), GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName()); attachmentPOList.add(GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName()); } } // 其他材料 if (CollUtil.isNotEmpty(feedBackVO.getAttachmentPOList2())) { for (AttachmentVO attachmentVO : feedBackVO.getAttachmentPOList2()) { GenerateOnWordTemplateKit.downloadImage(attachmentVO.getUrl(), GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName()); attachmentPOList2.add(GenerateOnWordTemplateKit.FILE_PATH + attachmentVO.getOriginalName()); } } achievement.setFilePathList1(attachmentPOList); achievement.setFilePathList2(attachmentPOList2); achievements.add(achievement); } project.setAchievements(achievements); } dataMap.put(project.getProjectName(), project); } return dataMap; }
5.设计模版文件

6.前端调用方式
<ta-button class="mt-10" @click="openWinExcel" type="primary" icon="to-top">
openWinExcel() {
// this.dcVisible = true
const param = {}
const idList = []
const selectRecords = this.$refs.sTable.$refs.mTable.getCheckboxRecords()
selectRecords.map(item => {
idList.push(item.id)
})
param.idList = idList
if (param.idList.length == 0) {
this.$message.warning('请选择您要导出的慈善项目!')
return
}
const id = param.idList.join(',');
window.open(faceConfig.basePath + '/collaborateProject/export?id=' + id, '_blank');
this.reTable(); // 刷新表格
},
实现结果:


4970

被折叠的 条评论
为什么被折叠?



