[toc]

导出Excel、PDF和下载文件

创建时间: 2020-03-16;测试:chrome v80.0.3987.122 正常 更新:2021-05-26

导出 Excel (利用xlsx插件)

  • 安装 xlsx 引入包 import XLSX from 'xlsx/dist/xlsx.mini.min.js';

实现原理:将其描述表格的 JSON 按照电子表格的协议转换为压缩的 zip 字符,再通过一系列方法将其转换为 Blob URL;(相关原理代码如下)

function blobify(strData) {
    var buf = new ArrayBuffer(strData.length), view = new Uint8Array(buf);
    for (var i=0; i!=strData.length; ++i) view[i] = strData.charCodeAt(i) & 0xFF;
    return buf;
}
var excelBlob = new Blob([blobify(data)], {type:"application/octet-stream"});
var blobURL=URL.createObjectURL(excelBlob);

一些说明

var workbook = XLSX.readFile('test.xlsx');
// 将一个 worksheet 添加到 workbook
var ws_name = "SheetJS";
/* make worksheet */
var ws_data = [
  [ "S", "h", "e", "e", "t", "J", "S" ],
  [  1 ,  2 ,  3 ,  4 ,  5 ]
];
var ws = XLSX.utils.aoa_to_sheet(ws_data);

// s 意为 start ,即开始的单元格
// e 意为 end ,即结束的单元格
// r 是 row ,表示行号,从 0 计起
// c 是 col ,表示列号,从 0 计起

// !ref"表示显示的范围,例:A1到E3

/* Add the worksheet to the workbook */
XLSX.utils.book_append_sheet(wb, ws, ws_name);

var wb = XLSX.utils.book_new()  // 返回 { SheetNames: [], Sheets: {} };

// SheetNames excel表格的 sheet 名称列表
// Sheets excel表格的对应 sheet 内容列表

相关业务实现代码如下:

exportData = () => {
    /**
     * @des 生成excel
     * @param { Array } data ([[name, score],['hew', 80]]) 
     * @param { String } name 表格名称
     */
    const fn = (data, name) => {
        name = name || 'table';
        /** 设置文件名以及格式, 默认xlsx */
        const filename = /\./.test(name) ? name : `${name}.xlsx`;
        /** Excel第一个sheet的名称 */
        const wsName = 'sheet'; 
        const newBook = XLSX.utils.book_new();
        const ws = XLSX.utils.aoa_to_sheet(data);
        /** 将数据添加到工作薄 */
        XLSX.utils.book_append_sheet(newBook, ws, wsName);  
        XLSX.writeFile(newBook, filename);  // 下载文件到本地
    }

    fn([
        ['h1','h2','h3', 'h4'], 
        [1,2,3,4],
        [true, false, null, 5],
        [true, false, null, '中文'],
    ], '表格')
}

详细实现demo 参考 react-admin -> third -> sheetJS

导出PDF

利用打印

  • 在浏览器弹出的打印页面,点击选择目标打印机,选择另存为PDF

引入 jspdf 和 html2canvas

  • 主要代码
 html2canvas(document.querySelector('.need-pdf')).then((canvas) => {
    let canvasWidth = canvas.width
    let canvasHeight = canvas.height
    // a4纸的尺寸[595.28,841.89]
    let pageHeight = canvasWidth / 592.28 * 841.89
    let imgWidth = 595.28
    let imgHeight = 592.28 / canvasWidth * canvasHeight
    let pageData = canvas.toDataURL('image/jpeg', 1.0)

    let pdf = new jsPDF('', 'pt', 'a4')
    if (canvasHeight < pageHeight) {
        // 参数说明:图片数据,格式,距左边距,距上边距,图宽,图高,...(其它参数)  这里的单位都和上面创建pdf实例时一致
        pdf.addImage(pageData, 'JPEG', 20, 0, imgWidth, imgHeight)
    } else {
        // 分页操作,以下操作方式较为粗糙,分页处直接分割内容 
        let theRestHeight = canvasHeight;
        let y = 0;
        while (theRestHeight > 0) {
            // 原理是将同一张图放在不同页面,但上移不同的距离,实现分页
            pdf.addImage(pageData, 'JPEG', 0, y, imgWidth, imgHeight)
            theRestHeight -= pageHeight
            y -= 841.89
            if (theRestHeight > 0) {
                pdf.addPage()
            }
        }
    }
    pdf.save('table.pdf')
})

下载图片 或 其它文件

利用 canvas 将 dom 生成图片并下载

  • html2canvas 原理是读取DOM,并根据规则在画布上绘制,但部分css样式是无法实现的

  • 如果有滑动条 需要将滑动条滚到要截取的DOM顶部

// 配置项 https://html2canvas.hertzen.com/configuration
// allowTaint: true  使背景图或img引入的图片生效
html2canvas(document.querySelector('xxx'), { allowTaint: true }).then((canvas) => {
    // 防止截图不完整
    document.documentElement.scrollTop = 0
    document.body.scrollTop = 0
    // toDataURL 方法参见 javascript.md
    var d = canvas.toDataURL('image/jpeg', 1)
    // downloadFile 方法参见下文
    downloadFile(d)
})

利用跳转

window.open('a.zip');

location.href = 'a.zip';

会出现URL长度限制问题;浏览器可直接浏览的类型无法下载如txt、png、jpg、gif等;不能添加header,也就不能进行鉴权;不知道下载的进度


