vue实现pdf在线预览

500 阅读1分钟

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">&nbsp;{{totalPages}}&nbsp;</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>

代码直接用,亲测有效

效果图

23AD9B1A-F6B2-4b3d-B1A6-56948718BB17.png