首页 > 其他分享 >vue基于vue-pdf实现pdf预览

vue基于vue-pdf实现pdf预览

时间:2023-11-14 16:01:37浏览次数:28  
标签:vue 预览 waterCtx scale let pdf div page

<template>
    <div class="pdf-container">
        <div class="page-tool">
            文件名称扩展
            <div class="page-tool-fixed" v-if="showTool">
                <span class="scale-icon" @click="prePage"><i class="el-icon-arrow-left"></i></span>
                <span class="scale-icon" @click="clock"><i class="el-icon-refresh-right"></i></span>
                <span class="scale-icon" @click="zoomOut"><i class="el-icon-zoom-out"></i></span>
                <div class="tool-input"><el-input-number :precision="0" :min="0" :max="pageTotalNum" :controls="false" @change="handleChange" v-model.number="pageNum"></el-input-number>/{{pageTotalNum}} </div>
                <span class="scale-icon" @click="zoomIn"><i class="el-icon-zoom-in"></i></span>
                <span class="scale-icon" @click="counterClock"><i class="el-icon-refresh-left"></i></span>
                <span class="scale-icon" @click="nextPage"><i class="el-icon-arrow-right"></i></span>
            </div>
            
		</div>
        <div v-if="pdfLoadState" :style="{width:scale+'%'}">
            <Pdf
            class="page-main"
            :page="pageNum"
            :rotate="pageRotate"
            @num-pages="pageTotalNum = $event" 
            ref="pdf"
            :src="url">
            </Pdf>
        </div>
        <el-result class="load-err" v-else icon="error" title="错误提示" subTitle="PDF文档加载失败,请检查文件完整性"></el-result>
    </div>
</template>

