VUE项目处理PDF文档流数据并实现PDF的预览以及打印功能以及处理PDF打印乱码问题

1.业务需求:
  • vue项目需要对后端返回的PDF文档流数据进行处理,在前端实现处理后的PDF文件的预览以及打印功能,最后再处理PDF页面打印乱码问题。
2.需求分析:
  • 首先由于后端返回的是文档流数据,所以第一步要将文档流数据转换为正确的PDF格式的数据,第二步是要将转化的PDF数据在页面上进行预览(这里我使用的是vue-pdf插件),最后是要在预览的PDF页面上添加打印按钮以及实现打印功能(PDF打印功能vue-pdf插件里面有方法提供,不用再单独去引入外部的打印插件)。另外由于vue-pdf插件使用的是iframe进行渲染,所以存在乱码问题,所以需要对其源文件进行修改。
3.解决方案:
  • 第1步是处理后端返回的文档流数据(注意:请求PDF文档流数据的接口时如果是axios封装的post请求注意使用params,{responseType:'blob'},如果是axios封装的get请求注意使用{params:params,responseType:'blob'},否则文档流解析会报错,或者解析出来是一片空白):
// 将返回的流数据转换为url(data: 文档流数据)
        getObjectURL(data) {
          let url = null
          let file = new Blob([data],{type:'application/pdf;chartset=utf-8'})
          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赋值
          this.pdfUrl = url
        },
  • 第2步是下载及引入vue-pdf:
// 首先是安装vue-pdf
  npm install --save vue-pdf
// 在页面引入
import pdf from 'vue-pdf'
components: {
      pdf,
    },
  • 第3步是将转化好的PDF数据通过vue-pdf插件进行预览及打印:
<div v-if="src" style="overflow-y: auto;overflow-x: hidden;">
    <!--打印直接调用vue-pdf里面的方法-->
        <el-button type="primary" @click="$refs.pdf.print()" size="mini">打印</el-button>
        <el-button type="warning" plain @click="viewDia = false" size="mini">关闭</el-button>
        <div>
          <pdf
            ref="pdf"
            :src="pdfUrl"
          ></pdf>
        </div>
      </div>
  • 第四部是处理PDF打印乱码问题:
  • 详情可参考https://github.com/FranckFreiburger/vue-pdf/pull/130/commits/253f6186ff0676abf9277786087dda8d95dd8ea7
  • 解决方法是修改pdfjsWrapper.js文件(索引:node_modules => vue-pdf => src => pdfjsWrapper.js)
  • 最后附上我这边最新的修改的pdfjsWrapper.js全部内容(亲测无误,可直接复制):
