Compare commits
9 Commits
e48bcbb74b
...
4a5e18a90d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a5e18a90d | ||
|
|
b7315bbdb4 | ||
|
|
378df947a9 | ||
|
|
a9c21d8478 | ||
|
|
7be35d7bb8 | ||
|
|
4e5f999881 | ||
|
|
c1b8a98bb4 | ||
|
|
46be0249a8 | ||
|
|
f2713e5e0a |
@@ -705,7 +705,7 @@ const KLineChartModal: React.FC<KLineChartModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ position: 'relative', height: isMobile ? '450px' : '680px', width: '100%' }}>
|
<div style={{ position: 'relative', width: '100%' }}>
|
||||||
{loading && (
|
{loading && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -736,7 +736,8 @@ const KLineChartModal: React.FC<KLineChartModalProps> = ({
|
|||||||
<span style={{ color: '#e0e0e0' }}>加载K线数据...</span>
|
<span style={{ color: '#e0e0e0' }}>加载K线数据...</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div ref={chartRef} style={{ width: '100%', height: '100%' }} />
|
{/* 使用 aspect-ratio 保持图表宽高比,K线图推荐 2.5:1 */}
|
||||||
|
<div ref={chartRef} style={{ width: '100%', aspectRatio: isMobile ? '1.8 / 1' : '2.5 / 1', minHeight: isMobile ? '280px' : '400px' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -251,7 +251,8 @@ const StockChartKLineModal: React.FC<StockChartKLineModalProps> = ({
|
|||||||
id={`kline-chart-${stock.stock_code}`}
|
id={`kline-chart-${stock.stock_code}`}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: `${CHART_HEIGHTS.main}px`,
|
minHeight: '300px',
|
||||||
|
height: 'min(400px, 60vh)',
|
||||||
opacity: showLoading ? 0.5 : 1,
|
opacity: showLoading ? 0.5 : 1,
|
||||||
transition: 'opacity 0.3s',
|
transition: 'opacity 0.3s',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -470,12 +470,13 @@ const TimelineChartModal: React.FC<TimelineChartModalProps> = ({
|
|||||||
<Modal isOpen={isOpen} onClose={onClose} size={size} isCentered>
|
<Modal isOpen={isOpen} onClose={onClose} size={size} isCentered>
|
||||||
<ModalOverlay bg="blackAlpha.700" />
|
<ModalOverlay bg="blackAlpha.700" />
|
||||||
<ModalContent
|
<ModalContent
|
||||||
maxW={isMobile ? '96vw' : '90vw'}
|
w={isMobile ? '96vw' : '90vw'}
|
||||||
maxH="85vh"
|
maxW={isMobile ? '96vw' : '1400px'}
|
||||||
borderRadius={isMobile ? '12px' : '8px'}
|
borderRadius={isMobile ? '12px' : '8px'}
|
||||||
bg="#1a1a1a"
|
bg="#1a1a1a"
|
||||||
border="2px solid #ffd700"
|
border="2px solid #ffd700"
|
||||||
boxShadow="0 0 30px rgba(255, 215, 0, 0.5)"
|
boxShadow="0 0 30px rgba(255, 215, 0, 0.5)"
|
||||||
|
overflow="visible"
|
||||||
>
|
>
|
||||||
<ModalHeader pb={isMobile ? 2 : 3} borderBottomWidth="1px" borderColor="#404040">
|
<ModalHeader pb={isMobile ? 2 : 3} borderBottomWidth="1px" borderColor="#404040">
|
||||||
<VStack align="flex-start" spacing={0}>
|
<VStack align="flex-start" spacing={0}>
|
||||||
@@ -498,7 +499,7 @@ const TimelineChartModal: React.FC<TimelineChartModalProps> = ({
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box position="relative" h={isMobile ? '400px' : '600px'} w="100%">
|
<Box position="relative" w="100%">
|
||||||
{loading && (
|
{loading && (
|
||||||
<Flex
|
<Flex
|
||||||
position="absolute"
|
position="absolute"
|
||||||
@@ -517,7 +518,15 @@ const TimelineChartModal: React.FC<TimelineChartModalProps> = ({
|
|||||||
</VStack>
|
</VStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<div ref={chartRef} style={{ width: '100%', height: '100%' }} />
|
{/* 使用 aspect-ratio 保持图表宽高比,与日K线保持一致 */}
|
||||||
|
<Box
|
||||||
|
ref={chartRef}
|
||||||
|
w="100%"
|
||||||
|
sx={{
|
||||||
|
aspectRatio: isMobile ? '1.8 / 1' : '2.5 / 1',
|
||||||
|
}}
|
||||||
|
minH={isMobile ? '280px' : '400px'}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -848,5 +848,18 @@ export const conceptHandlers = [
|
|||||||
lv1_id: lv1Id,
|
lv1_id: lv1Id,
|
||||||
lv2_id: lv2Id
|
lv2_id: lv2Id
|
||||||
});
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 热门概念静态数据文件(HeroPanel 使用)
|
||||||
|
http.get('/data/concept/latest.json', async () => {
|
||||||
|
await delay(200);
|
||||||
|
console.log('[Mock Concept] 获取热门概念静态数据');
|
||||||
|
|
||||||
|
const concepts = generatePopularConcepts(30);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
date: new Date().toISOString().split('T')[0],
|
||||||
|
results: concepts
|
||||||
|
});
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -60,10 +60,12 @@ const PaginationControl = React.memo(({ currentPage, totalPages, onPageChange })
|
|||||||
pageNumbers.push(i);
|
pageNumbers.push(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 当前页在中间
|
// 当前页在中间:显示当前页前后各1个页码
|
||||||
pageNumbers.push(1);
|
pageNumbers.push(1);
|
||||||
pageNumbers.push('...');
|
pageNumbers.push('...');
|
||||||
pageNumbers.push(currentPage);
|
pageNumbers.push(currentPage - 1); // 前一页
|
||||||
|
pageNumbers.push(currentPage); // 当前页
|
||||||
|
pageNumbers.push(currentPage + 1); // 后一页
|
||||||
pageNumbers.push('...');
|
pageNumbers.push('...');
|
||||||
pageNumbers.push(totalPages);
|
pageNumbers.push(totalPages);
|
||||||
}
|
}
|
||||||
@@ -180,7 +182,7 @@ const PaginationControl = React.memo(({ currentPage, totalPages, onPageChange })
|
|||||||
{/* 输入框跳转 */}
|
{/* 输入框跳转 */}
|
||||||
<HStack spacing={1.5}>
|
<HStack spacing={1.5}>
|
||||||
<Text fontSize="xs" color="gray.600">
|
<Text fontSize="xs" color="gray.600">
|
||||||
跳转到
|
第
|
||||||
</Text>
|
</Text>
|
||||||
<Input
|
<Input
|
||||||
size="xs"
|
size="xs"
|
||||||
@@ -191,10 +193,13 @@ const PaginationControl = React.memo(({ currentPage, totalPages, onPageChange })
|
|||||||
value={jumpPage}
|
value={jumpPage}
|
||||||
onChange={(e) => setJumpPage(e.target.value)}
|
onChange={(e) => setJumpPage(e.target.value)}
|
||||||
onKeyPress={handleKeyPress}
|
onKeyPress={handleKeyPress}
|
||||||
placeholder="页"
|
placeholder=""
|
||||||
bg={buttonBg}
|
bg={buttonBg}
|
||||||
borderColor={borderColor}
|
borderColor={borderColor}
|
||||||
/>
|
/>
|
||||||
|
<Text fontSize="xs" color="gray.600">
|
||||||
|
页
|
||||||
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
|
|||||||
@@ -569,8 +569,9 @@ const FlowingConcepts = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
animation={isPaused ? 'none' : `${animationName} ${duration}s linear infinite`}
|
animation={`${animationName} ${duration}s linear infinite`}
|
||||||
sx={{
|
sx={{
|
||||||
|
animationPlayState: isPaused ? 'paused' : 'running',
|
||||||
[`@keyframes ${animationName}`]: direction === 'left' ? {
|
[`@keyframes ${animationName}`]: direction === 'left' ? {
|
||||||
'0%': { transform: 'translateX(0)' },
|
'0%': { transform: 'translateX(0)' },
|
||||||
'100%': { transform: 'translateX(-50%)' },
|
'100%': { transform: 'translateX(-50%)' },
|
||||||
@@ -952,10 +953,6 @@ const HeroPanel = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack spacing={1} px={2} py={1} bg="rgba(255,255,255,0.05)" borderRadius="full">
|
|
||||||
<Box w="6px" h="6px" borderRadius="full" bg="gold" animation="pulse 2s infinite" />
|
|
||||||
<Text fontSize="10px" color="whiteAlpha.600">点击查看详情</Text>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 流动式概念展示 */}
|
{/* 流动式概念展示 */}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
|
||||||
transition: all 0.3s ease !important;
|
transition: all 0.3s ease !important;
|
||||||
z-index: 10 !important;
|
z-index: 10 !important;
|
||||||
|
user-select: none !important; /* 防止连续点击时选中文本 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-carousel-arrow:hover {
|
.custom-carousel-arrow:hover {
|
||||||
@@ -120,13 +121,38 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 重要度徽章 - 长方形圆角 */
|
||||||
.importance-badge {
|
.importance-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 2px 6px;
|
font-weight: 600;
|
||||||
border-radius: 4px;
|
color: #fff;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* S级 - 深红 */
|
||||||
|
.importance-s {
|
||||||
|
background: #c0392b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A级 - 浅红 */
|
||||||
|
.importance-a {
|
||||||
|
background: #e74c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* B级 - 深橙 */
|
||||||
|
.importance-b {
|
||||||
|
background: #d35400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* C级 - 浅橙 */
|
||||||
|
.importance-c {
|
||||||
|
background: #f39c12;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card content */
|
/* Card content */
|
||||||
@@ -143,26 +169,18 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 标题文字 - inline显示,可以换行 */
|
/* 标题文字 */
|
||||||
.event-title {
|
.event-title {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 标签紧跟标题后面 */
|
/* 涨幅标签 - 底部显示 */
|
||||||
.event-tag {
|
|
||||||
display: inline;
|
|
||||||
margin-left: 4px;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.event-tag .ant-tag {
|
.event-tag .ant-tag {
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
padding: 0 6px;
|
padding: 0 8px;
|
||||||
height: 18px;
|
height: 20px;
|
||||||
line-height: 18px;
|
line-height: 20px;
|
||||||
transform: scale(0.9);
|
margin: 0;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 详情描述 - 三行省略 */
|
/* 详情描述 - 三行省略 */
|
||||||
@@ -183,18 +201,12 @@
|
|||||||
.event-footer {
|
.event-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #8c8c8c;
|
color: #8c8c8c;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creator {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 60%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 时间样式 - 年月日高亮 */
|
/* 时间样式 - 年月日高亮 */
|
||||||
.time {
|
.time {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -90,6 +90,13 @@ const HotEvents = ({ events, onPageChange, onEventClick }) => {
|
|||||||
prevArrow: <CustomArrow direction="left" />,
|
prevArrow: <CustomArrow direction="left" />,
|
||||||
nextArrow: <CustomArrow direction="right" />,
|
nextArrow: <CustomArrow direction="right" />,
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
|
// 触控板/触摸优化
|
||||||
|
swipeToSlide: true, // 允许滑动到任意位置
|
||||||
|
touchThreshold: 10, // 滑动灵敏度
|
||||||
|
swipe: true, // 启用滑动
|
||||||
|
draggable: true, // PC 端拖拽支持
|
||||||
|
useCSS: true, // CSS 动画更流畅
|
||||||
|
cssEase: 'ease-out', // 滑动缓动效果
|
||||||
beforeChange: (_current, next) => {
|
beforeChange: (_current, next) => {
|
||||||
// 计算实际页码(考虑无限循环)
|
// 计算实际页码(考虑无限循环)
|
||||||
const actualPage = next % totalPages;
|
const actualPage = next % totalPages;
|
||||||
@@ -145,11 +152,9 @@ const HotEvents = ({ events, onPageChange, onEventClick }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{event.importance && (
|
{event.importance && (
|
||||||
<Badge
|
<span className={`importance-badge importance-${event.importance.toLowerCase()}`}>
|
||||||
className="importance-badge"
|
{event.importance}级
|
||||||
color={getImportanceColor(event.importance)}
|
</span>
|
||||||
text={`${event.importance}级`}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -167,9 +172,6 @@ const HotEvents = ({ events, onPageChange, onEventClick }) => {
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<span className="event-tag">
|
|
||||||
{renderPriceChange(event.related_avg_chg)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
@@ -185,7 +187,9 @@ const HotEvents = ({ events, onPageChange, onEventClick }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="event-footer">
|
<div className="event-footer">
|
||||||
<span className="creator">{event.creator?.username || 'Anonymous'}</span>
|
<span className="event-tag">
|
||||||
|
{renderPriceChange(event.related_avg_chg)}
|
||||||
|
</span>
|
||||||
<span className="time">
|
<span className="time">
|
||||||
<span className="time-date">{dayjs(event.created_at).format('YYYY-MM-DD')}</span>
|
<span className="time-date">{dayjs(event.created_at).format('YYYY-MM-DD')}</span>
|
||||||
{' '}
|
{' '}
|
||||||
|
|||||||
@@ -400,13 +400,13 @@ export default function CenterDashboard() {
|
|||||||
{followingEvents.length}
|
{followingEvents.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
</HStack>
|
</HStack>
|
||||||
<Button
|
<IconButton
|
||||||
size="sm"
|
icon={<FiPlus />}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
onClick={() => navigate('/community')}
|
onClick={() => navigate('/community')}
|
||||||
>
|
aria-label="添加关注事件"
|
||||||
查看更多
|
/>
|
||||||
</Button>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody pt={0} flex="1" overflowY="auto">
|
<CardBody pt={0} flex="1" overflowY="auto">
|
||||||
|
|||||||
@@ -122,13 +122,20 @@ const HistoricalEvents = ({
|
|||||||
// navigate(`/event-detail/${event.id}`);
|
// navigate(`/event-detail/${event.id}`);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// 获取重要性颜色
|
// 获取重要性颜色(用于 Badge)
|
||||||
const getImportanceColor = (importance) => {
|
const getImportanceColor = (importance) => {
|
||||||
if (importance >= 4) return 'red';
|
if (importance >= 4) return 'red';
|
||||||
if (importance >= 2) return 'orange';
|
if (importance >= 2) return 'orange';
|
||||||
return 'green';
|
return 'green';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取重要性背景色(用于卡片背景)
|
||||||
|
const getImportanceBgColor = (importance) => {
|
||||||
|
if (importance >= 4) return 'rgba(239, 68, 68, 0.15)'; // 红色背景
|
||||||
|
if (importance >= 2) return 'rgba(245, 158, 11, 0.12)'; // 橙色背景
|
||||||
|
return 'rgba(34, 197, 94, 0.1)'; // 绿色背景
|
||||||
|
};
|
||||||
|
|
||||||
// 获取相关度颜色(1-10)
|
// 获取相关度颜色(1-10)
|
||||||
const getSimilarityColor = (similarity) => {
|
const getSimilarityColor = (similarity) => {
|
||||||
if (similarity >= 8) return 'green';
|
if (similarity >= 8) return 'green';
|
||||||
@@ -240,27 +247,15 @@ const HistoricalEvents = ({
|
|||||||
<VStack spacing={3} align="stretch">
|
<VStack spacing={3} align="stretch">
|
||||||
{events.map((event) => {
|
{events.map((event) => {
|
||||||
const importanceColor = getImportanceColor(event.importance);
|
const importanceColor = getImportanceColor(event.importance);
|
||||||
|
const importanceBgColor = getImportanceBgColor(event.importance);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
key={event.id}
|
key={event.id}
|
||||||
bg={cardBg}
|
bg={importanceBgColor}
|
||||||
borderWidth="1px"
|
borderWidth="1px"
|
||||||
borderColor="gray.500"
|
borderColor="gray.500"
|
||||||
borderRadius="lg"
|
borderRadius="lg"
|
||||||
position="relative"
|
|
||||||
overflow="visible"
|
|
||||||
_before={{
|
|
||||||
content: '""',
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
height: '3px',
|
|
||||||
bgGradient: 'linear(to-r, blue.400, purple.500, pink.500)',
|
|
||||||
borderTopLeftRadius: 'lg',
|
|
||||||
borderTopRightRadius: 'lg',
|
|
||||||
}}
|
|
||||||
transition="all 0.2s"
|
transition="all 0.2s"
|
||||||
>
|
>
|
||||||
<VStack align="stretch" spacing={3} p={4}>
|
<VStack align="stretch" spacing={3} p={4}>
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ function getSankeyOption(data) {
|
|||||||
title: {
|
title: {
|
||||||
text: '事件影响力传导流向',
|
text: '事件影响力传导流向',
|
||||||
left: 'center',
|
left: 'center',
|
||||||
top: 10,
|
top: 5,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: '#00d2d3',
|
color: '#00d2d3',
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -379,6 +379,10 @@ function getSankeyOption(data) {
|
|||||||
series: [{
|
series: [{
|
||||||
type: 'sankey',
|
type: 'sankey',
|
||||||
layout: 'none',
|
layout: 'none',
|
||||||
|
top: 50, // 给标题留出空间
|
||||||
|
bottom: 20,
|
||||||
|
left: 20,
|
||||||
|
right: 150, // 右侧留空间给标签
|
||||||
emphasis: { focus: 'adjacency' },
|
emphasis: { focus: 'adjacency' },
|
||||||
nodeAlign: 'justify',
|
nodeAlign: 'justify',
|
||||||
layoutIterations: 0,
|
layoutIterations: 0,
|
||||||
|
|||||||
@@ -208,6 +208,10 @@ export default function ProfilePage() {
|
|||||||
<Button
|
<Button
|
||||||
leftIcon={<CloseIcon />}
|
leftIcon={<CloseIcon />}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
colorScheme="gray"
|
||||||
|
color="gray.300"
|
||||||
|
borderColor="gray.500"
|
||||||
|
_hover={{ bg: 'gray.700', borderColor: 'gray.400' }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
setFormData({
|
setFormData({
|
||||||
|
|||||||
Reference in New Issue
Block a user