community增加事件详情
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user