| | |
| | | margin-top: 2px; |
| | | } |
| | | .report-summary { |
| | | margin-top: 12px; |
| | | padding: 14px; |
| | | border-radius: 12px; |
| | | border: 1px solid #e4ebf2; |
| | |
| | | font-size: 15px; |
| | | color: #223046; |
| | | } |
| | | .report-summary pre { |
| | | white-space: pre-wrap; |
| | | word-break: break-word; |
| | | font-size: 13px; |
| | | line-height: 1.6; |
| | | .markdown-body { |
| | | font-size: 15px; |
| | | line-height: 1.8; |
| | | color: #333; |
| | | margin: 0; |
| | | max-height: 500px; |
| | | max-height: 600px; |
| | | overflow-y: auto; |
| | | } |
| | | .markdown-body h1 { font-size: 22px; margin: 20px 0 10px; border-bottom: 1px solid #eee; padding-bottom: 6px; } |
| | | .markdown-body h2 { font-size: 19px; margin: 18px 0 8px; color: #223046; } |
| | | .markdown-body h3 { font-size: 17px; margin: 14px 0 6px; } |
| | | .markdown-body p { margin: 8px 0; } |
| | | .markdown-body ul, .markdown-body ol { padding-left: 24px; margin: 8px 0; } |
| | | .markdown-body li { margin: 4px 0; } |
| | | .markdown-body code { background: #f0f2f5; padding: 2px 6px; border-radius: 3px; font-size: 14px; } |
| | | .markdown-body pre { background: #282c34; color: #abb2bf; padding: 14px; border-radius: 6px; overflow-x: auto; margin: 10px 0; } |
| | | .markdown-body pre code { background: none; color: inherit; padding: 0; font-size: 14px; } |
| | | .markdown-body table { border-collapse: collapse; margin: 10px 0; width: 100%; } |
| | | .markdown-body th, .markdown-body td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; font-size: 14px; } |
| | | .markdown-body th { background: #f5f7fa; font-weight: 600; } |
| | | .markdown-body blockquote { border-left: 3px solid #409eff; padding-left: 12px; color: #666; margin: 10px 0; } |
| | | .markdown-body strong { color: #223046; } |
| | | </style> |
| | | </head> |
| | | <body> |
| | |
| | | {{ periodLabel(selectedReport.periodType) }} · {{ formatTime(selectedReport.createTime) }} |
| | | </div> |
| | | </div> |
| | | <el-button size="mini" @click="selectedReport=null">关闭</el-button> |
| | | <div> |
| | | <el-button size="mini" type="primary" icon="el-icon-download" :loading="pdfLoading" @click="downloadPdf">下载 PDF</el-button> |
| | | <el-button size="mini" @click="selectedReport=null">关闭</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="panel-body"> |
| | | <div class="report-summary"> |
| | | <div class="report-summary" id="reportContent"> |
| | | <h3>分析报告</h3> |
| | | <pre>{{ selectedReport.summary || '暂无报告内容' }}</pre> |
| | | <div class="markdown-body" v-html="renderedSummary"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <script type="text/javascript" src="../../static/vue/js/vue.min.js"></script> |
| | | <script type="text/javascript" src="../../static/vue/element/element.js"></script> |
| | | <script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script> |
| | | <script type="text/javascript" src="../../static/js/marked.min.js"></script> |
| | | <script type="text/javascript" src="../../static/lib/pdf/html2canvas.min.js"></script> |
| | | <script type="text/javascript" src="../../static/lib/pdf/jspdf.umd.min.js"></script> |
| | | <script> |
| | | new Vue({ |
| | | el: '#app', |
| | |
| | | enabled: false, |
| | | config: {}, |
| | | enabledLoading: false, |
| | | pdfLoading: false, |
| | | triggerLoading: false, |
| | | triggerPeriod: '', |
| | | reportsLoading: false, |
| | |
| | | if (cron === '0 0 2 * * ?') return '每天凌晨 2:00'; |
| | | if (cron === '0 30 0 * * ?') return '每天 0:30'; |
| | | return cron; |
| | | }, |
| | | renderedSummary: function() { |
| | | var md = this.selectedReport && this.selectedReport.summary; |
| | | if (!md) return '<p style="color:#999;">暂无报告内容</p>'; |
| | | try { |
| | | return marked.parse(md); |
| | | } catch (e) { |
| | | return '<pre>' + md.replace(/</g, '<') + '</pre>'; |
| | | } |
| | | } |
| | | }, |
| | | mounted: function() { |
| | |
| | | var pad = function(n) { return n < 10 ? '0' + n : n; }; |
| | | return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) |
| | | + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()); |
| | | }, |
| | | downloadPdf: function() { |
| | | var self = this; |
| | | var el = document.getElementById('reportContent'); |
| | | if (!el) return; |
| | | self.pdfLoading = true; |
| | | |
| | | // 临时移除滚动限制,让 html2canvas 捕获完整内容 |
| | | var origMaxHeight = el.style.maxHeight; |
| | | var origOverflow = el.style.maxHeight; |
| | | var scrollEl = el.querySelector('.markdown-body'); |
| | | if (scrollEl) { |
| | | scrollEl.style.maxHeight = 'none'; |
| | | scrollEl.style.overflow = 'visible'; |
| | | } |
| | | |
| | | html2canvas(el, { |
| | | scale: 2, |
| | | useCORS: true, |
| | | backgroundColor: '#ffffff', |
| | | width: el.scrollWidth, |
| | | height: el.scrollHeight, |
| | | windowWidth: el.scrollWidth, |
| | | windowHeight: el.scrollHeight |
| | | }).then(function(canvas) { |
| | | // 恢复滚动限制 |
| | | if (scrollEl) { |
| | | scrollEl.style.maxHeight = origMaxHeight || ''; |
| | | scrollEl.style.overflow = origOverflow || ''; |
| | | } |
| | | |
| | | var pdf = new jspdf.jsPDF('p', 'mm', 'a4'); |
| | | var pdfWidth = pdf.internal.pageSize.getWidth(); |
| | | var pdfHeight = pdf.internal.pageSize.getHeight(); |
| | | var margin = 15; |
| | | var contentWidth = pdfWidth - margin * 2; |
| | | |
| | | // 按 A4 页面高度分页 |
| | | var pageContentHeight = pdfHeight - margin * 2; |
| | | var imgRatio = canvas.width / contentWidth; |
| | | var totalImgHeightPx = canvas.height; |
| | | var pageImgHeightPx = pageContentHeight * imgRatio; |
| | | var srcY = 0; |
| | | var page = 0; |
| | | |
| | | while (srcY < totalImgHeightPx) { |
| | | if (page > 0) pdf.addPage(); |
| | | var sliceH = Math.min(pageImgHeightPx, totalImgHeightPx - srcY); |
| | | var sliceCanvas = document.createElement('canvas'); |
| | | sliceCanvas.width = canvas.width; |
| | | sliceCanvas.height = sliceH; |
| | | var ctx = sliceCanvas.getContext('2d'); |
| | | ctx.fillStyle = '#ffffff'; |
| | | ctx.fillRect(0, 0, sliceCanvas.width, sliceCanvas.height); |
| | | ctx.drawImage(canvas, 0, srcY, canvas.width, sliceH, 0, 0, canvas.width, sliceH); |
| | | var sliceImgH = sliceH / imgRatio; |
| | | pdf.addImage(sliceCanvas.toDataURL('image/png'), 'PNG', margin, margin, contentWidth, sliceImgH); |
| | | srcY += sliceH; |
| | | page++; |
| | | } |
| | | |
| | | var fileName = 'WCS数据分析报告_' + (self.selectedReport.periodType || '') + '_' + self.formatTime(self.selectedReport.createTime).replace(/[: ]/g, '-') + '.pdf'; |
| | | pdf.save(fileName); |
| | | self.pdfLoading = false; |
| | | }).catch(function() { |
| | | if (scrollEl) { |
| | | scrollEl.style.maxHeight = origMaxHeight || ''; |
| | | scrollEl.style.overflow = origOverflow || ''; |
| | | } |
| | | self.pdfLoading = false; |
| | | self.$message.error('PDF 生成失败'); |
| | | }); |
| | | } |
| | | } |
| | | }); |