diff --git a/nezha-fronted/package-lock.json b/nezha-fronted/package-lock.json index e2cb255e5..edf57cd34 100644 --- a/nezha-fronted/package-lock.json +++ b/nezha-fronted/package-lock.json @@ -377,6 +377,12 @@ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", + "optional": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -815,8 +821,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "autoprefixer": { "version": "7.2.6", @@ -1771,6 +1776,11 @@ } } }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" + }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -2050,6 +2060,11 @@ "electron-to-chromium": "^1.3.30" } }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -2299,6 +2314,36 @@ "integrity": "sha512-7RR4Uh04t9K1uYRWzOJmzplgEOAXbfK72oVNokCdMzA67trrhPzy93ahKk1AWHiA0c58tD2P+NHqxrA8FZ+Trg==", "dev": true }, + "canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "dependencies": { + "core-js": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", + "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==", + "optional": true + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "optional": true + } + } + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -3175,6 +3220,14 @@ } } }, + "css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "requires": { + "utrie": "^1.0.2" + } + }, "css-loader": { "version": "0.28.11", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", @@ -4621,6 +4674,12 @@ "domelementtype": "1" } }, + "dompurify": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", + "integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==", + "optional": true + }, "domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", @@ -5934,6 +5993,11 @@ "websocket-driver": ">=0.5.1" } }, + "fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -6901,6 +6965,15 @@ } } }, + "html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "requires": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + } + }, "htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -7668,6 +7741,42 @@ "graceful-fs": "^4.1.6" } }, + "jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "requires": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "fflate": "^0.4.8", + "html2canvas": "^1.0.0-rc.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", + "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "core-js": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", + "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==", + "optional": true + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -11951,6 +12060,15 @@ "fast-diff": "1.1.2" } }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "optional": true, + "requires": { + "performance-now": "^2.1.0" + } + }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -12404,6 +12522,12 @@ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", "dev": true }, + "rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=", + "optional": true + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -13424,6 +13548,12 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "stackblur-canvas": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", + "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", + "optional": true + }, "stackframe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.0.tgz", @@ -13695,6 +13825,12 @@ "has-flag": "^3.0.0" } }, + "svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true + }, "svgo": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", @@ -13837,6 +13973,14 @@ } } }, + "text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "requires": { + "utrie": "^1.0.2" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -14459,6 +14603,14 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", "dev": true }, + "utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "requires": { + "base64-arraybuffer": "^1.0.2" + } + }, "uuid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", diff --git a/nezha-fronted/package.json b/nezha-fronted/package.json index 830601601..4fbccc36a 100644 --- a/nezha-fronted/package.json +++ b/nezha-fronted/package.json @@ -29,6 +29,8 @@ "echarts": "^5.2.2", "element-ui": "^2.15.3", "file-saver": "^2.0.2", + "html2canvas": "^1.4.1", + "jspdf": "^2.5.1", "leaflet": "^1.7.1", "moment-timezone": "^0.5.33", "mqtt": "4.2.6", diff --git a/nezha-fronted/src/components/common/js/htmlToPdf.js b/nezha-fronted/src/components/common/js/htmlToPdf.js new file mode 100644 index 000000000..639a6c56b --- /dev/null +++ b/nezha-fronted/src/components/common/js/htmlToPdf.js @@ -0,0 +1,68 @@ +import html2Canvas from 'html2canvas' +import JsPDF from 'jspdf' +import { Loading } from 'element-ui' // 此处根据你实际使用的框架的loading进行引入 +let loading +export default { + install (Vue, options) { + Vue.prototype.getPdf = function (dom, x, y) { // 传入需要导出的 dom + const title = this.htmlTitle || 'pdf' + // html2Canvas(document.querySelector('#pdfDom'), { //这是在界面上设置一个id + // 只下载id为pdfDom的内容 + html2Canvas(dom, { // body是下载整个界面 + useCORS: true, // 是否尝试使用CORS从服务器加载图像 + allowTaint: true, + dpi: 300, // 解决生产图片模糊 + // width: 490, //canvas宽度 + // height: 240, //canvas高度 + x: x || 0, // x坐标 需要计算 + y: y || 0, // y坐标 需要计算 + async: true, // 是否异步解析和呈现元素 + foreignObjectRendering: true // 是否在浏览器支持的情况下使用ForeignObject渲染 + }).then(function (canvas) { + loading.close() + document.body.style.height = '100%' + document.getElementsByTagName('html')[0].style.overflow = 'hidden' + const contentWidth = canvas.width + const contentHeight = canvas.height + const pageHeight = contentWidth / 592.28 * 841.89 // 一页pdf显示html页面生成的canvas高度; + let leftHeight = contentHeight // 未生成pdf的html页面高度 + let position = 0 // pdf页面偏移 + // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高 + const imgWidth = 595.28 + const imgHeight = 592.28 / contentWidth * contentHeight + const pageData = canvas.toDataURL('image/jpeg', 1.0) + const 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', 20, position, imgWidth, imgHeight) + leftHeight -= pageHeight + position -= 841.89 + if (leftHeight > 0) { + PDF.addPage() + } + } + } + PDF.save(title + '.pdf') + }) + } + Vue.prototype.showScreenLoading = function (flag) { + if (flag) { + loading = Loading.service({ + lock: true, + text: '正在下载pdf...', + background: 'rgba(0,500,200,.5)' + }) + } else { + if (loading) { + loading.close() + document.body.style.height = '100%' + document.getElementsByTagName('html')[0].style.overflow = 'hidden' + } + } + } + } +} diff --git a/nezha-fronted/src/components/common/mixin/htmlToPdfMixin.js b/nezha-fronted/src/components/common/mixin/htmlToPdfMixin.js new file mode 100644 index 000000000..03048022a --- /dev/null +++ b/nezha-fronted/src/components/common/mixin/htmlToPdfMixin.js @@ -0,0 +1,3 @@ +export default { + +} diff --git a/nezha-fronted/src/components/page/dashboard/panel.vue b/nezha-fronted/src/components/page/dashboard/panel.vue index 4a986b190..fd67db3f8 100644 --- a/nezha-fronted/src/components/page/dashboard/panel.vue +++ b/nezha-fronted/src/components/page/dashboard/panel.vue @@ -1,5 +1,5 @@ @@ -81,7 +84,7 @@
-
+
--> { + flag = true + this.$refs.chartList.copyDataList.forEach(chart => { + if (chart.type !== 'group') { + flag = flag && chart.loaded + } else if (chart.collapse) { + chart.children.forEach(groupChart => { + flag = flag && groupChart.loaded + }) + } + }) + if (flag) { + clearInterval(timer) + timer = null + setTimeout(() => { + document.body.style.height = 'auto' + document.getElementsByTagName('html')[0].style.overflow = 'visible' + const position = dom.getBoundingClientRect() + this.getPdf(dom, -1 * position.left, -1 * position.top) + }, 500) + } + }, 200) + } else { + this.showScreenLoading(false) + } } }, created () { diff --git a/nezha-fronted/src/main.js b/nezha-fronted/src/main.js index 17c4111c8..3d5f35677 100644 --- a/nezha-fronted/src/main.js +++ b/nezha-fronted/src/main.js @@ -35,6 +35,8 @@ import bus from '@/libs/bus' import myDatePicker from '@/components/common/myDatePicker' import vSelectPage from '@/components/common/v-selectpagenew' import nzDataList from '@/components/common/table/nzDataList' +import htmlToPdf from '@/components/common/js/htmlToPdf' +Vue.use(htmlToPdf) Vue.use(vSelectPage, { dataLoad: function (vue, url, params) { if (params.pageNumber) {