根据公司的实际项目需求,要求实现对pdf和ofd文件的预览,并且需要限制用户是否可以下载、打印、另存pdf、ofd文件,如果该用户可以打印、下载需要控制每个用户的下载次数以及可打印的次数。正常的预览pdf很简单,直接调用浏览器的预览就可以而且功能也比较全,但是一涉及到禁止用户打印、另存的话就不可以用浏览器自带的预览方式了。那就只能寻找插件来模拟了,之前在eletron-vue项目中使用过pdfjs以及vue-pdf插件,效果不是特别好,vue-pdf底层其实也是用的pdfjs的东西,当时在客户端项目中莫名的报一些错误(可能是因为版本问题,当时项目比较着急没有太多时间去排坑)果断弃坑选择了iframe预览pdf文件,也可以达到当时项目需求。但是,目前的需求背景是管理端以及门户端想要实现此功能,脱离了electron的限制,我决定还是使用vue-pdf比较靠谱,我也简单安装并测试使用了一下,没有问题可以正常预览本地文件并且并没有报electron中的不知名的错误。下面先来说一下插件的具体使用方法:

1.安装vue-pdf依赖:

npm/yarn i/add vue-pdf

2.import导入并注册依赖(哪个界面需要在哪个界面单独导入即可)

// 单组件引用
import pdf from 'vue-pdf'
// 然后在component中进行注册
components: {
pdf
},

3.界面中使用vue-pdf

// 点击分页
<div>
<el-button size="small" type="primary" @click="changePdfPage(0)" class="turn" :class="{grey: currentPage==1}">上一页</el-button>
{{currentPage}} / {{pageCount}}
<el-button size="small" type="primary" @click="changePdfPage(1)" class="turn" :class="{grey: currentPage==pageCount}">下一页</el-button>
</div>
<div class="content" ref="printContent">
<pdf
id="myIframe"
ref="pdfDom"
:src="pdfSrc"
:page="currentPage"
@num-pages="pageCount=$event"
@page-loaded="currentPage=$event"
@loaded="loadPdfHandler">
</pdf>
</div>
// 声明变量
data() {
return {
pdfSrc: '', // pdf文件src
pageCount: 0, // pdf文件总页数
currentPage: 1, // pdf文件页码
}
},
// 获取文件流和文件总页数
async getFileInfo() {
let formData = new FormData();
formData.append('wjid', this.wjid)
formData.append('yhid', this.userId)
let res = await getFileBuffer(formData)
if(res.status === 200){
const blob = new Blob([res.data])
let pageRes = await getFilePages(formData)
this.pdfSrc = URL.createObjectURL(blob)
this.pageCount = pageRes.data.content
}else{
this.$message({
message: res.message,
type: 'error'
});
}
},
// 改变PDF页码,val传过来区分上一页下一页的值,0上一页,1下一页
changePdfPage (val) {
if (val === 0 && this.currentPage > 1) {
this.currentPage--
}
if (val === 1 && this.currentPage < this.pageCount) {
this.currentPage++
}
}, // pdf加载时
loadPdfHandler (e) {
this.currentPage = 1 // 加载的时候先加载第一页
},

以上代码是从后端获取pdf文件的文件流,await getFileBuffer(formData)请求接口赋值给res,根据res的状态值为200进行判断文件流是否成功返回,返回成功后通过new Blob([res.data]) 转换成blob,然后通过URL.createObjectURL(blob)获取pdfSrc。

await getFilePages(formData)接口是单独获取pdf总页数。

上图为根据后端返回的pdf数据流预览的此pdf文件,现在只是把pdf文件显示出来了,目前仅仅成功了一小部分,可以说刚刚把这部分功能最基础的部分弄完,后续还会有控制打印、下载、另存、水印......

我们可以一个一个来实现,首先说一下禁止浏览器的快捷键ctrl+s(保存)、ctrl+p(打印),可以通过以下代码禁止当前界面这两个快捷键的使用,直接上代码:

mounted() {
// 禁止ctrl+S保存 禁止ctrl+P打印
document.addEventListener(
"keydown",
function (event) {
// 禁止ctrl+s
if (event.ctrlKey === true && event.which === 83) {
// console.log('ctrl+s');
event.preventDefault();
}else if(event.ctrlKey === true && event.which === 80) {
// 禁止ctrl+p
event.preventDefault();
}
},
false
);
}

文件下载,界面上写好下载按钮,写好click点击事件以及v-if(控制此用户的下载次数,根据用户每次点击下载按钮后台计算下载次数,如果下载次数达到设置的次数,隐藏下载按钮)直接上代码:

<el-button size="small" class="cz-button" type="primary" v-if="downLoadShow" @click="downloadClick">下载文件</el-button>

