在最近的工作中,遇到了一个这样的需求,就是用户要求在导出数据时,用户可以选择一条或者多条数据,并且连带着附件及其他信息同时导出到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(); // 刷新表格
},
实现结果: