马一丁

Modify the Logic for Downloading PDFs

@@ -244,8 +244,6 @@ class HTMLRenderer: @@ -244,8 +244,6 @@ class HTMLRenderer:
244 str: head片段HTML。 244 str: head片段HTML。
245 """ 245 """
246 css = self._build_css(theme_tokens) 246 css = self._build_css(theme_tokens)
247 - pdf_font_b64 = self._load_pdf_font_data()  
248 - pdf_font_literal = json.dumps(pdf_font_b64)  
249 247
250 # 加载第三方库 248 # 加载第三方库
251 chartjs = self._load_lib("chart.js") 249 chartjs = self._load_lib("chart.js")
@@ -288,10 +286,6 @@ class HTMLRenderer: @@ -288,10 +286,6 @@ class HTMLRenderer:
288 {css} 286 {css}
289 </style> 287 </style>
290 <script> 288 <script>
291 - // 预载 PDF 字体 Base64 数据,后续由 jspdf addFileToVFS 使用  
292 - window.pdfFontData = {pdf_font_literal};  
293 - </script>  
294 - <script>  
295 document.documentElement.classList.remove('no-js'); 289 document.documentElement.classList.remove('no-js');
296 document.documentElement.classList.add('js-ready'); 290 document.documentElement.classList.add('js-ready');
297 </script> 291 </script>
@@ -2845,6 +2839,7 @@ function hideExportOverlay(delay) { @@ -2845,6 +2839,7 @@ function hideExportOverlay(delay) {
2845 } 2839 }
2846 } 2840 }
2847 2841
  2842 +// exportPdf已移除
