vue中使用html2canvas+jsPDF实现pdf的导出
导入依赖
html2canvas依赖
npm install html2canvas
jspdf依赖
npm install jspdf
pdf导出
以导出横向,A4大小的pdf为例
规律:1. html2canvas 中,在保持jsPDF中的宽高不变的情况下,设置html2canvas中的 width 和 height 值越小,导出的pdf越显示不全(会被放大,只能看到局部),反之值越大,导出的pdf越显示完整(值也不能过大,过大在pdf中就显示的越小)。
2. jsPDF 中,在保持html2canvas中的宽高不变的情况下,pdf.addImage(pageData, ‘JPEG’, 5, yPosition, width, height) width 和 height 值越小,导出的pdf越显示完整,反之导出的pdf越显示不全。
总结:html2canvas 与 jsPDF 设置刚好相反,合理设置大小,才能使数据撑满整个pdf。
index.vue执行导出pdf页面
<a-spin :spinning="pdfConfirmLoading"><div ref="pdfDiv"><pdf-template :ref="'pdfTemplate' + i" v-for="i in arrNum"></pdf-template></div>
</a-spin><script>import {printPdf} from './utils/index'import pdfTemplate from './template/pdfTemplate'export default {name: 'courseTableList',components: {pdfTemplate},data() {return {pdfVisible: false,disableSubmit: false,arrNum: 1,pdfConfirmLoading: false,}},created() {},methods: {//导出pdfasync handleExportPdf() {this.handleElement()},handleElement() {if(this.selectionRows.length > 0) {this.pdfConfirmLoading = trueconst groupedData = this.selectionRows.reduce((result, item) => {// 检查是否已存在以该teacher为键的分组if (!result[item.instructor]) {result[item.instructor] = [];}// 将当前项添加到对应的分组中result[item.instructor].push(item);return result;}, {});// 转换成数组形式const groupedArray = Object.values(groupedData);let len = groupedArray.lengththis.arrNum = lenlet that = thisthis.pdfVisible = truethis.$nextTick(() => {for (let index = 0; index < groupedArray.length; index++) {const item = groupedArray[index];let num = index + 1let pageFooter = num + ' of ' + len;that.$refs['pdfTemplate' + num][0].setDataList(item, pageFooter);}this.pdfConfirmLoading = false})} else {this.$message.warning("请勾选数据")}},//确认导出pdfasync confirmExportPdf() {this.pdfConfirmLoading = trueawait printPdf(this.$refs.pdfDiv, "courseSchedule")this.pdfConfirmLoading = falsethis.pdfVisible = false},}}
</script>
pdfTemplate.vue模板,根据需求自定义创建
<template><div class="content" ref="pdfContent" :key="JSON.stringify(datasource)"><!-- 头部 --><div class="header"><div class="header_row1"><div class="header_row1_v"><div class="header_row1_left"><span>Regd. User: YiZhong College</span></div><div class="header_row1_right"><span>Christine Xia</span></div></div><div class="header_row1_middle"><span>Timetables</span></div></div><div class="header_row2"><div class="header_row2_v"><div class="header_row2_left"><span>School: FD - YiZhong Cambridge International School</span></div><div class="header_row2_right"><span>FDCC1</span></div></div><div class="header_row2_middle"><span>FD 2022-23 Teacher's Timetables</span></div></div><div class="header_line"></div></div><!-- 表格 --><div class="table_middle"><a-table :columns="columns" :data-source="datasource" bordered :pagination="false"><!-- 时间段 --><tamplate slot="timePeriodSlot" slot-scope="text, record"><span>{{record.period}}<br> {{text}} ~ {{record.endTime}}</span></tamplate></a-table></div><!-- 页脚 --><div class="footer"><div class="footer_line"></div><div class="footer_end"><div class="footer_end_datetime">{{this.currentTime}}</div><div class="footer_end_pages">{{this.pageNum}}</div></div></div></div>
</template><script>let columns = [{title: '教师',dataIndex: 'startTime',scopedSlots: { customRender: 'timePeriodSlot' },customCell: () => {return {style: {'min-width': '120px',},};},},{title: "Monday",children: [{title: '',dataIndex: 'monday',key: 'monday',customCell: () => {return {style: {'min-width': '180px',},};},},],},{title: "Tues",children: [{title: '',dataIndex: 'tuesday',key: 'tuesday',scopedSlots: { customRender: 'childrenRender' },customCell: () => {return {style: {'min-width': '180px',},};},},],},{title: "Wed",children: [{title: '',dataIndex: 'wednesday',key: 'wednesday',scopedSlots: { customRender: 'childrenRender' },customCell: () => {return {style: {'min-width': '180px',},};},},],},{title: "Thurs",children: [{title: '',dataIndex: 'thursday',key: 'thursday',scopedSlots: { customRender: 'childrenRender' },customCell: () => {return {style: {'min-width': '180px',},};},},],},{title: "Friday",children: [{title: '',dataIndex: 'friday',key: 'friday',scopedSlots: { customRender: 'childrenRender' },customCell: () => {return {style: {'min-width': '180px',},};},},],},
];export default {name: "pdfExport",data() {return {currentTime: '',pageNum: 'Page 1 of 1',datasource: [],}},computed: {columns() {return columns}},created() {this.getCurrentTime();},methods: {setDataList(list, pageFooter) {this.datasource = listif(this.datasource.length > 0) {this.columns[0].title = this.datasource[0].instructor}this.sortByStartTime()if(pageFooter) {this.pageNum = pageFooter}},sortByStartTime() {this.datasource.sort((a, b) => {const timeA = new Date(`2023/01/01 ${a.startTime}`);const timeB = new Date(`2023/01/01 ${b.startTime}`);return timeA - timeB;});},getCurrentTime() {const date = new Date();const month = date.getMonth() + 1;const day = date.getDate();const year = date.getFullYear();const hours = date.getHours();const minutes = date.getMinutes();const seconds = date.getSeconds();// 格式化为指定格式的字符串const dateString = `${this.formatNumber(month)}/${this.formatNumber(day)}/${year}`;const timeString = `${this.formatNumber(hours)}:${this.formatNumber(minutes)}:${this.formatNumber(seconds)}`;this.currentTime = `${dateString} ${timeString}`;},formatNumber(number) {return number < 10 ? `0${number}` : number;}}
}
</script><style scoped lang="less">.content {padding: 15px;color: black;}/**头部属性样式设置*/.header_row1 {display: flex;justify-content: center;font-size: 16px;margin-bottom: 3px;position: relative;}.header_row1 .header_row1_middle {font-weight: 700;position: absolute;}.header_row2 {display: flex;justify-content: center;font-size: 16px;margin-bottom: 15px;position: relative;}.header_row2 .header_row2_middle {font-size: 22px;font-weight: 700;position: absolute;}.header_row1_left,.header_row1_right,.header_row2_left,.header_row2_right {display: inline-block;}.header_row1_v,.header_row2_v {width: 100%;}.header_row1_left,.header_row2_left {float: left;}.header_row1_right,.header_row2_right {float: right;}.header_line {border: 1px solid black;margin-bottom: 5px;}/**中间属性样式设置*//* 将表格的标题行背景设置为白色 */.ant-table-thead {::v-deep & > tr > th {background: #fff;}}/* 将所有边框设置为黑色 */.table_middle {/deep/ .ant-table {color: black;font-size: 16px;}/deep/ .ant-table-bordered .ant-table-thead > tr:first-child > th:first-child {border-left: none;border-top: none;font-weight: 700;text-align: left;}/deep/ .ant-table-bordered .ant-table-thead > tr:not(:last-child) > th {border-bottom: 2px solid black;border-top: 2px solid black;font-weight: normal;text-align: center;}/deep/ .ant-table-bordered .ant-table-thead > tr > th{border-right: 2px solid black;border-bottom: 2px solid black;}/deep/.ant-table-bordered .ant-table-tbody > tr > td {border-right: 2px solid black;border-bottom: 2px solid black;}/deep/ .ant-table-bordered .ant-table-tbody > tr > td:first-child {border-left: 2px solid black;}/deep/ .ant-table-bordered.ant-table-empty .ant-table-placeholder {border: 2px solid black;border-top: 1px solid black;}}/**页脚属性样式设置*/.footer .footer_line {border: 1px solid black;}.footer {font-size: 16px;}.footer_line {margin-top: 75px;margin-bottom: 3px;}.footer_end {display: flex;justify-content: space-between;}
</style>
index.js pdf导出单页方法
export const printPdf = (dom, name = '文件') => {const printEle = domlet width = printEle.scrollWidth;let height = printEle.scrollHeight;html2canvas(printEle, {allowTaint: true, //允许跨域useCORS: true,width: width,height: height,background: '#FFFFFF', //如果指定的div没有设置背景色会默认成黑色scale: 2 // 按比例增加分辨率}).then(canvas => {let contentWidth = canvas.widthlet contentHeight = canvas.height//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高let pdf = new jsPDF('l', 'pt', 'a4')let imgWidth = pdf.internal.pageSize.getWidth()let imgHeight = (imgWidth / contentWidth) * contentHeightlet pageData = canvas.toDataURL('image/jpeg', 1.0)//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)//当内容未超过pdf一页显示的范围,无需分页pdf.addImage(pageData, 'JPEG', 5, 5, imgWidth, imgHeight)pdf.save(`${name}.pdf`)})
}
index.js pdf导出分页方法
import html2canvas from 'html2canvas'
import { jsPDF } from 'jspdf'export const printPdf = (dom, name = '文件') => {let contents = dom.getElementsByClassName("content")return exportToPDF(contents, name)
}//导出pdf
async function exportToPDF(contents, name = 'exportPdf') {// 创建一个空的横向A4大小的PDF文档对象const pdf = new jsPDF('l', 'pt', 'a4');let yPosition = 5; // 当前y坐标位置for (let i = 0; i < contents.length; i++) {const content = contents[i];let width = content.scrollWidth;let height = content.scrollHeight;// 使用html2canvas将content转换为canvasconst canvas = await html2canvas(content, {allowTaint: true, //允许跨域useCORS: true,width: width * 1.02,height: height,background: '#FFFFFF', //如果指定的div没有设置背景色会默认成黑色scale: 2 // 按比例增加分辨率})// 如果是下一个content的内容,则创建新的页码if (i > 0) {pdf.addPage();yPosition = 5;}let pageData = canvas.toDataURL('image/jpeg', 1.0)//宽度使用pdf的宽度let imgWidth = pdf.internal.pageSize.getWidth()//高度根据宽度的比列计算let imgHeight = (imgWidth / canvas.width) * canvas.height// 将canvas添加到PDF中pdf.addImage(pageData, 'JPEG', 5, yPosition, imgWidth, imgHeight)yPosition += pdf.internal.pageSize.getHeight();}// 输出PDF文件pdf.save(`${name}.pdf`)return new Promise((resolve, reject) => {resolve()})
}
相关文章:
vue中使用html2canvas+jsPDF实现pdf的导出
导入依赖 html2canvas依赖 npm install html2canvasjspdf依赖 npm install jspdfpdf导出 以导出横向,A4大小的pdf为例 规律:1. html2canvas 中,在保持jsPDF中的宽高不变的情况下,设置html2canvas中的 width 和 height 值越小&a…...

Linux学习之firewallD
systemctl status firewalld.service查看一下firewalld服务的状态,发现状态是inactive (dead)。 systemctl start firewalld.service启动firewalld,systemctl status firewalld.service查看一下firewalld服务的状态,发现状态是active (runni…...
【JS学习】Object.assign 用法介绍
Object.assign 是ES6中的一个方法。该方法能够实现对象的浅复制以及对象合并。Object.assign 并不会修改目标对象本身,而是返回一个新的对象,其中包含了所有源对象的属性。 例1 2个对象合并 const target { a: 1, b: 2 }; const source { b: 3, c: 4…...

【uni-app报错】获取用户收货地址uni.chooseAddress()报错问题
chooseAddress:fail the api need to be declared in …e requiredPrivateInf 原因: 小程序配置 / 全局配置 (qq.com) 解决: 登录小程序后台申请接口 按照流程申请即可 在项目根目录中找到 manifest.json 文件,在左侧导航栏选择源码视图&a…...

机器学习、cv、nlp的一些前置知识
为节省篇幅,不标注文章来源和文章的问题场景。大部分是我的通俗理解。 文章目录 向量关于向量的偏导数:雅可比矩阵二阶导数矩阵:海森矩阵随机变量随机场伽马函数beta分布数学术语坐标上升法协方差训练集,验证集,测试集…...

Steam 灵感的游戏卡悬停效果
先看效果: 再看代码(查看更多): <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Steam 灵感的游戏卡悬停效果</title><style>* {margin: …...
[Openwrt]一步一步搭建MT7981A uboot、atf、openwrt-21.02开发环境操作说明
安装ubuntu-18.04 软件安装包 ubuntu-18.04-desktop-amd64.iso 修改ubuntu管理员密码 sudo passwd [sudo] password for w1804: Enter new UNIX password: Retype new UNIX password: passwd: password updated successfully 更新ubuntu源 备份源 sudo cp /etc/apt/so…...

Unity C# 之 Azure 微软SSML语音合成TTS流式获取音频数据以及表情嘴型 Animation 的简单整理
Unity C# 之 Azure 微软SSML语音合成TTS流式获取音频数据以及表情嘴型 Animation 的简单整理 目录 Unity C# 之 Azure 微软SSML语音合成TTS流式获取音频数据以及表情嘴型 Animation 的简单整理 一、简单介绍 二、实现原理 三、注意事项 四、实现步骤 五、关键代码 一、简…...

安全学习DAY16_信息打点-CDN绕过
信息打点-CDN绕过 文章目录 信息打点-CDN绕过本节思维导图相关链接&工具站&项目工具前置知识:CDN配置:配置1:加速域名-需要启用加速的域名配置2:加速区域-需要启用加速的地区配置3:加速类型-需要启用加速的资源…...

genism word2vec方法
文章目录 概述使用示例模型的保存与使用训练参数详解([原链接](https://blog.csdn.net/weixin_44852067/article/details/130221655))语料库训练 概述 word2vec是按句子来处理的Sentences(句子们) 使用示例 from gensim.models import Word2Vec #sent…...

vue3自定义样式-路由-axios拦截器
基于vue,vite和elementPlus 基于elementPlus自定义样式 history模式的路由 在根目录配置jsconfig.json,添加json的配置项。输入自动联想到src目录,是根路径的别名拦截器 如果存在多个接口地址,可以配置多个axios实例 数据持久化之后&#x…...

【mysql】事务的四种特性的理解
🌇个人主页:平凡的小苏 📚学习格言:命运给你一个低的起点,是想看你精彩的翻盘,而不是让你自甘堕落,脚下的路虽然难走,但我还能走,比起向阳而生,我更想尝试逆风…...

C++中List的实现
前言 数据结构中,我们了解到了链表,但是我们使用时需要自己去实现链表才能用,但是C出现了list将这一切皆变为现。list可以看作是一个带头双向循环的链表结构,并且可以在任意的正确范围内进行增删查改数据的容器。list容器一样也是…...

ElementUI 树形表格的使用以及表单嵌套树形表格的校验问题等汇总
目录 一、树形表格如何添加序号体现层级关系 二、树形表格展开收缩图标位置放置,设置指定列 三、表单嵌套树形表格的校验问题以及如何给校验rules传参 普通表格绑定如下:这种方法只能校验表格的第一层,树形需要递归设置子级节点prop。 树…...
解决“Unable to start embedded Tomcat“错误的完整指南
系列文章目录 文章目录 系列文章目录前言一、查看错误信息二、确认端口是否被占用三、检查依赖版本兼容性四、清理临时文件夹五、检查应用程序配置六、检查依赖冲突七、查看异常堆栈信息八、升级或降级Spring Boot版本总结前言 在使用Spring Boot开发应用程序时,有时可能会遇…...

JVS开源基础框架:平台基本信息介绍
JVS是面向软件开发团队可以快速实现应用的基础开发脚手架,主要定位于企业信息化通用底座,采用微服务分布式框架,提供丰富的基础功能,集成众多业务引擎,它灵活性强,界面化配置对开发者友好,底层容…...
C++ - max_element
在C中,要找到一个数组中的最大元素,可以使用 std::max_element 函数。以下是使用步骤: 包含 <algorithm> 头文件,这里定义了 std::max_element 函数。声明一个数组,并初始化它。使用 std::max_element 函数来查找…...

聚隆转债上市价格预测
聚隆转债 基本信息 转债名称:聚隆转债,评级:A,发行规模:2.185亿元。 正股名称:南京聚隆,今日收盘价:16.64元,转股价格:18.27元。 当前转股价值 转债面值 / 转…...

pytest自动生成测试类 demo
一、 pytest自动生成测试类 demo # -*- coding:utf-8 -*- # Author: 喵酱 # time: 2023 - 08 -15 # File: test4.py # desc: import pytest import unittest# 动态生成测试类def create_test_class(class_name:str, test_cases:list) -> type:"""生成测试类…...
服务器卡顿了该如何处理
服务器卡顿了该如何处理 当Windows系统的服务器出现卡顿问题时,以下是一些常见的故障排除步骤: 1.检查网络连接:确保服务器的网络连接正常。检查网络设备、交换机、防火墙等设备,确保它们正常运行。尝试通过其他计算机访问服务器…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...

9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...

密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...
TCP/IP 网络编程 | 服务端 客户端的封装
设计模式 文章目录 设计模式一、socket.h 接口(interface)二、socket.cpp 实现(implementation)三、server.cpp 使用封装(main 函数)四、client.cpp 使用封装(main 函数)五、退出方法…...