mirror of
https://github.com/patdelphi/suanming.git
synced 2026-02-27 21:23:12 +08:00
feat: 完整实现奇门遁甲功能并优化显示效果
主要功能实现: - 新增奇门遁甲分析完整功能模块 - 实现奇门盘可视化展示 - 添加用神分析、格局识别、预测结果等核心功能 - 集成AI解读和PDF导出功能 - 扩展历史记录支持奇门遁甲类型 显示优化: - 修复时机评估[object Object]显示问题 - 优化时机评估显示为简洁格式 - 完善英文字段中文化映射 - 移除重复的成功概率显示 - 统一数值显示格式(小数转整数) 技术改进: - 扩展类型定义支持奇门遁甲 - 完善API接口和路由 - 优化错误处理和用户体验 - 统一前后端字段映射机制
This commit is contained in:
@@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS user_profiles (
|
||||
CREATE TABLE IF NOT EXISTS numerology_readings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
reading_type TEXT NOT NULL CHECK (reading_type IN ('bazi', 'ziwei', 'yijing', 'wuxing')),
|
||||
reading_type TEXT NOT NULL CHECK (reading_type IN ('bazi', 'ziwei', 'yijing', 'wuxing', 'qimen')),
|
||||
name TEXT,
|
||||
birth_date TEXT,
|
||||
birth_time TEXT,
|
||||
|
||||
@@ -11,6 +11,7 @@ const historyRoutes = require('./routes/history.cjs');
|
||||
const profileRoutes = require('./routes/profile.cjs');
|
||||
const downloadRoutes = require('./routes/download.cjs');
|
||||
const aiInterpretationRoutes = require('./routes/aiInterpretation.cjs');
|
||||
const qimenRoutes = require('./routes/qimen.cjs');
|
||||
|
||||
// 导入中间件
|
||||
const { errorHandler } = require('./middleware/errorHandler.cjs');
|
||||
@@ -129,6 +130,7 @@ app.use('/api/history', historyRoutes);
|
||||
app.use('/api/profile', profileRoutes);
|
||||
app.use('/api/download', downloadRoutes);
|
||||
app.use('/api/ai-interpretation', aiInterpretationRoutes);
|
||||
app.use('/api/qimen', qimenRoutes);
|
||||
|
||||
// 静态文件服务 (用于生产环境)
|
||||
// 强制在 Koyeb 部署时启用静态文件服务
|
||||
|
||||
@@ -216,7 +216,7 @@ router.post('/save-history', authenticate, asyncHandler(async (req, res) => {
|
||||
}
|
||||
|
||||
// 验证分析类型
|
||||
const validTypes = ['bazi', 'ziwei', 'yijing'];
|
||||
const validTypes = ['bazi', 'ziwei', 'yijing', 'qimen'];
|
||||
if (!validTypes.includes(analysis_type)) {
|
||||
throw new AppError('无效的分析类型', 400, 'INVALID_ANALYSIS_TYPE');
|
||||
}
|
||||
@@ -236,6 +236,15 @@ router.post('/save-history', authenticate, asyncHandler(async (req, res) => {
|
||||
birth_time = null;
|
||||
birth_place = null;
|
||||
gender = null;
|
||||
} else if (analysis_type === 'qimen') {
|
||||
// 奇门遁甲:获取用户档案信息
|
||||
const getUserProfile = db.prepare('SELECT full_name FROM user_profiles WHERE user_id = ?');
|
||||
const userProfile = getUserProfile.get(req.user.id);
|
||||
name = userProfile?.full_name || '奇门遁甲用户';
|
||||
birth_date = input_data?.birth_date || null;
|
||||
birth_time = input_data?.birth_time || null;
|
||||
birth_place = null;
|
||||
gender = null;
|
||||
} else {
|
||||
// 八字和紫微:从输入数据中获取
|
||||
name = input_data?.name || '用户';
|
||||
|
||||
@@ -35,7 +35,7 @@ router.post('/', authenticate, async (req, res) => {
|
||||
}
|
||||
|
||||
// 验证分析类型
|
||||
const supportedAnalysisTypes = ['bazi', 'ziwei', 'yijing'];
|
||||
const supportedAnalysisTypes = ['bazi', 'ziwei', 'yijing', 'qimen'];
|
||||
if (!supportedAnalysisTypes.includes(analysisType)) {
|
||||
return res.status(400).json({
|
||||
error: '不支持的分析类型',
|
||||
|
||||
@@ -8,6 +8,72 @@ const logger = require('../middleware/logger.cjs');
|
||||
|
||||
const router = express.Router();
|
||||
const qimenAnalyzer = new QimenAnalyzer();
|
||||
const { authenticate } = require('../middleware/auth.cjs');
|
||||
const { getDB } = require('../database/index.cjs');
|
||||
const { AppError, asyncHandler } = require('../middleware/errorHandler.cjs');
|
||||
|
||||
/**
|
||||
* @route POST /api/qimen/analyze
|
||||
* @desc 奇门遁甲完整分析
|
||||
* @access Private
|
||||
*/
|
||||
router.post('/analyze', asyncHandler(async (req, res) => {
|
||||
try {
|
||||
const { question, birth_date, birth_time, user_timezone, local_time, user_id } = req.body;
|
||||
const userId = user_id || 1; // 测试用户ID
|
||||
|
||||
// 输入验证
|
||||
if (!question || typeof question !== 'string' || question.trim().length === 0) {
|
||||
throw new AppError('缺少必要参数:占卜问题不能为空', 400, 'MISSING_QUESTION');
|
||||
}
|
||||
|
||||
if (!birth_date || typeof birth_date !== 'string') {
|
||||
throw new AppError('缺少必要参数:出生日期', 400, 'MISSING_BIRTH_DATE');
|
||||
}
|
||||
|
||||
if (!birth_time || typeof birth_time !== 'string') {
|
||||
throw new AppError('缺少必要参数:出生时间', 400, 'MISSING_BIRTH_TIME');
|
||||
}
|
||||
|
||||
// 构建分析数据
|
||||
const analysisData = {
|
||||
question: question.trim(),
|
||||
birth_date,
|
||||
birth_time,
|
||||
user_timezone: user_timezone || 'Asia/Shanghai',
|
||||
local_time: local_time || new Date().toISOString()
|
||||
};
|
||||
|
||||
// 执行奇门遁甲分析
|
||||
const analysisResult = qimenAnalyzer.performQimenAnalysis(analysisData);
|
||||
|
||||
// 保存到历史记录
|
||||
const db = getDB();
|
||||
const insertReading = db.prepare(`
|
||||
INSERT INTO numerology_readings (
|
||||
user_id, reading_type, input_data, analysis, created_at
|
||||
) VALUES (?, ?, ?, ?, datetime('now'))
|
||||
`);
|
||||
|
||||
const result = insertReading.run(
|
||||
userId,
|
||||
'qimen',
|
||||
JSON.stringify(analysisData),
|
||||
JSON.stringify(analysisResult)
|
||||
);
|
||||
|
||||
res.json({
|
||||
data: {
|
||||
record_id: result.lastInsertRowid,
|
||||
analysis: analysisResult
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('奇门遁甲分析错误:', error);
|
||||
throw new AppError(`奇门遁甲分析过程中发生错误: ${error.message}`, 500, 'QIMEN_ANALYSIS_ERROR');
|
||||
}
|
||||
}));
|
||||
|
||||
/**
|
||||
* @route POST /api/qimen/calculate
|
||||
@@ -536,7 +602,7 @@ router.post('/batch-calculate', async (req, res) => {
|
||||
|
||||
// 错误处理中间件
|
||||
router.use((error, req, res, next) => {
|
||||
logger.error('奇门API错误', error);
|
||||
console.error('奇门API错误:', error);
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
|
||||
@@ -18,6 +18,9 @@ const generateMarkdown = async (analysisData, analysisType, userName) => {
|
||||
case 'yijing':
|
||||
markdown = generateYijingMarkdown(analysisData, userName);
|
||||
break;
|
||||
case 'qimen':
|
||||
markdown = generateQimenMarkdown(analysisData, userName);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`不支持的分析类型: ${analysisType}`);
|
||||
}
|
||||
@@ -2291,6 +2294,458 @@ const generateYijingMarkdown = (analysisData, userName) => {
|
||||
return markdown;
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成奇门遁甲Markdown文档
|
||||
*/
|
||||
const generateQimenMarkdown = (analysisData, userName) => {
|
||||
const timestamp = new Date().toLocaleString('zh-CN');
|
||||
|
||||
// 字段名称中文映射
|
||||
const fieldNameMap = {
|
||||
'question': '问题',
|
||||
'method': '起局方法',
|
||||
'divination_time': '起局时间',
|
||||
'jieqi': '节气',
|
||||
'yuan': '元',
|
||||
'jushu': '局数',
|
||||
'yindun': '阴阳遁',
|
||||
'ganzhi': '干支',
|
||||
'year': '年柱',
|
||||
'month': '月柱',
|
||||
'day': '日柱',
|
||||
'hour': '时柱',
|
||||
'gan': '干',
|
||||
'zhi': '支',
|
||||
'zhifu': '值符',
|
||||
'zhishi': '值使',
|
||||
'star': '九星',
|
||||
'door': '八门',
|
||||
'god': '八神',
|
||||
'primary': '主用神',
|
||||
'secondary': '次用神',
|
||||
'overall': '综合评估',
|
||||
'favorability': '有利度',
|
||||
'strength': '力量强度',
|
||||
'timing': '时机评估',
|
||||
'recommendation': '建议',
|
||||
'element': '天干',
|
||||
'position': '宫位',
|
||||
'palaceName': '宫位名称',
|
||||
'wangshui': '旺衰',
|
||||
'wangshuiScore': '旺衰评分',
|
||||
'palaceRelation': '宫位关系',
|
||||
'palaceHarmony': '宫位和谐度',
|
||||
'seasonalInfluence': '季节影响',
|
||||
'seasonalScore': '季节评分',
|
||||
'totalScore': '综合评分',
|
||||
'status': '状态',
|
||||
'description': '详细描述',
|
||||
'name': '名称',
|
||||
'type': '类型',
|
||||
'level': '等级',
|
||||
'influence': '影响',
|
||||
'probability': '成功概率',
|
||||
'analysis': '详细分析',
|
||||
'key_factors': '关键因素',
|
||||
'timing_advice': '时机建议',
|
||||
'action_suggestions': '行动建议',
|
||||
'precautions': '注意事项',
|
||||
'wuxing_analysis': '五行分析',
|
||||
'timing_analysis': '时机分析',
|
||||
'zhifuAnalysis': '值符分析',
|
||||
'zhishiAnalysis': '值使分析',
|
||||
'hourAnalysis': '时辰分析',
|
||||
'seasonAnalysis': '节气分析',
|
||||
'yindunAnalysis': '阴阳遁分析',
|
||||
'score': '评分',
|
||||
'factors': '影响因素',
|
||||
// 财运相关字段
|
||||
'profit': '利润',
|
||||
'investment': '投资',
|
||||
'wealth': '财富',
|
||||
'money': '金钱',
|
||||
'finance': '财务',
|
||||
'business': '生意',
|
||||
'career': '事业',
|
||||
'work': '工作',
|
||||
'job': '职业',
|
||||
'success': '成功',
|
||||
'failure': '失败',
|
||||
'opportunity': '机会',
|
||||
'risk': '风险',
|
||||
'challenge': '挑战',
|
||||
'advantage': '优势',
|
||||
'disadvantage': '劣势',
|
||||
// 用神分析字段
|
||||
'matter': '事情',
|
||||
'result': '结果',
|
||||
'self': '自身',
|
||||
'opponent': '对手',
|
||||
'helper': '帮助者',
|
||||
'obstacle': '阻碍',
|
||||
// 五行分析字段
|
||||
'dominant': '主导五行',
|
||||
'balance': '平衡状态',
|
||||
'suggestions': '建议',
|
||||
'notes': '备注',
|
||||
'season': '季节',
|
||||
// 时机分析字段
|
||||
'favorable': '有利',
|
||||
'unfavorable': '不利',
|
||||
'neutral': '中性',
|
||||
// 其他常见字段
|
||||
'true': '是',
|
||||
'false': '否',
|
||||
'unknown': '未知',
|
||||
'good': '好',
|
||||
'bad': '差',
|
||||
'excellent': '极佳',
|
||||
'poor': '很差',
|
||||
'average': '一般',
|
||||
// 感情相关字段
|
||||
'spouse': '配偶',
|
||||
'relationship': '感情关系',
|
||||
'matchmaker': '媒人',
|
||||
'marriage_palace': '婚姻宫',
|
||||
'relationship_door': '感情门',
|
||||
'love': '爱情',
|
||||
'marriage': '婚姻',
|
||||
'partner': '伴侣',
|
||||
'emotion': '情感',
|
||||
'affection': '感情',
|
||||
'romance': '浪漫',
|
||||
'compatibility': '相配度',
|
||||
'harmony': '和谐度',
|
||||
'conflict': '冲突',
|
||||
'separation': '分离',
|
||||
'reunion': '复合',
|
||||
'commitment': '承诺',
|
||||
'trust': '信任',
|
||||
'loyalty': '忠诚'
|
||||
};
|
||||
|
||||
// 获取中文字段名
|
||||
const getChineseFieldName = (fieldName) => {
|
||||
return fieldNameMap[fieldName] || fieldName;
|
||||
};
|
||||
|
||||
let markdown = `# 奇门遁甲分析报告\n\n`;
|
||||
// 从userName中提取实际姓名,去掉"奇门_"前缀
|
||||
const actualUserName = userName ? userName.replace(/^奇门_/, '') : '用户';
|
||||
markdown += `**占卜者:** ${actualUserName}\n`;
|
||||
markdown += `**生成时间:** ${timestamp}\n`;
|
||||
markdown += `**分析类型:** 奇门遁甲\n\n`;
|
||||
|
||||
markdown += `---\n\n`;
|
||||
|
||||
// 占卜问题
|
||||
if (analysisData.basic_info?.divination_data) {
|
||||
markdown += `## ❓ 占卜问题\n\n`;
|
||||
const divination = analysisData.basic_info.divination_data;
|
||||
if (divination.question) {
|
||||
markdown += `**问题:** ${divination.question}\n\n`;
|
||||
}
|
||||
if (divination.method) {
|
||||
markdown += `**起局方法:** ${divination.method}\n\n`;
|
||||
}
|
||||
if (divination.divination_time) {
|
||||
const time = new Date(divination.divination_time).toLocaleString('zh-CN');
|
||||
markdown += `**起局时间:** ${time}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// 时空信息
|
||||
if (analysisData.basic_info?.qimen_info) {
|
||||
markdown += `## ⏰ 时空信息\n\n`;
|
||||
const qimenInfo = analysisData.basic_info.qimen_info;
|
||||
|
||||
if (qimenInfo.jieqi) {
|
||||
markdown += `**节气:** ${qimenInfo.jieqi}\n`;
|
||||
}
|
||||
if (qimenInfo.yuan) {
|
||||
markdown += `**元:** ${qimenInfo.yuan}\n`;
|
||||
}
|
||||
if (qimenInfo.jushu) {
|
||||
markdown += `**局数:** ${qimenInfo.jushu}局\n`;
|
||||
}
|
||||
if (qimenInfo.yindun !== undefined) {
|
||||
markdown += `**阴阳遁:** ${qimenInfo.yindun ? '阴遁' : '阳遁'}\n`;
|
||||
}
|
||||
|
||||
// 干支四柱
|
||||
if (qimenInfo.ganzhi) {
|
||||
markdown += `\n### 🎋 干支四柱\n\n`;
|
||||
const ganzhi = qimenInfo.ganzhi;
|
||||
if (ganzhi.year) markdown += `- **年柱:** ${ganzhi.year.gan}${ganzhi.year.zhi}\n`;
|
||||
if (ganzhi.month) markdown += `- **月柱:** ${ganzhi.month.gan}${ganzhi.month.zhi}\n`;
|
||||
if (ganzhi.day) markdown += `- **日柱:** ${ganzhi.day.gan}${ganzhi.day.zhi}\n`;
|
||||
if (ganzhi.hour) markdown += `- **时柱:** ${ganzhi.hour.gan}${ganzhi.hour.zhi}\n`;
|
||||
}
|
||||
|
||||
// 值符值使
|
||||
if (qimenInfo.zhifu || qimenInfo.zhishi) {
|
||||
markdown += `\n### ⭐ 值符值使\n\n`;
|
||||
if (qimenInfo.zhifu) markdown += `- **值符:** ${qimenInfo.zhifu}\n`;
|
||||
if (qimenInfo.zhishi) markdown += `- **值使:** ${qimenInfo.zhishi}\n`;
|
||||
}
|
||||
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
// 奇门盘布局
|
||||
if (analysisData.detailed_analysis?.qimen_pan) {
|
||||
markdown += `## 🔮 奇门盘布局\n\n`;
|
||||
const qimenPan = analysisData.detailed_analysis.qimen_pan;
|
||||
|
||||
if (qimenPan.dipan && Array.isArray(qimenPan.dipan)) {
|
||||
const palaceNames = ['坎一宫', '坤二宫', '震三宫', '巽四宫', '中五宫', '乾六宫', '兑七宫', '艮八宫', '离九宫'];
|
||||
|
||||
markdown += `| 宫位 | 九星 | 八门 | 八神 |\n`;
|
||||
markdown += `|------|------|------|------|\n`;
|
||||
|
||||
qimenPan.dipan.forEach((palace, index) => {
|
||||
if (palace && palaceNames[index]) {
|
||||
const star = palace.star || '-';
|
||||
const door = palace.door || '-';
|
||||
const god = palace.god || '-';
|
||||
markdown += `| ${palaceNames[index]} | ${star} | ${door} | ${god} |\n`;
|
||||
}
|
||||
});
|
||||
|
||||
markdown += `\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// 用神分析
|
||||
if (analysisData.detailed_analysis?.yongshen_analysis) {
|
||||
markdown += `## 🎯 用神分析\n\n`;
|
||||
const yongShenAnalysis = analysisData.detailed_analysis.yongshen_analysis;
|
||||
|
||||
// 主用神
|
||||
if (yongShenAnalysis.primary) {
|
||||
markdown += `### 主用神\n\n`;
|
||||
Object.entries(yongShenAnalysis.primary).forEach(([key, value]) => {
|
||||
const chineseKey = getChineseFieldName(key);
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
markdown += `**${chineseKey}:**\n`;
|
||||
Object.entries(value).forEach(([subKey, subValue]) => {
|
||||
const chineseSubKey = getChineseFieldName(subKey);
|
||||
markdown += `- ${chineseSubKey}:${subValue}\n`;
|
||||
});
|
||||
} else {
|
||||
markdown += `- **${chineseKey}:** ${value}\n`;
|
||||
}
|
||||
});
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
// 次用神
|
||||
if (yongShenAnalysis.secondary) {
|
||||
markdown += `### 次用神\n\n`;
|
||||
Object.entries(yongShenAnalysis.secondary).forEach(([key, value]) => {
|
||||
const chineseKey = getChineseFieldName(key);
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
markdown += `**${chineseKey}:**\n`;
|
||||
Object.entries(value).forEach(([subKey, subValue]) => {
|
||||
const chineseSubKey = getChineseFieldName(subKey);
|
||||
markdown += `- ${chineseSubKey}:${subValue}\n`;
|
||||
});
|
||||
} else {
|
||||
markdown += `- **${chineseKey}:** ${value}\n`;
|
||||
}
|
||||
});
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
// 综合评估
|
||||
if (yongShenAnalysis.overall) {
|
||||
markdown += `### 用神综合评估\n\n`;
|
||||
if (typeof yongShenAnalysis.overall === 'object') {
|
||||
Object.entries(yongShenAnalysis.overall).forEach(([key, value]) => {
|
||||
const chineseKey = getChineseFieldName(key);
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
markdown += `- **${chineseKey}:** ${value.join('、')}\n`;
|
||||
} else {
|
||||
const subEntries = Object.entries(value).map(([subK, subV]) => {
|
||||
const chineseSubKey = getChineseFieldName(subK);
|
||||
return `${chineseSubKey}:${subV}`;
|
||||
}).join(';');
|
||||
markdown += `- **${chineseKey}:** ${subEntries}\n`;
|
||||
}
|
||||
} else {
|
||||
markdown += `- **${chineseKey}:** ${value}\n`;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
markdown += `${yongShenAnalysis.overall}\n`;
|
||||
}
|
||||
markdown += `\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// 格局识别
|
||||
if (analysisData.detailed_analysis?.pattern_analysis && Array.isArray(analysisData.detailed_analysis.pattern_analysis)) {
|
||||
markdown += `## ⭐ 格局识别\n\n`;
|
||||
|
||||
const patterns = analysisData.detailed_analysis.pattern_analysis;
|
||||
const auspiciousPatterns = patterns.filter(p => p.type === 'auspicious');
|
||||
const inauspiciousPatterns = patterns.filter(p => p.type === 'inauspicious');
|
||||
const neutralPatterns = patterns.filter(p => p.type === 'neutral');
|
||||
|
||||
if (auspiciousPatterns.length > 0) {
|
||||
markdown += `### 🌟 吉利格局\n\n`;
|
||||
auspiciousPatterns.forEach(pattern => {
|
||||
markdown += `**${pattern.name}** (${pattern.level || '吉'})\n`;
|
||||
if (pattern.description) markdown += `${pattern.description}\n`;
|
||||
if (pattern.influence) markdown += `**影响:** ${pattern.influence}\n`;
|
||||
markdown += `\n`;
|
||||
});
|
||||
}
|
||||
|
||||
if (neutralPatterns.length > 0) {
|
||||
markdown += `### ⚖️ 中性格局\n\n`;
|
||||
neutralPatterns.forEach(pattern => {
|
||||
markdown += `**${pattern.name}** (${pattern.level || '中'})\n`;
|
||||
if (pattern.description) markdown += `${pattern.description}\n`;
|
||||
if (pattern.influence) markdown += `**影响:** ${pattern.influence}\n`;
|
||||
markdown += `\n`;
|
||||
});
|
||||
}
|
||||
|
||||
if (inauspiciousPatterns.length > 0) {
|
||||
markdown += `### ⚠️ 不利格局\n\n`;
|
||||
inauspiciousPatterns.forEach(pattern => {
|
||||
markdown += `**${pattern.name}** (${pattern.level || '凶'})\n`;
|
||||
if (pattern.description) markdown += `${pattern.description}\n`;
|
||||
if (pattern.influence) markdown += `**影响:** ${pattern.influence}\n`;
|
||||
markdown += `\n`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 预测结果
|
||||
if (analysisData.prediction_result) {
|
||||
markdown += `## 🔮 预测结果\n\n`;
|
||||
const prediction = analysisData.prediction_result;
|
||||
|
||||
if (prediction.probability !== undefined) {
|
||||
markdown += `### 成功概率\n\n`;
|
||||
markdown += `**概率:** ${prediction.probability}%\n\n`;
|
||||
|
||||
let probabilityLevel = '';
|
||||
if (prediction.probability >= 80) probabilityLevel = '极高';
|
||||
else if (prediction.probability >= 60) probabilityLevel = '较高';
|
||||
else if (prediction.probability >= 40) probabilityLevel = '中等';
|
||||
else if (prediction.probability >= 20) probabilityLevel = '较低';
|
||||
else probabilityLevel = '很低';
|
||||
|
||||
markdown += `**评级:** ${probabilityLevel}\n\n`;
|
||||
}
|
||||
|
||||
if (prediction.analysis) {
|
||||
markdown += `### 详细分析\n\n`;
|
||||
if (typeof prediction.analysis === 'object') {
|
||||
Object.entries(prediction.analysis).forEach(([key, value]) => {
|
||||
const chineseKey = getChineseFieldName(key);
|
||||
markdown += `**${chineseKey}:** ${value}\n`;
|
||||
});
|
||||
} else {
|
||||
markdown += `${prediction.analysis}\n`;
|
||||
}
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
if (prediction.key_factors) {
|
||||
markdown += `### 关键因素\n\n`;
|
||||
if (typeof prediction.key_factors === 'object') {
|
||||
Object.entries(prediction.key_factors).forEach(([factor, impact]) => {
|
||||
const chineseFactor = getChineseFieldName(factor);
|
||||
markdown += `- **${chineseFactor}:** ${impact}\n`;
|
||||
});
|
||||
} else {
|
||||
markdown += `${prediction.key_factors}\n`;
|
||||
}
|
||||
markdown += `\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// 指导建议
|
||||
if (analysisData.guidance) {
|
||||
markdown += `## 💡 指导建议\n\n`;
|
||||
const guidance = analysisData.guidance;
|
||||
|
||||
if (guidance.timing_advice) {
|
||||
markdown += `### ⏰ 时机建议\n\n`;
|
||||
if (typeof guidance.timing_advice === 'object') {
|
||||
Object.entries(guidance.timing_advice).forEach(([key, value]) => {
|
||||
const chineseKey = getChineseFieldName(key);
|
||||
markdown += `**${chineseKey}:** ${value}\n`;
|
||||
});
|
||||
} else {
|
||||
markdown += `${guidance.timing_advice}\n`;
|
||||
}
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
if (guidance.action_suggestions && Array.isArray(guidance.action_suggestions)) {
|
||||
markdown += `### 🎯 行动建议\n\n`;
|
||||
guidance.action_suggestions.forEach(suggestion => {
|
||||
markdown += `- ${suggestion}\n`;
|
||||
});
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
if (guidance.precautions && Array.isArray(guidance.precautions)) {
|
||||
markdown += `### ⚠️ 注意事项\n\n`;
|
||||
guidance.precautions.forEach(precaution => {
|
||||
markdown += `- ${precaution}\n`;
|
||||
});
|
||||
markdown += `\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// 五行分析
|
||||
if (analysisData.detailed_analysis?.wuxing_analysis) {
|
||||
markdown += `## 🌟 五行分析\n\n`;
|
||||
const wuxingAnalysis = analysisData.detailed_analysis.wuxing_analysis;
|
||||
|
||||
if (typeof wuxingAnalysis === 'object') {
|
||||
Object.entries(wuxingAnalysis).forEach(([key, value]) => {
|
||||
const chineseKey = getChineseFieldName(key);
|
||||
markdown += `**${chineseKey}:** ${value}\n`;
|
||||
});
|
||||
} else {
|
||||
markdown += `${wuxingAnalysis}\n`;
|
||||
}
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
// 时机分析
|
||||
if (analysisData.detailed_analysis?.timing_analysis) {
|
||||
markdown += `## ⏰ 时机分析\n\n`;
|
||||
const timingAnalysis = analysisData.detailed_analysis.timing_analysis;
|
||||
|
||||
if (typeof timingAnalysis === 'object') {
|
||||
Object.entries(timingAnalysis).forEach(([key, value]) => {
|
||||
const chineseKey = getChineseFieldName(key);
|
||||
markdown += `**${chineseKey}:** ${value}\n`;
|
||||
});
|
||||
} else {
|
||||
markdown += `${timingAnalysis}\n`;
|
||||
}
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
// 页脚
|
||||
markdown += `---\n\n`;
|
||||
markdown += `*本报告由神机阁AI命理分析平台生成*\n`;
|
||||
markdown += `*生成时间:${timestamp}*\n`;
|
||||
markdown += `*仅供参考,请理性对待*\n`;
|
||||
|
||||
return markdown;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
generateMarkdown
|
||||
};
|
||||
@@ -5,6 +5,7 @@ const AnalysisCache = require('./common/AnalysisCache.cjs');
|
||||
const EnhancedRandom = require('./common/EnhancedRandom.cjs');
|
||||
const TimeConverter = require('../utils/timeConverter.cjs');
|
||||
const SolarTerms = require('../utils/solarTerms.cjs');
|
||||
const BaziAnalyzer = require('./baziAnalyzer.cjs');
|
||||
|
||||
class QimenAnalyzer {
|
||||
constructor() {
|
||||
@@ -27,6 +28,9 @@ class QimenAnalyzer {
|
||||
// 初始化时间转换器
|
||||
this.timeConverter = new TimeConverter();
|
||||
|
||||
// 初始化八字分析器(复用农历计算功能)
|
||||
this.baziAnalyzer = new BaziAnalyzer();
|
||||
|
||||
// 初始化节气计算器
|
||||
this.solarTerms = new SolarTerms();
|
||||
|
||||
@@ -334,19 +338,19 @@ class QimenAnalyzer {
|
||||
const currentTime = datetime ? new Date(datetime) : new Date();
|
||||
|
||||
// 起局
|
||||
const qimenPan = this.calculateQimenPan(currentTime);
|
||||
const qimenPan = this.calculator.calculateQimenPan(currentTime);
|
||||
|
||||
// 选择用神
|
||||
const yongShen = this.selectYongShen(question, birth_data, qimenPan);
|
||||
const yongShen = this.yongShenAnalyzer.selectYongShen(question, birth_data, qimenPan);
|
||||
|
||||
// 格局分析
|
||||
const patterns = this.analyzePatterns(qimenPan);
|
||||
const patterns = this.patternAnalyzer.analyzePatterns(qimenPan);
|
||||
|
||||
// 用神分析
|
||||
const yongShenAnalysis = this.analyzeYongShen(yongShen, qimenPan);
|
||||
const yongShenAnalysis = this.yongShenAnalyzer.analyzeYongShen(yongShen, qimenPan);
|
||||
|
||||
// 生成预测结果
|
||||
const prediction = this.generatePrediction(qimenPan, yongShenAnalysis, question, patterns);
|
||||
const prediction = this.predictionGenerator.generatePrediction(qimenPan, yongShenAnalysis, question, patterns);
|
||||
|
||||
const result = {
|
||||
analysis_type: 'qimen',
|
||||
@@ -359,17 +363,17 @@ class QimenAnalyzer {
|
||||
lunar_info: this.calculateLunarInfo(currentTime)
|
||||
},
|
||||
qimen_info: {
|
||||
jieqi: qimenPan.timeInfo.jieqi,
|
||||
yuan: qimenPan.timeInfo.yuan,
|
||||
jushu: qimenPan.timeInfo.jushu,
|
||||
yindun: qimenPan.timeInfo.yindun ? '阴遁' : '阳遁',
|
||||
zhifu: qimenPan.zhifu,
|
||||
zhishi: qimenPan.zhishi,
|
||||
jieqi: qimenPan.timeInfo?.jieqi || '未知',
|
||||
yuan: qimenPan.timeInfo?.yuan || '未知',
|
||||
jushu: qimenPan.timeInfo?.jushu || qimenPan.jushu || '未知',
|
||||
yindun: qimenPan.timeInfo?.yindun !== undefined ? (qimenPan.timeInfo.yindun ? '阴遁' : '阳遁') : (qimenPan.yindun ? '阴遁' : '阳遁'),
|
||||
zhifu: qimenPan.zhifu || '未知',
|
||||
zhishi: qimenPan.zhishi || '未知',
|
||||
ganzhi: {
|
||||
year: qimenPan.timeInfo.year,
|
||||
month: qimenPan.timeInfo.month,
|
||||
day: qimenPan.timeInfo.day,
|
||||
hour: qimenPan.timeInfo.hour
|
||||
year: qimenPan.timeInfo?.year || { gan: '未知', zhi: '未知' },
|
||||
month: qimenPan.timeInfo?.month || { gan: '未知', zhi: '未知' },
|
||||
day: qimenPan.timeInfo?.day || { gan: '未知', zhi: '未知' },
|
||||
hour: qimenPan.timeInfo?.hour || { gan: '未知', zhi: '未知' }
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -957,17 +961,56 @@ class QimenAnalyzer {
|
||||
* @returns {number} 成功概率(0-100)
|
||||
*/
|
||||
calculateProbability(yongShenAnalysis, patterns) {
|
||||
let baseProbability = yongShenAnalysis.overall.favorability || 50;
|
||||
let baseProbability = 50; // 基础概率
|
||||
|
||||
// 基于用神分析调整概率
|
||||
if (yongShenAnalysis && yongShenAnalysis.overall) {
|
||||
const favorability = yongShenAnalysis.overall.favorability;
|
||||
if (typeof favorability === 'number') {
|
||||
baseProbability = favorability;
|
||||
} else {
|
||||
// 如果没有favorability,基于用神旺衰计算
|
||||
let yongShenScore = 0;
|
||||
let yongShenCount = 0;
|
||||
|
||||
// 遍历所有用神分析
|
||||
['primary', 'secondary', 'auxiliary'].forEach(category => {
|
||||
if (yongShenAnalysis[category]) {
|
||||
Object.values(yongShenAnalysis[category]).forEach(analysis => {
|
||||
if (analysis && analysis.wangshui) {
|
||||
yongShenCount++;
|
||||
switch (analysis.wangshui) {
|
||||
case '旺': yongShenScore += 20; break;
|
||||
case '相': yongShenScore += 10; break;
|
||||
case '休': yongShenScore += 0; break;
|
||||
case '囚': yongShenScore -= 10; break;
|
||||
case '死': yongShenScore -= 20; break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (yongShenCount > 0) {
|
||||
baseProbability = 50 + (yongShenScore / yongShenCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据格局调整概率
|
||||
if (patterns && patterns.length > 0) {
|
||||
const patternScore = patterns.reduce((sum, pattern) => sum + pattern.score, 0);
|
||||
const patternAdjustment = Math.min(Math.max(patternScore, -20), 20);
|
||||
const patternScore = patterns.reduce((sum, pattern) => {
|
||||
const score = pattern.score || 0;
|
||||
return sum + score;
|
||||
}, 0);
|
||||
|
||||
// 格局影响权重调整
|
||||
const patternAdjustment = Math.min(Math.max(patternScore * 0.8, -25), 25);
|
||||
baseProbability += patternAdjustment;
|
||||
}
|
||||
|
||||
// 确保概率在合理范围内
|
||||
return Math.min(Math.max(baseProbability, 10), 90);
|
||||
return Math.min(Math.max(Math.round(baseProbability), 15), 85);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1032,15 +1075,34 @@ class QimenAnalyzer {
|
||||
return '不利,建议暂缓或改变策略';
|
||||
}
|
||||
|
||||
// 辅助方法:计算农历信息
|
||||
// 辅助方法:计算农历信息(复用八字分析器的农历算法)
|
||||
calculateLunarInfo(datetime) {
|
||||
// 简化实现,实际应用中需要更精确的农历转换
|
||||
return {
|
||||
year: '甲辰',
|
||||
month: '丁卯',
|
||||
day: '庚申',
|
||||
description: '农历信息(简化版)'
|
||||
};
|
||||
try {
|
||||
// 将datetime转换为日期字符串格式
|
||||
const date = new Date(datetime);
|
||||
const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD格式
|
||||
|
||||
// 复用八字分析器的农历计算功能
|
||||
const lunarInfo = this.baziAnalyzer.calculateLunarInfo(dateStr);
|
||||
|
||||
return {
|
||||
year: lunarInfo.ganzhi_year,
|
||||
month: lunarInfo.lunar_month,
|
||||
day: lunarInfo.lunar_day,
|
||||
description: lunarInfo.lunar_date,
|
||||
zodiac: lunarInfo.zodiac,
|
||||
solar_term: lunarInfo.solar_term
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('农历信息计算失败:', error);
|
||||
// 降级处理:返回基本信息
|
||||
return {
|
||||
year: '未知',
|
||||
month: '未知',
|
||||
day: '未知',
|
||||
description: '农历信息计算失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:分类问题类型
|
||||
@@ -1928,14 +1990,265 @@ class QimenAnalyzer {
|
||||
};
|
||||
}
|
||||
|
||||
generateDetailedAnalysis(yongShenAnalysis, patterns) {
|
||||
// 生成详细分析
|
||||
return ['用神状态良好', '格局组合有利'];
|
||||
/**
|
||||
* 生成详细分析
|
||||
* @param {Object} qimenPan - 奇门盘
|
||||
* @param {Object} yongShenAnalysis - 用神分析
|
||||
* @param {Array} patterns - 格局列表
|
||||
* @returns {Object} 详细分析
|
||||
*/
|
||||
generateDetailedAnalysis(qimenPan, yongShenAnalysis, patterns) {
|
||||
const analysis = {
|
||||
yongshen_status: this.generateYongShenStatusAnalysis(yongShenAnalysis),
|
||||
pattern_influence: this.generatePatternInfluenceAnalysis(patterns),
|
||||
palace_analysis: this.generatePalaceAnalysis(qimenPan),
|
||||
wuxing_balance: this.generateWuXingAnalysis(qimenPan),
|
||||
timing_factors: this.generateTimingAnalysis(qimenPan),
|
||||
overall_trend: this.generateOverallTrendAnalysis(yongShenAnalysis, patterns)
|
||||
};
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
generateSuggestions(yongShenAnalysis, qimenPan, question) {
|
||||
// 生成建议
|
||||
return ['把握时机', '积极行动'];
|
||||
/**
|
||||
* 生成用神状态分析
|
||||
*/
|
||||
generateYongShenStatusAnalysis(yongShenAnalysis) {
|
||||
if (!yongShenAnalysis || !yongShenAnalysis.overall) {
|
||||
return '用神分析数据不完整,无法进行详细评估';
|
||||
}
|
||||
|
||||
const favorability = yongShenAnalysis.overall.favorability || 50;
|
||||
|
||||
if (favorability >= 80) {
|
||||
return '用神状态极佳,各项要素配合得当,时机非常有利,建议积极行动';
|
||||
} else if (favorability >= 65) {
|
||||
return '用神状态良好,大部分要素较为有利,整体趋势向好,可以适度推进';
|
||||
} else if (favorability >= 50) {
|
||||
return '用神状态一般,有利不利因素并存,需要谨慎评估,稳步前进';
|
||||
} else if (favorability >= 35) {
|
||||
return '用神状态偏弱,不利因素较多,建议暂缓行动,等待更好时机';
|
||||
} else {
|
||||
return '用神状态不佳,阻力较大,不宜贸然行动,应重新规划策略';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成格局影响分析
|
||||
*/
|
||||
generatePatternInfluenceAnalysis(patterns) {
|
||||
if (!patterns || patterns.length === 0) {
|
||||
return '未发现明显格局,整体影响中性,需要综合其他因素判断';
|
||||
}
|
||||
|
||||
const auspiciousCount = patterns.filter(p => p.level === '吉' || p.level === '大吉').length;
|
||||
const inauspiciousCount = patterns.filter(p => p.level === '凶' || p.level === '大凶').length;
|
||||
|
||||
if (auspiciousCount > inauspiciousCount) {
|
||||
return `发现${auspiciousCount}个吉利格局,${inauspiciousCount}个不利格局,整体格局偏向有利,可以借助吉格之力推进事情发展`;
|
||||
} else if (inauspiciousCount > auspiciousCount) {
|
||||
return `发现${inauspiciousCount}个不利格局,${auspiciousCount}个吉利格局,需要化解凶格影响,谨慎行事避免不利后果`;
|
||||
} else {
|
||||
return `吉凶格局数量相当,影响相互抵消,整体趋势平稳,需要依靠个人努力创造机会`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成宫位分析
|
||||
*/
|
||||
generatePalaceAnalysis(qimenPan) {
|
||||
if (!qimenPan || !qimenPan.dipan) {
|
||||
return '奇门盘数据不完整,无法进行宫位分析';
|
||||
}
|
||||
|
||||
const palaceNames = ['坎宫', '坤宫', '震宫', '巽宫', '中宫', '乾宫', '兑宫', '艮宫', '离宫'];
|
||||
const activePalaces = [];
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const palace = qimenPan.dipan[i];
|
||||
if (palace && (palace.star || palace.door || palace.god)) {
|
||||
activePalaces.push(palaceNames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return `当前奇门盘中${activePalaces.join('、')}等宫位较为活跃,星门神配置完整,形成了相对稳定的能量分布格局`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成五行分析
|
||||
*/
|
||||
generateWuXingAnalysis(qimenPan) {
|
||||
const wuxingCount = { '金': 0, '木': 0, '水': 0, '火': 0, '土': 0 };
|
||||
|
||||
if (qimenPan && qimenPan.dipan) {
|
||||
for (let i = 0; i < 9; i++) {
|
||||
const palace = qimenPan.dipan[i];
|
||||
if (palace && palace.gan) {
|
||||
const wuxing = this.getGanZhiWuXing(palace.gan);
|
||||
if (wuxingCount[wuxing] !== undefined) {
|
||||
wuxingCount[wuxing]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const maxWuxing = Object.keys(wuxingCount).reduce((a, b) => wuxingCount[a] > wuxingCount[b] ? a : b);
|
||||
const total = Object.values(wuxingCount).reduce((sum, count) => sum + count, 0);
|
||||
|
||||
if (total === 0) {
|
||||
return { dominant: '未知', balance: '无法判断', suggestions: '数据不足,无法分析' };
|
||||
}
|
||||
|
||||
const dominantRatio = wuxingCount[maxWuxing] / total;
|
||||
let balance, suggestions;
|
||||
|
||||
if (dominantRatio >= 0.5) {
|
||||
balance = '失衡';
|
||||
suggestions = `${maxWuxing}过旺,需要其他五行调和平衡`;
|
||||
} else if (dominantRatio >= 0.3) {
|
||||
balance = '较为平衡';
|
||||
suggestions = `${maxWuxing}稍强,整体尚可,注意维持平衡`;
|
||||
} else {
|
||||
balance = '平衡';
|
||||
suggestions = '五行分布均匀,相互制约,整体和谐';
|
||||
}
|
||||
|
||||
return { dominant: maxWuxing, balance, suggestions };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成时机分析
|
||||
*/
|
||||
generateTimingAnalysis(qimenPan) {
|
||||
const currentDate = new Date();
|
||||
const season = this.getCurrentSeason(currentDate);
|
||||
|
||||
let favorability, notes;
|
||||
|
||||
switch (season) {
|
||||
case '春季':
|
||||
favorability = '有利';
|
||||
notes = '春季木旺,万物复苏,利于新的开始和发展';
|
||||
break;
|
||||
case '夏季':
|
||||
favorability = '较为有利';
|
||||
notes = '夏季火旺,阳气充沛,利于积极行动和扩展';
|
||||
break;
|
||||
case '秋季':
|
||||
favorability = '中等';
|
||||
notes = '秋季金旺,收获季节,利于总结和巩固成果';
|
||||
break;
|
||||
case '冬季':
|
||||
favorability = '需谨慎';
|
||||
notes = '冬季水旺,宜蛰伏养精,不宜大动作';
|
||||
break;
|
||||
default:
|
||||
favorability = '中等';
|
||||
notes = '时机平常,需要综合其他因素判断';
|
||||
}
|
||||
|
||||
return { season, favorability, notes };
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成整体趋势分析
|
||||
*/
|
||||
generateOverallTrendAnalysis(yongShenAnalysis, patterns) {
|
||||
const favorability = yongShenAnalysis?.overall?.favorability || 50;
|
||||
const patternScore = patterns ? patterns.reduce((sum, p) => sum + (p.score || 0), 0) : 0;
|
||||
|
||||
const totalScore = favorability + patternScore;
|
||||
|
||||
if (totalScore >= 80) {
|
||||
return '整体趋势非常积极,各项因素配合良好,成功概率很高,建议抓住机会全力推进';
|
||||
} else if (totalScore >= 60) {
|
||||
return '整体趋势较为积极,大部分因素有利,有较好的成功基础,可以稳步推进';
|
||||
} else if (totalScore >= 40) {
|
||||
return '整体趋势中性偏好,有利不利因素并存,需要谨慎规划,稳中求进';
|
||||
} else if (totalScore >= 20) {
|
||||
return '整体趋势偏向不利,阻力较大,建议暂缓行动,寻找更好时机';
|
||||
} else {
|
||||
return '整体趋势不佳,不利因素较多,不建议贸然行动,应重新评估策略';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前季节
|
||||
*/
|
||||
getCurrentSeason(date) {
|
||||
const month = date.getMonth() + 1;
|
||||
|
||||
if (month >= 3 && month <= 5) {
|
||||
return '春季';
|
||||
} else if (month >= 6 && month <= 8) {
|
||||
return '夏季';
|
||||
} else if (month >= 9 && month <= 11) {
|
||||
return '秋季';
|
||||
} else {
|
||||
return '冬季';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成建议
|
||||
* @param {Object} yongShenAnalysis - 用神分析
|
||||
* @param {Array} patterns - 格局列表
|
||||
* @param {Object} timing - 应期分析
|
||||
* @returns {Array} 建议列表
|
||||
*/
|
||||
generateSuggestions(yongShenAnalysis, patterns, timing) {
|
||||
const suggestions = [];
|
||||
|
||||
// 基于用神状态的建议
|
||||
if (yongShenAnalysis && yongShenAnalysis.overall) {
|
||||
const favorability = yongShenAnalysis.overall.favorability || 50;
|
||||
|
||||
if (favorability >= 70) {
|
||||
suggestions.push('用神得力,时机有利,可以积极主动地推进计划');
|
||||
suggestions.push('抓住当前的有利时机,果断行动,成功概率较高');
|
||||
} else if (favorability >= 50) {
|
||||
suggestions.push('用神状态一般,需要谨慎评估,稳步推进');
|
||||
suggestions.push('可以适度行动,但要做好充分准备和风险控制');
|
||||
} else {
|
||||
suggestions.push('用神不利,建议暂缓行动,等待更好的时机');
|
||||
suggestions.push('当前阻力较大,宜以守为攻,积蓄力量');
|
||||
}
|
||||
}
|
||||
|
||||
// 基于格局的建议
|
||||
if (patterns && patterns.length > 0) {
|
||||
const auspiciousPatterns = patterns.filter(p => p.level === '吉' || p.level === '大吉');
|
||||
const inauspiciousPatterns = patterns.filter(p => p.level === '凶' || p.level === '大凶');
|
||||
|
||||
if (auspiciousPatterns.length > inauspiciousPatterns.length) {
|
||||
suggestions.push('格局组合整体有利,可以借助贵人力量,寻求合作机会');
|
||||
suggestions.push('吉格当头,宜主动出击,把握机遇,扩大成果');
|
||||
} else if (inauspiciousPatterns.length > 0) {
|
||||
suggestions.push('存在不利格局,需要化解阻碍,避免冲动行事');
|
||||
suggestions.push('凶格影响,宜低调行事,避免锋芒太露,以免招致麻烦');
|
||||
}
|
||||
}
|
||||
|
||||
// 基于应期的建议
|
||||
if (timing && timing.mainTiming) {
|
||||
switch (timing.mainTiming) {
|
||||
case '近期':
|
||||
suggestions.push('应期在即,宜抓紧时间行动,不可错失良机');
|
||||
break;
|
||||
case '中期':
|
||||
suggestions.push('需要耐心等待,做好充分准备,时机成熟时再行动');
|
||||
break;
|
||||
case '远期':
|
||||
suggestions.push('应期较远,宜长远规划,循序渐进,不可急于求成');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 通用建议
|
||||
suggestions.push('保持积极心态,相信自己的判断,同时要灵活应变');
|
||||
suggestions.push('多与有经验的人交流,听取不同意见,完善行动方案');
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
calculateTiming(yongShenAnalysis, qimenPan) {
|
||||
@@ -2871,7 +3184,7 @@ class YongShenAnalyzer {
|
||||
* @returns {Object} 用神配置
|
||||
*/
|
||||
selectYongShen(question, birthData, qimenPan) {
|
||||
const questionType = this.classifyQuestion(question);
|
||||
const questionType = this.analyzer.classifyQuestion(question);
|
||||
const rigan = qimenPan.timeInfo.day.gan;
|
||||
const gender = birthData?.gender || '未知';
|
||||
|
||||
@@ -3858,8 +4171,8 @@ class YongShenAnalyzer {
|
||||
recommendation,
|
||||
factors,
|
||||
analysis: {
|
||||
zhifuAnalysis: zhifu ? `值符${zhifu.star}` : '值符未知',
|
||||
zhishiAnalysis: zhishi ? `值使${zhishi.door}` : '值使未知',
|
||||
zhifuAnalysis: zhifu ? `值符${typeof zhifu === 'object' ? zhifu.star : zhifu}` : '值符未知',
|
||||
zhishiAnalysis: zhishi ? `值使${typeof zhishi === 'object' ? zhishi.door : zhishi}` : '值使未知',
|
||||
hourAnalysis: `${hourZhi}时`,
|
||||
seasonAnalysis: `${jieqi}节气`,
|
||||
yindunAnalysis: timeInfo.yindun ? '阴遁' : '阳遁'
|
||||
@@ -3887,13 +4200,14 @@ class PredictionGenerator {
|
||||
}
|
||||
|
||||
generatePrediction(qimenPan, yongShenAnalysis, question, patterns) {
|
||||
const score = this.calculateOverallScore(yongShenAnalysis, patterns);
|
||||
// 使用主分析器的概率计算方法
|
||||
const probability = this.analyzer.calculateProbability(yongShenAnalysis, patterns);
|
||||
|
||||
return {
|
||||
overall: this.interpretScore(score),
|
||||
probability: score,
|
||||
details: this.generateDetailedAnalysis(yongShenAnalysis, patterns),
|
||||
suggestions: this.generateSuggestions(yongShenAnalysis, qimenPan, question),
|
||||
overall: this.analyzer.generateOverallAssessment(probability, yongShenAnalysis),
|
||||
probability: probability,
|
||||
details: this.analyzer.generateDetailedAnalysis(qimenPan, yongShenAnalysis, patterns),
|
||||
suggestions: this.analyzer.generateSuggestions(yongShenAnalysis, patterns, { description: '时机分析' }),
|
||||
timing: this.calculateTiming(yongShenAnalysis, qimenPan)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// 专业紫微斗数分析服务模块
|
||||
// 基于传统紫微斗数理论的精确实现
|
||||
|
||||
const BaziAnalyzer = require('./baziAnalyzer.cjs');
|
||||
const BaseData = require('./common/BaseData.cjs');
|
||||
const AnalysisCache = require('./common/AnalysisCache.cjs');
|
||||
const StarBrightness = require('./common/StarBrightness.cjs');
|
||||
const EnhancedSiHua = require('./common/EnhancedSiHua.cjs');
|
||||
const BaziAnalyzer = require('./baziAnalyzer.cjs');
|
||||
|
||||
class ZiweiAnalyzer {
|
||||
constructor() {
|
||||
@@ -61,42 +61,27 @@ class ZiweiAnalyzer {
|
||||
};
|
||||
}
|
||||
|
||||
// 计算农历信息(与八字分析器保持一致)
|
||||
// 计算农历信息(复用八字分析器的农历算法)
|
||||
calculateLunarInfo(birth_date) {
|
||||
const birthDate = new Date(birth_date);
|
||||
const year = birthDate.getFullYear();
|
||||
const month = birthDate.getMonth() + 1;
|
||||
const day = birthDate.getDate();
|
||||
|
||||
// 计算干支年
|
||||
const tianGan = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
|
||||
const diZhi = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
|
||||
const zodiacAnimals = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
|
||||
|
||||
const ganIndex = (year - 4) % 10;
|
||||
const zhiIndex = (year - 4) % 12;
|
||||
const ganzhiYear = tianGan[ganIndex] + diZhi[zhiIndex];
|
||||
const zodiac = zodiacAnimals[zhiIndex];
|
||||
|
||||
// 计算节气信息
|
||||
let solarTerm = this.calculateSolarTerm(month, day);
|
||||
|
||||
// 改进的农历日期计算
|
||||
const lunarInfo = this.calculateAccurateLunarDate(year, month, day);
|
||||
const lunarDay = lunarInfo.day;
|
||||
const lunarMonth = lunarInfo.month;
|
||||
const lunarYear = lunarInfo.year;
|
||||
|
||||
return {
|
||||
lunar_date: `农历${this.getChineseYear(lunarYear)}年${this.getChineseMonth(lunarMonth)}月${this.getChineseDay(lunarDay)}日`,
|
||||
lunar_year: `${this.getChineseYear(lunarYear)}年`,
|
||||
lunar_month: this.getChineseMonth(lunarMonth) + '月',
|
||||
lunar_day: this.getChineseDay(lunarDay) + '日',
|
||||
ganzhi_year: ganzhiYear,
|
||||
zodiac: zodiac,
|
||||
solar_term: this.calculateDetailedSolarTerm(month, day)
|
||||
try {
|
||||
// 直接复用八字分析器的农历计算功能,避免重复实现
|
||||
return this.baziAnalyzer.calculateLunarInfo(birth_date);
|
||||
} catch (error) {
|
||||
console.error('农历信息计算失败:', error);
|
||||
// 降级处理:返回基本信息
|
||||
const birthDate = new Date(birth_date);
|
||||
const year = birthDate.getFullYear();
|
||||
return {
|
||||
lunar_date: '农历信息计算失败',
|
||||
lunar_year: `${year}年`,
|
||||
lunar_month: '未知月',
|
||||
lunar_day: '未知日',
|
||||
ganzhi_year: '未知',
|
||||
zodiac: '未知',
|
||||
solar_term: '未知'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为中文年份
|
||||
getChineseYear(year) {
|
||||
@@ -152,81 +137,7 @@ class ZiweiAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
// 改进的公历转农历计算方法(与八字分析器保持一致)
|
||||
calculateAccurateLunarDate(year, month, day) {
|
||||
// 农历年份对照表(部分年份的春节日期)
|
||||
const springFestivals = {
|
||||
1976: { month: 1, day: 31 }, // 1976年春节:1月31日
|
||||
1977: { month: 2, day: 18 },
|
||||
1978: { month: 2, day: 7 },
|
||||
1979: { month: 1, day: 28 },
|
||||
1980: { month: 2, day: 16 },
|
||||
1981: { month: 2, day: 5 },
|
||||
1982: { month: 1, day: 25 },
|
||||
1983: { month: 2, day: 13 },
|
||||
1984: { month: 2, day: 2 },
|
||||
1985: { month: 2, day: 20 },
|
||||
1986: { month: 2, day: 9 },
|
||||
1987: { month: 1, day: 29 },
|
||||
1988: { month: 2, day: 17 },
|
||||
1989: { month: 2, day: 6 },
|
||||
1990: { month: 1, day: 27 }
|
||||
};
|
||||
|
||||
const springFestival = springFestivals[year];
|
||||
if (!springFestival) {
|
||||
// 如果没有对应年份数据,使用估算
|
||||
return {
|
||||
year: year,
|
||||
month: month > 2 ? month - 1 : month + 11,
|
||||
day: Math.max(1, day - 15)
|
||||
};
|
||||
}
|
||||
|
||||
// 计算距离春节的天数
|
||||
const currentDate = new Date(year, month - 1, day);
|
||||
const springDate = new Date(year, springFestival.month - 1, springFestival.day);
|
||||
const daysDiff = Math.floor((currentDate - springDate) / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (daysDiff < 0) {
|
||||
// 在春节之前,属于上一年农历
|
||||
const prevSpringFestival = springFestivals[year - 1];
|
||||
if (prevSpringFestival) {
|
||||
const prevSpringDate = new Date(year - 1, prevSpringFestival.month - 1, prevSpringFestival.day);
|
||||
const prevDaysDiff = Math.floor((currentDate - prevSpringDate) / (1000 * 60 * 60 * 24));
|
||||
const totalDays = prevDaysDiff + 365; // 简化计算
|
||||
|
||||
// 估算农历月日
|
||||
const lunarMonth = Math.floor(totalDays / 30) + 1;
|
||||
const lunarDay = (totalDays % 30) + 1;
|
||||
|
||||
return {
|
||||
year: year - 1,
|
||||
month: Math.min(12, lunarMonth),
|
||||
day: Math.min(30, lunarDay)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 在春节之后,计算农历月日
|
||||
const lunarMonth = Math.floor(daysDiff / 30) + 1;
|
||||
const lunarDay = (daysDiff % 30) + 1;
|
||||
|
||||
// 特殊处理:1976年3月17日应该对应农历2月17日左右
|
||||
if (year === 1976 && month === 3 && day === 17) {
|
||||
return {
|
||||
year: 1976,
|
||||
month: 2,
|
||||
day: 17
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
year: year,
|
||||
month: Math.min(12, lunarMonth),
|
||||
day: Math.min(30, Math.max(1, lunarDay))
|
||||
};
|
||||
}
|
||||
// 已删除重复的农历计算方法,现在复用八字分析器的功能
|
||||
|
||||
// 计算节气信息
|
||||
calculateSolarTerm(month, day) {
|
||||
@@ -257,19 +168,7 @@ class ZiweiAnalyzer {
|
||||
return '节气间';
|
||||
}
|
||||
|
||||
// 转换为中文月份
|
||||
getChineseMonth(month) {
|
||||
const chineseMonths = ['', '正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '腊'];
|
||||
return chineseMonths[month] || '未知';
|
||||
}
|
||||
|
||||
// 转换为中文日期
|
||||
getChineseDay(day) {
|
||||
const chineseDays = ['', '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
|
||||
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
|
||||
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十'];
|
||||
return chineseDays[day] || '未知';
|
||||
}
|
||||
// 已删除重复的中文转换方法,现在复用八字分析器的功能
|
||||
|
||||
// 生成子时计算方法说明(紫微斗数版本)
|
||||
generateZishiCalculationNote(baziInfo, birth_time) {
|
||||
|
||||
@@ -527,7 +527,7 @@ class SolarTerms {
|
||||
* @returns {Date} 立春时间
|
||||
*/
|
||||
getSpringBeginning(year) {
|
||||
return this.calculateSolarTerm(year, 0);
|
||||
return this.calculateSolarTermDate(year, '立春');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,11 +2,12 @@ import React from 'react';
|
||||
import CompleteBaziAnalysis from './CompleteBaziAnalysis';
|
||||
import CompleteZiweiAnalysis from './CompleteZiweiAnalysis';
|
||||
import CompleteYijingAnalysis from './CompleteYijingAnalysis';
|
||||
import CompleteQimenAnalysis from './CompleteQimenAnalysis';
|
||||
import BaziAnalysisDisplay from './BaziAnalysisDisplay';
|
||||
|
||||
interface AnalysisResultDisplayProps {
|
||||
analysisResult?: any;
|
||||
analysisType: 'bazi' | 'ziwei' | 'yijing';
|
||||
analysisType: 'bazi' | 'ziwei' | 'yijing' | 'qimen';
|
||||
birthDate?: {
|
||||
date: string;
|
||||
time: string;
|
||||
@@ -429,6 +430,8 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
||||
return renderZiweiAnalysis();
|
||||
case 'yijing':
|
||||
return renderYijingAnalysis();
|
||||
case 'qimen':
|
||||
return <CompleteQimenAnalysis analysis={analysisResult?.data || analysisResult} />;
|
||||
default:
|
||||
return (
|
||||
<div className="bg-white rounded-lg p-6 shadow-lg">
|
||||
@@ -461,6 +464,11 @@ const AnalysisResultDisplay: React.FC<AnalysisResultDisplayProps> = ({
|
||||
return <CompleteZiweiAnalysis birthDate={birthDate} analysisData={preAnalysisData} recordId={recordId} />;
|
||||
}
|
||||
|
||||
// 对于奇门遁甲,如果有预分析数据,直接返回 CompleteQimenAnalysis 组件
|
||||
if (analysisType === 'qimen' && preAnalysisData) {
|
||||
return <CompleteQimenAnalysis analysis={preAnalysisData} recordId={recordId} />;
|
||||
}
|
||||
|
||||
// 如果没有分析结果数据
|
||||
if (!analysisResult) {
|
||||
return (
|
||||
|
||||
1457
src/components/CompleteQimenAnalysis.tsx
Normal file
1457
src/components/CompleteQimenAnalysis.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ export type ExportMode = 'server' | 'frontend';
|
||||
|
||||
interface DownloadButtonProps {
|
||||
analysisData: any;
|
||||
analysisType: 'bazi' | 'ziwei' | 'yijing';
|
||||
analysisType: 'bazi' | 'ziwei' | 'yijing' | 'qimen';
|
||||
userName?: string;
|
||||
onDownload?: (format: DownloadFormat) => Promise<void>;
|
||||
className?: string;
|
||||
@@ -480,6 +480,7 @@ const DownloadButton: React.FC<DownloadButtonProps> = ({
|
||||
case 'bazi': return '八字命理';
|
||||
case 'ziwei': return '紫微斗数';
|
||||
case 'yijing': return '易经占卜';
|
||||
case 'qimen': return '奇门遁甲';
|
||||
default: return '命理';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ interface YijingQuestionSelectorProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
className?: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
// 问题分类和预设问题数据
|
||||
@@ -115,7 +117,9 @@ const questionCategories = {
|
||||
export const YijingQuestionSelector: React.FC<YijingQuestionSelectorProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
className
|
||||
className,
|
||||
label = '占卜问题',
|
||||
placeholder = '请输入您要占卜的问题'
|
||||
}) => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('');
|
||||
const [selectedQuestion, setSelectedQuestion] = useState<string>('');
|
||||
@@ -261,10 +265,10 @@ variant="default"
|
||||
|
||||
{/* 主要问题输入框 */}
|
||||
<ChineseInput
|
||||
label="占卜问题"
|
||||
label={label}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder="请输入您希望占卜的具体问题,或选择上方预设问题"
|
||||
placeholder={placeholder}
|
||||
required
|
||||
variant="filled"
|
||||
helperText="💡 提示:问题越具体,占卜结果越准确。您可以使用预设问题或自行输入。"
|
||||
|
||||
@@ -68,7 +68,24 @@ export const aiPromptTemplates = {
|
||||
易经占卜结果:
|
||||
{analysisContent}
|
||||
|
||||
请提供智慧的AI解读:`
|
||||
请提供智慧的AI解读:`,
|
||||
|
||||
qimen: `你是一位精通奇门遁甲的预测大师,请对以下奇门遁甲分析结果进行专业解读。
|
||||
|
||||
请重点分析:
|
||||
1. 奇门盘局的整体格局特点
|
||||
2. 用神落宫的吉凶分析
|
||||
3. 九星八门八神的组合意义
|
||||
4. 格局对事情发展的具体影响
|
||||
5. 最佳行动时机和策略建议
|
||||
6. 需要注意的不利因素
|
||||
|
||||
请结合现代实际情况,提供具有指导价值的预测分析。
|
||||
|
||||
奇门遁甲分析结果:
|
||||
{analysisContent}
|
||||
|
||||
请提供专业的AI解读:`
|
||||
};
|
||||
|
||||
// 获取AI配置
|
||||
@@ -103,6 +120,6 @@ export const validateAIConfig = (config: AIConfig): boolean => {
|
||||
};
|
||||
|
||||
// 获取提示词模板
|
||||
export const getPromptTemplate = (analysisType: 'bazi' | 'ziwei' | 'yijing'): string => {
|
||||
export const getPromptTemplate = (analysisType: 'bazi' | 'ziwei' | 'yijing' | 'qimen'): string => {
|
||||
return aiPromptTemplates[analysisType] || aiPromptTemplates.bazi;
|
||||
};
|
||||
@@ -264,6 +264,14 @@ class LocalApiClient {
|
||||
}, yijingData);
|
||||
},
|
||||
|
||||
// 奇门遁甲分析
|
||||
qimen: async (qimenData: any): Promise<ApiResponse<{ record_id: number; analysis: any }>> => {
|
||||
return this.requestWithDeduplication<{ record_id: number; analysis: any }>('/qimen/analyze', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(qimenData),
|
||||
}, qimenData);
|
||||
},
|
||||
|
||||
// 综合分析
|
||||
comprehensive: async (birthData: any, includeTypes?: string[]): Promise<ApiResponse<{ record_id: number; analysis: any }>> => {
|
||||
return this.request<{ record_id: number; analysis: any }>('/analysis/comprehensive', {
|
||||
|
||||
@@ -8,11 +8,11 @@ import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle }
|
||||
import YijingQuestionSelector from '../components/ui/YijingQuestionSelector';
|
||||
import AnalysisResultDisplay from '../components/AnalysisResultDisplay';
|
||||
import { toast } from 'sonner';
|
||||
import { Sparkles, Star, Compass, Calendar, MapPin, User, Loader2 } from 'lucide-react';
|
||||
import { Sparkles, Star, Compass, Calendar, MapPin, User, Loader2, Hexagon } from 'lucide-react';
|
||||
import { UserProfile, AnalysisRequest, NumerologyReading } from '../types';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
type AnalysisType = 'bazi' | 'ziwei' | 'yijing';
|
||||
type AnalysisType = 'bazi' | 'ziwei' | 'yijing' | 'qimen';
|
||||
|
||||
const AnalysisPage: React.FC = () => {
|
||||
const { user } = useAuth();
|
||||
@@ -32,7 +32,7 @@ const AnalysisPage: React.FC = () => {
|
||||
|
||||
// 使用useMemo缓存birthDate对象,避免重复渲染导致useEffect重复执行
|
||||
const memoizedBirthDate = useMemo(() => {
|
||||
if (analysisType === 'bazi' || analysisType === 'ziwei') {
|
||||
if (analysisType === 'bazi' || analysisType === 'ziwei' || analysisType === 'qimen') {
|
||||
return {
|
||||
date: formData.birth_date,
|
||||
time: formData.birth_time,
|
||||
@@ -83,6 +83,15 @@ const AnalysisPage: React.FC = () => {
|
||||
toast.error('请填写占卜问题');
|
||||
return;
|
||||
}
|
||||
} else if (analysisType === 'qimen') {
|
||||
if (!formData.question) {
|
||||
toast.error('请填写占卜问题');
|
||||
return;
|
||||
}
|
||||
if (!formData.birth_date || !formData.birth_time) {
|
||||
toast.error('奇门遁甲需要准确的出生日期和时间');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!formData.name || !formData.birth_date) {
|
||||
toast.error('请填写姓名和出生日期');
|
||||
@@ -123,6 +132,16 @@ const AnalysisPage: React.FC = () => {
|
||||
response = await localApi.analysis.yijing(yijingData);
|
||||
break;
|
||||
}
|
||||
case 'qimen': {
|
||||
const qimenData = {
|
||||
...birthData,
|
||||
question: formData.question,
|
||||
user_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
local_time: new Date().toISOString()
|
||||
};
|
||||
response = await localApi.analysis.qimen(qimenData);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`不支持的分析类型: ${analysisType}`);
|
||||
}
|
||||
@@ -215,6 +234,15 @@ const AnalysisPage: React.FC = () => {
|
||||
color: 'text-orange-600',
|
||||
bgColor: 'bg-orange-50',
|
||||
borderColor: 'border-orange-300'
|
||||
},
|
||||
{
|
||||
type: 'qimen' as AnalysisType,
|
||||
title: '奇门遁甲',
|
||||
description: '古代帝王之学,通过时空奇门盘分析事物发展趋势',
|
||||
icon: Hexagon,
|
||||
color: 'text-purple-600',
|
||||
bgColor: 'bg-purple-50',
|
||||
borderColor: 'border-purple-300'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -233,7 +261,7 @@ const AnalysisPage: React.FC = () => {
|
||||
<p className="text-gray-600 font-chinese">选择您感兴趣的命理分析方式</p>
|
||||
</ChineseCardHeader>
|
||||
<ChineseCardContent>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{analysisTypes.map((type) => {
|
||||
const Icon = type.icon;
|
||||
const isSelected = analysisType === type.type;
|
||||
@@ -291,6 +319,52 @@ const AnalysisPage: React.FC = () => {
|
||||
onChange={(value) => setFormData(prev => ({ ...prev, question: value }))}
|
||||
/>
|
||||
</div>
|
||||
) : analysisType === 'qimen' ? (
|
||||
// 奇门遁甲表单
|
||||
<>
|
||||
<div className="mb-6">
|
||||
<YijingQuestionSelector
|
||||
value={formData.question}
|
||||
onChange={(value) => setFormData(prev => ({ ...prev, question: value }))}
|
||||
placeholder="请输入您要占卜的问题,如:事业发展、投资决策、感情婚姻等"
|
||||
label="占卜问题"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4 md:gap-6 mb-6">
|
||||
<div className="relative">
|
||||
<ChineseInput
|
||||
type="date"
|
||||
label="出生日期"
|
||||
value={formData.birth_date}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value && !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||
return;
|
||||
}
|
||||
setFormData(prev => ({ ...prev, birth_date: value }));
|
||||
}}
|
||||
min="1900-01-01"
|
||||
max="2100-12-31"
|
||||
required
|
||||
variant="filled"
|
||||
className="pr-10"
|
||||
helperText="奇门遁甲需要准确的出生日期"
|
||||
/>
|
||||
<Calendar className="absolute right-3 top-9 h-4 w-4 text-gray-400 pointer-events-none" />
|
||||
</div>
|
||||
|
||||
<ChineseInput
|
||||
type="time"
|
||||
label="出生时间"
|
||||
value={formData.birth_time}
|
||||
onChange={(e) => setFormData(prev => ({ ...prev, birth_time: e.target.value }))}
|
||||
required
|
||||
variant="filled"
|
||||
helperText="奇门遁甲必须填写准确的出生时间"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
// 八字和紫微表单
|
||||
<>
|
||||
@@ -402,9 +476,10 @@ const AnalysisPage: React.FC = () => {
|
||||
analysisResult={analysisResult}
|
||||
analysisType={analysisType}
|
||||
birthDate={memoizedBirthDate}
|
||||
question={analysisType === 'yijing' ? formData.question : undefined}
|
||||
question={analysisType === 'yijing' || analysisType === 'qimen' ? formData.question : undefined}
|
||||
userId={user?.id?.toString()}
|
||||
divinationMethod="time"
|
||||
preAnalysisData={analysisResult.data}
|
||||
recordId={analysisResult.recordId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ChineseLoading } from '../components/ui/ChineseLoading';
|
||||
import AnalysisResultDisplay from '../components/AnalysisResultDisplay';
|
||||
import DownloadButton from '../components/ui/DownloadButton';
|
||||
import { toast } from 'sonner';
|
||||
import { History, Calendar, User, Sparkles, Star, Compass, Eye, Trash2, Download, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { History, Calendar, User, Sparkles, Star, Compass, Hexagon, Eye, Trash2, Download, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { NumerologyReading } from '../types';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
@@ -145,6 +145,7 @@ const HistoryPage: React.FC = () => {
|
||||
case 'bazi': return Sparkles;
|
||||
case 'ziwei': return Star;
|
||||
case 'yijing': return Compass;
|
||||
case 'qimen': return Hexagon;
|
||||
default: return History;
|
||||
}
|
||||
};
|
||||
@@ -154,6 +155,7 @@ const HistoryPage: React.FC = () => {
|
||||
case 'bazi': return 'text-red-600 bg-red-50';
|
||||
case 'ziwei': return 'text-yellow-600 bg-yellow-50';
|
||||
case 'yijing': return 'text-orange-600 bg-orange-50';
|
||||
case 'qimen': return 'text-purple-600 bg-purple-50';
|
||||
default: return 'text-gray-600 bg-gray-50';
|
||||
}
|
||||
};
|
||||
@@ -163,6 +165,7 @@ const HistoryPage: React.FC = () => {
|
||||
case 'bazi': return '八字命理';
|
||||
case 'ziwei': return '紫微斗数';
|
||||
case 'yijing': return '易经占卜';
|
||||
case 'qimen': return '奇门遁甲';
|
||||
default: return '未知类型';
|
||||
}
|
||||
};
|
||||
@@ -210,14 +213,14 @@ const HistoryPage: React.FC = () => {
|
||||
|
||||
<AnalysisResultDisplay
|
||||
analysisResult={selectedReading.analysis}
|
||||
analysisType={selectedReading.reading_type as 'bazi' | 'ziwei' | 'yijing'}
|
||||
analysisType={selectedReading.reading_type as 'bazi' | 'ziwei' | 'yijing' | 'qimen'}
|
||||
birthDate={selectedReading.reading_type !== 'yijing' ? {
|
||||
date: selectedReading.birth_date || '',
|
||||
time: selectedReading.birth_time || '12:00',
|
||||
name: selectedReading.name || '',
|
||||
gender: selectedReading.gender || 'male'
|
||||
} : undefined}
|
||||
question={selectedReading.reading_type === 'yijing' ?
|
||||
question={selectedReading.reading_type === 'yijing' || selectedReading.reading_type === 'qimen' ?
|
||||
getInputDataValue(selectedReading.input_data, 'question', '综合运势如何?') : undefined}
|
||||
userId={selectedReading.user_id?.toString()}
|
||||
divinationMethod={selectedReading.reading_type === 'yijing' ?
|
||||
@@ -333,7 +336,7 @@ const HistoryPage: React.FC = () => {
|
||||
...(reading.analysis || reading.results),
|
||||
created_at: reading.created_at
|
||||
}}
|
||||
analysisType={reading.reading_type as 'bazi' | 'ziwei' | 'yijing'}
|
||||
analysisType={reading.reading_type as 'bazi' | 'ziwei' | 'yijing' | 'qimen'}
|
||||
userName={reading.name}
|
||||
className="min-h-[40px] px-2 sm:px-6 py-2.5 text-xs sm:text-sm flex-shrink-0"
|
||||
/>
|
||||
|
||||
@@ -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, Hexagon, Heart, BarChart3, BookOpen, Shield, Zap, Users, Award, Brain, TrendingUp } from 'lucide-react';
|
||||
import { ChineseButton } from '../components/ui/ChineseButton';
|
||||
import { ChineseCard, ChineseCardContent, ChineseCardHeader, ChineseCardTitle } from '../components/ui/ChineseCard';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
@@ -35,6 +35,15 @@ const HomePage: React.FC = () => {
|
||||
bgColor: 'chinese-golden-glow',
|
||||
iconBg: 'bg-gradient-to-br from-yellow-400 to-amber-500',
|
||||
link: '/analysis'
|
||||
},
|
||||
{
|
||||
icon: Hexagon,
|
||||
title: '奇门遁甲',
|
||||
description: '古代帝王之学,通过时空奇门盘分析事物发展趋势。结合九星八门八神布局,为重要决策提供战略指导',
|
||||
color: 'text-red-700',
|
||||
bgColor: 'chinese-golden-glow',
|
||||
iconBg: 'bg-gradient-to-br from-yellow-400 to-amber-500',
|
||||
link: '/analysis'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface AIInterpretationResult {
|
||||
|
||||
// AI解读请求参数
|
||||
export interface AIInterpretationRequest {
|
||||
analysisType: 'bazi' | 'ziwei' | 'yijing';
|
||||
analysisType: 'bazi' | 'ziwei' | 'yijing' | 'qimen';
|
||||
analysisContent: any; // 改为any类型,支持对象数据
|
||||
customPrompt?: string;
|
||||
onStreamUpdate?: (content: string) => void; // 流式更新回调
|
||||
@@ -34,6 +34,9 @@ export const convertAnalysisToMarkdown = (analysisData: any, analysisType: strin
|
||||
case 'yijing':
|
||||
markdown += generateYijingMarkdown(analysisData);
|
||||
break;
|
||||
case 'qimen':
|
||||
markdown += generateQimenMarkdown(analysisData);
|
||||
break;
|
||||
default:
|
||||
markdown += JSON.stringify(analysisData, null, 2);
|
||||
}
|
||||
@@ -578,12 +581,113 @@ const generateYijingMarkdown = (data: any): string => {
|
||||
return markdown;
|
||||
};
|
||||
|
||||
// 生成奇门遁甲分析的Markdown
|
||||
const generateQimenMarkdown = (data: any): string => {
|
||||
const timestamp = new Date().toLocaleString('zh-CN');
|
||||
let markdown = `## 奇门遁甲分析报告\n\n**分析时间:** ${timestamp}\n\n`;
|
||||
|
||||
try {
|
||||
// 基本信息
|
||||
if (data.timeInfo) {
|
||||
markdown += `### 时空信息\n\n`;
|
||||
if (data.timeInfo.jieqi) markdown += `**节气:** ${data.timeInfo.jieqi}\n`;
|
||||
if (data.qimenPan?.jushu) markdown += `**局数:** ${data.qimenPan.jushu}局\n`;
|
||||
if (data.qimenPan?.yindun !== undefined) {
|
||||
markdown += `**阴阳遁:** ${data.qimenPan.yindun ? '阴遁' : '阳遁'}\n`;
|
||||
}
|
||||
if (data.timeInfo.hour) {
|
||||
markdown += `**时辰:** ${data.timeInfo.hour.gan}${data.timeInfo.hour.zhi}时\n`;
|
||||
}
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
// 奇门盘信息
|
||||
if (data.qimenPan && data.qimenPan.dipan) {
|
||||
markdown += `### 奇门盘布局\n\n`;
|
||||
const palaceNames = ['坎一宫', '坤二宫', '震三宫', '巽四宫', '中五宫', '乾六宫', '兑七宫', '艮八宫', '离九宫'];
|
||||
|
||||
data.qimenPan.dipan.forEach((palace: any, index: number) => {
|
||||
if (palace) {
|
||||
markdown += `**${palaceNames[index]}:**\n`;
|
||||
if (palace.star) markdown += `- 九星:${palace.star}\n`;
|
||||
if (palace.door) markdown += `- 八门:${palace.door}\n`;
|
||||
if (palace.god) markdown += `- 八神:${palace.god}\n`;
|
||||
markdown += `\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 用神分析
|
||||
if (data.yongShenAnalysis) {
|
||||
markdown += `### 用神分析\n\n`;
|
||||
|
||||
if (data.yongShenAnalysis.primary) {
|
||||
markdown += `**主用神:**\n`;
|
||||
Object.entries(data.yongShenAnalysis.primary).forEach(([key, value]) => {
|
||||
markdown += `- ${key}:${value}\n`;
|
||||
});
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
if (data.yongShenAnalysis.secondary) {
|
||||
markdown += `**次用神:**\n`;
|
||||
Object.entries(data.yongShenAnalysis.secondary).forEach(([key, value]) => {
|
||||
markdown += `- ${key}:${value}\n`;
|
||||
});
|
||||
markdown += `\n`;
|
||||
}
|
||||
|
||||
if (data.yongShenAnalysis.overall) {
|
||||
markdown += `**综合分析:**\n${data.yongShenAnalysis.overall}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// 格局识别
|
||||
if (data.patterns && data.patterns.length > 0) {
|
||||
markdown += `### 格局识别\n\n`;
|
||||
data.patterns.forEach((pattern: any, index: number) => {
|
||||
markdown += `**${pattern.name}** (${pattern.type === 'auspicious' ? '吉格' : '凶格'})\n`;
|
||||
if (pattern.description) markdown += `${pattern.description}\n`;
|
||||
if (pattern.influence) markdown += `影响:${pattern.influence}\n`;
|
||||
markdown += `\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// 预测结果
|
||||
if (data.prediction) {
|
||||
markdown += `### 预测结果\n\n`;
|
||||
|
||||
if (data.prediction.probability) {
|
||||
markdown += `**成功概率:** ${data.prediction.probability}%\n\n`;
|
||||
}
|
||||
|
||||
if (data.prediction.analysis) {
|
||||
markdown += `**详细分析:**\n${data.prediction.analysis}\n\n`;
|
||||
}
|
||||
|
||||
if (data.prediction.suggestions && data.prediction.suggestions.length > 0) {
|
||||
markdown += `**建议:**\n`;
|
||||
data.prediction.suggestions.forEach((suggestion: string) => {
|
||||
markdown += `- ${suggestion}\n`;
|
||||
});
|
||||
markdown += `\n`;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
markdown += `\n**原始数据:**\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\`\n`;
|
||||
}
|
||||
|
||||
return markdown;
|
||||
};
|
||||
|
||||
// 获取分析类型标题
|
||||
const getAnalysisTitle = (analysisType: string): string => {
|
||||
const titles = {
|
||||
'bazi': '八字命理',
|
||||
'ziwei': '紫微斗数',
|
||||
'yijing': '易经占卜'
|
||||
'yijing': '易经占卜',
|
||||
'qimen': '奇门遁甲'
|
||||
};
|
||||
return titles[analysisType as keyof typeof titles] || '命理';
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface UserProfile {
|
||||
export interface AnalysisRecord {
|
||||
id: number;
|
||||
user_id: number;
|
||||
analysis_type: 'bazi' | 'ziwei' | 'yijing';
|
||||
analysis_type: 'bazi' | 'ziwei' | 'yijing' | 'qimen';
|
||||
name: string;
|
||||
birth_date: string;
|
||||
birth_time?: string;
|
||||
@@ -32,7 +32,7 @@ export interface NumerologyReading {
|
||||
id: number;
|
||||
user_id: number;
|
||||
profile_id?: string;
|
||||
reading_type: 'bazi' | 'ziwei' | 'yijing' | 'comprehensive';
|
||||
reading_type: 'bazi' | 'ziwei' | 'yijing' | 'qimen' | 'comprehensive';
|
||||
name: string;
|
||||
birth_date: string;
|
||||
birth_time?: string;
|
||||
@@ -43,6 +43,7 @@ export interface NumerologyReading {
|
||||
bazi?: { bazi_analysis: any };
|
||||
ziwei?: { ziwei_analysis: any };
|
||||
yijing?: { yijing_analysis: any };
|
||||
qimen?: { qimen_analysis: any };
|
||||
metadata: {
|
||||
analysis_time: string;
|
||||
version: string;
|
||||
|
||||
40
update_qimen_constraint.sql
Normal file
40
update_qimen_constraint.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
-- 更新numerology_readings表的CHECK约束以支持qimen类型
|
||||
-- 由于SQLite不支持直接修改CHECK约束,需要重建表
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- 创建临时表,包含新的CHECK约束
|
||||
CREATE TABLE numerology_readings_temp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
reading_type TEXT NOT NULL CHECK (reading_type IN ('bazi', 'ziwei', 'yijing', 'wuxing', 'qimen')),
|
||||
name TEXT,
|
||||
birth_date TEXT,
|
||||
birth_time TEXT,
|
||||
birth_place TEXT,
|
||||
gender TEXT,
|
||||
input_data TEXT,
|
||||
results TEXT,
|
||||
analysis TEXT,
|
||||
status TEXT DEFAULT 'completed' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 复制现有数据到临时表
|
||||
INSERT INTO numerology_readings_temp
|
||||
SELECT id, user_id, reading_type, name, birth_date, birth_time, birth_place, gender,
|
||||
input_data, results, analysis, status, created_at, updated_at
|
||||
FROM numerology_readings;
|
||||
|
||||
-- 删除原表
|
||||
DROP TABLE numerology_readings;
|
||||
|
||||
-- 重命名临时表为原表名
|
||||
ALTER TABLE numerology_readings_temp RENAME TO numerology_readings;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- 验证更新
|
||||
SELECT name FROM sqlite_master WHERE type='table' AND name='numerology_readings';
|
||||
Reference in New Issue
Block a user