利用 a 标签 download

存在兼容问题,并且必须是同源图片(非同源会在新标签页打开)

download属性值要跟图片格式 且必须是同源才能设置文件名
Content-Disposition: attachment; filename="filename.jpg" 可以让后端这是整个属性 来配置文件名

<a href="xxx" alt="no" download="new-name.jpg">下载图片</a>
const downloadFile = (url, name) => {
    const aEle = document.createElement('a')
    if (!name) {
        const urlArray = [] // 如果没有name,或直接用链接上的name  酌情删除相关代码
        name = urlArray[urlArray.length - 1]
    }
    aEle.setAttribute('href', url)
    aEle.setAttribute('download', name || 'name') // 默认是链接上的文件名
    document.body.appendChild(aEle) // 可以不注入到 DOM chrome 测试有效
    aEle.click()
}

// 判断是否支持download
'download' in document.createElement('a');

利用 canvas 结合 Image 下载图片

如果是放 cdn 或是 图片服务器(服务器也要设置允许跨域)

toDataURL 如果本地开发引入图片 会报跨域错误 可以将图片转为base64 直接使用

const canvasEle = document.createElement('canvas');
const ctx = canvasEle.getContext('2d');

const imgEle = new Image();
imgEle.setAttribute('crossOrigin', 'anonymous'); 
imgEle.onload = function() {
    canvasEle.width = this.width;
    canvasEle.height = this.height;
    ctx.drawImage(this, 0 , 0, this.width, this.height);

    const aEle = document.createElement('a');
    aEle.setAttribute('href', canvasEle.toDataURL('image/jpeg'));
    aEle.setAttribute('download', 'cross.jpg');
    aEle.click();
}
imgEle.src = 'xxx.jpg';

利用 ajax 请求,后端返回二进制流,结合 Blob ,下载图片 excel 等类型文件

要求也是同上,只是不需要兼容 crossOrigin;可以设置 header 请求时要带上 responseType = 'blob';

// ajax 直接请求
function dl() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '/xx/xx', true);
    xhr.responseType = 'blob';
    xhr.send();
    xhr.onload = function () {
        /*
         * @des 利用 Blob
         */
        var url = URL.createObjectURL(this.response);
        const aEle = document.createElement('a');
        aEle.setAttribute('href', canvasEle.toDataURL('image/jpeg'));
        aEle.setAttribute('download', 'cross.jpg');
        aEle.click();
        // 释放内存
        URL.revokeObjectURL(url);

        /*
         * @des 利用 base64
         */
        const fileReader = new FileReader();
        fileReader.readAsDataURL(this.response);
        fileReader.onload = function () {
            const aEle = document.createElement('a');
            aEle.href = this.result;
            aEle.download = 'fileName.doc';
            aEle.click();
        }
    };
}

注意如果项目中有使用 mock 可能会导致下载出错

// axios 请求  
function axios() {
     const httpService = axios.create({
        baseURL: '',
        headers: {},
    })
    httpService({
        method: 'get',
        url: 'xxx',
        data: { },
        responseType: 'arraybuffer' // 或者 blob
    }).then((result) => {       
        /*
            type 类型
            .doc  application/msword
            .docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
            .xls  application/vnd.ms-excel
            .xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
            .ppt  application/vnd.ms-powerpoint
            .pptx application/vnd.openxml   formats-officedocument.presentationml.presentation
            .ppt  text/csv
        */
        const blob = new Blob([result], { type: 'application/vnd.ms-excel;charset=UTF-8' }) // 始测这里不带类型下载页可以
        const eleA = document.createElement('a')
        eleA.href = window.URL.createObjectURL(blob)
        eleA.download = new Date().getTime() + '-file'
        // document.querySelector('body').append(eleA)
        eleA.click()
    }).catch((err) => {
        console.log(err)
    })
}
  • 文件流返回,获取文件名

header上有 Content-Disposition 其值有一段的内容是 filename=xxxxxx.xlsx; filename*=xxxxxxy

filename* 后跟的就是现代浏览器都支持较好的 文件名

如果获取不到 Content-Disposition 的值, 就需要后端配合设置 Access-Control-Expose-Headers = Content-Disposition

const disposition = xhr.getResponseHeader('content-disposition'); 
// axios 中 直接 result.headers['content-disposition'] 获取
if (disposition) {
    // 具体的获取方法根据实际字符串而定
    let filename = content.match(/filename\*=(.*)/)[1];
    // 不要后最名 ([^.]*)
    filename = decodeURIComponent(filename);
}
// 中文如果出现乱码 需要后端进行编码, 编码后还是有问题 可以参考如下编码方式
// import java.net.URLEncoder;
// httpHeaders.add("Content-disposition", "attachment; filename=\"" + fileName + "\""); //httpHeaders.add("Content-Type", mimeType + "; name=\"" + fileName + "\"");
// String encodedFileName = URLEncoder.encode(fileName, "UTF-8"); httpHeaders.add("Content-disposition", "attachment; filename*=UTF-8''" + encodedFileName ); httpHeaders.add("Content-Type", mimeType + ";charset=UTF-8");

若以上方式无法解决名称获取,只能后端配合添加自定义 header

欢迎交流 Githubopen in new window

Last Updated:
Contributors: Warren