后端返回PDF文件流,前端处理展示及打印(补:PDF多页不显示问题)

Posted 荒芜 i

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后端返回PDF文件流,前端处理展示及打印(补:PDF多页不显示问题)相关的知识,希望对你有一定的参考价值。

国庆突如其来的加班q.q,上午接到的需求,只能起床啦

自己也是第一次接触这种业务需求,进行了一顿cv大法,网上有很多版本,个人学习借鉴(cv)了一下,在这里做个学习记录,欢迎各位指正优化,参考学习,如有侵权立即删除。

printDialog.vue(PDF使用弹窗展示,确认btn打印)

html

<template>
  <div>
    <el-dialog title="打印" :visible.sync="dialogVisible" width="50%">
        <div class="box">
          <pdf ref="pdf" :src="pdfUrl"></pdf>
        </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button
          type="primary"
        @click="$refs.pdf.print()"
          >确 定</el-button
        >
      </span>
    </el-dialog>
  </div>
</template>

js

<script>
//插件vue-pdf-signature
import pdf from "vue-pdf-signature";
import CMapReaderFactory from "vue-pdf-signature/src/CMapReaderFactory.js";
export default 
    components: 
    pdf,
  ,
  data() 
    return 
      dialogVisible: false,
      pdfUrl:''
    ;
  ,
  methods:
      init(res)
          this.dialogVisible = true;
          this.getObjectURL(res)
      ,
      // 将返回的文件流数据转换为url(data: 文件流数据)
    getObjectURL(data) 
      let url = null;
      let file = new Blob([data],  type: "application/pdf" );
      if (window.createObjectURL != undefined) 
        // 通用
        url = window.createObjectURL(file);
       else if (window.webkitURL != undefined) 
        // 兼容谷歌
        try 
          url = window.webkitURL.createObjectURL(file);
         catch (error) 
       else if (window.URL != undefined) 
        // 兼容其他
        try 
          url = window.URL.createObjectURL(file);
         catch (error) 
      
      //这里是重点,将处理的url使用CMapReaderFactory方法在进行处理
      url = pdf.createLoadingTask( url: url, CMapReaderFactory );
      // 将转化后url赋值
      this.pdfUrl = url;
    ,
  
;
</script>

父组件.vue(btn点击)

HTMl

<el-button size="small" type="primary" @click="subscriptionPrinting()"
          >打印</el-button>

//弹窗
<print-dialog ref="printDialog" ></print-dialog>

js

<script>
import printDialog from "./printDialog.vue";
export default 
  components: 
    printDialog,
  ,
  methods:
  subscriptionPrinting() 
      this.$http
        .get(
          `请求~~~`,
          
            responseType: "blob",//这个必须带
          
        )
        .then(( data: res ) => 
          this.$refs['printDialog'].init(res);
        );
    ,
  
</script>

以上就是全部代码了,坑还是比较多的以下来进行总结

1.插件的使用

  使用的是vue-pdf-signature而不是vue-pdf,是因为vue-pdf在第一次正常打开会正常显示,第二次打开会显示空白,控制台会提示报错(Error during font loading: Failed to execute 'postMessage' on 'Worker': ArrayBuffer at index 0 is already detached),vue-pdf-signature是大佬针对vue-pdf出现的这一问题进行的改良版。细看可前往大佬链接~

vue-pdf踩坑指南_快进小树林的博客-CSDN博客

2.不显示中文

  通过引入CMapReaderFactory.js解决,但是引入会出现1所描述的问题,所以采用了vue-pdf-signature进行解决(崇拜大佬ing)

3.向后端请求解析失败问题(显示空白,控制台提示报错)

  在请求中添加
             responseType: "blob",//这个必须带
            即可解决。

  请求回的文件流所做的数据处理在上面代码有注释,详看翻上

4.打印调用$refs.pdf.print()即可。

*补:PDF多页不展示问题

在处理返回流的方法(getObjectURL)中,this.numPages在data中

