阅读 3567

前端下载文件与读取文件内容(多种类型的文件)

写在前面

在实际开发过程中经常会碰到用户要下载或者导出一个文件的需求。传统的做法是在后端存储或者即时生成一个文件来提供下载功能,这样的优势是可以做权限控制、方便数据二次处理,但缺点是需要额外发起请求、增大服务端压力、下载速度慢。但随着HTML5的标准发布,我大前端已经完全可以独立实现文件下载与导出啦~

利用a标签的 download 属性下载文件

download属性指示浏览器下载 URL而不是导航到它,因此将提示用户将其保存为本地文件。如果属性有一个值,那么此值将在下载保存过程中作为预填充的文件名(如果用户需要,仍然可以更改文件名)。此属性对允许的值没有限制,但是 / 和 \ 会被转换为下划线。大多数文件系统限制了文件名中的标点符号,故此,浏览器将相应地调整建议的文件名。

<a download="文件名" href="文件地址">下载测试</a>
复制代码

需要注意的是:

  1. download 仅适用于同源 URL,但是可以使用 blob: URL 和 data: URL。
  2. 如果 HTTP 头中的 Content-Disposition 属性赋予了一个不同于此属性的文件名,HTTP 头属性优先于此属性。
  3. 如果 HTTP 头属性 Content-Disposition 被设置为inline 即Content-Disposition='inline',那么 Firefox 优先考虑 HTTP 头 Content-Disposition download 属性。

生成Data URLs 并下载文件

Data URL 即前缀为 data: 协议的URL,其允许内容创建者向文档中嵌入小文件。它 由四个部分组成:前缀(data:)、指示数据类型的MIME类型、如果非文本则为可选的base64标记、数据本身。

data:[<mediatype>][;base64],<data>

mediatype 是个 MIME 类型的字符串
例如 "image/jpeg" 表示 JPEG 图像文件。
如果被省略,则默认值为 text/plain;charset=US-ASCII
如果数据是文本类型,你可以直接将文本嵌入 
如果是二进制数据,你可以将数据进行base64编码之后再进行嵌入。
复制代码

导出文件代码示例:

//导出Json文件
exportJson(){
    const downloadData = {
        name:"April",
        ager:"18",
        hobby:"学习"
    };
    let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(downloadData));
    let downloadAnchorNode = document.createElement('a')
    downloadAnchorNode.setAttribute("href", dataStr);
    downloadAnchorNode.setAttribute("download", "文件名.json")
    downloadAnchorNode.click();
    downloadAnchorNode.remove();
},
复制代码

生成blob: URL 并下载文件

Blob()构造函数返回一个新的 Blob 对象。 blob的内容由参数数组中给出的值的串联组成。

let aBlob = new Blob( array, options );
复制代码

array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8。

options 是一个可选的BlobPropertyBag字典,它可能会指定如下两个属性:

  1. type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型。
  2. endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持blob中保存的结束符不变 。

URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。

objectURL = URL.createObjectURL(object);
复制代码

导出文件代码示例:

exportJson() {
    const downloadData = {
        name: "April",
        ager: "18",
        hobby: "学习"
    };
                
    let blob = new Blob(
        [JSON.stringify(downloadData, null, 2)],
        {type: 'application/json'});
    let url = URL.createObjectURL(blob);
    let downloadAnchorNode = document.createElement('a')
    downloadAnchorNode.setAttribute("href", url);
    downloadAnchorNode.setAttribute("download", "文件名.json")
    downloadAnchorNode.click();
    downloadAnchorNode.remove();
 }
复制代码

点击下载图片

虽然目前浏览器都支持保存图片到本地的功能(右键>图片另存为)但是实际开发中会涉及到批量下载图片、Canvas绘图的保存功能,应运上面的知识,我大前端也可以轻松实现。代码如下:

 <button @click="downloadImg">下载图片</button>
复制代码
// 通过src获取图片的blob对象
getImageBlob(url, cb) {
    let xhr = new XMLHttpRequest();
    xhr.open("get", url, true);
    xhr.responseType = "blob";
    xhr.onload = function () {
        if (this.status == 200) {
            cb(this.response);
        }
    };
    xhr.send();
},
// 点击下载图片
downloadImg(){
    let reader = new FileReader();
    this.getImageBlob('https://b-gold-cdn.xitu.io/v3/static/img/simplify-logo.3e3c253.svg', function(blob){
        // 读取来看下下载的内容 最终生成的字符串
        reader.readAsDataURL(blob);
        // 生成下载用的URL对象
        let url = URL.createObjectURL(blob);
        // 生成一个a标签,并模拟点击,即可下载,批量下载同理
        let downloadAnchorNode = document.createElement('a')
        downloadAnchorNode.setAttribute("href", url);
        downloadAnchorNode.setAttribute("download", "下载图片")
        downloadAnchorNode.click();
        downloadAnchorNode.remove();
    })
},
复制代码

