In our daily development, we often encounter the need to export files in the demand scenario, such as Excel, PDF, Word, image and so on. We have always met the current problem, but did not make a review summary. Today, we will list this matter well, once and for all!

1. Export Excel, Word and PowerPoint files

The file format

  1. Excel files for data statistics. The mainstream file formats are.xls and.xlsx.
  2. Word file for document notification, the mainstream file formats are:.doc and.docx;
  3. The main file formats of PowerPoint files are.ppt and.pptx.

There are usually three ways to export files

  1. aLink Directly jumps to the file link for download
  2. Obtain the binary stream file address from the background API and process it. (Emphasis on the)
  • Configure the front-end request mode toblobRequest access to the file stream;
  • throughURL.createObjectURL()Static method to create oneDOMString
  • Dynamically created for file downloadsaTag and assign a valueherfdownloadProperty and triggers a click event for the tag;
  • throughURL.revokeObjectURL()Static method to release before passingURL.createObjectURL()To create theURLobject
  1. The server returns the data, and the front end renders it into a table and downloads it
This method is the encapsulated method for handling binary stream data returned by the server./** * OBJECT is the axios request body parameter * NAME is the NAME of the exported file * TYPE is the TYPE of processing stream data **/
export const blobExport = function (OBJECT, NAME = null, TYPE = ' ') {
    return new Promise(((resolve, reject) = > {
        try {
            let url = ' '
            let name = NAME
            let type = TYPE
            let method = 'get'
            let options={
                method,
                url, // Request an address
                responseType: 'blob'.// Indicates the type of data returned by the server,
                allData: true
            }
            if (Object.prototype.toString.call(OBJECT) === '[object Object]') {
                options = Object.assign({
                    DONT_TRANSFORM: true
                }, options, OBJECT)
            }
            axios(options).then(data= > {
                const blob = new Blob([data.data], {type})
                const reader = new FileReader()
                reader.addEventListener('loadend'.function () {
                    if(data.data.type ! = ='application/json') {
                        const disposition=data.headers['content-disposition'].toLowerCase()
                        const startReg = /filename\*[^;=\n]*=(? :\S+'')? ([^;=\n]*)/
                        const reg = /filename[^;=\n]*=([^;=\n]*)/
                        let fileName = ' '
                        try {
                            fileName = name ?
                                name :
                                decodeURIComponent( disposition.match(startReg.test(disposition)? startReg:reg)[1])}catch (e) {
                            fileName = ' '
                        }
                        if ('download' in document.createElement('a')) { // Non-IE download
                            const elink = document.createElement('a')
                            elink.download = fileName
                            elink.style.display = 'none'
                            elink.href = URL.createObjectURL(blob)
                            document.body.appendChild(elink)
                            elink.click()
                            URL.revokeObjectURL(elink.href) // Release the URL object
                            document.body.removeChild(elink)
                        } else { / / ie 10 + downloads
                            navigator.msSaveBlob(blob, fileName)
                        }
                        resolve()
                    } else {
                        const result = JSON.parse(reader.result)
                        reject(result.message)
                    }
                })
                reader.readAsText(blob)
            }).catch((e) = > {
                reject(e)
            })
        } catch (e) {
            reject(e)
        }
    }))
}
Copy the code

Export file name extension definition

// File naming format

// random 5-digit number
let timeStr = parseInt(Math.random(0.1) * 1e5);

// The current timestamp
let timeStr = new Date().toLocaleString();

// Get the file name in headers
let fileName = data.headers['content-disposition'].toLowerCase().split(";") [1].split("filename=") [1];

Copy the code

Note: The preceding "File name in Obtaining Headers" is the file name carried in the background when the interface is requested. If "none" is displayed, you need to customize the file name.

Export PDF

Exporting a PDF file is a bit special, and it’s a bit of a hassle on the back end because the server has to generate a graph instead of just processing the data like an Excel file. It is easier to put it in the front end. The principle is to transfer the HTML structure of the page to a picture through html2Canvas, and then convert the picture to a file named.pdf by JSPDF and download it.

import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'

