筆者後端使用的是Java、前端使用的是Vuejs,在一個先後端分離的環境下導出一個Word文檔,要求導出vue-charts中的折線圖,最終的效果:html
//後端接口是受保護的,須要在發送請求的時候在請求頭上加上一個token,讀者能夠忽略這一塊內容:getToken以及下面的headers部分,除了我說的地方,其餘都是相對固定的
import {getToken} from '$root/libs/auth'
//這裏注意一下,不少vue的後臺中,都會用到一些封裝好的axios,例如添加一些攔截器什麼的,這裏我建議你別用封裝好的,由於這和普通的接口是有區別的,具體什麼區別筆者就不細說了。
import axios from 'axios'
//收入導出
/** * * @param images 圖片數組(base64的字節) * @param query 這個是和後端進行交互的一些參數,包括時間的一些參數 * @returns {AxiosPromise} */
export function exportIncomeStatisticsWord(images, query) {
const token = getToken();
return axios({
headers:{'Authorization':'Bearer'+token},
params:query,
data:images,
url: 'admin/consumption/info/amount/word',
//必定要是post請求,get請求的參數大小是有限制的
method: 'post',
//必定要是blob
responseType: 'blob',
})
}
複製代碼
這裏筆者只將一些相關的代碼貼上來,我這裏使用的是
vue-echarts
,若是你使用的是什麼其餘什麼的也是能夠根據這樣的方式來實現功能的,及時你使用的不是vue,而是react那些都是沒問題的前端
這裏的話使用了vue-echarts
提供了組件,我在這分別給他們設置了一個id,分別是pie一、pie二、line,由於等下要操做dom節點vue
<el-row>
<!-- 拼圖1(方案收入與增值服務的收入的佔比) -->
<el-col :span="12">
<ve-pie id="pie1" :data="pieoneData" v-bind="pinoneSetting"></ve-pie>
</el-col>
<!-- 拼圖2(消費用戶與未消費用戶的佔比) -->
<el-col id="pie2" :span="12">
<ve-pie :data="pietowData" v-bind="pinoneSetting"></ve-pie>
</el-col>
</el-row>
<el-row>
<!-- 折線圖 -->
<el-col :span="24">
<div style="width:94%;margin:auto;margin-top:10px">
<ve-line id="line" :data="lineData" :settings="chartSettings" v-bind="pubSetting"></ve-line>
</div>
</el-col>
</el-row>
複製代碼
js部分相關代碼java
//將前面的exportincomeStatisticsWord方法導入進來
import {exportIncomeStatisticsWord} from '$root/api/xxx';
export default {
methods:{
//這裏我就不去把導出按鈕寫上去了,你記得把這個方法綁定到按鈕上
exportWord(){
//來講一下這一段代碼,首先咱們要獲取到這3個dom節點,而後再獲取到這3個dom節點中的子節點 canvas
let pie1=(document.getElementById('pie1')).getElementsByTagName('canvas')[0];
let pie2=(document.getElementById('pie2')).getElementsByTagName('canvas')[0];
let line=(document.getElementById('line')).getElementsByTagName('canvas')[0];
//獲取了canvas以後,咱們把它們用這種方式轉換爲base64的字節
let pie1Image=(pie1.toDataURL('image/png')).split(',')[1];
let pie2Image=pie2.toDataURL('image/png').split(',')[1];
let lineImage= line.toDataURL('image/png').split(',')[1];
//後端接口要求這些base64爲一個數組
let array=[pie1Image,pie2Image,lineImage];
//分割--------------------
//exportincomeStatisticsWord方法的query參數,這個不是重點
let toData={
....
}
//這裏的then代碼很固定,筆者能夠直接拿去使用,具體的代碼我就很少說了,相信你能看明白
exportincomeStatisticsWord(array,toData).then(response=>{
var url = window.URL.createObjectURL(new Blob([response.data]));
var link = document.createElement('a');
link.href = url;
link.setAttribute('download', '文件名.doc');
document.body.appendChild(link);
link.click();
})
}
},
}
複製代碼
到這裏前端代碼就寫完了react
這裏筆者使用的是spring boot,導入的相關依賴爲:ios
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
複製代碼
若是你沒有使用spring boot,可使用這樣的依賴,而不是上面這種git
這裏說一下,若是你用spring,我不保證你能成功,由於我沒去試過github
<dependency>
<groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>
複製代碼
URLBase64Utilsspring
負責對前端傳遞過來的base64作轉義,這裏筆者沒有太好的辦法對這些特殊字符作轉義,只能用這種笨一點的辦法,若是你有好的辦法請務必通知我(試過不少辦法了)!!!canvas
public class URLBase64Utils {
public static String urlBase64toEscape(String data) {
data = data.replaceAll("%2B", "+");
data = data.replaceAll("%20", " ");
data = data.replaceAll("%2F", "/");
data = data.replaceAll("%3F", "?");
data = data.replaceAll("%25", "%");
data = data.replaceAll("%26", "&");
data = data.replaceAll("%3D", "=");
data = data.replaceAll("%23", "#");
return data;
}
}
複製代碼
FreeMarkerUtil
導出工具類,這裏代碼很固定,你什麼都不用動
import ....
public class FreeMarkerUtil {
private static Logger log = LoggerFactory.getLogger(FreeMarkerUtil.class);
private static final String ENCODING = "UTF-8";
private static Configuration cfg = new Configuration();
//初始化cfg
static {
//設置模板所在文件夾
cfg.setClassForTemplateLoading(FreeMarkerUtil.class, "/word");
// setEncoding這個方法必定要設置國家及其編碼,否則在ftl中的中文在生成html後會變成亂碼
cfg.setEncoding(Locale.getDefault(), ENCODING);
// 設置對象的包裝器
cfg.setObjectWrapper(new DefaultObjectWrapper());
// 設置異常處理器,這樣的話就能夠${a.b.c.d}即便沒有屬性也不會出錯
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
}
//獲取模板對象
public static Template getTemplate(String templateFileName) throws IOException {
return cfg.getTemplate(templateFileName, ENCODING);
}
/** * 據數據及模板生成文件 * @param data Map的數據結果集 * @param templateFileName ftl模版文件名 */
public static File crateFile(Map<String, Object> data, String templateFileName) {
String name = "temp" + (int) (Math.random() * 100000) + ".doc";
Writer out = null;
File outFile = new File(name);
try {
// 獲取模板,並設置編碼方式,這個編碼必需要與頁面中的編碼格式一致
Template template = getTemplate(templateFileName);
out = new OutputStreamWriter(new FileOutputStream(outFile), ENCODING);
// 處理模版
template.process(data, out);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
log.error("關閉Write對象出錯", e);
e.printStackTrace();
}
}
return outFile;
}
public static void outDownload(Map<String, Object> data, String templateFileName,HttpServletResponse response){
File file = crateFile(data, templateFileName);
out(response, file);
}
public static void out(HttpServletResponse response, File file) {
try {
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
ServletOutputStream out = response.getOutputStream();
byte[] buffer = new byte[512]; // 緩衝區
int bytesToRead;
InputStream fin = new FileInputStream(file);
// 經過循環將讀入的Word文件的內容輸出到瀏覽器中
while ((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} catch (IOException e) {
e.printStackTrace();
throw new ZcException("文件輸出失敗");
}
}
}
複製代碼
Controller
@PostMapping("/info/amount/word")
public void wordInfoConsumptionByTime(HttpServletResponse response, @RequestBody String [] images, String startTime, String endTime){
List<ConsumptionVO> dataList = consumptionService.selectConsumptionByTime(startTime, endTime).getDateStrList();
//你能夠將這個data想成是一個Model對象
Map<String, Object> data = new HashMap<>();
data.put("dataList", dataList);
for (int i = 0; i < images.length; i++) {
data.put("image"+(i+1), URLBase64Utils.urlBase64toEscape(images[i]));
}
//使用income-statistics.ftl模板
FreeMarkerUtil.outDownload(data, "income-statistics.ftl",response);
}
複製代碼
income-statistics.ftl要怎麼寫?這裏你要先這樣作:
首先新建一個word文檔,而後根據你要定製的樣子去寫一些固定的東西,例如我這裏是這樣的:
筆者使用的word工具是wps,在左上方有一個文件按鈕,文件->另存爲->其餘格式,選擇xml格式保存
把它放到你java工程中,我這裏使用的是maven,因此我是放在src\main\resources\word
中的,而後這裏將它的xml後綴改成
ftl
而後咱們用IDE工具打開它,用格式化快捷鍵帶它裏面的代碼進行整理,而後經過搜索,找到
<w:tbl>
標籤,這裏就考研你的眼裏的時候來了,找到重複的結構,把多餘的部分刪除,只留一層作遍歷。
下面的代碼你不要認爲不少,我只是改了一下它裏面的內容而已,別的你都不用動。像這些表達式
${item.time}
、<#list dataList as item>
,好比說你對某某行作跨列跨行能夠經過if表達式來實現,若是你不懂得話,能夠去查看freemarker的相關文檔,若是實在是不會,也能夠來問筆者
表格相關的
<#list dataList as item>
<w:tr>
<w:tblPrEx>
<w:tblBorders>
<w:top w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:left w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:bottom w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:right w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:insideH w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
<w:insideV w:val="single" w:sz="4" wx:bdrwidth="10" w:space="0" w:color="auto"/>
</w:tblBorders>
<w:tblCellMar>
<w:top w:w="0" w:type="dxa"/>
<w:left w:w="108" w:type="dxa"/>
<w:bottom w:w="0" w:type="dxa"/>
<w:right w:w="108" w:type="dxa"/>
</w:tblCellMar>
</w:tblPrEx>
<w:trPr>
<w:jc w:val="center"/>
</w:trPr>
<#--時間-->
<w:tc>
<w:tcPr>
<w:tcW w:w="2508" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="default"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${item.time}</w:t>
</w:r>
</w:p>
</w:tc>
<#--方案新增數量(個)-->
<w:tc>
<w:tcPr>
<w:tcW w:w="2911" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="default"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${item.planBuyCount}</w:t>
</w:r>
</w:p>
</w:tc>
<#--方案新增總金額(元)-->
<w:tc>
<w:tcPr>
<w:tcW w:w="2577" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="default"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${item.planBuySum}</w:t>
</w:r>
</w:p>
</w:tc>
<#--方案續費數量(個)-->
<w:tc>
<w:tcPr>
<w:tcW w:w="2577" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="default"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${item.planRenewCount}</w:t>
</w:r>
</w:p>
</w:tc>
<#--方案續費總金額(元)-->
<w:tc>
<w:tcPr>
<w:tcW w:w="2577" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="default"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${item.planRenewSum}</w:t>
</w:r>
</w:p>
</w:tc>
<#--新增服務購買數量(個)-->
<w:tc>
<w:tcPr>
<w:tcW w:w="2577" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="default"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${item.serviceCount}</w:t>
</w:r>
</w:p>
</w:tc>
<#--新增服務購買總金額(元)-->
<w:tc>
<w:tcPr>
<w:tcW w:w="2577" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="default"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${item.serviceSum}</w:t>
</w:r>
</w:p>
</w:tc>
<#--總收入(元)-->
<w:tc>
<w:tcPr>
<w:tcW w:w="2577" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="auto"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:rFonts w:hint="default"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<w:vertAlign w:val="baseline"/>
<w:lang w:val="EN-US" w:fareast="ZH-CN"/>
</w:rPr>
<w:t>${item.sumAmount}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</#list>
複製代碼
圖片相關的
這裏你直接使用IDE的搜索內容:
1.png
,將w:binData
標籤中的內容所有刪除,換成${image1}
便可
<w:binData w:name="wordml://1.png">
${image1}
</w:binData>
複製代碼
其餘2張圖片搜索2.png
、3.png
換成對應的 ${image2}
、 ${image3}
就能夠了
到這裏代碼就所有寫完了,這裏筆者在後來發現了一個後端作導出不錯的輪子:POI-TL,感興趣能夠去試一試,應該會比這種方式簡單不少