The vue front-end previews pdf and adds watermarks, ofd files, controls printing, downloading, and saving, how to use vue-pdf and a collection of pits stepped on during development

According to the company's actual project needs, it is required to realize the preview of pdf and ofd files, and it is necessary to restrict whether users can download, print, and save pdf and ofd files. If the user can print and download, it is necessary to control the number of downloads and availability of each user. The number of times to print. The normal preview of pdf is very simple, you can directly call the preview of the browser and the functions are relatively complete, but when it comes to prohibiting users from printing and saving as a file, you cannot use the preview method that comes with the browser. Then we can only look for plug-ins to simulate. I have used pdfjs and vue-pdf plug-ins in the eletron-vue project before, but the effect is not particularly good. The bottom layer of vue-pdf is actually using pdfjs, which was inexplicable in the client project at that time. Some errors were reported (probably due to version issues, the project was in a hurry at the time and did not have much time to sort out the pitfalls) decisively abandoned the pitfalls and chose iframe to preview the pdf file, which could also meet the project requirements at that time. However, the current demand background is that the management side and the portal side want to realize this function. Without the limitation of electron, I decided to use vue-pdf. Local files and did not report unknown errors in electron. Let's talk about the specific usage of the plug-in first:

1. Install vue-pdf dependencies:

npm/yarn i/add vue-pdf

2.import imports and registers dependencies (which interface needs to be imported separately in which interface)

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

3. Use vue-pdf in the interface

// 点击分页
<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 // 加载的时候先加载第一页
},

The above code is to obtain the file stream of the pdf file from the backend. The await getFileBuffer(formData) request interface is assigned to res, and the status value of res is 200 to determine whether the file stream is successfully returned. After the return is successful, pass new Blob([res.data ]) to blob, and then get pdfSrc via URL.createObjectURL(blob).

The await getFilePages(formData) interface is to obtain the total number of pdf pages separately.

The picture above shows the preview of the pdf file according to the pdf data stream returned by the backend. Now it only displays the pdf file. At present, only a small part of it has been successful. It can be said that the most basic part of this part of the function has just been completed. There will be control print, download, save as, watermark...

We can implement them one by one. First, let’s talk about the prohibition of the shortcut keys ctrl+s (save) and ctrl+p (print) of the browser. You can use the following code to prohibit the use of these two shortcut keys in the current interface, and directly upload the code:

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
  );  
}

File download, write the download button on the interface, write the click event and v-if (control the number of downloads for this user, calculate the number of downloads in the background every time the user clicks the download button, if the number of downloads reaches the set number of times, hide the download button ) directly on the code:

<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
    ......	
},

File printing, write the download button on the interface, write the click event and v-if (control the number of prints for this user, calculate the number of prints in the background every time the user clicks the print button, and hide the print button if the number of prints reaches the set number ) Printing needs to download the print-js dependency

1. Install dependencies

yarn add print-js

2. Introduce plug-ins

import printJS from 'print-js'

3. Use the plugin (directly pass in the path of the file)

printJS(url)

4. Official document 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
        ......
    })
},

File preview watermark, the normal watermarking process is that the java backend adds the watermark to the file and returns it to the frontend, and the frontend can directly preview, download, and print the file with the watermark. The method of adding watermark to the frontend is relatively simple, and it can be found online. Here is an example of adding a watermark on the front end, and made a simple adjustment, and make a record here:

<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);
		}

The above mainly introduces the vue-pdf, printjs plug-ins and how to control the download, save and print functions at the front end, so let’s focus on the vue-pdf plug-in, and summarize the pits I encountered during the development process, for reference only There are many possible solutions

Dividing line Dividing line, this is a dividing line—dividing line Dividing line, this is a dividing line—dividing line Dividing line, this is a dividing line—dividing line Dividing line, this is a dividing line

1. There is nothing wrong with the preview during local development, that is, after the build is packaged and placed on the server, the preview cannot be previewed and an error is reported: prompt xxx.worker.js 500 or 404

Solution: Two places need to be changed:
① Modify the file location under dependency: node_modules/vue-pdf/src/vuePdfNoSss.vue



Note: Some version paths of the line that needs to be commented in the code are different, some are
: // var PdfjsWorker = require('worker-loader!pdfjs-dist/build/pdf.worker.js');
some are:// var PdfjsWorker = require('worker-loader!pdfjs-dist/es5/build/pdf.worker.js');

② Find the vue.config.js file of the project, and add the following code in the configuration file
    // 处理vue-pdf打包文件404
    config.module
    .rule('worker')
    .test(/\.worker\.js$/)
    .use('worker-loader').loader('worker-loader')
    .options({
        inline: true,
        fallback: false,
    }).end();
After the two places are changed, execute yarn run build to package and deploy to the server, and find that xxx.worker.js 500 or 404 will not be reported, and you can preview normally!

2. Most of the pdf previews in the online environment are fine, but some pdf files are not displayed completely, missing arms and legs, either this word is not displayed or that word is missing (there is still no problem locally)

Solution: I searched on the Internet, and some people encountered similar problems. The reason is that there is a lack of corresponding fonts. You need to introduce CMapReaderFactory on the interface, and then pass in the url to generate a new path and assign it to this.pdfSrc to solve the problem.
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. When we use the CMapReaderFactory in the previous question, another problem will be caused, that is, the problem of displaying blanks when previewing, the display is incomplete and there are blank pages, and the console still does not report an error

I also went to the Internet to find solutions to related problems. Some people said that it was because of a cache problem. The second loading took the loadModules cache of the language file when the PDF file was loaded for the first time, but it failed during the fetching process and returned If the value is empty, you only need to add a paragraph when modifying
//加载完语言文件后清除缓存
delete require.cache[require.resolve('./buffer-loader!pdfjs-dist-sign/cmaps/'+query.name+'.bcmap')];

4. After packaging and deploying to the server, the preview cannot see the content, the controller does not report an error, it is blank, and local development is fine.

I have tried all the tricks for this problem. I am working on the management side and the portal side at the same time. I need to preview the pdf. I use vue-pdf. The code logic is exactly the same, and the background interface is exactly the same. There is a problem with the development environment and deployment environment of the portal side. None, the development environment on the management side is fine. After the deployment is completed, the content is not displayed online. I changed this problem for a day, and there is no way to test it locally. I can only change it a bit and send it to the site as a package. The site deploys a version for me to test Is it easy to use? One day I sent 20 packages to the site. When I got off work, I sent a package to the site. I told the site buddies that this is not enough, so I will temporarily change back to the backup package. To be honest, I really don’t care about this package. What hope do you give. It turned out to be solved. I was shocked when I heard it. I changed it and sent 20 packages to the site for a day. In the end, this package was easy to use without changing anything. This version works just fine without changing anything. I used the latest "vue-pdf": "4.3.0" before, I uninstalled and re-clicked 4.0.7, and it was solved! ! !

5. Uncaught SyntaxError: Unexpected token ‘<‘

From the beginning of contacting vue-pdf, I feel that it is not a good thing. At the beginning, I contacted in the electron-vue project. After installing the dependencies, I reported Uncaught SyntaxError: Unexpected token '<'. I found a lot of solutions to this problem on the Internet. method, but it is useless, and finally this problem has not been solved. In the electron project, it is directly abandoned and replaced with an iframe to preview the pdf file, which can also achieve the desired effect.

Guess you like

Origin blog.csdn.net/weixin_48138187/article/details/131327416