大屏项目有一个需求,需要对展示的内容进行文件导出,但是目前后台没有相关的逻辑,所以只能前端硬上,在参考了其他许多的逻辑之后,目前我自己这边做了一套比较笨的组件,通过拼接标签这种方法来实现对你想需要的地方进行文件导出,并且呈现出比较让人接受的样式,下面以一个实例来介绍。
需求模拟:
目前有一个大屏项目
客户在点击导出分析报告的时候能够得到下面这种样式的pdf文件(只能是pdf,word,jpg什么的都不行),并且需要有预览界面
demo生成的pdf文件展示:
讲一下技术手段吧,主要用到的还是这俩大熟人
html2canvas
jspdf
首先是根据对界面的提取,获得canvas生成的图片,然后附着在一个pdf上,最后保存下载。这一部分的代码可以说是直接照搬的别人的:
/*
* @Author: 你的名字
* @Date: 2023-03-16 11:05:59
* @FilePath: /demo-gather/src/lib/pdf.js
* @Description:
*/
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
export const downloadPDF = async (page,title) => {
// let modules = [page];
let modules = page
if (!modules.length) return;
const a4Width = 595.28
const a4Height = 841.89
const pdf = new jsPDF('p', 'pt')
// 生成所有pdf页
async function setPdfPage() {
for (let i = 0; i < modules.length; i++) {
const item = modules[i]
const canvas = await html2canvas(item)
const contentWidth = canvas.width
const contentHeight = canvas.height
const pageData = canvas.toDataURL('image/jpeg', 1) // 第二个参数为图片质量,1为最高质量
let imgHeight = (a4Width / contentWidth) * contentHeight // 根据a4纸比例,计算出图片的高度
const marginY = (a4Height - imgHeight) / 2 // 计算出图片的上下边距
// 第三个参数图片x轴位置,第四个参数图片y轴位置,第五个参数图片宽度,第六个参数图片高度
pdf.addImage(pageData, 'JPEG', 0, marginY > 0 ? 0 : 0, a4Width, imgHeight > a4Height ? imgHeight : imgHeight)
// 最后一个模块不需要再新增空白页
if (i < modules.length - 1) {
pdf.addPage()
}
}
}
await setPdfPage()
pdf.save(title+".pdf") // 导出pdf
// 还原元素
// html2canvas(page).then(function(canvas) {
// canvas2PDF(canvas,title);
// });
};
export const canvas2PDF = (canvas,title) => {
let contentWidth = canvas.width;
let contentHeight = canvas.height;
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
let imgWidth = 595.28;
let imgHeight = 592.28/contentWidth * contentHeight;
if(imgHeight>841){
imgHeight = 841;
imgWidth = 841/contentHeight * contentWidth;
}
// 第一个参数: l:横向 p:纵向
// 第二个参数:测量单位("pt","mm", "cm", "m", "in" or "px")
let pdf = new jsPDF("p", "pt");
pdf.addImage(
canvas.toDataURL("image/jpeg", 0.9),
"JPEG",
0,
0,
imgWidth,
imgHeight
);
pdf.save(title+".pdf");
};
这里主要介绍一下这个预览界面我的实现方法,目前适配了这些情况:
一般采用ref拿到元素下面的html内容,克隆在弹出框的内容中,组件接收到一个fileList数组,每一项代表克隆的显示内容, 每一项的展示示例:
title:当前内容的标题
src:组件渲染的主要内容
type:当前需要渲染的内容的类型(包括:不定义的类型(‘’),echarts,table)
不定义类型的话采用vhtml绑定src直接展示出来,
echarts类型需要联动我之前做的echarts展示组件来使用,拿到整个option给src,导出预览的组件里面会有一个chart-com组件接收options配置项;
table类型需要将表头的配置项一个表格数据一并传给预览组件,具体实例参考我的demo项目代码。
initFileSrc(domList) {
let _this = this;
let src = "";
// this.dynamicDom = "";
for (let item of domList) {
if (item.isComponent) {
src = _this.$refs[item.name].$el.outerHTML;
} else {
src = _this.$refs[item.name].outerHTML;
}
_this.fileList.push({ title: item.title ? item.title : "", src: src });
}
},
组件应用当中遇到的问题:
1.分页如何处理
2.如果有子标签怎么处理
demo项目代码地址
https://gitee.com/mrzaco/demo-gather/blob/main/src/views/ChartTest/ChartTest.vue