/**
|
* @author 韩天尊
|
* @time 2024-01-15
|
* @version 1.0.0
|
* @description 应用全局状态管理上下文
|
*/
|
import React, { createContext, useContext, useReducer, ReactNode, useEffect, useCallback, useState, useRef } from 'react';
|
import { authAPI, activityAPI, pointsAPI, messageAPI, checkinAPI, adminAPI } from '../services/api';
|
import { User, Activity, CheckinRecord, DeclarationRecord, Message, ApiResponse } from '../types';
|
|
// 应用状态接口
|
interface AppState {
|
currentPage: string;
|
currentActivityId: number | null;
|
currentMessageId: number | null;
|
currentRedemptionId: number | null;
|
currentVolunteerReviewId: number | null;
|
currentActivityTab: string;
|
user: User | null;
|
activities: Activity[];
|
checkinRecords: CheckinRecord[];
|
declarationRecords: DeclarationRecord[];
|
messages: Message[];
|
adminInfo: any | null; // 管理员信息
|
loading: boolean;
|
error: string | null;
|
}
|
|
// 初始状态
|
const initialState: AppState = {
|
currentPage: 'home',
|
currentActivityId: null,
|
currentMessageId: null,
|
currentRedemptionId: null,
|
currentVolunteerReviewId: null,
|
currentActivityTab: 'all',
|
user: null,
|
activities: [],
|
checkinRecords: [],
|
declarationRecords: [],
|
messages: [],
|
adminInfo: null,
|
loading: false,
|
error: null,
|
};
|
|
// Action类型
|
type AppAction =
|
| { type: 'SET_CURRENT_PAGE'; payload: string }
|
| { type: 'SET_CURRENT_ACTIVITY_ID'; payload: number | null }
|
| { type: 'SET_CURRENT_ACTIVITY_TAB'; payload: string }
|
| { type: 'SET_LOADING'; payload: boolean }
|
| { type: 'SET_ERROR'; payload: string | null }
|
| { type: 'SET_USER'; payload: User | null }
|
| { type: 'UPDATE_USER_POINTS'; payload: number }
|
| { type: 'SET_ACTIVITIES'; payload: Activity[] }
|
| { type: 'ADD_ACTIVITY'; payload: Activity }
|
| { type: 'UPDATE_ACTIVITY'; payload: Activity }
|
| { type: 'SET_CHECKIN_RECORDS'; payload: CheckinRecord[] }
|
| { type: 'ADD_CHECKIN_RECORD'; payload: CheckinRecord }
|
| { type: 'SET_DECLARATION_RECORDS'; payload: DeclarationRecord[] }
|
| { type: 'ADD_DECLARATION_RECORD'; payload: DeclarationRecord }
|
| { type: 'UPDATE_DECLARATION_STATUS'; payload: { id: number; status: string } }
|
| { type: 'SET_MESSAGES'; payload: Message[] }
|
| { type: 'MARK_MESSAGE_READ'; payload: number }
|
| { type: 'SET_ADMIN_INFO'; payload: any | null };
|
|
// Reducer函数
|
const appReducer = (state: AppState, action: AppAction): AppState => {
|
switch (action.type) {
|
case 'SET_CURRENT_PAGE':
|
return { ...state, currentPage: action.payload };
|
case 'SET_CURRENT_ACTIVITY_ID':
|
return { ...state, currentActivityId: action.payload };
|
case 'SET_CURRENT_ACTIVITY_TAB':
|
return { ...state, currentActivityTab: action.payload };
|
case 'SET_LOADING':
|
return { ...state, loading: action.payload };
|
case 'SET_ERROR':
|
return { ...state, error: action.payload };
|
case 'SET_USER':
|
return { ...state, user: action.payload };
|
case 'UPDATE_USER_POINTS':
|
return {
|
...state,
|
user: state.user ? { ...state.user, points: state.user.points + action.payload } : null
|
};
|
case 'SET_ACTIVITIES':
|
return { ...state, activities: action.payload };
|
case 'ADD_ACTIVITY':
|
return { ...state, activities: [...state.activities, action.payload] };
|
case 'UPDATE_ACTIVITY':
|
return {
|
...state,
|
activities: state.activities.map(activity =>
|
activity.id === action.payload.id ? action.payload : activity
|
)
|
};
|
case 'SET_CHECKIN_RECORDS':
|
return { ...state, checkinRecords: action.payload };
|
case 'ADD_CHECKIN_RECORD':
|
return { ...state, checkinRecords: [...state.checkinRecords, action.payload] };
|
case 'SET_DECLARATION_RECORDS':
|
return { ...state, declarationRecords: action.payload };
|
case 'ADD_DECLARATION_RECORD':
|
return { ...state, declarationRecords: [...state.declarationRecords, action.payload] };
|
case 'UPDATE_DECLARATION_STATUS':
|
return {
|
...state,
|
declarationRecords: state.declarationRecords.map(record =>
|
record.id === action.payload.id
|
? { ...record, status: action.payload.status as any }
|
: record
|
)
|
};
|
case 'SET_MESSAGES':
|
return { ...state, messages: action.payload };
|
case 'MARK_MESSAGE_READ':
|
return {
|
...state,
|
messages: state.messages.map(message =>
|
message.id === action.payload
|
? { ...message, isRead: true, readTime: new Date().toISOString() }
|
: message
|
)
|
};
|
case 'SET_ADMIN_INFO':
|
return { ...state, adminInfo: action.payload };
|
default:
|
return state;
|
}
|
};
|
|
// 上下文接口
|
interface AppContextType {
|
state: AppState;
|
dispatch: React.Dispatch<AppAction>;
|
// API调用方法
|
login: (phone: string, password: string) => Promise<void>;
|
register: (name: string, phone: string, password: string, idCard: string) => Promise<void>;
|
logout: () => Promise<void>;
|
loadActivities: (params?: any) => Promise<void>;
|
loadUserInfo: () => Promise<void>;
|
loadCheckinRecords: (params?: any) => Promise<void>;
|
loadDeclarationRecords: (params?: any) => Promise<void>;
|
loadMessages: (params?: any) => Promise<void>;
|
markMessageRead: (id: number) => Promise<void>;
|
loadAdminInfo: (phone: string) => Promise<void>;
|
}
|
|
// 创建上下文
|
const AppContext = createContext<AppContextType | undefined>(undefined);
|
|
// 上下文提供者组件
|
interface AppProviderProps {
|
children: ReactNode;
|
}
|
|
export const AppProvider: React.FC<AppProviderProps> = ({ children }) => {
|
const [state, dispatch] = useReducer(appReducer, initialState);
|
|
// 请求状态管理
|
const [isLoadingActivities, setIsLoadingActivities] = useState(false);
|
const [isLoadingCheckinRecords, setIsLoadingCheckinRecords] = useState(false);
|
const [isLoadingDeclarationRecords, setIsLoadingDeclarationRecords] = useState(false);
|
const [isLoadingMessages, setIsLoadingMessages] = useState(false);
|
|
// 使用useRef管理请求状态,避免useCallback依赖
|
const isLoadingDeclarationRecordsRef = useRef(false);
|
const isLoadingMessagesRef = useRef(false);
|
|
// 初始化时检查用户登录状态
|
useEffect(() => {
|
const token = localStorage.getItem('token');
|
const user = localStorage.getItem('user');
|
if (token && user) {
|
try {
|
const userData = JSON.parse(user);
|
dispatch({ type: 'SET_USER', payload: userData });
|
} catch (error) {
|
console.error('解析用户信息失败:', error);
|
localStorage.removeItem('token');
|
localStorage.removeItem('user');
|
}
|
}
|
}, []);
|
|
// 登录
|
const login = async (phone: string, password: string) => {
|
try {
|
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_ERROR', payload: null });
|
console.log('phone:', phone);
|
const response = await authAPI.login(phone, password);
|
if (response.code === 0) {
|
localStorage.setItem('token', response.data.token);
|
// 确保用户数据包含isAdmin字段
|
const userData = {
|
...response.data.user,
|
isAdmin: response.data.isAdmin || false
|
};
|
localStorage.setItem('user', JSON.stringify(userData));
|
dispatch({ type: 'SET_USER', payload: userData });
|
} else {
|
throw new Error(response.msg || '登录失败');
|
}
|
} catch (error: any) {
|
dispatch({ type: 'SET_ERROR', payload: error.message || '登录失败' });
|
throw error;
|
} finally {
|
dispatch({ type: 'SET_LOADING', payload: false });
|
}
|
};
|
|
// 注册
|
const register = async (name: string, phone: string, password: string, idCard: string) => {
|
try {
|
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_ERROR', payload: null });
|
|
const response = await authAPI.register(name, phone, password, idCard);
|
if (response.code === 0) {
|
// 注册成功后自动登录
|
await login(phone, password);
|
} else {
|
throw new Error(response.msg || '注册失败');
|
}
|
} catch (error: any) {
|
dispatch({ type: 'SET_ERROR', payload: error.message || '注册失败' });
|
throw error;
|
} finally {
|
dispatch({ type: 'SET_LOADING', payload: false });
|
}
|
};
|
|
// 退出登录
|
const logout = async () => {
|
try {
|
await authAPI.logout();
|
} catch (error) {
|
console.error('退出登录失败:', error);
|
} finally {
|
// 清空所有相关缓存
|
localStorage.removeItem('fwmPhone');
|
localStorage.removeItem('token');
|
localStorage.removeItem('user');
|
localStorage.removeItem('adminInfo');
|
dispatch({ type: 'SET_USER', payload: null });
|
dispatch({ type: 'SET_ADMIN_INFO', payload: null });
|
}
|
};
|
|
// 加载用户信息
|
const loadUserInfo = async () => {
|
try {
|
const token = localStorage.getItem('token');
|
const user = localStorage.getItem('user');
|
console.log('token:', token);
|
if (token && user) {
|
// 从localStorage加载用户信息到state
|
const userData = JSON.parse(user);
|
dispatch({ type: 'SET_USER', payload: userData });
|
}
|
|
// 这里可以调用获取用户信息的API来验证token有效性
|
// const response = await userAPI.getProfile();
|
// dispatch({ type: 'SET_USER', payload: response.data });
|
} catch (error) {
|
console.error('加载用户信息失败:', error);
|
// 如果解析失败,清除localStorage
|
localStorage.removeItem('token');
|
localStorage.removeItem('user');
|
}
|
};
|
|
// 加载活动列表 - 使用useCallback优化,添加请求状态管理
|
const loadActivities = useCallback(async (params: any = {}) => {
|
// 防止重复请求
|
if (isLoadingActivities) {
|
console.log('活动列表正在加载中,跳过重复请求');
|
return;
|
}
|
|
try {
|
setIsLoadingActivities(true);
|
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_ERROR', payload: null });
|
|
const response = await activityAPI.getList(params);
|
if (response.code === 0) {
|
dispatch({ type: 'SET_ACTIVITIES', payload: response.data.list || [] });
|
} else {
|
dispatch({ type: 'SET_ERROR', payload: response.msg || '加载活动列表失败' });
|
}
|
} catch (error: any) {
|
console.error('加载活动列表失败:', error);
|
dispatch({ type: 'SET_ERROR', payload: error.message || '加载活动列表失败' });
|
} finally {
|
setIsLoadingActivities(false);
|
dispatch({ type: 'SET_LOADING', payload: false });
|
}
|
}, [isLoadingActivities]);
|
|
// 加载签到记录 - 使用useCallback优化,添加请求状态管理
|
const loadCheckinRecords = useCallback(async (params: any = {}) => {
|
if (isLoadingCheckinRecords) {
|
console.log('签到记录正在加载中,跳过重复请求');
|
return;
|
}
|
|
try {
|
setIsLoadingCheckinRecords(true);
|
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_ERROR', payload: null });
|
|
const response = await checkinAPI.getRecords(params);
|
if (response.code === 0) {
|
dispatch({ type: 'SET_CHECKIN_RECORDS', payload: response.data.list || [] });
|
} else {
|
dispatch({ type: 'SET_ERROR', payload: response.msg || '加载签到记录失败' });
|
}
|
} catch (error: any) {
|
console.error('加载签到记录失败:', error);
|
dispatch({ type: 'SET_ERROR', payload: error.message || '加载签到记录失败' });
|
} finally {
|
setIsLoadingCheckinRecords(false);
|
dispatch({ type: 'SET_LOADING', payload: false });
|
}
|
}, [isLoadingCheckinRecords]);
|
|
// 加载申报记录 - 使用useRef避免循环依赖
|
const loadDeclarationRecords = useCallback(async (params: any = {}) => {
|
if (isLoadingDeclarationRecordsRef.current) {
|
console.log('申报记录正在加载中,跳过重复请求');
|
return;
|
}
|
|
try {
|
isLoadingDeclarationRecordsRef.current = true;
|
setIsLoadingDeclarationRecords(true);
|
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_ERROR', payload: null });
|
|
const response = await pointsAPI.getDeclarations(params);
|
if (response.code === 0) {
|
dispatch({ type: 'SET_DECLARATION_RECORDS', payload: response.data.list || [] });
|
} else {
|
dispatch({ type: 'SET_ERROR', payload: response.msg || '加载申报记录失败' });
|
}
|
} catch (error: any) {
|
console.error('加载申报记录失败:', error);
|
dispatch({ type: 'SET_ERROR', payload: error.message || '加载申报记录失败' });
|
} finally {
|
isLoadingDeclarationRecordsRef.current = false;
|
setIsLoadingDeclarationRecords(false);
|
dispatch({ type: 'SET_LOADING', payload: false });
|
}
|
}, []); // 空依赖数组,避免循环依赖
|
|
// 加载消息列表 - 使用useRef避免循环依赖
|
const loadMessages = useCallback(async (params: any = {}) => {
|
if (isLoadingMessagesRef.current) {
|
console.log('消息列表正在加载中,跳过重复请求');
|
return;
|
}
|
|
try {
|
isLoadingMessagesRef.current = true;
|
setIsLoadingMessages(true);
|
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_ERROR', payload: null });
|
|
const response = await messageAPI.getList(params);
|
if (response.code === 0) {
|
dispatch({ type: 'SET_MESSAGES', payload: response.data.list || [] });
|
} else {
|
dispatch({ type: 'SET_ERROR', payload: response.msg || '加载消息列表失败' });
|
}
|
} catch (error: any) {
|
console.error('加载消息列表失败:', error);
|
dispatch({ type: 'SET_ERROR', payload: error.message || '加载消息列表失败' });
|
} finally {
|
isLoadingMessagesRef.current = false;
|
setIsLoadingMessages(false);
|
dispatch({ type: 'SET_LOADING', payload: false });
|
}
|
}, []); // 空依赖数组,避免循环依赖
|
|
// 标记消息为已读
|
const markMessageRead = async (id: number) => {
|
try {
|
await messageAPI.markAsRead(id);
|
dispatch({ type: 'MARK_MESSAGE_READ', payload: id });
|
} catch (error) {
|
console.error('标记消息已读失败:', error);
|
}
|
};
|
|
// 获取管理员信息
|
const loadAdminInfo = async (phone: string) => {
|
try {
|
dispatch({ type: 'SET_LOADING', payload: true });
|
dispatch({ type: 'SET_ERROR', payload: null });
|
|
const response = await adminAPI.getAdminInfo(phone);
|
if (response.code === 0) {
|
// 缓存管理员信息到localStorage
|
localStorage.setItem('adminInfo', JSON.stringify(response.data));
|
dispatch({ type: 'SET_ADMIN_INFO', payload: response.data });
|
} else {
|
throw new Error(response.msg || '获取管理员信息失败');
|
}
|
} catch (error: any) {
|
console.error('获取管理员信息失败:', error);
|
dispatch({ type: 'SET_ERROR', payload: error.message || '获取管理员信息失败' });
|
throw error;
|
} finally {
|
dispatch({ type: 'SET_LOADING', payload: false });
|
}
|
};
|
|
const contextValue: AppContextType = {
|
state,
|
dispatch,
|
login,
|
register,
|
logout,
|
loadActivities,
|
loadUserInfo,
|
loadCheckinRecords,
|
loadDeclarationRecords,
|
loadMessages,
|
markMessageRead,
|
loadAdminInfo,
|
};
|
|
return (
|
<AppContext.Provider value={contextValue}>
|
{children}
|
</AppContext.Provider>
|
);
|
};
|
|
// 自定义Hook
|
export const useAppContext = (): AppContextType => {
|
const context = useContext(AppContext);
|
if (context === undefined) {
|
throw new Error('useAppContext must be used within an AppProvider');
|
}
|
return context;
|
};
|