// 下载方法
async downloadClick() {
this.fileName = '' // 获取文件名
let formData = new FormData();
formData.append('wjid', this.wjid)
formData.append('yhid', yhid)
let res = await getFileBufferDl(formData)
// 根据用户id和文件id向后台请求接口返回pdf文件流
const blob = new Blob([res])
const url = window.URL.createObjectURL(blob)
let dom = document.createElement('a')
dom.style.display = 'none'
dom.href = url
dom.setAttribute('download', this.fileName)
document.body.appendChild(dom)
// 执行下载操作
dom.click()
// 下载完成以后向后台发起接口 传入用户id 后台将该用户的下载次数进行加1
......
},

文件打印,界面上写好下载按钮,写好click点击事件以及v-if(控制此用户的打印次数,根据用户每次点击打印按钮后台计算打印次数,如果打印次数达到设置的次数,隐藏打印按钮)打印需要下载print-js依赖

1.安装依赖

yarn add print-js

2.引入插件

import printJS from 'print-js'

3.使用插件(直接传入文件的路径)

printJS(url)

4.官方文档https://printjs.crabbly.com/

<el-button size="small" style="margin-right: 90px;" class="cz-button" type="primary" v-if="printShow" @click="billPrintClick">打印</el-button>

// 打印方法
billPrintClick() {
this.$nextTick(async () => {
let formData = new FormData();
formData.append('wjid', this.wjid)
formData.append('yhid', yhid)
let res = await getFileBufferDl(formData)
const blob = new Blob([res])
const url = window.URL.createObjectURL(blob) //URL.createObjectURL(object)表示生成一个File对象或Blob对象
printJS(url)
let resName = await getProfile()
// 下载完成以后向后台发起接口 传入用户id 后台将该用户的下载次数进行加1
......
})
},

文件预览水印,正常加水印流程是java后端把文件加上水印后返回给前端,前端可以直接预览、下载、打印带有水印的文件,之前弄过前端加水印方法也比较简单,从网上找了个前端加水印的例子,简单调整了一下,在这也做下记录:

<div class="content" id="myIframe" ref="printContent">
<pdf ref="pdfDom" :src="pdfSrc" :page="currentPage" @num-pages="pageCount = $event" @page-loaded="currentPage = $event" @loaded="loadPdfHandler"></pdf>
</div>
// 调用方法传入的参数是水印内容根据实际需求而定,我这里写的是获取当前用户的中文用户名直接传入方法里即可
this.$nextTick(async () => {
this.setWatermarkContent(resName.data.userInfo.userName);
window.onresize = () => {
this.setWatermarkContent(resName.data.userInfo.userName);
};
}); // 设置水印
setWatermarkContent(resName) {
// 创建canvas容器
let ele = document.createElement("canvas");
ele.width = 250;
ele.height = 200;
// 水印参数对象
let objmsg = {
canvas: ele,
fontText: resName, // 显示的内容,显示方法传入的内容 字符串格式
fontSize: 20, // 水印的字体大小
fontFamily: "microsoft yahei", // 字体
fontcolor: "#dadbdc", //字体颜色 默认 #dadbdc
rotate: 25, //旋转角度 数字类型
textAlign: "left", //水印文字居中方式:left center right 默认 left
};
this.createWaterMark(objmsg);
this.drawWaterMark(ele); // 将水印canvas遮罩层定位到pdf容器中
},
// 创建canvas水印图片
createWaterMark({ canvas, fontText, fontFamily = "microsoft yahei", fontSize = 30, fontcolor = "#dadbdc", rotate = 30, textAlign = "left" }) {
let ctx = canvas.getContext("2d");
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.rotate((-rotate * Math.PI) / 180);
ctx.fillStyle = fontcolor;
ctx.textAlign = textAlign;
ctx.textBaseline = "Middle";
ctx.fillText(fontText, canvas.width / 6, canvas.height / 2);
},
// 给pdf增加水印遮罩层
drawWaterMark(ele) {
let div = document.createElement("div");
div.style.pointerEvents = "none";
div.style.position = "absolute";
div.style.background = "url(" + ele.toDataURL("image/png") + ") left top repeat";
// 获取容器的宽度和高度
let width = document.getElementById("myIframe").clientWidth || 700;
let height = document.getElementById("myIframe").clientHeight || 700;
div.style.width = width + "px";
div.style.height = height + "px";
document.getElementById("myIframe").appendChild(div);
}

上面主要是介绍vue-pdf、printjs插件以及怎么前端控制下载、另存以及打印功能,那么接下来重点说一下vue-pdf这个插件,总结一下我在开发过程中所遇到过的坑,仅供参考可能解决方法有很多种

分割线分割线,这是一条分割线---分割线分割线,这是一条分割线---分割线分割线,这是一条分割线---分割线分割线,这是一条分割线

