vue-pdf 实现pdf预览、高亮、分页、定位功能

news2024/9/27 9:27:18

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.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 = 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版本太老了…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2164909.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C# 面对对象基础 枚举,Enum.TryParse的使用

代码&#xff1a; using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks;namespace Student_c_ {enum Week : int{Mon,Tus,Wed,Thu,Fri,Sat,Sun,}public cla…

微服务之服务保护

Sentinel引入Java项目中 一&#xff1a;安装Sentinel 官网地址&#xff1a;https://github.com/alibaba/Sentinel/releases 二&#xff1a;安装好后在sentinel-dashboard.jar所在目录运行终端 三&#xff1a;运行命令&#xff0c;端口自己指定 java -Dserver.port8090 -Dcs…

iPhone16新机到手,,这些操作都要设置好

iPhone16新机首批机子已经发货&#xff0c;陆陆续续都几到了买家们手中了&#xff0c;iPhone 16到手后&#xff0c;虽然没有严格意义上的“必须”设置&#xff0c;但有一些推荐设置可以帮助您更好地使用和保护设备&#xff0c;同时提升安全性和使用体验&#xff0c;让你的新iPh…

栈的深度解析:链式队列的实现

引言 队列是一种广泛应用于计算机科学的数据结构&#xff0c;具有先进先出&#xff08;FIFO&#xff09;的特性。在许多实际应用中&#xff0c;例如任务调度、缓冲区管理等&#xff0c;队列扮演着重要角色。本文将详细介绍队列的基本概念&#xff0c;并通过链表实现一个简单的…

初识Jenkins持续集成系统

随着软件开发复杂度的不断提高&#xff0c;团队成员之间如何更好地协同工作以确保软件开发的质量&#xff0c;已经慢慢成为开发过程中不可回避的问题。Jenkins 自动化部署可以解决集成、测试、部署等重复性的工作&#xff0c;工具集成的效率明显高于人工操作;并且持续集成可以更…

网络原理3-应用层(HTTP/HTTPS)

目录 DNSHTTP/HTTPSHTTP协议报文HTTP的方法请求报头、响应报头(header)状态码构造HTTP请求HTTPS 应用层是我们日常开发中最常用的一层&#xff0c;因为其他层&#xff1a;传输层、网络层、数据链路层、物理层这些都是操作系统和硬件、驱动已经实现好的&#xff0c;我们只能使用…

【Python】的语言基础学习方法 快速掌握! 源码可分享!

python语言基础 第一章 你好python 1.1安装python https://www.python.org/downloads/release/python-3104/ 自定义安装&#xff0c;全选 配置python的安装路径 验证&#xff1a;cmd输入python 1.2python解释器 解释器主要做了两件事&#xff1a; 翻译代码提交给计算机去运…

Linux 下安装mysql

1.检查之前是否安装过mysql rpm -qa | grep mysql 如果之前安装过&#xff0c;删除之前的安装包 rpm -e 安装包 如果没有&#xff0c;进行后续安装 2. 下载 MySQL :: Download MySQL Community Server (Archived Versions)https://downloads.mysql.com/archives/community/ 3…

plt常用函数介绍二

目录 fig.add_subplot()ax.set()plt.legend()plt.subplots_adjust()plt.suptitle()plt.grid() fig.add_subplot() fig.add_subplot() 是 Matplotlib 中 Figure 对象的方法&#xff0c;用于在图形中添加子图&#xff08;subplot&#xff09;。 其语法为&#xff1a; subplot(…

同声传译用什么软件最方便?推荐五款易用的同声传译软件

在国际贸易、国际会议及跨国合作项目中&#xff0c;语言障碍往往是沟通效率的一大挑战。 为了解决这个问题&#xff0c;同声传译免费软件应运而生&#xff0c;它们通过先进的技术实现了即时准确的语言转换&#xff0c;极大地促进了不同语言使用者间的交流与协作。 下面&#…

2025考研倒计时 考研时间公布了 你准备好复习冲刺了吗?

2025考研倒计时 考研时间公布了 你准备好复习冲刺了吗&#xff1f;今年的考研时间终于公布了&#xff1a; 正式报名时间2024.10.15-2024.10.28&#xff0c;初试时间12月21日&#xff0c;相信很多学子们已经做好冲刺的准备了。 在这关键的90天的时间内&#xff0c;如何做到时刻…

