前端實現導出功能整理

本文是用來記錄項目中遇到各類業務場景下的導出功能實現。歡迎點贊收藏html

1、後端返回連接的導出

這種導出,我最喜歡了,後端比較有良心,前端很省事。固然在這種場景下,還分兩種狀況。前端

  • 連接帶域名
if (res.code == 200) {
    window.location.href = res.data;
}
複製代碼
  • 連接不帶域名vue

    域名是請求後端的域名,不是前端頁面的域名。項目通常是給多個客戶使用,故後端的域名通常都交給運維來配置。項目中這麼實現。ios

    在靜態資源public文件夾中創建config.js文件和config.js.example文件,其中config.js文件是忽悠上傳到git上的,開發環境能夠裏面配置後端的域名,在生產環境是由運維在裏面配置後端的域名,由於已經忽悠上傳到git上的,更新代碼時不會覆蓋運維所配置的。config.js.example文件是一個示例做用,告訴運維怎麼配置。git

    config.js和config.js.example內容同樣github

    /*
    配置文件示列:
    配置文件路徑 public/config.js
    */
    window.apiConfig = {
        baseUrl: '後端的域名',
    };
    複製代碼

    而後再public/index.html引入config.jsweb

    <script>
        var script = document.createElement('script');
        var num = Math.floor(Math.random() * 10000);
        script.src = 'config.js?a=' + num;
        document.getElementsByTagName('head')[0].appendChild(script);
        script = document.getElementById('scriptConfig');
        script.parentNode.removeChild(script);
        script = null;
    </script>
    複製代碼

    最後這麼使用便可npm

    if (res.code == 200) {
        window.location.href = window.apiConfig.baseUrl+res.data;
    }
    複製代碼

2、後端返回二進制數據的導出

  • 首先咱們要配置一下axios,由於默認服務器響應的數據類型是json,要改成blob。
    export function export(data){
        return service.get('接口地址',{
            params:data,
            responseType:'blob'
        })
    }
    複製代碼
  • 而後利用new Blob()來處理二進制數據,生成一文件,再用createObjectURL()建立連接後,用a連接自動下載。下面把方法封裝一下,掛在Vue原型鏈上。
const install = function(Vue,opts){
     * 處理二進制數據導出
     * @param blob 二進制流
     * @param name 文件名
     */
    Vue.prototype.exportExcels = function(blob,name){
        // type 爲須要導出的文件類型,此處爲xls表格類型
        const file = new Blob([blob], { type: 'application/vnd.ms-excel' });
        // 兼容不一樣瀏覽器的URL對象
        const url = window.URL || window.webkitURL || window.moxURL;
        // 建立下載連接
        const downloadHref = url.createObjectURL(file);
        // 建立a標籤併爲其添加屬性
        let downloadLink = document.createElement('a');
        downloadLink.setAttribute('href', downloadHref);
        downloadLink.setAttribute('download', name);
        //將a標籤添加到body中
        document.body.appendChild(downloadLink);
        // 觸發a標籤的點擊,自動下載
        downloadLink.click();
        //下載完成後移除a標籤
        document.body.removeChild(downloadLink);
        //釋放下載連接
        url.revokeObjectURL(downloadHref);
    }
}
export default{
    install
}
複製代碼
  • 其中new Blob()的第一個參數是array,裏面每項是一個二進制流,第二個參數是可選屬性,其中type屬性是文件的MIME類型,這個類型由後端決定是什麼類型。json

    經常使用的MIME類型以下canvas

    後綴名 MIME名稱
    *.csv text/csv
    *.doc application/msword
    *.dot application/msword
    *.xls application/vnd.ms-excel
    *.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

3、在導出接口地址上加參數直接打開下載

上面兩種場景都是先要請求服務器,獲得返回數據後處理後再下載。這種場景是在導出接口地址上加參數直接打開下載,例如:

window.location.href = '導出接口地址'?user='lhy'&date='2020-05';
複製代碼

上面的請求方法至關get方法,可是當請求導出接口時候參數太多了,使用get方法請求會致使參數缺乏。這時候就想辦法用post方法請求。

因爲這種場景是直接打開導出接口地址下載,有點很差用post方法,那麼這時候就要藉助HTML<form> 標籤和DOM Form 對象來解決。

咱們封裝一個組件來實現。

