diff --git a/server/database/schema.sql b/server/database/schema.sql
index 3a6009f..a05aaa4 100644
--- a/server/database/schema.sql
+++ b/server/database/schema.sql
@@ -45,6 +45,18 @@ CREATE TABLE IF NOT EXISTS numerology_readings (
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
+-- 下载历史表
+CREATE TABLE IF NOT EXISTS download_history (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL,
+ analysis_type TEXT NOT NULL CHECK (analysis_type IN ('bazi', 'ziwei', 'yijing')),
+ format TEXT NOT NULL CHECK (format IN ('markdown', 'pdf', 'png')),
+ filename TEXT NOT NULL,
+ file_size INTEGER,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+);
+
-- 会话表 (用于JWT token管理)
CREATE TABLE IF NOT EXISTS user_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
diff --git a/server/index.cjs b/server/index.cjs
index ae1d403..3120589 100644
--- a/server/index.cjs
+++ b/server/index.cjs
@@ -9,6 +9,7 @@ const authRoutes = require('./routes/auth.cjs');
const analysisRoutes = require('./routes/analysis.cjs');
const historyRoutes = require('./routes/history.cjs');
const profileRoutes = require('./routes/profile.cjs');
+const downloadRoutes = require('./routes/download.cjs');
// 导入中间件
const { errorHandler } = require('./middleware/errorHandler.cjs');
@@ -92,6 +93,7 @@ app.use('/api/auth', authRoutes);
app.use('/api/analysis', analysisRoutes);
app.use('/api/history', historyRoutes);
app.use('/api/profile', profileRoutes);
+app.use('/api/download', downloadRoutes);
// 静态文件服务 (用于生产环境)
// 强制在 Koyeb 部署时启用静态文件服务
diff --git a/server/routes/download.cjs b/server/routes/download.cjs
new file mode 100644
index 0000000..6904b8e
--- /dev/null
+++ b/server/routes/download.cjs
@@ -0,0 +1,226 @@
+const express = require('express');
+const { authenticate } = require('../middleware/auth.cjs');
+const { dbManager } = require('../database/index.cjs');
+
+// 临时注释生成器导入,先测试路由基本功能
+// const { generateMarkdown } = require('../services/generators/markdownGenerator.cjs');
+// const { generatePDF } = require('../services/generators/pdfGenerator.cjs');
+// const { generatePNG } = require('../services/generators/pngGenerator.cjs');
+
+const router = express.Router();
+
+/**
+ * 下载分析结果
+ * POST /api/download
+ * 支持格式:markdown, pdf, png
+ */
+router.post('/', authenticate, async (req, res) => {
+ try {
+ const { analysisData, analysisType, format, userName } = req.body;
+ const userId = req.user.id;
+
+ // 验证必需参数
+ if (!analysisData || !analysisType || !format) {
+ return res.status(400).json({
+ error: '缺少必需参数',
+ details: 'analysisData, analysisType, format 都是必需的'
+ });
+ }
+
+ // 验证格式类型
+ const supportedFormats = ['markdown', 'pdf', 'png'];
+ if (!supportedFormats.includes(format)) {
+ return res.status(400).json({
+ error: '不支持的格式',
+ supportedFormats
+ });
+ }
+
+ // 验证分析类型
+ const supportedAnalysisTypes = ['bazi', 'ziwei', 'yijing'];
+ if (!supportedAnalysisTypes.includes(analysisType)) {
+ return res.status(400).json({
+ error: '不支持的分析类型',
+ supportedAnalysisTypes
+ });
+ }
+
+ let fileBuffer;
+ let contentType;
+ let fileExtension;
+ let filename;
+
+ // 生成文件名
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, '');
+ const analysisTypeLabel = {
+ 'bazi': '八字命理',
+ 'ziwei': '紫微斗数',
+ 'yijing': '易经占卜'
+ }[analysisType];
+
+ const baseFilename = `${analysisTypeLabel}_${userName || 'user'}_${timestamp}`;
+
+ try {
+ switch (format) {
+ case 'markdown':
+ // 临时简单实现
+ const markdownContent = `# ${analysisTypeLabel}分析报告\n\n**姓名:** ${userName || '用户'}\n**生成时间:** ${new Date().toLocaleString('zh-CN')}\n\n## 分析结果\n\n这是一个测试文件。\n\n---\n\n*本报告由神机阁AI命理分析平台生成*`;
+ fileBuffer = Buffer.from(markdownContent, 'utf8');
+ contentType = 'text/markdown';
+ fileExtension = 'md';
+ filename = `${baseFilename}.md`;
+ break;
+
+ case 'pdf':
+ // 临时返回HTML内容
+ const htmlContent = `
${analysisTypeLabel}分析报告${analysisTypeLabel}分析报告
姓名:${userName || '用户'}
生成时间:${new Date().toLocaleString('zh-CN')}
分析结果
这是一个测试文件。
`;
+ fileBuffer = Buffer.from(htmlContent, 'utf8');
+ contentType = 'text/html';
+ fileExtension = 'html';
+ filename = `${baseFilename}.html`;
+ break;
+
+ case 'png':
+ // 临时返回SVG内容
+ const svgContent = ``;
+ fileBuffer = Buffer.from(svgContent, 'utf8');
+ contentType = 'image/svg+xml';
+ fileExtension = 'svg';
+ filename = `${baseFilename}.svg`;
+ break;
+ }
+ } catch (generationError) {
+ console.error(`生成${format}文件失败:`, generationError);
+ return res.status(500).json({
+ error: `生成${format}文件失败`,
+ details: generationError.message
+ });
+ }
+
+ // 记录下载历史(可选)
+ try {
+ const db = dbManager.getDb();
+ const stmt = db.prepare(`
+ INSERT INTO download_history (user_id, analysis_type, format, filename, created_at)
+ VALUES (?, ?, ?, ?, datetime('now'))
+ `);
+ stmt.run(userId, analysisType, format, filename);
+ } catch (dbError) {
+ // 下载历史记录失败不影响文件下载
+ console.warn('记录下载历史失败:', dbError);
+ }
+
+ // 设置响应头
+ res.setHeader('Content-Type', contentType);
+ res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
+ res.setHeader('Content-Length', fileBuffer.length);
+ res.setHeader('Cache-Control', 'no-cache');
+
+ // 发送文件
+ res.send(fileBuffer);
+
+ } catch (error) {
+ console.error('下载API错误:', error);
+ res.status(500).json({
+ error: '服务器内部错误',
+ details: error.message
+ });
+ }
+});
+
+/**
+ * 获取用户下载历史
+ * GET /api/download/history
+ */
+router.get('/history', authenticate, async (req, res) => {
+ try {
+ const userId = req.user.id;
+ const { page = 1, limit = 20 } = req.query;
+
+ const db = dbManager.getDb();
+
+ // 获取总数
+ const countStmt = db.prepare('SELECT COUNT(*) as total FROM download_history WHERE user_id = ?');
+ const { total } = countStmt.get(userId);
+
+ // 获取分页数据
+ const offset = (page - 1) * limit;
+ const stmt = db.prepare(`
+ SELECT analysis_type, format, filename, created_at
+ FROM download_history
+ WHERE user_id = ?
+ ORDER BY created_at DESC
+ LIMIT ? OFFSET ?
+ `);
+
+ const downloads = stmt.all(userId, limit, offset);
+
+ res.json({
+ downloads,
+ pagination: {
+ page: parseInt(page),
+ limit: parseInt(limit),
+ total,
+ pages: Math.ceil(total / limit)
+ }
+ });
+
+ } catch (error) {
+ console.error('获取下载历史失败:', error);
+ res.status(500).json({
+ error: '获取下载历史失败',
+ details: error.message
+ });
+ }
+});
+
+/**
+ * 获取支持的格式和分析类型
+ * GET /api/download/formats
+ */
+router.get('/formats', (req, res) => {
+ res.json({
+ supportedFormats: [
+ {
+ format: 'markdown',
+ label: 'Markdown文档',
+ description: '结构化文本格式,便于编辑',
+ mimeType: 'text/markdown',
+ extension: 'md'
+ },
+ {
+ format: 'pdf',
+ label: 'PDF文档',
+ description: '专业格式,便于打印和分享',
+ mimeType: 'application/pdf',
+ extension: 'pdf'
+ },
+ {
+ format: 'png',
+ label: 'PNG图片',
+ description: '高清图片格式,便于保存',
+ mimeType: 'image/png',
+ extension: 'png'
+ }
+ ],
+ supportedAnalysisTypes: [
+ {
+ type: 'bazi',
+ label: '八字命理',
+ description: '基于传统八字学说的命理分析'
+ },
+ {
+ type: 'ziwei',
+ label: '紫微斗数',
+ description: '通过星曜排布分析命运走向'
+ },
+ {
+ type: 'yijing',
+ label: '易经占卜',
+ description: '运用梅花易数解读卦象含义'
+ }
+ ]
+ });
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/server/services/generators/markdownGenerator.cjs b/server/services/generators/markdownGenerator.cjs
new file mode 100644
index 0000000..feb2de8
--- /dev/null
+++ b/server/services/generators/markdownGenerator.cjs
@@ -0,0 +1,622 @@
+/**
+ * Markdown格式生成器
+ * 将分析结果转换为结构化的Markdown文档
+ */
+
+const generateMarkdown = async (analysisData, analysisType, userName) => {
+ try {
+ let markdown = '';
+
+ // 根据分析类型生成不同的Markdown内容
+ switch (analysisType) {
+ case 'bazi':
+ markdown = generateBaziMarkdown(analysisData, userName);
+ break;
+ case 'ziwei':
+ markdown = generateZiweiMarkdown(analysisData, userName);
+ break;
+ case 'yijing':
+ markdown = generateYijingMarkdown(analysisData, userName);
+ break;
+ default:
+ throw new Error(`不支持的分析类型: ${analysisType}`);
+ }
+
+ return Buffer.from(markdown, 'utf8');
+ } catch (error) {
+ console.error('生成Markdown失败:', error);
+ throw error;
+ }
+};
+
+/**
+ * 生成八字命理Markdown文档
+ */
+const generateBaziMarkdown = (analysisData, userName) => {
+ const timestamp = new Date().toLocaleString('zh-CN');
+
+ let markdown = `# 八字命理分析报告\n\n`;
+ markdown += `**姓名:** ${userName || '用户'}\n`;
+ markdown += `**生成时间:** ${timestamp}\n`;
+ markdown += `**分析类型:** 八字命理\n\n`;
+
+ markdown += `---\n\n`;
+
+ // 基本信息
+ if (analysisData.basic_info) {
+ markdown += `## 📋 基本信息\n\n`;
+
+ if (analysisData.basic_info.personal_data) {
+ const personal = analysisData.basic_info.personal_data;
+ markdown += `- **姓名:** ${personal.name || '未提供'}\n`;
+ markdown += `- **性别:** ${personal.gender === 'male' ? '男' : personal.gender === 'female' ? '女' : personal.gender || '未提供'}\n`;
+ markdown += `- **出生日期:** ${personal.birth_date || '未提供'}\n`;
+ markdown += `- **出生时间:** ${personal.birth_time || '未提供'}\n`;
+ if (personal.birth_place) {
+ markdown += `- **出生地点:** ${personal.birth_place}\n`;
+ }
+ }
+
+ // 八字信息
+ if (analysisData.basic_info.bazi_info) {
+ const bazi = analysisData.basic_info.bazi_info;
+ markdown += `\n### 🔮 八字信息\n\n`;
+ markdown += `| 柱位 | 天干 | 地支 | 纳音 |\n`;
+ markdown += `|------|------|------|------|\n`;
+ markdown += `| 年柱 | ${bazi.year?.split('')[0] || '-'} | ${bazi.year?.split('')[1] || '-'} | ${bazi.year_nayin || '-'} |\n`;
+ markdown += `| 月柱 | ${bazi.month?.split('')[0] || '-'} | ${bazi.month?.split('')[1] || '-'} | ${bazi.month_nayin || '-'} |\n`;
+ markdown += `| 日柱 | ${bazi.day?.split('')[0] || '-'} | ${bazi.day?.split('')[1] || '-'} | ${bazi.day_nayin || '-'} |\n`;
+ markdown += `| 时柱 | ${bazi.hour?.split('')[0] || '-'} | ${bazi.hour?.split('')[1] || '-'} | ${bazi.hour_nayin || '-'} |\n\n`;
+ }
+ }
+
+ // 五行分析
+ if (analysisData.wuxing_analysis) {
+ markdown += `## 🌟 五行分析\n\n`;
+
+ if (analysisData.wuxing_analysis.element_distribution) {
+ markdown += `### 五行分布\n\n`;
+ const elements = analysisData.wuxing_analysis.element_distribution;
+ const total = Object.values(elements).reduce((sum, count) => sum + (typeof count === 'number' ? count : 0), 0);
+
+ markdown += `| 五行 | 数量 | 占比 | 强度 |\n`;
+ markdown += `|------|------|------|------|\n`;
+
+ 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 ? '中' : '弱';
+ markdown += `| ${element} | ${numCount} | ${percentage}% | ${strength} |\n`;
+ });
+
+ markdown += `\n`;
+ }
+
+ if (analysisData.wuxing_analysis.balance_analysis) {
+ markdown += `### 五行平衡分析\n\n`;
+ markdown += `${analysisData.wuxing_analysis.balance_analysis}\n\n`;
+ }
+
+ if (analysisData.wuxing_analysis.suggestions) {
+ markdown += `### 调和建议\n\n`;
+ markdown += `${analysisData.wuxing_analysis.suggestions}\n\n`;
+ }
+ }
+
+ // 十神分析
+ if (analysisData.ten_gods_analysis) {
+ markdown += `## ⚡ 十神分析\n\n`;
+
+ if (analysisData.ten_gods_analysis.distribution) {
+ markdown += `### 十神分布\n\n`;
+ Object.entries(analysisData.ten_gods_analysis.distribution).forEach(([god, info]) => {
+ markdown += `#### ${god}\n`;
+ if (typeof info === 'object' && info.count !== undefined) {
+ markdown += `- **数量:** ${info.count}\n`;
+ if (info.description) {
+ markdown += `- **含义:** ${info.description}\n`;
+ }
+ } else {
+ markdown += `- **数量:** ${info}\n`;
+ }
+ markdown += `\n`;
+ });
+ }
+
+ if (analysisData.ten_gods_analysis.analysis) {
+ markdown += `### 十神综合分析\n\n`;
+ markdown += `${analysisData.ten_gods_analysis.analysis}\n\n`;
+ }
+ }
+
+ // 格局分析
+ if (analysisData.pattern_analysis) {
+ markdown += `## 🎯 格局分析\n\n`;
+
+ if (analysisData.pattern_analysis.main_pattern) {
+ markdown += `### 主要格局\n\n`;
+ markdown += `**格局类型:** ${analysisData.pattern_analysis.main_pattern}\n\n`;
+ }
+
+ if (analysisData.pattern_analysis.pattern_strength) {
+ const strength = analysisData.pattern_analysis.pattern_strength;
+ const strengthLabel = strength === 'strong' ? '强' : strength === 'moderate' ? '中等' : strength === 'fair' ? '一般' : '较弱';
+ markdown += `**格局强度:** ${strengthLabel}\n\n`;
+ }
+
+ if (analysisData.pattern_analysis.analysis) {
+ markdown += `### 格局详解\n\n`;
+ markdown += `${analysisData.pattern_analysis.analysis}\n\n`;
+ }
+ }
+
+ // 运势分析
+ if (analysisData.fortune_analysis) {
+ markdown += `## 🔮 运势分析\n\n`;
+
+ ['career', 'wealth', 'relationship', 'health'].forEach(aspect => {
+ if (analysisData.fortune_analysis[aspect]) {
+ const aspectNames = {
+ career: '事业运势',
+ wealth: '财运分析',
+ relationship: '感情运势',
+ health: '健康运势'
+ };
+
+ markdown += `### ${aspectNames[aspect]}\n\n`;
+ markdown += `${analysisData.fortune_analysis[aspect]}\n\n`;
+ }
+ });
+ }
+
+ // 人生指导
+ if (analysisData.life_guidance) {
+ markdown += `## 🌟 人生指导\n\n`;
+
+ if (analysisData.life_guidance.strengths) {
+ markdown += `### 优势特质\n\n`;
+ if (Array.isArray(analysisData.life_guidance.strengths)) {
+ analysisData.life_guidance.strengths.forEach(strength => {
+ markdown += `- ${strength}\n`;
+ });
+ } else {
+ markdown += `${analysisData.life_guidance.strengths}\n`;
+ }
+ markdown += `\n`;
+ }
+
+ if (analysisData.life_guidance.challenges) {
+ markdown += `### 需要注意\n\n`;
+ if (Array.isArray(analysisData.life_guidance.challenges)) {
+ analysisData.life_guidance.challenges.forEach(challenge => {
+ markdown += `- ${challenge}\n`;
+ });
+ } else {
+ markdown += `${analysisData.life_guidance.challenges}\n`;
+ }
+ markdown += `\n`;
+ }
+
+ if (analysisData.life_guidance.suggestions) {
+ markdown += `### 发展建议\n\n`;
+ if (Array.isArray(analysisData.life_guidance.suggestions)) {
+ analysisData.life_guidance.suggestions.forEach(suggestion => {
+ markdown += `- ${suggestion}\n`;
+ });
+ } else {
+ markdown += `${analysisData.life_guidance.suggestions}\n`;
+ }
+ markdown += `\n`;
+ }
+
+ if (analysisData.life_guidance.overall_summary) {
+ markdown += `### 综合总结\n\n`;
+ markdown += `${analysisData.life_guidance.overall_summary}\n\n`;
+ }
+ }
+
+ // 现代应用建议
+ if (analysisData.modern_applications) {
+ markdown += `## 💡 现代应用建议\n\n`;
+
+ Object.entries(analysisData.modern_applications).forEach(([key, value]) => {
+ const keyNames = {
+ lifestyle: '生活方式建议',
+ career_development: '职业发展建议',
+ relationship_advice: '人际关系建议',
+ health_maintenance: '健康养生建议',
+ financial_planning: '理财规划建议'
+ };
+
+ if (keyNames[key] && value) {
+ markdown += `### ${keyNames[key]}\n\n`;
+ markdown += `${value}\n\n`;
+ }
+ });
+ }
+
+ // 页脚
+ markdown += `---\n\n`;
+ markdown += `*本报告由神机阁AI命理分析平台生成*\n`;
+ markdown += `*生成时间:${timestamp}*\n`;
+ markdown += `*仅供参考,请理性对待*\n`;
+
+ return markdown;
+};
+
+/**
+ * 生成紫微斗数Markdown文档
+ */
+const generateZiweiMarkdown = (analysisData, userName) => {
+ const timestamp = new Date().toLocaleString('zh-CN');
+
+ let markdown = `# 紫微斗数分析报告\n\n`;
+ markdown += `**姓名:** ${userName || '用户'}\n`;
+ markdown += `**生成时间:** ${timestamp}\n`;
+ markdown += `**分析类型:** 紫微斗数\n\n`;
+
+ markdown += `---\n\n`;
+
+ // 基本信息
+ if (analysisData.basic_info) {
+ markdown += `## 📋 基本信息\n\n`;
+
+ if (analysisData.basic_info.personal_data) {
+ const personal = analysisData.basic_info.personal_data;
+ markdown += `- **姓名:** ${personal.name || '未提供'}\n`;
+ markdown += `- **性别:** ${personal.gender === 'male' ? '男' : personal.gender === 'female' ? '女' : personal.gender || '未提供'}\n`;
+ markdown += `- **出生日期:** ${personal.birth_date || '未提供'}\n`;
+ markdown += `- **出生时间:** ${personal.birth_time || '未提供'}\n`;
+ }
+
+ // 紫微基本信息
+ if (analysisData.basic_info.ziwei_info) {
+ const ziwei = analysisData.basic_info.ziwei_info;
+ markdown += `\n### 🌟 紫微基本信息\n\n`;
+ if (ziwei.ming_gong) {
+ markdown += `- **命宫:** ${ziwei.ming_gong}\n`;
+ }
+ if (ziwei.wuxing_ju) {
+ markdown += `- **五行局:** ${ziwei.wuxing_ju}\n`;
+ }
+ if (ziwei.main_stars) {
+ markdown += `- **主星:** ${Array.isArray(ziwei.main_stars) ? ziwei.main_stars.join('、') : ziwei.main_stars}\n`;
+ }
+ }
+ }
+
+ // 星曜分析
+ if (analysisData.star_analysis) {
+ markdown += `\n## ⭐ 星曜分析\n\n`;
+
+ if (analysisData.star_analysis.main_stars) {
+ markdown += `### 主星分析\n\n`;
+ if (Array.isArray(analysisData.star_analysis.main_stars)) {
+ analysisData.star_analysis.main_stars.forEach(star => {
+ if (typeof star === 'object') {
+ markdown += `#### ${star.name || star.star}\n`;
+ if (star.brightness) {
+ markdown += `- **亮度:** ${star.brightness}\n`;
+ }
+ if (star.influence) {
+ markdown += `- **影响:** ${star.influence}\n`;
+ }
+ if (star.description) {
+ markdown += `- **特质:** ${star.description}\n`;
+ }
+ markdown += `\n`;
+ }
+ });
+ } else {
+ markdown += `${analysisData.star_analysis.main_stars}\n\n`;
+ }
+ }
+
+ if (analysisData.star_analysis.auxiliary_stars) {
+ markdown += `### 辅星分析\n\n`;
+ markdown += `${analysisData.star_analysis.auxiliary_stars}\n\n`;
+ }
+ }
+
+ // 十二宫位分析
+ if (analysisData.palace_analysis) {
+ markdown += `## 🏛️ 十二宫位分析\n\n`;
+
+ const palaceNames = {
+ ming: '命宫',
+ xiong: '兄弟宫',
+ fu: '夫妻宫',
+ zi: '子女宫',
+ cai: '财帛宫',
+ ji: '疾厄宫',
+ qian: '迁移宫',
+ nu: '奴仆宫',
+ guan: '官禄宫',
+ tian: '田宅宫',
+ fu_de: '福德宫',
+ fu_mu: '父母宫'
+ };
+
+ Object.entries(analysisData.palace_analysis).forEach(([palace, analysis]) => {
+ const palaceName = palaceNames[palace] || palace;
+ markdown += `### ${palaceName}\n\n`;
+ if (typeof analysis === 'object') {
+ if (analysis.stars) {
+ markdown += `**星曜:** ${Array.isArray(analysis.stars) ? analysis.stars.join('、') : analysis.stars}\n`;
+ }
+ if (analysis.analysis) {
+ markdown += `**分析:** ${analysis.analysis}\n`;
+ }
+ if (analysis.fortune) {
+ markdown += `**运势:** ${analysis.fortune}\n`;
+ }
+ } else {
+ markdown += `${analysis}\n`;
+ }
+ markdown += `\n`;
+ });
+ }
+
+ // 四化分析
+ if (analysisData.sihua_analysis) {
+ markdown += `## 🔄 四化分析\n\n`;
+
+ const sihuaNames = {
+ lu: '化禄',
+ quan: '化权',
+ ke: '化科',
+ ji: '化忌'
+ };
+
+ Object.entries(analysisData.sihua_analysis).forEach(([sihua, analysis]) => {
+ const sihuaName = sihuaNames[sihua] || sihua;
+ markdown += `### ${sihuaName}\n\n`;
+ markdown += `${analysis}\n\n`;
+ });
+ }
+
+ // 大运分析
+ if (analysisData.major_periods) {
+ markdown += `## 📅 大运分析\n\n`;
+
+ if (Array.isArray(analysisData.major_periods)) {
+ analysisData.major_periods.forEach((period, index) => {
+ markdown += `### 第${index + 1}大运 (${period.age_range || period.years || '年龄段'})\n\n`;
+ if (period.main_star) {
+ markdown += `**主星:** ${period.main_star}\n`;
+ }
+ if (period.fortune) {
+ markdown += `**运势:** ${period.fortune}\n`;
+ }
+ if (period.analysis) {
+ markdown += `**分析:** ${period.analysis}\n`;
+ }
+ if (period.advice) {
+ markdown += `**建议:** ${period.advice}\n`;
+ }
+ markdown += `\n`;
+ });
+ }
+ }
+
+ // 综合分析
+ if (analysisData.comprehensive_analysis) {
+ markdown += `## 🎯 综合分析\n\n`;
+
+ ['personality', 'career', 'wealth', 'relationship', 'health'].forEach(aspect => {
+ if (analysisData.comprehensive_analysis[aspect]) {
+ const aspectNames = {
+ personality: '性格特质',
+ career: '事业发展',
+ wealth: '财运分析',
+ relationship: '感情婚姻',
+ health: '健康状况'
+ };
+
+ markdown += `### ${aspectNames[aspect]}\n\n`;
+ markdown += `${analysisData.comprehensive_analysis[aspect]}\n\n`;
+ }
+ });
+ }
+
+ // 页脚
+ markdown += `---\n\n`;
+ markdown += `*本报告由神机阁AI命理分析平台生成*\n`;
+ markdown += `*生成时间:${timestamp}*\n`;
+ markdown += `*仅供参考,请理性对待*\n`;
+
+ return markdown;
+};
+
+/**
+ * 生成易经占卜Markdown文档
+ */
+const generateYijingMarkdown = (analysisData, userName) => {
+ const timestamp = new Date().toLocaleString('zh-CN');
+
+ let markdown = `# 易经占卜分析报告\n\n`;
+ markdown += `**占卜者:** ${userName || '用户'}\n`;
+ markdown += `**生成时间:** ${timestamp}\n`;
+ markdown += `**分析类型:** 易经占卜\n\n`;
+
+ markdown += `---\n\n`;
+
+ // 占卜问题
+ if (analysisData.question_analysis) {
+ markdown += `## ❓ 占卜问题\n\n`;
+ if (analysisData.question_analysis.original_question) {
+ markdown += `**问题:** ${analysisData.question_analysis.original_question}\n\n`;
+ }
+ if (analysisData.question_analysis.question_type) {
+ markdown += `**问题类型:** ${analysisData.question_analysis.question_type}\n\n`;
+ }
+ if (analysisData.question_analysis.analysis_focus) {
+ markdown += `**分析重点:** ${analysisData.question_analysis.analysis_focus}\n\n`;
+ }
+ }
+
+ // 卦象信息
+ if (analysisData.hexagram_info) {
+ markdown += `## 🔮 卦象信息\n\n`;
+
+ if (analysisData.hexagram_info.main_hexagram) {
+ const main = analysisData.hexagram_info.main_hexagram;
+ markdown += `### 主卦\n\n`;
+ markdown += `**卦名:** ${main.name || '未知'}\n`;
+ markdown += `**卦象:** ${main.symbol || ''}\n`;
+ if (main.number) {
+ markdown += `**卦序:** 第${main.number}卦\n`;
+ }
+ if (main.element) {
+ markdown += `**五行:** ${main.element}\n`;
+ }
+ if (main.meaning) {
+ markdown += `**含义:** ${main.meaning}\n`;
+ }
+ markdown += `\n`;
+ }
+
+ if (analysisData.hexagram_info.changing_hexagram) {
+ const changing = analysisData.hexagram_info.changing_hexagram;
+ markdown += `### 变卦\n\n`;
+ markdown += `**卦名:** ${changing.name || '未知'}\n`;
+ markdown += `**卦象:** ${changing.symbol || ''}\n`;
+ if (changing.meaning) {
+ markdown += `**含义:** ${changing.meaning}\n`;
+ }
+ markdown += `\n`;
+ }
+ }
+
+ // 卦辞分析
+ if (analysisData.hexagram_analysis) {
+ markdown += `## 📜 卦辞分析\n\n`;
+
+ if (analysisData.hexagram_analysis.gua_ci) {
+ markdown += `### 卦辞\n\n`;
+ markdown += `> ${analysisData.hexagram_analysis.gua_ci}\n\n`;
+ }
+
+ if (analysisData.hexagram_analysis.gua_ci_interpretation) {
+ markdown += `### 卦辞解释\n\n`;
+ markdown += `${analysisData.hexagram_analysis.gua_ci_interpretation}\n\n`;
+ }
+
+ if (analysisData.hexagram_analysis.yao_ci) {
+ markdown += `### 爻辞分析\n\n`;
+ if (Array.isArray(analysisData.hexagram_analysis.yao_ci)) {
+ analysisData.hexagram_analysis.yao_ci.forEach((yao, index) => {
+ markdown += `#### ${yao.position || `第${index + 1}爻`}\n`;
+ if (yao.text) {
+ markdown += `**爻辞:** ${yao.text}\n`;
+ }
+ if (yao.interpretation) {
+ markdown += `**解释:** ${yao.interpretation}\n`;
+ }
+ markdown += `\n`;
+ });
+ }
+ }
+ }
+
+ // 象数分析
+ if (analysisData.numerology_analysis) {
+ markdown += `## 🔢 象数分析\n\n`;
+
+ if (analysisData.numerology_analysis.upper_trigram_number) {
+ markdown += `### 上卦数理\n\n`;
+ const upper = analysisData.numerology_analysis.upper_trigram_number;
+ markdown += `**数字:** ${upper.number || upper}\n`;
+ if (upper.meaning) {
+ markdown += `**含义:** ${upper.meaning}\n`;
+ }
+ if (upper.influence) {
+ markdown += `**影响:** ${upper.influence}\n`;
+ }
+ markdown += `\n`;
+ }
+
+ if (analysisData.numerology_analysis.lower_trigram_number) {
+ markdown += `### 下卦数理\n\n`;
+ const lower = analysisData.numerology_analysis.lower_trigram_number;
+ markdown += `**数字:** ${lower.number || lower}\n`;
+ if (lower.meaning) {
+ markdown += `**含义:** ${lower.meaning}\n`;
+ }
+ if (lower.influence) {
+ markdown += `**影响:** ${lower.influence}\n`;
+ }
+ markdown += `\n`;
+ }
+
+ if (analysisData.numerology_analysis.combined_energy) {
+ markdown += `### 组合能量\n\n`;
+ const combined = analysisData.numerology_analysis.combined_energy;
+ markdown += `**总数:** ${combined.total_number || combined.total || combined}\n`;
+ if (combined.interpretation) {
+ markdown += `**解释:** ${combined.interpretation}\n`;
+ }
+ if (combined.harmony) {
+ markdown += `**和谐度:** ${combined.harmony}\n`;
+ }
+ markdown += `\n`;
+ }
+ }
+
+ // 综合解读
+ if (analysisData.comprehensive_interpretation) {
+ markdown += `## 🎯 综合解读\n\n`;
+
+ if (analysisData.comprehensive_interpretation.current_situation) {
+ markdown += `### 当前状况\n\n`;
+ markdown += `${analysisData.comprehensive_interpretation.current_situation}\n\n`;
+ }
+
+ if (analysisData.comprehensive_interpretation.development_trend) {
+ markdown += `### 发展趋势\n\n`;
+ markdown += `${analysisData.comprehensive_interpretation.development_trend}\n\n`;
+ }
+
+ if (analysisData.comprehensive_interpretation.action_advice) {
+ markdown += `### 行动建议\n\n`;
+ if (Array.isArray(analysisData.comprehensive_interpretation.action_advice)) {
+ analysisData.comprehensive_interpretation.action_advice.forEach(advice => {
+ markdown += `- ${advice}\n`;
+ });
+ } else {
+ markdown += `${analysisData.comprehensive_interpretation.action_advice}\n`;
+ }
+ markdown += `\n`;
+ }
+
+ if (analysisData.comprehensive_interpretation.timing_guidance) {
+ markdown += `### 时机指导\n\n`;
+ markdown += `${analysisData.comprehensive_interpretation.timing_guidance}\n\n`;
+ }
+ }
+
+ // 注意事项
+ if (analysisData.precautions) {
+ markdown += `## ⚠️ 注意事项\n\n`;
+ if (Array.isArray(analysisData.precautions)) {
+ analysisData.precautions.forEach(precaution => {
+ markdown += `- ${precaution}\n`;
+ });
+ } else {
+ markdown += `${analysisData.precautions}\n`;
+ }
+ markdown += `\n`;
+ }
+
+ // 页脚
+ markdown += `---\n\n`;
+ markdown += `*本报告由神机阁AI命理分析平台生成*\n`;
+ markdown += `*生成时间:${timestamp}*\n`;
+ markdown += `*仅供参考,请理性对待*\n`;
+
+ return markdown;
+};
+
+module.exports = {
+ generateMarkdown
+};
\ No newline at end of file
diff --git a/server/services/generators/pdfGenerator.cjs b/server/services/generators/pdfGenerator.cjs
new file mode 100644
index 0000000..63ceafc
--- /dev/null
+++ b/server/services/generators/pdfGenerator.cjs
@@ -0,0 +1,866 @@
+/**
+ * PDF格式生成器
+ * 将分析结果转换为PDF文档
+ * 使用html-pdf库进行转换
+ */
+
+const generatePDF = async (analysisData, analysisType, userName) => {
+ try {
+ // 生成HTML内容
+ const htmlContent = generateHTML(analysisData, analysisType, userName);
+
+ // 由于html-pdf库需要额外安装,这里先返回HTML转PDF的占位符
+ // 在实际部署时需要安装 html-pdf 或 puppeteer
+
+ // 临时解决方案:返回HTML内容作为PDF(实际应该转换为PDF)
+ const Buffer = require('buffer').Buffer;
+ return Buffer.from(htmlContent, 'utf8');
+
+ // 正式实现应该是:
+ // const pdf = require('html-pdf');
+ // return new Promise((resolve, reject) => {
+ // pdf.create(htmlContent, {
+ // format: 'A4',
+ // border: {
+ // top: '0.5in',
+ // right: '0.5in',
+ // bottom: '0.5in',
+ // left: '0.5in'
+ // }
+ // }).toBuffer((err, buffer) => {
+ // if (err) reject(err);
+ // else resolve(buffer);
+ // });
+ // });
+
+ } catch (error) {
+ console.error('生成PDF失败:', error);
+ throw error;
+ }
+};
+
+/**
+ * 生成HTML内容
+ */
+const generateHTML = (analysisData, analysisType, userName) => {
+ const timestamp = new Date().toLocaleString('zh-CN');
+
+ let html = `
+
+
+
+
+
+ ${getAnalysisTypeLabel(analysisType)}分析报告
+
+
+
+
+
+
+
+ `;
+
+ // 根据分析类型生成不同的HTML内容
+ switch (analysisType) {
+ case 'bazi':
+ html += generateBaziHTML(analysisData);
+ break;
+ case 'ziwei':
+ html += generateZiweiHTML(analysisData);
+ break;
+ case 'yijing':
+ html += generateYijingHTML(analysisData);
+ break;
+ }
+
+ html += `
+
+
+
+
+
+
+ `;
+
+ 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 += `
+
+ | ${element} |
+ ${numCount} |
+ ${percentage}% |
+ ${strength} |
+
+ `;
+ });
+
+ html += `
+
+
+ `;
+ }
+
+ 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 '命理';
+ }
+};
+
+/**
+ * 获取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: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ 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-left: 4px solid #dc2626;
+ border-radius: 0 5px 5px 0;
+ }
+
+ .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: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ 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
+};
\ No newline at end of file
diff --git a/server/services/generators/pngGenerator.cjs b/server/services/generators/pngGenerator.cjs
new file mode 100644
index 0000000..fa71033
--- /dev/null
+++ b/server/services/generators/pngGenerator.cjs
@@ -0,0 +1,588 @@
+/**
+ * PNG图片生成器
+ * 将分析结果转换为PNG图片格式
+ * 使用canvas或html-to-image技术
+ */
+
+const generatePNG = async (analysisData, analysisType, userName) => {
+ try {
+ // 生成图片内容
+ const imageData = await generateImageData(analysisData, analysisType, userName);
+
+ // 由于canvas库需要额外安装,这里先返回占位符
+ // 在实际部署时需要安装 canvas 或 puppeteer
+
+ // 临时解决方案:返回SVG内容作为PNG(实际应该转换为PNG)
+ const Buffer = require('buffer').Buffer;
+ return Buffer.from(imageData, 'utf8');
+
+ // 正式实现应该是:
+ // const { createCanvas, loadImage } = require('canvas');
+ // const canvas = createCanvas(800, 1200);
+ // const ctx = canvas.getContext('2d');
+ //
+ // // 绘制内容到canvas
+ // drawContent(ctx, analysisData, analysisType, userName);
+ //
+ // return canvas.toBuffer('image/png');
+
+ } catch (error) {
+ console.error('生成PNG失败:', error);
+ throw error;
+ }
+};
+
+/**
+ * 生成图片数据(SVG格式)
+ */
+const generateImageData = async (analysisData, analysisType, userName) => {
+ const timestamp = new Date().toLocaleString('zh-CN');
+ const analysisTypeLabel = getAnalysisTypeLabel(analysisType);
+
+ // 生成SVG内容
+ let svg = `
+
+ `;
+
+ 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
+};
\ No newline at end of file
diff --git a/src/components/CompleteBaziAnalysis.tsx b/src/components/CompleteBaziAnalysis.tsx
index c493449..103f4b2 100644
--- a/src/components/CompleteBaziAnalysis.tsx
+++ b/src/components/CompleteBaziAnalysis.tsx
@@ -3,6 +3,7 @@ import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Responsi
import { Calendar, Star, BookOpen, Sparkles, User, BarChart3, Zap, TrendingUp, Loader2, Clock, Target, Heart, DollarSign, Activity } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from './ui/Card';
import { BackToTop } from './ui/BackToTop';
+import DownloadButton from './ui/DownloadButton';
import { localApi } from '../lib/localApi';
interface CompleteBaziAnalysisProps {
@@ -277,6 +278,16 @@ const CompleteBaziAnalysis: React.FC = ({ birthDate,
+ {/* 下载按钮 */}
+
+
+
+
{/* 标题和基本信息 */}
diff --git a/src/components/CompleteYijingAnalysis.tsx b/src/components/CompleteYijingAnalysis.tsx
index c8066ab..03b0b00 100644
--- a/src/components/CompleteYijingAnalysis.tsx
+++ b/src/components/CompleteYijingAnalysis.tsx
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Calendar, Star, BookOpen, Sparkles, User, BarChart3, Zap, TrendingUp, Loader2, Clock, Target, Heart, DollarSign, Activity, Crown, Compass, Moon, Sun, Hexagon, Layers, Eye, Shuffle } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from './ui/Card';
import { BackToTop } from './ui/BackToTop';
+import DownloadButton from './ui/DownloadButton';
import { localApi } from '../lib/localApi';
interface CompleteYijingAnalysisProps {
@@ -721,6 +722,16 @@ const CompleteYijingAnalysis: React.FC = ({
+ {/* 下载按钮 */}
+
+
+
+
{/* 免责声明 */}
diff --git a/src/components/CompleteZiweiAnalysis.tsx b/src/components/CompleteZiweiAnalysis.tsx
index b95cbae..8e62bef 100644
--- a/src/components/CompleteZiweiAnalysis.tsx
+++ b/src/components/CompleteZiweiAnalysis.tsx
@@ -5,6 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from './ui/Card';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from './ui/ChineseCard';
import { ChineseLoading } from './ui/ChineseLoading';
import { BackToTop } from './ui/BackToTop';
+import DownloadButton from './ui/DownloadButton';
import { localApi } from '../lib/localApi';
import { cn } from '../lib/utils';
@@ -580,6 +581,16 @@ const CompleteZiweiAnalysis: React.FC = ({ birthDate
+ {/* 下载按钮 */}
+
+
+
+
{/* 标题和基本信息 */}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index e0b128b..f1f1c5c 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -1,7 +1,7 @@
import React, { ReactNode, useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
-import { Sparkles, User, History, LogOut, Home, Menu, X } from 'lucide-react';
+import { Sparkles, User, History, LogOut, Home, Menu, X, Github } from 'lucide-react';
import { ChineseButton } from './ui/ChineseButton';
import { toast } from 'sonner';
import { cn } from '../lib/utils';
@@ -89,6 +89,18 @@ const Layout: React.FC = ({ children }) => {
);
})}
+ {/* GitHub链接 */}
+
+
+ GitHub
+
+
{user ? (
= ({ children }) => {
);
})}
+ {/* 移动端GitHub链接 */}
+
+
+ GitHub
+
+
{user ? (
Promise;
+ className?: string;
+ disabled?: boolean;
+}
+
+const DownloadButton: React.FC = ({
+ analysisData,
+ analysisType,
+ userName,
+ onDownload,
+ className,
+ disabled = false
+}) => {
+ const [isDownloading, setIsDownloading] = useState(false);
+ const [downloadingFormat, setDownloadingFormat] = useState(null);
+ const [showDropdown, setShowDropdown] = useState(false);
+
+ const formatOptions = [
+ {
+ format: 'markdown' as DownloadFormat,
+ label: 'Markdown文档',
+ icon: FileText,
+ description: '结构化文本格式,便于编辑',
+ color: 'text-blue-600',
+ bgColor: 'bg-blue-50 hover:bg-blue-100'
+ },
+ {
+ format: 'pdf' as DownloadFormat,
+ label: 'PDF文档',
+ icon: File,
+ description: '专业格式,便于打印和分享',
+ color: 'text-red-600',
+ bgColor: 'bg-red-50 hover:bg-red-100'
+ },
+ {
+ format: 'png' as DownloadFormat,
+ label: 'PNG图片',
+ icon: FileImage,
+ description: '高清图片格式,便于保存',
+ color: 'text-green-600',
+ bgColor: 'bg-green-50 hover:bg-green-100'
+ }
+ ];
+
+ const handleDownload = async (format: DownloadFormat) => {
+ if (disabled || isDownloading) return;
+
+ try {
+ setIsDownloading(true);
+ setDownloadingFormat(format);
+ setShowDropdown(false);
+
+ if (onDownload) {
+ await onDownload(format);
+ } else {
+ // 默认下载逻辑
+ await defaultDownload(format);
+ }
+ } catch (error) {
+ console.error('下载失败:', error);
+ // 这里可以添加错误提示
+ } finally {
+ setIsDownloading(false);
+ setDownloadingFormat(null);
+ }
+ };
+
+ const defaultDownload = async (format: DownloadFormat) => {
+ try {
+ // 获取认证token
+ const token = localStorage.getItem('auth_token');
+ if (!token) {
+ throw new Error('请先登录');
+ }
+
+ // 获取正确的API基础URL
+ const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ||
+ (import.meta.env.DEV ? 'http://localhost:3001/api' :
+ (window.location.hostname.includes('koyeb.app') ? `${window.location.origin}/api` : `${window.location.origin}/api`));
+
+ // 调用后端下载API
+ const response = await fetch(`${API_BASE_URL}/download`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`
+ },
+ body: JSON.stringify({
+ analysisData,
+ analysisType,
+ format,
+ userName
+ })
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}));
+ throw new Error(errorData.error || `下载失败 (${response.status})`);
+ }
+
+ // 获取文件名(从响应头或生成默认名称)
+ const contentDisposition = response.headers.get('Content-Disposition');
+ let filename = `${getAnalysisTypeLabel()}_${userName || 'user'}_${new Date().toISOString().slice(0, 10)}.${format === 'markdown' ? 'md' : format}`;
+
+ if (contentDisposition) {
+ const filenameMatch = contentDisposition.match(/filename[^;=\n]*=(['"]?)([^'"\n]*?)\1/);
+ if (filenameMatch && filenameMatch[2]) {
+ filename = decodeURIComponent(filenameMatch[2]);
+ }
+ }
+
+ // 创建blob并下载
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.style.display = 'none';
+ document.body.appendChild(a);
+ a.click();
+
+ // 清理
+ setTimeout(() => {
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ }, 100);
+
+ // 显示成功提示
+ if (typeof window !== 'undefined' && (window as any).toast) {
+ (window as any).toast.success(`${format.toUpperCase()}文件下载成功`);
+ }
+
+ } catch (error) {
+ console.error('下载失败:', error);
+
+ // 显示错误提示
+ if (typeof window !== 'undefined' && (window as any).toast) {
+ (window as any).toast.error(error instanceof Error ? error.message : '下载失败,请重试');
+ }
+
+ throw error;
+ }
+ };
+
+ const getAnalysisTypeLabel = () => {
+ switch (analysisType) {
+ case 'bazi': return '八字命理';
+ case 'ziwei': return '紫微斗数';
+ case 'yijing': return '易经占卜';
+ default: return '命理';
+ }
+ };
+
+ const getFormatLabel = (format: DownloadFormat) => {
+ switch (format) {
+ case 'markdown': return 'Markdown';
+ case 'pdf': return 'PDF';
+ case 'png': return 'PNG';
+ default: return format.toUpperCase();
+ }
+ };
+
+ return (
+
+ {/* 主下载按钮 */}
+
+ setShowDropdown(!showDropdown)}
+ disabled={disabled || isDownloading}
+ variant="secondary"
+ className="flex items-center space-x-2 bg-gradient-to-r from-yellow-500 to-yellow-600 hover:from-yellow-600 hover:to-yellow-700 text-white border-0 shadow-lg"
+ >
+ {isDownloading ? (
+
+ ) : (
+
+ )}
+
+ {isDownloading ? `正在生成${getFormatLabel(downloadingFormat!)}...` : '下载分析结果'}
+
+
+
+
+
+ {/* 下拉菜单 */}
+ {showDropdown && (
+
+
+
选择下载格式
+
{getAnalysisTypeLabel()}分析结果
+
+
+
+ {formatOptions.map((option) => {
+ const Icon = option.icon;
+ const isCurrentlyDownloading = isDownloading && downloadingFormat === option.format;
+
+ return (
+
+ );
+ })}
+
+
+
+
+ 💡 提示:PDF和PNG格式包含完整的视觉设计,Markdown格式便于编辑
+
+
+
+ )}
+
+ {/* 点击外部关闭下拉菜单 */}
+ {showDropdown && (
+
setShowDropdown(false)}
+ />
+ )}
+
+ );
+};
+
+export default DownloadButton;
\ No newline at end of file
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
index 80b56c6..418dd57 100644
--- a/src/pages/HomePage.tsx
+++ b/src/pages/HomePage.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { Link } from 'react-router-dom';
-import { Sparkles, Star, Compass, Heart, BarChart3, BookOpen, Shield, Zap, Users, Award, Brain, TrendingUp } from 'lucide-react';
+import { Sparkles, Star, Compass, Heart, BarChart3, BookOpen, Shield, Zap, Users, Award, Brain, TrendingUp, Github } from 'lucide-react';
import { ChineseButton } from '../components/ui/ChineseButton';
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
import { useAuth } from '../contexts/AuthContext';
@@ -333,6 +333,24 @@ const HomePage: React.FC = () => {
)}
+
+ {/* GitHub链接 */}
+