// src/store/slices/industrySlice.js // 行业分类数据 Redux Slice - 从 IndustryContext 迁移 import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { industryData as staticIndustryData } from '../../data/industryData'; import { industryService } from '../../services/industryService'; import { logger } from '../../utils/logger'; // 缓存配置 const CACHE_KEY = 'industry_classifications_cache'; const CACHE_DURATION = 24 * 60 * 60 * 1000; // 1天(24小时) /** * 从 localStorage 读取缓存 */ const loadFromCache = () => { try { const cached = localStorage.getItem(CACHE_KEY); if (!cached) return null; const { data, timestamp } = JSON.parse(cached); const now = Date.now(); // 检查缓存是否过期(1天) if (now - timestamp > CACHE_DURATION) { localStorage.removeItem(CACHE_KEY); logger.debug('industrySlice', '缓存已过期,清除缓存'); return null; } logger.debug('industrySlice', '从缓存加载行业数据', { count: data?.length || 0, age: Math.round((now - timestamp) / 1000 / 60) + ' 分钟前' }); return data; } catch (error) { logger.error('industrySlice', 'loadFromCache', error); return null; } }; /** * 保存到 localStorage */ const saveToCache = (data) => { try { localStorage.setItem(CACHE_KEY, JSON.stringify({ data, timestamp: Date.now() })); logger.debug('industrySlice', '行业数据已缓存', { count: data?.length || 0 }); } catch (error) { logger.error('industrySlice', 'saveToCache', error); } }; /** * 异步 Thunk: 加载行业数据 * 策略:缓存 -> API -> 静态数据 */ export const fetchIndustryData = createAsyncThunk( 'industry/fetchData', async (_, { rejectWithValue }) => { try { logger.debug('industrySlice', '开始加载行业数据'); // 1. 先尝试从缓存加载 const cachedData = loadFromCache(); if (cachedData && cachedData.length > 0) { logger.debug('industrySlice', '使用缓存数据', { count: cachedData.length }); return { data: cachedData, source: 'cache' }; } // 2. 缓存不存在或过期,调用 API logger.debug('industrySlice', '缓存无效,调用API获取数据'); const response = await industryService.getClassifications(); if (response.success && response.data && response.data.length > 0) { saveToCache(response.data); logger.debug('industrySlice', 'API数据加载成功', { count: response.data.length }); return { data: response.data, source: 'api' }; } else { throw new Error('API返回数据为空'); } } catch (error) { // 3. API 失败,回退到静态数据 logger.warn('industrySlice', 'API加载失败,使用静态数据', { error: error.message }); return { data: staticIndustryData, source: 'static', error: error.message }; } } ); /** * 异步 Thunk: 刷新行业数据(清除缓存并重新加载) */ export const refreshIndustryData = createAsyncThunk( 'industry/refresh', async (_, { dispatch }) => { logger.debug('industrySlice', '刷新行业数据,清除缓存'); localStorage.removeItem(CACHE_KEY); return dispatch(fetchIndustryData()); } ); // Industry Slice const industrySlice = createSlice({ name: 'industry', initialState: { data: null, // 行业数据数组 loading: false, // 加载状态 error: null, // 错误信息 source: null, // 数据来源: 'cache' | 'api' | 'static' lastFetchTime: null, // 最后加载时间 }, reducers: { // 清除缓存 clearCache: (state) => { localStorage.removeItem(CACHE_KEY); logger.debug('industrySlice', '手动清除缓存'); }, // 重置状态 resetState: (state) => { state.data = null; state.loading = false; state.error = null; state.source = null; state.lastFetchTime = null; }, }, extraReducers: (builder) => { builder // fetchIndustryData .addCase(fetchIndustryData.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchIndustryData.fulfilled, (state, action) => { state.loading = false; state.data = action.payload.data; state.source = action.payload.source; state.lastFetchTime = Date.now(); if (action.payload.error) { state.error = action.payload.error; } }) .addCase(fetchIndustryData.rejected, (state, action) => { state.loading = false; state.error = action.error.message; // 确保总有数据可用 if (!state.data) { state.data = staticIndustryData; state.source = 'static'; } }) // refreshIndustryData .addCase(refreshIndustryData.pending, (state) => { state.loading = true; }); }, }); // 导出 actions export const { clearCache, resetState } = industrySlice.actions; // 导出 selectors export const selectIndustryData = (state) => state.industry.data; export const selectIndustryLoading = (state) => state.industry.loading; export const selectIndustryError = (state) => state.industry.error; export const selectIndustrySource = (state) => state.industry.source; // 导出 reducer export default industrySlice.reducer;