之前做PDF预览一直用的pdf.js,这次没有太多附加需求比较简单简,所以决定用vue-pdf这个组件,虽然说它没有原生那样强大,但已经满足常用的需求了,重要是这个组件使用简单。顺便也把word文件和excel表格预览也整理了下,接下来我们直接进入正题!
一、预览word文件
1.安装 npm 依赖
npm i docx-preview@0.1.4
npm i jszip
2.预览在线地址文件
<template>
<div class="home">
<div ref="file"></div>
</div>
</template>
<script>
import axios from 'axios'
const docx = require('docx-preview');
window.JSZip = require('jszip')
export default {
mounted(){
axios({
method: 'get',
responseType: 'blob', // 设置响应文件格式
url: '/docx',
}).then(({data}) => {
docx.renderAsync(data,this.$refs.file) // 渲染到页面预览
})
}
}
</script>
3.预览本地文件
<template>
<div class="my-component" ref="preview">
<input type="file" @change="preview" ref="file">
</div>
</template>
<script>
const docx = require('docx-preview');
window.JSZip = require('jszip')
export default {
methods:{
preview(e){
docx.renderAsync(this.$refs.file.files[0],this.$refs.preview) // 渲染到页面预览
}
}
};
</script>
<style lang="less" scoped>
.my-component{
width: 100%;
height: 90vh;
border: 1px solid #000;
}
</style>
二、预览excel表格
1.安装依赖
npm i xlsx
2.预览在线表格
<template>
<div class="home">
<div v-html="tableau"></div>
</div>
</template>
<script>
import axios from 'axios'
import XLSX from "xlsx";
export default {
data(){
return {
tableau: null,
}
},
mounted(){
axios.get('/xlsx',{
responseType: "arraybuffer", // 设置响应体类型为arraybuffer
}).then(({data})=> {
let workbook = XLSX.read(new Uint8Array(data), {type:"array"}); // 解析数据
var worksheet = workbook.Sheets[workbook.SheetNames[0]]; // workbook.SheetNames 下存的是该文件每个工作表名字,这里取出第一个工作表
this.tableau = XLSX.utils.sheet_to_html(worksheet); // 渲染
})
}
}
</script>
三、pdf预览
1.安装依赖vue-pdf
npm install --save vue-pdf
2.在需要的页面注册
<script>
import PDF from 'vue-pdf'
export default {
components:{
PDF,
},
data(){
return {
}
}
</script>
3.使用,一般预览pdf时都是很多页的,为了提升性能可以做个分页。page: 当前显示的页数,比如第一页page=1;rotate : 旋转角度,比如0就是不旋转,+90,-90 就是水平旋转。progress :当前页面的加载进度,范围是0-1 ,等于1的时候代表当前页已经完全加载完成了。page-loaded :页面加载成功的回调函数,不咋能用到。num-pages :总页数;error :加载错误的回调link-clicked:单机pdf内的链接会触发。print 这个是打印函数。(注意:谷歌浏览器预览时会出现乱码,这个问题是因为你pdf中使用了自定义字体导致的。若要解决须替换node_modules/vue-pdf/src/pdfjsWrapper.js,替换的pdfjsWrapper.js我放在文章最后)
<!-- 预览PDF -->
<el-dialog v-dialogDrag :visible.sync="previewDialog">
<template>
<div>
<div class="tools">
<el-button :theme="'default'" type="submit" :title="'上一页'" @click.stop="prePage" class="mr10"> 上一页</el-button>
<el-button :theme="'default'" type="submit" :title="'下一页'" @click.stop="nextPage" class="mr10"> 下一页</el-button>
<div class="page">{{pageNum}}/{{pageTotalNum}} </div>
<el-button :theme="'default'" type="submit" :title="'顺时针旋转'" @click.stop="clock" class="mr10"> 顺时针旋转</el-button>
<el-button :theme="'default'" type="submit" :title="'逆时针旋转'" @click.stop="counterClock" class="mr10"> 逆时针旋转</el-button>
<el-button :theme="'default'" type="submit" :title="'打印'" @click.stop="pdfPrintAll" class="mr10"> 打印</el-button>
</div>
<pdf ref="pdf" :src="url" :page="pageNum" :rotate="pageRotate" @progress="loadedRatio = $event" @page-loaded="pageLoaded($event)"
@num-pages="pageTotalNum=$event" @error="pdfError($event)" @link-clicked="page = $event"></pdf>
</div>
</template>
</el-dialog>
<script>
import PDF from 'vue-pdf'
export default {
components:{
PDF,
},
data(){
return {
previewDialog:false,
url: "http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf",
pageNum: 1,
pageTotalNum: 1,
pageRotate: 0,
// 加载进度
loadedRatio: 0,
curPageNum: 0,
},
methods:{
/**
* 预览PDF
*/
previewPDF(row,index) {
this.previewDialog = true;
console.log("", row,index);
},
// 上一页函数,
prePage() {
var page = this.pageNum
page = page > 1 ? page - 1 : this.pageTotalNum
this.pageNum = page
},
// 下一页函数
nextPage() {
var page = this.pageNum
page = page < this.pageTotalNum ? page + 1 : 1
this.pageNum = page
},
// 页面顺时针翻转90度。
clock() {
this.pageRotate += 90
},
// 页面逆时针翻转90度。
counterClock() {
this.pageRotate -= 90
},
// 页面加载回调函数,其中e为当前页数
pageLoaded(e) {
this.curPageNum = e
},
// 错误时回调函数。
pdfError(error) {
console.error(error)
},
// 打印全部
pdfPrintAll() {
/**
* 打印界面字符乱码是因为你pdf中使用了自定义字体导致的,谷歌浏览器打印的时候预览界面真的变成了真·方块字 ,解决方案如下:
* 用文章最后的pdfjsWrapper.js在替换掉node_modules/vue-pdf/src/pdfjsWrapper.js
*/
console.log("打印");
this.$refs.pdf.print()
},
},
}
</script>
4.加载本地pdf文件
如果需要加载本地pdf文件(不需要则不用配置),需要我们做一下本地配置flie-loader才行,否则webpack无法编译pdf类型的文件,配置方法也很简单,在项目根目录找到vue.config.js文件(若没有则在根目录下新建一个vue.config.js)。之后再url:require(“…/assets/xxx.pdf”)就没有任何问题了,注意,vue-pdf src接收的是string对象,如果直接传url这里报错了,需要传url.default一下。
先安装file-loader
npm install --save file-loader
然后在vue.config.js中加入以下内容:
module.exports = {
chainWebpack: config => {
const fileRule = config.module.rule('file')
fileRule.uses.clear()
fileRule
.test(/\.pdf|ico$/)
.use('file-loader')
.loader('file-loader')
.options({
limit: 10000,
})
},
publicPath: './'
}
5.解决pdf使用自定义字体预览和打印乱码问题: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;
if ( options && options.withCredentials )
source.withCredentials = options.withCredentials;
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 printContainerElement = document.createElement('div');
printContainerElement.setAttribute('id', 'print-container')
function removePrintContainer() {
printContainerElement.parentNode.removeChild(printContainerElement);
}
new Promise(function(resolve, reject) {
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 });
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 }' +
'#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 = 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();
}
removePrintContainer();
})
.catch(function(err) {
removePrintContainer();
emitEvent('error', err);
})
})
}
this.renderPage = function(rotate) {
if ( pdfRender !== null ) {
if ( canceling )
return;
canceling = true;
pdfRender.cancel().catch(function(err) {
emitEvent('error', err);
});
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,
}
}