|
|
|
@ -9,13 +9,14 @@ const { Search } = Input;
|
|
|
|
const { Option } = Select;
|
|
|
|
const { Option } = Select;
|
|
|
|
const { Column } = Table;
|
|
|
|
const { Column } = Table;
|
|
|
|
|
|
|
|
|
|
|
|
const Dashboard = () => {
|
|
|
|
const Dashboard = ({ onViewDetail }) => {
|
|
|
|
const [futuresData, setFuturesData] = useState(allFuturesData);
|
|
|
|
const [futuresData, setFuturesData] = useState(allFuturesData);
|
|
|
|
const [filteredData, setFilteredData] = useState(allFuturesData);
|
|
|
|
const [filteredData, setFilteredData] = useState(allFuturesData);
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [viewMode, setViewMode] = useState('card'); // card or list
|
|
|
|
const [viewMode, setViewMode] = useState('card'); // card or list
|
|
|
|
const [filterType, setFilterType] = useState('');
|
|
|
|
const [filterType, setFilterType] = useState('');
|
|
|
|
const [filterKeyword, setFilterKeyword] = useState('');
|
|
|
|
const [filterKeyword, setFilterKeyword] = useState('');
|
|
|
|
|
|
|
|
const [sortRule, setSortRule] = useState('original'); // original, hot, bullish, bearish, neutral
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟数据刷新
|
|
|
|
// 模拟数据刷新
|
|
|
|
const handleRefresh = () => {
|
|
|
|
const handleRefresh = () => {
|
|
|
|
@ -32,10 +33,11 @@ const Dashboard = () => {
|
|
|
|
}, 1000);
|
|
|
|
}, 1000);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤数据
|
|
|
|
// 过滤和排序数据
|
|
|
|
useEffect(() => {
|
|
|
|
useEffect(() => {
|
|
|
|
let result = futuresData;
|
|
|
|
let result = futuresData;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤
|
|
|
|
if (filterKeyword) {
|
|
|
|
if (filterKeyword) {
|
|
|
|
const keyword = filterKeyword.toLowerCase();
|
|
|
|
const keyword = filterKeyword.toLowerCase();
|
|
|
|
result = result.filter(item =>
|
|
|
|
result = result.filter(item =>
|
|
|
|
@ -48,8 +50,47 @@ const Dashboard = () => {
|
|
|
|
result = result.filter(item => item.type === filterType);
|
|
|
|
result = result.filter(item => item.type === filterType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 排序
|
|
|
|
|
|
|
|
switch (sortRule) {
|
|
|
|
|
|
|
|
case 'hot':
|
|
|
|
|
|
|
|
// 按涨跌幅绝对值排序(热点)
|
|
|
|
|
|
|
|
result.sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent));
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'bullish':
|
|
|
|
|
|
|
|
// 按看多趋势排序
|
|
|
|
|
|
|
|
result.sort((a, b) => {
|
|
|
|
|
|
|
|
// 计算看多周期数量
|
|
|
|
|
|
|
|
const aBullishCount = Object.values(a.trends).filter(trend => trend.direction === '看多').length;
|
|
|
|
|
|
|
|
const bBullishCount = Object.values(b.trends).filter(trend => trend.direction === '看多').length;
|
|
|
|
|
|
|
|
return bBullishCount - aBullishCount;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'bearish':
|
|
|
|
|
|
|
|
// 按看空趋势排序
|
|
|
|
|
|
|
|
result.sort((a, b) => {
|
|
|
|
|
|
|
|
// 计算看空周期数量
|
|
|
|
|
|
|
|
const aBearishCount = Object.values(a.trends).filter(trend => trend.direction === '看空').length;
|
|
|
|
|
|
|
|
const bBearishCount = Object.values(b.trends).filter(trend => trend.direction === '看空').length;
|
|
|
|
|
|
|
|
return bBearishCount - aBearishCount;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'neutral':
|
|
|
|
|
|
|
|
// 按观望趋势排序
|
|
|
|
|
|
|
|
result.sort((a, b) => {
|
|
|
|
|
|
|
|
// 计算横盘震荡周期数量
|
|
|
|
|
|
|
|
const aNeutralCount = Object.values(a.trends).filter(trend => trend.status.includes('震荡')).length;
|
|
|
|
|
|
|
|
const bNeutralCount = Object.values(b.trends).filter(trend => trend.status.includes('震荡')).length;
|
|
|
|
|
|
|
|
return bNeutralCount - aNeutralCount;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'original':
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
// 保持原始序列
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setFilteredData(result);
|
|
|
|
setFilteredData(result);
|
|
|
|
}, [futuresData, filterKeyword, filterType]);
|
|
|
|
}, [futuresData, filterKeyword, filterType, sortRule]);
|
|
|
|
|
|
|
|
|
|
|
|
// 品种类型选项
|
|
|
|
// 品种类型选项
|
|
|
|
const typeOptions = Array.from(new Set(allFuturesData.map(item => item.type))).map(type => (
|
|
|
|
const typeOptions = Array.from(new Set(allFuturesData.map(item => item.type))).map(type => (
|
|
|
|
@ -152,10 +193,18 @@ const Dashboard = () => {
|
|
|
|
{/* AI分析 */}
|
|
|
|
{/* AI分析 */}
|
|
|
|
<Col span={24}>
|
|
|
|
<Col span={24}>
|
|
|
|
<div style={{ fontSize: '12px', fontWeight: 'bold', marginBottom: 4 }}>AI分析:</div>
|
|
|
|
<div style={{ fontSize: '12px', fontWeight: 'bold', marginBottom: 4 }}>AI分析:</div>
|
|
|
|
<div style={{ fontSize: '11px', color: '#8c8c8c', lineHeight: 1.4 }}>
|
|
|
|
<div style={{ fontSize: '11px', color: '#8c8c8c', lineHeight: 1.4, marginBottom: 8 }}>
|
|
|
|
MACD: {item.aiAnalysis.macd}<br />
|
|
|
|
MACD: {item.aiAnalysis.macd}<br />
|
|
|
|
{item.aiAnalysis.rsi}
|
|
|
|
{item.aiAnalysis.rsi}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
block
|
|
|
|
|
|
|
|
onClick={() => onViewDetail && onViewDetail(item.code, item.name)}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
查看详细分析
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
</Row>
|
|
|
|
</Card>
|
|
|
|
</Card>
|
|
|
|
@ -194,7 +243,7 @@ const Dashboard = () => {
|
|
|
|
style={{ width: '100%' }}
|
|
|
|
style={{ width: '100%' }}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
<Col span={8}>
|
|
|
|
<Col span={16}>
|
|
|
|
<Select
|
|
|
|
<Select
|
|
|
|
placeholder="按品种类型筛选"
|
|
|
|
placeholder="按品种类型筛选"
|
|
|
|
style={{ width: '100%' }}
|
|
|
|
style={{ width: '100%' }}
|
|
|
|
@ -208,7 +257,51 @@ const Dashboard = () => {
|
|
|
|
</Row>
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 品种概览区 */}
|
|
|
|
{/* 品种概览区 */}
|
|
|
|
<Card title="品种概览" style={{ marginBottom: 24 }}>
|
|
|
|
<Card
|
|
|
|
|
|
|
|
title={
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
|
|
|
|
|
|
<span>品种概览</span>
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
|
|
type={sortRule === 'original' ? 'primary' : 'default'}
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
onClick={() => setSortRule('original')}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
原始
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
|
|
type={sortRule === 'hot' ? 'primary' : 'default'}
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
onClick={() => setSortRule('hot')}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
热点
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
|
|
type={sortRule === 'bullish' ? 'primary' : 'default'}
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
onClick={() => setSortRule('bullish')}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
看多
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
|
|
type={sortRule === 'bearish' ? 'primary' : 'default'}
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
onClick={() => setSortRule('bearish')}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
看空
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
|
|
type={sortRule === 'neutral' ? 'primary' : 'default'}
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
onClick={() => setSortRule('neutral')}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
观望
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
style={{ marginBottom: 24 }}
|
|
|
|
|
|
|
|
>
|
|
|
|
<Spin spinning={loading}>
|
|
|
|
<Spin spinning={loading}>
|
|
|
|
{viewMode === 'card' ? (
|
|
|
|
{viewMode === 'card' ? (
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
|
|
@ -219,9 +312,20 @@ const Dashboard = () => {
|
|
|
|
))}
|
|
|
|
))}
|
|
|
|
</Row>
|
|
|
|
</Row>
|
|
|
|
) : (
|
|
|
|
) : (
|
|
|
|
<Table dataSource={filteredData} rowKey="code" pagination={{ pageSize: 10 }}>
|
|
|
|
<Table
|
|
|
|
<Column title="品种" dataIndex="name" key="name" render={(text, record) => `${text} (${record.code})`} />
|
|
|
|
dataSource={filteredData}
|
|
|
|
|
|
|
|
rowKey="code"
|
|
|
|
|
|
|
|
pagination={{ pageSize: 10 }}
|
|
|
|
|
|
|
|
onRow={(record) => ({
|
|
|
|
|
|
|
|
onClick: () => onViewDetail && onViewDetail(record.code, record.name),
|
|
|
|
|
|
|
|
style: { cursor: 'pointer' }
|
|
|
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<Column title="品种" dataIndex="name" key="name" render={(text, record) => `${text}-${record.code}2603`} />
|
|
|
|
<Column title="当前价格" dataIndex="currentPrice" key="currentPrice" />
|
|
|
|
<Column title="当前价格" dataIndex="currentPrice" key="currentPrice" />
|
|
|
|
|
|
|
|
<Column title="支撑" dataIndex="support" key="support" />
|
|
|
|
|
|
|
|
<Column title="压力" dataIndex="resistance" key="resistance" />
|
|
|
|
|
|
|
|
<Column title="胜率" dataIndex="winRate" key="winRate" render={(text) => `${text}%`} />
|
|
|
|
<Column
|
|
|
|
<Column
|
|
|
|
title="涨跌幅"
|
|
|
|
title="涨跌幅"
|
|
|
|
dataIndex="changePercent"
|
|
|
|
dataIndex="changePercent"
|
|
|
|
@ -232,10 +336,9 @@ const Dashboard = () => {
|
|
|
|
</span>
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
<Column title="胜率" dataIndex="winRate" key="winRate" render={(text) => `${text}%`} />
|
|
|
|
|
|
|
|
<Column title="ATR" dataIndex="atr" key="atr" />
|
|
|
|
<Column title="ATR" dataIndex="atr" key="atr" />
|
|
|
|
<Column title="ADX" dataIndex="adx" key="adx" />
|
|
|
|
<Column title="ADX" dataIndex="adx" key="adx" />
|
|
|
|
<Column title="趋势状态" dataIndex="adxStatus" key="adxStatus" />
|
|
|
|
<Column title="整体预判" dataIndex="overallView" key="overallView" />
|
|
|
|
</Table>
|
|
|
|
</Table>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
</Spin>
|
|
|
|
</Spin>
|
|
|
|
|