vue中使用html2canvas配合jspdf导出pdf(以及在导出时遇到的导出样式问题)

指定页面中导出为pdf格式并打包,使用html2canvas先转为图片格式,在利用jspdf转为pdf,最后下载打包为本地压缩包

js 复制代码
yarn add html2canvas
yarn add jspdf
1. 注册一个插件并挂载
js 复制代码
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
export default {
    install(Vue, options) {
        Vue.prototype.getPdf = function (dom) {
            return new Promise((resolve, reject) => {
                html2Canvas(dom.$el, {
                    allowTaint: true,
                    scale: 2,
                    dpi: 300,
                }).then(function (canvas) {
                    let contentWidth = canvas.width;
                    let contentHeight = canvas.height;
                    let pdfWidth = 595.28; // A4 width in pixels
                    let pdfHeight = (pdfWidth / contentWidth) * contentHeight;
                    let pageData = canvas.toDataURL('image/jpeg', 1.0);

                    let PDF = new JsPDF('p', 'pt', 'a4');
                    let scale = pdfWidth / contentWidth;
                    PDF.addImage(pageData, 'JPEG', 0, 0, pdfWidth, pdfHeight);
                    let remainingHeight = pdfHeight;

                    while (remainingHeight < contentHeight) {
                        PDF.addPage();
                        let offsetY = -remainingHeight * scale;
                        PDF.addImage(pageData, 'JPEG', 0, offsetY, pdfWidth, pdfHeight);
                        remainingHeight += pdfHeight;
                    }
                    PDF.save('export.pdf'); //直接下载
                    // resolve(PDF.output('blob')); //转为blob格式 返回合并下载
                }).catch(reject);
            });
        };
    }
}
2. 页面使用
js 复制代码
DownPDFAndFile(){
  this.getPdf('传入ref或者是id')
}

以上正常导出步骤 封装方法 使用 但是如果你的项目中用的是rem布局或者是适配方法,样式或者字体没有显示出来被盖掉请往下看

3.由于在项目中用rem做了适配导致样式变形
4. 我们可以在html2canvas生成图片时把对应的样式或者添加类名,在生成图片前修改样式,转换为pdf后恢复初始样式
js 复制代码
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
/**
 * @param {*} 添加样式类名
 */
function removeChildrenWithClass(parent, className) {
    var children = parent.childNodes;
    if (parent.classList && parent.classList.contains('form_view_item')) {
        parent.childNodes.forEach((item) => {
            item.className += ' is_to_print'
        })
    }
    for (var i = 0; i < children.length; i++) {
        if (children[i].classList && children[i].classList.contains('isprint')) {
            children[i].style.display = 'block'
            if (children[i].classList.contains('is_toprint')) children[i].style.display = 'block'
        }
        if (children[i].classList && children[i].classList.contains('form_view_title')) {
            children[i].style.marginTop = '25px'
        }
        if (children[i].classList && children[i].classList.contains(className)) {
            children[i].style.display = 'none'
        } else if (children[i].childNodes) {
            removeChildrenWithClass(children[i], className);
        }
    }
}
/**
 * @param {*} 恢复初始样式
 */
