feat: 事件关注功能优化 - Redux 乐观更新 + Mock 数据状态同步
1. communityDataSlice 添加事件关注乐观更新 - pending: 立即切换 isFollowing 状态 - rejected: 回滚到之前状态 - fulfilled: 使用 API 返回的准确数据覆盖 2. Mock 数据添加内存状态管理 - 新增 followedEventsSet 和 followedEventsMap 存储 - toggleEventFollowStatus: 切换关注状态 - isEventFollowed: 检查是否已关注 - getFollowedEvents: 获取关注事件列表 3. Mock handlers 使用内存状态 - follow handler: 使用 toggleEventFollowStatus - following handler: 使用 getFollowedEvents 动态返回 - 事件详情: 返回正确的 is_following 状态 修复: 关注事件后导航栏"自选事件"列表不同步更新的问题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,13 @@ export const mockRealtimeQuotes = [
|
|||||||
|
|
||||||
// ==================== 关注事件数据 ====================
|
// ==================== 关注事件数据 ====================
|
||||||
|
|
||||||
|
// 事件关注内存存储(Set 存储已关注的事件 ID)
|
||||||
|
export const followedEventsSet = new Set();
|
||||||
|
|
||||||
|
// 关注事件完整数据存储(Map: eventId -> eventData)
|
||||||
|
export const followedEventsMap = new Map();
|
||||||
|
|
||||||
|
// 初始关注事件列表(用于初始化)
|
||||||
export const mockFollowingEvents = [
|
export const mockFollowingEvents = [
|
||||||
{
|
{
|
||||||
id: 101,
|
id: 101,
|
||||||
@@ -231,6 +238,74 @@ export const mockFollowingEvents = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 初始化:将 mockFollowingEvents 的数据加入内存存储
|
||||||
|
mockFollowingEvents.forEach(event => {
|
||||||
|
followedEventsSet.add(event.id);
|
||||||
|
followedEventsMap.set(event.id, event);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换事件关注状态
|
||||||
|
* @param {number} eventId - 事件 ID
|
||||||
|
* @param {Object} eventData - 事件数据(关注时需要)
|
||||||
|
* @returns {{ isFollowing: boolean, followerCount: number }}
|
||||||
|
*/
|
||||||
|
export function toggleEventFollowStatus(eventId, eventData = null) {
|
||||||
|
const wasFollowing = followedEventsSet.has(eventId);
|
||||||
|
|
||||||
|
if (wasFollowing) {
|
||||||
|
// 取消关注
|
||||||
|
followedEventsSet.delete(eventId);
|
||||||
|
followedEventsMap.delete(eventId);
|
||||||
|
} else {
|
||||||
|
// 添加关注
|
||||||
|
followedEventsSet.add(eventId);
|
||||||
|
if (eventData) {
|
||||||
|
followedEventsMap.set(eventId, {
|
||||||
|
...eventData,
|
||||||
|
followed_at: new Date().toISOString()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 如果没有提供事件数据,创建基础数据
|
||||||
|
followedEventsMap.set(eventId, {
|
||||||
|
id: eventId,
|
||||||
|
title: `事件 ${eventId}`,
|
||||||
|
tags: [],
|
||||||
|
followed_at: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFollowing = !wasFollowing;
|
||||||
|
const followerCount = isFollowing ? Math.floor(Math.random() * 500) + 100 : Math.floor(Math.random() * 500) + 50;
|
||||||
|
|
||||||
|
console.log('[Mock Data] 切换事件关注状态:', {
|
||||||
|
eventId,
|
||||||
|
wasFollowing,
|
||||||
|
isFollowing,
|
||||||
|
followedEventsCount: followedEventsSet.size
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isFollowing, followerCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查事件是否已关注
|
||||||
|
* @param {number} eventId - 事件 ID
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isEventFollowed(eventId) {
|
||||||
|
return followedEventsSet.has(eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有已关注的事件列表
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
export function getFollowedEvents() {
|
||||||
|
return Array.from(followedEventsMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 评论数据 ====================
|
// ==================== 评论数据 ====================
|
||||||
|
|
||||||
export const mockEventComments = [
|
export const mockEventComments = [
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {
|
|||||||
mockInvestmentPlans,
|
mockInvestmentPlans,
|
||||||
mockCalendarEvents,
|
mockCalendarEvents,
|
||||||
mockSubscriptionCurrent,
|
mockSubscriptionCurrent,
|
||||||
getCalendarEventsByDateRange
|
getCalendarEventsByDateRange,
|
||||||
|
getFollowedEvents
|
||||||
} from '../data/account';
|
} from '../data/account';
|
||||||
|
|
||||||
// 模拟网络延迟(毫秒)
|
// 模拟网络延迟(毫秒)
|
||||||
@@ -250,7 +251,7 @@ export const accountHandlers = [
|
|||||||
|
|
||||||
// ==================== 事件关注管理 ====================
|
// ==================== 事件关注管理 ====================
|
||||||
|
|
||||||
// 8. 获取关注的事件
|
// 8. 获取关注的事件(使用内存状态动态返回)
|
||||||
http.get('/api/account/events/following', async () => {
|
http.get('/api/account/events/following', async () => {
|
||||||
await delay(NETWORK_DELAY);
|
await delay(NETWORK_DELAY);
|
||||||
|
|
||||||
@@ -262,11 +263,14 @@ export const accountHandlers = [
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Mock] 获取关注的事件');
|
// 从内存存储获取已关注的事件列表
|
||||||
|
const followedEvents = getFollowedEvents();
|
||||||
|
|
||||||
|
console.log('[Mock] 获取关注的事件, 数量:', followedEvents.length);
|
||||||
|
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: mockFollowingEvents
|
data: followedEvents
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import { getEventRelatedStocks, generateMockEvents, generateHotEvents, generatePopularKeywords, generateDynamicNewsEvents } from '../data/events';
|
import { getEventRelatedStocks, generateMockEvents, generateHotEvents, generatePopularKeywords, generateDynamicNewsEvents } from '../data/events';
|
||||||
import { getMockFutureEvents, getMockEventCountsForMonth } from '../data/account';
|
import { getMockFutureEvents, getMockEventCountsForMonth, toggleEventFollowStatus, isEventFollowed } from '../data/account';
|
||||||
import { generatePopularConcepts } from './concept';
|
import { generatePopularConcepts } from './concept';
|
||||||
|
|
||||||
// 模拟网络延迟
|
// 模拟网络延迟
|
||||||
@@ -260,15 +260,19 @@ export const eventHandlers = [
|
|||||||
await delay(200);
|
await delay(200);
|
||||||
|
|
||||||
const { eventId } = params;
|
const { eventId } = params;
|
||||||
|
const numericEventId = parseInt(eventId, 10);
|
||||||
|
|
||||||
console.log('[Mock] 获取事件详情, eventId:', eventId);
|
console.log('[Mock] 获取事件详情, eventId:', numericEventId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 检查是否已关注
|
||||||
|
const isFollowing = isEventFollowed(numericEventId);
|
||||||
|
|
||||||
// 返回模拟的事件详情数据
|
// 返回模拟的事件详情数据
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
id: parseInt(eventId),
|
id: numericEventId,
|
||||||
title: `测试事件 ${eventId} - 重大政策发布`,
|
title: `测试事件 ${eventId} - 重大政策发布`,
|
||||||
description: '这是一个模拟的事件描述,用于开发测试。该事件涉及重要政策变化,可能对相关板块产生显著影响。建议关注后续发展动态。',
|
description: '这是一个模拟的事件描述,用于开发测试。该事件涉及重要政策变化,可能对相关板块产生显著影响。建议关注后续发展动态。',
|
||||||
importance: ['S', 'A', 'B', 'C'][Math.floor(Math.random() * 4)],
|
importance: ['S', 'A', 'B', 'C'][Math.floor(Math.random() * 4)],
|
||||||
@@ -278,7 +282,7 @@ export const eventHandlers = [
|
|||||||
related_avg_chg: parseFloat((Math.random() * 10 - 5).toFixed(2)),
|
related_avg_chg: parseFloat((Math.random() * 10 - 5).toFixed(2)),
|
||||||
follower_count: Math.floor(Math.random() * 500) + 50,
|
follower_count: Math.floor(Math.random() * 500) + 50,
|
||||||
view_count: Math.floor(Math.random() * 5000) + 100,
|
view_count: Math.floor(Math.random() * 5000) + 100,
|
||||||
is_following: false,
|
is_following: isFollowing, // 使用内存状态
|
||||||
post_count: Math.floor(Math.random() * 50),
|
post_count: Math.floor(Math.random() * 50),
|
||||||
expectation_surprise_score: parseFloat((Math.random() * 100).toFixed(1)),
|
expectation_surprise_score: parseFloat((Math.random() * 100).toFixed(1)),
|
||||||
},
|
},
|
||||||
@@ -395,19 +399,29 @@ export const eventHandlers = [
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// 切换事件关注状态
|
// 切换事件关注状态(使用内存状态管理)
|
||||||
http.post('/api/events/:eventId/follow', async ({ params }) => {
|
http.post('/api/events/:eventId/follow', async ({ params, request }) => {
|
||||||
await delay(200);
|
await delay(200);
|
||||||
|
|
||||||
const { eventId } = params;
|
const { eventId } = params;
|
||||||
|
const numericEventId = parseInt(eventId, 10);
|
||||||
|
|
||||||
console.log('[Mock] 切换事件关注状态, eventId:', eventId);
|
console.log('[Mock] 切换事件关注状态, eventId:', numericEventId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 模拟切换逻辑:随机生成关注状态
|
// 尝试从请求体获取事件数据(用于新关注时保存完整信息)
|
||||||
// 实际应用中,这里应该从某个状态存储中读取和更新
|
let eventData = null;
|
||||||
const isFollowing = Math.random() > 0.5;
|
try {
|
||||||
const followerCount = Math.floor(Math.random() * 1000) + 100;
|
const body = await request.json();
|
||||||
|
if (body && body.title) {
|
||||||
|
eventData = body;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 没有请求体或解析失败,忽略
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用内存状态管理切换关注
|
||||||
|
const { isFollowing, followerCount } = toggleEventFollowStatus(numericEventId, eventData);
|
||||||
|
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -608,14 +608,40 @@ const communityDataSlice = createSlice({
|
|||||||
state.error[stateKey] = action.payload;
|
state.error[stateKey] = action.payload;
|
||||||
logger.error('CommunityData', `${stateKey} 加载失败`, new Error(action.payload));
|
logger.error('CommunityData', `${stateKey} 加载失败`, new Error(action.payload));
|
||||||
})
|
})
|
||||||
// toggleEventFollow
|
// ===== toggleEventFollow(乐观更新)=====
|
||||||
|
// pending: 立即切换状态
|
||||||
|
.addCase(toggleEventFollow.pending, (state, action) => {
|
||||||
|
const eventId = action.meta.arg;
|
||||||
|
const current = state.eventFollowStatus[eventId];
|
||||||
|
// 乐观切换:如果当前已关注则变为未关注,反之亦然
|
||||||
|
state.eventFollowStatus[eventId] = {
|
||||||
|
isFollowing: !(current?.isFollowing),
|
||||||
|
followerCount: current?.followerCount ?? 0
|
||||||
|
};
|
||||||
|
logger.debug('CommunityData', 'toggleEventFollow pending (乐观更新)', {
|
||||||
|
eventId,
|
||||||
|
newIsFollowing: !(current?.isFollowing)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// rejected: 回滚状态
|
||||||
|
.addCase(toggleEventFollow.rejected, (state, action) => {
|
||||||
|
const eventId = action.meta.arg;
|
||||||
|
const current = state.eventFollowStatus[eventId];
|
||||||
|
// 回滚:恢复到之前的状态(再次切换回去)
|
||||||
|
state.eventFollowStatus[eventId] = {
|
||||||
|
isFollowing: !(current?.isFollowing),
|
||||||
|
followerCount: current?.followerCount ?? 0
|
||||||
|
};
|
||||||
|
logger.error('CommunityData', 'toggleEventFollow rejected (已回滚)', {
|
||||||
|
eventId,
|
||||||
|
error: action.payload
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// fulfilled: 使用 API 返回的准确数据覆盖
|
||||||
.addCase(toggleEventFollow.fulfilled, (state, action) => {
|
.addCase(toggleEventFollow.fulfilled, (state, action) => {
|
||||||
const { eventId, isFollowing, followerCount } = action.payload;
|
const { eventId, isFollowing, followerCount } = action.payload;
|
||||||
state.eventFollowStatus[eventId] = { isFollowing, followerCount };
|
state.eventFollowStatus[eventId] = { isFollowing, followerCount };
|
||||||
logger.debug('CommunityData', 'toggleEventFollow fulfilled', { eventId, isFollowing, followerCount });
|
logger.debug('CommunityData', 'toggleEventFollow fulfilled', { eventId, isFollowing, followerCount });
|
||||||
})
|
|
||||||
.addCase(toggleEventFollow.rejected, (_state, action) => {
|
|
||||||
logger.error('CommunityData', 'toggleEventFollow rejected', action.payload);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user