/** * PNG图片生成器 * 将分析结果转换为PNG图片格式 * 使用Puppeteer将SVG转换为PNG */ const puppeteer = require('puppeteer'); const generatePNG = async (analysisData, analysisType, userName) => { let browser; try { // 生成SVG内容 const svgContent = await generateImageData(analysisData, analysisType, userName); // 创建包含SVG的HTML页面 const htmlContent = ` ${svgContent} `; // 启动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' ], timeout: 30000 }); const page = await browser.newPage(); // 设置页面内容 await page.setContent(htmlContent, { waitUntil: 'networkidle0' }); // 设置视口大小 await page.setViewport({ width: 800, height: 1200 }); // 截图生成PNG const pngBuffer = await page.screenshot({ type: 'png', fullPage: true, omitBackground: false }); // 确保返回的是Buffer对象 if (!Buffer.isBuffer(pngBuffer)) { console.warn('Puppeteer返回的不是Buffer,正在转换:', typeof pngBuffer); return Buffer.from(pngBuffer); } return pngBuffer; } catch (error) { console.error('生成PNG失败:', error); throw error; } finally { if (browser) { await browser.close(); } } }; /** * 生成图片数据(SVG格式) */ const generateImageData = async (analysisData, analysisType, userName) => { const timestamp = new Date().toLocaleString('zh-CN'); const analysisTypeLabel = getAnalysisTypeLabel(analysisType); // 生成SVG内容 let svg = ` 神机阁 专业命理分析平台 ${analysisTypeLabel}分析报告 姓名:${userName || '用户'} 生成时间:${timestamp.split(' ')[0]} `; // 根据分析类型添加不同内容 let yOffset = 260; switch (analysisType) { case 'bazi': yOffset = addBaziContent(svg, analysisData, yOffset); break; case 'ziwei': yOffset = addZiweiContent(svg, analysisData, yOffset); break; case 'yijing': yOffset = addYijingContent(svg, analysisData, yOffset); break; } // 页脚 svg += ` 本报告由神机阁AI命理分析平台生成,仅供参考 © 2025 神机阁 - AI命理分析平台 `; return svg; }; /** * 添加八字命理内容 */ const addBaziContent = (svg, analysisData, yOffset) => { let content = ''; // 基本信息 if (analysisData.basic_info) { content += ` 📋 基本信息 `; yOffset += 40; if (analysisData.basic_info.personal_data) { const personal = analysisData.basic_info.personal_data; const genderText = personal.gender === 'male' ? '男' : personal.gender === 'female' ? '女' : personal.gender || '未提供'; content += ` 姓名: ${personal.name || '未提供'} 性别: ${genderText} `; yOffset += 30; content += ` 出生日期: ${personal.birth_date || '未提供'} `; yOffset += 30; content += ` 出生时间: ${personal.birth_time || '未提供'} `; yOffset += 40; } // 八字信息 if (analysisData.basic_info.bazi_info) { const bazi = analysisData.basic_info.bazi_info; content += ` 🔮 八字信息 `; yOffset += 30; // 表格头 content += ` 柱位 天干 地支 纳音 `; yOffset += 25; // 表格内容 const pillars = [ { name: '年柱', data: bazi.year, nayin: bazi.year_nayin }, { name: '月柱', data: bazi.month, nayin: bazi.month_nayin }, { name: '日柱', data: bazi.day, nayin: bazi.day_nayin }, { name: '时柱', data: bazi.hour, nayin: bazi.hour_nayin } ]; pillars.forEach((pillar, index) => { const bgColor = index % 2 === 0 ? '#f8f9fa' : 'white'; content += ` ${pillar.name} ${pillar.data?.split('')[0] || '-'} ${pillar.data?.split('')[1] || '-'} ${pillar.nayin || '-'} `; yOffset += 25; }); yOffset += 20; } } // 五行分析 if (analysisData.wuxing_analysis && yOffset < 1000) { content += ` 🌟 五行分析 `; yOffset += 40; if (analysisData.wuxing_analysis.element_distribution) { const elements = analysisData.wuxing_analysis.element_distribution; const total = Object.values(elements).reduce((sum, count) => sum + (typeof count === 'number' ? count : 0), 0); content += ` 五行分布 `; yOffset += 30; // 五行分布图表 let xOffset = 120; Object.entries(elements).forEach(([element, count]) => { const numCount = typeof count === 'number' ? count : 0; const percentage = total > 0 ? Math.round((numCount / total) * 100) : 0; const barHeight = Math.max(numCount * 20, 5); const elementColor = getElementColor(element); // 柱状图 content += ` ${element} ${numCount} ${percentage}% `; xOffset += 100; }); yOffset += 150; } if (analysisData.wuxing_analysis.balance_analysis && yOffset < 1000) { content += ` 五行平衡分析 `; yOffset += 25; // 分析内容(截取前200字符) const analysisText = analysisData.wuxing_analysis.balance_analysis.substring(0, 200) + (analysisData.wuxing_analysis.balance_analysis.length > 200 ? '...' : ''); const lines = wrapText(analysisText, 50); lines.forEach(line => { if (yOffset < 1000) { content += ` ${line} `; yOffset += 20; } }); yOffset += 20; } } svg += content; return yOffset; }; /** * 添加紫微斗数内容 */ const addZiweiContent = (svg, analysisData, yOffset) => { let content = ''; // 基本信息 if (analysisData.basic_info) { content += ` 📋 基本信息 `; yOffset += 40; if (analysisData.basic_info.ziwei_info) { const ziwei = analysisData.basic_info.ziwei_info; if (ziwei.ming_gong) { content += ` 命宫: ${ziwei.ming_gong} `; yOffset += 30; } if (ziwei.wuxing_ju) { content += ` 五行局: ${ziwei.wuxing_ju} `; yOffset += 30; } if (ziwei.main_stars) { const starsText = Array.isArray(ziwei.main_stars) ? ziwei.main_stars.join('、') : ziwei.main_stars; content += ` 主星: ${starsText} `; yOffset += 40; } } } // 星曜分析 if (analysisData.star_analysis && yOffset < 1000) { content += ` ⭐ 星曜分析 `; yOffset += 40; if (analysisData.star_analysis.main_stars) { content += ` 主星分析 `; yOffset += 30; if (Array.isArray(analysisData.star_analysis.main_stars)) { analysisData.star_analysis.main_stars.slice(0, 3).forEach(star => { if (typeof star === 'object' && yOffset < 1000) { content += ` ${star.name || star.star} `; if (star.brightness) { content += ` 亮度:${star.brightness} `; } if (star.influence) { const influenceText = star.influence.substring(0, 60) + (star.influence.length > 60 ? '...' : ''); content += ` 影响:${influenceText} `; } yOffset += 80; } }); } } } svg += content; return yOffset; }; /** * 添加易经占卜内容 */ const addYijingContent = (svg, analysisData, yOffset) => { let content = ''; // 占卜问题 if (analysisData.question_analysis) { content += ` ❓ 占卜问题 `; yOffset += 40; if (analysisData.question_analysis.original_question) { content += ` 问题: `; const questionText = analysisData.question_analysis.original_question; const questionLines = wrapText(questionText, 45); questionLines.forEach((line, index) => { content += ` ${line} `; yOffset += 20; }); yOffset += 10; } if (analysisData.question_analysis.question_type) { content += ` 问题类型: ${analysisData.question_analysis.question_type} `; yOffset += 40; } } // 卦象信息 if (analysisData.hexagram_info && yOffset < 1000) { content += ` 🔮 卦象信息 `; yOffset += 40; if (analysisData.hexagram_info.main_hexagram) { const main = analysisData.hexagram_info.main_hexagram; content += ` 主卦 卦名: ${main.name || '未知'} 卦象: ${main.symbol || ''} `; if (main.meaning) { const meaningText = main.meaning.substring(0, 50) + (main.meaning.length > 50 ? '...' : ''); content += ` 含义: ${meaningText} `; } yOffset += 120; } } svg += content; return yOffset; }; /** * 获取五行颜色 */ const getElementColor = (element) => { const colors = { '木': '#22c55e', '火': '#ef4444', '土': '#eab308', '金': '#64748b', '水': '#3b82f6' }; return colors[element] || '#666'; }; /** * 文本换行处理 */ const wrapText = (text, maxLength) => { const lines = []; let currentLine = ''; for (let i = 0; i < text.length; i++) { currentLine += text[i]; if (currentLine.length >= maxLength || text[i] === '\n') { lines.push(currentLine.trim()); currentLine = ''; } } if (currentLine.trim()) { lines.push(currentLine.trim()); } return lines; }; /** * 获取分析类型标签 */ const getAnalysisTypeLabel = (analysisType) => { switch (analysisType) { case 'bazi': return '八字命理'; case 'ziwei': return '紫微斗数'; case 'yijing': return '易经占卜'; default: return '命理'; } }; /** * 获取SVG样式 */ const getSVGStyles = () => { return ` .main-title { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 36px; font-weight: bold; } .subtitle { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 16px; } .report-title { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 24px; font-weight: bold; } .info-text { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 14px; } .section-title { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 20px; font-weight: bold; } .subsection-title { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 16px; font-weight: bold; } .info-label { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 14px; font-weight: bold; } .info-value { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 14px; } .info-highlight { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 14px; font-weight: bold; } .table-header { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 12px; font-weight: bold; } .table-cell { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 12px; } .element-label { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 12px; font-weight: bold; } .element-count { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 11px; } .element-percent { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 10px; } .analysis-text { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 12px; } .star-name { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 14px; font-weight: bold; } .star-detail { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 11px; } .hexagram-name { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 16px; font-weight: bold; } .hexagram-symbol { font-family: monospace; font-size: 14px; font-weight: bold; } .footer-text { font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; font-size: 10px; } `; }; module.exports = { generatePNG };