function restoreOriginalState(parent, className) {
    var children = parent.childNodes;
    if (parent.classList && parent.classList.contains('form_view_item')) {
        parent.childNodes.forEach((item) => {
            item.classList.remove('is_to_print');
        });
    }
    for (var i = 0; i < children.length; i++) {
        if (children[i].classList && children[i].classList.contains(className)) {
            children[i].style.display = '';
        }
        if (children[i].classList && children[i].classList.contains('form_view_title')) {
            children[i].style.marginTop = '';
        }
        if (children[i].classList && children[i].classList.contains('isprint')) {
            children[i].style.display = 'none';
        }
        if (children[i].childNodes) {
            restoreOriginalState(children[i], className);
        }
    }
}
export default {
    install(Vue, options) {
        Vue.prototype.getPdf = function (dom) {
            return new Promise((resolve, reject) => {
                removeChildrenWithClass(dom.$el, 'noprint');
                html2Canvas(dom.$el, {
                    allowTaint: true,
                    //如果不要求清晰度可以去掉
                    scale: 2,  //按比例增加分辨率 
                    dpi: 300, //将分辨率提高到特定的 DPI
                }).then(function (canvas) {
                    restoreOriginalState(dom.$el, 'noprint')
                    let contentWidth = canvas.width;
                    let contentHeight = canvas.height;
                    let pdfWidth = 595.28; // A4 width in pixels
                    let pdfHeight = (pdfWidth / contentWidth) * contentHeight;
                    let pageData = canvas.toDataURL('image/jpeg', 1.0);

                    let PDF = new JsPDF('p', 'pt', 'a4');
                    let scale = pdfWidth / contentWidth;
                    PDF.addImage(pageData, 'JPEG', 0, 0, pdfWidth, pdfHeight);
                    let remainingHeight = pdfHeight;

                    while (remainingHeight < contentHeight) {
                        PDF.addPage();
                        let offsetY = -remainingHeight * scale;
                        PDF.addImage(pageData, 'JPEG', 0, offsetY, pdfWidth, pdfHeight);
                        remainingHeight += pdfHeight;
                    }
                    PDF.save('export.pdf'); //直接下载
                    // resolve(PDF.output('blob')); //转为blob格式 返回合并下载
                }).catch(reject);
            });
        };
    }
}
5. 修改完后导出正常
6. 如果需求是直接下载以下就忽略,如果需求是打包为本地压缩包请继续
js 复制代码
// 引入使用attachDownload方法
// 下载事件
async DownPDFAndFile() {
      const pdfinfo = await this.getPdf('传入ref或者是id')
      const pdfList = [
      //参数字段自行修改 为attachDownload 中的字段对应
				  {
				     id: "...",
				     data: pdfinfo,
				  },
      	];
      const ImageList = []
      const VideoList = []
      const ImageAndVideoAndPdfList = [...pdfList,...ImageList,...VideoList]
      const { downloadStatus } = await this.is_downFile(ImageAndVideoAndPdfList);
      // downloadStatus 这个状态为下载状态 true为完成 可以自行添加业务
    },
// 下载方法
async is_downFile(list) {
      if (list.length > 0) {
        const config = {
          downloadList: list,
          suffix: "病历编辑.zip",
        };
        const { downloadStatus } = await attachDownload(config);
        return { downloadStatus };
      }
    },
7. 打包下载使用的是JSZip 和 FileSaver
js 复制代码
yarn add jszip
yarn add file-saver
js 复制代码
// attachDownload.js
import JSZip from "jszip";
import FileSaver from "file-saver";
export async function attachDownload(config) {
    const { downloadList, suffix } = config
    const zip = new JSZip();
    const cache = {};
    let downloadStatus = false
    const downloadPromises = downloadList.map(async (item) => {
        try {
            if (item.url) {
                let data;
                if(item.type=='.pdf'){
                    data = item.data;
                }else{
                    data = await getImgArrayBuffer(item.url);
                }
                zip.folder(suffix).file(`${item.Title}_${item.FileID}` + item.type, data, { binary: true });
                cache[item.id] = data;
            } else {
                throw new Error(`文件${item.fileName}地址错误,下载失败`);
            }
        } catch (error) {
            console.error("文件获取失败", error);
        }
    });
    try {
        await Promise.all(downloadPromises);
        const content = await zip.generateAsync({ type: "blob" });
        FileSaver.saveAs(content, suffix);
        downloadStatus = true
        return {
            downloadStatus
        }
    } catch (error) {
        console.error("文件压缩失败", error);
    }
}
async function getImgArrayBuffer(url) {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`请求失败: ${response.status}`);
    }
    return await response.blob();
}
相关推荐
Dread_lxy2 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
网络安全指导员1 小时前
恶意PDF文档分析记录
网络·安全·web安全·pdf
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
前端郭德纲1 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR1 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜1 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript