/** * PDF格式生成器 * 将分析结果转换为PDF文档 * 使用puppeteer进行HTML到PDF的转换 */ const puppeteer = require('puppeteer'); const { generateMarkdown } = require('./markdownGenerator.cjs'); const generatePDF = async (analysisData, analysisType, userName) => { let browser; try { // 生成Markdown内容 const markdownBuffer = await generateMarkdown(analysisData, analysisType, userName); // 将Buffer转换为字符串 const markdownString = Buffer.isBuffer(markdownBuffer) ? markdownBuffer.toString('utf8') : String(markdownBuffer); // 将Markdown转换为HTML const htmlContent = convertMarkdownToHTML(markdownString, analysisType, userName); // 启动puppeteer浏览器 browser = await puppeteer.launch({ headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--no-first-run', '--disable-extensions', '--disable-plugins', '--disable-images', '--disable-javascript', '--run-all-compositor-stages-before-draw', '--disable-background-timer-throttling', '--disable-renderer-backgrounding', '--disable-backgrounding-occluded-windows', '--disable-ipc-flooding-protection' ], timeout: 30000 }); const page = await browser.newPage(); // 设置页面内容 await page.setContent(htmlContent, { waitUntil: 'networkidle0' }); // 生成PDF const pdfBuffer = await page.pdf({ format: 'A4', margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' }, printBackground: true, preferCSSPageSize: true }); // 确保返回的是Buffer对象 if (!Buffer.isBuffer(pdfBuffer)) { console.warn('Puppeteer返回的不是Buffer,正在转换:', typeof pdfBuffer); return Buffer.from(pdfBuffer); } return pdfBuffer; } catch (error) { console.error('生成PDF失败:', error); throw error; } finally { if (browser) { await browser.close(); } } }; /** * 将Markdown内容转换为适合PDF的HTML */ const convertMarkdownToHTML = (markdownContent, analysisType, userName) => { // 预处理:分离表格 const lines = markdownContent.split('\n'); let html = ''; let inTable = false; let tableRows = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // 检测表格开始 if (line.includes('|') && line.includes('---')) { inTable = true; // 添加表格头(前一行) if (i > 0 && lines[i-1].includes('|')) { const headerCells = lines[i-1].split('|').map(cell => cell.trim()).filter(cell => cell); tableRows.push('' + headerCells.map(cell => `${cell}`).join('') + ''); } continue; } // 处理表格行 if (inTable && line.includes('|')) { const cells = line.split('|').map(cell => cell.trim()).filter(cell => cell); if (cells.length > 0) { tableRows.push('' + cells.map(cell => `${cell}`).join('') + ''); } continue; } // 表格结束 if (inTable && !line.includes('|')) { html += '' + tableRows.join('') + '
\n'; tableRows = []; inTable = false; } // 处理非表格行 if (!inTable) { html += line + '\n'; } } // 处理未结束的表格 if (tableRows.length > 0) { html += '' + tableRows.join('') + '
\n'; } // Markdown到HTML转换 html = html // 标题转换 .replace(/^# (.+)$/gm, '

$1

') .replace(/^## (.+)$/gm, '

$1

') .replace(/^### (.+)$/gm, '

$1

') .replace(/^#### (.+)$/gm, '

$1

') // 加粗文本 .replace(/\*\*(.+?)\*\*/g, '$1') // 处理列表 .replace(/^- (.+)$/gm, '
  • $1
  • ') // 将连续的li包装在ul中 .replace(/(
  • .*<\/li>\s*)+/gs, (match) => { return ''; }) // 水平分割线 .replace(/^---$/gm, '
    ') // 段落处理 .replace(/\n\s*\n/g, '

    ') .replace(/^(?!<[h1-6]|$1

    ') // 清理多余的p标签 .replace(/

    <\/p>/g, '') .replace(/

    (<[^>]+>)/g, '$1') .replace(/(<\/[^>]+>)<\/p>/g, '$1') // 换行处理 .replace(/\n/g, ''); // 包装在完整的HTML文档中 return ` ${getAnalysisTypeLabel(analysisType)}分析报告

    ${html}
    `; }; /** * 生成HTML内容 */ const generateHTML = (analysisData, analysisType, userName) => { const timestamp = new Date().toLocaleString('zh-CN'); let html = ` ${getAnalysisTypeLabel(analysisType)}分析报告

    ${getAnalysisTypeLabel(analysisType)}分析报告

    姓名:${userName || '用户'}

    生成时间:${timestamp}

    `; // 根据分析类型生成不同的HTML内容 switch (analysisType) { case 'bazi': html += generateBaziHTML(analysisData); break; case 'ziwei': html += generateZiweiHTML(analysisData); break; case 'yijing': html += generateYijingHTML(analysisData); break; } html += `

    免责声明:

    本报告由神机阁AI命理分析平台生成,仅供参考,请理性对待。

    命理分析不能替代个人努力和理性决策。

    `; return html; }; /** * 生成八字命理HTML内容 */ const generateBaziHTML = (analysisData) => { let html = ''; // 基本信息 if (analysisData.basic_info) { html += `

    📋 基本信息

    `; if (analysisData.basic_info.personal_data) { const personal = analysisData.basic_info.personal_data; html += `
    ${personal.name || '未提供'}
    ${personal.gender === 'male' ? '男' : personal.gender === 'female' ? '女' : personal.gender || '未提供'}
    ${personal.birth_date || '未提供'}
    ${personal.birth_time || '未提供'}
    `; if (personal.birth_place) { html += `
    ${personal.birth_place}
    `; } } html += `
    `; // 八字信息 if (analysisData.basic_info.bazi_info) { const bazi = analysisData.basic_info.bazi_info; html += `

    🔮 八字信息

    柱位 天干 地支 纳音
    年柱 ${bazi.year?.split('')[0] || '-'} ${bazi.year?.split('')[1] || '-'} ${bazi.year_nayin || '-'}
    月柱 ${bazi.month?.split('')[0] || '-'} ${bazi.month?.split('')[1] || '-'} ${bazi.month_nayin || '-'}
    日柱 ${bazi.day?.split('')[0] || '-'} ${bazi.day?.split('')[1] || '-'} ${bazi.day_nayin || '-'}
    时柱 ${bazi.hour?.split('')[0] || '-'} ${bazi.hour?.split('')[1] || '-'} ${bazi.hour_nayin || '-'}
    `; } html += `
    `; } // 五行分析 if (analysisData.wuxing_analysis) { html += `

    🌟 五行分析

    `; if (analysisData.wuxing_analysis.element_distribution) { html += `

    五行分布

    `; const elements = analysisData.wuxing_analysis.element_distribution; const total = Object.values(elements).reduce((sum, count) => sum + (typeof count === 'number' ? count : 0), 0); Object.entries(elements).forEach(([element, count]) => { const numCount = typeof count === 'number' ? count : 0; const percentage = total > 0 ? Math.round((numCount / total) * 100) : 0; const strength = numCount >= 3 ? '旺' : numCount >= 2 ? '中' : '弱'; html += ` `; }); html += `
    五行 数量 占比 强度
    ${element} ${numCount} ${percentage}% ${strength}
    `; } if (analysisData.wuxing_analysis.balance_analysis) { html += `

    五行平衡分析

    ${analysisData.wuxing_analysis.balance_analysis}

    `; } if (analysisData.wuxing_analysis.suggestions) { html += `

    调和建议

    ${analysisData.wuxing_analysis.suggestions}

    `; } html += `
    `; } // 格局分析 if (analysisData.pattern_analysis) { html += `

    🎯 格局分析

    `; if (analysisData.pattern_analysis.main_pattern) { html += `
    ${analysisData.pattern_analysis.main_pattern}
    `; } if (analysisData.pattern_analysis.pattern_strength) { const strength = analysisData.pattern_analysis.pattern_strength; const strengthLabel = strength === 'strong' ? '强' : strength === 'moderate' ? '中等' : strength === 'fair' ? '一般' : '较弱'; html += `
    ${strengthLabel}
    `; } if (analysisData.pattern_analysis.analysis) { html += `

    格局详解

    ${analysisData.pattern_analysis.analysis}

    `; } html += `
    `; } // 人生指导 if (analysisData.life_guidance) { html += `

    🌟 人生指导

    `; if (analysisData.life_guidance.strengths) { html += `

    优势特质

    `; if (Array.isArray(analysisData.life_guidance.strengths)) { html += '
      '; analysisData.life_guidance.strengths.forEach(strength => { html += `
    • ${strength}
    • `; }); html += '
    '; } else { html += `

    ${analysisData.life_guidance.strengths}

    `; } html += `
    `; } if (analysisData.life_guidance.challenges) { html += `

    需要注意

    `; if (Array.isArray(analysisData.life_guidance.challenges)) { html += '
      '; analysisData.life_guidance.challenges.forEach(challenge => { html += `
    • ${challenge}
    • `; }); html += '
    '; } else { html += `

    ${analysisData.life_guidance.challenges}

    `; } html += `
    `; } if (analysisData.life_guidance.overall_summary) { html += `

    综合总结

    ${analysisData.life_guidance.overall_summary}

    `; } html += `
    `; } return html; }; /** * 生成紫微斗数HTML内容 */ const generateZiweiHTML = (analysisData) => { let html = ''; // 基本信息 if (analysisData.basic_info) { html += `

    📋 基本信息

    `; if (analysisData.basic_info.personal_data) { const personal = analysisData.basic_info.personal_data; html += `
    ${personal.name || '未提供'}
    ${personal.gender === 'male' ? '男' : personal.gender === 'female' ? '女' : personal.gender || '未提供'}
    ${personal.birth_date || '未提供'}
    ${personal.birth_time || '未提供'}
    `; } // 紫微基本信息 if (analysisData.basic_info.ziwei_info) { const ziwei = analysisData.basic_info.ziwei_info; if (ziwei.ming_gong) { html += `
    ${ziwei.ming_gong}
    `; } if (ziwei.wuxing_ju) { html += `
    ${ziwei.wuxing_ju}
    `; } if (ziwei.main_stars) { html += `
    ${Array.isArray(ziwei.main_stars) ? ziwei.main_stars.join('、') : ziwei.main_stars}
    `; } } html += `
    `; } // 星曜分析 if (analysisData.star_analysis) { html += `

    ⭐ 星曜分析

    `; if (analysisData.star_analysis.main_stars) { html += `

    主星分析

    `; if (Array.isArray(analysisData.star_analysis.main_stars)) { analysisData.star_analysis.main_stars.forEach(star => { if (typeof star === 'object') { html += `
    ${star.name || star.star}
    `; if (star.brightness) { html += `

    亮度:${star.brightness}

    `; } if (star.influence) { html += `

    影响:${star.influence}

    `; } if (star.description) { html += `

    特质:${star.description}

    `; } html += `
    `; } }); } else { html += `

    ${analysisData.star_analysis.main_stars}

    `; } html += `
    `; } html += `
    `; } return html; }; /** * 生成易经占卜HTML内容 */ const generateYijingHTML = (analysisData) => { let html = ''; // 占卜问题 if (analysisData.question_analysis) { html += `

    ❓ 占卜问题

    `; if (analysisData.question_analysis.original_question) { html += `
    ${analysisData.question_analysis.original_question}
    `; } if (analysisData.question_analysis.question_type) { html += `
    ${analysisData.question_analysis.question_type}
    `; } html += `
    `; } // 卦象信息 if (analysisData.hexagram_info) { html += `

    🔮 卦象信息

    `; if (analysisData.hexagram_info.main_hexagram) { const main = analysisData.hexagram_info.main_hexagram; html += `

    主卦

    ${main.name || '未知'}
    ${main.symbol || ''}
    `; if (main.number) { html += `
    第${main.number}卦
    `; } if (main.meaning) { html += `
    ${main.meaning}
    `; } html += `
    `; } html += `
    `; } return html; }; /** * 获取分析类型标签 */ const getAnalysisTypeLabel = (analysisType) => { switch (analysisType) { case 'bazi': return '八字命理'; case 'ziwei': return '紫微斗数'; case 'yijing': return '易经占卜'; default: return '命理'; } }; /** * 获取PDF专用CSS样式 */ const getPDFCSS = () => { return ` * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Microsoft YaHei', '微软雅黑', 'SimSun', '宋体', Arial, sans-serif; line-height: 1.6; color: #333; background-color: white; font-size: 16px; } .container { max-width: 100%; margin: 0; padding: 0; } h1 { font-size: 32px; color: #2c3e50; text-align: center; margin: 20px 0; padding: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; } h2 { font-size: 24px; color: #34495e; margin: 20px 0 10px 0; padding: 10px 0; border-bottom: 2px solid #3498db; page-break-after: avoid; } h3 { font-size: 20px; color: #2980b9; margin: 15px 0 8px 0; padding-left: 10px; page-break-after: avoid; } h4 { font-size: 18px; color: #27ae60; margin: 12px 0 6px 0; page-break-after: avoid; } p { margin: 8px 0; line-height: 1.6; text-align: justify; } strong { color: #2c3e50; font-weight: bold; } ul, ol { margin: 10px 0; padding-left: 20px; } li { margin: 4px 0; line-height: 1.5; } table { width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 14px; page-break-inside: avoid; } th, td { border: 1px solid #bdc3c7; padding: 8px; text-align: left; } th { background-color: #ecf0f1; font-weight: bold; color: #2c3e50; } tr:nth-child(even) { background-color: #f8f9fa; } .section { margin: 20px 0; page-break-inside: avoid; } .page-break { page-break-before: always; } .no-break { page-break-inside: avoid; } /* 打印优化 */ @page { margin: 20mm 15mm; size: A4; } @media print { body { font-size: 11px; } h1 { font-size: 20px; } h2 { font-size: 16px; } h3 { font-size: 14px; } .section { break-inside: avoid; } } `; }; /** * 获取原有CSS样式(保持兼容性) */ const getCSS = () => { return ` * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; line-height: 1.6; color: #333; background-color: #f9f9f9; } .container { max-width: 800px; margin: 0 auto; background: white; box-shadow: 0 0 20px rgba(0,0,0,0.1); } .header { background: linear-gradient(135deg, #dc2626, #b91c1c); color: white; padding: 30px; text-align: center; } .header .logo h1 { font-size: 2.5em; margin-bottom: 5px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } .header .logo p { font-size: 1.1em; opacity: 0.9; } .header .report-info { margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.3); } .header .report-info h2 { font-size: 1.8em; margin-bottom: 10px; } .content { padding: 30px; } .section { margin-bottom: 40px; padding-bottom: 30px; border-bottom: 1px solid #eee; } .section:last-child { border-bottom: none; } .section-title { font-size: 1.5em; color: #dc2626; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid #dc2626; } .subsection-title { font-size: 1.2em; color: #b91c1c; margin: 20px 0 10px 0; } .info-grid { display: flex; flex-direction: column; gap: 15px; margin-bottom: 20px; } .info-item { display: flex; align-items: center; padding: 10px; background: #f8f9fa; border-radius: 5px; } .info-item label { font-weight: bold; margin-right: 10px; min-width: 80px; } .highlight { color: #dc2626; font-weight: bold; } .bazi-table, .element-table { width: 100%; border-collapse: collapse; margin: 15px 0; } .bazi-table th, .bazi-table td, .element-table th, .element-table td { border: 1px solid #ddd; padding: 12px; text-align: center; } .bazi-table th, .element-table th { background: #dc2626; color: white; font-weight: bold; } .bazi-table tr:nth-child(even), .element-table tr:nth-child(even) { background: #f8f9fa; } .element-木 { color: #22c55e; font-weight: bold; } .element-火 { color: #ef4444; font-weight: bold; } .element-土 { color: #eab308; font-weight: bold; } .element-金 { color: #64748b; font-weight: bold; } .element-水 { color: #3b82f6; font-weight: bold; } .strength-旺 { color: #22c55e; font-weight: bold; } .strength-中 { color: #eab308; font-weight: bold; } .strength-弱 { color: #ef4444; font-weight: bold; } .strength-strong { color: #22c55e; font-weight: bold; } .strength-moderate { color: #eab308; font-weight: bold; } .strength-fair { color: #f97316; font-weight: bold; } .strength-weak { color: #ef4444; font-weight: bold; } .analysis-content { margin: 20px 0; padding: 20px; background: #f8f9fa; border-radius: 5px; } .guidance-item { margin: 20px 0; padding: 20px; background: #fff7ed; border-radius: 8px; border: 1px solid #fed7aa; } .guidance-content ul { margin-left: 20px; } .guidance-content li { margin: 8px 0; } .star-analysis { display: flex; flex-direction: column; gap: 20px; } .star-item { padding: 15px; background: #f1f5f9; border-radius: 8px; border-left: 4px solid #3b82f6; } .star-item h5 { color: #1e40af; margin-bottom: 10px; font-size: 1.1em; } .hexagram-item { margin: 20px 0; padding: 20px; background: #fef3c7; border-radius: 8px; border: 1px solid #fbbf24; } .hexagram-symbol { font-family: monospace; font-size: 1.2em; font-weight: bold; color: #92400e; } .footer { background: #f8f9fa; padding: 30px; border-top: 1px solid #eee; } .disclaimer { margin-bottom: 20px; padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; } .disclaimer p { margin: 5px 0; } .footer-info { text-align: center; color: #666; font-size: 0.9em; } .footer-info p { margin: 5px 0; } @media print { body { background: white; } .container { box-shadow: none; } } `; }; module.exports = { generatePDF };