community增加事件详情

This commit is contained in:
2026-01-07 13:51:41 +08:00
parent cde3707c51
commit 883d33c6c7

View File

@@ -806,122 +806,256 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
},
];
// 涨停板块表格列
// 涨停板块表格列 - 精致风格设计
const sectorColumns = [
{
title: '排名',
key: 'rank',
width: 55,
width: 60,
align: 'center',
render: (_, __, index) => (
<Tag color={index < 3 ? 'gold' : 'default'} style={{ fontWeight: 'bold', margin: 0 }}>
{index + 1}
</Tag>
),
render: (_, __, index) => {
const getRankStyle = (idx) => {
if (idx === 0) return { background: 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)', color: '#000', fontWeight: 'bold' };
if (idx === 1) return { background: 'linear-gradient(135deg, #C0C0C0 0%, #A8A8A8 100%)', color: '#000', fontWeight: 'bold' };
if (idx === 2) return { background: 'linear-gradient(135deg, #CD7F32 0%, #A0522D 100%)', color: '#fff', fontWeight: 'bold' };
return { background: 'rgba(255,255,255,0.1)', color: '#888' };
};
const style = getRankStyle(index);
return (
<div style={{
width: 28,
height: 28,
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '14px',
margin: '0 auto',
...style
}}>
{index + 1}
</div>
);
},
},
{
title: '板块',
title: '板块名称',
dataIndex: 'name',
key: 'name',
width: 120,
render: (name) => (
<AntText strong style={{ color: '#FFD700' }}>{name}</AntText>
width: 130,
render: (name, record, index) => (
<HStack spacing={2}>
<Box
w="4px"
h="24px"
borderRadius="full"
bg={index < 3 ? 'linear-gradient(180deg, #FFD700 0%, #FF8C00 100%)' : 'whiteAlpha.300'}
/>
<AntText strong style={{
color: index < 3 ? '#FFD700' : '#E0E0E0',
fontSize: '14px'
}}>
{name}
</AntText>
</HStack>
),
},
{
title: '涨停数',
dataIndex: 'count',
key: 'count',
width: 80,
width: 90,
align: 'center',
render: (count) => (
<Tag color={count >= 8 ? 'red' : count >= 5 ? 'volcano' : count >= 3 ? 'orange' : 'blue'}>
<FireOutlined style={{ marginRight: 3 }} />
{count}
</Tag>
),
render: (count) => {
const getCountColor = (c) => {
if (c >= 8) return { bg: '#ff4d4f', text: '#fff' };
if (c >= 5) return { bg: '#fa541c', text: '#fff' };
if (c >= 3) return { bg: '#fa8c16', text: '#fff' };
return { bg: 'rgba(255,215,0,0.2)', text: '#FFD700' };
};
const colors = getCountColor(count);
return (
<HStack justify="center" spacing={1}>
<Box
px={3}
py={1}
borderRadius="full"
bg={colors.bg}
display="flex"
alignItems="center"
gap={1}
>
<FireOutlined style={{ color: colors.text, fontSize: '12px' }} />
<span style={{ color: colors.text, fontWeight: 'bold', fontSize: '14px' }}>{count}</span>
</Box>
</HStack>
);
},
},
{
title: '涨停股票',
dataIndex: 'stocks',
key: 'stocks',
render: (stocks) => {
// 根据股票代码查找股票名称
const getStockName = (code) => {
// 根据股票代码查找股票详情
const getStockInfo = (code) => {
const stockInfo = stockList.find(s => s.scode === code);
return stockInfo?.sname || code;
return stockInfo || { sname: code, scode: code };
};
return (
<Space wrap size={[4, 4]}>
{stocks.slice(0, 6).map((code) => (
<Tag
key={code}
style={{ cursor: 'pointer', margin: 0 }}
color="processing"
>
<a
href={`https://valuefrontier.cn/company?scode=${code}`}
target="_blank"
rel="noopener noreferrer"
style={{ color: 'inherit' }}
<HStack spacing={2} flexWrap="wrap">
{stocks.slice(0, 5).map((code) => {
const info = getStockInfo(code);
return (
<Tooltip
key={code}
title={
<Box>
<div style={{ fontWeight: 'bold', marginBottom: 4 }}>{info.sname}</div>
<div style={{ fontSize: '12px', color: '#888' }}>{code}</div>
{info.core_sectors && (
<div style={{ fontSize: '11px', marginTop: 4 }}>
{info.core_sectors.slice(0, 2).join(' · ')}
</div>
)}
</Box>
}
placement="top"
>
{getStockName(code)}
</a>
<Tag
style={{
cursor: 'pointer',
margin: '2px',
background: 'rgba(59, 130, 246, 0.15)',
border: '1px solid rgba(59, 130, 246, 0.3)',
borderRadius: '6px',
}}
>
<a
href={`https://valuefrontier.cn/company?scode=${code}`}
target="_blank"
rel="noopener noreferrer"
style={{ color: '#60A5FA', fontSize: '13px' }}
>
{info.sname}
</a>
</Tag>
</Tooltip>
);
})}
{stocks.length > 5 && (
<Tag style={{
margin: '2px',
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.2)',
borderRadius: '6px',
color: '#888'
}}>
+{stocks.length - 5}
</Tag>
))}
{stocks.length > 6 && (
<Tag style={{ margin: 0 }}>+{stocks.length - 6}</Tag>
)}
</Space>
</HStack>
);
},
},
];
// 涨停股票详情表格列
// 涨停股票详情表格列 - 精致风格 + K线图 + 加自选
const ztStockColumns = [
{
title: '股票',
title: '股票信息',
key: 'stock',
width: 120,
width: 140,
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>
<VStack align="start" spacing={0}>
<a
href={`https://valuefrontier.cn/company?scode=${record.scode}`}
target="_blank"
rel="noopener noreferrer"
style={{ color: '#FFD700', fontWeight: 'bold', fontSize: '14px' }}
>
{record.sname}
</a>
<AntText style={{ color: '#60A5FA', fontSize: '12px' }}>{record.scode}</AntText>
</VStack>
),
},
{
title: '涨停时间',
dataIndex: 'formatted_time',
key: 'time',
width: 85,
width: 90,
align: 'center',
render: (time) => (
<Tag color={time <= '09:30:00' ? 'red' : time <= '10:00:00' ? 'volcano' : 'default'}>
{time?.substring(0, 5) || '-'}
</Tag>
),
render: (time) => {
const getTimeStyle = (t) => {
if (t <= '09:30:00') return { bg: '#ff4d4f', text: '#fff', label: '秒板' };
if (t <= '09:35:00') return { bg: '#fa541c', text: '#fff', label: '早板' };
if (t <= '10:00:00') return { bg: '#fa8c16', text: '#fff', label: '盘初' };
if (t <= '11:00:00') return { bg: '#52c41a', text: '#fff', label: '盘中' };
return { bg: 'rgba(255,255,255,0.1)', text: '#888', label: '尾盘' };
};
const style = getTimeStyle(time || '15:00:00');
return (
<VStack spacing={0}>
<Box
px={2}
py={0.5}
borderRadius="md"
bg={style.bg}
fontSize="13px"
fontWeight="bold"
color={style.text}
>
{time?.substring(0, 5) || '-'}
</Box>
<AntText style={{ fontSize: '10px', color: '#888' }}>{style.label}</AntText>
</VStack>
);
},
},
{
title: '连板',
dataIndex: 'continuous_days',
key: 'continuous',
width: 75,
width: 70,
align: 'center',
render: (text) => {
if (!text || text === '首板') return <Tag>首板</Tag>;
if (!text || text === '首板') {
return (
<Box
px={2}
py={0.5}
borderRadius="md"
bg="rgba(255,255,255,0.1)"
fontSize="12px"
color="#888"
>
首板
</Box>
);
}
const match = text.match(/(\d+)/);
const days = match ? parseInt(match[1]) : 1;
const getDaysStyle = (d) => {
if (d >= 5) return { bg: 'linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%)', text: '#fff' };
if (d >= 3) return { bg: 'linear-gradient(135deg, #fa541c 0%, #ff7a45 100%)', text: '#fff' };
if (d >= 2) return { bg: 'linear-gradient(135deg, #fa8c16 0%, #ffc53d 100%)', text: '#fff' };
return { bg: 'rgba(255,255,255,0.1)', text: '#888' };
};
const style = getDaysStyle(days);
return (
<Tag color={days >= 5 ? 'red' : days >= 3 ? 'volcano' : days >= 2 ? 'orange' : 'default'}>
<Box
px={2}
py={0.5}
borderRadius="md"
bg={style.bg}
fontSize="13px"
fontWeight="bold"
color={style.text}
>
{text}
</Tag>
</Box>
);
},
},
@@ -929,40 +1063,139 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
title: '核心板块',
dataIndex: 'core_sectors',
key: 'sectors',
width: 180,
width: 200,
render: (sectors) => (
<Space wrap size={[2, 2]}>
<HStack spacing={1} flexWrap="wrap">
{(sectors || []).slice(0, 3).map((sector, idx) => (
<Tag key={idx} color="gold" style={{ margin: 0, fontSize: '12px' }}>
<Tag
key={idx}
style={{
margin: '2px',
background: idx === 0
? 'linear-gradient(135deg, rgba(255,215,0,0.25) 0%, rgba(255,165,0,0.15) 100%)'
: 'rgba(255,215,0,0.1)',
border: idx === 0 ? '1px solid rgba(255,215,0,0.5)' : '1px solid rgba(255,215,0,0.2)',
borderRadius: '6px',
color: idx === 0 ? '#FFD700' : '#D4A84B',
fontSize: '12px',
fontWeight: idx === 0 ? 'bold' : 'normal',
}}
>
{sector}
</Tag>
))}
</Space>
</HStack>
),
},
{
title: '涨停简报',
dataIndex: 'brief',
key: 'brief',
ellipsis: true,
render: (text) => {
width: 200,
render: (text, record) => {
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 }}>
<Tooltip
title={
<Box maxW="400px" p={2}>
<div style={{ fontWeight: 'bold', marginBottom: 8, color: '#FFD700' }}>
{record.sname} 涨停简报
</div>
<div style={{ fontSize: '13px', lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>
{cleanText}
</div>
</Box>
}
placement="topLeft"
overlayStyle={{ maxWidth: 450 }}
>
<Button
type="link"
size="small"
onClick={() => showContentDetail(text.replace(/<br\s*\/?>/gi, '\n\n'), '涨停简报')}
style={{ padding: 0, height: 'auto', whiteSpace: 'normal', textAlign: 'left' }}
onClick={() => showContentDetail(text.replace(/<br\s*\/?>/gi, '\n\n'), `${record.sname} 涨停简报`)}
style={{
padding: 0,
height: 'auto',
whiteSpace: 'normal',
textAlign: 'left',
color: '#60A5FA',
fontSize: '13px'
}}
>
{cleanText.length > 40 ? cleanText.substring(0, 40) + '...' : cleanText}
{cleanText.length > 30 ? cleanText.substring(0, 30) + '...' : cleanText}
</Button>
</Tooltip>
);
},
},
{
title: 'K线图',
key: 'kline',
width: 80,
align: 'center',
render: (_, record) => (
<Button
type="primary"
size="small"
icon={<LineChartOutlined />}
onClick={() => {
// 添加交易所后缀
const code = record.scode;
let stockCode = code;
if (!code.includes('.')) {
if (code.startsWith('6')) stockCode = `${code}.SH`;
else if (code.startsWith('0') || code.startsWith('3')) stockCode = `${code}.SZ`;
else if (code.startsWith('688')) stockCode = `${code}.SH`;
}
setSelectedKlineStock({
stock_code: stockCode,
stock_name: record.sname
});
setKlineModalVisible(true);
}}
style={{
background: 'linear-gradient(135deg, #3B82F6 0%, #8B5CF6 100%)',
border: 'none',
borderRadius: '6px',
}}
>
查看
</Button>
),
},
{
title: '操作',
key: 'action',
width: 90,
align: 'center',
render: (_, record) => {
const code = record.scode;
const inWatchlist = isStockInWatchlist(code);
return (
<Button
type={inWatchlist ? 'primary' : 'default'}
size="small"
icon={inWatchlist ? <StarFilled /> : <StarOutlined />}
onClick={() => addSingleToWatchlist({ code, name: record.sname })}
disabled={inWatchlist}
style={inWatchlist ? {
background: 'linear-gradient(135deg, #faad14 0%, #fa8c16 100%)',
border: 'none',
borderRadius: '6px',
} : {
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,215,0,0.3)',
borderRadius: '6px',
color: '#FFD700',
}}
>
{inWatchlist ? '已添加' : '加自选'}
</Button>
);
},
},
];
// 事件表格列(参考投资日历)- 去掉相关概念列
@@ -1127,92 +1360,218 @@ const DetailModal = ({ isOpen, onClose, selectedDate, ztDetail, events, loading
disabled={!ztDetail}
>
{(sectorList.length > 0 || stockList.length > 0) ? (
<VStack spacing={4} align="stretch">
{/* 热门关键词 */}
<VStack spacing={5} 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)"
p={4}
bg="linear-gradient(135deg, rgba(255, 215, 0, 0.08) 0%, rgba(255, 140, 0, 0.05) 100%)"
borderRadius="xl"
border="1px solid rgba(255, 215, 0, 0.2)"
position="relative"
overflow="hidden"
>
<HStack spacing={2} mb={2}>
<FireOutlined style={{ color: '#FFD700' }} />
<Text fontSize="sm" fontWeight="bold" color="gold">
{/* 装饰线 */}
<Box
position="absolute"
top={0}
left={0}
right={0}
h="2px"
bgGradient="linear(to-r, transparent, #FFD700, #FF8C00, #FFD700, transparent)"
/>
<HStack spacing={2} mb={3}>
<Box
p={1.5}
bg="rgba(255,215,0,0.2)"
borderRadius="md"
>
<FireOutlined style={{ color: '#FFD700', fontSize: '16px' }} />
</Box>
<Text fontSize="md" fontWeight="bold" color="gold">
今日热词
</Text>
<Text fontSize="xs" color="whiteAlpha.500">
词频越高排名越前
</Text>
</HStack>
<HStack spacing={2} flexWrap="wrap">
{hotKeywords.map((kw, idx) => {
// 根据排名计算样式
const getKeywordStyle = (index) => {
if (index < 3) return {
fontSize: '15px',
fontWeight: 'bold',
background: 'linear-gradient(135deg, rgba(255,215,0,0.3) 0%, rgba(255,165,0,0.2) 100%)',
border: '1px solid rgba(255,215,0,0.5)',
color: '#FFD700',
px: 3,
py: 1.5,
};
if (index < 6) return {
fontSize: '14px',
fontWeight: 'semibold',
background: 'rgba(255,215,0,0.15)',
border: '1px solid rgba(255,215,0,0.3)',
color: '#D4A84B',
px: 2.5,
py: 1,
};
return {
fontSize: '13px',
fontWeight: 'normal',
background: 'rgba(255,255,255,0.08)',
border: '1px solid rgba(255,255,255,0.15)',
color: '#888',
px: 2,
py: 0.5,
};
};
const style = getKeywordStyle(idx);
return (
<Box
key={kw.name}
px={style.px}
py={style.py}
borderRadius="full"
bg={style.background}
border={style.border}
fontSize={style.fontSize}
fontWeight={style.fontWeight}
color={style.color}
transition="all 0.2s"
_hover={{
transform: 'scale(1.05)',
boxShadow: '0 0 10px rgba(255,215,0,0.3)',
}}
>
{kw.name}
</Box>
);
})}
</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'}
{/* 视图切换按钮 - 更精致的样式 */}
<HStack justify="space-between" align="center" px={1}>
<HStack spacing={3}>
<Box
as="button"
px={4}
py={2}
borderRadius="lg"
bg={ztViewMode === 'sector'
? 'linear-gradient(135deg, rgba(255,215,0,0.25) 0%, rgba(255,165,0,0.15) 100%)'
: 'rgba(255,255,255,0.05)'}
border={ztViewMode === 'sector'
? '1px solid rgba(255,215,0,0.5)'
: '1px solid rgba(255,255,255,0.1)'}
color={ztViewMode === 'sector' ? '#FFD700' : '#888'}
fontWeight={ztViewMode === 'sector' ? 'bold' : 'normal'}
onClick={() => setZtViewMode('sector')}
icon={<TagsOutlined />}
transition="all 0.2s"
_hover={{ bg: 'rgba(255,215,0,0.15)' }}
display="flex"
alignItems="center"
gap={2}
>
按板块 ({sectorList.length})
</Button>
<Button
size="small"
type={ztViewMode === 'stock' ? 'primary' : 'default'}
<TagsOutlined />
<span>按板块 ({sectorList.length})</span>
</Box>
<Box
as="button"
px={4}
py={2}
borderRadius="lg"
bg={ztViewMode === 'stock'
? 'linear-gradient(135deg, rgba(59,130,246,0.25) 0%, rgba(139,92,246,0.15) 100%)'
: 'rgba(255,255,255,0.05)'}
border={ztViewMode === 'stock'
? '1px solid rgba(59,130,246,0.5)'
: '1px solid rgba(255,255,255,0.1)'}
color={ztViewMode === 'stock' ? '#60A5FA' : '#888'}
fontWeight={ztViewMode === 'stock' ? 'bold' : 'normal'}
onClick={() => setZtViewMode('stock')}
icon={<StockOutlined />}
transition="all 0.2s"
_hover={{ bg: 'rgba(59,130,246,0.15)' }}
display="flex"
alignItems="center"
gap={2}
>
按个股 ({stockList.length})
</Button>
<StockOutlined />
<span>按个股 ({stockList.length})</span>
</Box>
</HStack>
<HStack spacing={2}>
<Box
px={3}
py={1}
borderRadius="full"
bg="rgba(255,77,79,0.15)"
border="1px solid rgba(255,77,79,0.3)"
>
<HStack spacing={1}>
<FireOutlined style={{ color: '#ff4d4f', fontSize: '12px' }} />
<Text fontSize="sm" color="#ff4d4f" fontWeight="bold">
{ztDetail?.total_stocks || 0}
</Text>
<Text fontSize="xs" color="whiteAlpha.600">只涨停</Text>
</HStack>
</Box>
</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 }}
/>
<Box
borderRadius="xl"
border="1px solid rgba(255,215,0,0.15)"
overflow="hidden"
>
<Table
dataSource={sectorList}
columns={sectorColumns}
rowKey="name"
size="middle"
pagination={false}
scroll={{ y: 380 }}
/>
</Box>
)}
{/* 个股视图 */}
{ztViewMode === 'stock' && (
<Table
dataSource={stockList}
columns={ztStockColumns}
rowKey="scode"
size="small"
pagination={false}
scroll={{ x: 800, y: 350 }}
/>
<Box
borderRadius="xl"
border="1px solid rgba(59,130,246,0.15)"
overflow="hidden"
>
<Table
dataSource={stockList}
columns={ztStockColumns}
rowKey="scode"
size="middle"
pagination={false}
scroll={{ x: 950, y: 380 }}
/>
</Box>
)}
</VStack>
) : (
<Center h="200px">
<VStack>
<FireOutlined style={{ fontSize: 48, color: '#666' }} />
<AntText type="secondary" style={{ fontSize: 16 }}>暂无涨停数据</AntText>
<Center h="250px">
<VStack spacing={4}>
<Box
p={4}
borderRadius="full"
bg="rgba(255,255,255,0.05)"
>
<FireOutlined style={{ fontSize: 48, color: '#444' }} />
</Box>
<VStack spacing={1}>
<AntText style={{ fontSize: 16, color: '#666' }}>暂无涨停数据</AntText>
<AntText style={{ fontSize: 13, color: '#444' }}>该日期没有涨停股票记录</AntText>
</VStack>
</VStack>
</Center>
)}