1. 本地开发时预览一点毛病都没有,就是build打完包后放到服务器上无法预览报错:提示xxx.worker.js 500 或者 404

解决方法:需要改两个地方:
① 修改依赖下的文件位置:node_modules/vue-pdf/src/vuePdfNoSss.vue





注意:代码中需要注释的那一行有的版本路径不一样

有的是:// var PdfjsWorker = require('worker-loader!pdfjs-dist/build/pdf.worker.js');

有的是:// var PdfjsWorker = require('worker-loader!pdfjs-dist/es5/build/pdf.worker.js');

② 找到项目的vue.config.js 文件,在配置文件中新家下面这段代码
    // 处理vue-pdf打包文件404
config.module
.rule('worker')
.test(/\.worker\.js$/)
.use('worker-loader').loader('worker-loader')
.options({
inline: true,
fallback: false,
}).end();
两个地方改完之后再执行yarn run build打包部署到服务器上发现不会报xxx.worker.js 500 或者 404这种错了,可以正常预览了!

2. 线上环境大部分pdf预览都没有问题,但是有个别pdf文件里面的内容显示不全,缺胳膊少腿,不是这个字没显示出来就是那个字丢了的(本地还是没有问题)

解决方法:在网上找了一下,也有人遇到类似问题,原因是缺少相应字体 ,需要在界面上引入CMapReaderFactory,然后把url传进去生成新的路径赋值给this.pdfSrc,就可以解决
import pdf from 'vue-pdf'
import CMapReaderFactory from 'vue-pdf/src/CMapReaderFactory' let loadingTask = pdf.createLoadingTask({
url: URL.createObjectURL(blob),
cMapPacked: true,
CMapReaderFactory
})
this.pdfSrc = loadingTask;

3. 当我们使用上个问题中的CMapReaderFactory时,会引发另外一个问题,就是预览的时候显示空白的问题,显示不全有空白页,控制台还不报错

我也去网上找了相关问题的解决方法,有人说是因为缓存问题,第二次加载时取的是初次加载PDF文件时的语言文件的loadModules的缓存,但是取的过程中导致了失败,返回了空值,修改的时候只需要加上一段话
//加载完语言文件后清除缓存
delete require.cache[require.resolve('./buffer-loader!pdfjs-dist-sign/cmaps/'+query.name+'.bcmap')];

4. 打包部署到服务器上后,预览时死活看不到内容,控制器不报错,就是空白,本地开发也没问题

这个问题啥招我都试了,我同时做管理端以及门户端,都需要预览pdf,用的都是vue-pdf,代码逻辑一模一样,而且后台接口也一模一样,门户端开发环境和部署环境一点问题都没有,管理端开发环境没问题部署完以后到线上就是不展示内容,这个问题我改了一天,没办法在本地测试,只能改一点发给现场一个包现场部署一版给我测一下好不好用,一天我给现场发包就发了20个,马上下班的时候我给现场发了个包,和现场哥们说这个不行就暂时换回备份的包吧,说实话我对这个包真没报啥希望。竟然解决了,我听到以后都震惊了改了一天给现场发了20个包,最后这个包啥都没改竟然好用了,我也很诧异,最后发现是我把vue-pdf降了几个版本,什么都没改就好使了。之前用的是最新的"vue-pdf": "4.3.0",我卸了重新按了个4.0.7的竟然解决了!!!

5. Uncaught SyntaxError: Unexpected token ‘<‘

从接触vue-pdf开始,就感觉不是啥善茬,最开始是在electron-vue项目中接触,安装完依赖以后就报Uncaught SyntaxError: Unexpected token ‘<‘这个错,网上找了好多这个问题的解决方法,但是并没有什么用,最后这个问题也没解决,在electron项目中直接弃坑了,换成iframe预览pdf文件,也可以达成想要的效果。

