You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
5.8 KiB
182 lines
5.8 KiB
import { useState } from 'react';
|
|
import {
|
|
ComposedChart,
|
|
Bar,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
ResponsiveContainer,
|
|
Line
|
|
} from 'recharts';
|
|
import type { KLineData } from '@/types';
|
|
|
|
interface KLineChartProps {
|
|
data: KLineData[];
|
|
}
|
|
|
|
// 简化的K线图 - 使用折线图模拟
|
|
export function KLineChart({ data }: KLineChartProps) {
|
|
const [timeRange, setTimeRange] = useState<'1D' | '5D' | '1M' | '3M' | '6M' | '1Y'>('1M');
|
|
|
|
// 处理数据,添加颜色标记
|
|
const chartData = data.map(item => ({
|
|
...item,
|
|
isUp: item.close >= item.open,
|
|
change: item.close - item.open,
|
|
changePercent: ((item.close - item.open) / item.open * 100).toFixed(2)
|
|
}));
|
|
|
|
const CustomTooltip = ({ active, payload }: any) => {
|
|
if (active && payload && payload.length) {
|
|
const d = payload[0].payload;
|
|
const isUp = d.close >= d.open;
|
|
|
|
return (
|
|
<div className="bg-[#141414] border border-white/10 rounded-lg p-3 shadow-xl">
|
|
<div className="text-white/60 text-sm mb-2">{d.date}</div>
|
|
<div className="space-y-1 text-sm">
|
|
<div className="flex justify-between gap-4">
|
|
<span className="text-white/60">开盘:</span>
|
|
<span className="text-white">{d.open.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between gap-4">
|
|
<span className="text-white/60">最高:</span>
|
|
<span className="text-white">{d.high.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between gap-4">
|
|
<span className="text-white/60">最低:</span>
|
|
<span className="text-white">{d.low.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between gap-4">
|
|
<span className="text-white/60">收盘:</span>
|
|
<span className={isUp ? 'text-red-400' : 'text-green-400'}>{d.close.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between gap-4">
|
|
<span className="text-white/60">涨跌:</span>
|
|
<span className={isUp ? 'text-red-400' : 'text-green-400'}>
|
|
{isUp ? '+' : ''}{d.changePercent}%
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between gap-4">
|
|
<span className="text-white/60">成交量:</span>
|
|
<span className="text-white">{(d.volume / 10000).toFixed(0)}万</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// 计算均线
|
|
const ma5 = chartData.map((_, index) => {
|
|
if (index < 4) return null;
|
|
const sum = chartData.slice(index - 4, index + 1).reduce((acc, d) => acc + d.close, 0);
|
|
return sum / 5;
|
|
});
|
|
|
|
const ma10 = chartData.map((_, index) => {
|
|
if (index < 9) return null;
|
|
const sum = chartData.slice(index - 9, index + 1).reduce((acc, d) => acc + d.close, 0);
|
|
return sum / 10;
|
|
});
|
|
|
|
const chartDataWithMA = chartData.map((item, index) => ({
|
|
...item,
|
|
ma5: ma5[index],
|
|
ma10: ma10[index]
|
|
}));
|
|
|
|
const timeRanges: Array<'1D' | '5D' | '1M' | '3M' | '6M' | '1Y'> = ['1D', '5D', '1M', '3M', '6M', '1Y'];
|
|
|
|
return (
|
|
<div className="w-full">
|
|
{/* 时间范围选择 */}
|
|
<div className="flex items-center gap-2 mb-4">
|
|
{timeRanges.map((range) => (
|
|
<button
|
|
key={range}
|
|
onClick={() => setTimeRange(range)}
|
|
className={`px-3 py-1.5 rounded-lg text-sm transition-colors ${
|
|
timeRange === range
|
|
? 'bg-blue-500 text-white'
|
|
: 'bg-white/5 text-white/60 hover:bg-white/10 hover:text-white'
|
|
}`}
|
|
>
|
|
{range}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* K线图 */}
|
|
<div className="h-[350px]">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart data={chartDataWithMA} margin={{ top: 10, right: 10, left: 0, bottom: 0 }}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.05)" />
|
|
|
|
<XAxis
|
|
dataKey="date"
|
|
stroke="rgba(255,255,255,0.3)"
|
|
tick={{ fill: 'rgba(255,255,255,0.5)', fontSize: 10 }}
|
|
tickLine={false}
|
|
tickFormatter={(value) => value.slice(5)}
|
|
/>
|
|
|
|
<YAxis
|
|
domain={['auto', 'auto']}
|
|
stroke="rgba(255,255,255,0.3)"
|
|
tick={{ fill: 'rgba(255,255,255,0.5)', fontSize: 11 }}
|
|
tickLine={false}
|
|
tickFormatter={(value) => value.toFixed(2)}
|
|
orientation="right"
|
|
/>
|
|
|
|
<Tooltip content={<CustomTooltip />} />
|
|
|
|
{/* K线 - 使用Bar模拟 */}
|
|
<Bar
|
|
dataKey="close"
|
|
fill="#ef4444"
|
|
stroke="#ef4444"
|
|
barSize={8}
|
|
/>
|
|
|
|
{/* MA5 */}
|
|
<Line
|
|
type="monotone"
|
|
dataKey="ma5"
|
|
stroke="#f97316"
|
|
strokeWidth={1.5}
|
|
dot={false}
|
|
connectNulls
|
|
/>
|
|
|
|
{/* MA10 */}
|
|
<Line
|
|
type="monotone"
|
|
dataKey="ma10"
|
|
stroke="#3b82f6"
|
|
strokeWidth={1.5}
|
|
dot={false}
|
|
connectNulls
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
{/* 图例 */}
|
|
<div className="flex items-center justify-center gap-6 mt-4 text-xs">
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-0.5 bg-orange-500"></div>
|
|
<span className="text-white/50">MA5</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-4 h-0.5 bg-blue-500"></div>
|
|
<span className="text-white/50">MA10</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|