Compare commits

...

8 Commits

Author SHA1 Message Date
zdl
f05daa3a78 fix(TradingSimulation): 修复 React Hooks 调用顺序错误
提取 JSX 中直接调用的 useColorModeValue 到组件顶部,避免 Hooks 顺序不一致。

修改内容:
- 在第 95 行添加 contentTextColor 常量
- 替换第 350 行 Heading 中的内联 Hook 调用
- 替换第 361 行 Text 中的内联 Hook 调用

修复警告:React has detected a change in the order of Hooks called by TradingSimulation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 19:08:24 +08:00
zdl
2461ce81c9 fix: 修复导航菜单 hover 触发实现方式
修复之前提交(47f84c5)中使用的无效 trigger="hover" 属性。
Chakra UI Menu 组件不支持 trigger 属性,改用正确的实现方式:

**实现方式:**
- 使用 useDisclosure Hook 管理菜单开关状态
- 为 MenuButton 和 MenuList 添加 onMouseEnter/onMouseLeave 事件
- 这样可以确保鼠标从按钮移到菜单列表时保持打开状态

**修改的组件:**
- DesktopNav.js: 为4个菜单添加独立的 useDisclosure Hook
- MoreMenu.js: 平板版"更多"菜单
- PersonalCenterMenu.js: 个人中心菜单

**技术要点:**
- MenuButton 和 MenuList 都需要 hover 事件处理
- 每个菜单使用独立的 useDisclosure 实例
- 符合 Chakra UI 官方推荐的 hover 菜单实现方式

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 19:06:21 +08:00
zdl
85d505cd53 fix: 修复 InvestmentCalendar Ant Design 5.x API 废弃警告
## 问题
控制台出现 4 个 Ant Design API 废弃警告:
```
[antd: Calendar] `dateCellRender` is deprecated. Please use `cellRender` instead.
[antd: Modal] `visible` is deprecated. Please use `open` instead.
[antd: Modal] `bodyStyle` is deprecated. Please use `styles.body` instead.
[antd: Drawer] `visible` is deprecated. Please use `open` instead.
```

## 修复内容

### 1. Calendar API (Line 137, 687)
**旧 API**:
```javascript
const dateCellRender = (value) => {
  const dateStr = value.format('YYYY-MM-DD');
  // ...
};
<Calendar dateCellRender={dateCellRender} />
```

**新 API (Ant Design 5.x)**:
```javascript
const cellRender = (current, info) => {
  // 只处理日期单元格,月份单元格返回默认
  if (info.type !== 'date') return info.originNode;

  const dateStr = current.format('YYYY-MM-DD');
  // ...
};
<Calendar cellRender={cellRender} />
```

### 2. Modal API (Line 701, 766)
`visible` → `open`
```javascript
// 旧 API
<Modal visible={modalVisible} />

// 新 API
<Modal open={modalVisible} />
```

### 3. Modal Styles API (Line 705)
`bodyStyle` → `styles.body`
```javascript
// 旧 API
<Modal bodyStyle={{ padding: '24px' }} />

// 新 API
<Modal styles={{ body: { padding: '24px' } }} />
```

### 4. Drawer API (Line 740)
`visible` → `open`
```javascript
// 旧 API
<Drawer visible={detailDrawerVisible} />

// 新 API
<Drawer open={detailDrawerVisible} />
```

## 影响
-  消除 4 个 Ant Design API 废弃警告
-  兼容 Ant Design 5.x
-  功能不受影响

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 19:04:30 +08:00
zdl
1886c54e0f fix: 修复 StockOverview prevStats 未定义错误
## 问题
控制台报错:
```
ReferenceError: prevStats is not defined
    at fetchMarketStats (index.js:247:1)
```

## 根本原因
`fetchMarketStats` 函数中引用了不存在的变量 `prevStats`:
```javascript
//  错误代码
const newStats = {
  ...data.summary,
  rising_count: prevStats?.rising_count,
  falling_count: prevStats?.falling_count,
  date: data.trade_date
};
```

这里的 `prevStats` 变量从未定义或声明。

## 解决方案
使用状态变量 `marketStats` 来获取之前的值:
```javascript
//  正确代码
const newStats = {
  ...data.summary,
  rising_count: marketStats?.rising_count,
  falling_count: marketStats?.falling_count,
  date: data.trade_date
};
```

