公共Hooks封裝報(bào)表導(dǎo)出useExportExcel實(shí)現(xiàn)詳解
寫(xiě)在前面
對(duì)于經(jīng)常需要開(kāi)發(fā)企業(yè)管理后臺(tái)的前端開(kāi)發(fā)來(lái)說(shuō),必不可少的需要使用表格對(duì)于數(shù)據(jù)進(jìn)行操作,在對(duì)于現(xiàn)有項(xiàng)目進(jìn)行代碼優(yōu)化時(shí),封裝一些公共的Hooks.
本篇文章為useExportExcel.js
基于個(gè)人項(xiàng)目環(huán)境進(jìn)行封裝的Hooks,僅以本文介紹封裝Hooks思想心得,故相關(guān)代碼可能不適用他人
項(xiàng)目環(huán)境
Vue3.x + Ant Design Vue3.x + Vite3.x
對(duì)于企業(yè)管理后臺(tái)最大的作用來(lái)說(shuō),用以管理企業(yè)內(nèi)各種數(shù)據(jù)狀況,同時(shí),基于實(shí)際業(yè)務(wù)過(guò)程中,客戶每逢年終(中)時(shí)都有大型匯報(bào)的需求,因此,數(shù)據(jù)報(bào)表形式的文檔產(chǎn)出必不可少,本文則基于該常見(jiàn)需求場(chǎng)景進(jìn)行封裝的Hooks -- 導(dǎo)出數(shù)據(jù)報(bào)表
封裝思考:報(bào)表數(shù)據(jù)來(lái)源
- 后端接口返回?cái)?shù)據(jù)
后端返回二進(jìn)制Blob文件,前端利用Blob進(jìn)行下載,即參考系列文章公共Hooks封裝之文件下載useDownloadFile的方式。
- 前端導(dǎo)出界面數(shù)據(jù)
前端導(dǎo)出界面數(shù)據(jù)的方式在企業(yè)管理后臺(tái)中占比相對(duì)較少,一般用以數(shù)據(jù)量較少的特殊情況,以自己項(xiàng)目舉例則是 用戶在導(dǎo)入Excel時(shí)部分?jǐn)?shù)據(jù)失敗后,展示的失敗數(shù)據(jù)報(bào)表及失敗原因統(tǒng)計(jì)的表格,使用前端導(dǎo)出數(shù)據(jù)的方式進(jìn)行導(dǎo)出。
封裝分解:前端生成報(bào)表
接收options配置對(duì)象,包括data(源數(shù)據(jù))、key(用來(lái)生成表格的行數(shù)據(jù)唯一標(biāo)識(shí))、title(表格標(biāo)題)、fileName(導(dǎo)出文件名稱)
// 通過(guò)數(shù)組數(shù)據(jù)前端導(dǎo)出excel
const exportByArray = options => {
if (!options.data || !options.key || !options.title || !options.fileName)
return new Error('缺少必需參數(shù)');
const arr = options.data.map(v =>
options.key.map(j => {
return v[j];
}),
);
arr.unshift(options.title);
const ws = utils.aoa_to_sheet(arr);
const colWidth = arr.map(row =>
row.map(val => {
if (val == null) {
return { wch: 10 };
} else if (val.toString().charCodeAt(0) > 255) {
return { wch: val.toString().length * 2 };
} else {
return { wch: val.toString().length };
}
}),
);
const result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch'];
}
}
}
ws['!cols'] = result;
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, options.fileName);
writeFile(wb, options.fileName + '.xlsx');
};
前端生成報(bào)表方法Sheet.js
前端生成報(bào)表方法中用到的utils.aoa_to_sheet,utils.book_new,utils.book_append_sheet,writeFile,都來(lái)源于 SheetJS。
Step1: 項(xiàng)目安裝依賴yarn add xlsx
Step2: 在Hooks文件中引入 import { utils, writeFile } from 'xlsx'
Step3: 參考官方API,完善Hooks中前端導(dǎo)出方法 SheetJS - Utility Functions
- utils.aoa_to_sheet
將一個(gè)二維數(shù)組轉(zhuǎn)成sheet,會(huì)自動(dòng)處理number、string、boolean、boolean、date 等類(lèi)型數(shù)據(jù)
- utils.table_to_sheet
將一個(gè)table的dom直接轉(zhuǎn)成sheet,會(huì)自動(dòng)識(shí)別colspan和rowspan并將其轉(zhuǎn)成對(duì)應(yīng)的單元格合并
- utils.json_to_sheet
將一個(gè)由對(duì)象key-value組成的數(shù)組轉(zhuǎn)成sheet,可以設(shè)置header
這三種方法都是SheetJS的導(dǎo)出方法,存在差異,考慮實(shí)際數(shù)據(jù),最后選擇的是utils.aoa_to_sheet,其余方法可以在官方文檔中找到對(duì)應(yīng)的示例
以上是一個(gè)完整的導(dǎo)出報(bào)表流程utils.book_new => 創(chuàng)建一個(gè)工作簿 utils.aoa_to_sheet => 源數(shù)據(jù)轉(zhuǎn)成工作表 utils.book_append_sheet => 將工作表插入到工作簿中 writeFile => 調(diào)用下載
封裝分解:后端接口返回?cái)?shù)據(jù)導(dǎo)出優(yōu)化
因?yàn)樾枰?qǐng)求后端接口導(dǎo)出,即下載返回的二進(jìn)制文件,依舊考慮用戶體驗(yàn)設(shè)計(jì),增加二次確認(rèn)彈窗,并從store里拿接口必須的token
// 打開(kāi)導(dǎo)出文件確認(rèn)彈窗
const exportByResBlob = options => {
Modal.confirm({
title: options.title ? options.title : '導(dǎo)出確認(rèn)',
content: options.content ? options.content : '確認(rèn)導(dǎo)出報(bào)表嗎?',
onOk() {
downloadFile(options);
return Promise.resolve();
},
});
};
useExportExcel.js完整代碼
import { onBeforeUnmount } from 'vue';
import { utils, writeFile } from 'xlsx';
import { stringify } from 'qs';
import { Modal } from 'ant-design-vue';
import { useUserStore } from '@/store/userStore';
export function useExportExcel() {
const userStore = useUserStore();
let xhr = null;
let downloading = false; // 限制同一文件同時(shí)觸發(fā)多次下載
onBeforeUnmount(() => {
xhr && xhr.abort();
});
// 打開(kāi)導(dǎo)出文件確認(rèn)彈窗
const exportByResBlob = options => {
Modal.confirm({
title: options.title ? options.title : '導(dǎo)出確認(rèn)',
content: options.content ? options.content : '確認(rèn)導(dǎo)出報(bào)表嗎?',
onOk() {
downloadFile(options);
return Promise.resolve();
},
});
};
// 通過(guò)請(qǐng)求后端接口文件流導(dǎo)出excel
const downloadFile = options => {
try {
if (downloading || !options.url || !options.fileName)
return new Error('缺少必需參數(shù)');
downloading = true;
const paramsStr = stringify(options.params || {});
xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
if (paramsStr) {
xhr.open('get', `${options.url}?${paramsStr}`, true);
} else {
xhr.open('get', options.url, true);
}
xhr.setRequestHeader('token', userStore.userToken);
xhr.onloadend = function (e) {
if (e.target.status === 200) {
const aElement = document.createElement('a');
const blob = e.target.response;
const url = window.URL.createObjectURL(blob);
aElement.style.display = 'none';
aElement.href = url;
aElement.download = `${options.fileName}.xlsx`;
document.body.appendChild(aElement);
aElement.click();
if (window.URL) {
window.URL.revokeObjectURL(blob);
} else {
window.webkitURL.revokeObjectURL(blob);
}
document.body.removeChild(aElement);
downloading = false;
}
};
xhr.send();
} catch (e) {
console.error(e);
downloading = false;
Modal.error({
title: '提示',
content: '導(dǎo)出發(fā)生異常,請(qǐng)重試',
});
}
};
// 通過(guò)數(shù)組數(shù)據(jù)前端導(dǎo)出excel
const exportByArray = options => {
if (!options.data || !options.key || !options.title || !options.fileName) return new Error('缺少必需參數(shù)');
const arr = options.data.map(v =>
options.key.map(j => {
return v[j];
}),
);
arr.unshift(options.title);
const ws = utils.aoa_to_sheet(arr);
const colWidth = arr.map(row =>
row.map(val => {
if (val == null) {
return { wch: 10 };
} else if (val.toString().charCodeAt(0) > 255) {
return { wch: val.toString().length * 2 };
} else {
return { wch: val.toString().length };
}
}),
);
const result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch'];
}
}
}
ws['!cols'] = result;
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, options.fileName);
writeFile(wb, options.fileName + '.xlsx');
};
return {
exportByResBlob,
exportByArray,
};
}以上就是公共Hooks封裝報(bào)表導(dǎo)出useExportExcel實(shí)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于Hooks封裝useExportExcel的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決axios發(fā)送post請(qǐng)求返回400狀態(tài)碼的問(wèn)題
今天小編就為大家分享一篇解決axios發(fā)送post請(qǐng)求返回400狀態(tài)碼的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Vue按時(shí)間段查詢數(shù)據(jù)組件使用詳解
這篇文章主要為大家詳細(xì)介紹了Vue按時(shí)間段查詢數(shù)據(jù)組件的使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
快速解決Vue項(xiàng)目在IE瀏覽器中顯示空白的問(wèn)題
今天小編就為大家分享一篇快速解決Vue項(xiàng)目在IE瀏覽器中顯示空白的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
npm run dev報(bào)錯(cuò)信息及解決方法
這篇文章主要為大家介紹了npm run dev報(bào)錯(cuò)信息及解決方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09