export default {
    install(Vue) {
        Vue.prototype.getPdf = function(title, options = { id: '#pdfDom' }) {
            html2Canvas(document.querySelector(options.id), {
                allowTaint: true,
            }).then(function(canvas) {
                let contentWidth = canvas.width
                let contentHeight = canvas.height
                let pageHeight = (contentWidth / 592.28) * 841.89
                let leftHeight = contentHeight
                let position = 0
                let imgWidth = 595.28
                let imgHeight = (592.28 / contentWidth) * contentHeight
                let pageData = canvas.toDataURL('image/jpeg'.1.0)
                let PDF = new JsPDF(' '.'pt'.'a4')
                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(title + '.pdf')})}},}Copy the code

The above export PDF, in the normal export of one or two pages is no problem, recently encountered an export of more than 50 pages or even more pages, there is a problem.

Problem description: There are some missing page contents and incomplete display in the middle. At this time, I have been questioning whether there is a problem with this scheme. This project of exporting dozens of pages and hundreds of pages is called a front-end to do, but I refuse to tell the truth in my heart.

The first step is that HTML2Canvas converts HTML structure into image (Cancanvas. ToDataURL (‘image/ JPEG ‘, 1)) output Base64 encoding. Second, base64 encoding is used to generate PDF files by jsPdf plug-in. So the first step is to study the first step and output the image to see, if not, check the second step, export the image according to this idea and find that the exported image is already wrong, then gnaze the html2Canvas document, but it does not take effect, then simplify the HTML code, replace element’s table component with native, miracle appears. Normal export, at this time the sense of achievement burst. Raise test!!

Problem description: The test was followed by a violence test, which failed again. The exported PDF document was blank. Once again, the feasibility of the scheme was questioned.

Solution: According to the performance of the basic can be determined that the data is too long, manual control data volume can be determined to try more than 20 pages will be problematic. Baidu checked the reason for this problem, and it was found later that canvas screenshot has the maximum limit. Knowing this problem, I thought of segmentary interception. After searching an article online, I found the partial article below

In order to download HTML documents into PDF, the following problems are encountered: 1. Html2canvas will generate a black screen if the top is not set after the file is downloaded. 2. There are large echars charts generated in the code, which can only generate canvas on the original DOM node for download, rather than cloneNode() node for operation. 3. When HTML2Canvas generates pictures, if html2Canvas is not configured to display pictures, the pictures will not be displayed. 4. There are a large number of charts on the page (exceeding the maximum height of the browser’s Canvas drawing element), and the rest of the page will display a black screen. The solution is to generate canvas again over the part and add it to the PDF object to be generated.

function f_uploadPdf() {
    handleHtml2Down(data.reportName);
}
/ / generate PDF
handleHtml2Down = async (fileName, pdfDom, reportBody) => {
    var pdf = new jsPDF('p'.'pt'.'a4');
    scrollTo(0.0);
    const maxH = 16384
    let h = reportBody[0].scrollHeight
    var index = 0
    var pdfDom = pdfDom || $("#pdfDom") [0];
    while (h > 0) {
        await html2canvas(pdfDom, {
            background: "#fff".height: (h > maxH ? maxH : h),
            width: pdfDom.scrollWidth,
            foreignObjectRendering: true.allowTaint: false.useCORS: true,
        }).then((canvas= > {
            var contentWidth = canvas.width;
            var contentHeight = canvas.height;
            // a PDF page displays the canvas height generated by the HTML page;
            var pageHeight = contentWidth / 592.28 * 841.89;
            // Height of HTML page not generated PDF
            var leftHeight = contentHeight;
            // Page offset
            var position = 0;
            // The size of the A4 paper [595.28,841.89], the width and height of the image in the PDF generated by the CANVAS of the HTML page
            var imgWidth = 595.28;
            var imgHeight = 592.28 / contentWidth * contentHeight;
            var pageData = canvas.toDataURL('image/jpeg'.1);
            // There are two heights to distinguish, one is the actual height of the HTML page, and the page height of the generated PDF (841.89)
            // When the content does not exceed the size of a PDF page, no pagination is required
            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;
                    // Avoid adding blank pages
                    if (leftHeight > 0) {
                        pdf.addPage();
                    }
                }
            }
            h = h - maxH;
            index++;
            pdfDom.style.height = h;
            reportBody[0].style.marginTop = ` -${index * maxH}px`
        }))
    }
    reportBody[0].style.marginTop = '0px'
    pdf.save(fileName+".pdf");
}

