lifekline / components /KLineTextTable.tsx
xiaobo ren
Rebrand to 姣旇抗: Update brand name, colors (primrose yellow #FFF143, fresh green #98FB98), logo icons, and theme throughout application
339fff1
import React, { useState, useMemo } from 'react';
import { ChevronDown, ChevronUp, Download, FileSpreadsheet, TrendingUp, TrendingDown } from 'lucide-react';
import { KLinePoint } from '../types';
interface KLineTextTableProps {
data: KLinePoint[];
birthYear?: number;
className?: string;
}
const KLineTextTable: React.FC<KLineTextTableProps> = ({
data,
birthYear,
className = '',
}) => {
const [expanded, setExpanded] = useState(false);
const currentYear = new Date().getFullYear();
// Calculate display range - default show 10 years around current age
const displayData = useMemo(() => {
if (!data || data.length === 0) return [];
if (expanded) {
return data;
}
// Find current year index
const currentYearIndex = data.findIndex(d => d.year === currentYear);
if (currentYearIndex === -1) {
// If current year not found, show first 10 items
return data.slice(0, 10);
}
// Show 5 years before and 5 years after current year
const startIndex = Math.max(0, currentYearIndex - 5);
const endIndex = Math.min(data.length, currentYearIndex + 5);
return data.slice(startIndex, endIndex);
}, [data, expanded, currentYear]);
// Export to Excel (CSV format)
const handleExportExcel = () => {
if (!data || data.length === 0) return;
// Create CSV content with BOM for Excel compatibility
const BOM = '\uFEFF';
const headers = ['年龄', '年份', '干支', '大运', '评分', '趋势', '运势分析'];
const rows = data.map(item => {
const trend = item.close >= item.open ? '上涨' : '下跌';
// Escape quotes and handle line breaks in reason
const reason = (item.reason || '').replace(/"/g, '""').replace(/\n/g, ' ');
return [
`${item.age}岁`,
item.year,
item.ganZhi || '',
item.daYun || '',
item.score,
trend,
`"${reason}"`,
].join(',');
});
const csvContent = BOM + headers.join(',') + '\n' + rows.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `比迹_流年详批_${new Date().getTime()}.csv`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
if (!data || data.length === 0) {
return null;
}
return (
<div className={`bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden ${className}`}>
{/* Header */}
<div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between bg-gradient-to-r from-gray-50 to-white">
<div className="flex items-center gap-2">
<div className="w-1 h-6 bg-indigo-600 rounded-full"></div>
<h3 className="text-lg font-bold text-gray-800 font-serif-sc">流年运势详批</h3>
<span className="text-sm text-gray-500">
({expanded ? `全部 ${data.length} 年` : `近10年`})
</span>
</div>
<div className="flex items-center gap-2">
<button
onClick={handleExportExcel}
className="flex items-center gap-1.5 px-3 py-1.5 text-sm text-emerald-600 bg-emerald-50 hover:bg-emerald-100 rounded-lg transition-colors"
>
<FileSpreadsheet className="w-4 h-4" />
导出Excel
</button>
</div>
</div>
{/* Table */}
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-gray-50 text-gray-600 font-medium">
<th className="px-4 py-3 text-center whitespace-nowrap border-r border-gray-100 w-16">年龄</th>
<th className="px-4 py-3 text-center whitespace-nowrap border-r border-gray-100 w-32">年份/干支</th>
<th className="px-4 py-3 text-center whitespace-nowrap border-r border-gray-100 w-16">大运</th>
<th className="px-4 py-3 text-center whitespace-nowrap border-r border-gray-100 w-20">评分</th>
<th className="px-4 py-3 text-left">运势分析</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
{displayData.map((item, index) => {
const isCurrentYear = item.year === currentYear;
const isUpTrend = item.close >= item.open;
const scoreColor = isUpTrend ? 'text-green-600' : 'text-red-600';
const scoreBg = isUpTrend ? 'bg-green-50' : 'bg-red-50';
return (
<tr
key={item.year}
className={`hover:bg-gray-50 transition-colors ${isCurrentYear ? 'bg-amber-50/50' : ''}`}
>
{/* Age */}
<td className="px-4 py-3 text-center border-r border-gray-100">
<span className="font-mono font-medium text-gray-700">
{item.age}<span className="text-gray-400 text-xs"></span>
</span>
</td>
{/* Year + GanZhi */}
<td className="px-4 py-3 text-center border-r border-gray-100">
<div className="flex flex-col items-center">
<span className="font-bold text-gray-800">{item.year}</span>
<span className="text-indigo-600 font-medium">{item.ganZhi || '-'}</span>
</div>
</td>
{/* DaYun */}
<td className="px-4 py-3 text-center border-r border-gray-100">
<span className="text-purple-600 font-medium">{item.daYun || '-'}</span>
</td>
{/* Score */}
<td className="px-4 py-3 text-center border-r border-gray-100">
<div className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg ${scoreBg}`}>
<span className={`font-bold ${scoreColor}`}>{item.score}</span>
{isUpTrend ? (
<TrendingUp className={`w-3.5 h-3.5 ${scoreColor}`} />
) : (
<TrendingDown className={`w-3.5 h-3.5 ${scoreColor}`} />
)}
</div>
</td>
{/* Reason */}
<td className="px-4 py-3">
<p className="text-gray-700 leading-relaxed text-justify">
{item.reason || '暂无详细分析'}
</p>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{/* Expand/Collapse Button */}
<div className="px-6 py-3 border-t border-gray-100 bg-gray-50">
<button
onClick={() => setExpanded(!expanded)}
className="w-full flex items-center justify-center gap-2 py-2 text-sm font-medium text-indigo-600 hover:text-indigo-700 hover:bg-indigo-50 rounded-lg transition-colors"
>
{expanded ? (
<>
<ChevronUp className="w-4 h-4" />
收起,仅显示近10年
</>
) : (
<>
<ChevronDown className="w-4 h-4" />
展开查看全部 {data.length} 年运势
</>
)}
</button>
</div>
</div>
);
};
export default KLineTextTable;