最近系统中需要导出监测报告书而且是word版的,但是这个过程中遇到不小的挫折,但是结果还是挺令人满意的!其中问题以及解决方法都有说明!
问题1:vue使用axios发送请求获取到文件流为乱码的问题?
问题2:前端如何向后端发送请求才是正确的?
问题3:如何动态修改模板以及自动填充word内容?
问题4:后端如何向前端发送需要的数据信息,以及需要注意什么?
问题5:远程服务器部署文件路径获取不到的问题?
解答问题1、2:不管系统框架是用什么技术框架,大家都知道下载文件是不能使用ajax发送get或者post请求的(提交表单可以下载),如果发送后使用then回调的话都是字符流(json格式的),因此axios其实解决了这个问题比如:
一般的axios发送post请求
export const POST = (url, params) => {
return axios.post(`${base}${url}`, params).then(res => res.data)
}
下载文件调用请求
export const DOWN = (url, parameter) =>{
return axios({
url:`${base}${url}`,
params:parameter,
method: 'get',
responseType: 'blob',
noErrorMsg:true,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).then(res => res.data)}
这两种请求发送的方式都不一样所以要使用他自己封装的方法来进行文件下载
然后前端处理的东西都是和网上一样的操作
API.downLoadWord("url", parm).then(res =>{
let blob = new Blob([res], {type: `application/msword`});
let objectUrl = URL.createObjectURL(blob);
let link = document.createElement("a");
link.href = objectUrl;
link.setAttribute("download", "文件名称");
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(link.href)
})
使用这样的方法前端就不会出现一堆乱码的问题,他只会有一个blob对象打印出来
console.log(res)
解答问题3:后端我想大家都能对数据处理生成word都已完成,这里是我自己写的,虽然很low但是能用,
package com.qwsy.query.utils;
import com.qwsy.query.entity.vo.result.*;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
public class WordUtil {
/**
* 替换 表格中的
*
* @param document docx解析对象
* @param newData 需要替换的信息集合
*/
//统计表单替换
public static void changeParagraphTableShiLi(XWPFDocument document, List<JianCeShiLiVo> newData, String destPath, String fileName) throws IOException, XmlException {
//获取表单个数(必要的时候传入第几个表单)
List<XWPFTable> tables = document.getTables();
//word中的第几个表单 0:表示第一个后面一次增加
XWPFTable xwpfTable = tables.get(0);
//表单样式设置
CTTblBorders borders = xwpfTable.getCTTbl().getTblPr().addNewTblBorders();
CTBorder hBorder = borders.addNewInsideH();
hBorder.setVal(STBorder.Enum.forString("single")); // 线条类型
hBorder.setSz(new BigInteger("1")); // 线条大小
hBorder.setColor("000000"); // 设置颜色
//获取表格行数
List<XWPFTableRow> rows = xwpfTable.getRows();
//有多少数据就添加多少行
for (int i = 0; i < newData.size() ; i++) {
XWPFTableRow row = xwpfTable.createRow();
row.setHeight(500);
}
//这里直接在模板中确定好列数不用在去动态增加
//动态赋值并且设置样式(每一个单元格设置一次样式)
for (int i = 1; i < rows.size(); i++) {
XWPFTableCell cell = rows.get(i).getTableCells().get(0);
XWPFTableCell cell1 = rows.get(i).getTableCells().get(1);
XWPFTableCell cell2 = rows.get(i).getTableCells().get(2);
XWPFTableCell cell3 = rows.get(i).getTableCells().get(3);
XWPFTableCell cell4 = rows.get(i).getTableCells().get(4);
CTTc cttc = cell.getCTTc();
CTTc cttc1 = cell1.getCTTc();
CTTc cttc2 = cell2.getCTTc();
CTTc cttc3 = cell3.getCTTc();
CTTc cttc4 = cell4.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
CTTcPr ctPr2 = cttc2.addNewTcPr();
CTTcPr ctPr3 = cttc3.addNewTcPr();
CTTcPr ctPr1 = cttc1.addNewTcPr();
CTTcPr ctPr4 = cttc4.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
ctPr1.addNewVAlign().setVal(STVerticalJc.CENTER);
ctPr2.addNewVAlign().setVal(STVerticalJc.CENTER);
ctPr3.addNewVAlign().setVal(STVerticalJc.CENTER);
ctPr4.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
cttc1.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
cttc2.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
cttc3.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
cttc3.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
cttc4.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
rows.get(i).getTableCells().get(0).setText(newData.get(i-1).getDepartName());
rows.get(i).getTableCells().get(1).setText((newData.get(i-1).getSex()==null?"":(newData.get(i-1).getSex()==1?"男":"女")));
rows.get(i).getTableCells().get(2).setText(newData.get(i-1).getDiaoChaRen()+"");
rows.get(i).getTableCells().get(3).setText(newData.get(i-1).getBuLiangRen()+"");
rows.get(i).getTableCells().get(4).setText(newData.get(i-1).getJianChuLv()+"%");
}
File f = new File(destPath);
if (!f.exists()) {
boolean mk = f.mkdirs();
}
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(destPath + "/" + fileName);
document.write(outStream);
} catch (RuntimeException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outStream != null) {
outStream.close();
}
}
}
}
效果截图
只需要在word中设置一个表头就行了,自动增加会和表头的列数一样,然后数据填入就行。
缺点:word中有多个表格的话要传入多个对象集合要写很多个这样的方法(一个对象集合要对应一个这样的方法),可复用性太低。
这是关于表格的,文字内容替换的话需要写一个方法如下:
public static void changeParagraph(XWPFDocument document, Map<String, String> textMap, String destPath, String fileName) throws IOException, XmlException {
//获取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
String text = run.getText(0);
//判断文本是否需要进行替换
if (checkText(text)) {
for (Map.Entry<String, String> entry : textMap.entrySet()) {
//匹配模板与替换值 格式${key}
String key = "${" + entry.getKey() + "}";
Object value = entry.getValue();
if (text.contains(key)) {
if (value instanceof String) { //文字替换
text = text.replace(key, (String) value);
}
} else {
text = text.replace(key, "");
}
}
//替换模板原来位置
run.setText(text, 0);
}
}
}
File f = new File(destPath);
if (!f.exists()) {
boolean mk = f.mkdirs();
System.out.println(mk);
}
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(destPath + "/" + fileName);
document.write(outStream);
} catch (RuntimeException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outStream != null) {
outStream.close();
}
}
}
这个方法是与上面表格的方法写在同一个工具类里面。
模板要求
传值要求
Map<String,String>contentMap = new HashMap<>();
contentMap.put("c1","内容");
contentMap.put("c2","内容");
contentMap.put("c3","内容");
最后将文件流输出到response传给前端就行
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
document.write(outputStream);
IoUtil.close(outputStream);
IoUtil.close(document);
解答问题4:
注意事项:无需返回值这里不需要return一个值给前端,前端会获取到请求头信息然后前端下载即可!(防止前端获取内容混乱)
解答问题5:
由于写的文件都是基于项目的相对路径,所以说如果达成jar包的话会出现模板找不到的情况,这里我解决的思路是:既然jar包内部不能访问模板路径那么我将模板路径放在服务器上这样直接在服务器上操作模板文件
服务器文件路径,这样就能够解决!,因为前端是使用的response文件流所以不用去远程传输文件!
附加:
如果想在服务服务其上面下载文件可使用a标签,但是nginx要配置路径让a标签的href能够访问到该文件夹里面的文件,而且在项目里面的删除文件操作是要去掉的,不然获取不到文件,而且前端需要将文件名称(防止文件冲突生成的UUID文件名)传送给前端,然后a标签路径+文件名就能够访问服务器上的文件地址然后下载,也能够实现动态下载的工能!