vue批量导出pdf(含分页及解决最后一页空白问题)
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
1、问题概述:由于vue模板的限制,导出pdf只能打印出来一页。因此我单独写了一个h5页面,将该页面放在服务器上,接收需要导出内容的id即可完成分页导出。附加解决导出的最后一页是空白的问题。
2、效果预览:
3、完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>导出手卡</title>
<style>
#card-inner {
background-color: #fff;
width: 1400px;
height: 100%;
}
#imgWrap {
width: 1400px;
height: 1990px;
}
.card-list {
page-break-before: always;
page-break-after: always;
page-break-inside: avoid;
}
.cardBox {
display: flex;
}
.cardItem {
min-width: 150px;
width: 12%;
display: flex;
flex-direction: column;
border: 1px solid #b8b8b8;
text-align: center;
border-right: 0;
}
.cardItem1 {
min-width: 100px;
width: 12%;
display: flex;
flex-direction: column;
border: 1px solid #b8b8b8;
text-align: center;
border-right: 0;
}
.cardItem2 {
min-width: 30px;
width: 4%;
display: flex;
flex-direction: column;
border: 1px solid #b8b8b8;
text-align: center;
border-right: 1px solid #b8b8b8;
}
.cardItem:last-child {
border-right: 1px solid #b8b8b8;
}
.item_con {
display: flex;
justify-content: center;
padding: 10px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
margin-bottom: 20px;
text-align: left;
line-height: 30px;
}
.item_text {
padding: 10px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
margin-bottom: 20px;
text-align: left;
line-height: 30px;
}
.item_con:nth-child(1) {
border-bottom: 1px solid #b8b8b8;
}
.cardBox_second {
display: flex;
}
.secondItem {
min-width: 150px;
width: 12%;
display: flex;
justify-content: center;
flex-direction: column;
border: 1px solid #b8b8b8;
text-align: left;
border-right: 0;
border-top: 0;
line-height: 30px;
padding: 10px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
}
.secondItem:last-child {
width: 16%;
border-right: 1px solid #b8b8b8;
}
.cardBox_last {
display: flex;
border: 1px solid #b8b8b8;
border-top: 0;
border-bottom: 0;
}
.last_left {
width: 37.5%;
border-right: 1px solid #b8b8b8;
}
.leftItem,
.leftItem_list {
display: flex;
border-bottom: 1px solid #b8b8b8;
}
.leftItem_list .itemCon {
min-height: 180px;
}
.itemCon {
width: 50%;
display: flex;
justify-content: center;
text-align: left;
line-height: 30px;
padding: 10px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
}
.itemCon:nth-child(1) {
border-right: 1px solid #b8b8b8;
}
.last_right {
width: 62.5%;
border-bottom: 1px solid #b8b8b8;
text-align: left;
line-height: 30px;
padding: 10px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
}
.spinner {
width: 60px;
height: 60px;
background-image: linear-gradient(0deg, #8a76f6, #70cafe);
margin: 100px auto;
-webkit-animation: rotateplane 1.2s infinite ease-in-out;
animation: rotateplane 1.2s infinite ease-in-out;
}
@-webkit-keyframes rotateplane {
0% {
-webkit-transform: perspective(120px);
}
50% {
-webkit-transform: perspective(120px) rotateY(180deg);
}
100% {
-webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg);
}
}
@keyframes rotateplane {
0% {
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
-webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg);
}
50% {
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
-webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
}
100% {
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
-webkit-transform: perspective(120px) rotateX(-180deg)
rotateY(-179.9deg);
}
}
</style>
</head>
<body style="text-align: center; width: 100%; height: 100%">
<!-- 导出的所有手卡 -->
<div style="padding-bottom: 30px" id="card-inner">
<div
id="imgWrap"
ref="imgCon"
v-for="(cardData,index) in cardDataList"
:key="index"
class="card-list"
>
<div class="cardBox">
<div class="cardItem">
<div class="item_con" style="min-width: 100px; font-weight: 700">
生产日期/保质期
</div>
<div class="item_text">
<p>生产日期: <span v-html="cardData.productionDate"></span></p>
<br />
<p>保质期: <span v-html="cardData.shelfLife"></span></p>
</div>
</div>
<div class="cardItem">
<div class="item_con" style="font-weight: 700">利益点</div>
<div class="item_con"><span v-html="cardData.benefit"></span></div>
</div>
<div class="cardItem">
<div class="item_con" style="font-weight: 700">道具准备</div>
<div class="item_con"><span v-html="cardData.prop"></span></div>
</div>
<div class="cardItem">
<div class="item_con" style="font-weight: 700">直播间福利</div>
<div class="item_con">
<span v-html="cardData.broadcastBenefits"></span>
</div>
</div>
<div class="cardItem">
<div class="item_con" style="font-weight: 700">随单赠品</div>
<div class="item_con">
<span v-html="cardData.orderGift"></span>
</div>
</div>
<div class="cardItem">
<div class="item_con" style="font-weight: 700">直播价</div>
<div class="item_con">
<span v-html="cardData.priceLive"></span>
</div>
</div>
<div class="cardItem">
<div class="item_con" style="font-weight: 700">商品规格(SKU)</div>
<div class="item_con">
<span v-html="cardData.sku"></span>
</div>
</div>
<div class="cardItem1">
<div class="item_con" style="font-weight: 700">产品图</div>
<div class="item_con">
<img :src="cardData.image" style="width: 100%" alt="" />
</div>
</div>
<div class="cardItem2">
<div class="item_con" style="font-weight: 700">序号</div>
<div class="item_con">{{index+1}}</div>
</div>
</div>
<div class="cardBox_second">
<div class="secondItem" style="font-weight: 700">发货方式</div>
<div class="secondItem">
<span v-html="cardData.sampleExpress"></span>
</div>
<div class="secondItem" style="font-weight: 700">月销</div>
<div class="secondItem">
<span v-html="cardData.monthlySales"></span>
</div>
<div class="secondItem" style="font-weight: 700">市价</div>
<div class="secondItem">
<span v-html="cardData.marketPrice"></span>
</div>
<div class="secondItem" style="font-weight: 700">对接招商</div>
<div class="secondItem">
<span v-html="cardData.dockingUserName"></span>
</div>
</div>
<div class="cardBox_last">
<div class="last_left" style="">
<div class="leftItem">
<div class="itemCon">
<span v-html="cardData.companyName"></span>
</div>
<div class="itemCon">
<span v-html="cardData.brandName"></span>
</div>
</div>
<div class="leftItem">
<div class="itemCon" style="font-weight: 700">不包邮地区</div>
<div class="itemCon">
<span v-html="cardData.nonShippingArea"></span>
</div>
</div>
<div class="leftItem">
<div class="itemCon" style="font-weight: 700">是否配合控库存</div>
<div class="itemCon">
<span v-html="cardData.isStockNum"></span>
</div>
</div>
<div class="leftItem">
<div class="itemCon" style="font-weight: 700">现货库存</div>
<div class="itemCon" style="font-weight: 700">商品二维码</div>
</div>
<div class="leftItem_list">
<div class="itemCon">
<span v-html="cardData.spotStock"></span>
</div>
<div class="itemCon">
<img
style="width: 90px; height: 80px"
:src="cardData.linkUrlQrcode"
alt=""
v-if="cardData.linkUrlQrcode"
/>
<img
style="width: 90px; height: 80px"
:src="cardData.giftUrlQrcode"
alt=""
v-if="cardData.giftUrlQrcode"
/>
<!-- <card-upload v-if="cardOpen" v-model="cardData.urlQrcodeSupplement" /> -->
<div
v-if="cardData.urlQrcodeSupplement"
style="margin-top: 10px"
>
<div
v-for="(img, index) in cardData.urlQrcodeSupplement"
:key="index"
>
<img :src="img" alt="" style="width: 60px; height: 60px" />
</div>
</div>
</div>
</div>
</div>
<div class="last_right" v-html="cardData.sallingDes"></div>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<!-- <script type="text/javascript" src ="js/canvas2image.js"></script> -->
<script src="https://cdn.bootcss.com/html2canvas/0.5.0-beta4/html2canvas.js"></script>
<script src="https://cdn.bootcss.com/jspdf/1.3.4/jspdf.debug.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.1/qs.js"></script>
<script>
//获取浏览器页面可见高度和宽度
var _PageHeight = document.documentElement.clientHeight,
_PageWidth = document.documentElement.clientWidth;
//计算loading框距离顶部和左部的距离(loading框的宽度为215px,高度为61px)
var _LoadingTop = _PageHeight > 90 ? (_PageHeight - 90) / 2 : 0,
_LoadingLeft = _PageWidth > 90 ? (_PageWidth - 90) / 2 : 0;
//在页面未加载完毕之前显示的loading Html自定义内容
var _LoadingHtml =
'<div id="loadingDiv" style="position:absolute;left:0;width:100%;height:' +
_PageHeight +
'px;top:600;background:#FFFFFF;opacity:1.0;filter:alpha(opacity=80);z-index:10000;"><img style="margin-top:200px;" src="https://jm-talk.oss-cn-hangzhou.aliyuncs.com/static/2023-1/167324838001991.gif"/><div style="margin-top:10px;">下载中 ...</div></div></div>';
//呈现loading效果
document.write(_LoadingHtml);
//监听加载状态改变
document.onreadystatechange = completeLoading;
//加载状态为complete时移除loading效果
function completeLoading() {
if (document.readyState == "complete") {
$("#loadingDiv").fadeOut(15000);
}
}
new Vue({
el: "#card-inner",
data: {
cardDataList: [],
},
methods: {
testGet() {
var url = window.location.href; //获取当前链接
let p = url.split("?")[1];
let params = new URLSearchParams(p);
let ids = params.get("ids");
let URL =
"https:www.daidu.com?ids=" + ids;
// var idCode = url.split("="); //截取id的值
// var ids = idCode[1].split(',') //截取id的值。即第一项
axios({
method: "GET",
headers: {
Accept: "*/*",
"content-type": "application/x-www-form-urlencoded",
"Access-Control-Allow-Origin": "*",
},
url: URL,
}).then((res) => {
if (res.status == 200) {
this.cardDataList = res.data.card;
setTimeout(()=>{
this.jsPrintPdf();
},1000)
}
});
},
//页面转canvas再转PDF
jsPrintPdf() {
var filename = "导出手卡.pdf";
var element = $("#card-inner"); // 这个dom元素是要导出pdf的div容器
var w = element.width(); // 获得该容器的宽
var h = element.height(); // 获得该容器的高
var offsetTop = element.offset().top; // 获得该容器到文档顶部的距离
var offsetLeft = element.offset().left; // 获得该容器到文档最左的距离
var canvas = document.createElement("canvas");
var abs = 0;
var win_i = $(window).width(); // 获得当前可视窗口的宽度(不包含滚动条)
var win_o = window.innerWidth; // 获得当前窗口的宽度(包含滚动条)
if (win_o > win_i) {
abs = (win_o - win_i) / 2; // 获得滚动条长度的一半
}
canvas.width = w * 2; // 将画布宽&&高放大两倍
canvas.height = h * 2;
var context = canvas.getContext("2d");
context.scale(2, 2);
context.translate(-offsetLeft - abs, -offsetTop);
// 这里默认横向没有滚动条的情况,因为offset.left(),有无滚动条的时候存在差值,因此
// translate的时候,要把这个差值去掉
html2canvas($("#card-inner"), {
useCORS: true,
dpi: window.devicePixelRatio * 2,
scale: 2,
width: w,
height: h,
pagesplit: true,
background: "#ffffff",
onrendered: function (canvas) {
var contentWidth = canvas.width;
var contentHeight = canvas.height;
//一页pdf显示html页面生成的canvas高度;
var pageHeight = (contentWidth / 592.28) * 841.89;
//未生成pdf的html页面高度
var leftHeight = contentHeight;
//页面偏移
var position = 0;
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28;
var imgHeight = (592.28 / contentWidth) * contentHeight;
var pageData = canvas.toDataURL("image/jpeg", 1.0);
var pdf = new jsPDF("", "pt", "a4");
//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
//当内容未超过pdf一页显示的范围,无需分页
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(filename);
var pageCount = pdf.internal.getNumberOfPages()//获取当前pdf有多少页
pdf.deletePage(pageCount)//删除最后一页
},
});
},
},
created() {
this.testGet();
},
});
</script>
</html>
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891
Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
更多推荐
已为社区贡献2条内容
所有评论(0)