各种编程语言中有哪些共性?超详细+通俗易懂版!!!

各种编程语言中存在着一些共性&#xff0c;这些共性构成了编程语言的基础框架和设计原则。以下是这些共性的主要方面&#xff1a; 1. 遵循基本的编程原则 模块化&#xff1a;将程序划分为多个独立、可复用的模块&#xff0c;有助于降低程序的复杂度并提高可维护性。封装&#…

【计网】从零开始学习http协议 --- http的请求与应答

如果你不能飞&#xff0c;那就跑&#xff1b; 如果跑不动&#xff0c;那就走&#xff1b; 实在走不了&#xff0c;那就爬。 无论做什么&#xff0c;你都要勇往直前。 --- 马丁路德金 --- 从零开始学习http协议 1 什么是http协议2 认识URL3 http的请求和应答3.1 服务端设计…

Stack Overflow 如何提升其单元测试水平

在 Stack Overflow 成立之初&#xff0c;我们只是一个快速、精简运行的网站。Stackoverflow.com 是由开发人员为开发人员创建的小型初创公司。像所有初创公司一样&#xff0c;我们优先考虑对我们来说最重要的质量属性&#xff0c;而忽视了许多其他属性&#xff0c;包括根据最佳…

功耗或600W,RTX 5090、5080集体降级国内特供版

早在一个多月前&#xff0c;国外知名硬件泄密者 kopite7kimi 就曾透露 NVIDIA RTX 50 系显卡或推迟到明年初 CES 大会上发布。 来源&#xff1a;X 结合前几天爆出的台积电 4NP 工艺产能告急、NVIDIA 为提升下代显卡良率重新流片等。 如果不出意外&#xff0c;这原定在今年第四…

Π-系上的最小 d-系等于 Π-系上的最小集代数

称空间 Ω \Omega Ω 中满足下述条件的集系为 d d d-系: Ω ∈ D \Omega \in \mathscr{D} Ω∈D若 A , B ∈ D A, B \in \mathscr{D} A,B∈D 且 A ∩ B ϕ A \cap B\phi A∩Bϕ, 则 A B ∈ D AB \in \mathscr{D} AB∈D;若 A ⊂ B , A , B ∈ D A \subset B, A, B \in \…

visio 2021入门直通车(一天全搞定)

安装Visio 2021 (64bit)安装教程 1.1. 模板类型 1.2. 界面布局 1.3. 插入对象 1.4. 添加页面 1.5. 全屏演示|页面自适应|visio文件切换 1.6. 快捷键 快捷键说明 Shift 鼠标滚轮 按下shift&#xff0c;点击鼠标滚轮水平页面滚动 鼠标滚轮 垂直页面滚动 Ctrl 鼠标滚轮 按…

每天分享一个FPGA开源代码(6)- 浮点数运算

FPGA&#xff08;现场可编程门阵列&#xff09;是一种高度可配置的集成电路&#xff0c;它可以用于实现各种数字信号处理任务&#xff0c;包括浮点数运算。 在FPGA上进行浮点数运算通常涉及以下几个步骤&#xff1a; 1. 选择浮点数格式 浮点数运算首先要确定使用哪种浮点数格…

百度amis框架经验分享

百度amis框架经验分享 官方文档 amis - 低代码前端框架 这篇文章讲了amis的设计 为什么说百度AMIS框架是一个优秀的设计_百度前端框架-CSDN博客 学习方法&#xff1a; 最好的学习方法就是GPT官方文档 不要去很大力气通读官方文档&#xff0c;大概浏览一遍就行&#xff0c; 以你…

docker中搭建nacos并将springboot项目的配置文件转移到nacos中

前言 网上搜索docker中搭建nacos发现文章不是很好理解&#xff0c;正好最近在搭建nacos练手。记录一下整个搭建过程。 docker中搭建nacos并将springboot项目的配置文件转移到nacos中 前言1 docker中下拉nacos镜像2 配置nacos信息1. 创建docker的挂载目录&#xff0c;实现数据的…