若依+FreeMarker实现培养方案教学计划表导出Word文件(超详细)
上篇文章分享了基于Vue3若依开源框架的专业培养方案教学计划表的前端实现,今天这篇文章分享一下如何将前端设计的教学计划表导出为Word文件。
先看看导出word的效果
而在前端,设计教学计划表的界面是这样的:
技术选型
Java实现word文件导出,可以有这些技术方案:Jacob,POI,Java2word,XML+FreeMarker,PageOffice插件,Docx4j,POI-tl等。经过分析,最终选择XML+FreeMarker的技术方案。
FreeMarker模板制作
现在word或wps软件中,根据导出的word文件格式要求设计模板,并设定好占位符,以便后续替换为数据变量。模板设计结果如下图所示。
设计好后将模板文件另存为*.xml格式,用Notepad++编辑器打开该文件,执行菜单【插件-->XML Tools-->Pretty print】,格式化xml文件,便于后续查找和增加freemarker语法。格式化后如下图所示。
将整个xml文件内容用下面的freemarker标签进行包裹,其作用是对最终导出的word文件进行压缩,也就是去掉代码的缩进和换行符,以达到减小文件体积的目的。
<@compress single_line=true>
......
是用Notepad++软件的查找功能,找到占位符“代码1”所在位置。
向上找到它的父节点
<#list courseList as item>
<#if item_index==0>
接下来,在第一行内,把相应的占位符替换成freemarker变量。具体替换如下:
占位符 | 替换为 |
代码1 | ${item.courseCode} |
课程1 | <#if item.isCore=='1'>★ <#if item.isZcrh=='1'>*${item.courseNameZh} ${item.courseNameEn} |
分1 | ${item.credits} |
总1 | ${item.classesTotal} |
讲1 | ${item.classesTheory} |
实1 | ${item.classesPractice}(${item.classesExperiment}) |
11 | <#if item.doTerm=='1'>${item.classesWeek} |
12 | <#if item.doTerm=='2'>${item.classesWeek} |
13 | <#if item.doTerm=='3'>${item.classesWeek} |
14 | <#if item.doTerm=='4'>${item.classesWeek} |
15 | <#if item.doTerm=='5'>${item.classesWeek} |
16 | <#if item.doTerm=='6'>${item.classesWeek} |
17 | <#if item.doTerm=='7'>${item.classesWeek} |
18 | <#if item.doTerm=='8'>${item.classesWeek} |
在第一个数据行之后添加如下代码,结束第一行的判断和循环标签,开始第二行循环:
<#list courseList as item>
<#if (item_index>0)>
第二行的循环体内部,占位符的替换与第一行的替换类似,不再赘述。
在第二个数据行之后添加如下代码,结束判断和循环标签:
第三个数据行是小计行,进行占位符替换如下:
分0 | ${courseSummary.credits} |
总0 | ${courseSummary.classesTotal} |
讲0 | ${courseSummary.classesTheory} |
实0 | ${courseSummary.classesPractice}(${courseSummary.classesExperiment}) |
01 | ${courseSummary.classesWeek1} |
02 | ${courseSummary.classesWeek2} |
03 | ${courseSummary.classesWeek3} |
04 | ${courseSummary.classesWeek4} |
05 | ${courseSummary.classesWeek5} |
06 | ${courseSummary.classesWeek6} |
07 | ${courseSummary.classesWeek7} |
08 | ${courseSummary.classesWeek8} |
修改好后,将XML文件copy到项目的resource文件夹下的templates下,并重命名为“plan.ftl”。
?【service实现类】
package com.ruoyi.web.service.impl;
import com.ruoyi.web.controller.common.CommonController;
import com.ruoyi.web.utils.WordUtils;
import com.ruoyi.web.service.IWordExportService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
@Service
public class WordExportServiceImpl implements IWordExportService {
private static final Logger log = LoggerFactory.getLogger(WordExportServiceImpl.class);
@Override
public void exportWord(HttpServletRequest request, HttpServletResponse response) {
//测试列表数据
List list = new ArrayList<>();
//声明一个数据Map
Map map1 = new HashMap();
map1.put("courseCode","12345678");
map1.put("courseNameZh","专业导论");
map1.put("courseNameEn","Professional introduction");
map1.put("credits","1");
map1.put("classesTotal","16");
map1.put("classesTheory","16");
map1.put("classesExperiment","1");
map1.put("classesPractice","2");
map1.put("doTerm","1");
map1.put("classesWeek","2");
map1.put("isCore","0");
map1.put("isZcrh","1");
list.add(map1);
Map map2 = new HashMap();
map2.put("courseCode","22345678");
map2.put("courseNameZh","高级语言程序设计(C)");
map2.put("courseNameEn","High Level Language Programming (C)");
map2.put("credits","4");
map2.put("classesTotal","64");
map2.put("classesTheory","44");
map2.put("classesExperiment","20");
map2.put("classesPractice","0");
map2.put("doTerm","2");
map2.put("classesWeek","3");
map2.put("isCore","1");
map2.put("isZcrh","1");
list.add(map2);
Map map3 = new HashMap();
map3.put("courseCode","32345678");
map3.put("courseNameZh","电路与模拟电子技术");
map3.put("courseNameEn","Circuit and analog electronic technology");
map3.put("credits","3.5");
map3.put("classesTotal","56");
map3.put("classesTheory","44");
map3.put("classesExperiment","12");
map3.put("classesPractice","0");
map3.put("doTerm","3");
map3.put("classesWeek","3");
map3.put("isCore","1");
map3.put("isZcrh","0");
list.add(map3);
Map map_sum = new HashMap();
map_sum.put("credits", "8.5");
map_sum.put("classesTotal", "136");
map_sum.put("classesTheory", "104");
map_sum.put("classesExperiment", "33");
map_sum.put("classesPractice", "2");
map_sum.put("classesWeek1", "2");
map_sum.put("classesWeek2", "3");
map_sum.put("classesWeek3", "3");
map_sum.put("classesWeek4", "0");
map_sum.put("classesWeek5", "0");
map_sum.put("classesWeek6", "0");
map_sum.put("classesWeek7", "0");
map_sum.put("classesWeek8", "0");
Map map = new HashMap();
map.put("courseList",list);
map.put("courseSummary", map_sum);
try {
//将数据导出
WordUtils.exportMillCertificateWord(request,response,map,"导出word测试","plan.ftl");
} catch (IOException e) {
e.printStackTrace();
}
}
}
【word工具类】
参考自网友编写的工具类
package com.ruoyi.web.utils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
* @ClassName: WordUtil
* @Description: word导出工具类
* @author ssqxx
* @version 1.0
* @date 2020-11-19
*/
public class WordUtils {
private static Configuration configuration = null;
private static final String templateFolder = WordUtils.class.getResource("/templates").getPath();
static {
configuration = new Configuration(Configuration.VERSION_2_3_31);
configuration.setDefaultEncoding("utf-8");
try {
configuration.setDirectoryForTemplateLoading(new File(templateFolder));
} catch (IOException e) {
e.printStackTrace();
}
}
private WordUtils() {
throw new AssertionError();
}
/**
* 导出word文件到浏览器
* @param request
* @param response
* @param map 内容
* @param title 标题
* @param ftlFile 模板
* @throws IOException
*/
public static void exportMillCertificateWord(HttpServletRequest request, HttpServletResponse response, Map map, String title, String ftlFile) throws IOException {
Template freemarkerTemplate = configuration.getTemplate(ftlFile);
File file = null;
InputStream fin = null;
ServletOutputStream out = null;
try {
// 调用工具类的createDoc方法生成Word文档
file = createDoc(map, freemarkerTemplate);
fin = new FileInputStream(file);
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
//获取指定格式时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 设置浏览器以下载的方式处理该文件名
String fileName = title + sdf.format(new Date()) + ".doc";
//文件名称乱码解决
String encodedFileName = fileName +";filename*=utf-8''"+ URLEncoder.encode(fileName,"UTF-8");
response.setHeader("Content-Disposition", "attachment;filename="+encodedFileName+"");
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
out = response.getOutputStream();
byte[] buffer = new byte[512]; // 缓冲区
int bytesToRead = -1;
// 通过循环将读入的Word文件的内容输出到浏览器中
while ((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} finally {
if (fin != null) fin.close();
if (out != null) out.close();
if (file != null) file.delete(); // 删除临时文件
}
}
/**
* 创建的doc
* @param dataMap 数据内容
* @param template 模板
* @return
*/
private static File createDoc(Map dataMap, Template template) {
String name = "ssqxx.doc";
File f = new File(name);
Template t = template;
try {
// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
t.process(dataMap, w);
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f;
}
}
【service】
package com.ruoyi.web.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface IWordExportService {
void exportWord(HttpServletRequest request, HttpServletResponse response);
}
【controller】
package com.ruoyi.web.controller.common;
import com.ruoyi.web.service.IWordExportService;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("exportWord")
@Api(tags = "Word导出控制器")
public class WordController {
@Autowired
private IWordExportService wordExportService;
/**
* 导出word方法
* @param request
* @param response
*/
@PostMapping("export")
public void exportWord(HttpServletRequest request, HttpServletResponse response){
wordExportService.exportWord(request,response);
}
}
【vue文件】
添加按钮,按钮的click事件处理函数如下。
function of handleExport() {
proxy.download("exportWord/export", {
...queryParams.value,
},`plan_${new Date().getTime()}.docx`);
};