前言:事情是这样的,在一个并不愉快的周一部门会议上,产品经理突然给我需求,想要**页面,按勾选导出指定模块,当时不明所以,觉得so easy,就接下了需求,结果一调研,我**,对于html导出pdf,网上一片哀嚎,不是这个样式不行,就是清晰度不够,还没有什么明确的按模块导出的方案,心想完了,不能拿来即用了,没得时间摸鱼了,最后经过一番调研确定使用html2canvas + padMake + Canvas2Image导出pdf。
原理:先将html页面使用html2canvas转化成canvas,再使用Canvas2Image将canvas转换成一张高清图片,最后将图片用pdfMake导出成pdf。
按模块导出的思路:在每个模块前端增加一个勾选框,勾选中的模块是需要导出的模块,没有勾选中的模块,在点击按钮的时候改变模块绑定的v-show的值,使其在html页面不可见,实现按需导出的功能,具体实现看代码吧
示例图:
前置依赖插件:pdfmake,html2canvas,dayjs,Canvas2Image(由于安装不成功问题,直接复制文件吧)
npm i pdfmake,html2canvas,dayjs
/**
* covert canvas to image
* and save the image file
*/
var Canvas2Image = function () {
// check if support sth.
var $support = function () {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
return {
canvas: !!ctx,
imageData: !!ctx.getImageData,
dataURL: !!canvas.toDataURL,
btoa: !!window.btoa
};
}();
var downloadMime = 'image/octet-stream';
function scaleCanvas (canvas, width, height) {
var w = canvas.width,
h = canvas.height;
if (width == undefined) {
width = w;
}
if (height == undefined) {
height = h;
}
var retCanvas = document.createElement('canvas');
var retCtx = retCanvas.getContext('2d');
retCanvas.width = width;
retCanvas.height = height;
retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);
return retCanvas;
}
function getDataURL (canvas, type, width, height) {
canvas = scaleCanvas(canvas, width, height);
return canvas.toDataURL(type);
}
function saveFile (strData) {
document.location.href = strData;
}
function genImage(strData) {
var img = document.createElement('img');
img.src = strData;
return img;
}
function fixType (type) {
type = type.toLowerCase().replace(/jpg/i, 'jpeg');
var r = type.match(/png|jpeg|bmp|gif/)[0];
return 'image/' + r;
}
function encodeData (data) {
if (!window.btoa) { throw 'btoa undefined' }
var str = '';
if (typeof data == 'string') {
str = data;
} else {
for (var i = 0; i < data.length; i ++) {
str += String.fromCharCode(data[i]);
}
}
return btoa(str);
}
function getImageData (canvas) {
var w = canvas.width,
h = canvas.height;
return canvas.getContext('2d').getImageData(0, 0, w, h);
}
function makeURI (strData, type) {
return 'data:' + type + ';base64,' + strData;
}
/**
* create bitmap image
* 按照规则生成图片响应头和响应体
*/
var genBitmapImage = function (oData) {
//
// BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
// BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
//
var biWidth = oData.width;
var biHeight = oData.height;
var biSizeImage = biWidth * biHeight * 3;
var bfSize = biSizeImage + 54; // total header size = 54 bytes
//
// typedef struct tagBITMAPFILEHEADER {
// WORD bfType;
// DWORD bfSize;
// WORD bfReserved1;
// WORD bfReserved2;
// DWORD bfOffBits;
// } BITMAPFILEHEADER;
//
var BITMAPFILEHEADER = [
// WORD bfType -- The file type signature; must be "BM"
0x42, 0x4D,
// DWORD bfSize -- The size, in bytes, of the bitmap file
bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
// WORD bfReserved1 -- Reserved; must be zero
0, 0,
// WORD bfReserved2 -- Reserved; must be zero
0, 0,
// DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
54, 0, 0, 0
];
//
// typedef struct tagBITMAPINFOHEADER {
// DWORD biSize;
// LONG biWidth;
// LONG biHeight;
// WORD biPlanes;
// WORD biBitCount;
// DWORD biCompression;
// DWORD biSizeImage;
// LONG biXPelsPerMeter;
// LONG biYPelsPerMeter;
// DWORD biClrUsed;
// DWORD biClrImportant;
// } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
//
var BITMAPINFOHEADER = [
// DWORD biSize -- The number of bytes required by the structure
40, 0, 0, 0,
// LONG biWidth -- The width of the bitmap, in pixels
biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
// LONG biHeight -- The height of the bitmap, in pixels
biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
// WORD biPlanes -- The number of planes for the target device. This value must be set to 1
1, 0,
// WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
// has a maximum of 2^24 colors (16777216, Truecolor)
24, 0,
// DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
0, 0, 0, 0,
// DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
// LONG biXPelsPerMeter, unused
0,0,0,0,
// LONG biYPelsPerMeter, unused
0,0,0,0,
// DWORD biClrUsed, the number of color indexes of palette, unused
0,0,0,0,
// DWORD biClrImportant, unused
0,0,0,0
];
var iPadding = (4 - ((biWidth * 3) % 4)) % 4;
var aImgData = oData.data;
var strPixelData = '';
var biWidth4 = biWidth<<2;
var y = biHeight;
var fromCharCode = String.fromCharCode;
do {
var iOffsetY = biWidth4*(y-1);
var strPixelRow = '';
for (var x = 0; x < biWidth; x++) {
var iOffsetX = x<<2;
strPixelRow += fromCharCode(aImgData[iOffsetY+iOffsetX+2]) +
fromCharCode(aImgData[iOffsetY+iOffsetX+1]) +
fromCharCode(aImgData[iOffsetY+iOffsetX]);
}
for (var c = 0; c < iPadding; c++) {
strPixelRow += String.fromCharCode(0);
}
strPixelData += strPixelRow;
} while (--y);
var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);
return strEncoded;
};
/**
* saveAsImage
* @param canvasElement
* @param {String} image type
* @param {Number} [optional] png width
* @param {Number} [optional] png height
*/
var saveAsImage = function (canvas, width, height, type) {
if ($support.canvas && $support.dataURL) {
if (typeof canvas == "string") { canvas = document.getElementById(canvas); }
if (type == undefined) { type = 'png'; }
type = fixType(type);
if (/bmp/.test(type)) {
var data = getImageData(scaleCanvas(canvas, width, height));
var strData = genBitmapImage(data);
saveFile(makeURI(strData, downloadMime));
} else {
var strData = getDataURL(canvas, type, width, height);
saveFile(strData.replace(type, downloadMime));
}
}
};
var convertToImage = function (canvas, width, height, type) {
if ($support.canvas && $support.dataURL) {
if (typeof canvas == "string") { canvas = document.getElementById(canvas); }
if (type == undefined) { type = 'png'; }
type = fixType(type);
if (/bmp/.test(type)) {
var data = getImageData(scaleCanvas(canvas, width, height));
var strData = genBitmapImage(data);
return genImage(makeURI(strData, 'image/bmp'));
} else {
var strData = getDataURL(canvas, type, width, height);
return genImage(strData);
}
}
};
return {
saveAsImage: saveAsImage,
saveAsPNG: function (canvas, width, height) {
return saveAsImage(canvas, width, height, 'png');
},
saveAsJPEG: function (canvas, width, height) {
return saveAsImage(canvas, width, height, 'jpeg');
},
saveAsGIF: function (canvas, width, height) {
return saveAsImage(canvas, width, height, 'gif');
},
saveAsBMP: function (canvas, width, height) {
return saveAsImage(canvas, width, height, 'bmp');
},
convertToImage: convertToImage,
convertToPNG: function (canvas, width, height) {
return convertToImage(canvas, width, height, 'png');
},
convertToJPEG: function (canvas, width, height) {
return convertToImage(canvas, width, height, 'jpeg');
},
convertToGIF: function (canvas, width, height) {
return convertToImage(canvas, width, height, 'gif');
},
convertToBMP: function (canvas, width, height) {
return convertToImage(canvas, width, height, 'bmp');
}
};
}();
export default Canvas2Image;
1.上面是Canvas2Image的文件,直接复制到自己建立的本地文件里,然后引入需要调用的文件中
2.创建一个pdf.js文件,在其中创建一个downloadPDF方法
import html2canvas from "html2canvas";
import pdfMake from "pdfmake/build/pdfmake"
import Canvas2Image from '@/assets/utils/canvas2image'
import dayjs from 'dayjs'
export const downloadPDF = (page, name) => {
const time = new Date()
const timeName = dayjs(time).format('YYYY-MM-DD HH:mm:ss')
const fileName = name + timeName
const width = page.offsetWidth
const height = page.offsetHeight
const scale = 1
const canvas = document.createElement("canvas")
canvas.width = width * scale
canvas.height = height * scale
canvas.getContext('2d').scale(scale, scale)
const opts = {
backgroundColor:'#ffffff',
scale: scale, // 添加的scale 参数
canvas: canvas, // 自定义 canvas
// logging: true, // 日志开关,便于查看html2canvas的内部执行流程
width: width, // dom 原始宽度
height: height,
useCORS: true //[重要]开启跨域配置
}
html2canvas(page, opts).then((canvas) => {
const context = canvas.getContext('2d')
// [重要]关闭抗锯齿
context.mozImageSmoothingEnabled = false
context.webkitImageSmoothingEnabled = false
context.msImageSmoothingEnabled = false
context.imageSmoothingEnabled = false
const img = Canvas2Image.convertToJPEG(canvas, canvas.width, canvas.height)
const docDefinition = {
content: [
{
image: img.src,
width: 500,
height: canvas.height/4,
}
]
}
pdfMake.createPdf(docDefinition).download(fileName)
})
}
3.在vue文件中调用
// 点击导出按钮,
onExportPdf() {
const isChose = this.activeChexkbox.every(item => {
return !item.key
})
// 判断是否勾选需要导出的模块
if (isChose) return this.$message.warning('请先勾选需要导出的模块')
// 有导出模块,先调用onHiden方法,将那些没有勾选的模块隐藏起来
this.onHiden('batch').then(() => {
// 然后再调用导出这个方法,导出勾选的模块
this.onExport()
}).then(() => {
// 最后将那些隐藏的模块复现,因为代码执行速度极快,用户无法感知到隐藏了模块的
this.onShow('batch')
})
},
async onHiden(type) {
if (type === 'batch') {
await this.activeChexkbox.forEach(item => {
item.key ? item.value = true : item.value = false
})
}
this.isHandle = false
},
async onExport() {
await downloadPDF(this.$refs.pdf, '活动分析')
},
onShow(type) {
if (type === 'batch') {
this.activeChexkbox.forEach(item => {
item.key = false
item.value = true
})
}
this.isHandle = true
},
this.activeChexkbox
data() {
return {
activeChexkbox: [
{ key: false, value: true },
{ key: false, value: true },
{ key: false, value: true },
{ key: false, value: true },
{ key: false, value: true },
{ key: false, value: true },
{ key: false, value: true },
{ key: false, value: true },
{ key: false, value: true },
{ key: false, value: true }
],
}
}
具体在template中的实现,这里没有写全,解释一下,在一个模块最大的div下面绑定this.activechexkbox[4].value的,通过看下面的勾选框,是否勾选,来控制在导出的时候控制显示隐藏
注意:这里要用v-show,不要用v-if来控制显示隐藏,因为v-if需要重新渲染dom,会导致导出的时候没有数据
<div v-show="activeChexkbox[4].value" class="activeAnalysis-content">
<div class="activeAnalysis-content__header">
<div class="title">
<el-checkbox v-model="activeChexkbox[4].key" class="mr10"></el-checkbox>次数/人数
</div>
以上就是大体思路了