Angular无第三方插件的文件操作(保存/预览/下载)

本文旨在不依靠第三方插件如pdfJsviewerJs 来进行文件的保存,预览和下载主要功能实现。

关键词: Blob FileReader Base64 Iframe

一. 预备知识

1. Blob

对Blob的解释如下:

Blob 对象表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。 File接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

重点:

  1. 不可不,原始数据的类文件对象
  2. File继承了Blob

也就是说,我们通过的File对象的操作,本质上是对一个Blob对象的操作。因此大家不要疑问,为什么我不直接操作File而是针对一个Blob。当然不是说不可以,但从通用性出发使用Blob,而真正需要使用 File 的时候,使用构造函数可以快速的创建。

看一下.d.ts:

interface Blob {
    readonly size: number;
    readonly type: string;
    arrayBuffer(): Promise<ArrayBuffer>;
    slice(start?: number, end?: number, contentType?: string): Blob;
    stream(): ReadableStream;
    text(): Promise<string>;
}

declare var Blob: {
    prototype: Blob;
    new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
};

interface File extends Blob {
    readonly lastModified: number;
    readonly name: string;
}

declare var File: {
    prototype: File;
    new(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File;
};

type BlobPart = BufferSource | Blob | string;

interface BlobPropertyBag {
    endings?: EndingType;
    type?: string;
}

是不是一看声明更加直观,注意几个地方:

  1. 无论是BlobFile,构造函数的第一个参数是数组;
  2. Blob可以没有文件名,但File必须有文件名,另外File多了两个read only: name & lastModified;
  3. optons虽然是可选,但是为了日后操作的简单,建议至少加上type字段,附上Mime 类型列表

2. FileReader()

参考链接:在web应用程序中使用文件

<input type="file" id="input">

上面的代码在Html页面中创建一个文件上传的入口,它会渲染出一个File Input Button,用户点击这个上传按钮选择本地文件进行上传。DOM 提供 FileList 对象列出了用户通过<input>选择的所有文件,每一个文件被指定为一个File对象。

代码中对它的定义:

interface FileList {
    readonly length: number;
    item(index: number): File | null;
    [index: number]: File;
}

declare var FileList: {
    prototype: FileList;
    new(): FileList;
};

很好理解FileList就是File的数组,const file: File = fileList[0]

我们获取文件对象方法很多,直接的办法:

const selectedFile = document.getElementById('input').files[0];

或通过事件监听:

<input type="file" id="input" onchange="handleFiles(this.files)">

那这样得到的File对象就是我们需要的么?

很抱歉,并不是,我们这样获得的File仅仅是对文件的描述信息,包括:

file = {
  lastModified:number // 表示最近一次修改时间的毫秒数;
  lastModifiedDate:Date; // 表示最后一次表示最近一次修改时间的Date对象
  name:string; // 文件名;
  size:number; // 文件的字节大小;
  type:string; // 文件的MIME类型;
}

所以,我们需要借助Html提供的FileReader来获取文件的内容。

也是先看定义(我们简单看最常用的):

interface FileReader extends EventTarget {
    readonly error: DOMException | null;
    readonly readyState: number;
    readonly result: string | ArrayBuffer | null;
    abort(): void; // 中止读取操作。在返回时,readyState属性为DONE。
    onabort: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null; // Event Handler
    onerror: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null; // Event Handler
    onload: ((this: FileReader, ev: ProgressEvent<FileReader>) => any) | null; // Event Handler
    readAsArrayBuffer(blob: Blob): void; // 开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象
    readAsDataURL(blob: Blob): void; // 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的Base64字符串以表示所读取文件的内容
    readAsText(blob: Blob, encoding?: string): void; // 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。

    // ...
}

3. Base64

Base64 是一组相似的二进制到文本(binary-to-text)的编码规则,使得二进制数据在解释成 radix-64 的表现形式后能够用 ASCII 字符串的格式表示出来。Base64 这个词出自一种 MIME 数据传输编码

Base64编码普遍应用于需要通过被设计为处理文本数据的媒介上储存和传输二进制数据而需要编码该二进制数据的场景。这样是为了保证数据的完整并且不用在传输过程中修改这些数据。Base64 也被一些应用(包括使用 MIME 的电子邮件)和在 XML 中储存复杂数据时使用。

在 JavaScript 中,有两个函数被分别用来处理解码和编码 base64 字符串:

  • atob(): 够解码通过base-64编码的字符串数据。
  • btoa():从二进制数据“字符串”创建一个base-64编码的ASCII字符串。

了解以上信息我们就可以直接开始了。

二. 文件的上传与保存

直接上代码:

<!-- Html --> 
<input type="file" name="pic" accept="image/gif" (change)="onfileSelected($event)" >
// .ts only sample for single file upload
public onfileSelected(files: FileList) {
  const file = files[0];
  const fileReader = new FileReader();
  fileReader.onload = () => {
    const uploadedFile = {
      ...file,
      data: this.fileReader.result as string
    };
    // better add the validation and also the error handler
  };
  fileReader.readAsDataURL(files[0]);
}

// post data to server
public onSubmit() {
  this.http.post(url, {
    file: JSON.stringify(this.selectedFile);
  }, options);
}

重点:

  1. 通过<input>创建上传入口;
  2. 通过FileReader.readAsDataURL(File)得到文件内容的Base64字符串;
  3. 将需要的信息整合,最终以字符串形式保存。

三. 文件的下载与预览

基本代码如下:

// ts
public getObjUrl() {
  const blob = this.getPdfBlob();
  return URL.createObjectURL(blob);
 
}

private getfileBlob(dataString: string): Blob {
  try {
    const dataStr = atob(dataString.split(',')[1]); // 去除头部的 type 等非文件内容的字符串
    const u8arr = new Uint8Array(dataStr.length);
    return new Blob([u8arr.map((byte, index) => dataStr.charCodeAt(index))], { type: 'application/xxx' }); // MIME类型
  } catch (error) {
    throw new Error(error);
  }
}

public downloadFile(url: string) {
  if (window.navigator && window.navigator.msSaveBlob) {
    window.navigator.msSaveBlob(blob, this.previewData.name);
  } else {
    const link = document.createElement('a'); // 创建一个<a>标签
    if (link.download !== undefined) {
      link.setAttribute('href', url);  
      link.setAttribute('download', this.previewData.name); // 下载文件名
      link.style.visibility = 'hidden';  // 文件
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    }
  }
}

pulic previewFile(url: string) {
  window.open(url)
}

重点:

  1. 创建Blob时,只需要文件内容的信息,头部的 type 等非文件内容的字符串需要去除;
  2. 字符串转换成Unicode 编码;
  3. 需要声明文件类型;
  4. 下载方法不唯一,在上面的实例中仅是一个参考;
  5. 预览采用的直接打开新界面,通过浏览器自带的浏览功能,可能部分文件无法打开,但对于常见的img, pdf有效,具体预览方法可根据自己需求来进行更改。

提醒:

如果采用<iframe><embed>等来预览的话,确保你的iframe有进行相关的sand-box设置,如allow-downloads等。

以上是所有内容,如有不正确的地方希望各位指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249