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.

378 lines
11 KiB

import React, { useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Card, Row, Col, Button, Select, Tag, Statistic, Alert, Spin } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined, LineChartOutlined, BarChartOutlined, AlertOutlined, CalculatorOutlined } from '@ant-design/icons';
import { fetchFutureDetail } from '../../store/futuresSlice';
import { useLocation, useNavigate } from 'react-router-dom';
import { generateKlineData } from '../../utils/mockData';
import './Detail.css';
// 导入TradingView Lightweight Charts
import { createChart } from 'lightweight-charts';
const { Option } = Select;
const Detail = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const chartRef = useRef(null);
const chartInstance = useRef(null);
const { selectedFuture, loading } = useSelector(state => state.futures);
const [timeframe, setTimeframe] = useState('1D');
// 解析URL参数获取品种信息
const getQueryParams = () => {
const params = new URLSearchParams(location.search);
return {
code: params.get('code') || 'MA',
name: params.get('name') || '甲醇'
};
};
const { code, name } = getQueryParams();
useEffect(() => {
// 获取品种详情数据
dispatch(fetchFutureDetail({ code, name }));
}, [dispatch, code, name]);
useEffect(() => {
// 初始化K线图表
if (chartRef.current && selectedFuture) {
if (chartInstance.current) {
chartInstance.current.destroy();
}
const chart = createChart(chartRef.current, {
width: chartRef.current.clientWidth,
height: 400,
layout: {
backgroundColor: '#fff',
textColor: '#262626'
},
grid: {
vertLines: {
color: '#f0f0f0'
},
horzLines: {
color: '#f0f0f0'
}
},
priceScale: {
borderColor: '#f0f0f0'
},
timeScale: {
borderColor: '#f0f0f0',
timeVisible: true,
secondsVisible: false
}
});
// 添加K线系列
const candlestickSeries = chart.addCandlestickSeries({
upColor: '#52c41a',
downColor: '#ff4d4f',
borderUpColor: '#52c41a',
borderDownColor: '#ff4d4f',
wickUpColor: '#52c41a',
wickDownColor: '#ff4d4f'
});
// 生成K线数据
const klineData = generateKlineData(30);
candlestickSeries.setData(klineData);
// 添加成交量系列
const volumeSeries = chart.addHistogramSeries({
color: '#82ca9d',
lineWidth: 1,
priceScaleId: '',
scaleMargins: {
top: 0.8,
bottom: 0
}
});
const volumeData = klineData.map(item => ({
time: item.time,
value: item.volume,
color: item.close >= item.open ? '#52c41a' : '#ff4d4f'
}));
volumeSeries.setData(volumeData);
// 缩放到合适的范围
chart.timeScale().fitContent();
chartInstance.current = chart;
// 响应窗口大小变化
const handleResize = () => {
if (chartInstance.current) {
chartInstance.current.resize(chartRef.current.clientWidth, 400);
}
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
if (chartInstance.current) {
chartInstance.current.destroy();
}
};
}
}, [selectedFuture]);
const handleBack = () => {
navigate('/');
};
const getChangeColor = (changePercent) => {
return changePercent >= 0 ? '#52c41a' : '#ff4d4f';
};
const getChangeIcon = (changePercent) => {
return changePercent >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />;
};
const getTrendColor = (direction) => {
if (direction === '看多') return '#52c41a';
if (direction === '看空') return '#ff4d4f';
return '#faad14';
};
if (loading) {
return (
<div className="loading-container">
<Spin size="large" tip="加载数据中..." />
</div>
);
}
if (!selectedFuture) {
return (
<div className="error-container">
<Alert message="未找到品种数据" type="error" />
<Button type="primary" onClick={handleBack} style={{ marginTop: 16 }}>
返回主页
</Button>
</div>
);
}
return (
<div className="detail">
{/* 页面头部 */}
<div className="detail-header">
<Button type="default" onClick={handleBack} style={{ marginBottom: 16 }}>
返回主页
</Button>
<h2>{selectedFuture.fullName}</h2>
</div>
{/* 基本信息 */}
<Card className="detail-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={8}>
<Statistic
title="当前价格"
value={selectedFuture.currentPrice}
valueStyle={{ color: '#262626' }}
/>
</Col>
<Col span={8}>
<Statistic
title="涨跌幅"
value={Math.abs(selectedFuture.changePercent)}
suffix="%"
valueStyle={{ color: getChangeColor(selectedFuture.changePercent) }}
prefix={getChangeIcon(selectedFuture.changePercent)}
/>
</Col>
<Col span={8}>
<Statistic
title="胜率"
value={selectedFuture.winRate}
suffix="%"
valueStyle={{ color: selectedFuture.winRate > 60 ? '#52c41a' : selectedFuture.winRate > 40 ? '#faad14' : '#ff4d4f' }}
/>
</Col>
<Col span={8}>
<Statistic
title="ATR"
value={selectedFuture.atr}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
<Col span={8}>
<Statistic
title="ADX"
value={selectedFuture.adx}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
<Col span={8}>
<Statistic
title="趋势状态"
value={selectedFuture.adxStatus}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
</Row>
</Card>
{/* K线图表 */}
<Card
title={
<div className="chart-title">
<LineChartOutlined /> K线图表
<Select
defaultValue="1D"
style={{ width: 120, marginLeft: 16 }}
onChange={setTimeframe}
>
<Option value="5MIN">5分钟</Option>
<Option value="30MIN">30分钟</Option>
<Option value="1H">1小时</Option>
<Option value="1D">1</Option>
<Option value="1W">1</Option>
</Select>
</div>
}
className="detail-card"
style={{ marginBottom: 24 }}
>
<div ref={chartRef} className="kline-chart"></div>
</Card>
{/* 多周期趋势分析 */}
<Card
title={<span><BarChartOutlined /> 多周期趋势分析</span>}
className="detail-card"
style={{ marginBottom: 24 }}
>
<Row gutter={[16, 16]}>
{Object.entries(selectedFuture.trends).map(([period, trend]) => (
<Col span={6} key={period}>
<Card className="trend-card">
<div className="trend-header">
<h4>{period}</h4>
<Tag color={getTrendColor(trend.direction)}>
{trend.direction}
</Tag>
</div>
<div className="trend-status">
{trend.status}
</div>
<div className="trend-rsi">
RSI: {trend.rsi}
</div>
</Card>
</Col>
))}
</Row>
</Card>
{/* 技术指标 */}
<Card
title="技术指标"
className="detail-card"
style={{ marginBottom: 24 }}
>
<Row gutter={[16, 16]}>
<Col span={6}>
<div className="indicator-item">
<div className="indicator-label">MACD</div>
<div className="indicator-value">{selectedFuture.indicators.macd}</div>
</div>
</Col>
<Col span={6}>
<div className="indicator-item">
<div className="indicator-label">RSI</div>
<div className="indicator-value">{selectedFuture.indicators.rsi}</div>
</div>
</Col>
<Col span={6}>
<div className="indicator-item">
<div className="indicator-label">布林带</div>
<div className="indicator-value">{selectedFuture.indicators.bollinger}</div>
</div>
</Col>
<Col span={6}>
<div className="indicator-item">
<div className="indicator-label">KDJ</div>
<div className="indicator-value">{selectedFuture.indicators.kdj}</div>
</div>
</Col>
</Row>
</Card>
{/* 交易建议 */}
<Card
title={<span><CalculatorOutlined /> 交易建议</span>}
className="detail-card"
style={{ marginBottom: 24 }}
>
<Row gutter={[16, 16]}>
<Col span={8}>
<Statistic
title="入场价"
value={selectedFuture.tradingAdvice.entry}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
<Col span={8}>
<Statistic
title="止损价"
value={selectedFuture.tradingAdvice.stopLoss}
valueStyle={{ color: '#ff4d4f' }}
/>
</Col>
<Col span={8}>
<Statistic
title="目标价"
value={selectedFuture.tradingAdvice.target}
valueStyle={{ color: '#52c41a' }}
/>
</Col>
</Row>
</Card>
{/* 风险评估 */}
<Card
title={<span><AlertOutlined /> 风险评估</span>}
className="detail-card"
>
<Row gutter={[16, 16]}>
<Col span={12}>
<div className="risk-item">
<div className="risk-label">风险等级</div>
<Tag color={selectedFuture.riskLevel === '高' ? 'red' : selectedFuture.riskLevel === '中等' ? 'orange' : 'green'}>
{selectedFuture.riskLevel}
</Tag>
</div>
</Col>
<Col span={12}>
<div className="risk-item">
<div className="risk-label">波动率</div>
<Tag color={selectedFuture.volatility === '高' ? 'red' : selectedFuture.volatility === '中等' ? 'orange' : 'green'}>
{selectedFuture.volatility}
</Tag>
</div>
</Col>
</Row>
<Alert
message="风险提示"
description="期货交易具有高风险,请根据自身风险承受能力合理控制仓位,严格执行止损策略。"
type="warning"
showIcon
style={{ marginTop: 16 }}
/>
</Card>
</div>
);
};
export default Detail;