ziweidoushu / src /components /AnalysisPanel.tsx
luoly2026
Deploy: Fresh initial commit for Hugging Face
631a7d3
Raw
History Blame Contribute Delete
16.6 kB
import React, { useState } from 'react';
import { RuleBasedReport, RuleEvidence, TimeLayerScope } from '../core-engine/types';
interface AnalysisPanelProps {
report: RuleBasedReport;
activeScope: TimeLayerScope;
onScopeChange: (scope: TimeLayerScope) => void;
onClose: () => void;
}
const AnalysisPanel: React.FC<AnalysisPanelProps> = ({ report, activeScope, onScopeChange, onClose }) => {
const [activeTab, setActiveTab] = useState<'overview' | 'topics' | 'audit'>('overview');
const currentReport = report.scopedReports[activeScope] || report.globalReport;
return (
<div className="fixed top-0 right-0 w-[500px] h-full bg-gray-50 shadow-2xl border-l border-gray-200 overflow-y-auto z-50 flex flex-col font-sans">
{/* 头部 */}
<div className="p-4 bg-indigo-900 text-white flex justify-between items-center sticky top-0 z-10 shadow-md">
<div>
<h2 className="text-lg font-bold">紫微斗数深度解析报告</h2>
<div className="text-xs text-indigo-200 mt-1 flex space-x-2">
<span>静态引擎: static-v2.0.0</span>
<span>|</span>
<span>动态引擎: dynamic-v2.0.0</span>
</div>
</div>
<button onClick={onClose} className="text-gray-300 hover:text-white transition-colors bg-white/10 p-1.5 rounded-md">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 时间层切换栏 */}
<div className="bg-indigo-800 p-2 flex space-x-2 overflow-x-auto sticky top-[72px] z-10 shadow-sm hide-scrollbar">
{report.activeScopes.map(scope => {
const labels: Record<string, string> = {
original: '本命全局',
daXian: '大限(十年)',
liuNian: '流年(当年)',
liuYue: '流月',
liuRi: '流日',
liuShi: '流时'
};
return (
<button
key={scope}
onClick={() => onScopeChange(scope)}
className={`whitespace-nowrap px-3 py-1.5 text-xs font-bold rounded-md transition-colors ${
activeScope === scope
? 'bg-white text-indigo-900 shadow-sm'
: 'bg-indigo-900/50 text-indigo-100 hover:bg-indigo-800'
}`}
>
{labels[scope]}
</button>
);
})}
</div>
{/* Tab 切换 */}
<div className="flex bg-white border-b sticky top-[116px] z-10">
<button
className={`flex-1 py-3 text-sm font-bold ${activeTab === 'overview' ? 'text-indigo-600 border-b-2 border-indigo-600' : 'text-gray-500 hover:bg-gray-50'}`}
onClick={() => setActiveTab('overview')}
>
综合报告
</button>
<button
className={`flex-1 py-3 text-sm font-bold ${activeTab === 'topics' ? 'text-indigo-600 border-b-2 border-indigo-600' : 'text-gray-500 hover:bg-gray-50'}`}
onClick={() => setActiveTab('topics')}
>
时间层与专题
</button>
<button
className={`flex-1 py-3 text-sm font-bold ${activeTab === 'audit' ? 'text-indigo-600 border-b-2 border-indigo-600' : 'text-gray-500 hover:bg-gray-50'}`}
onClick={() => setActiveTab('audit')}
>
规则审计
</button>
</div>
<div className="p-5 flex-1 overflow-y-auto space-y-6">
{activeTab === 'overview' && (
<>
{/* 1. 总览 */}
<section className="bg-white p-5 rounded-xl shadow-sm border border-gray-100">
<div className="flex items-center justify-between mb-4 pb-3 border-b border-gray-100">
<h3 className="font-bold text-gray-800 text-lg flex items-center">
<span className="text-indigo-500 mr-2">📊</span> 命局总览 ({activeScope === 'original' ? '本命全局' : '当前时间层'})
</h3>
<div className="text-right">
<div className="text-3xl font-black text-indigo-600">{currentReport.synthesis.totalScore}</div>
<div className="text-xs text-gray-400">综合得分 (满分100)</div>
</div>
</div>
<p className="text-gray-700 font-medium mb-2">{currentReport.output.overview.summary}</p>
<ul className="space-y-2 text-sm text-gray-600 mt-4">
<li className="flex items-start"><span className="text-green-500 mr-2"></span> {currentReport.output.overview.strength}</li>
<li className="flex items-start"><span className="text-red-500 mr-2"></span> {currentReport.output.overview.risk}</li>
<li className="flex items-start"><span className="text-blue-500 mr-2"></span> {currentReport.output.overview.focus}</li>
</ul>
</section>
{/* 2. 静态结论 */}
<section className="bg-white p-5 rounded-xl shadow-sm border border-gray-100">
<h3 className="font-bold text-gray-800 text-lg mb-4 flex items-center">
<span className="text-blue-500 mr-2">🏛️</span> 先天底色 (静态格局)
</h3>
<div className="space-y-4">
<div>
<h4 className="text-sm font-bold text-gray-700 mb-1">性格特质</h4>
<p className="text-sm text-gray-600 leading-relaxed">{currentReport.output.staticBase.traits.join(' ')}</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="bg-green-50 p-3 rounded-lg border border-green-100">
<h4 className="text-xs font-bold text-green-800 mb-2">先天优势</h4>
<ul className="list-disc pl-4 text-xs text-green-700 space-y-1">
{currentReport.output.staticBase.pros.map((p, i) => <li key={i}>{p}</li>)}
</ul>
</div>
<div className="bg-red-50 p-3 rounded-lg border border-red-100">
<h4 className="text-xs font-bold text-red-800 mb-2">潜在隐患</h4>
<ul className="list-disc pl-4 text-xs text-red-700 space-y-1">
{currentReport.output.staticBase.cons.map((c, i) => <li key={i}>{c}</li>)}
</ul>
</div>
</div>
</div>
</section>
{/* 3. 动态结论 */}
<section className="bg-white p-5 rounded-xl shadow-sm border border-gray-100">
<h3 className="font-bold text-gray-800 text-lg mb-4 flex items-center">
<span className="text-purple-500 mr-2">🌪️</span> 后天推演 (动态四化)
</h3>
<div className="space-y-3 text-sm text-gray-600">
<div className="bg-purple-50 p-3 rounded-lg border border-purple-100">
<span className="font-bold text-purple-800 block mb-1">引动宫位:</span>
{currentReport.output.dynamicTrend.triggeredPalaces.length > 0 ? currentReport.output.dynamicTrend.triggeredPalaces.join('、') : '无明显引动'}
</div>
<div>
<span className="font-bold text-gray-800 block mb-1">风险与机遇链条:</span>
<ul className="list-disc pl-5 space-y-1">
{currentReport.output.dynamicTrend.risksAndOps.length > 0 ? currentReport.output.dynamicTrend.risksAndOps.map((r, i) => <li key={i}>{r}</li>) : <li>无明显动态链</li>}
</ul>
</div>
<div className="text-xs text-gray-400 mt-2 italic">{currentReport.output.dynamicTrend.timing}</div>
</div>
</section>
{/* 4. 天纪处方 */}
<section className="bg-gradient-to-br from-red-50 to-orange-50 p-5 rounded-xl shadow-sm border border-red-200">
<h3 className="font-bold text-red-800 text-lg mb-4 flex items-center">
<span className="text-red-600 mr-2">📜</span> 天纪派处方建议
</h3>
<ul className="space-y-3">
{currentReport.output.prescriptions.map((adv, i) => (
<li key={i} className="text-red-900 text-sm leading-relaxed font-medium bg-white/60 p-3 rounded border border-red-100">
{adv}
</li>
))}
</ul>
</section>
{/* 5. 能力边界标识 */}
<section className="bg-gray-50 p-4 rounded-xl border border-gray-200 mt-6 text-xs text-gray-500">
<h4 className="font-bold text-gray-700 mb-2 flex items-center">
<span className="mr-1">ℹ️</span> 推演层级与能力边界说明
</h4>
<ul className="space-y-1">
<li className="flex items-start"><span className="text-green-500 mr-1"></span> <strong>已推演:</strong>本命盘静态格局、生年四化、大限流年流月流日流时全时间层引动、天纪派断语字典接入、静动态综合评分、全报告时间层切换。</li>
<li className="flex items-start"><span className="text-orange-400 mr-1"></span> <strong>待接入:</strong>外部规则知识库检索(已预留 `KnowledgeSource` 接口)、体用链深度推演。</li>
</ul>
</section>
</>
)}
{activeTab === 'topics' && (
<div className="space-y-6">
{/* 核心专题展示 */}
<div className="bg-white p-4 rounded-xl shadow-sm border border-gray-100">
<h3 className="font-bold text-gray-800 mb-4 flex items-center">
<span className="text-purple-500 mr-2">🎯</span> 核心人生专题 ({activeScope === 'original' ? '本命盘' : '当前时间层'})
</h3>
<div className="space-y-4">
{Object.entries({
'事业 (官禄)': currentReport.static.topics.career,
'财富 (财帛)': currentReport.static.topics.wealth,
'婚姻 (夫妻)': currentReport.static.topics.marriage,
'健康 (疾厄)': currentReport.static.topics.health,
'资产 (田宅)': currentReport.static.topics.property,
}).map(([title, evidences]) => (
<div key={title} className="border border-gray-100 rounded-lg p-3">
<h4 className="font-bold text-sm text-gray-700 mb-2 border-b border-gray-50 pb-1">{title}</h4>
{evidences.length > 0 ? (
<ul className="space-y-2">
{evidences.map((ev, idx) => (
<li key={idx} className="text-xs text-gray-600">
<span className="font-semibold text-gray-800">{ev.title}: </span>
{ev.description}
</li>
))}
</ul>
) : (
<div className="text-xs text-gray-400 italic">该专题在本层未见明显吉凶极端组合,属平稳态。</div>
)}
</div>
))}
</div>
</div>
</div>
)}
{activeTab === 'audit' && (
<div className="space-y-6">
{/* 分项得分雷达图替代文字 */}
<div className="bg-white p-4 rounded-xl shadow-sm border border-gray-100">
<h3 className="font-bold text-gray-800 mb-4">维度得分明细 ({activeScope === 'original' ? '本命全局' : '当前时间层'})</h3>
<div className="grid grid-cols-2 gap-4 text-sm">
<div className="flex justify-between p-2 bg-gray-50 rounded"><span className="text-gray-600">事业</span><span className="font-bold text-indigo-600">{currentReport.synthesis.dimensionScores.career}</span></div>
<div className="flex justify-between p-2 bg-gray-50 rounded"><span className="text-gray-600">财富</span><span className="font-bold text-indigo-600">{currentReport.synthesis.dimensionScores.wealth}</span></div>
<div className="flex justify-between p-2 bg-gray-50 rounded"><span className="text-gray-600">婚姻</span><span className="font-bold text-indigo-600">{currentReport.synthesis.dimensionScores.marriage}</span></div>
<div className="flex justify-between p-2 bg-gray-50 rounded"><span className="text-gray-600">健康</span><span className="font-bold text-indigo-600">{currentReport.synthesis.dimensionScores.health}</span></div>
<div className="flex justify-between p-2 bg-gray-50 rounded col-span-2"><span className="text-gray-600">置信度评估</span><span className={`font-bold ${currentReport.synthesis.confidence === 'High' ? 'text-green-600' : currentReport.synthesis.confidence === 'Low' ? 'text-red-600' : 'text-yellow-600'}`}>{currentReport.synthesis.confidence}</span></div>
</div>
</div>
{/* 规则审计列表 */}
<div>
<h3 className="font-bold text-gray-800 mb-3 pl-1">触发规则证据链 (Audit Log)</h3>
<div className="space-y-3">
{currentReport.allEvidences.map((ev, i) => (
<RuleCard key={i} evidence={ev} />
))}
</div>
</div>
</div>
)}
</div>
</div>
);
};
const RuleCard: React.FC<{ evidence: RuleEvidence }> = ({ evidence }) => {
const [expanded, setExpanded] = useState(false);
const isPositive = evidence.scoreImpact > 0;
const isNegative = evidence.scoreImpact < 0;
return (
<div className="bg-white border border-gray-200 rounded-lg shadow-sm overflow-hidden text-sm">
<div
className="p-3 cursor-pointer hover:bg-gray-50 flex justify-between items-center"
onClick={() => setExpanded(!expanded)}
>
<div className="flex items-center space-x-2">
<span className={`w-2 h-2 rounded-full ${isPositive ? 'bg-green-500' : isNegative ? 'bg-red-500' : 'bg-gray-400'}`}></span>
<span className="font-bold text-gray-800">{evidence.title}</span>
<span className="text-xs text-gray-400 bg-gray-100 px-1.5 py-0.5 rounded border">{evidence.type}</span>
</div>
<div className="flex items-center space-x-3">
<span className={`font-bold ${isPositive ? 'text-green-600' : isNegative ? 'text-red-600' : 'text-gray-500'}`}>
{evidence.scoreImpact > 0 ? '+' : ''}{evidence.scoreImpact}
</span>
<svg className={`w-4 h-4 text-gray-400 transition-transform ${expanded ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
{expanded && (
<div className="p-4 bg-gray-50 border-t border-gray-100 space-y-3 text-xs text-gray-600">
<div><strong className="text-gray-700 block mb-1">结论描述:</strong> {evidence.description}</div>
{evidence.boardEvidence.length > 0 && (
<div>
<strong className="text-gray-700 block mb-1">盘面证据:</strong>
<ul className="list-disc pl-4 text-gray-500">{evidence.boardEvidence.map((b, i) => <li key={i}>{b}</li>)}</ul>
</div>
)}
{evidence.conflicts && evidence.conflicts.length > 0 && (
<div>
<strong className="text-red-700 block mb-1">冲突制化项:</strong>
<ul className="list-disc pl-4 text-red-600">{evidence.conflicts.map((c, i) => <li key={i}>{c}</li>)}</ul>
</div>
)}
<div className="flex justify-between items-center pt-2 border-t border-gray-200 mt-2">
<span className="text-gray-400 font-mono">Rule ID: {evidence.ruleId}</span>
<div className="flex space-x-1">
{evidence.tags.map((tag, i) => (
<span key={i} className="bg-indigo-50 text-indigo-600 px-1.5 py-0.5 rounded text-[10px]">{tag}</span>
))}
</div>
</div>
</div>
)}
</div>
);
};
export default AnalysisPanel;