Copy the code

After the example article, the method of transformation, do some fine tuning. Because according to the above method, there will be several data missing between the upper and lower pages of pagination, guess it is the PDF. AddImage has several data overwritten, but did not find a solution, fine adjusted the height of maxH, welcome your advice

Vue.prototype.getNewPDF = async (title, pdfDom, reportBody) => { 
    let pdf = new JsPDF('p'.'pt'.'a4')
    scrollTo(0.0)
    const maxH = 16100
    let h = reportBody.scrollHeight
    pdfDom.style.height = maxH
    window.console.log(h, maxH)
    let index = 0
    while (h > 0) {
        const opt = {
            height: (h > maxH ? maxH : h),
            width: pdfDom.clientWidth,
            allowTaint: false.useCORS: true
        }
        window.console.log(opt)
        await html2Canvas(pdfDom, opt).then((canvas) = > {
            let contentWidth = canvas.width
            let contentHeight = canvas.height
            window.console.log(canvas)
            // A PDF page displays the canvas height generated by the HTML page
            let pageHeight = contentWidth / 592.28 * 841.89
            // Height of HTML page not generated PDF
            let leftHeight = contentHeight
            // Page offset
            let position = 0
            // The size of the A4 paper [595.28,841.89], the width and height of the image in the PDF generated by the CANVAS of the HTML page
            let imgWidth = 595.28
            let imgHeight = (592.28 / contentWidth) * contentHeight
            let pageData = canvas.toDataURL('image/jpeg'.1)
            // window.console.log(pageData)
            // There are two heights to distinguish, one is the actual height of the HTML page, and the page height of the generated PDF (841.89)
            // When the content does not exceed the size of a PDF page, no pagination is required
            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
                    // Avoid adding blank pages
                    pdf.addPage()
                }
            }
            h = h - maxH
            index++
            pdfDom.style.height = h
            reportBody.style.marginTop = ` -${index * maxH}px`
            // pdfDom.style.overflow = 'hidden'
        })
    }
    reportBody.style.marginTop = '0px'
    pdfDom.style.height = '100%'
    pdf.save(title+'.pdf')}Copy the code

The problem was solved, but it wasn’t implemented perfectly, leaving the problem described above where some pages didn’t fill the entire page. Through this problem, I did grow a lot, but this scheme is really not a good one: first, users may need to wait more than 10s after clicking the download. Second, because of the large amount of data, computer performance nearly the machine may not bear. This is an unreasonable part of the product design, and it is a good solution to export Excel. If such a problem is similar to the root cause, it should be said no. If such a solution is adopted, there will probably be problems even if it is solved in the follow-up launch

3. Export images

// Download the image address and image name
downloadIamge(imageUrl) {
    var image = new Image()
    // Resolve cross-domain Canvas pollution
    image.setAttribute('crossOrigin'.'anonymous')
    image.onload = function() {
        var canvas = document.createElement('canvas')
        canvas.width = image.width
        canvas.height = image.height
        var context = canvas.getContext('2d')
        context.drawImage(image, 0.0, image.width, image.height)
        var url = canvas.toDataURL('image/png') // Get the base64 encoded data of the image

        var a = document.createElement('a') // Generate an A element
        var event = new MouseEvent('click') // Create a click event
        a.download = 'qrcode' // Set the image name
        a.href = url // Set the generated URL to the A.ref property
        a.dispatchEvent(event) // Triggers the click event of A
    }
    image.src = imageUrl  // Image link
    this.qrcodeShowFlag = false
}
Copy the code

To C’s business usually generates posters with various cool styles for spreading in wechat and friends circle. Html2Canvas can be used to transfer the exquisite HTML structure drawn to the picture for this scene

Pass in the specified ID to get the corresponding page interface html2Canvas(document.querySelector(options.id), {
    allowTaint: true,
}).then(function(canvas) {
    let pageData = canvas.toDataURL('image/jpeg'.1.0* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *Copy the code

Note: Xiaobian tried to draw this kind of poster in the front before, but the picture quality is not very high. If it is mixed development can be generated by the client’s capabilities, so it can be solved. When the poster size reaches a certain degree when the client is also difficult to meet, you can build a Node service to solve the problem