需求背景
最近遇到一个需求,在使用富文本框编辑文章内容时,需要上传附件,同时上传的这个附件需要以附件的格式或者直接预览的方式展现。
实现基础
这个项目主要是基于vue2,使用了百度的富文本编辑器UEditor,其中对这个富文本编辑框的上传附件模块做了一些小封装,这些都忽略不计(因为大致的逻辑还是一样的,处理上传附件,将上传的附件通过ueditor内置的.execCommand("insertHTML",html)插入到对应的文章内部)。所以此处不做赘述。
其中对pdf进行预览的模块没有使用iframe或者embed等标签进行处理,原因就是其高度不会正常撑开,而这个过程中不好对pdf进行高度计算,所以总是会出现滚动条等不利于展示的因素。最后采用的是PDF.js,大致的实现过程就是通过pdf.js对pdf文件进行解析,从中转为canvas再通过canvas.toDataURL()通过img标签进行渲染该pdf文件;实测效果最佳,页面平铺。
实现过程
1.安装对应的pdf.js插件
版本最好注意一下,否则会有一些问题;笔者安装的是2.2.228版本
npm install pdfjs-dist@2.2.228
2.组件中引入:
import * as pdfjsLib from "pdfjs-dist/build/pdf";
// globalworker 设置,用 CDN 的资源;如果你本地的也可以那就可以配置成本地的 "pdfjs-dist/build/pdf.worker.min.js"
pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js";
3.处理对应的逻辑
以下是上传附件成功之后触发的事件,当上传附件成功之后 传入results为附件list,其中每个对象赋予了type:文件类型,preview:是否预览,是则展示预览的,否则展示附件格式;其他参数不做赘述
handleResourceDialogOk (results) {
if (results && results.length > 0) {
const r = results[0];
let html = '';
const editor = window.UE.getEditor(this.editorId)
results.forEach(r => { // TODO
if (r.resourceType == 'video' && r.preview) {
html += '<video controls="controls" style="width: 720px; height: 480px; margin: 0 auto;" src="' + r.src + '"></video>';
}else if (r.resourceType == 'file' && r.preview && r.type == 'pdf') {
this.loadPdf(r.src, editor)
return
} else if (r.resourceType == 'image') {
html += '<p><img src="' + r.src + '" iurl="' + r.path + '" class="art-body-img" /></p>'
} else {
html += '<p><a href="' + r.src + '" iurl="' + r.path + '" target="_blank" class="art-body-' + r.resourceType + '">' + r.name + '</a></p>'
}
});
if (html && html.length > 0) {
editor.execCommand("insertHTML",html);
}
}
},
随后就是处理pdf的代码如下:
/**
* pdfUrl 为文件所在路径
* editor 为富文本编辑器实例
*/
async loadPdf(pdfUrl, editor) {
let images = []
try {
const pdf = await pdfjsLib.getDocument(pdfUrl).promise;
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
const page = await pdf.getPage(pageNum);
const viewport = page.getViewport({ scale: 1.5 });
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({
canvasContext: context,
viewport: viewport,
}).promise;
const imageURL = canvas.toDataURL();
images.push(imageURL);
}
} catch (error) {
console.error('PDF 加载失败:', error);
}
const that = this
this.$nextTick(() => {
let html = '';
images.forEach(i => {
html += '<p style="text-align:center;"><img src="' + i + '" /><p>'
});
editor.execCommand("insertHTML",html, true);
})
},
理论上 差不多就可以实现了 但是由于笔者的这个项目 前人对ueditor做了一些处理,导致有一些问题,随着排查的同时也对对应的一些配置进行了修改;
踩坑修改过程
1.当插入html的过程中第三个参数是判断是否要过滤插入的代码,这个时候我还没意识到过滤的问题所在,思来想去先不做过滤好了,随即传递了第三个参数为true,ok,插入dom的时候正常了,页面也能正常去渲染。但是到了回显的时候,又找不到了
2.回显这个预览的pdf,回显的时候会调用实例的setContent方法渲染对于的html内容;
this.editor.setContent(this.value);
这个时候,我发现他不回显了;随即又跑到ueditor.all.js中查看,找到该函数
大致代码如下
它回显的时候在8088行处又又又根据制定的规则去过滤了 我便给此处打了一个标签,对此处进行重点排查;下方的是过滤的函数
其中有个对img的case判断
它将base64的去除了,所以导致上面两个阶段都不会正常处理图片的格式渲染,随即我将此处过滤机制注释掉,后面就很流畅的进行pdf预览等操作啦