下载表格

推荐由SheetJS出品的js-xlsx是一款非常方便的只需要纯JS即可读取和导出excel的工具库,功能强大,支持格式众多,支持xls、xlsx、ods(一种OpenOffice专有表格文件格式)等十几种格式。本文全部都是以xlsx格式为例。 官方github:github.com/SheetJS/js-…

// 测试用的数据
staff: [
    {name: "April", job: "programmer", age: "18", hobby: "study"},
    {name: "Shawn", job: "student", age: "8", hobby: "study"},
    {name: "Leo", job: "teacher", age: "28", hobby: "play"},
    {name: "Todd", job: "programmer", age: "19", hobby: "sleep"},
    {name: "Scoot", job: "cook", age: "38", hobby: "paintting"},
]
复制代码

安装xlsx

npm install xlsx --save
复制代码
import XLSX from 'xlsx';
复制代码
auto_width(ws, data) {
    /*set worksheet max width per col*/
    const colWidth = data.map(row => row.map(val => {
        /*if null/undefined*/
        if (val == null) {
            return {'wch': 10};
        }
        /*if chinese*/
        else if (val.toString().charCodeAt(0) > 255) {
            return {'wch': val.toString().length * 2};
        } else {
            return {'wch': val.toString().length};
        }
    }))
    /*start in the first row*/
    let 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;
},
json_to_array(key, jsonData) {
    return jsonData.map(v => key.map(j => {
        return v[j]
    }));
},
export_array_to_excel({key, data, title, filename, autoWidth}) {
    const wb = XLSX.utils.book_new();
    const arr = this.json_to_array(key, data);
    arr.unshift(title);
    const ws = XLSX.utils.aoa_to_sheet(arr);
    if (autoWidth) {
        this.auto_width(ws, arr);
    }
    XLSX.utils.book_append_sheet(wb, ws, filename);
    XLSX.writeFile(wb, filename + '.xlsx');
},
exportExcel() {
    let staff = this.staff;
    const params = {
        title: ['姓名', '工作', '年龄', '爱好'],
        key: ['name', 'job', 'age', 'hobby'],
        data: staff,
        autoWidth: true,
        filename: '文件名'
    }
    this.export_array_to_excel(params)
},
复制代码

读取上传文件的数据

想要读取Blob数据的唯一方法是FileReader。 FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 FileBlob 对象指定要读取的文件或数据。

其中File对象可以是来自用户在一个<input>元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的 DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回结果。

包含5个方法:

  1. FileReader.abort() 中止读取操作。在返回时,readyState属性为DONE。

  2. FileReader.readAsArrayBuffer() 开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象.

  3. FileReader.readAsBinaryString() 开始读取指定的Blob中的内容。一旦完成,result属性中将包含所读取文件的原始二进制数据。

  4. FileReader.readAsDataURL() 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容。

  5. FileReader.readAsText() 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。

将上传的文件读取为字符串的代码示例

handleUpload(blob) {
    // 新建一个FileReader
    const reader = new FileReader()
    // 读取文件
    reader.readAsText(blob, "UTF-8")
    // 读取完文件之后会回来这里
    reader.onload = function (e) {
        // 读取文件内容
        const fileString = e.target.result
        // 接下来可对文件内容进行处理
        const myData = JSON.parse(fileString);
        console.log(myData) // 打印读取到的内容
     }
},

复制代码

将上传的文件读取为URL格式的字符串的代码示例

handleUpload(file) {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function (e) {
        const urlStr = reader.result
        console.log(urlStr)
    }
}
复制代码

读取上传Excel文件

同样推荐由SheetJS出品的js-xlsx

import XLSX from 'xlsx';
复制代码
handleUpload(file) {
    let that = this;
    var reader = new FileReader();
    reader.readAsBinaryString(file);
    reader.onload = function (evt) {
      var data = evt.target.result;
      var workbook = XLSX.read(data, {
        type: 'binary'
      }) // 以二进制流方式读取得到整份excel表格对象
    }
    reader.onload = function (evt) {
      //当读取完成后回调这个函数,然后此时文件的内容存储到了result中,直接操作即可
      try {
        var data = evt.target.result,
          workbook = XLSX.read(data, {
            type: 'binary'
          }), // 以二进制流方式读取得到整份excel表格对象
          buildings = []; // 存储获取到的数据
        var fromTo = '';
        // 遍历每张表读取
        for (var sheet in workbook.Sheets) {
          if (workbook.Sheets.hasOwnProperty(sheet)) {
            fromTo = workbook.Sheets[sheet]['!ref'];
            buildings = buildings.concat(XLSX.utils.sheet_to_json(workbook.Sheets[sheet]));
          }
        }
        console.log(buildings) // 文件内容
      } catch (e) {
        console.log('文件类型不正确', e);
        return;
      }
    }
    return false;
},
复制代码