# 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 (
);
}
```
---
### 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
---
**完成!🎉** 现在你的前端可以实时接收事件推送了!