0.主要目的:将这样一个页面导出为word文档为doc格式,包含一些文本和循环遍历出来的echarts图表。
1. 目录结构如下:主要文件countAnalysed.xml(命名随意)为生成doc的内容模板文件
2.先看一下前端请求方式,传过来json格式数据,请求两次服务器,第一次生成doc文件,第二次读取文件写入response输出流,实现下载。
function download_report() {
//此处遍历页面得数据,放到json中,可根据自己需要省略-----------------start
var title= '${reportData.title}';
var reportUnit= '${reportData.reportUnit!}';
var reportTypeDate= '${reportData.reportTypeDate!}';
var json;
var jsonHead = {"title": title, "reportUnit": reportUnit,"reportTypeDate":reportTypeDate};
//此处用到了freemarker的模板遍历数据
<#list reportData.reportModels as model>
var modelTitle_${model ? index}= '${model.title!}';
var modelDataSource_${model ? index}= '${model.dataSource!}';
var modelShowContent_${model ? index}= '${model.showContent!}';
var pic_${model ? index}=model_${model ? index}.chart.getConnectedDataURL();
var model_pic_${model ? index}=pic_${model ? index}.substr(22,pic_${model ? index}.length);
var jsonBody = {"model_${model ? index}":{
"modelTitle": modelTitle_${model ? index},
"modelDataSource": modelDataSource_${model ? index},
"modelShowContent":modelShowContent_${model ? index},
"model_pic":model_pic_${model ? index}
}};
//最后的json对象
json=$.extend(true,jsonHead,jsonBody);
</#list>
//此处遍历页面得数据,放到json中,可根据自己需要省略-----------------end
$.ajax({
//第一次请求生成doc临时文件
url: '${base}/report/reportView/reportExport.do',
method: 'POST',
contentType: 'application/json;charset=utf-8',
data: JSON.stringify(json),
success: function (data) {
if (data.status == 0) {
var encode_name = encodeURIComponent("大数据报告");
//第二次请求读取文件写入response输出流,实现下载。
window.location.href = '${base}/report/reportView/reportExportLast.do'+ "?filepath=" + data.retinfo + "&name=" + encode_name;
} else {
alert("下载word失败!");
}
console.log("success");
},
error: function (data) {
alert('文件下载失败' + data);
}
})
}
3.第一次请求生成doc临时文件:word导出为doc格式的后台controller类(此文章只能带出doc格式,不能导出docx格式,注释中代码根据自己需要修改)
/**
* @param
* @Description 报表导出doc
* @Date 2019/11/13 11:53
* @Param map 填入模板的数据
* @Author
*/
@PostMapping("/reportExport")
public void reportExport(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String,Object> map) throws IOException {
//从前端传过来的map中重新构建数据,用于填充到模板中(根据自己需要构建Map) ------start
Map<String, Object> report = new HashMap<>(16);
Map<String, Object> model;
List<Map<String, Object>> modelList = new ArrayList<>(16);
report.put("title", map.get("title"));
report.put("reportUnit", map.get("reportUnit"));
report.put("reportTypeDate", map.get("reportTypeDate"));
//模块数量
int modelNum = map.size() - report.size();
//model_pic为存储前端传过来的echarts图片的Base64字符串
for (int i = 0; i < modelNum; i++) {
model = (Map<String, Object>) map.get("model_" + i);
modelList.add(model);
}
report.put("modelList", modelList);
//从前端传过来的map中重新构建数据,用于填充到模板中(根据自己需要构建Map) ------end
File file = null;
try {
//关键方法,传入模板名称和map数据 转为doc文件
file = ResumeWord.createDoc(report, "countAnalysed");
String text=file.getName();
//生成的文件名放到response中返回(此处根据自己需要可直接返回text) ---------start
PrintWriter out = null;
try {
response.setContentType("application/json;charset=UTF-8");
out = response.getWriter();
out.write(text);
} catch (IOException var9) {
LOGGER.error(var9.getMessage(), var9);
} finally {
if (out != null) {
out.print("");
out.close();
}
}
//生成的文件名放到response中返回(此处根据自己需要可直接返回text) ---------end
} catch (Exception e) {
e.printStackTrace();
}
}
4.生成doc的工具类createDoc方法:
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.log4j.Logger;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @Author:
* @Date: 2019/11/13 11:08
* @description:报表导出
*/
public class ResumeWord {
private static Logger log = Logger.getLogger(ResumeWord.class);
private static Configuration configuration = null;
private static Map<String, Template> allTemplate = null;
static{
configuration = new Configuration(Configuration.VERSION_2_3_0);
configuration.setDefaultEncoding("UTF-8");
configuration.setClassForTemplateLoading(ResumeWord.class, "/template");
allTemplate = new HashMap<String, Template>(16);
try{
//获取模板,名称为countAnalysed.xml,在项目的/resources/template目录下
allTemplate.put("countAnalysed",configuration.getTemplate("countAnalysed.xml"));
}catch(IOException e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
private ResumeWord(){
}
public static File createDoc(Map<?,?> dataMap, String type){
//临时文件名
String name = "temp"+(int)(Math.random()*100000)+".doc";
File f = new File(name);
Template t = allTemplate.get(type);
try{
//这个地方不能使用FileWriter因为需要指定编码类型否则生成的word文档会因为有无法识别的编码而无法打开
Writer w = new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8);
t.process(dataMap,w);
w.close();
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
return f;
}
}
5.第二次请求读取文件写入response输出流,实现下载。:将文件输出到response中,浏览器实现下载
/**
* @param
* @Description 报表导出
* @Date 2019/11/13 11:53
* @Author
*/
@RequestMapping("/reportExportLast")
public void reportExportLast(HttpServletRequest request, HttpServletResponse response) throws IOException {
//前端传过来的文件路径
String templateName = request.getParameter("filepath");
if (templateName.isEmpty()) {
templateName = "report" + System.currentTimeMillis();
}
File file = new File(templateName);
try (InputStream inputStream = new FileInputStream(file);
ServletOutputStream out = response.getOutputStream()) {
//前端传过来的用户下载后文件的文件名
String name = new String(request.getParameter("name").getBytes("GB2312"), "ISO8859-1");
response.setContentType("application/msword");
response.setHeader("Content-Disposition", "attachment;filename=" + name + ".doc");
byte[] buffer = new byte[1024];
int bytesToRead;
//写入输出流中
while ((bytesToRead = inputStream.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//删除临时文件
file.delete();
}
}
freemarker遍历map集合,循环插入文字和图片(Base64字符串),大致看一下就好。
<w:p wsp:rsidR="00187954" wsp:rsidRPr="00EB273B" wsp:rsidRDefault="005A5D42" wsp:rsidP="00EB273B">
<w:pPr>
<w:pStyle w:val="a3"/>
<w:spacing w:before="0" w:before-autospacing="off" w:after="0" w:after-autospacing="off"
w:line="360" w:line-rule="auto"/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="Arial" w:h-ansi="Arial" w:cs="Arial"/>
<wx:font wx:val="Arial"/>
<w:b/>
<w:b-cs/>
<w:color w:val="333333"/>
<w:sz w:val="36"/>
<w:sz-cs w:val="36"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="00EB273B">
<w:rPr>
<w:rFonts w:ascii="Arial" w:h-ansi="Arial" w:cs="Arial"/>
<w:b/>
<w:b-cs/>
<w:color w:val="333333"/>
<w:sz w:val="36"/>
<w:sz-cs w:val="36"/>
</w:rPr>
<w:t>${title!}</w:t>
</w:r>
</w:p>
<w:p wsp:rsidR="005A5D42" wsp:rsidRDefault="005A5D42" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="005A5D42">
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="28"/>
</w:rPr>
<w:t>${reportUnit!}</w:t>
</w:r>
</w:p>
<w:p wsp:rsidR="005A5D42" wsp:rsidRDefault="005A5D42" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="21"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="005A5D42">
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="21"/>
</w:rPr>
<w:t>${reportTypeDate!}</w:t>
</w:r>
</w:p>
<!-- 遍历模块List -->
<#if modelList ??>
<#list modelList as mdl>
<w:p wsp:rsidR="00EB273B" wsp:rsidRDefault="00EB273B" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="28"/>
</w:rPr>
</w:pPr>
</w:p>
<!-- 模块标题 -->
<w:p wsp:rsidR="005A5D42" wsp:rsidRDefault="005A5D42" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<w:b/>
<w:b-cs/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="28"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="005A5D42">
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<w:b/>
<w:b-cs/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="28"/>
</w:rPr>
<w:t>${mdl.modelTitle!}</w:t>
</w:r>
</w:p>
<!-- 模块数据来源-->
<w:p wsp:rsidR="005A5D42" wsp:rsidRDefault="005A5D42" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="24"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="005A5D42">
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="24"/>
</w:rPr>
<w:t>${mdl.modelDataSource!}</w:t>
</w:r>
</w:p>
<!-- 模块展示内容-->
<w:p wsp:rsidR="005A5D42" wsp:rsidRDefault="005A5D42" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="left"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="21"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="005A5D42">
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="21"/>
</w:rPr>
<w:t>${mdl.modelShowContent!}</w:t>
</w:r>
</w:p>
<!-- 空行 -->
<w:p wsp:rsidR="00EB273B" wsp:rsidRDefault="00EB273B" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="21"/>
</w:rPr>
</w:pPr>
</w:p>
<!-- 趋势图 -->
<w:p wsp:rsidR="00EB273B" wsp:rsidRDefault="003E3926" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="left"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="21"/>
</w:rPr>
</w:pPr>
<w:r wsp:rsidRPr="00E21A12">
<w:rPr>
<w:noProof/>
</w:rPr>
<w:pict>
<w:binData w:name="wordml://0200000${mdl_index+1}.png"
xml:space="preserve">${mdl.model_pic!}</w:binData>
<v:shape id="图片 1" o:spid="_x0000_i1025" type="#_x0000_t75"
style="width:415.8pt;height:200pt;visibility:visible;mso-wrap-style:square">
<v:imagedata src="wordml://0200000${mdl_index+1}.png" o:title=""/>
</v:shape>
</w:pict>
</w:r>
</w:p>
<!-- 空行 -->
<w:p wsp:rsidR="00EB273B" wsp:rsidRDefault="00EB273B" wsp:rsidP="00EB273B">
<w:pPr>
<w:widowControl/>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:ascii="宋体" w:fareast="宋体" w:h-ansi="宋体" w:cs="Arial"/>
<wx:font wx:val="宋体"/>
<w:color w:val="333333"/>
<w:kern w:val="0"/>
<w:sz-cs w:val="21"/>
</w:rPr>
</w:pPr>
</w:p>
<!-- List结束-->
<!-- 空行 -->
</#list>
</#if>
7.导出样式为:文字内容和多张图片