diff --git a/server/services/generators/pdfGenerator.cjs b/server/services/generators/pdfGenerator.cjs index 7cade8b..44f29de 100644 --- a/server/services/generators/pdfGenerator.cjs +++ b/server/services/generators/pdfGenerator.cjs @@ -771,7 +771,7 @@ const getPDFCSS = () => { line-height: 1.6; color: #333; background-color: white; - font-size: 12px; + font-size: 16px; } .container { @@ -781,7 +781,7 @@ const getPDFCSS = () => { } h1 { - font-size: 24px; + font-size: 32px; color: #2c3e50; text-align: center; margin: 20px 0; @@ -792,7 +792,7 @@ const getPDFCSS = () => { } h2 { - font-size: 18px; + font-size: 24px; color: #34495e; margin: 20px 0 10px 0; padding: 10px 0; @@ -801,16 +801,15 @@ const getPDFCSS = () => { } h3 { - font-size: 16px; + font-size: 20px; color: #2980b9; margin: 15px 0 8px 0; padding-left: 10px; - border-left: 4px solid #3498db; page-break-after: avoid; } h4 { - font-size: 14px; + font-size: 18px; color: #27ae60; margin: 12px 0 6px 0; page-break-after: avoid; @@ -841,7 +840,7 @@ const getPDFCSS = () => { width: 100%; border-collapse: collapse; margin: 15px 0; - font-size: 11px; + font-size: 14px; page-break-inside: avoid; } @@ -987,8 +986,8 @@ const getCSS = () => { } .info-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + display: flex; + flex-direction: column; gap: 15px; margin-bottom: 20px; } @@ -1055,8 +1054,7 @@ const getCSS = () => { margin: 20px 0; padding: 20px; background: #f8f9fa; - border-left: 4px solid #dc2626; - border-radius: 0 5px 5px 0; + border-radius: 5px; } .guidance-item { @@ -1076,8 +1074,8 @@ const getCSS = () => { } .star-analysis { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + display: flex; + flex-direction: column; gap: 20px; } diff --git a/src/components/ui/DownloadButton.tsx b/src/components/ui/DownloadButton.tsx index 31178b1..0bd69db 100644 --- a/src/components/ui/DownloadButton.tsx +++ b/src/components/ui/DownloadButton.tsx @@ -135,22 +135,78 @@ const DownloadButton: React.FC = ({ // 导出为PNG const exportToPNG = async (element: HTMLElement): Promise => { + // 确保页面完全加载和渲染 + await new Promise(resolve => setTimeout(resolve, 1000)); + const canvas = await html2canvas(element, { - scale: 2, - useCORS: true, - allowTaint: true, - backgroundColor: '#ffffff', - scrollX: 0, - scrollY: 0, - logging: false, - onclone: (clonedDoc) => { - const elementsToHide = clonedDoc.querySelectorAll( - '.no-export, [data-no-export], .fixed, .sticky, .floating' - ); - elementsToHide.forEach(el => { - (el as HTMLElement).style.display = 'none'; - }); - } + scale: 2, + useCORS: true, + allowTaint: true, + backgroundColor: '#ffffff', + scrollX: 0, + scrollY: 0, + logging: true, // 启用日志以调试 + // 移除固定尺寸限制,让html2canvas自动计算 + // width: 640, + // height: element.scrollHeight + 100, + windowWidth: 640, + // windowHeight: element.scrollHeight + 100, + onclone: (clonedDoc) => { + // 隐藏不需要导出的元素 + const elementsToHide = clonedDoc.querySelectorAll( + '.no-export, [data-no-export], .fixed, .sticky, .floating' + ); + elementsToHide.forEach(el => { + (el as HTMLElement).style.display = 'none'; + }); + + // 模拟移动端视口 + const viewport = clonedDoc.createElement('meta'); + viewport.name = 'viewport'; + viewport.content = 'width=640, initial-scale=1'; + clonedDoc.head.appendChild(viewport); + + // 添加移动端样式 + const style = clonedDoc.createElement('style'); + style.textContent = ` + /* 模拟移动端视口 */ + body { + width: 640px !important; + max-width: 640px !important; + margin: 0 !important; + padding: 16px !important; + font-size: 16px !important; + line-height: 1.6 !important; + } + + /* 移除所有边框 */ + * { + border-left: none !important; + border-right: none !important; + } + + /* 确保移动端布局 */ + .grid { + grid-template-columns: 1fr !important; + } + + .flex { + flex-direction: column !important; + } + + /* 移动端响应式类生效 */ + .sm\\:grid-cols-1, + .sm\\:grid-cols-2, + .sm\\:grid-cols-3 { + grid-template-columns: 1fr !important; + } + + .sm\\:flex-col { + flex-direction: column !important; + } + `; + clonedDoc.head.appendChild(style); + } }); const link = document.createElement('a'); @@ -167,22 +223,78 @@ const DownloadButton: React.FC = ({ // 导出为PDF const exportToPDF = async (element: HTMLElement): Promise => { + // 确保页面完全加载和渲染 + await new Promise(resolve => setTimeout(resolve, 1000)); + const canvas = await html2canvas(element, { - scale: 1.5, - useCORS: true, - allowTaint: true, - backgroundColor: '#ffffff', - scrollX: 0, - scrollY: 0, - logging: false, - onclone: (clonedDoc) => { - const elementsToHide = clonedDoc.querySelectorAll( - '.no-export, [data-no-export], .fixed, .sticky, .floating' - ); - elementsToHide.forEach(el => { - (el as HTMLElement).style.display = 'none'; - }); - } + scale: 1.5, + useCORS: true, + allowTaint: true, + backgroundColor: '#ffffff', + scrollX: 0, + scrollY: 0, + logging: true, // 启用日志以调试 + // 移除固定尺寸限制,让html2canvas自动计算 + // width: 640, + // height: element.scrollHeight + 100, + windowWidth: 640, + // windowHeight: element.scrollHeight + 100, + onclone: (clonedDoc) => { + // 隐藏不需要导出的元素 + const elementsToHide = clonedDoc.querySelectorAll( + '.no-export, [data-no-export], .fixed, .sticky, .floating' + ); + elementsToHide.forEach(el => { + (el as HTMLElement).style.display = 'none'; + }); + + // 模拟移动端视口 + const viewport = clonedDoc.createElement('meta'); + viewport.name = 'viewport'; + viewport.content = 'width=640, initial-scale=1'; + clonedDoc.head.appendChild(viewport); + + // 添加移动端样式 + const style = clonedDoc.createElement('style'); + style.textContent = ` + /* 模拟移动端视口 */ + body { + width: 640px !important; + max-width: 640px !important; + margin: 0 !important; + padding: 16px !important; + font-size: 16px !important; + line-height: 1.6 !important; + } + + /* 移除所有边框 */ + * { + border-left: none !important; + border-right: none !important; + } + + /* 确保移动端布局 */ + .grid { + grid-template-columns: 1fr !important; + } + + .flex { + flex-direction: column !important; + } + + /* 移动端响应式类生效 */ + .sm\\:grid-cols-1, + .sm\\:grid-cols-2, + .sm\\:grid-cols-3 { + grid-template-columns: 1fr !important; + } + + .sm\\:flex-col { + flex-direction: column !important; + } + `; + clonedDoc.head.appendChild(style); + } }); const imgData = canvas.toDataURL('image/png');