## 影响
-  修复市场统计数据加载错误
-  正确保留上涨/下跌家数
-  消除控制台 ReferenceError

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 18:59:49 +08:00
zdl
6829f687ee fix: 修复 MSW EventEmitter 内存泄漏警告
## 问题
控制台警告:
```
MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
11 response:mocked listeners added. Use emitter.setMaxListeners() to increase limit
```

## 根本原因

类似 PostHog 的问题:

1. **React StrictMode 双重渲染** - 开发环境组件渲染两次
2. **热重载** - 代码更改时频繁重新加载模块
3. **缺少启动锁** - `startMockServiceWorker()` 被多次调用
4. **事件监听器累积** - 每次启动添加新 listener,旧的未清理

## 解决方案

### 方案A: 防止重复启动
添加启动状态锁:
```javascript
let isStarting = false;
let isStarted = false;

export async function startMockServiceWorker() {
  // 防止重复启动
  if (isStarting || isStarted) {
    console.log('[MSW] 已启动,跳过重复调用');
    return;
  }

  isStarting = true;

  try {
    await worker.start({...});
    isStarted = true;  // 成功后标记
  } finally {
    isStarting = false;  // 无论成功失败都重置
  }
}
```

### 方案B: 完善 stop 逻辑
确保正确清理:
```javascript
export function stopMockServiceWorker() {
  if (!isStarted) return;  // 避免重复停止

  worker.stop();
  isStarted = false;  // 重置状态
  console.log('[MSW] Mock Service Worker 已停止');
}
```

## 影响
-  修复 EventEmitter 内存泄漏警告
-  防止热重载时重复启动 MSW
-  正确清理事件监听器
-  提升开发体验

## 验证
重启开发服务器后:
-  不再有 MaxListenersExceededWarning
-  MSW 只启动一次
-  热重载正常工作
-  Mock 功能正常

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 18:56:12 +08:00
zdl
47f84c5eff feat: 导航菜单改为 hover 触发
为所有导航菜单组件添加 trigger="hover" 属性,使菜单在鼠标悬停时自动展开,提升用户体验。

修改的组件:
- DesktopNav.js: 4 个主导航菜单(高频跟踪、行情复盘、AGENT社群、联系我们)
- MoreMenu.js: 平板版"更多"下拉菜单
- PersonalCenterMenu.js: 个人中心下拉菜单

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 18:49:14 +08:00
zdl
a0d1790469 fix: 修复 PostHog AbortError 和重复初始化问题
## 问题
控制台报错:
```
[PostHog.js] AbortError: The user aborted a request.
```

## 根本原因

### 1. 热重载导致重复初始化
- 开发环境频繁热重载
- App.js 每次重载都调用 initPostHog()
- 之前的网络请求被新请求中断 → AbortError

### 2. 缺少初始化状态管理
- 没有防止重复初始化的锁
- 每次组件更新都可能触发新的初始化

## 解决方案

### 方案A: 防止重复初始化
添加初始化状态锁:
```javascript
let isInitializing = false;
let isInitialized = false;

export const initPostHog = () => {
  // 防止重复初始化
  if (isInitializing || isInitialized) {
    console.log('📊 PostHog 已初始化,跳过重复调用');
    return;
  }

  isInitializing = true;

  try {
    posthog.init(apiKey, {...});
    isInitialized = true;  // 成功后标记为已初始化
  } finally {
    isInitializing = false;  // 无论成功失败都重置标志
  }
};
```

### 方案B: 捕获并忽略 AbortError
在 catch 块中特殊处理:
```javascript
} catch (error) {
  // 忽略 AbortError(通常由热重载引起)
  if (error.name === 'AbortError') {
    console.log('⚠️ PostHog 初始化请求被中断(热重载)');
    return;  // 静默处理,不报错
  }
  console.error(' PostHog initialization failed:', error);
}
```

## 影响
-  修复 AbortError 警告
-  防止热重载时重复初始化
-  提升开发体验
-  不影响生产环境

## 验证
重启开发服务器后:
-  不再有 AbortError
-  PostHog 只初始化一次
-  热重载正常工作

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 18:49:03 +08:00
zdl
0364b3a927 fix(NotificationContainer): 修复 React Hooks 调用顺序错误
**问题描述**
React 检测到 NotificationItem 组件中 Hooks 调用顺序不一致:

```
Warning: React has detected a change in the order of Hooks called by null.
Previous render: 24. useCallback
Next render:     25. useContext
```

**根本原因**
在第 433 行,`useColorModeValue` Hook 在条件对象展开中被调用:

```javascript
{...(isNewest && {
    borderTopColor: useColorModeValue(...),  //  违反 Hooks 规则
})}
```

当 `isNewest` 值变化时:
- `isNewest = false` → Hook 不调用
- `isNewest = true` → Hook 调用
- 导致不同渲染的 Hooks 数量不一致

**React Hooks 规则**
> Hooks 必须在组件顶层调用,不能在条件语句、循环或嵌套函数中调用

**修复内容**

1. **将 Hook 移到组件顶层** (第 349-353 行)
```javascript
// 最新通知的 borderTopColor(避免在条件语句中调用 Hook)
const newestBorderTopColor = useColorModeValue(
    `${typeConfig.colorScheme}.100`,
    `${typeConfig.colorScheme}.700`
);
```

2. **添加到 colors 对象** (第 365 行)
```javascript
const colors = useMemo(() => ({
    // ... 其他颜色
    newestBorderTop: newestBorderTopColor,
}), [/* dependencies */]);
```

3. **在 JSX 中使用预计算的值** (第 439 行)
```diff
  {...(isNewest && {
-     borderTopColor: useColorModeValue(...),
+     borderTopColor: colors.newestBorderTop,
  })}
```

**修复验证**
-  所有 Hooks 在每次渲染都以相同顺序调用
-  消除 React Hooks 警告
-  功能保持不变(视觉效果一致)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 18:43:19 +08:00
9 changed files with 117 additions and 31 deletions

View File

