vue实现pdf文件在线预览
本人使用的是pdfjs-dist插件配合canvas来一起使用
第一步
下载依赖: npm install pdfjs-dist --save
废话不多说直接上代码
html部分
注意这里使用了elementUI组件库中的分页组件
//publicMethods.js文件
/**
* @url {Sring} 下载地址流文件的 必填
* @fileName {String} 下载文件的名称
* @description 用于下载流文件格式的方法
*/
export function downloadFileBlod (url, fileName) {
const x = new window.XMLHttpRequest()
x.open('GET', url, true)
x.responseType = 'blob'
x.onload = () => {
const href = window.URL.createObjectURL(x.response)
const a = document.createElement('a')
a.href = href
a.download = fileName
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(href)
}
x.send()
}
//pdfView组件
<template>
<div class="model" v-if="dialogVisible">
<span class="el-icon-close absoluteBtn" @click="dialogVisible=false"></span>
<div class="dialogHeader" v-if="pdfSrc">
<span title="缩小" :class="`bnt el-icon-zoom-out ${pdfScale<=0.7?'disabled':''}`" @click="scaleReduce()"></span>
<span title="放大" :class="`bnt el-icon-zoom-in ${pdfScale>=2.0?'disabled':''}`" @click="scaleAdd()"></span>
<span title="向左旋转" :class="`bnt el-icon-refresh-left ${rotateDeg<=-4?'disabled':''}`" @click="rotateDeg--"></span>
<span title="向右旋转" :class="`bnt el-icon-refresh-right ${rotateDeg>=4?'disabled':''}`" @click="rotateDeg++"></span>
<span title="下载" :class="`bnt el-icon-download`" @click="downloadFileBlod(pdfSrc,pdfName)"></span>
</div>
<div class="dialogBody">
<div
:style="`margin:0 auto;width:${pdfWidth}px;`"
v-loading="isLoading"
element-loading-text="加载中..."
>
<canvas :style="`transform:rotate(${rotateDeg*90}deg);`" id="pdfCanvas"></canvas>
</div>
</div>
<div class="dialogFooter">
//来自于elementui的分页组件(可以自行修改)
<el-pagination
background
layout="slot,prev, pager, next"
:total="totalPages"
:page-size="1"
:current-page="currentPage"
@current-change="changePagination"
>
<span key="1">共 {{totalPages}} 页</span>
</el-pagination>
</div>
</div>
</template>
js部分
//pdfView组件
<script>
import { downloadFileBlod } from './publicMethods' //引入下载方法
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry'
import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist' //引入pdfjs-dist
GlobalWorkerOptions.workerSrc = pdfjsWorker //手动改变workerSrc指向pdfjs-dist/build/pdf.worker.entry(这是pdfjs-dist的坑)
export default {
props: {
visible: {
type: Boolean,
default: false
},
pdfSrc: {
type: String,
default: ''
},
pdfName: {
type: String,
default: ''
}
},
data () {
return {
totalPages: null, // 总页数
currentPage: 1, // 当前页
pageRendering: false, // 是否正在渲染
pageNumPending: null, // 待处理页码
pdfWidth: 714, // 宽度
pdfDoc: null, // 文档内容
pdfScale: 1.2, // 放大倍数
isLoading: false, // 文档是否正在加载
rotateDeg: 0,//旋转
}
},
computed: {
//开启与关闭的数据双向绑定
dialogVisible: {
get () {
return this.visible
},
set (val) {
this.$emit('update:visible', val)
}
}
},
mounted () {
// 把这个组件放在body内的最后一个
this.$nextTick(() => {
const body = document.querySelector('body')
if (body.append) {
body.append(this.$el)
} else {
body.appendChild(this.$el)
}
})
this.getPdfUrl()
},
watch: {
pageRendering (newVal, old) {
this.isLoading = newVal
}
},
methods: {
downloadFileBlod,
getPdfUrl () {
// todo 请求后台,获取pdf的url,注意这里不能是本地的pdf文件
this.loadFile(this.pdfSrc)
},
loadFile (url) {
// 通过promise获取页面
const loadingTask = getDocument(url)
this.pageRendering = true
loadingTask.promise.then(pdf => {
this.pdfDoc = pdf
this.totalPages = pdf.numPages
this.$nextTick(() => {
this.renderPage(1) // 初始/首页渲染
})
})
},
renderPage (num) {
this.currentPage = num // 更新当前页码
this.pdfDoc.getPage(num).then(page => {
const canvas = document.getElementById('pdfCanvas')
const ctx = canvas.getContext('2d')
const dpr = window.devicePixelRatio || 1
const bsr =
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1
const ratio = dpr / bsr
const viewport = page.getViewport({ scale: this.pdfScale })
canvas.width = viewport.width * ratio
canvas.height = viewport.height * ratio
canvas.style.width = viewport.width + 'px'
this.pdfWidth = viewport.width
canvas.style.height = viewport.height + 'px'
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
// 将 PDF 页面渲染到 canvas 上下文中
const renderContext = {
canvasContext: ctx,
viewport
}
const renderTask = page.render(renderContext)
renderTask.promise.then(() => {
this.pageRendering = false
if (this.pageNumPending === null) return
this.renderPage(this.pageNumPending)
this.pageNumPending = null
})
})
},
queueRender (num) {
// 渲染等待;如果正在进行另一个页面渲染,请等待渲染完成。 否则,立即执行渲染
!this.pageRendering ? this.renderPage(num) : (this.pageNumPending = num)
},
//放大
scaleAdd () {
const num = Number(this.pdfScale) + 0.1
this.pdfScale = num < 2 ? Number(num).toFixed(1) : 2.0
this.queueRender(this.currentPage)
},
//缩小
scaleReduce () {
const num = Number(this.pdfScale) - 0.1
this.pdfScale = num > 0.7 ? Number(num).toFixed(1) : 0.7
this.queueRender(this.currentPage)
},
//改变页码
changePagination (e) {
this.currentPage = e
this.queueRender(this.currentPage)
}
}
}
</script>
css部分
//pdfView组件
<style lang="scss" scoped>
.model{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
flex-direction: column;
}
.dialogHeader{
z-index: 1000;
position: absolute;
left: 50%;
bottom: 40px;
transform: translateX(-50%);
height: 44px;
padding: 0 23px;
background-color: rgba(0, 0, 0, 0.5);
border-color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
.bnt{
cursor: pointer;
user-select: none;
padding: 0!important;
font-size: 20px!important;
color: white;
display: inline-block;
height: 40px;
line-height: 40px;
margin: 0 10px;
text-align: center;
}
.disabled{
color: #bbb;
pointer-events:none;
cursor: not-allowed;
}
}
.dialogBody{
flex: 1;
width: 100%;
overflow: auto;
}
.dialogFooter{
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
background-color: #fff;
padding: 0 15px;
border-radius: 15px;
}
#pdfCanvas{
border: 1px solid #ddd;
min-height: 792px;
}
.absoluteBtn{
display: inline-block;
position: absolute;
text-align: center;
color: #fff;
background-color: #606266;
cursor: pointer;
user-select: none;
border-radius: 50%;
}
.el-icon-close{
line-height: 40px;
top: 40px;
right: 40px;
width: 40px;
height: 40px;
font-size: 24px;
}
</style>
pdfView组件的使用
<template>
<div>
<el-button type="text" @click="viewPdf(row)">查看</el-button>
<PdfView
:visible.sync="pdfVisible"
v-if="pdfVisible"
:pdfSrc="pdfUrl"
:pdfName="pdfName"
></PdfView>
</div>
<template>
<script>
import PdfView from '@/components/PdfView'
export default {
components: { PdfView },
data () {
return {
demodata:{
docUrl:'http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf',
pdfName:'palyerApi电子文件'
},
// pdf预览和下载
pdfVisible: false,
pdfUrl: '',//pdf文件地址
pdfName: ''//pdf文件名称
}
},
methods
viewPdf (row) {
this.pdfUrl = row.docUrl
this.pdfName = row.docName
this.pdfVisible = true
}
}
}
</script>
代码直接用,亲测有效