四川網(wǎng)站建設套餐北京網(wǎng)站seo設計
前言
? ? ? ? 在工作中遇到了一個需求,就是把前端頁面生成PDF并保存在本地,因為前端網(wǎng)站可能會展示各種表格,圖表信息內容并帶有比較鮮艷的色彩樣式,如果讓后端生產(chǎn)的PDF的話樣式可能和前端頁面展示的有所差異,所以這個任務就落到了前端的身上。
技術涉及
- jsPDF
-
html2canvas?
-
ali-oss
代碼實現(xiàn)
1、獲取DOM結點
? ? ? ? 首先需要獲取需要打印的DOM結點,這個時候獲取的DOM結點是帶有樣式的,就相當于頁面中的內容
const eleHtml = document.querySelector('.zxksBody');
2、獲取打印容器的屬性
? ? ? ? 首先做個兼容判斷,判斷是否取到了DOM結點信息,如果取到了DOM結點就獲取DOM結點的內容,進行高度和寬度的賦值
if (eleHtml) {let eleW = eleHtml.offsetWidth; // 獲得該容器的寬let eleH = eleHtml.offsetHeight; // 獲得該容器的高}
3、生成PDF
????????這一步就是把獲取到的DOM結點,通過jsPDF和html2canvas 生成為PDF
html2canvas(eleHtml, {dpi: 300,width: eleW,height: eleH,scale: 2, // 提高渲染質量useCORS: true //允許canvas畫布內 可以跨域請求外部鏈接圖片, 允許跨域請求。}).then(async (canvas) => {const pdf = new jsPDF('', 'pt', 'a4');const imgData = canvas.toDataURL('image/png', 1.0);//a4紙的尺寸[595.28,841.89],html頁面生成的canvas在pdf中圖片的寬高const imgWidth = 555.28;//一頁pdf顯示html頁面生成的canvas高度;const imgHeight = 555.28 / canvas.width * canvas.height;// 計算分頁const pageHeight = 841.89;//未生成pdf的html頁面高度let leftHeight = imgHeight;//頁面偏移let position = 0;if (leftHeight < pageHeight) {//在pdf.addImage(pageData, 'JPEG', 左,上,寬度,高度)設置在pdf中顯示pdf.addImage(imgData, 'PNG', 20, 20, imgWidth, imgHeight);} else { // 分頁while (leftHeight > 0) {pdf.addImage(imgData, 'PNG', 20, position, imgWidth, imgHeight);leftHeight -= pageHeight;position -= 841.89;if (leftHeight > 0) {pdf.addPage();}}});
4、保存本地或者上傳OSS
?保存本地
????????保存本地的話比較簡單,直接調用PDF庫自帶的方法就可以保存到本地
pdf.save(`${state.xsMc}-${state.xsBh}.pdf`)
上傳OSS
? ? ? ? 上傳的OSS的話就比較復雜一點,首先就是需要配置OSS的內容,然后把PDF轉換為Blob對象,最后就是調用OSS的接口實現(xiàn)上傳。
// 配置OSS
const client = new OSS({region: "******",bucket: 'bucketName',endpoint: 'endpoint',stsToken: 'securityToken',accessKeyId: 'accessKeyId',accessKeySecret: 'accessKeySecret',
});// 將 PDF 文件轉換為 Blob 對象
const pdfBlob = pdf.output('blob');// 調用OSS上方實現(xiàn)上傳
const fileRes = await client.put(`${state.xsMc}-${state.xsBh}.pdf`, pdfBlob);
console.log(fileRes, '接收返回的OSS信息');
5、注意事項
- ?使用html2canvas和jsPDF可能會遇見文本錯位或者樣式錯誤問題,這個時候需要進行調整,可以通過html2canvas中的onclone回調方法進行調整
html2canvas(eleHtml, {onclone: (documentClone) => {// 在克隆的文檔上進行修改const partRight2 = documentClone.querySelector('.partRight2');const titleBars = documentClone.querySelectorAll('.titleBar');if (partRight2) {partRight2.style.display = 'none'; // 隱藏內容}if (titleBars) {//修改樣式屬性titleBars.forEach(titleBar => {titleBar.style.marginTop = '-8px';titleBar.style.marginBottom = '20px';});}},dpi: 300,width: eleW,height: eleH,scale: 2, // 提高渲染質量useCORS: true //允許canvas畫布內 可以跨域請求外部鏈接圖片, 允許跨域請求。
}).then(async (canvas) => {.......});
- 對于在獲取DOM時,帶有滾動條的內容無法正確獲取他的高度和寬度,內容可能會被遮蓋無法正確打印,這個時候需要在打印前更改頁面中的DOM樣式才能正確打印
// 獲取全部內容
const eleHtml = document.querySelector('.zxksBody');// 在生成canvas之前就把樣式進行更改,獲取盒子的正常高度或者寬度,防止樣式被遮蓋,
const changeHeight = document.querySelector('.zxksContent');if (changeHeight) {changeHeight.style.height = '100%'; // 更改高度
}html2canvas(eleHtml, {dpi: 300,width: eleW,height: eleH,scale: 2, // 提高渲染質量useCORS: true //允許canvas畫布內 }).then(async (canvas) => {.....// 在打印完成后,再把樣式改回去if (changeHeight) {changeHeight.style.height = 'calc(100vh - 182px)';}}
- 對于帶有滾動條的div盒子,在點擊打印時,最好把頁面內容進行更改,防止無法正確獲取盒子高度,導致文字被隱藏,在打印完成后,在更改回去
// 對于vue
可以使用v-if進行更換,把展示的內容保存在div中,去掉溢出滾動功能
// 對于react
可以使用三元運算符進行判斷,展示的內容
6、完整代碼
const printPdf = async () => {const client = new OSS({const client = new OSS({region: "******",bucket: 'bucketName',endpoint: 'endpoint',stsToken: 'securityToken',accessKeyId: 'accessKeyId',accessKeySecret: 'accessKeySecret',}); try {// 獲取全部內容const eleHtml = document.querySelector('.zxksBody');// 帶有移除隱藏的功能const changeHeight = document.querySelector('.zxksContent');if (changeHeight) {changeHeight.style.height = '100%'; // 更改高度}if (eleHtml) {let eleW = eleHtml.offsetWidth; // 獲得該容器的寬let eleH = eleHtml.offsetHeight; // 獲得該容器的高// 確保獲取加載完全的DOMsetTimeout(() => { html2canvas(eleHtml, {onclone: (documentClone) => {// 在克隆的文檔上進行修改const partRight2 = documentClone.querySelector('.partRight2');const titleBars = documentClone.querySelectorAll('.titleBar');if (partRight2) {partRight2.style.display = 'none'; // 隱藏內容}if (titleBars) {titleBars.forEach(titleBar => {titleBar.style.marginTop = '-8px';titleBar.style.marginBottom = '20px';});}},dpi: 300,width: eleW,height: eleH,scale: 2, // 提高渲染質量useCORS: true //允許canvas畫布內 可以跨域請求外部鏈接圖片, 允許跨域請求。}).then(async (canvas) => {const pdf = new jsPDF('', 'pt', 'a4');const imgData = canvas.toDataURL('image/png', 1.0);const imgWidth = 555.28;const imgHeight = 555.28 / canvas.width * canvas.height;// 計算分頁const pageHeight = 841.89;let leftHeight = imgHeight;let position = 0;if (leftHeight < pageHeight) {pdf.addImage(imgData, 'PNG', 20, 20, imgWidth, imgHeight);} else {while (leftHeight > 0) {pdf.addImage(imgData, 'PNG', 20, position, imgWidth, imgHeight);leftHeight -= pageHeight;position -= 841.89;if (leftHeight > 0) {pdf.addPage();}}}// 將 PDF 文件轉換為 Blob 對象const pdfBlob = pdf.output('blob');// 使用 OSS 客戶端上傳 Blob 對象try {const fileRes = await client.put(`${state.xsMc}-${statexsBh}.pdf`, pdfBlob);console.log('client res', fileRes);} catch (err) {console.error('PDF上傳失敗,請重新提交!', err);}if (changeHeight) {changeHeight.style.height = 'calc(100vh - 182px)';}});}, 1000);}} catch (error) {console.log("Error!", error);if (changeHeight) {changeHeight.style.height = 'calc(100vh - 182px)';}}};