<template>
    <form :action="action" :target="target" :method="method" ref="exports">
        <template v-if="data.length">
            <input type="hidden" autocomplete="off" v-for="(item,i) in data" :name="item.name" :value="item.value"/>
        </template>
        <input type="hidden" autocomplete="off" readonly name="token" :value="token"/>
    </form>
</template>
<script>
    export default {
        name: 'formExport',
        props: {
            action: {
                type: String,
                default: '',
            },
            target: {
                type: String,
                default: '_blank',
            },
            method: {
                type: String,
                default: 'post',
            },
            token:{
                type: String,
                default: '',
            },
            data: {
                type: Array,
                default() {
                    return [];
                }
            }
        },
        methods: {
            submit() {
                return new Promise((resoleve,reject) =>{
                    if (this.token) {
                        this.$refs.exports.submit();
                        resolv()
                    }else{
                        reject()
                    }
                }
            }
        }
    }
</script>
複製代碼

組件文檔

  • 參數
    參數 說明 類型 可選值 默認值
    action 必填,導出接口地址 String
    target 規定在何處打開導出接口地址 String _blank:在新窗口打開
    _self:在當前窗口打開
    _blank
    method 請求方法 String post/get post
    token 必填,鑑權 String
    data 必填,傳給服務器的參數
    {name:參數名稱,value:參數值}
    String
  • 方法
    事件名稱 說明 回調參數
    submit 提交表單 Promise對象
  • 示例
    <template>
        <formExport ref="export" :action="exportData.url" :data="exportData.url" :token="exportData.token"></formExport>
        <el-button @click="handleExport">導出</el-button>
    </template>
    <script>
        export default {
            data(){
                return{
                    exportData:{
                        url:'導出接口地址',
                        data:{
                            user:'lds',
                            page:1,
                            pageSize:20,
                            statTime:'2020-04',
                            endTime:'2020-05'
                        },
                        token:'12334f'
                    }
                }
            },
            components:{
                formExport: () =>import('./formExport.vue')
            },
            methods:{
                handleExport(){
                    setTimeout(() => {
                        this.$refs.export.submit();
                    }, 500);
                }
            }
        }
    </script>
    複製代碼

4、後端只返回json數據前端生成Excel下載

這裏要藉助xlsx和file-saver兩個插件實現。其中xlsx是生成Excel文件,file-save是保存下載Excel文件

  • 使用npm安裝xlsx和file-saver插件,執行命令
npm install xlsx --save
npm install file-saver --save
複製代碼
  • 在servie文件夾中引入Export2Excel.js,在這腳本中對xlsx和file-saver插件中的方法進行封裝。

  • 下面把方法封裝一下,掛在Vue原型鏈上

    • 參數文檔
    參數 說明 類型 默認 可選值 示例
    header 表格頭數據 array ['一級客戶', '申請客戶', '支付寶帳號', '提現金額', '申請人', '申請時間']
    data 表格數據 array [{},{}]
    filename Excel文件名稱 string 'excle1'
    opition 額外配置 object {}
    • 參數 opition 文檔
    參數 說明 類型 默認 可選值 示例
    filterVal 過濾表格數據 array ['id','name']
    multiHeader 表格頭數據除最後一行表格頭的數據,是個二維數據,不夠的用''補全,只在bookType爲xlsx或xls下有效。 array [ ['序號', '客戶信息', '', '', '', ''], ['', '客戶姓名', '提現信息', '', '', '']]
    merges 合併表格頭的規則,只在bookType爲xlsx或xls下有效。 array ['A1:A3', 'B1:F1', 'B2:B3', 'C2:F2']
    autoWidth 表格內容是否自適應寬度 boolean true true/false
    bookType 生成文件類型 string xlsx xlsx/xls/csv
const install = function(Vue,opts){
    /**
    * json數據生成Excel並下載
    * @param header 表格頭數據
    * @param data 表格數據
    * @param filename Excel文件名稱
    * @param opition 額外配置
    */
   Vue.prototype.downloadExcels = function (header, data, filename, opition) {
       let defaultOpition = {
           filterVal: [],
           multiHeader: [],
           merges: [],
           autoWidth: true,
           bookType: 'xlsx',
       }
       if (header && Object.prototype.toString.call(header) != '[object Array]') {
           throw new Error('header請傳入數組');
       }
       if (data && Object.prototype.toString.call(data) != '[object Array]') {
           throw new Error('data請傳入數組');
       }
       if (opition && Object.prototype.toString.call(opition) == '[object Object]') {
           defaultOpition = Object.assign({}, defaultOpition, opition);
       }
       if (Object.prototype.toString.call(defaultOpition.filterVal) != '[object Array]') {
           throw new Error('filterVal請傳入數組');
       }
       if (Object.prototype.toString.call(defaultOpition.multiHeader) != '[object Array]') {
           throw new Error('multiHeader請傳入數組');
       }
       if (Object.prototype.toString.call(defaultOpition.merges) != '[object Array]') {
           throw new Error('merges請傳入數組');
       }
       const formatJson = function (filterVal, jsonData) {
           if (filterVal.length == 0) {
               return jsonData;
           } else {
               return jsonData.map(v => filterVal.map(j => v[j]));
           }
       }
       data = formatJson(defaultOpition.filterVal, data);
       if (data[0].length > header.length) {
           throw new Error('data中每項數據長度大於頭部長度');
       }
       defaultOpition['data'] = data;
       defaultOpition['header'] = header;
       defaultOpition['filename'] = filename;
       import('./Export2Excel').then(res => {
           res.export_json_to_excel(defaultOpition);
       })
   }
}
export default{
   install
}
複製代碼
  • 示例
handleExport() {
    const header = ['一級客戶', '申請客戶', '支付寶帳號', '提現金額', '申請人', '申請時間'];
    const option = {
        bookType: 'xlsx',
        filterVal: ['firstCustomName', 'custom_name', 'withdrawals_bank', 'withdrawals_amount', 'do_username', 'add_time'],
        multiHeader: [
            ['序號', '客戶信息', '', '', '', ''],
            ['', '客戶姓名', '提現信息', '', '', '']
        ],
        merges: ['A1:A3', 'B1:F1', 'B2:B3', 'C2:F2']
    }
    this.downloadExcels(header, this.tableData, '審覈記錄', option)
},
複製代碼

5、經過PDF導出

這種導出適用可視化數據場景

  • 在index.html 引入生成PDF的腳本jspdf.js
  • 在index.html 引入截屏的腳本html2canvas.js
  • 下面把方法封裝一下,掛在Vue原型鏈上
const install = function(Vue,opts){
     * 將頁面導出成pdf文件
     * @param id 要生成PDF文件DOM區域的id
     * @param fileName 導出的文件名稱
     * @param height 導出的pdf高度不夠,須要設置額外高度,默認80
     */
    Vue.prototype.exportPDF= function(id, fileName, height = 80){
        //html2canvas只截取dom的可視區域,將dom的可視區域設置大解決導出視圖不全的問題
        document.getElementById(id).ownerDocument.defaultView.innerHeight = document.getElementById(id).scrollHeight + height;
        html2canvas(document.getElementById(id), {
            scale: 2,//按比例增長分辨率 (2=雙倍).
            dpi: 1080,//導出pdf清晰度 將分辨率提升到特定的DPI(每英寸點數)
            background: "#fff", //背景設爲白色(默認爲黑色)
            onrendered: function (canvas) {
                let contentWidth = canvas.width;
                let contentHeight = canvas.height;
    
                //一頁pdf顯示html頁面生成的canvas高度;
                //a4紙的尺寸[595.28,841.89]
                let pageHeight = contentWidth / 592.28 * 841.89;
                //未生成pdf的html頁面高度
                let leftHeight = contentHeight;
                //pdf頁面偏移
                let position = 0;
    
                //html頁面生成的canvas在pdf中圖片的寬高
                let imgWidth = 595.28;
                let imgHeight = 592.28 / contentWidth * contentHeight;
    
                let pageData = canvas.toDataURL('image/jpeg', 1.0);
                let pdf = new jsPDF('', 'pt', 'a4');
    
                //有兩個高度須要區分,一個是html頁面的實際高度,和生成pdf的頁面高度(841.89)
                //當內容未超過pdf一頁顯示的範圍,無需分頁
                if (leftHeight < pageHeight) {
                    pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
                } else {
                    while (leftHeight > 0) {
                        pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
                        leftHeight -= pageHeight;
                        position -= 841.89;
    
                        //避免添加空白頁
                        if (leftHeight > 0) {
                            pdf.addPage();
                        }
                    }
                }
                pdf.save(fileName);
            }
        )
    }
}
export default{
    install
}
複製代碼
相關文章
相關標籤/搜索