community增加事件详情
This commit is contained in:
@@ -409,11 +409,13 @@ CalendarCell.displayName = 'CalendarCell';
|
||||
const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading }) => {
|
||||
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
|
||||
const [selectedContent, setSelectedContent] = useState(null);
|
||||
const [ztViewMode, setZtViewMode] = useState('sector'); // 'sector' | 'stock'
|
||||
|
||||
// 板块数据处理 - 必须在条件返回之前调用所有hooks
|
||||
const sectorList = useMemo(() => {
|
||||
if (!ztDetail?.sector_data) return [];
|
||||
return Object.entries(ztDetail.sector_data)
|
||||
.filter(([name]) => name !== '其他')
|
||||
.map(([name, data]) => ({
|
||||
name,
|
||||
count: data.count,
|
||||
@@ -422,6 +424,21 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}, [ztDetail]);
|
||||
|
||||
// 股票详情数据处理
|
||||
const stockList = useMemo(() => {
|
||||
if (!ztDetail?.stock_infos) return [];
|
||||
return ztDetail.stock_infos.map(stock => ({
|
||||
...stock,
|
||||
key: stock.scode,
|
||||
}));
|
||||
}, [ztDetail]);
|
||||
|
||||
// 热门关键词
|
||||
const hotKeywords = useMemo(() => {
|
||||
if (!ztDetail?.word_freq_data) return [];
|
||||
return ztDetail.word_freq_data.slice(0, 12);
|
||||
}, [ztDetail]);
|
||||
|
||||
// 条件返回必须在所有hooks之后
|
||||
if (!selectedDate) return null;
|
||||
|
||||
@@ -456,28 +473,33 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
|
||||
{
|
||||
title: '排名',
|
||||
key: 'rank',
|
||||
width: 60,
|
||||
width: 55,
|
||||
align: 'center',
|
||||
render: (_, __, index) => (
|
||||
<Tag color={index < 3 ? 'gold' : 'default'} style={{ fontWeight: 'bold' }}>
|
||||
<Tag color={index < 3 ? 'gold' : 'default'} style={{ fontWeight: 'bold', margin: 0 }}>
|
||||
{index + 1}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '板块名称',
|
||||
title: '板块',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (name) => <AntText strong>{name}</AntText>,
|
||||
width: 120,
|
||||
render: (name) => (
|
||||
<AntText strong style={{ color: '#FFD700' }}>{name}</AntText>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '涨停数',
|
||||
dataIndex: 'count',
|
||||
key: 'count',
|
||||
width: 100,
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: (count) => (
|
||||
<Tag color={count >= 10 ? 'red' : count >= 5 ? 'orange' : 'blue'} style={{ fontSize: '14px' }}>
|
||||
<FireOutlined style={{ marginRight: 4 }} />
|
||||
{count}家
|
||||
<Tag color={count >= 8 ? 'red' : count >= 5 ? 'volcano' : count >= 3 ? 'orange' : 'blue'}>
|
||||
<FireOutlined style={{ marginRight: 3 }} />
|
||||
{count}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
@@ -485,27 +507,127 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
|
||||
title: '涨停股票',
|
||||
dataIndex: 'stocks',
|
||||
key: 'stocks',
|
||||
render: (stocks) => (
|
||||
<Space wrap>
|
||||
{stocks.slice(0, 8).map((code) => (
|
||||
<Tag key={code} style={{ cursor: 'pointer' }}>
|
||||
<a
|
||||
href={`https://valuefrontier.cn/company?scode=${code}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: 'inherit' }}
|
||||
render: (stocks) => {
|
||||
// 根据股票代码查找股票名称
|
||||
const getStockName = (code) => {
|
||||
const stockInfo = stockList.find(s => s.scode === code);
|
||||
return stockInfo?.sname || code;
|
||||
};
|
||||
return (
|
||||
<Space wrap size={[4, 4]}>
|
||||
{stocks.slice(0, 6).map((code) => (
|
||||
<Tag
|
||||
key={code}
|
||||
style={{ cursor: 'pointer', margin: 0 }}
|
||||
color="processing"
|
||||
>
|
||||
{code}
|
||||
</a>
|
||||
</Tag>
|
||||
))}
|
||||
{stocks.length > 8 && <Tag>+{stocks.length - 8}</Tag>}
|
||||
</Space>
|
||||
),
|
||||
<a
|
||||
href={`https://valuefrontier.cn/company?scode=${code}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: 'inherit' }}
|
||||
>
|
||||
{getStockName(code)}
|
||||
</a>
|
||||
</Tag>
|
||||
))}
|
||||
{stocks.length > 6 && (
|
||||
<Tag style={{ margin: 0 }}>+{stocks.length - 6}</Tag>
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 事件表格列(参考投资日历)
|
||||
// 涨停股票详情表格列
|
||||
const stockColumns = [
|
||||
{
|
||||
title: '股票',
|
||||
key: 'stock',
|
||||
width: 120,
|
||||
fixed: 'left',
|
||||
render: (_, record) => (
|
||||
<a
|
||||
href={`https://valuefrontier.cn/company?scode=${record.scode}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ color: '#FFD700', fontWeight: 'bold' }}
|
||||
>
|
||||
{record.sname}
|
||||
</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '涨停时间',
|
||||
dataIndex: 'formatted_time',
|
||||
key: 'time',
|
||||
width: 85,
|
||||
align: 'center',
|
||||
render: (time) => (
|
||||
<Tag color={time <= '09:30:00' ? 'red' : time <= '10:00:00' ? 'volcano' : 'default'}>
|
||||
{time?.substring(0, 5) || '-'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '连板',
|
||||
dataIndex: 'continuous_days',
|
||||
key: 'continuous',
|
||||
width: 75,
|
||||
align: 'center',
|
||||
render: (text) => {
|
||||
if (!text || text === '首板') return <Tag>首板</Tag>;
|
||||
const match = text.match(/(\d+)/);
|
||||
const days = match ? parseInt(match[1]) : 1;
|
||||
return (
|
||||
<Tag color={days >= 5 ? 'red' : days >= 3 ? 'volcano' : days >= 2 ? 'orange' : 'default'}>
|
||||
{text}
|
||||
</Tag>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '核心板块',
|
||||
dataIndex: 'core_sectors',
|
||||
key: 'sectors',
|
||||
width: 180,
|
||||
render: (sectors) => (
|
||||
<Space wrap size={[2, 2]}>
|
||||
{(sectors || []).slice(0, 3).map((sector, idx) => (
|
||||
<Tag key={idx} color="gold" style={{ margin: 0, fontSize: '12px' }}>
|
||||
{sector}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '涨停简报',
|
||||
dataIndex: 'brief',
|
||||
key: 'brief',
|
||||
ellipsis: true,
|
||||
render: (text) => {
|
||||
if (!text) return <AntText type="secondary">-</AntText>;
|
||||
// 移除HTML标签
|
||||
const cleanText = text.replace(/<br\s*\/?>/gi, ' ').replace(/<[^>]+>/g, '');
|
||||
return (
|
||||
<Tooltip title={cleanText} placement="topLeft" overlayStyle={{ maxWidth: 500 }}>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => showContentDetail(text.replace(/<br\s*\/?>/gi, '\n\n'), '涨停简报')}
|
||||
style={{ padding: 0, height: 'auto', whiteSpace: 'normal', textAlign: 'left' }}
|
||||
>
|
||||
{cleanText.length > 40 ? cleanText.substring(0, 40) + '...' : cleanText}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 事件表格列(参考投资日历)- 去掉相关概念列
|
||||
const eventColumns = [
|
||||
{
|
||||
title: '时间',
|
||||
@@ -599,30 +721,6 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '相关概念',
|
||||
dataIndex: 'concepts',
|
||||
key: 'concepts',
|
||||
width: 200,
|
||||
render: (concepts) => (
|
||||
<Space wrap>
|
||||
{concepts && concepts.length > 0 ? (
|
||||
concepts.slice(0, 3).map((concept, index) => (
|
||||
<Tag key={index} icon={<TagsOutlined />}>
|
||||
{typeof concept === 'string'
|
||||
? concept
|
||||
: (concept?.concept || concept?.name || '未知')}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<AntText type="secondary">无</AntText>
|
||||
)}
|
||||
{concepts && concepts.length > 3 && (
|
||||
<Tag>+{concepts.length - 3}</Tag>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -696,15 +794,88 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
|
||||
key="zt"
|
||||
disabled={!ztDetail}
|
||||
>
|
||||
{sectorList.length > 0 ? (
|
||||
<Table
|
||||
dataSource={sectorList}
|
||||
columns={sectorColumns}
|
||||
rowKey="name"
|
||||
size="middle"
|
||||
pagination={false}
|
||||
scroll={{ y: 450 }}
|
||||
/>
|
||||
{(sectorList.length > 0 || stockList.length > 0) ? (
|
||||
<VStack spacing={4} align="stretch">
|
||||
{/* 热门关键词 */}
|
||||
{hotKeywords.length > 0 && (
|
||||
<Box
|
||||
p={3}
|
||||
bg="rgba(255, 215, 0, 0.05)"
|
||||
borderRadius="lg"
|
||||
border="1px solid rgba(255, 215, 0, 0.15)"
|
||||
>
|
||||
<HStack spacing={2} mb={2}>
|
||||
<FireOutlined style={{ color: '#FFD700' }} />
|
||||
<Text fontSize="sm" fontWeight="bold" color="gold">
|
||||
今日热词
|
||||
</Text>
|
||||
</HStack>
|
||||
<Space wrap size={[6, 6]}>
|
||||
{hotKeywords.map((kw, idx) => (
|
||||
<Tag
|
||||
key={kw.name}
|
||||
color={idx < 3 ? 'gold' : idx < 6 ? 'orange' : 'default'}
|
||||
style={{
|
||||
fontSize: idx < 3 ? '14px' : '13px',
|
||||
fontWeight: idx < 3 ? 'bold' : 'normal',
|
||||
}}
|
||||
>
|
||||
{kw.name}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 视图切换按钮 */}
|
||||
<HStack justify="space-between" align="center">
|
||||
<HStack spacing={2}>
|
||||
<Button
|
||||
size="small"
|
||||
type={ztViewMode === 'sector' ? 'primary' : 'default'}
|
||||
onClick={() => setZtViewMode('sector')}
|
||||
icon={<TagsOutlined />}
|
||||
>
|
||||
按板块 ({sectorList.length})
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type={ztViewMode === 'stock' ? 'primary' : 'default'}
|
||||
onClick={() => setZtViewMode('stock')}
|
||||
icon={<StockOutlined />}
|
||||
>
|
||||
按个股 ({stockList.length})
|
||||
</Button>
|
||||
</HStack>
|
||||
<Text fontSize="sm" color="whiteAlpha.600">
|
||||
共 {ztDetail?.total_stocks || 0} 只涨停
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
{/* 板块视图 */}
|
||||
{ztViewMode === 'sector' && (
|
||||
<Table
|
||||
dataSource={sectorList}
|
||||
columns={sectorColumns}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
pagination={false}
|
||||
scroll={{ y: 350 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 个股视图 */}
|
||||
{ztViewMode === 'stock' && (
|
||||
<Table
|
||||
dataSource={stockList}
|
||||
columns={stockColumns}
|
||||
rowKey="scode"
|
||||
size="small"
|
||||
pagination={false}
|
||||
scroll={{ x: 800, y: 350 }}
|
||||
/>
|
||||
)}
|
||||
</VStack>
|
||||
) : (
|
||||
<Center h="200px">
|
||||
<VStack>
|
||||
@@ -727,38 +898,14 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
|
||||
disabled={!events?.length}
|
||||
>
|
||||
{events?.length > 0 ? (
|
||||
<Tabs defaultActiveKey="all" size="small">
|
||||
<TabPane tab={`全部 (${events.length})`} key="all">
|
||||
<Table
|
||||
dataSource={events}
|
||||
columns={eventColumns}
|
||||
rowKey="id"
|
||||
size="middle"
|
||||
pagination={false}
|
||||
scroll={{ x: 1000, y: 400 }}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab={`事件 (${events.filter(e => e.type === 'event').length})`} key="eventType">
|
||||
<Table
|
||||
dataSource={events.filter(e => e.type === 'event')}
|
||||
columns={eventColumns}
|
||||
rowKey="id"
|
||||
size="middle"
|
||||
pagination={false}
|
||||
scroll={{ x: 1000, y: 400 }}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab={`数据 (${events.filter(e => e.type === 'data').length})`} key="data">
|
||||
<Table
|
||||
dataSource={events.filter(e => e.type === 'data')}
|
||||
columns={eventColumns}
|
||||
rowKey="id"
|
||||
size="middle"
|
||||
pagination={false}
|
||||
scroll={{ x: 1000, y: 400 }}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
<Table
|
||||
dataSource={events}
|
||||
columns={eventColumns}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
pagination={false}
|
||||
scroll={{ x: 900, y: 420 }}
|
||||
/>
|
||||
) : (
|
||||
<Center h="200px">
|
||||
<VStack>
|
||||
|
||||
Reference in New Issue
Block a user