feat: 完整实现奇门遁甲功能并优化显示效果

主要功能实现:
- 新增奇门遁甲分析完整功能模块
- 实现奇门盘可视化展示
- 添加用神分析、格局识别、预测结果等核心功能
- 集成AI解读和PDF导出功能
- 扩展历史记录支持奇门遁甲类型

显示优化:
- 修复时机评估[object Object]显示问题
- 优化时机评估显示为简洁格式
- 完善英文字段中文化映射
- 移除重复的成功概率显示
- 统一数值显示格式(小数转整数)

技术改进:
- 扩展类型定义支持奇门遁甲
- 完善API接口和路由
- 优化错误处理和用户体验
- 统一前后端字段映射机制
This commit is contained in:
patdelphi
2025-08-25 21:56:31 +08:00
parent 5af9d01bfa
commit 0f3e1f406f
21 changed files with 2661 additions and 189 deletions

View File

@@ -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,

View File

@@ -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 部署时启用静态文件服务

View File

@@ -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 || '用户';

View File

@@ -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: '不支持的分析类型',

View File

@@ -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,

View File

@@ -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
};

View File

@@ -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)
};
}

View File

@@ -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) {

View File

@@ -527,7 +527,7 @@ class SolarTerms {
* @returns {Date} 立春时间
*/
getSpringBeginning(year) {
return this.calculateSolarTerm(year, 0);
return this.calculateSolarTermDate(year, '立春');
}
/**

View File

@@ -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 (

File diff suppressed because it is too large Load Diff

View File

@@ -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 '命理';
}
};

View File

@@ -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="💡 提示:问题越具体,占卜结果越准确。您可以使用预设问题或自行输入。"

View File

@@ -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;
};

View File

@@ -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', {

View File

@@ -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>

View File

@@ -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"
/>

View File

@@ -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'
}
];

View File

@@ -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] || '命理';
};

View File

@@ -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;

View 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';