业务开发实现二维码、PDF下载记录

一、前言

最近开发过程中遇到了二维码和PDF下载的需求,虽然这些都有常见的开源库实现,但在开发的过程还是出现了一些卡壳问题,今天记录下,方便参考。

二、二维码设置和下载

csharp 复制代码
yarn add qrcode.react

下载二维码的开源库很多,我们选择qrcode.react,这个功能比较满足我的需要,体验可以点击zpao.github.io/qrcode.reac...

要实现的效果如下:

二维码展示在页面中是很简单的,在实际业务中,遇到了如下的问题:

1、调整二维码的尺寸,展示给用户时尺寸不变,下载的时候要生成用户需要的尺寸

2、二维码中间的图片的地址存在跨域问题,那么需要转化成同源的才可以同时下载。

3、下载的PNG或JPEG文件尺寸,在Mac平台和Win平台尺寸不一致

1、解决展示和下载二维码尺寸不一致问题

当你调整二维码大小参数时,二维码会变大,导致展示不友好,我们只需要下载的时候保持用户选择的尺寸就行。

下面的实现是随着size的大小而变化。

js 复制代码
import { QRCodeSVG, QRCodeCanvas } from 'qrcode.react';
  
<QRCodeSVG
  value={"https://picturesofpeoplescanningqrcodes.tumblr.com/"}
  size={size}
  level={"L"}
  includeMargin={false}
  imageSettings={{
    src: "https://static.zpao.com/favicon.png",
    height: 24,
    width: 24,
    excavate: true,
  }}
/>

上面是我们用来展示二维码,下面的我们添加display: none用来下载,对用户不可见。

js 复制代码
<div style={{ display: 'none' }}>
  <div ref={RQCodeRef}>
    {currentValue.imageType === 'svg' ? (
      <QRCodeSVG {...QROptions} />
    ) : (
      <QRCodeCanvas {...QROptions} />
    )}
  </div>
</div>

2、解决远程图片跨域无法下载问题

无法下载是因为图片异步加载,无法读取图片,所以我们可以将图片下载到本地或者引用本地的图片。

js 复制代码
const [imageUrl, setImageUrl] = useState('');
const getImage = () => { 
    fetch(faviconSrc) .then((response) => { 
        if (!response.ok) { 
            throw new Error('网络响应不正常'); 
        } 
        return response.blob(); 
    }).then((imageBlob) => { 
        const imageUrl = URL.createObjectURL(imageBlob);
        setImageUrl(imageUrl); 
    });
}

通过http下载图片资源,通过URL.createObjectURL转化成本地的URL,然后再使用。

3、解决下载的尺寸不一致问题

尺寸不一样是因为不同电脑的屏幕像素比不一样。

测试发现,在window电脑上window.devicePixelRatio为0.75,在mac上为1,为了获取实际像素,需要计算下Math.ceil(canvasImg.width / scaleFactor),然后再将图片绘制在canvas上,就可以转化成图片进行下载了。

下载PNG或JPEG

js 复制代码
const downloadImage = (fileName, imageType) => {
    // png/jpeg
    const canvasImg = RQCodeRef.current; // 获取canvas类型的二维码
    const scaleFactor = window.devicePixelRatio || 1;

    // 获取原始尺寸的 dataUrl
    const dataUrl = canvasImg.toDataURL(`image/${imageType}`);

    // 设置 canvas 的实际像素尺寸
    canvasImg.width = Math.ceil(canvasImg.width / scaleFactor);
    canvasImg.height = Math.ceil(canvasImg.height / scaleFactor);

    const context = canvasImg.getContext('2d');

    // 清空 canvas
    context.clearRect(0, 0, canvasImg.width, canvasImg.height);

    // 将缩小后的 dataUrl 绘制到 canvas
    const img = new Image();
    img.onload = function () {
      context.drawImage(img, 0, 0, canvasImg.width, canvasImg.height);

      // 创建下载链接并触发下载
      const a = document.createElement('a');
      a.href = canvasImg.toDataURL(`image/${imageType}`);
      a.download = `${fileName}.${imageType}`;
      a.click();
    };
    img.src = dataUrl;
  };

下载SVG

js 复制代码
const downloadSVG = (fileName) => {
  const svgElement = RQCodeRef.current;
  const svgXml = new XMLSerializer().serializeToString(svgElement);
  const blob = new Blob([svgXml], { type: 'image/svg+xml' });

  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = `${fileName}.svg`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

上面就分别实现了SVG和PNG的下载,并且对用户不可见。

三、PDF下载

PDF的下载调研了html2pdf.jsjspdf,前者简单一点,可以支持自动分页,但下载下来发现页面中的图表错位严重,暂时放弃了,这里通过jspdf看如何实现。

使用了html2canvas库,它可以将 DOM 元素渲染为Canvas。方便生成截图、制作网页快照或将HTML嵌入其他文档。

js 复制代码
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';

const htmlCanvasOptions = {
    allowTaint: true,
    taintTest: false,
    dpi: '192',
    scale: 2
}

const download = () => {
    const element = document.getElementById('slk-export-pdf');
    if (!element) return;
    const canvas = await html2canvas(element, htmlCanvasOptions);
    // 获取canvas的宽度和高度
    const contentWidth = canvas.width;
    const contentHeight = canvas.height;
    // 按A4纸的宽度和高度,将html元素的宽度和a4纸的比,计算出A4值的高度。
    const pageHeight = (contentWidth / a4Width) * a4Height;
    let leftHeight = contentHeight;
    let position = 0;
    // 计算出添加到PDF的高度
    const imgWidth = a4Width;
    const imgHeight = (a4Width / contentWidth) * contentHeight;
    
    // 将Canvas转化成base64编码
    const pageData = canvas.toDataURL('image/jpeg', 1.0);
    const pdf = new jsPDF('', 'pt', 'a4');
    // 如果剩余高度小于页面高度,就不再分页
    if (leftHeight < pageHeight) {
      pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
    } else {
    // 分页逻辑
      while (leftHeight > 0) {
        pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
        leftHeight -= pageHeight;
        position -= 841.89;
        if (leftHeight > 0) {
          pdf.addPage();
        }
      }
    }
    pdf.save('test.pdf');
}

我们用html2canvas将html转化成canvas元素,然后写入PDF,但此时要注意页面比例要和pdf保持一致,特别是长页面,需要在特定比例高度保持间隙,否则会被截取部分。

最后我们来看下效果,还是很不错的。

四、总结

现在各种开源工具真的是造福程序员,但选择成本也在上升,我选择的不一定是最好的,各位朋友如果有更好的可以推荐下。这篇文章主要用来记录踩过的一些坑。

相关推荐
(⊙o⊙)~哦44 分钟前
JavaScript substring() 方法
前端
无心使然云中漫步1 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者1 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_2 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋3 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120533 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢3 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写4 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
史努比.4 小时前
redis群集三种模式:主从复制、哨兵、集群
前端·bootstrap·html
快乐牌刀片885 小时前
web - JavaScript
开发语言·前端·javascript