diff --git a/src/contexts/IndustryContext.js b/src/contexts/IndustryContext.js index 9d80b2f0..42780420 100644 --- a/src/contexts/IndustryContext.js +++ b/src/contexts/IndustryContext.js @@ -1,12 +1,17 @@ // src/contexts/IndustryContext.js -// 行业分类数据全局上下文 - 直接使用静态数据 +// 行业分类数据全局上下文 - 使用API获取 + 缓存机制 -import React, { createContext, useContext } from 'react'; +import React, { createContext, useContext, useState, useEffect, useRef } from 'react'; import { industryData as staticIndustryData } from '../data/industryData'; +import { industryService } from '../services/industryService'; import { logger } from '../utils/logger'; const IndustryContext = createContext(); +// 缓存配置 +const CACHE_KEY = 'industry_classifications_cache'; +const CACHE_DURATION = 24 * 60 * 60 * 1000; // 1天(24小时) + /** * useIndustry Hook * 在任何组件中使用行业数据 @@ -19,28 +24,148 @@ export const useIndustry = () => { return context; }; +/** + * 从 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('IndustryContext', '缓存已过期,清除缓存'); + return null; + } + + logger.debug('IndustryContext', '从缓存加载行业数据', { + count: data?.length || 0, + age: Math.round((now - timestamp) / 1000 / 60) + ' 分钟前' + }); + return data; + } catch (error) { + logger.error('IndustryContext', 'loadFromCache', error); + return null; + } +}; + +/** + * 保存到 localStorage + */ +const saveToCache = (data) => { + try { + localStorage.setItem(CACHE_KEY, JSON.stringify({ + data, + timestamp: Date.now() + })); + logger.debug('IndustryContext', '行业数据已缓存', { + count: data?.length || 0 + }); + } catch (error) { + logger.error('IndustryContext', 'saveToCache', error); + } +}; + /** * IndustryProvider 组件 - * 提供全局行业数据管理 - 直接使用静态数据,无需加载 + * 提供全局行业数据管理 - 使用API获取 + 缓存机制 */ export const IndustryProvider = ({ children }) => { - // 直接使用静态数据,无需状态管理 - const industryData = staticIndustryData; + const [industryData, setIndustryData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const hasLoadedRef = useRef(false); + const isLoadingRef = useRef(false); - logger.debug('IndustryContext', '使用静态行业数据', { - count: industryData?.length || 0 - }); + /** + * 加载行业数据 + */ + const loadIndustryData = async () => { + // 防止重复加载(处理 StrictMode 双重调用) + if (hasLoadedRef.current || isLoadingRef.current) { + logger.debug('IndustryContext', '跳过重复加载', { + hasLoaded: hasLoadedRef.current, + isLoading: isLoadingRef.current + }); + return industryData; + } + + try { + isLoadingRef.current = true; + setLoading(true); + setError(null); + + logger.debug('IndustryContext', '开始加载行业数据'); + + // 1. 先尝试从缓存加载 + const cachedData = loadFromCache(); + if (cachedData && cachedData.length > 0) { + setIndustryData(cachedData); + hasLoadedRef.current = true; + return cachedData; + } + + // 2. 缓存不存在或过期,调用 API + logger.debug('IndustryContext', '缓存无效,调用API获取数据'); + const response = await industryService.getClassifications(); + + if (response.success && response.data && response.data.length > 0) { + setIndustryData(response.data); + saveToCache(response.data); + hasLoadedRef.current = true; + + logger.debug('IndustryContext', 'API数据加载成功', { + count: response.data.length + }); + + return response.data; + } else { + throw new Error('API返回数据为空'); + } + } catch (err) { + // 3. API 失败,回退到静态数据 + logger.warn('IndustryContext', 'API加载失败,使用静态数据', { + error: err.message + }); + + setError(err.message); + setIndustryData(staticIndustryData); + hasLoadedRef.current = true; + + return staticIndustryData; + } finally { + setLoading(false); + isLoadingRef.current = false; + } + }; + + /** + * 刷新行业数据(清除缓存并重新加载) + */ + const refreshIndustryData = async () => { + logger.debug('IndustryContext', '刷新行业数据,清除缓存'); + localStorage.removeItem(CACHE_KEY); + hasLoadedRef.current = false; + isLoadingRef.current = false; + return loadIndustryData(); + }; + + // 组件挂载时自动加载数据 + useEffect(() => { + loadIndustryData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const value = { - industryData, // 行业数据(静态) - loading: false, // 静态数据无需加载 - error: null, // 静态数据无错误 - loadIndustryData: () => { // 兼容旧接口,返回数据 - return Promise.resolve(industryData); - }, - refreshIndustryData: () => { // 兼容旧接口,返回数据 - return Promise.resolve(industryData); - } + industryData, + loading, + error, + loadIndustryData, + refreshIndustryData }; return ( diff --git a/src/views/Community/components/EventFilters.js b/src/views/Community/components/EventFilters.js index 7efc6788..0f11422a 100644 --- a/src/views/Community/components/EventFilters.js +++ b/src/views/Community/components/EventFilters.js @@ -1,6 +1,6 @@ // src/views/Community/components/EventFilters.js import React, { useState, useEffect } from 'react'; -import { Card, Row, Col, DatePicker, Button, Select, Form, Input, Cascader } from 'antd'; +import { Card, Row, Col, DatePicker, Button, Select, Form, Cascader } from 'antd'; import { FilterOutlined } from '@ant-design/icons'; import moment from 'moment'; import locale from 'antd/es/date-picker/locale/zh_CN'; @@ -12,7 +12,9 @@ const { Option } = Select; const EventFilters = ({ filters, onFilterChange, loading }) => { const [form] = Form.useForm(); - const [industryOptions, setIndustryOptions] = useState([]); + + // 使用全局行业数据 + const { industryData, loading: industryLoading, loadIndustryData } = useIndustry(); // 初始化表单值 useEffect(() => { @@ -25,25 +27,14 @@ const EventFilters = ({ filters, onFilterChange, loading }) => { form.setFieldsValue(initialValues); }, [filters, form]); - // 加载申银万国行业分类树形数据 - const loadIndustryClassifications = async () => { - try { - const response = await industryService.getClassifications(); - if (response.success && response.data) { - setIndustryOptions(response.data); - logger.debug('EventFilters', '申银万国行业分类加载成功', { - count: response.data?.length || 0 - }); - } - } catch (error) { - logger.error('EventFilters', 'loadIndustryClassifications', error); + // Cascader 获得焦点时确保数据已加载 + const handleCascaderFocus = async () => { + if (!industryData || industryData.length === 0) { + logger.debug('EventFilters', 'Cascader 获得焦点,触发数据加载'); + await loadIndustryData(); } }; - useEffect(() => { - loadIndustryClassifications(); - }, []); - const handleDateRangeChange = (dates) => { if (dates && dates.length === 2) { const dateRange = `${dates[0].format('YYYY-MM-DD')} 至 ${dates[1].format('YYYY-MM-DD')}`; @@ -92,14 +83,14 @@ const EventFilters = ({ filters, onFilterChange, loading }) => { }; // 行业级联选择变化 - const handleIndustryChange = (value, selectedOptions) => { + const handleIndustryChange = (value) => { if (!value || value.length === 0) { onFilterChange('industry_code', ''); return; } // 获取选中的节点 - const selectedNode = findNodeByPath(industryOptions, value); + const selectedNode = findNodeByPath(industryData || [], value); if (!selectedNode) { // 如果找不到节点,使用最后一个值 @@ -163,17 +154,20 @@ const EventFilters = ({ filters, onFilterChange, loading }) => { {/* 行业分类级联选择器 - 替换原来的 5 个独立 Select */} - + path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1) }} - disabled={loading} + disabled={loading || industryLoading} + loading={industryLoading} allowClear style={{ width: '100%' }} displayRender={(labels) => labels.join(' / ')}