import { PDFLinkService } from 'pdfjs-dist/es5/web/pdf_viewer';
  
  var pendingOperation = Promise.resolve();
  
  export default function(PDFJS) {
  
  	function isPDFDocumentLoadingTask(obj) {
  
  		return typeof(obj) === 'object' && obj !== null && obj.__PDFDocumentLoadingTask === true;
  		// or: return obj.constructor.name === 'PDFDocumentLoadingTask';
  	}
  
  	function createLoadingTask(src, options) {
  
  		var source;
  		if ( typeof(src) === 'string' )
  			source = { url: src };
  		else if ( src instanceof Uint8Array )
  			source = { data: src };
  		else if ( typeof(src) === 'object' && src !== null )
  			source = Object.assign({}, src);
  		else
  			throw new TypeError('invalid src type');
  
  		// source.verbosity = PDFJS.VerbosityLevel.INFOS;
  		// source.pdfBug = true;
  		// source.stopAtErrors = true;
  
  		var loadingTask = PDFJS.getDocument(source);
  		loadingTask.__PDFDocumentLoadingTask = true; // since PDFDocumentLoadingTask is not public
  
  		if ( options && options.onPassword )
  			loadingTask.onPassword = options.onPassword;
  
  		if ( options && options.onProgress )
  			loadingTask.onProgress = options.onProgress;
  
  		return loadingTask;
  	}
  
  
  	function PDFJSWrapper(canvasElt, annotationLayerElt, emitEvent) {
  
  		var pdfDoc = null;
  		var pdfPage = null;
  		var pdfRender = null;
  		var canceling = false;
  
  		canvasElt.getContext('2d').save();
  
  		function clearCanvas() {
  
  			canvasElt.getContext('2d').clearRect(0, 0, canvasElt.width, canvasElt.height);
  		}
  
  		function clearAnnotations() {
  
  			while ( annotationLayerElt.firstChild )
  				annotationLayerElt.removeChild(annotationLayerElt.firstChild);
  		}
  
  		this.destroy = function() {
  
  			if ( pdfDoc === null )
  				return;
  
  			// Aborts all network requests and destroys worker.
  			pendingOperation = pdfDoc.destroy();
  			pdfDoc = null;
  		}
  
  		this.getResolutionScale = function() {
  
  			return canvasElt.offsetWidth / canvasElt.width;
  		}
  
  		this.printPage = function(dpi, pageNumberOnly) {
  
  			if ( pdfPage === null )
  				return;
  
  			// 1in == 72pt
  			// 1in == 96px
  			var PRINT_RESOLUTION = dpi === undefined ? 150 : dpi;
  			var PRINT_UNITS = PRINT_RESOLUTION / 72.0;
  			var CSS_UNITS = 96.0 / 72.0;
  
  			// var iframeElt = document.createElement('iframe');
        var printContainerElement = document.createElement('div');
        printContainerElement.setAttribute('id', 'print-container')
  			// function removeIframe() {
        //
  			// 	iframeElt.parentNode.removeChild(iframeElt);
  			// }
        function removePrintContainer() {
          printContainerElement.parentNode.removeChild(printContainerElement);
        }
  
  			new Promise(function(resolve, reject) {
  
  				// iframeElt.frameBorder = '0';
  				// iframeElt.scrolling = 'no';
  				// iframeElt.width = '0px;'
  				// iframeElt.height = '0px;'
  				// iframeElt.style.cssText = 'position: absolute; top: 0; left: 0';
          //
  				// iframeElt.onload = function() {
          //
  				// 	resolve(this.contentWindow);
  				// }
          //
  				// window.document.body.appendChild(iframeElt);
          printContainerElement.frameBorder = '0';
          printContainerElement.scrolling = 'no';
          printContainerElement.width = '0px;'
          printContainerElement.height = '0px;'
          printContainerElement.style.cssText = 'position: absolute; top: 0; left: 0';
  
          window.document.body.appendChild(printContainerElement);
          resolve(window)
  			})
  			.then(function(win) {
  
  				win.document.title = '';
  
  				return pdfDoc.getPage(1)
  				.then(function(page) {
  
  					var viewport = page.getViewport({ scale: 1 });
  					// win.document.head.appendChild(win.document.createElement('style')).textContent =
            printContainerElement.appendChild(win.document.createElement('style')).textContent =
  						'@supports ((size:A4) and (size:1pt 1pt)) {' +
  							'@page { margin: 1pt; size: ' + ((viewport.width * PRINT_UNITS) / CSS_UNITS) + 'pt ' + ((viewport.height * PRINT_UNITS) / CSS_UNITS) + 'pt; }' +
  						'}' +
              '#print-canvas { display: none }' +
  						'@media print {' +
  							'body { margin: 0 }' +
  							// 'canvas { page-break-before: avoid; page-break-after: always; page-break-inside: avoid }' +
              '#print-canvas { page-break-before: avoid; page-break-after: always; page-break-inside: avoid; display: block }' +
              'body > *:not(#print-container) { display: none; }' +
  						'}'+
  
  						'@media screen {' +
  							'body { margin: 0 }' +
  						'}'
  					return win;
  				})
  			})
  			.then(function(win) {
  
  				var allPages = [];
  
  				for ( var pageNumber = 1; pageNumber <= pdfDoc.numPages; ++pageNumber ) {
  
  					if ( pageNumberOnly !== undefined && pageNumberOnly.indexOf(pageNumber) === -1 )
  						continue;
  
  					allPages.push(
  						pdfDoc.getPage(pageNumber)
  						.then(function(page) {
  
  							var viewport = page.getViewport({ scale: 1 });
  
  							// var printCanvasElt = win.document.body.appendChild(win.document.createElement('canvas'));
                var printCanvasElt = printContainerElement.appendChild(win.document.createElement('canvas'));
                printCanvasElt.setAttribute('id', 'print-canvas')
  							printCanvasElt.width = (viewport.width * PRINT_UNITS);
  							printCanvasElt.height = (viewport.height * PRINT_UNITS);
  
  							return page.render({
  								canvasContext: printCanvasElt.getContext('2d'),
  								transform: [ // Additional transform, applied just before viewport transform.
  									PRINT_UNITS, 0, 0,
  									PRINT_UNITS, 0, 0
  								],
  								viewport: viewport,
  								intent: 'print'
  							}).promise;
  						})
  					);
  				}
  
  				Promise.all(allPages)
  				.then(function() {
  
  					win.focus(); // Required for IE
  					if (win.document.queryCommandSupported('print')) {
  						win.document.execCommand('print', false, null);
  						} else {
  						win.print();
  					  }
  					// removeIframe();
            removePrintContainer();
  				})
  				.catch(function(err) {
  
  					// removeIframe();
            removePrintContainer();
  					emitEvent('error', err);
  				})
  			})
  		}
  
  		this.renderPage = function(rotate) {
  			if ( pdfRender !== null ) {
  
  				if ( canceling )
  					return;
  				canceling = true;
  				pdfRender.cancel();
  				return;
  			}
  
  			if ( pdfPage === null )
  				return;
  
  			var pageRotate = (pdfPage.rotate === undefined ? 0 : pdfPage.rotate) + (rotate === undefined ? 0 : rotate);
  
  			var scale = canvasElt.offsetWidth / pdfPage.getViewport({ scale: 1 }).width * (window.devicePixelRatio || 1);
  			var viewport = pdfPage.getViewport({ scale: scale, rotation:pageRotate });
  
  			emitEvent('page-size', viewport.width, viewport.height, scale);
  
  			canvasElt.width = viewport.width;
  			canvasElt.height = viewport.height;
  
  			pdfRender = pdfPage.render({
  				canvasContext: canvasElt.getContext('2d'),
  				viewport: viewport
  			});
  
  			annotationLayerElt.style.visibility = 'hidden';
  			clearAnnotations();
  
  			var viewer = {
  				scrollPageIntoView: function(params) {
  					emitEvent('link-clicked', params.pageNumber)
  				},
  			};
  
  			var linkService = new PDFLinkService();
  			linkService.setDocument(pdfDoc);
  			linkService.setViewer(viewer);
  
  			pendingOperation = pendingOperation.then(function() {
  
  				var getAnnotationsOperation =
  				pdfPage.getAnnotations({ intent: 'display' })
  				.then(function(annotations) {
  
  					PDFJS.AnnotationLayer.render({
  						viewport: viewport.clone({ dontFlip: true }),
  						div: annotationLayerElt,
  						annotations: annotations,
  						page: pdfPage,
  						linkService: linkService,
  						renderInteractiveForms: false
  					});
  				});
  
  				var pdfRenderOperation =
  				pdfRender.promise
  				.then(function() {
  
  					annotationLayerElt.style.visibility = '';
  					canceling = false;
  					pdfRender = null;
  				})
  				.catch(function(err) {
  
  					pdfRender = null;
  					if ( err instanceof PDFJS.RenderingCancelledException ) {
  
  						canceling = false;
  						this.renderPage(rotate);
  						return;
  					}
  					emitEvent('error', err);
  				}.bind(this))
  
  				return Promise.all([getAnnotationsOperation, pdfRenderOperation]);
  			}.bind(this));
  		}
  
  
  		this.forEachPage = function(pageCallback) {
  
  			var numPages = pdfDoc.numPages;
  
  			(function next(pageNum) {
  
  				pdfDoc.getPage(pageNum)
  				.then(pageCallback)
  				.then(function() {
  
  					if ( ++pageNum <= numPages )
  						next(pageNum);
  				})
  			})(1);
  		}
  
  
  		this.loadPage = function(pageNumber, rotate) {
  
  			pdfPage = null;
  
  			if ( pdfDoc === null )
  				return;
  
  			pendingOperation = pendingOperation.then(function() {
  
  				return pdfDoc.getPage(pageNumber);
  			})
  			.then(function(page) {
  
  				pdfPage = page;
  				this.renderPage(rotate);
  				emitEvent('page-loaded', page.pageNumber);
  			}.bind(this))
  			.catch(function(err) {
  
  				clearCanvas();
  				clearAnnotations();
  				emitEvent('error', err);
  			});
  		}
  
  		this.loadDocument = function(src) {
  
  			pdfDoc = null;
  			pdfPage = null;
  
  			emitEvent('num-pages', undefined);
  
  			if ( !src ) {
  
  				canvasElt.removeAttribute('width');
  				canvasElt.removeAttribute('height');
  				clearAnnotations();
  				return;
  			}
  
  			// wait for pending operation ends
  			pendingOperation = pendingOperation.then(function() {
  
  				var loadingTask;
  				if ( isPDFDocumentLoadingTask(src) ) {
  
  					if ( src.destroyed ) {
  
  						emitEvent('error', new Error('loadingTask has been destroyed'));
  						return
  					}
  
  					loadingTask = src;
  				} else {
  
  					loadingTask = createLoadingTask(src, {
  						onPassword: function(updatePassword, reason) {
  
  							var reasonStr;
  							switch (reason) {
  								case PDFJS.PasswordResponses.NEED_PASSWORD:
  									reasonStr = 'NEED_PASSWORD';
  									break;
  								case PDFJS.PasswordResponses.INCORRECT_PASSWORD:
  									reasonStr = 'INCORRECT_PASSWORD';
  									break;
  							}
  							emitEvent('password', updatePassword, reasonStr);
  						},
  						onProgress: function(status) {
  
  							var ratio = status.loaded / status.total;
  							emitEvent('progress', Math.min(ratio, 1));
  						}
  					});
  				}
  
  				return loadingTask.promise;
  			})
  			.then(function(pdf) {
  
  				pdfDoc = pdf;
  				emitEvent('num-pages', pdf.numPages);
  				emitEvent('loaded');
  			})
  			.catch(function(err) {
  
  				clearCanvas();
  				clearAnnotations();
  				emitEvent('error', err);
  			})
  		}
  
  		annotationLayerElt.style.transformOrigin = '0 0';
  	}
  
  	return {
  		createLoadingTask: createLoadingTask,
  		PDFJSWrapper: PDFJSWrapper,
  	}
  }.then(function(pdf) {
  
  				pdfDoc = pdf;
  				emitEvent('num-pages', pdf.numPages);
  				emitEvent('loaded');
  			})
  			.catch(function(err) {
  
  				clearCanvas();
  				clearAnnotations();
  				emitEvent('error', err);
  			})
  		}
  
  		annotationLayerElt.style.transformOrigin = '0 0';
  	}
  
  	return {
  		createLoadingTask: createLoadingTask,
  		PDFJSWrapper: PDFJSWrapper,
  	}
  }
  • 统一说明:最近有码友反馈修改pdfjsWrapper.js不生效,这里大家注意一下vue-pdf版本不要太高了,参考我使用的4.2.0版本,不然会有问题。
  • 解决