<script>
import Pdf from 'vue-pdf'
import CMapReaderFactory from 'vue-pdf/src/CMapReaderFactory.js';
let ob;
let div;
export default {
  components:{
    Pdf
  },
  data(){
      return {
        url:"",
        pageNum:1,
        pageTotalNum: 1,
        pageRotate: 0,
        scale:80,
        pdfLoadState:true,
        showTool:false,
        watermarkDom:0,
      }
  },
  created(){
    this.getNumPages('/public/text.pdf')
  },
  mounted(){
    // 创建MutationObserver 来进行监控
    ob = new MutationObserver((records) => {
        for(let record of records){
            for (const dom of record.removedNodes) {
                if (dom === div) {
                    this.watermarkDom++; // 删除节点的时候更新依赖
                    return;
                }
            }
            if (record.target === div) {
                this.watermarkDom++; // 修改属性的时候更新依赖
                return;
            }
        }
    });
    // 创建好监听器之后,告诉监听器需要监听的元素
    const parent=this.$refs.pdf.$el
    ob.observe(parent, {
        // 监听的时候需要加一些配置
        childList: true, // 元素内容有没有发生变化
        attributes: true, // 元素本身的属性有没有发生变化
        subtree: true, // 监控整个子树,包含整个子元素
    });
  },
  destroyed() {  
    ob && ob.disconnect(); // 取消监听
    div=null
  },
  watch:{
    watermarkDom(){
        this.setWatermark()
        console.log('暂不支持修改界面元素')
    }
  },
  methods:{
    setWatermark(){
        if (div) {
            div.remove();
        }
        const parent=this.$refs.pdf
        const waterCanvas=document.createElement('canvas')
        const waterCtx=waterCanvas.getContext('2d')
        // 在画布上输出文本之前,检查字体的宽度
        const { width } = waterCtx.measureText('测试水印');
        const canvasSize = Math.max(300, width)
        waterCanvas.width = canvasSize;
        waterCanvas.height = canvasSize;
        waterCtx.translate(waterCanvas.width / 2, waterCanvas.height / 2);
        // 旋转 45 度让文字变倾斜
        waterCtx.rotate((Math.PI / 180) * -45);
        waterCtx.font = '40px Microsoft Yahei';
        waterCtx.fillStyle = 'rgba(0, 0, 0, 0.3)';
        waterCtx.textAlign='center'
        waterCtx.textBaseline = 'middle';
        waterCtx.fillText('测试水印',0,0)
        let imgSrc = waterCanvas.toDataURL("image/png");
        div = document.createElement('div');
        // 背景设置为 base64 的图片
        div.style.backgroundImage = `url(${imgSrc})`;
        // 背景的大小设置为 styleSize
        div.style.backgroundSize = `${canvasSize}px ${canvasSize}px`;
        // 重复方式设置为 repeat
        div.style.backgroundRepeat = 'repeat';
        // 设置子元素与父元素四个方向的间隔(这里设置为 0 的效果同宽高设置 100%)
        div.style.inset = 0;
        div.style.zIndex = 9998;
        // 设置绝对定位
        div.style.position = 'absolute';
        // 设置点击穿漏,防止底部元素失去鼠标事件的交互
        div.style.pointerEvents = 'none';
        parent.$el.appendChild(div)
    },
    getNumPages(url) {
        let loadingTask = Pdf.createLoadingTask({url,CMapReaderFactory})
        loadingTask.promise.then(pdf => {
            this.url=loadingTask
            this.numPages = pdf.numPages
            this.showTool=true
            this.setWatermark()
        }).catch((err) => {
            console.error(err)
            console.error('pdf加载失败')
            this.pdfLoadState=false
        })
    },
    toTop(){
        window.scrollTo(0, 0)
    },
    zoomIn() {
        let scale=this.scale +5
        this.scale = scale>100?100:scale
    },

    //缩小
    zoomOut() {
        let scale=this.scale -5
        this.scale = scale<60?60:scale
    },
    // 上一页函数,
    prePage() {
        let page = this.pageNum
        page = page > 1 ? page - 1 : this.pageTotalNum
        this.pageNum = page
        this.toTop()
    },
    // 下一页函数
    nextPage() {
        let page = this.pageNum
        page = page < this.pageTotalNum ? page + 1 : 1
        this.pageNum = page
        this.toTop()
    },
    handleChange(){
        this.toTop()
    },
    // 页面顺时针翻转90度。
    clock() {
        this.pageRotate += 90
    },
    // 页面逆时针翻转90度。
    counterClock() {
        this.pageRotate -= 90
    }
  }
}
</script>
<style lang="scss">
.pdf-container{
    display: flex;
    justify-content: center;
    background-color: #e9e9e9;
    overflow: hidden;
    .page-tool {
        position: fixed;
        left: 0;
        top: 0;
        z-index: 9999;
        background-color: #fafafa;
        height: 40px;
        line-height: 40px;
        text-align: center;
        font-size: 20px;
        width: 100%;
        border-bottom: 1px solid #e9e9e9;
    }
    .page-tool-fixed{
        display: flex;
        justify-content: center;
        position: fixed;
        left: 50%;
        bottom: 50px;
        transform: translateX(-50%);
        z-index: 1050;
        height: 44px;
        line-height: 44px;
        background-color: #e9e9e9;
        font-size: 16px;
        font-weight: 500;
        border-radius: 20px;
        opacity: 0.8;
        user-select: none;
        .scale-icon{
            font-size: 25px;
            margin: 0 10px;
            cursor: pointer;
        }
        .tool-input{
            display: flex;
            align-items: center;
            .el-input-number{
                width: 45px;
                .el-input__inner {
                    padding: 0 10px;
                }
            }
        }
    }
    .page-main{
        margin: 10px 20px;
        position: absolute;
        top: 40px;
        left: 0;
    }
    .load-err{
        position: absolute;
        top: 40px;
        left: 0;
        width: 100%;
    }
}
</style>

实现效果:

标签:vue,预览,waterCtx,scale,let,pdf,div,page
From: https://www.cnblogs.com/772330747wh/p/17831825.html