vue前端预览pdf并加水印、ofd文件,控制打印、下载、另存,vue-pdf的使用方法以及在开发中所踩过的坑合集的更多相关文章

  1. layui.js---layer;;前端预览pdf

    layui.js---layer;;前端预览pdf 1.必须引入layui.js 2.uul是pdf文件地址 3.触发function函数:小于号button onclick="pdfsee ...

  2. js图片前端预览之 filereader 和 window.URL.createObjectURL

    //preview img : filereader方式 document.getElementById('imgFile').onchange = var ele = document.getEle ...

  3. 前端预览图片和H5canvas压缩图片上传

    思路是将图片抽样显示在canvas上,然后用通过canvas.toDataURL方法得到base64字符串来实现压缩. 1.base64转二进制文件 /** * dataURL to blob, re ...

  4. Lodop如何设置预览后导出带背景的图,打印不带背景图

    Lodop中的ADD_PRINT_SETUP_BKIMG,可以加载上背景图,该背景图在预览的时候可以显示也可以不显示,打印可以打印出来也可以不打印出来.一般套打,都是不打印背景图的,比如一些快递的快递 ...

  5. Visual Studio 2015和.Net 2015 预览版在线安装和ISO镜像安装光盘下载

    微软刚刚宣布了 Visual Studio 2015和.Net 2015 预览版,并同时提供了下载. 微软在纽约正进行中的#Connect# 全球开发者在线大会上宣布了Visual Studio 20 ...

  6. 怎么在线预览.doc,.docx,.ofd,.pdf,.wps,.cad文件以及Office文档的在线解析方式。

    前言 Office文件在线预览是目前移动化办公的一种新趋势.Office在线预览指的是Office系列的文件在线查看而不依附域客户端的存在.在浏览器或者浏览器控件中可以预览查看Word.PDF.Exc ...

  7. vue-preview vue图片预览插件+缩略图样式

    一.安装 npm i vue-preview -S 二.main.js中  导入组件 //vue-preview 开始 import VuePreview from 'vue-preview'; // ...

  8. 使用FileReader实现前端预览所选图片

    需求描述 在浏览器环境下进开发网站(也就是B/S架构的应用),获取到当前设备上的图片后,希望可以在上传到服务器前简单预览一下图片内容. 具体实现 主要代码 html 部分 <input type ...

  9. HTML5预览图片、异步上传文件

    注意啦:本文的代码都是以JQuery为示例,jq_开头的变量都是jq对象. 在HTML5中,我们可以在图片上传之前对图片进行预览,就像下面这么做 jq_upload_file.change(funct ...

  10. .net core版 文件上传/ 支持批量上传,拖拽以及预览,bootstrap fileinput上传文件

    asp.net mvc请移步 mvc文件上传支持批量上传,拖拽以及预览,文件内容校验 本篇内容主要解决.net core中文件上传的问题  开发环境:ubuntu+vscode 1.导入所需要的包:n ...

随机推荐

  1. uniapp H5图片编辑器(安卓/iOS适用)

    箭头绘制参考了:https://blog.csdn.net/qq_45939676/article/details/127425426 这位大佬的文章 gitee地址: https://gitee.c ...

  2. JS引擎(2):Java平台上JavaScript引擎—Rhino/Nashorn概述

    可以后端开发的 javascript引擎有 Chrome V8 基于C++ java的Rhino引擎(JDK6被植入),Java8 被替换为Nashorn Rhino和Nashorn都是用Java实现 ...

  3. 【Diary】CSP-S2 2021 游记 & NOIP 备赛发疯日记

    Day 0 两个极端的回跳. .....不行啊. 我快输不起了........... ------------------------------- 早上被生物钟强行唤醒,逼自己懒床到6:40. 弹琴 ...

  4. 【云享专刊】开源遇上华为云,OCP架构变身“云原生框架”

    摘要:华为云DTSE团队出品云原生改造指南,助力轻松实践OCP上云. 本文分享自华为云社区<[云享专刊]开源遇上华为云,OCP架构变身"云原生框架">,作者:华为云社区 ...

  5. sql ytd 附python 实现方式

    ytd释义 YTD分析属于同比分析类,其特点在于对比汇总值,即从年初第一日值一直至今的值累加.作用在于分析企业中长期的经营绩效. 做法 假定: 有一张销量明细表 date 仓库 sku 销量 2020 ...

  6. 08列表(list)与元组(tuple)

    列表(list)与元组(tuple) 列表的格式 >- [数据1,数据2,数据3,数据4,......] >- 列表可以存储多个数据,数据之间的逗号以英文分割而且可以数据是不同类型的数据, ...

  7. 火山引擎 DataTester:让企业“无代码”也能用起来的 A/B 实验平台

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 当数字化变革方兴未艾,无代码正受到前所未有的关注.Salesforce 的数据显示,52%的 IT 部门表示,公司 ...

  8. Win Pycharm + Airtest + 夜神模拟器 实现APP自动化

    前言: 前面已经讲过了Airtest的简单配置与使用了,相信大家已经对于操作Airtest没有什么问题了(#^.^#) 但是在Airtest IDE中编写代码是有局限性的,而且不能封装Airtest的 ...

  9. RocketMQ的简单使用

    大家好,我是Leo!今天来和大家分享RocketMQ的一些用法. 领域模型介绍 Producer: 用于生产消息的运行实体. Topic: 主题,用于消息传输和存储的分组容器. MessageQueu ...

  10. OCR 文字检测(Differentiable Binarization --- DB)

    文本检测 基于分割的做法(如蓝色箭头所示): 传统的pipeline使用固定的阈值对于分割后的热力图进行二值化处理 首先,它们设置了固定的阈值,用于将分割网络生成的概率图转换为二进制图像 然后,用一些 ...