文章目录 一、实现思路 二、前端实现方式 2.1 使用Blob实现PDF下载预览 2.2 使用JSON格式实现PDF下载预览 三、后端实现代码 四、使用pdfjs转换为canvas渲染PDF 今天……
文
章
目
录
今天来给大伙分享一下前端Vue结合后端Node.js实现PDF下载和预览功能的具体方法。先给大伙看看最终实现的效果:有一个记录了不同版本更新信息的表格,里面包含版本号、更新内容、负责人和更新时间等信息。在实际应用场景里,这个表格数据可能就存放在PDF文件中,咱们要做的就是实现对这个PDF的下载和预览。
一、实现思路
整体实现思路是这样的:前端借助pdfjs来渲染PDF文件;后端负责读取PDF文件,并把它转换为流文件,之后通过接口响应发送给前端,前端收到数据后进行渲染展示。
二、前端实现方式
前端从服务端获取流文件有JSON和Blob两种形式,这两种方式各有优劣。JSON返回的数据字段更灵活,开发者能根据不同的业务情况进行针对性处理;Blob形式则相对简单直接,后端返回的数据会被整合成一个Blob类型的数据对象。
2.1 使用Blob实现PDF下载预览
采用Blob形式时,需要把接口的返回类型responseType
设置为Blob
。这种方式会将后端返回的所有数据打包成一个Blob类型的数据。这里简单解释一下Blob,它是一种二进制大对象,在处理文件数据时经常会用到。下面看看具体代码:
// 创建下载链接的函数,接收文件数据buffer和文件名filename(默认为\'可回溯.pdf\')
const createLinkDownload = (buffer, filename = \'可回溯.pdf\') => {
// 根据文件数据创建Blob对象,指定类型为\'application/pdf\'
const blob = new Blob([buffer], { type: \'application/pdf\' });
// 创建一个a标签用于触发下载
const link = document.createElement(\'a\');
// 为a标签设置下载链接,通过URL.createObjectURL将Blob对象转换为可访问的URL
link.href = URL.createObjectURL(blob);
// 设置下载的文件名
link.download = filename;
// 模拟点击a标签,触发下载操作
link.click();
};
// 执行Blob下载的函数
const blobDownload = async () => {
// 发送axios的get请求到\'/api/download\'接口,设置responseType为\'blob\'
const response = await axios.get(\'/api/download\', {
responseType: \'blob\',
});
// 调用createLinkDownload函数,传入响应数据进行下载
createLinkDownload(response.data);
};
再看看服务端的接口逻辑,这里用的是Node.js和Express框架:
从上面的代码可以看出,当使用Blob形式时,前端请求接口后,即便服务端返回了加载成功等信息,前端也获取不到这些字段,因为请求时设置了responseType: \'blob\'
,而不是application/json
。
2.2 使用JSON格式实现PDF下载预览
使用JSON格式的关键在于,后端要把读取到的PDF文件转换为base64编码。由于base64本质是字符串,和Blob不同,前端获取到数据后,需要将base64转换为Buffer才能进一步处理。JSON格式的优势在于能更明确地返回字段,数据结构更清晰。下面是具体实现代码:
// 创建下载链接的函数,和Blob方式中的这个函数作用相同
const createLinkDownload = (buffer, filename = \'可回溯.pdf\') => {
const blob = new Blob([buffer], { type: \'application/pdf\' });
const link = document.createElement(\'a\');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click()、、、、;
};
// 执行JSON下载的函数
const jsonDownload = async () => {
// 发送axios的post请求到\'/api/download\'接口
const response = await axios.post(\'/api/download\', {});
// 打印响应数据,方便调试查看
console.log(\'response\', response);
// 将base64编码的数据转换为ArrayBuffer
const arrerBuffer = base64ToBytes(response.data.data);
// 调用createLinkDownload函数,传入转换后的ArrayBuffer进行下载
createLinkDownload(arrerBuffer);
};
三、后端实现代码
后端代码和前面Blob方式中处理下载接口的代码基本一致,主要是通过Express框架设置接口,读取PDF文件并进行相应处理。代码如下:
// 引入express框架
const expressApp = require(\'express\')
// 用于处理文件路径
const path = require(\'path\')
// 用于异步操作文件系统
const fs = require(\'fs/promises\')
// 自定义的通用响应处理函数
const { commonResponse } = require(\'./common\')
// 创建express应用实例
const app = expressApp()
// 设置服务端口
const port = 8085
// 定义一个函数,用于解析相对路径为绝对路径
const resolveDir = (fileDir) => path.resolve(__dirname, fileDir)
// 指定要下载的PDF文件路径
const pdfDir = resolveDir(\'../assets/pdf/可回溯逻辑梳理.pdf\')
// 处理GET请求的下载接口
app.get(\'/download\', (req, res) => {
// 使用res.download方法直接下载指定路径的PDF文件,并命名为\'可回溯pdf\'
res.download(pdfDir, \'可回溯pdf\')
// 发送通用响应,告知前端加载成功
res.send(commonResponse(\'加载成功\', \'1\', {}))
})
// 处理POST请求的下载接口
app.post(\'/download\', async (req, res) => {
// 以base64编码方式读取PDF文件内容
const pdfStream = await fs.readFile(pdfDir, { encoding: \'base64\' })
// 如果读取失败,返回加载失败的响应
if (!pdfStream) {
return res.send(commonResponse(\'加载失败\', \'-2\', {}))
}
// 读取成功则返回加载成功的响应,包含读取的PDF文件内容
res.send(commonResponse(\'加载成功\', \'1\', pdfStream))
})
// 启动服务,监听指定端口
app.listen(port, () => {
console.log(\"服务启动成功\", port)
})
四、使用pdfjs转换为canvas渲染PDF
在实现PDF预览时,使用pdfjs将PDF转换为canvas进行渲染。这里的核心要点是如何让PDF显示得更清晰,关键就在于对PDF进行缩放,使其适配屏幕大小。下面这段代码详细展示了实现过程:
// 渲染PDF页面的函数,接收要渲染的页码pageNumber
const renderPdfPage = async (pageNumber) => {
// 获取指定页码的页面
const page = await pdfDocumentInstance.getPage(pageNumber);
// 打印页面信息,方便调试查看
console.log(\'page\', page);
// 获取设备像素比,用于适配不同屏幕分辨率
const dpr = window.devicePixelRatio || 1;
// 获取设备的实际宽度
const deviceWidth = window.innerWidth || 414;
// 计算rem基准值,这里设定1rem = 100px
const remBase = 100;
// 获取原始的viewport,用于计算合适的缩放比例
const originalViewport = page.getViewport({ scale: 1.0 });
// 根据设备宽度和原始viewport宽度计算适合屏幕的缩放比例
const fitScale = (deviceWidth * dpr) / originalViewport.width;
// 设置一个更高的基础缩放比例,用于提高清晰度
const qualityScale = 2.5;
// 根据计算的缩放比例获取新的viewport,用于设置画布尺寸
const viewport = page.getViewport({ scale: fitScale * qualityScale });
// 获取用于渲染PDF的canvas元素
const canvasEls = pdfCanvas.value[pageNumber - 1];
// 获取canvas的2d绘图上下文
const context = canvasEls.getContext(\'2d\', { alpha: false });
// 设置canvas的实际高度和宽度,考虑缩放比例
canvasEls.height = viewport.height;
canvasEls.width = viewport.width;
// 设置canvas的显示高度和宽度,使用rem单位,方便适配不同屏幕
canvasEls.style.height = `${viewport.height / (dpr * qualityScale) / remBase}rem`;
canvasEls.style.width = `${viewport.width / (dpr * qualityScale) / remBase}rem`;
// 启用图像平滑处理,提高渲染质量
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = \'high\';
// 根据设备像素比和质量缩放来调整上下文
context.scale(1, 1);
// 定义渲染上下文对象,包含canvas绘图上下文和viewport信息
const renderContext = {
canvasContext: context,
viewport: viewport,
};
// 渲染页面,并等待渲染完成
await page.render(renderContext).promise;
// 如果还有更多页面需要渲染,递归调用渲染下一页
if (maxRenderPdfNum.value > pageNumber) {
renderPdfPage(pageNumber + 1);
}
};
// 获取PDF文件数据的函数
const getPdf = async () => {
// 打印日志,方便调试查看
console.log(\'1111\');
// 发送请求获取PDF文件流
const response = await getPdfFileStream();
// 打印响应数据,方便调试查看
console.log(\'response\', response);
// 如果响应存在且数据中的STATUS字段为\'1\',说明获取数据成功
if (response && response.data?.STATUS === \'1\') {
// 将获取到的PDF数据存储到响应式变量pdfBufferData中
pdfBufferData.value = response.data.data;
}
};
// 组件挂载完成后的生命周期钩子函数
onMounted(async () => {
// 设置pdfjs的worker源,确保其能正常工作
new pdfjsLib.GlobalWorkerOptions().workerSrc = pdfjsWorker || {};
// 发送请求获取PDF文件数据
await getPdf();
// 将base64编码的PDF数据转换为ArrayBuffer
const arrayBuffer = await base64ToBytes(pdfBufferData.value);
// 使用pdfjsLib加载PDF文件数据
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
// 等待PDF文档加载完成
pdfDocumentInstance = await loadingTask.promise;
// 获取PDF文档的总页数并存储到响应式变量maxRenderPdfNum中
maxRenderPdfNum.value = pdfDocumentInstance._pdfInfo.numPages;
// 开始渲染第一页
renderPdfPage(1);
});
以上就是前端Vue结合后端Node.js实现PDF下载和预览功能的全部内容啦!在实际开发过程中,大家可以根据具体的业务需求对代码进行调整和优化。要是在实现过程中遇到什么问题,欢迎在评论区留言交流!
还没有评论呢,快来抢沙发~