# WebSocket 事件实时推送 - 前端集成指南 ## 📦 已创建的文件 1. **`src/services/socketService.js`** - WebSocket 服务(已扩展) 2. **`src/hooks/useEventNotifications.js`** - React Hook 3. **`test_websocket.html`** - 测试页面 4. **`test_create_event.py`** - 测试脚本 --- ## 🚀 快速开始 ### 方案 1:使用 React Hook(推荐) 在任何 React 组件中使用: ```jsx import { useEventNotifications } from 'hooks/useEventNotifications'; import { useToast } from '@chakra-ui/react'; function EventsPage() { const toast = useToast(); // 订阅事件推送 const { newEvent, isConnected } = useEventNotifications({ eventType: 'all', // 'all' | 'policy' | 'market' | 'tech' | ... importance: 'all', // 'all' | 'S' | 'A' | 'B' | 'C' enabled: true, // 是否启用订阅 onNewEvent: (event) => { // 收到新事件时的处理 console.log('🔔 收到新事件:', event); // 显示 Toast 通知 toast({ title: '新事件提醒', description: event.title, status: 'info', duration: 5000, isClosable: true, position: 'top-right', }); } }); return ( 连接状态: {isConnected ? '已连接 ✅' : '未连接 ❌'} {/* 你的事件列表 */} ); } ``` --- ### 方案 2:在事件列表页面集成(完整示例) **在 `src/views/Community/components/EventList.js` 中集成:** ```jsx import React, { useState, useEffect } from 'react'; import { Box, Text, Badge, useToast } from '@chakra-ui/react'; import { useEventNotifications } from 'hooks/useEventNotifications'; function EventList() { const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const toast = useToast(); // 1️⃣ 初始加载事件列表(REST API) useEffect(() => { fetchEvents(); }, []); const fetchEvents = async () => { try { const response = await fetch('/api/events?per_page=20'); const data = await response.json(); if (data.success) { setEvents(data.data.events); } } catch (error) { console.error('加载事件失败:', error); } finally { setLoading(false); } }; // 2️⃣ 订阅 WebSocket 实时推送 const { newEvent, isConnected } = useEventNotifications({ eventType: 'all', importance: 'all', enabled: true, // 可以根据用户设置控制是否启用 onNewEvent: (event) => { console.log('🔔 收到新事件:', event); // 显示通知 toast({ title: '📰 新事件发布', description: `${event.title}`, status: 'info', duration: 6000, isClosable: true, position: 'top-right', }); // 将新事件添加到列表顶部 setEvents((prevEvents) => { // 检查是否已存在(防止重复) const exists = prevEvents.some(e => e.id === event.id); if (exists) { return prevEvents; } // 添加到顶部,最多保留 100 个 return [event, ...prevEvents].slice(0, 100); }); } }); return ( {/* 连接状态指示器 */} {isConnected ? '实时推送已开启' : '实时推送未连接'} {/* 事件列表 */} {loading ? ( 加载中... ) : ( {events.map((event) => ( ))} )} ); } export default EventList; ``` --- ### 方案 3:只订阅重要事件(S 和 A 级) ```jsx import { useImportantEventNotifications } from 'hooks/useEventNotifications'; function Dashboard() { const { importantEvents, isConnected } = useImportantEventNotifications((event) => { // 只会收到 S 和 A 级别的重要事件 console.log('⚠️ 重要事件:', event); // 播放提示音 new Audio('/notification.mp3').play(); }); return ( 重要事件通知 {importantEvents.map(event => ( {event.title} ))} ); } ``` --- ### 方案 4:直接使用 Service(不用 Hook) ```jsx import { useEffect } from 'react'; import socketService from 'services/socketService'; function MyComponent() { useEffect(() => { // 连接 socketService.connect(); // 订阅 const unsubscribe = socketService.subscribeToAllEvents((event) => { console.log('新事件:', event); }); // 清理 return () => { unsubscribe(); socketService.disconnect(); }; }, []); return
...
; } ``` --- ## 🎨 UI 集成示例 ### 1. Toast 通知(Chakra UI) ```jsx import { useToast } from '@chakra-ui/react'; const toast = useToast(); // 在 onNewEvent 回调中 onNewEvent: (event) => { toast({ title: '新事件', description: event.title, status: 'info', duration: 5000, isClosable: true, position: 'top-right', }); } ``` --- ### 2. 顶部通知栏 ```jsx import { Alert, AlertIcon, CloseButton } from '@chakra-ui/react'; function EventNotificationBanner() { const [showNotification, setShowNotification] = useState(false); const [latestEvent, setLatestEvent] = useState(null); useEventNotifications({ eventType: 'all', onNewEvent: (event) => { setLatestEvent(event); setShowNotification(true); } }); if (!showNotification || !latestEvent) return null; return ( 新事件:{latestEvent.title} setShowNotification(false)} /> ); } ``` --- ### 3. 角标提示(红点) ```jsx import { Badge } from '@chakra-ui/react'; function EventsMenuItem() { const [unreadCount, setUnreadCount] = useState(0); useEventNotifications({ eventType: 'all', onNewEvent: () => { setUnreadCount(prev => prev + 1); } }); return ( 事件中心 {unreadCount > 0 && ( {unreadCount > 99 ? '99+' : unreadCount} )} ); } ``` --- ### 4. 浮动通知卡片 ```jsx import { Box, Slide, useDisclosure } from '@chakra-ui/react'; function FloatingEventNotification() { const { isOpen, onClose, onOpen } = useDisclosure(); const [event, setEvent] = useState(null); useEventNotifications({ eventType: 'all', onNewEvent: (newEvent) => { setEvent(newEvent); onOpen(); // 5秒后自动关闭 setTimeout(onClose, 5000); } }); return ( {event?.title} {event?.description} ); } ``` --- ## 📋 API 参考 ### `useEventNotifications(options)` **参数:** | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `eventType` | string | `'all'` | 事件类型:`'all'` / `'policy'` / `'market'` / `'tech'` 等 | | `importance` | string | `'all'` | 重要性:`'all'` / `'S'` / `'A'` / `'B'` / `'C'` | | `enabled` | boolean | `true` | 是否启用订阅 | | `onNewEvent` | function | - | 收到新事件时的回调函数 | **返回值:** | 属性 | 类型 | 说明 | |------|------|------| | `newEvent` | object | 最新收到的事件对象 | | `isConnected` | boolean | WebSocket 连接状态 | | `error` | object | 错误信息 | | `clearNewEvent` | function | 清除新事件状态 | --- ### `socketService` API ```javascript // 连接 socketService.connect(options) // 断开 socketService.disconnect() // 订阅所有事件 socketService.subscribeToAllEvents(callback) // 订阅特定类型 socketService.subscribeToEventType('tech', callback) // 订阅特定重要性 socketService.subscribeToImportantEvents('S', callback) // 取消订阅 socketService.unsubscribeFromEvents({ eventType: 'all' }) // 检查连接状态 socketService.isConnected() ``` --- ## 🔧 事件数据结构 收到的 `event` 对象包含: ```javascript { id: 123, title: "事件标题", description: "事件描述", event_type: "tech", // 类型 importance: "S", // 重要性 status: "active", created_at: "2025-01-21T14:30:00", hot_score: 85.5, view_count: 1234, related_avg_chg: 5.2, // 平均涨幅 related_max_chg: 15.8, // 最大涨幅 keywords: ["AI", "芯片"], // 关键词 } ``` --- ## ⚙️ 高级配置 ### 1. 条件订阅(用户设置) ```jsx function EventsPage() { const [enableNotifications, setEnableNotifications] = useState( localStorage.getItem('enableEventNotifications') === 'true' ); useEventNotifications({ eventType: 'all', enabled: enableNotifications, // 根据用户设置控制 onNewEvent: handleNewEvent }); return ( { const enabled = e.target.checked; setEnableNotifications(enabled); localStorage.setItem('enableEventNotifications', enabled); }} > 启用事件实时通知 ); } ``` --- ### 2. 多个订阅(不同类型) ```jsx function MultiSubscriptionExample() { // 订阅科技类事件 useEventNotifications({ eventType: 'tech', onNewEvent: (event) => console.log('科技事件:', event) }); // 订阅政策类事件 useEventNotifications({ eventType: 'policy', onNewEvent: (event) => console.log('政策事件:', event) }); return
...
; } ``` --- ### 3. 防抖处理(避免通知过多) ```jsx import { debounce } from 'lodash'; const debouncedNotify = debounce((event) => { toast({ title: '新事件', description: event.title, }); }, 1000); useEventNotifications({ eventType: 'all', onNewEvent: debouncedNotify }); ``` --- ## 🧪 测试步骤 1. **启动 Flask 服务** ```bash python app.py ``` 2. **启动 React 应用** ```bash npm start ``` 3. **创建测试事件** ```bash python test_create_event.py ``` 4. **观察结果** - 最多等待 30 秒 - 前端页面应该显示通知 - 控制台输出日志 --- ## 🐛 常见问题 ### Q: 没有收到推送? **A:** 检查: 1. Flask 服务是否启动 2. 浏览器控制台是否有连接错误 3. 后端日志是否显示 `[轮询] 发现 X 个新事件` ### Q: 连接一直失败? **A:** 检查: 1. API_BASE_URL 配置是否正确 2. CORS 配置是否包含前端域名 3. 防火墙/代理设置 ### Q: 收到重复通知? **A:** 检查是否多次调用了 Hook,确保只在需要的地方订阅一次。 --- ## 📚 更多资源 - Socket.IO 文档: https://socket.io/docs/v4/ - Chakra UI Toast: https://chakra-ui.com/docs/components/toast - React Hooks: https://react.dev/reference/react --- **完成!🎉** 现在你的前端可以实时接收事件推送了!