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