refactor(events): 关注事件数据源统一到 Redux
- useFollowingEvents: 改用 Redux selector 获取关注事件 - GlobalSidebarContext: 移除本地 followingEvents 状态,使用 Redux - 侧边栏和导航栏共享同一数据源,保持状态同步 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
// src/hooks/useFollowingEvents.js
|
||||
// 关注事件管理自定义 Hook
|
||||
// 关注事件管理自定义 Hook(与 Redux 状态同步,支持多组件共用)
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { logger } from '../utils/logger';
|
||||
import { getApiBase } from '../utils/apiConfig';
|
||||
import {
|
||||
loadFollowingEvents as loadFollowingEventsAction,
|
||||
toggleFollowEvent
|
||||
} from '../store/slices/stockSlice';
|
||||
|
||||
const EVENTS_PAGE_SIZE = 8;
|
||||
|
||||
/**
|
||||
* 关注事件管理 Hook
|
||||
* 提供事件加载、分页、取消关注等功能
|
||||
* 关注事件管理 Hook(导航栏专用)
|
||||
* 提供关注事件加载、分页、取消关注等功能
|
||||
* 监听 Redux 中的 followingEvents 变化,自动同步
|
||||
*
|
||||
* @returns {{
|
||||
* followingEvents: Array,
|
||||
@@ -24,77 +29,66 @@ const EVENTS_PAGE_SIZE = 8;
|
||||
*/
|
||||
export const useFollowingEvents = () => {
|
||||
const toast = useToast();
|
||||
const [followingEvents, setFollowingEvents] = useState([]);
|
||||
const [eventsLoading, setEventsLoading] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const [eventsPage, setEventsPage] = useState(1);
|
||||
|
||||
// 加载关注的事件
|
||||
const loadFollowingEvents = useCallback(async () => {
|
||||
try {
|
||||
setEventsLoading(true);
|
||||
const base = getApiBase();
|
||||
const resp = await fetch(base + '/api/account/events/following', {
|
||||
credentials: 'include',
|
||||
cache: 'no-store'
|
||||
});
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
if (data && data.success && Array.isArray(data.data)) {
|
||||
// 合并重复的事件(用最新的数据)
|
||||
const eventMap = new Map();
|
||||
for (const evt of data.data) {
|
||||
if (evt && evt.id) {
|
||||
eventMap.set(evt.id, evt);
|
||||
}
|
||||
}
|
||||
const merged = Array.from(eventMap.values());
|
||||
// 按创建时间降序排列(假设事件有 created_at 或 id)
|
||||
if (merged.length > 0 && merged[0].created_at) {
|
||||
merged.sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0));
|
||||
} else {
|
||||
merged.sort((a, b) => (b.id || 0) - (a.id || 0));
|
||||
}
|
||||
setFollowingEvents(merged);
|
||||
} else {
|
||||
setFollowingEvents([]);
|
||||
}
|
||||
} else {
|
||||
setFollowingEvents([]);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('useFollowingEvents', '加载关注事件失败', {
|
||||
error: e.message
|
||||
});
|
||||
setFollowingEvents([]);
|
||||
} finally {
|
||||
setEventsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
// 从 Redux 获取关注事件数据(与 GlobalSidebar 共用)
|
||||
const followingEvents = useSelector(state => state.stock.followingEvents || []);
|
||||
const eventsLoading = useSelector(state => state.stock.loading?.followingEvents || false);
|
||||
|
||||
// 取消关注事件
|
||||
// 从 Redux 获取关注事件列表长度(用于监听变化)
|
||||
const reduxEventsLength = useSelector(state => state.stock.followingEvents?.length || 0);
|
||||
|
||||
// 用于跟踪上一次的事件长度
|
||||
const prevEventsLengthRef = useRef(-1);
|
||||
|
||||
// 初始化时加载 Redux followingEvents(确保 Redux 状态被初始化)
|
||||
const hasInitializedRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!hasInitializedRef.current) {
|
||||
hasInitializedRef.current = true;
|
||||
logger.debug('useFollowingEvents', '初始化 Redux followingEvents');
|
||||
dispatch(loadFollowingEventsAction());
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
// 加载关注事件(通过 Redux)
|
||||
const loadFollowingEvents = useCallback(() => {
|
||||
logger.debug('useFollowingEvents', '触发 loadFollowingEvents');
|
||||
dispatch(loadFollowingEventsAction());
|
||||
}, [dispatch]);
|
||||
|
||||
// 监听 Redux followingEvents 长度变化,自动更新分页
|
||||
useEffect(() => {
|
||||
const currentLength = reduxEventsLength;
|
||||
const prevLength = prevEventsLengthRef.current;
|
||||
|
||||
// 当事件列表长度变化时,更新分页(确保不超出范围)
|
||||
if (prevLength !== -1 && currentLength !== prevLength) {
|
||||
const newMaxPage = Math.max(1, Math.ceil(currentLength / EVENTS_PAGE_SIZE));
|
||||
setEventsPage(p => Math.min(p, newMaxPage));
|
||||
}
|
||||
|
||||
prevEventsLengthRef.current = currentLength;
|
||||
}, [reduxEventsLength]);
|
||||
|
||||
// 取消关注事件(通过 Redux)
|
||||
const handleUnfollowEvent = useCallback(async (eventId) => {
|
||||
try {
|
||||
const base = getApiBase();
|
||||
const resp = await fetch(base + `/api/events/${eventId}/follow`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await resp.json().catch(() => ({}));
|
||||
if (resp.ok && data && data.success !== false) {
|
||||
setFollowingEvents((prev) => {
|
||||
const updated = (prev || []).filter((x) => x.id !== eventId);
|
||||
const newMaxPage = Math.max(1, Math.ceil((updated.length || 0) / EVENTS_PAGE_SIZE));
|
||||
setEventsPage((p) => Math.min(p, newMaxPage));
|
||||
return updated;
|
||||
});
|
||||
toast({ title: '已取消关注该事件', status: 'info', duration: 1500 });
|
||||
} else {
|
||||
toast({ title: '操作失败', status: 'error', duration: 2000 });
|
||||
}
|
||||
// 通过 Redux action 取消关注(乐观更新)
|
||||
await dispatch(toggleFollowEvent({
|
||||
eventId,
|
||||
isFollowing: true // 表示当前已关注,需要取消
|
||||
})).unwrap();
|
||||
|
||||
toast({ title: '已取消关注该事件', status: 'info', duration: 1500 });
|
||||
} catch (e) {
|
||||
toast({ title: '网络错误,操作失败', status: 'error', duration: 2000 });
|
||||
logger.error('useFollowingEvents', '取消关注事件失败', e);
|
||||
toast({ title: e.message || '操作失败', status: 'error', duration: 2000 });
|
||||
// 失败时重新加载列表
|
||||
dispatch(loadFollowingEventsAction());
|
||||
}
|
||||
}, [toast]);
|
||||
}, [dispatch, toast]);
|
||||
|
||||
return {
|
||||
followingEvents,
|
||||
|
||||
Reference in New Issue
Block a user