diff --git a/CLAUDE.md b/CLAUDE.md
index b1460323..07e104f4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2958,6 +2958,29 @@ refactor(components): 将 EventCard 拆分为原子组件
> - **Community 页面**: [docs/Community.md](./docs/Community.md) - 页面架构、组件结构、数据流、变更历史
> - **其他页面**: 根据需要创建独立的页面文档
+### 2025-10-30: EventList.js 组件化重构
+
+**影响范围**: Community 页面核心组件
+
+**重构成果**:
+- 将 1095 行的 `EventList.js` 拆分为 497 行主组件 + 10 个子组件
+- 代码行数减少 **54.6%** (598 行)
+- 创建了 7 个原子组件 (Atoms) 和 2 个组合组件 (Molecules)
+
+**新增组件**:
+- `EventCard/` - 统一入口,智能路由紧凑/详细模式
+ - `CompactEventCard.js` - 紧凑模式事件卡片
+ - `DetailedEventCard.js` - 详细模式事件卡片
+ - 7 个原子组件: EventTimeline, EventImportanceBadge, EventStats, EventFollowButton, EventPriceDisplay, EventDescription, EventHeader
+
+**新增工具函数**:
+- `src/utils/priceFormatters.js` - 价格格式化工具 (getPriceChangeColor, formatPriceChange, PriceArrow)
+- `src/constants/animations.js` - 动画常量 (pulseAnimation, fadeIn, slideInUp)
+
+**优势**: 提高了代码可维护性、可复用性、可测试性和性能
+
+**详细文档**: 参见 [docs/Community.md](./docs/Community.md)
+
---
## 更新本文档
diff --git a/src/components/ChatBot/ChatInterface.js b/src/components/ChatBot/ChatInterface.js
new file mode 100644
index 00000000..35984432
--- /dev/null
+++ b/src/components/ChatBot/ChatInterface.js
@@ -0,0 +1,376 @@
+// src/components/ChatBot/ChatInterface.js
+// 聊天界面主组件
+
+import React, { useState, useRef, useEffect } from 'react';
+import {
+ Box,
+ Flex,
+ Input,
+ IconButton,
+ VStack,
+ HStack,
+ Text,
+ Spinner,
+ useColorModeValue,
+ useToast,
+ Divider,
+ Badge,
+ Menu,
+ MenuButton,
+ MenuList,
+ MenuItem,
+ Button,
+} from '@chakra-ui/react';
+import { FiSend, FiRefreshCw, FiSettings, FiDownload } from 'react-icons/fi';
+import { ChevronDownIcon } from '@chakra-ui/icons';
+import MessageBubble from './MessageBubble';
+import { mcpService } from '../../services/mcpService';
+import { logger } from '../../utils/logger';
+
+/**
+ * 聊天界面组件
+ */
+export const ChatInterface = () => {
+ const [messages, setMessages] = useState([
+ {
+ id: 1,
+ content: '你好!我是AI投资助手,我可以帮你查询股票信息、新闻资讯、概念板块、涨停分析等。请问有什么可以帮到你的?',
+ isUser: false,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ },
+ ]);
+ const [inputValue, setInputValue] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [availableTools, setAvailableTools] = useState([]);
+
+ const messagesEndRef = useRef(null);
+ const inputRef = useRef(null);
+ const toast = useToast();
+
+ // 颜色主题
+ const bgColor = useColorModeValue('white', 'gray.800');
+ const borderColor = useColorModeValue('gray.200', 'gray.600');
+ const inputBg = useColorModeValue('gray.50', 'gray.700');
+
+ // 加载可用工具列表
+ useEffect(() => {
+ const loadTools = async () => {
+ const result = await mcpService.listTools();
+ if (result.success) {
+ setAvailableTools(result.data);
+ logger.info('ChatInterface', '已加载MCP工具', { count: result.data.length });
+ }
+ };
+ loadTools();
+ }, []);
+
+ // 自动滚动到底部
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
+ // 发送消息
+ const handleSendMessage = async () => {
+ if (!inputValue.trim() || isLoading) return;
+
+ const userMessage = {
+ id: Date.now(),
+ content: inputValue,
+ isUser: true,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ };
+
+ setMessages((prev) => [...prev, userMessage]);
+ setInputValue('');
+ setIsLoading(true);
+
+ try {
+ // 调用MCP服务
+ const response = await mcpService.chat(inputValue, messages);
+
+ let botMessage;
+ if (response.success) {
+ // 根据返回的数据类型构造消息
+ const data = response.data;
+
+ if (typeof data === 'string') {
+ botMessage = {
+ id: Date.now() + 1,
+ content: data,
+ isUser: false,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ };
+ } else if (Array.isArray(data)) {
+ // 数据列表
+ botMessage = {
+ id: Date.now() + 1,
+ content: `找到 ${data.length} 条结果:`,
+ isUser: false,
+ type: 'data',
+ data: data,
+ timestamp: new Date().toISOString(),
+ };
+ } else if (typeof data === 'object') {
+ // 对象数据
+ botMessage = {
+ id: Date.now() + 1,
+ content: JSON.stringify(data, null, 2),
+ isUser: false,
+ type: 'markdown',
+ timestamp: new Date().toISOString(),
+ };
+ } else {
+ botMessage = {
+ id: Date.now() + 1,
+ content: '抱歉,我无法理解这个查询结果。',
+ isUser: false,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ };
+ }
+ } else {
+ botMessage = {
+ id: Date.now() + 1,
+ content: `抱歉,查询失败:${response.error}`,
+ isUser: false,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ };
+ }
+
+ setMessages((prev) => [...prev, botMessage]);
+ } catch (error) {
+ logger.error('ChatInterface', 'handleSendMessage', error);
+ const errorMessage = {
+ id: Date.now() + 1,
+ content: `抱歉,发生了错误:${error.message}`,
+ isUser: false,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ };
+ setMessages((prev) => [...prev, errorMessage]);
+ } finally {
+ setIsLoading(false);
+ inputRef.current?.focus();
+ }
+ };
+
+ // 处理键盘事件
+ const handleKeyPress = (e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSendMessage();
+ }
+ };
+
+ // 清空对话
+ const handleClearChat = () => {
+ setMessages([
+ {
+ id: 1,
+ content: '对话已清空。有什么可以帮到你的?',
+ isUser: false,
+ type: 'text',
+ timestamp: new Date().toISOString(),
+ },
+ ]);
+ };
+
+ // 复制消息
+ const handleCopyMessage = () => {
+ toast({
+ title: '已复制',
+ status: 'success',
+ duration: 2000,
+ isClosable: true,
+ });
+ };
+
+ // 反馈
+ const handleFeedback = (type) => {
+ logger.info('ChatInterface', 'Feedback', { type });
+ toast({
+ title: type === 'positive' ? '感谢反馈!' : '我们会改进',
+ status: 'info',
+ duration: 2000,
+ isClosable: true,
+ });
+ };
+
+ // 快捷问题
+ const quickQuestions = [
+ '查询贵州茅台的股票信息',
+ '搜索人工智能相关新闻',
+ '今日涨停股票有哪些',
+ '新能源概念板块分析',
+ ];
+
+ const handleQuickQuestion = (question) => {
+ setInputValue(question);
+ inputRef.current?.focus();
+ };
+
+ // 导出对话
+ const handleExportChat = () => {
+ const chatText = messages
+ .map((msg) => `[${msg.isUser ? '用户' : 'AI'}] ${msg.content}`)
+ .join('\n\n');
+
+ const blob = new Blob([chatText], { type: 'text/plain' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `chat_${new Date().toISOString().slice(0, 10)}.txt`;
+ a.click();
+ URL.revokeObjectURL(url);
+ };
+
+ return (
+
+ {/* 头部工具栏 */}
+
+
+ AI投资助手
+ 在线
+ {availableTools.length > 0 && (
+ {availableTools.length} 个工具
+ )}
+
+
+ }
+ size="sm"
+ variant="ghost"
+ aria-label="清空对话"
+ onClick={handleClearChat}
+ />
+ }
+ size="sm"
+ variant="ghost"
+ aria-label="导出对话"
+ onClick={handleExportChat}
+ />
+
+
+
+
+ {/* 消息列表 */}
+
+
+ {messages.map((message) => (
+
+ ))}
+ {isLoading && (
+
+
+
+ AI正在思考...
+
+
+ )}
+
+
+
+
+ {/* 快捷问题(仅在消息较少时显示) */}
+ {messages.length <= 2 && (
+
+ 快捷问题:
+
+ {quickQuestions.map((question, idx) => (
+
+ ))}
+
+
+ )}
+
+
+
+ {/* 输入框 */}
+
+
+ setInputValue(e.target.value)}
+ onKeyPress={handleKeyPress}
+ placeholder="输入消息... (Shift+Enter换行,Enter发送)"
+ bg={inputBg}
+ border="none"
+ _focus={{ boxShadow: 'none' }}
+ mr={2}
+ disabled={isLoading}
+ />
+ }
+ colorScheme="blue"
+ aria-label="发送"
+ onClick={handleSendMessage}
+ isLoading={isLoading}
+ disabled={!inputValue.trim()}
+ />
+
+
+
+ );
+};
+
+export default ChatInterface;
diff --git a/src/components/ChatBot/MessageBubble.js b/src/components/ChatBot/MessageBubble.js
new file mode 100644
index 00000000..ce491702
--- /dev/null
+++ b/src/components/ChatBot/MessageBubble.js
@@ -0,0 +1,149 @@
+// src/components/ChatBot/MessageBubble.js
+// 聊天消息气泡组件
+
+import React from 'react';
+import {
+ Box,
+ Flex,
+ Text,
+ Avatar,
+ useColorModeValue,
+ IconButton,
+ HStack,
+ Code,
+ Badge,
+ VStack,
+} from '@chakra-ui/react';
+import { FiCopy, FiThumbsUp, FiThumbsDown } from 'react-icons/fi';
+import ReactMarkdown from 'react-markdown';
+
+/**
+ * 消息气泡组件
+ * @param {Object} props
+ * @param {Object} props.message - 消息对象
+ * @param {boolean} props.isUser - 是否是用户消息
+ * @param {Function} props.onCopy - 复制消息回调
+ * @param {Function} props.onFeedback - 反馈回调
+ */
+export const MessageBubble = ({ message, isUser, onCopy, onFeedback }) => {
+ const userBg = useColorModeValue('blue.500', 'blue.600');
+ const botBg = useColorModeValue('gray.100', 'gray.700');
+ const userColor = 'white';
+ const botColor = useColorModeValue('gray.800', 'white');
+
+ const handleCopy = () => {
+ navigator.clipboard.writeText(message.content);
+ onCopy?.();
+ };
+
+ return (
+
+
+ {/* 头像 */}
+
+
+ {/* 消息内容 */}
+
+
+ {message.type === 'text' ? (
+
+ {message.content}
+
+ ) : message.type === 'markdown' ? (
+
+ {message.content}
+
+ ) : message.type === 'data' ? (
+
+ {message.data && Array.isArray(message.data) && message.data.slice(0, 5).map((item, idx) => (
+
+ {Object.entries(item).map(([key, value]) => (
+
+ {key}:
+ {String(value)}
+
+ ))}
+
+ ))}
+ {message.data && message.data.length > 5 && (
+
+ +{message.data.length - 5} 更多结果
+
+ )}
+
+ ) : null}
+
+
+ {/* 消息操作按钮(仅AI消息) */}
+ {!isUser && (
+
+ }
+ size="xs"
+ variant="ghost"
+ aria-label="复制"
+ onClick={handleCopy}
+ />
+ }
+ size="xs"
+ variant="ghost"
+ aria-label="赞"
+ onClick={() => onFeedback?.('positive')}
+ />
+ }
+ size="xs"
+ variant="ghost"
+ aria-label="踩"
+ onClick={() => onFeedback?.('negative')}
+ />
+
+ )}
+
+ {/* 时间戳 */}
+
+ {message.timestamp ? new Date(message.timestamp).toLocaleTimeString('zh-CN', {
+ hour: '2-digit',
+ minute: '2-digit',
+ }) : ''}
+
+
+
+
+ );
+};
+
+export default MessageBubble;
diff --git a/src/components/ChatBot/index.js b/src/components/ChatBot/index.js
new file mode 100644
index 00000000..59a0a5f6
--- /dev/null
+++ b/src/components/ChatBot/index.js
@@ -0,0 +1,7 @@
+// src/components/ChatBot/index.js
+// 聊天机器人组件统一导出
+
+export { ChatInterface } from './ChatInterface';
+export { MessageBubble } from './MessageBubble';
+
+export { ChatInterface as default } from './ChatInterface';
diff --git a/src/components/Navbars/components/MobileDrawer/MobileDrawer.js b/src/components/Navbars/components/MobileDrawer/MobileDrawer.js
index 1649deff..89d7d7ad 100644
--- a/src/components/Navbars/components/MobileDrawer/MobileDrawer.js
+++ b/src/components/Navbars/components/MobileDrawer/MobileDrawer.js
@@ -243,6 +243,26 @@ const MobileDrawer = memo(({
AGENT社群
+ handleNavigate('/agent-chat')}
+ py={1}
+ px={3}
+ borderRadius="md"
+ _hover={{ bg: 'gray.100' }}
+ cursor="pointer"
+ bg={location.pathname.includes('/agent-chat') ? 'blue.50' : 'transparent'}
+ borderLeft={location.pathname.includes('/agent-chat') ? '3px solid' : 'none'}
+ borderColor="blue.600"
+ fontWeight={location.pathname.includes('/agent-chat') ? 'bold' : 'normal'}
+ >
+
+ AI聊天助手
+
+ AI
+ NEW
+
+
+
{
as={Button}
variant="ghost"
rightIcon={}
+ bg={isActive(['/agent-chat']) ? 'blue.50' : 'transparent'}
+ color={isActive(['/agent-chat']) ? 'blue.600' : 'inherit'}
+ fontWeight={isActive(['/agent-chat']) ? 'bold' : 'normal'}
+ borderBottom={isActive(['/agent-chat']) ? '2px solid' : 'none'}
+ borderColor="blue.600"
+ _hover={{ bg: isActive(['/agent-chat']) ? 'blue.100' : 'gray.50' }}
onMouseEnter={agentCommunityMenu.handleMouseEnter}
onMouseLeave={agentCommunityMenu.handleMouseLeave}
onClick={agentCommunityMenu.handleClick}
@@ -207,10 +213,31 @@ const DesktopNav = memo(({ isAuthenticated, user }) => {
+