相关文章

  • 如何实现元素的平滑上升?(vue和react版)
    首先我们看下我们有时候需要在官网或者列表中给元素添加一个动画使元素能够平滑的出现在我们的视野中。 如上图所示,我们在vue中可以自定义指令,当我们需要的时候可以直接使用。废话不多说直接上代码。首先我们创建一个vSlideIn.ts文件import{DirectiveBinding}from'vue......
  • Vue3调用Element-plus涉及子组件v-model双向绑定props问题
    Vue3调用Element-plus涉及子组件v-model双向绑定props问题在Vue3调用Element-plus的el-dialog组件时,碰到个很有意思的问题,el-dialog的属性值v-model直接控制对话框的显示与否,点击关闭对话框和遮罩区域,组件内部会自动更改v-model的值为false来关闭对话框。问题在于当组件作为子组......
  • vuejs3.0 从入门到精通——Pinia——定义Store
    定义Store Store是用defineStore()定义的,它的第一个参数要求是一个独一无二的名字:import{defineStore}from'pinia'//你可以对`defineStore()`的返回值进行任意命名,但最好使用store的名字,同时以`use`开头且以`Store`结尾。(比如`useUserStore`,`useCartStore......
  • 在vue项目开发过程中,输入框以表单形式提交后,路径中多了问号?
    结果是:http://localhost:8100/#/  改变为  http://localhost:8100/?#/  导致路由跳转出现问题。 原因:这里是form表单,点击了button按钮,触发了他的默认事件,就是触发了提交这个行为。 解决方案:使用@click.prevent阻止默认事件 <a-buttontype="primary"@click.pr......
  • Smallpdf 1.24.2 安装包与无限期试用安装教程
    https://www.52pojie.cn/thread-1093277-1-1.html三、安装包下载:链接:https://pan.baidu.com/s/13_r7x9YjMnAMP4jlmoGvGg提取码:vw4d安装包大小超过100M,不能上传蓝奏云,只能上传百度网盘了,各位请见谅。四、安装教程:1、打开安装包,会自动安装,安装完在左上角关闭软件(不要直接右上角关闭!)2......
  • 044_第三代软件开发-保存PDF
    第三代软件开发-保存PDF文章目录第三代软件开发-保存PDF项目介绍保存PDF头文件源文件使用关键字:Qt、Qml、pdf、painter、打印项目介绍欢迎来到我们的QML&C++项目!这个项目结合了QML(QtMeta-ObjectLanguage)和C++的强大功能,旨在开发出色的用户界面和高性能的后端逻辑......
  • vuejs3.0 从入门到精通——Pinia——Store 是什么?
    Pinia——Store是什么?https://pinia.vuejs.org/zh/getting-started.html#what-is-a-store一、Store是什么? Store(如Pinia)是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。......
  • 深入理解 Vuex
    Vue.js是一款轻量级、灵活且易学的前端框架,但随着应用的复杂性增加,组件之间的状态管理变得愈发复杂。为了解决这一问题,Vue.js提供了一个强大的状态管理工具——Vuex。在本篇博客中,我们将深入探讨Vuex的核心概念和使用方法,并通过实例演示如何优雅地管理应用状态。什么是Vuex?Vue......
  • Vue3实现图片滚轮缩放和拖拽
    在项目开发中遇到一个需求:1:用鼠标滚轮可对图片进行缩放处理2:点击按钮可对图片进行缩放处理3:可对图片进行拖拽处理 我在开发中通过自己实现与百度查看优秀的铁子进行了两种类型的使用  <template><divref="imgWrap"class="wrap"@mousewheel.prevent="rollImg"......
  • vue初始
    简介Vue的两个核心功能:声明式渲染:Vue基于标准HTML拓展了一套模板语法,使得我们可以声明式地描述最终输出的HTML和JavaScript状态之间的关系。响应性:Vue会自动跟踪JavaScript状态并在其发生变化时响应式地更新DOM。API风格Vue的组件可以按两种不同的风......