getObjectURL(data) 
      let url = null;
      let file = new Blob([data],  type: "application/pdf" );
      if (window.createObjectURL != undefined) 
        // 通用
        url = window.createObjectURL(file);
       else if (window.webkitURL != undefined) 
        // 兼容谷歌
        try 
          url = window.webkitURL.createObjectURL(file);
         catch (error) 
       else if (window.URL != undefined) 
        // 兼容其他
        try 
          url = window.URL.createObjectURL(file);
         catch (error) 
      
      //这里是重点,将处理的url使用CMapReaderFactory方法在进行处理
      url = pdf.createLoadingTask( url: url, CMapReaderFactory );
      url.promise.then(pdf=>
        this.unmPages = pdf.numPages
      // 将转化后url赋值
      this.pdfUrl = url;
    ,

html处

<div class="box">
    <pdf ref="pdf" :src="pdfUrl" v-for="item in numPages" :key="index" :page="item"></pdf>
</div>

<el-button
          type="primary"
        @click="$refs.pdf[0].print()"
          >打 印</el-button
        >

处理pdf多页不展示问题

pdf流文件的展示下载打印

背景:合同(后台返回pdf流文件)展示、下载、打印,基于angular4

场景区分:

1、checkout页面 —— post接口,入参为offering、shippingInfo、invoice等(body),返回生成合同的pdf流文件;

2、orderList、orderDetail页面 —— get接口,入参contentId(生成合同在内容管理保存对应的id,url),返回对应合同的pdf流文件。

需求补充说明:

在一个弹出dialog,合同展示在中间部分,footer部分为 下载、打印等按钮。

实现核心:

1、展示:

1> package.json中

  "dependencies": {

   "ng2-pdf-viewer": "^3.0.8",

   "pdfjs-dist": "^1.9.426" 

     }

2> html

展示:

  <pdf-viewer *ngIf ="contract.stc" [src]="contract.src" [render-text]="true"  (after-load-complete)="afterLoadComplete($event)" (error)="loadContractError($event)" ></pdf-viewer>

下载:

  <a id="contract_download" class="fl mar1d5r mat6" [href]=‘sanitize(contract.src)‘ [download]="contract.name" *ngIf="contract.isLoadSuccess" >
    <svg class="hlds-icon fillWhite icon2r">
      <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="./assets/theme/default/icons/omniIcon-sprite/svg/symbols.svg#downLoad"></use>
    </svg>
  </a>

打印:
  <a class="fl mat6" (click)="print()" *ngIf="contract.isLoadSuccess">
    <svg class="hlds-icon fillWhite icon2r" >
      <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="./assets/theme/default/icons/omniIcon-sprite/svg/symbols.svg#print"></use>
    </svg>
  </a>
  <iframe #iframe [src]="contract.url" style=‘display:none;‘ *ngIf="contract.isLoadSuccess" ></iframe>

3>ts文件

 

  import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges, ViewChild, ElementRef } from ‘@angular/core‘;
  import { DomSanitizer, SafeResourceUrl } from ‘@angular/platform-browser‘;
  import { Router, ActivatedRoute, ParamMap } from ‘@angular/router‘;

  import { ConstantService } from ‘../../../../service/constant.service‘;
  import { MessageService } from "../../../../service/message.service";
  import { NormalHttpService } from "../../../../service/normal-http.service";

  import { SmartrecCommonService } from ‘../../../bizcommon/service/smartrec.service‘;
  import { OrderService } from ‘../../service/order.service‘;

  declare var $: any;

  @Component({
    selector: ‘app-order-contract‘,
    templateUrl: ‘./contract.component.html‘,
    styleUrls: [‘./contract.component.css‘]
   })
  export class ContractComponent implements OnInit, OnChanges {
    /*contractInfo : any = {
      isGenerated : true,
      orderId: ‘‘,
      orderInfo : {} //the param that generate contract
    }*/
  @ViewChild(‘iframe‘) iframe: ElementRef;
  @Input() contractInfo : any ;
  @Output() acceptContractState = new EventEmitter<any>();

  contract : any = {
    src : ‘‘,
    url : ‘‘,
    totalPage : 1,
    page : 1,
    isLoading : true,
    isLoadSuccess: false
  }

  constructor(
   private router: Router,
   private sanitizer: DomSanitizer,
   private messageService: MessageService,
   private normalHttpService: NormalHttpService,
   private smartrecCommonService: SmartrecCommonService,
   private orderService: OrderService
  ) { }

  ngOnInit() {
  }

  ngOnChanges(change: SimpleChanges) {
    if(this.contractInfo && this.contractInfo.isGenerated && change.contractInfo.currentValue.orderInfo){
     this.contract.isLoadSuccess = false;
     this.generateContract();
      }
    if(this.contractInfo && !this.contractInfo.isGenerated && this.contractInfo.orderId) {
      this.queryContract();
    }
  }

  generateContract() {
   if(!this.contract.isLoadSuccess){
    this.contract.src = ‘‘;
    this.contract.isLoading = true;
    let header = this.normalHttpService.expandHeader({}, true);
    let url = this.normalHttpService.makeRequestUrl(`/order/order/${ConstantService.API_VERSION}/contract`);
    let xhr = new XMLHttpRequest();
    xhr.open("POST", url, true);
    xhr.responseType = "blob";
    for(let key in header) {
      xhr.setRequestHeader(key, header[key]);
    }
    xhr.send(JSON.stringify(this.contractInfo.orderInfo));
    xhr.onreadystatechange = function () {
    if (xhr.readyState == 4 && xhr.status == 200) {
      let blob = new Blob([xhr.response], {type: ‘application/pdf‘});
      this.contract.src = URL.createObjectURL(blob);
      this.contract.name = ‘contract.pdf‘;
      this.contract.url = this.sanitizer.bypassSecurityTrustResourceUrl(this.contract.src);
    }
  }.bind(this);
  }
}

  queryContract() {
   this.contract.src = ‘‘;
   this.orderService.queryContractId(this.contractInfo.orderId).then((res) => {
    if (res.code !== 0) {
      this.messageService.errorAlert(‘Query contractId is failed !‘);
      return;
    }
    if (res.data.contentId) {
      this.contract.src = this.smartrecCommonService.getContentUrlById(res.data.contentId);
    }
   })
  }

  afterLoadComplete(pdf: any) {
    this.contract.totalPage = pdf.pdfInfo.numPages;
    this.contract.isLoading = false;
    this.contract.isLoadSuccess = true;
  }

  loadContractError(event: any) {
    this.contract.isLoading = false;
    this.contract.isLoadSuccess = false;
    this.messageService.errorAlert(‘Contract failed to load !‘);
  }

  agree() {
    this.acceptContractState.emit({
      isAgree : true,
      isLoadSuccess : this.contract.isLoadSuccess
    })
  }

  disAgree() {
    this.acceptContractState.emit({
      isAgree : false,
      isLoadSuccess : this.contract.isLoadSuccess
    })
  }

  print() {
    let getMyFrame = this.iframe.nativeElement.contentWindow || this.iframe.nativeElement.contentDocument;
    getMyFrame.print();
  }

  changePage(event) {
   const pageHeight = $(‘.ng2-pdf-viewer-container‘).height() / this.contract.totalPage;
   this.contract.page = Math.ceil($(event.target).scrollTop()/pageHeight);
  }

  sanitize(url:string){
    return this.sanitizer.bypassSecurityTrustUrl(url);
  }
}

参考:
1.https://github.com/VadimDez/ng2-pdf-viewer //展示打印思路

2.https://stackoverflow.com/questions/38457662/iframe-inside-angular2-component-property-contentwindow-does-not-exist-on-typ/50508477 //pdf局部打印思路

3.http://kriscan.oschina.io/blog/2017/05/26/20170526/  //链接安全问题解决

 

 




 





















































































































































以上是关于后端返回PDF文件流,前端处理展示及打印(补:PDF多页不显示问题)的主要内容,如果未能解决你的问题,请参考以下文章

前端接受后端 blob 文件流 并进行展示和下载

java后端返回多个文件给前端打开

利用 Blob 处理 node 层返回的二进制文件流字符串并下载文件

vue项目-pdf预览和下载,后台返回文件流形式

前端生成PDF,让后端刮目相看

.net webapi后台返回pdf文件流,前端ajax请求下载,空白pdf排错经历