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 DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading }) => {
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false); const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
const [selectedContent, setSelectedContent] = useState(null); const [selectedContent, setSelectedContent] = useState(null);
const [ztViewMode, setZtViewMode] = useState('sector'); // 'sector' | 'stock'
// 板块数据处理 - 必须在条件返回之前调用所有hooks // 板块数据处理 - 必须在条件返回之前调用所有hooks
const sectorList = useMemo(() => { const sectorList = useMemo(() => {
if (!ztDetail?.sector_data) return []; if (!ztDetail?.sector_data) return [];
return Object.entries(ztDetail.sector_data) return Object.entries(ztDetail.sector_data)
.filter(([name]) => name !== '其他')
.map(([name, data]) => ({ .map(([name, data]) => ({
name, name,
count: data.count, count: data.count,
@@ -422,6 +424,21 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
.sort((a, b) => b.count - a.count); .sort((a, b) => b.count - a.count);
}, [ztDetail]); }, [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之后 // 条件返回必须在所有hooks之后
if (!selectedDate) return null; if (!selectedDate) return null;
@@ -456,28 +473,33 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
{ {
title: '排名', title: '排名',
key: 'rank', key: 'rank',
width: 60, width: 55,
align: 'center',
render: (_, __, index) => ( render: (_, __, index) => (
<Tag color={index < 3 ? 'gold' : 'default'} style={{ fontWeight: 'bold' }}> <Tag color={index < 3 ? 'gold' : 'default'} style={{ fontWeight: 'bold', margin: 0 }}>
{index + 1} {index + 1}
</Tag> </Tag>
), ),
}, },
{ {
title: '板块名称', title: '板块',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
render: (name) => <AntText strong>{name}</AntText>, width: 120,
render: (name) => (
<AntText strong style={{ color: '#FFD700' }}>{name}</AntText>
),
}, },
{ {
title: '涨停数', title: '涨停数',
dataIndex: 'count', dataIndex: 'count',
key: 'count', key: 'count',
width: 100, width: 80,
align: 'center',
render: (count) => ( render: (count) => (
<Tag color={count >= 10 ? 'red' : count >= 5 ? 'orange' : 'blue'} style={{ fontSize: '14px' }}> <Tag color={count >= 8 ? 'red' : count >= 5 ? 'volcano' : count >= 3 ? 'orange' : 'blue'}>
<FireOutlined style={{ marginRight: 4 }} /> <FireOutlined style={{ marginRight: 3 }} />
{count} {count}
</Tag> </Tag>
), ),
}, },
@@ -485,27 +507,127 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
title: '涨停股票', title: '涨停股票',
dataIndex: 'stocks', dataIndex: 'stocks',
key: 'stocks', key: 'stocks',
render: (stocks) => ( render: (stocks) => {
<Space wrap> // 根据股票代码查找股票名称
{stocks.slice(0, 8).map((code) => ( const getStockName = (code) => {
<Tag key={code} style={{ cursor: 'pointer' }}> 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"
>
<a <a
href={`https://valuefrontier.cn/company?scode=${code}`} href={`https://valuefrontier.cn/company?scode=${code}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
style={{ color: 'inherit' }} style={{ color: 'inherit' }}
> >
{code} {getStockName(code)}
</a> </a>
</Tag> </Tag>
))} ))}
{stocks.length > 8 && <Tag>+{stocks.length - 8}</Tag>} {stocks.length > 6 && (
<Tag style={{ margin: 0 }}>+{stocks.length - 6}</Tag>
)}
</Space> </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 = [ const eventColumns = [
{ {
title: '时间', 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 ( return (
@@ -696,15 +794,88 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
key="zt" key="zt"
disabled={!ztDetail} disabled={!ztDetail}
> >
{sectorList.length > 0 ? ( {(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 <Table
dataSource={sectorList} dataSource={sectorList}
columns={sectorColumns} columns={sectorColumns}
rowKey="name" rowKey="name"
size="middle" size="small"
pagination={false} pagination={false}
scroll={{ y: 450 }} 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"> <Center h="200px">
<VStack> <VStack>
@@ -727,38 +898,14 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
disabled={!events?.length} disabled={!events?.length}
> >
{events?.length > 0 ? ( {events?.length > 0 ? (
<Tabs defaultActiveKey="all" size="small">
<TabPane tab={`全部 (${events.length})`} key="all">
<Table <Table
dataSource={events} dataSource={events}
columns={eventColumns} columns={eventColumns}
rowKey="id" rowKey="id"
size="middle" size="small"
pagination={false} pagination={false}
scroll={{ x: 1000, y: 400 }} scroll={{ x: 900, y: 420 }}
/> />
</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>
) : ( ) : (
<Center h="200px"> <Center h="200px">
<VStack> <VStack>