vue-pdf 实现pdf预览、高亮、分页、定位功能
vue-pdf 实现pdf预览、高亮、分页、定位功能(基于vue2.0!!!)
- 前言
- 一、实现步骤
- 1.引入库
- 2.示例代码
- 3.触发高亮事件
- 4.分页高亮
- 5.跳转指定页面并高亮(不分页)
- 参考
- 笔记(重要)
- 总结
前言
vue-pdf 实现pdf预览、高亮、分页、定位功能(基于vue2.0!!! )
找了一圈 vue-pdf 高亮,举步维艰,只能自己实现了
效果图:
一、实现步骤
1.引入库
npm install --save vue-pdf
2.示例代码
参考:vue 使用 vue-pdf 实现文件在线预览.md
3.触发高亮事件
<template><el-container><el-main><el-row type='flex'><el-col :span='14'><!-- 文件列表表格 --><el-table :data='dataList'v-loading='loading'ref='table'borderrow-key='id'@row-dblclick='editChunk'@row-click='rowClick'@selection-change='tableSelectChange'><el-table-columntype='selection'reserve-selectionwidth='55'></el-table-column><el-table-column prop='name' label='块'></el-table-column></el-table></el-col><el-col :span='10' ><!-- <pdf-viewer v-if='isPdf' ref='pdf' url='http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf'></pdf-viewer>--><!-- 带分页的高亮 --><pdf-page-viewer v-if='isPdf' ref='pdf' url='http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf'></pdf-page-viewer></el-col></el-row></el-main></el-container>
</template>
<script>
import PdfViewer from './PdfViewer.vue'
import ParseChunkDialog from './ParseChunkDialog.vue'
import VuePdfViewer from './VuePdfViewer.vue'
import PdfPageViewer from './PdfPageViewer.vue'export default {name: 'Chunk',components: { PdfPageViewer, VuePdfViewer, ParseChunkDialog, PdfViewer },props: {docId: {type: String,default: ''}},data () {return {dataList: [{'id': '1','name': 'test',// 后端给出位置,参数分别是 page, left, right, top, bottom'positions': [[2,79,518,106,193],[2,82,520,296,314],[2,330,768,296,314]]}],selectedIds: [], // 已选择列表doc: {},loading: false,loaded: false,totalCount: 0,queryParams: {doc_id: '',keywords: '',size: 10,page: 1}}},created () {},computed: {isPdf () {return true}},methods: {goBack () {this.$emit('goBack')},// 表格多选tableSelectChange (val) {this.selectedIds = val.map(item => item.id)},editChunk (data) {},rowClick (data) {this.$refs.pdf && this.$refs.pdf.highlight(data.positions)},// 获取id集getIds (id) {return id ? [id] : this.selectedIds}}
}
</script>
4.分页高亮
- PdfPageViewer.vue
<template><div v-loading='loading':element-loading-text="'拼命加载中'+percentage"element-loading-spinner='el-icon-loading'><pdf ref='pdf':src='url':page='pageNum':rotate='pageRotate'@progress='loadedRatio = $event'@page-loaded='pageLoaded($event)'@loaded='loaded'@num-pages='pageTotalNum=$event'@error='pdfError($event)'id='pdfID'></pdf><div class='tools' v-show='pageTotalNum'><el-button type='primary' @click='prePage'><i class='el-icon-arrow-left'></i>上一页</el-button>{{ pageNum }}/{{ pageTotalNum }}<el-button type='primary' @click='nextPage'>下一页<i class='el-icon-arrow-right'></i></el-button></div></div>
</template><script>
import pdf from 'vue-pdf'
export default {name: 'PdfPageViewer',components: {pdf},props: {url: {type: String,default: ''}},data () {return {loading: true,pageNum: 1,pageTotalNum: 1,pageRotate: 0,loadedRatio: 0,pdfWidth: 595,zoom: 1,tid: null,positions: [],highlightDivs: [] // 用于存储高亮显示的 div 元素}},computed: {percentage () {return parseFloat((this.loadedRatio * 100).toFixed(2)) + '%'}},mounted () {console.log('PDF mounted url:', this.url)window.addEventListener('resize', this.updateHighlights)},methods: {loaded () {this.loading = falseconsole.log('loaded', this.$refs.pdf.pdf)this.$refs.pdf.pdf.getPage().then((pdf) => {console.log('aaaaaaaaaaa', pdf.view[2])this.pdfWidth = pdf.view[2]console.log('pdfWidth', this.pdfWidth)this.updateHighlights()})},pageLoaded (page) {this.loading = falseconsole.log('page loaded', page)},pdfError (error) {console.error(error)},prePage () {this.clearHighlights()var page = this.pageNumpage = page > 1 ? page - 1 : this.pageTotalNumthis.pageNum = page},nextPage () {this.clearHighlights()var page = this.pageNumpage = page < this.pageTotalNum ? page + 1 : 1this.pageNum = page},clearHighlights () {this.highlightDivs.forEach(div => {if (div.parentNode) {div.parentNode.removeChild(div)}})this.highlightDivs = []},highlight (positions) {if (!this.positions) returnconsole.log('positions', positions)this.clearHighlights()// 为每个高亮区域创建新的 div 元素positions.forEach(highlight => {const [page, left, right, top, bottom] = highlightthis.pageNum = page || 1const highlightDiv = document.createElement('div')const highlightSize = {}highlightSize.top = top * this.zoomhighlightSize.left = left * this.zoomhighlightSize.height = (bottom - top) * this.zoomhighlightSize.width = (right - left) * this.zoomconsole.log(highlightSize)highlightDiv.setAttribute('style', `top:${highlightSize.top}px;left:${highlightSize.left}px;height:${highlightSize.height}px;width:${highlightSize.width}px;`)highlightDiv.className = 'highlight__part'// 将新的高亮显示元素添加到 PDF 容器中document.getElementById('pdfID').appendChild(highlightDiv)// 将新的 div 元素添加到 highlightDivs 数组中以便管理this.highlightDivs.push(highlightDiv)this.positions = positions})},updateHighlights () {let clientWidth = this.$refs.pdf.$el.clientWidththis.zoom = clientWidth / this.pdfWidthconsole.log(this.zoom, clientWidth, this.pdfWidth)clearTimeout(this.tid)this.tid = setTimeout(() => {this.highlight(this.positions)}, 300)}},// 在 beforeDestroy 钩子中移除事件监听器beforeDestroy () {window.removeEventListener('resize', this.updateHighlights)}
}
</script><style>
#pdfID {overflow-x: hidden;overflow-y: hidden;
}
.tools {text-align: center;
}.highlight__part {position: absolute;background: #ffe28f;opacity: 0.5;transition: background .3s;
}
</style>
5.跳转指定页面并高亮(不分页)
- PdfViewer.vue
<template><div ref='pdfDiv' id='pdfDiv'><vue-pdf-viewer v-for="page in numPages" @loaded='loaded' :ref='"pdf"+page' :page="page" :url="url" ></vue-pdf-viewer></div>
</template><script>
import pdf from 'vue-pdf'
import VuePdfViewer from './VuePdfViewer.vue'export default {components: {VuePdfViewer,pdf},props: {url: {type: String,default: ''}},data () {return {numPages: 1,loadedPage: 0,pdfWidth: 595,pdfHeight: 800,clientWidth: 595,clientHeight: 800,tid: null,widthZoom: 1,heightZoom: 1,positions: [],highlightDivs: [] // 用于存储高亮显示的 div 元素}},mounted () {console.log('PDF loaded', this.url)this.getNumPages()console.log(this.$refs.pdfDiv)window.addEventListener('resize', this.updateHighlights)},methods: {getNumPages () {var loadingTask = pdf.createLoadingTask(this.url)console.log(loadingTask, 'pdf 获取总页数成功')loadingTask.promise.then(pdf => {this.numPages = pdf.numPages}).catch(() => {console.error('pdf 获取总页数失败,重新获取...')setTimeout(() => {this.getNumPages()}, 300)})},scrollTo (page) {this.$nextTick(() => {let pdfDiv = this.$refs.pdfDivpdfDiv.scrollTop = this.clientHeight * (page - 1) * this.heightZoom})},loaded (width, pdfHeight, page) {console.log('pdf 第 ' + page + ' 加载完成')this.$nextTick(() => {this.loadedPage = pagethis.pdfWidth = widththis.pdfHeight = pdfHeightsetTimeout(() => {this.updateHighlights()}, 1500)})},highlight (positions) {if (!this.positions) returnthis.clearHighlights()// 为每个高亮区域创建新的 div 元素positions.forEach(highlight => {const [page, left, right, top, bottom] = highlightif (!page) returnthis.scrollTo(page)const highlightDiv = document.createElement('div')const highlightSize = {}highlightSize.top = top * this.widthZoomhighlightSize.left = left * this.widthZoomhighlightSize.height = (bottom - top) * this.widthZoomhighlightSize.width = (right - left) * this.widthZoomhighlightDiv.setAttribute('style', `top:${highlightSize.top}px;left:${highlightSize.left}px;height:${highlightSize.height}px;width:${highlightSize.width}px;`)highlightDiv.className = 'highlight__part'// 将新的高亮显示元素添加到 PDF 容器中document.getElementById('pdf' + page).appendChild(highlightDiv)// 将新的 div 元素添加到 highlightDivs 数组中以便管理this.highlightDivs.push(highlightDiv)this.positions = positions})},/*** 分辨率发生变化重新计算缩放并重新绘制高亮区域*/updateHighlights () {clearTimeout(this.tid)// 防抖this.tid = setTimeout(() => {this.clientHeight = document.getElementById('pdf' + (this.loadedPage || 1))?.clientHeightthis.heightZoom = this.clientHeight / this.pdfHeightthis.clientWidth = this.$refs.pdfDiv.clientWidththis.widthZoom = this.clientWidth / this.pdfWidththis.highlight(this.positions)}, 300)},clearHighlights () {this.highlightDivs.forEach(div => {if (div.parentNode) {div.parentNode.removeChild(div)}})this.highlightDivs = []}},// 在 beforeDestroy 钩子中移除事件监听器beforeDestroy () {window.removeEventListener('resize', this.updateHighlights)}
}
</script><style>
#pdfDiv{overflow-y: auto;height: calc(100vh - 420px);
}
</style>
子组件(不分页需用到)
- VuePdfViewer.vue
<template><div v-loading='loading':element-loading-text="'拼命加载中'+percentage"element-loading-spinner='el-icon-loading'><pdf :ref='pdfRef'class='overflow-none':key="page" :src="url" :page="page"@progress='loadedRatio = $event'@loaded='loaded':id='pdfId'></pdf></div>
</template><script>
import pdf from 'vue-pdf'export default {components: {pdf},props: {url: {type: String,default: ''},page: {type: Number,default: 1}},data () {return {loading: true,tid: null,loadedRatio: 0}},computed: {pdfRef () {return 'ref' + this.page},pdfId () {return 'pdf' + this.page},percentage () {return parseFloat((this.loadedRatio * 100).toFixed(2)) + '%'}},mounted () {this.reloadCheck()},methods: {loaded () {this.loading = falsethis.$refs[this.pdfRef].pdf.getPage().then((pdf) => {let pdfWidth = pdf.view[2]let pdfHeight = pdf.view[3]this.$emit('loaded', pdfWidth, pdfHeight, this.page)})},reloadCheck () {if (!this.loading && this.loadedRatio === 1) {console.log('pdf 加载失败,重新加载...')this.$refs[this.pdfRef].pdf.loadDocument(this.url)setTimeout(() => {this.reloadCheck()}, 3000)}}}
}
</script><style scoped>.overflow-none {overflow-x: hidden;overflow-y: hidden;
}
</style>
参考
vue 使用 vue-pdf 实现文件在线预览.md
笔记(重要)
如果需要实现高亮需要增强 \node_modules\vue-pdf\src\pdfjsWrapper.js 在288行后面增加:
this.getPage = function() {return pdfDoc.getPage(1)}
提示无法加载组件或者页面出不来,修改 \node_modules\vue-pdf\src\vuePdfNoSss.vue 替换
<style src="./annotationLayer.css"></style>
<script>import componentFactory from './componentFactory.js'import PdfjsWorker from 'pdfjs-dist/es5/build/pdf.worker.js'if ( process.env.VUE_ENV !== 'server' ) {var pdfjsWrapper = require('./pdfjsWrapper.js').default;var PDFJS = require('pdfjs-dist/es5/build/pdf.js');if ( typeof window !== 'undefined' && 'Worker' in window && navigator.appVersion.indexOf('MSIE 10') === -1 ) {// var PdfjsWorker = require('worker-loader!pdfjs-dist/es5/build/pdf.worker.js');PDFJS.GlobalWorkerOptions.workerPort = new PdfjsWorker();
}var component = componentFactory(pdfjsWrapper(PDFJS));
} else {var component = componentFactory({});
}export default component;
</script>
并在 vue.config.js 中的 chainWebpack: config => { 加入以下代码
config.module.rule('worker').test(/\.worker\.js$/).use('worker-loader').loader('worker-loader').options({inline: true,fallback: false}).end()
总结
只能说vue版本太老了…