community增加事件详情

This commit is contained in:
2026-01-07 12:35:15 +08:00
parent d60dfe45ac
commit 03be49a459

View File

@@ -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>