Compare commits

...

9 Commits

Author SHA1 Message Date
zdl
4a5e18a90d style(HotEvents): 优化热点事件卡片 UI 和交互体验
- 触控板滑动优化:添加 swipeToSlide 等配置,滑动更流畅
- 布局调整:涨幅标签从标题移到底部,去掉作者显示
- 重要度徽章优化:长方形圆角样式,S/A红色系、B/C橙色系

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 15:58:03 +08:00
zdl
b7315bbdb4 feat: 历史事件卡片根据重要性显示不同背景色
- 重要性 >= 4:红色背景(高重要性)
- 重要性 >= 2:橙色背景(中等重要性)
- 重要性 < 2:绿色背景(低重要性)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 15:33:45 +08:00
zdl
378df947a9 fix(Pagination): 优化中间页码显示,调整跳转文案格式
- 中间页码:显示当前页前后各1个页码 (如 1...4,5,6...10)
- 跳转文案:从"跳转到 [页]"改为"第 [  ] 页"格式

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 15:20:16 +08:00
zdl
a9c21d8478 fix(UI): Profile 取消按钮样式、HotEvents 轮播箭头、Dashboard 按钮优化
- Profile: 取消按钮添加深色主题样式 (color, borderColor, hover)
- HotEvents: 轮播箭头添加 user-select: none 防止连续点击选中文本
- Dashboard: "查看更多"按钮改为图标按钮 (IconButton + FiPlus)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 15:20:02 +08:00
zdl
7be35d7bb8 fix(StockChart): 图表组件使用 aspect-ratio 保持宽高比,统一弹窗大小
- KLineChartModal: 日K线图使用 aspectRatio 替代固定高度
- StockChartKLineModal: K线图高度改为响应式 min(400px, 60vh)
- TimelineChartModal: 分时图弹窗大小与日K线统一,maxWidth: 1400px

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 15:19:51 +08:00
zdl
4e5f999881 fix: 修复热门概念滚动动画暂停时跳跃问题
- 使用 animation-play-state 代替移除动画
- 暂停时保持在当前位置而不是跳回初始位置

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 14:54:13 +08:00
zdl
c1b8a98bb4 fix: 添加热门概念静态数据的 mock handler
- 拦截 /data/concept/latest.json 请求
- 返回 mock 生成的热门概念数据
- 修复 HeroPanel 热门概念模块无数据问题

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 14:52:31 +08:00
zdl
46be0249a8 ui: 移除热门概念模块的"点击查看详情"提示
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 14:49:33 +08:00
zdl
f2713e5e0a fix: 桑基图标题位置调整,避免被图表遮挡
- 标题 top 调整为 5
- 桑基图 series 添加 top: 50 给标题留出空间
- 添加 bottom, left, right 边距配置

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-15 14:22:09 +08:00
12 changed files with 115 additions and 70 deletions

View File

@@ -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>

View File

@@ -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',
}}

View File

@@ -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>

View File

@@ -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
});
})
];

View File

@@ -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"

View File

@@ -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>
{/* 流动式概念展示 */}

View File

@@ -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;

View File

@@ -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>
{' '}

View File

@@ -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">

View File

@@ -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}>

View File

@@ -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,

View File

@@ -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({