admin管理员组

文章数量:1648640

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'
                    border
                    row-key='id'
                    @row-dblclick='editChunk'
                    @row-click='rowClick'
                    @selection-change='tableSelectChange'
          >
            <el-table-column
              type='selection'
              reserve-selection
              width='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/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf'></pdf-viewer>-->
          <!-- 带分页的高亮 -->
          <pdf-page-viewer v-if='isPdf' ref='pdf' url='http://storage.xuetangx/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 = false
      console.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 = false
      console.log('page loaded', page)
    },
    pdfError (error) {
      console.error(error)
    },
    prePage () {
      this.clearHighlights()
      var page = this.pageNum
      page = page > 1 ? page - 1 : this.pageTotalNum
      this.pageNum = page
    },
    nextPage () {
      this.clearHighlights()
      var page = this.pageNum
      page = page < this.pageTotalNum ? page + 1 : 1
      this.pageNum = page
    },
    clearHighlights () {
      this.highlightDivs.forEach(div => {
        if (div.parentNode) {
          div.parentNode.removeChild(div)
        }
      })
      this.highlightDivs = []
    },
    highlight (positions) {
      if (!this.positions) return
      console.log('positions', positions)
      this.clearHighlights()
      // 为每个高亮区域创建新的 div 元素
      positions.forEach(highlight => {
        const [page, left, right, top, bottom] = highlight
        this.pageNum = page || 1
        const highlightDiv = document.createElement('div')
        const highlightSize = {}
        highlightSize.top = top * this.zoom
        highlightSize.left = left * this.zoom
        highlightSize.height = (bottom - top) * this.zoom
        highlightSize.width = (right - left) * this.zoom
        console.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.clientWidth
      this.zoom = clientWidth / this.pdfWidth
      console.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.pdfDiv
        pdfDiv.scrollTop = this.clientHeight * (page - 1) * this.heightZoom
      })
    },
    loaded (width, pdfHeight, page) {
      console.log('pdf 第 ' + page + ' 加载完成')
      this.$nextTick(() => {
        this.loadedPage = page
        this.pdfWidth = width
        this.pdfHeight = pdfHeight
        setTimeout(() => {
          this.updateHighlights()
        }, 1500)
      })
    },
    highlight (positions) {
      if (!this.positions) return
      this.clearHighlights()
      // 为每个高亮区域创建新的 div 元素
      positions.forEach(highlight => {
        const [page, left, right, top, bottom] = highlight
        if (!page) return
        this.scrollTo(page)
        const highlightDiv = document.createElement('div')
        const highlightSize = {}
        highlightSize.top = top * this.widthZoom
        highlightSize.left = left * this.widthZoom
        highlightSize.height = (bottom - top) * this.widthZoom
        highlightSize.width = (right - left) * this.widthZoom
        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('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))?.clientHeight
        this.heightZoom = this.clientHeight / this.pdfHeight
        this.clientWidth = this.$refs.pdfDiv.clientWidth
        this.widthZoom = this.clientWidth / this.pdfWidth
        this.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 = false
      this.$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版本太老了…

本文标签: 分页功能vuePDF高亮