feat: 实现 Redux 全局状态管理事件关注功能

本次提交实现了滚动列表和事件详情的关注按钮状态同步:

 Redux 状态管理
- communityDataSlice.js: 添加 eventFollowStatus state
- 新增 toggleEventFollow AsyncThunk(复用 EventList.js 逻辑)
- 新增 setEventFollowStatus reducer 和 selectEventFollowStatus selector

 组件集成
- DynamicNewsCard.js: 从 Redux 读取关注状态并传递给子组件
- EventScrollList.js: 接收并传递关注状态给事件卡片
- DynamicNewsDetailPanel.js: 移除本地 state,使用 Redux 状态

 Mock API 支持
- event.js: 添加 POST /api/events/:eventId/follow 处理器
- 返回 { is_following, follower_count } 模拟数据

 Bug 修复
- EventDetail/index.js: 添加 useRef 导入
- concept.js: 导出 generatePopularConcepts 函数
- event.js: 添加 /api/events/:eventId/concepts 处理器

功能:
- 点击滚动列表的关注按钮,详情面板的关注状态自动同步
- 点击详情面板的关注按钮,滚动列表的关注状态自动同步
- 关注人数实时更新
- 状态在整个应用中保持一致

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-11-03 17:40:09 +08:00
parent 6a0a8e8e2b
commit f17a8fbd87
6 changed files with 308 additions and 88 deletions

View File

@@ -31,6 +31,9 @@ import PaginationControl from './PaginationControl';
* @param {boolean} loading - 加载状态
* @param {string} mode - 展示模式:'carousel'(单排轮播)| 'grid'(双排网格)
* @param {Function} onModeChange - 模式切换回调
* @param {boolean} hasMore - 是否还有更多数据
* @param {Object} eventFollowStatus - 事件关注状态 { [eventId]: { isFollowing, followerCount } }
* @param {Function} onToggleFollow - 关注按钮回调
*/
const EventScrollList = ({
events,
@@ -42,7 +45,10 @@ const EventScrollList = ({
onPageChange,
loading = false,
mode = 'carousel',
onModeChange
onModeChange,
hasMore = true,
eventFollowStatus = {},
onToggleFollow
}) => {
const scrollContainerRef = useRef(null);
@@ -121,7 +127,7 @@ const EventScrollList = ({
)}
{/* 右侧翻页按钮 - 下一页 */}
{currentPage < totalPages && (
{currentPage < totalPages && hasMore && (
<IconButton
icon={<ChevronRightIcon boxSize={6} color="blue.500" />}
position="absolute"
@@ -143,6 +149,7 @@ const EventScrollList = ({
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)',
transform: 'translateY(-50%) scale(1.05)'
}}
isDisabled={currentPage >= totalPages && !hasMore}
aria-label="下一页"
title="下一页"
/>
@@ -211,8 +218,8 @@ const EventScrollList = ({
<DynamicNewsEventCard
event={event}
index={index}
isFollowing={false}
followerCount={event.follower_count || 0}
isFollowing={eventFollowStatus[event.id]?.isFollowing || false}
followerCount={eventFollowStatus[event.id]?.followerCount || event.follower_count || 0}
isSelected={selectedEvent?.id === event.id}
onEventClick={(clickedEvent) => {
onEventSelect(clickedEvent);
@@ -222,7 +229,7 @@ const EventScrollList = ({
e.stopPropagation();
onEventSelect(event);
}}
onToggleFollow={() => {}}
onToggleFollow={() => onToggleFollow?.(event.id)}
timelineStyle={getTimelineBoxStyle()}
borderColor={borderColor}
/>
@@ -244,8 +251,8 @@ const EventScrollList = ({
<DynamicNewsEventCard
event={event}
index={index}
isFollowing={false}
followerCount={event.follower_count || 0}
isFollowing={eventFollowStatus[event.id]?.isFollowing || false}
followerCount={eventFollowStatus[event.id]?.followerCount || event.follower_count || 0}
isSelected={selectedEvent?.id === event.id}
onEventClick={(clickedEvent) => {
onEventSelect(clickedEvent);
@@ -255,7 +262,7 @@ const EventScrollList = ({
e.stopPropagation();
onEventSelect(event);
}}
onToggleFollow={() => {}}
onToggleFollow={() => onToggleFollow?.(event.id)}
timelineStyle={getTimelineBoxStyle()}
borderColor={borderColor}
/>