@@ -12,7 +12,8 @@ import {
Text,
Flex,
Badge,
useColorModeValue
useColorModeValue,
useDisclosure
} from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
import { useNavigate, useLocation } from 'react-router-dom';
@@ -36,6 +37,12 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
// 🎯 初始化导航埋点Hook
const navEvents = useNavigationEvents({ component: 'top_nav' });
// 🎯 为每个菜单创建独立的 useDisclosure Hook
const { isOpen: isHighFreqOpen, onOpen: onHighFreqOpen, onClose: onHighFreqClose } = useDisclosure();
const { isOpen: isMarketReviewOpen, onOpen: onMarketReviewOpen, onClose: onMarketReviewClose } = useDisclosure();
const { isOpen: isAgentCommunityOpen, onOpen: onAgentCommunityOpen, onClose: onAgentCommunityClose } = useDisclosure();
const { isOpen: isContactUsOpen, onOpen: onContactUsOpen, onClose: onContactUsClose } = useDisclosure();
// 辅助函数:判断导航项是否激活
const isActive = useCallback((paths) => {
return paths.some(path => location.pathname.includes(path));
@@ -46,7 +53,7 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
return (
<HStack spacing={8}>
{/* 高频跟踪 */}
<Menu>
<Menu isOpen={isHighFreqOpen} onClose={onHighFreqClose}>
<MenuButton
as={Button}
variant="ghost"
@@ -57,10 +64,12 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
borderBottom={isActive(['/community', '/concepts']) ? '2px solid' : 'none'}
borderColor="blue.600"
_hover={{ bg: isActive(['/community', '/concepts']) ? 'blue.100' : 'gray.50' }}
onMouseEnter={onHighFreqOpen}
onMouseLeave={onHighFreqClose}
>
高频跟踪
</MenuButton>
<MenuList minW="260px" p={2}>
<MenuList minW="260px" p={2} onMouseEnter={onHighFreqOpen} onMouseLeave={onHighFreqClose}>
<MenuItem
onClick={() => {
// 🎯 追踪菜单项点击
@@ -102,7 +111,7 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
</Menu>
{/* 行情复盘 */}
<Menu>
<Menu isOpen={isMarketReviewOpen} onClose={onMarketReviewClose}>
<MenuButton
as={Button}
variant="ghost"
@@ -113,10 +122,12 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
borderBottom={isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? '2px solid' : 'none'}
borderColor="blue.600"
_hover={{ bg: isActive(['/limit-analyse', '/stocks', '/trading-simulation']) ? 'blue.100' : 'gray.50' }}
onMouseEnter={onMarketReviewOpen}
onMouseLeave={onMarketReviewClose}
>
行情复盘
</MenuButton>
<MenuList minW="260px" p={2}>
<MenuList minW="260px" p={2} onMouseEnter={onMarketReviewOpen} onMouseLeave={onMarketReviewClose}>
<MenuItem
onClick={() => navigate('/limit-analyse')}
borderRadius="md"
@@ -160,11 +171,17 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
</Menu>
{/* AGENT社群 */}
<Menu>
<MenuButton as={Button} variant="ghost" rightIcon={<ChevronDownIcon />}>
<Menu isOpen={isAgentCommunityOpen} onClose={onAgentCommunityClose}>
<MenuButton
as={Button}
variant="ghost"
rightIcon={<ChevronDownIcon />}
onMouseEnter={onAgentCommunityOpen}
onMouseLeave={onAgentCommunityClose}
>
AGENT社群
</MenuButton>
<MenuList minW="300px" p={4}>
<MenuList minW="300px" p={4} onMouseEnter={onAgentCommunityOpen} onMouseLeave={onAgentCommunityClose}>
<MenuItem
isDisabled
cursor="not-allowed"
@@ -183,11 +200,17 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
</Menu>
{/* 联系我们 */}
<Menu>
<MenuButton as={Button} variant="ghost" rightIcon={<ChevronDownIcon />}>
<Menu isOpen={isContactUsOpen} onClose={onContactUsClose}>
<MenuButton
as={Button}
variant="ghost"
rightIcon={<ChevronDownIcon />}
onMouseEnter={onContactUsOpen}
onMouseLeave={onContactUsClose}
>
联系我们
</MenuButton>
<MenuList minW="260px" p={4}>
<MenuList minW="260px" p={4} onMouseEnter={onContactUsOpen} onMouseLeave={onContactUsClose}>
<Text fontSize="sm" color={contactTextColor}>敬请期待</Text>
</MenuList>
</Menu>

View File

@@ -12,7 +12,8 @@ import {
Text,
Flex,
HStack,
Badge
Badge,
useDisclosure
} from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
import { useNavigate, useLocation } from 'react-router-dom';
@@ -29,6 +30,9 @@ const MoreMenu = memo(({ isAuthenticated, user }) => {
const navigate = useNavigate();
const location = useLocation();
// 🎯 为"更多"菜单创建 useDisclosure Hook
const { isOpen, onOpen, onClose } = useDisclosure();
// 辅助函数:判断导航项是否激活
const isActive = useCallback((paths) => {
return paths.some(path => location.pathname.includes(path));
@@ -37,16 +41,18 @@ const MoreMenu = memo(({ isAuthenticated, user }) => {
if (!isAuthenticated || !user) return null;
return (
<Menu>
<Menu isOpen={isOpen} onClose={onClose}>
<MenuButton
as={Button}
variant="ghost"
rightIcon={<ChevronDownIcon />}
fontWeight="medium"
onMouseEnter={onOpen}
onMouseLeave={onClose}
>
更多
</MenuButton>
<MenuList minW="300px" p={2}>
<MenuList minW="300px" p={2} onMouseEnter={onOpen} onMouseLeave={onClose}>
{/* 高频跟踪组 */}
<Text fontSize="xs" fontWeight="bold" px={3} py={2} color="gray.500">高频跟踪</Text>
<MenuItem

View File

@@ -12,7 +12,8 @@ import {
Box,
Text,
Badge,
useColorModeValue
useColorModeValue,
useDisclosure
} from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
import { FiHome, FiUser, FiSettings, FiLogOut } from 'react-icons/fi';
@@ -31,6 +32,9 @@ const PersonalCenterMenu = memo(({ user, handleLogout }) => {
const navigate = useNavigate();
const hoverBg = useColorModeValue('gray.100', 'gray.700');
// 🎯 为个人中心菜单创建 useDisclosure Hook
const { isOpen, onOpen, onClose } = useDisclosure();
// 获取显示名称
const getDisplayName = () => {
if (user.nickname) return user.nickname;
@@ -41,17 +45,19 @@ const PersonalCenterMenu = memo(({ user, handleLogout }) => {
};
return (
<Menu>
<Menu isOpen={isOpen} onClose={onClose}>
<MenuButton
as={Button}
size="sm"
variant="ghost"
rightIcon={<ChevronDownIcon />}
_hover={{ bg: hoverBg }}
onMouseEnter={onOpen}
onMouseLeave={onClose}
>
个人中心
</MenuButton>
<MenuList>
<MenuList onMouseEnter={onOpen} onMouseLeave={onClose}>
{/* 用户信息区 */}
<Box px={3} py={2} borderBottom="1px" borderColor="gray.200">
<Text fontSize="sm" fontWeight="bold">{getDisplayName()}</Text>

View File

@@ -346,6 +346,11 @@ const NotificationItem = React.memo(({ notification, onClose, isNewest = false }
`${typeConfig.colorScheme}.200`,
`${typeConfig.colorScheme}.700`
);
// 最新通知的 borderTopColor避免在条件语句中调用 Hook
const newestBorderTopColor = useColorModeValue(
`${typeConfig.colorScheme}.100`,
`${typeConfig.colorScheme}.700`
);
// 使用 useMemo 缓存颜色对象(避免不必要的重新创建)
const colors = useMemo(() => ({
@@ -357,7 +362,8 @@ const NotificationItem = React.memo(({ notification, onClose, isNewest = false }
metaText: metaTextColor,
hoverBg: hoverBgColor,
closeButtonHoverBg: closeButtonHoverBgColor,
}), [priorityBgColor, borderColor, iconColor, textColor, subTextColor, metaTextColor, hoverBgColor, closeButtonHoverBgColor]);
newestBorderTop: newestBorderTopColor,
}), [priorityBgColor, borderColor, iconColor, textColor, subTextColor, metaTextColor, hoverBgColor, closeButtonHoverBgColor, newestBorderTopColor]);
// 点击处理(只有真正可点击时才执行)- 使用 useCallback 优化
const handleClick = useCallback(() => {
@@ -430,7 +436,7 @@ const NotificationItem = React.memo(({ notification, onClose, isNewest = false }
borderRight: '1px solid',
borderRightColor: colors.border,
borderTop: '1px solid',
borderTopColor: useColorModeValue(`${typeConfig.colorScheme}.100`, `${typeConfig.colorScheme}.700`),
borderTopColor: colors.newestBorderTop,
})}
>
{/* 头部区域:标题 + 可选标识 */}

View File

@@ -1,11 +1,21 @@
// src/lib/posthog.js
import posthog from 'posthog-js';
// 初始化状态管理(防止重复初始化)
let isInitializing = false;
let isInitialized = false;
/**
* Initialize PostHog SDK
* Should be called once when the app starts
*/
export const initPostHog = () => {
// 防止重复初始化
if (isInitializing || isInitialized) {
console.log('📊 PostHog 已初始化或正在初始化中,跳过重复调用');
return;
}
// Only run in browser environment
if (typeof window === 'undefined') return;
@@ -17,6 +27,8 @@ export const initPostHog = () => {
return;
}
isInitializing = true;
try {
posthog.init(apiKey, {
api_host: apiHost,
@@ -85,9 +97,17 @@ export const initPostHog = () => {
},
});
isInitialized = true;
console.log('📊 PostHog Analytics initialized');
} catch (error) {
// 忽略 AbortError通常由热重载或快速导航引起
if (error.name === 'AbortError') {
console.log('⚠️ PostHog 初始化请求被中断(可能是热重载),这是正常的');
return;
}
console.error('❌ PostHog initialization failed:', error);
} finally {
isInitializing = false;
}
};

View File

@@ -7,8 +7,18 @@ import { handlers } from './handlers';
// 创建 Service Worker 实例
export const worker = setupWorker(...handlers);
// 启动状态管理(防止重复启动)
let isStarting = false;
let isStarted = false;
// 启动 Mock Service Worker
export async function startMockServiceWorker() {
// 防止重复启动
if (isStarting || isStarted) {
console.log('[MSW] Mock Service Worker 已启动或正在启动中,跳过重复调用');
return;
}
// 只在开发环境且 REACT_APP_ENABLE_MOCK=true 时启动
const shouldEnableMock = process.env.REACT_APP_ENABLE_MOCK === 'true';
@@ -17,6 +27,8 @@ export async function startMockServiceWorker() {
return;
}
isStarting = true;
try {
await worker.start({
// 🎯 智能穿透模式(关键配置)
@@ -34,6 +46,7 @@ export async function startMockServiceWorker() {
quiet: false,
});
isStarted = true;
console.log(
'%c[MSW] Mock Service Worker 已启动 🎭',
'color: #4CAF50; font-weight: bold; font-size: 14px;'
@@ -48,12 +61,20 @@ export async function startMockServiceWorker() {
);
} catch (error) {
console.error('[MSW] 启动失败:', error);
} finally {
isStarting = false;
}
}
// 停止 Mock Service Worker
export function stopMockServiceWorker() {
if (!isStarted) {
console.log('[MSW] Mock Service Worker 未启动,无需停止');
return;
}
worker.stop();
isStarted = false;
console.log('[MSW] Mock Service Worker 已停止');
}

View File

@@ -133,9 +133,12 @@ const InvestmentCalendar = () => {
loadEventCounts(currentMonth);
}, [currentMonth, loadEventCounts]);
// 自定义日期单元格渲染
const dateCellRender = (value) => {
const dateStr = value.format('YYYY-MM-DD');
// 自定义日期单元格渲染Ant Design 5.x API
const cellRender = (current, info) => {
// 只处理日期单元格,月份单元格返回默认
if (info.type !== 'date') return info.originNode;
const dateStr = current.format('YYYY-MM-DD');
const dayEvents = eventCounts.find(item => item.date === dateStr);
if (dayEvents && dayEvents.count > 0) {
@@ -681,7 +684,7 @@ const InvestmentCalendar = () => {
>
<Calendar
fullscreen={false}
dateCellRender={dateCellRender}
cellRender={cellRender}
onSelect={handleDateSelect}
onPanelChange={(date) => setCurrentMonth(date)}
/>
@@ -695,11 +698,11 @@ const InvestmentCalendar = () => {
<span>{selectedDate?.format('YYYY年MM月DD日')} 投资事件</span>
</Space>
}
visible={modalVisible}
open={modalVisible}
onCancel={() => setModalVisible(false)}
width={1200}
footer={null}
bodyStyle={{ padding: '24px' }}
styles={{ body: { padding: '24px' } }}
zIndex={1500}
>
<Spin spinning={loading}>
@@ -734,7 +737,7 @@ const InvestmentCalendar = () => {
placement="right"
width={600}
onClose={() => setDetailDrawerVisible(false)}
visible={detailDrawerVisible}
open={detailDrawerVisible}
zIndex={1500}
>
{selectedDetail?.content?.type === 'citation' ? (
@@ -760,7 +763,7 @@ const InvestmentCalendar = () => {
)}
</Space>
}
visible={stockModalVisible}
open={stockModalVisible}
onCancel={() => {
setStockModalVisible(false);
setExpandedReasons({}); // 清理展开状态

View File

@@ -244,8 +244,8 @@ const StockOverview = () => {
const newStats = {
...data.summary,
// 保留之前从 heatmap 接口获取的上涨/下跌家数
rising_count: prevStats?.rising_count,
falling_count: prevStats?.falling_count,
rising_count: marketStats?.rising_count,
falling_count: marketStats?.falling_count,
date: data.trade_date
};
setMarketStats(newStats);

View File

@@ -92,6 +92,7 @@ export default function TradingSimulation() {
const xAxisLabelColor = useColorModeValue('#718096', '#A0AEC0');
const yAxisLabelColor = useColorModeValue('#718096', '#A0AEC0');
const gridBorderColor = useColorModeValue('#E2E8F0', '#4A5568');
const contentTextColor = useColorModeValue('gray.700', 'white');
// ========== 2. 所有 useEffect 也必须在条件返回之前 ==========
useEffect(() => {
@@ -346,7 +347,7 @@ export default function TradingSimulation() {
<VStack spacing={6} align="stretch">
{/* 账户概览统计 */}
<Box>
<Heading size="lg" mb={4} color={useColorModeValue('gray.700', 'white')}>
<Heading size="lg" mb={4} color={contentTextColor}>
📊 账户统计分析
</Heading>
<AccountOverview account={account} tradingEvents={tradingEvents} />
@@ -357,7 +358,7 @@ export default function TradingSimulation() {
<Card>
<CardHeader>
<HStack justify="space-between">
<Text fontSize="lg" fontWeight="bold" color={useColorModeValue('gray.700', 'white')}>
<Text fontSize="lg" fontWeight="bold" color={contentTextColor}>
📈 资产走势分析
</Text>
<Badge colorScheme="purple" variant="outline">