Vue3实现pdf本地预览功能

一、先直接看看效果吧

放大后

缩小后

也可以分页显示

二、选用vue-pdf-embed和vue3-pdfjs的原因

选用这两个的插件是因为如果实现pdf预览其实使用iframe标签就可以的,但是使用iframe标签实现的比较臭,vue-pdf-embed是能够自定义样式的,更加灵活,我也是第一次尝试这个,也遇到了一些坑,后面会跟大家分享,反正按照这个来就能实现,源码我也放后面了。

三、注意点及踩坑

1、我这里项目搭建是用的Vite也不知道使用vue cli会不会有什么问题

2、vue-pdf-embed我开始是pnpm i vue-pdf-embed 安装了最新的,后面会发现pdf预览不出来,找了原因是版本的问题,我就pnpm i vue-pdf-embed@1.2.1 切换到1.2.1版本就好了

3、vue3-pdfjs在这是用来做监听pdf页数的当然还有别的用处。

4、当预览成果后会发现文字并未出来,f12会报这个

复制代码
warning: error during font loading: the cmap "baseurl" parameter must be specified, ensure that the "cmapurl" and "cmappacked" api parameters are provided.

原因就是缺少中文包,加上下面这段就行了,这段代码在源码里面

5、还有个问题是scale缩放开始无效,原因大概是因为在使用 vue-pdf-embed 组件的 scale 参数时发现无法实现动态更改,且 width 参数无法设置成百分比的形式,所以采用修改父容器的方案实现。

四、直接上源码了

//父组件

javascript 复制代码
<template>
  <div class="main">
    <div class="home-box">
      <pdfView :pdfUrl="jsPdf"/>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import pdfView from '@/components/pdfView/index.vue';
import jsPdf from './jsPdf.pdf';
</script>
<style lang="scss" scoped>
.main {
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  .home-box{
    width: 80%;
  }
}
</style>

子组件

javascript 复制代码
<template>
  <div class="pdf-preview">
    <div
      class="pdf-wrap"
      :style="{ transform: `translate(-50%,-50%) scale(${scaleData})` }"
    >
      <vue-pdf-embed
        :source="state.source"
        :style="state.scale"
        class="vue-pdf-embed"
        :page="state.pageNum"
      />
    </div>
    <div class="page-tool">
      <div class="page-tool-item" @click="lastPage">上一页</div>
      <div class="page-tool-item" @click="nextPage">下一页</div>
      <div class="page-tool-item">{{ state.pageNum }}/{{ state.numPages }}</div>
      <div class="page-tool-item" @click="zoomIn">放大</div>
      <div class="page-tool-item" @click="zoomOut">缩小</div>
      <div class="page-tool-item" @click="pageRest">重置缩放</div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, onMounted, computed, ref } from 'vue'
import VuePdfEmbed from 'vue-pdf-embed'
import { createLoadingTask } from 'vue3-pdfjs'
const props = defineProps({
  pdfUrl: {
    type: String,
    required: true
  }
})
const state = reactive({
  source: {
    url: props.pdfUrl,
    cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.9.359/cmaps/',
    cMapPacked: true
  }, //预览pdf文件地址
  pageNum: 1, //当前页面
  scale: 1, // 缩放比例
  numPages: 0 // 总页数
})
const scaleData = ref(1.0)
const pageHeight = ref('100%')
//上一页
function lastPage() {
  if (state.pageNum > 1) {
    state.pageNum -= 1
  }
}
//下一页
function nextPage() {
  if (state.pageNum < state.numPages) {
    state.pageNum += 1
  }
}

// 放大
function zoomIn() {
  scaleData.value += 0.1
  pageHeight.value = parseInt(pageHeight.value) - 5.0 + '%'
}
// 缩小
function zoomOut() {
  scaleData.value -= 0.1
  pageHeight.value = parseInt(pageHeight.value) + 5.0 + '%'
}
// 页面 放大/缩小 还原默认值
function pageRest() {
  scaleData.value = 1.0
  pageHeight.value = '100%'
}

onMounted(() => {
  // debugger
  // console.log(props.pdfUrl)
  // debugger

  //获取pdf总页数
  const loadingTask = createLoadingTask(state.source)
  loadingTask.promise.then((pdf: { numPages: number }) => {
    // debugger
    state.numPages = pdf.numPages
    // debugger
  })
})
</script>
<style lang="scss" scoped>
.pdf-preview {
  position: relative;
  height: 100vh;
  padding: 20px 0;
  box-sizing: border-box;
  background-color: e9e9e9;
  .pdf-wrap {
    position: absolute;
    width: 100%;
    top: 50%;
    left: 50%;
    height: 100%;
    overflow-y: auto;
    .vue-pdf-embed {
      height: 100%;
      text-align: center;
      width: 80%;
      border: 1px solid #e5e5e5;
      margin: 0 auto;
      box-sizing: border-box;
    }
  }
  .page-tool {
    position: absolute;
    bottom: 35px;
    padding-left: 15px;
    padding-right: 15px;
    display: flex;
    align-items: center;
    background: rgb(66, 66, 66);
    color: white;
    border-radius: 19px;
    z-index: 100;
    cursor: pointer;
    margin-left: 50%;
    transform: translateX(-50%);
    .page-tool-item {
      padding: 8px 15px;
      padding-left: 10px;
      cursor: pointer;
    }
  }
}
</style>
相关推荐
lichenyang45312 小时前
从 Express 老项目到 NestJS + Docker:一次车辆管理系统的渐进式重构
前端
竹林81813 小时前
用 wagmi v2 + viem 监听链上事件,我踩了三天坑终于搞懂了实时日志与历史补全
javascript
Momo__13 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
只一13 小时前
😭从回调地狱到 async/await:一文打通 Ajax 与 JS 异步编程
javascript
程序员小富13 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇13 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇13 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆13 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马13 小时前
Verilog开发常见问题汇总解析
前端
子兮曰13 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端