2848 function exportPdf() { 2843 function exportPdf() {
2849 const target = document.querySelector('main'); 2844 const target = document.querySelector('main');
2850 if (!target || typeof jspdf === 'undefined' || typeof jspdf.jsPDF !== 'function') { 2845 if (!target || typeof jspdf === 'undefined' || typeof jspdf.jsPDF !== 'function') {
@@ -3150,6 +3150,7 @@ @@ -3150,6 +3150,7 @@
3150 <div class="report-controls"> 3150 <div class="report-controls">
3151 <button class="report-button primary" id="generateReportButton">生成最终报告</button> 3151 <button class="report-button primary" id="generateReportButton">生成最终报告</button>
3152 <button class="report-button" id="downloadReportButton" disabled>下载HTML</button> 3152 <button class="report-button" id="downloadReportButton" disabled>下载HTML</button>
  3153 + <button class="report-button" id="downloadPdfButton" disabled>下载PDF</button>
3153 </div> 3154 </div>
3154 3155
3155 <!-- 任务进度区域 --> 3156 <!-- 任务进度区域 -->
@@ -3212,10 +3213,15 @@ @@ -3212,10 +3213,15 @@
3212 } 3213 }
3213 3214
3214 const downloadButton = document.getElementById('downloadReportButton'); 3215 const downloadButton = document.getElementById('downloadReportButton');
  3216 + const downloadPdfButton = document.getElementById('downloadPdfButton');
3215 if (downloadButton && !downloadButton.dataset.bound) { 3217 if (downloadButton && !downloadButton.dataset.bound) {
3216 downloadButton.dataset.bound = 'true'; 3218 downloadButton.dataset.bound = 'true';
3217 downloadButton.addEventListener('click', () => downloadReport()); 3219 downloadButton.addEventListener('click', () => downloadReport());
3218 } 3220 }
  3221 + if (downloadPdfButton && !downloadPdfButton.dataset.bound) {
  3222 + downloadPdfButton.dataset.bound = 'true';
  3223 + downloadPdfButton.addEventListener('click', () => downloadPdfFromPreview());
  3224 + }
3219 3225
3220 if (reportTaskId) { 3226 if (reportTaskId) {
3221 setGenerateButtonState(true); 3227 setGenerateButtonState(true);
@@ -3247,7 +3253,8 @@ @@ -3247,7 +3253,8 @@
3247 3253
3248 function updateDownloadButtonState(task) { 3254 function updateDownloadButtonState(task) {
3249 const downloadButton = document.getElementById('downloadReportButton'); 3255 const downloadButton = document.getElementById('downloadReportButton');
3250 - if (!downloadButton) return; 3256 + const downloadPdfButton = document.getElementById('downloadPdfButton');
  3257 + if (!downloadButton || !downloadPdfButton) return;
3251 3258
3252 if (task && task.status === 'completed' && (task.report_file_ready || task.report_file_path)) { 3259 if (task && task.status === 'completed' && (task.report_file_ready || task.report_file_path)) {
3253 downloadButton.disabled = false; 3260 downloadButton.disabled = false;
@@ -3255,12 +3262,16 @@ @@ -3255,12 +3262,16 @@
3255 downloadButton.dataset.filename = task.report_file_name || ''; 3262 downloadButton.dataset.filename = task.report_file_name || '';
3256 const label = task.report_file_name ? `下载HTML (${task.report_file_name})` : '下载HTML'; 3263 const label = task.report_file_name ? `下载HTML (${task.report_file_name})` : '下载HTML';
3257 downloadButton.textContent = label; 3264 downloadButton.textContent = label;
  3265 + downloadPdfButton.disabled = false;
  3266 + downloadPdfButton.dataset.taskId = task.task_id;
3258 lastCompletedReportTask = task; 3267 lastCompletedReportTask = task;
3259 } else if (!lastCompletedReportTask || (task && task.status !== 'completed')) { 3268 } else if (!lastCompletedReportTask || (task && task.status !== 'completed')) {
3260 downloadButton.disabled = true; 3269 downloadButton.disabled = true;
3261 downloadButton.dataset.taskId = ''; 3270 downloadButton.dataset.taskId = '';
3262 downloadButton.dataset.filename = ''; 3271 downloadButton.dataset.filename = '';
3263 downloadButton.textContent = '下载HTML'; 3272 downloadButton.textContent = '下载HTML';
  3273 + downloadPdfButton.disabled = true;
  3274 + downloadPdfButton.dataset.taskId = '';
3264 if (!reportTaskId) { 3275 if (!reportTaskId) {
3265 lastCompletedReportTask = null; 3276 lastCompletedReportTask = null;
3266 } 3277 }
@@ -3320,6 +3331,59 @@ @@ -3320,6 +3331,59 @@
3320 }); 3331 });
3321 } 3332 }
3322 3333
  3334 + async function downloadPdfFromPreview() {
  3335 + const iframe = document.getElementById('report-iframe');
  3336 + const btn = document.getElementById('downloadPdfButton');
  3337 + if (!iframe || !iframe.contentDocument) {
  3338 + showMessage('请先加载报告预览再下载PDF', 'error');
  3339 + return;
  3340 + }
  3341 + const target = iframe.contentDocument.documentElement;
  3342 + if (!target) {
  3343 + showMessage('报告内容未就绪', 'error');
  3344 + return;
  3345 + }
  3346 + if (btn) btn.disabled = true;
  3347 + showMessage('正在生成PDF,请稍候...', 'info');
  3348 + try {
  3349 + const { jsPDF } = window.jspdf || {};
  3350 + if (!jsPDF) {
  3351 + throw new Error('PDF依赖未加载');
  3352 + }
  3353 + const pdf = new jsPDF('p', 'mm', 'a4');
  3354 + const pageWidth = pdf.internal.pageSize.getWidth();
  3355 + const pxWidth = Math.max(target.scrollWidth || 0, Math.round(pageWidth * 3.78));
  3356 + const renderTask = pdf.html(target, {
  3357 + x: 10,
  3358 + y: 10,
  3359 + width: pageWidth - 20,
  3360 + windowWidth: pxWidth,
  3361 + margin: [10, 10, 16, 10],
  3362 + autoPaging: 'text',
  3363 + html2canvas: {
  3364 + scale: Math.min(1.2, Math.max(0.8, pageWidth / (target.clientWidth || pageWidth))),
  3365 + useCORS: true,
  3366 + scrollX: 0,
  3367 + scrollY: -iframe.contentWindow.scrollY,
  3368 + logging: false
  3369 + },
  3370 + pagebreak: {
  3371 + mode: ['css', 'legacy'],
  3372 + avoid: ['.chapter', '.callout', '.chart-card', '.table-wrap', '.kpi-grid', '.hero-section'],
  3373 + before: '.chapter-divider'
  3374 + }
  3375 + });
  3376 + await (renderTask && typeof renderTask.then === 'function' ? renderTask : Promise.resolve());
  3377 + pdf.save('report.pdf');
  3378 + showMessage('PDF生成完成,已开始下载', 'success');
  3379 + } catch (err) {
  3380 + console.error('生成PDF失败:', err);
  3381 + showMessage('生成PDF失败: ' + err.message, 'error');
  3382 + } finally {
  3383 + if (btn) btn.disabled = false;
  3384 + }
  3385 + }
  3386 +
3323 // 渲染任务状态(使用新的进度条样式) 3387 // 渲染任务状态(使用新的进度条样式)
3324 function renderTaskStatus(task) { 3388 function renderTaskStatus(task) {
3325 // 状态文本的中文映射 3389 // 状态文本的中文映射