vue基于vue-pdf实现pdf预览

发布时间 2023-11-14 15:57:05作者: 年轻浅识
<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>

实现效果: