diff --git a/src/views/Community/components/EventTimelineCard.js b/src/views/Community/components/EventTimelineCard.js index 9e7ebd3b..82664ad7 100644 --- a/src/views/Community/components/EventTimelineCard.js +++ b/src/views/Community/components/EventTimelineCard.js @@ -57,7 +57,6 @@ const EventTimelineCard = forwardRef(({ onSearch={onSearch} popularKeywords={popularKeywords} filters={filters} - loading={loading} /> diff --git a/src/views/Community/components/UnifiedSearchBox.js b/src/views/Community/components/UnifiedSearchBox.js index 74a6f46f..f3fc8dd6 100644 --- a/src/views/Community/components/UnifiedSearchBox.js +++ b/src/views/Community/components/UnifiedSearchBox.js @@ -8,6 +8,7 @@ import { SearchOutlined, CloseCircleOutlined, StockOutlined } from '@ant-design/icons'; import moment from 'moment'; +import dayjs from 'dayjs'; import locale from 'antd/es/date-picker/locale/zh_CN'; import debounce from 'lodash/debounce'; import { useIndustry } from '../../../contexts/IndustryContext'; @@ -21,17 +22,15 @@ const { Option } = AntSelect; const UnifiedSearchBox = ({ onSearch, popularKeywords = [], - filters = {}, - loading = false + filters = {} }) => { - const [searchValue, setSearchValue] = useState(''); // 统一搜索值 - const [isStockSearch, setIsStockSearch] = useState(false); // 是否股票搜索 + + // 其他状态 const [stockOptions, setStockOptions] = useState([]); // 股票下拉选项列表 const [allStocks, setAllStocks] = useState([]); // 所有股票数据 const [industryValue, setIndustryValue] = useState([]); - const [selectedStockInfo, setSelectedStockInfo] = useState(null); // 保存选中的股票完整信息 - // 新增:管理所有筛选条件 + // 筛选条件状态 const [sort, setSort] = useState('new'); // 排序方式 const [importance, setImportance] = useState('all'); // 重要性 const [dateRange, setDateRange] = useState(null); // 日期范围 @@ -39,14 +38,31 @@ const UnifiedSearchBox = ({ // 使用全局行业数据 const { industryData, loadIndustryData, loading: industryLoading } = useIndustry(); + // ✅ 从 filters 推导搜索框显示值(受控状态) + const searchValue = useMemo(() => { + if (filters.stock_code && allStocks.length > 0) { + // 股票搜索模式:显示 "代码 名称" + const stock = allStocks.find(s => s.code === filters.stock_code); + return stock ? `${stock.code} ${stock.name}` : filters.stock_code; + } + // 话题搜索模式:显示关键词 + return filters.q || ''; + }, [filters.stock_code, filters.q, allStocks]); + + // 搜索触发函数 + const triggerSearch = useCallback((params) => { + logger.debug('UnifiedSearchBox', '触发搜索', params); + onSearch(params); + }, [onSearch]); + // ✅ 创建防抖的搜索函数(300ms 延迟) const debouncedSearchRef = useRef(null); useEffect(() => { - // 创建防抖函数 + // 创建防抖函数,使用 triggerSearch 而不是直接调用 onSearch debouncedSearchRef.current = debounce((params) => { logger.debug('UnifiedSearchBox', '防抖搜索触发', params); - onSearch(params); + triggerSearch(params); }, 300); // 清理函数 @@ -55,7 +71,7 @@ const UnifiedSearchBox = ({ debouncedSearchRef.current.cancel(); } }; - }, [onSearch]); + }, [triggerSearch]); // 加载所有股票数据 useEffect(() => { @@ -100,8 +116,8 @@ const UnifiedSearchBox = ({ return null; }, []); - // ✅ 修复:从 props.filters 初始化所有筛选条件 - React.useEffect(() => { + // ✅ 从 props.filters 初始化筛选条件(不包括搜索值,搜索值由 searchValue useMemo 推导) + useEffect(() => { if (!filters) return; // 初始化排序和重要性 @@ -112,41 +128,13 @@ const UnifiedSearchBox = ({ if (filters.date_range) { const parts = filters.date_range.split(' 至 '); if (parts.length === 2) { - try { - setDateRange([dayjs(parts[0]), dayjs(parts[1])]); - logger.debug('UnifiedSearchBox', '初始化日期范围', { - date_range: filters.date_range - }); - } catch (error) { - logger.error('UnifiedSearchBox', '日期范围解析失败', error); - } + setDateRange([dayjs(parts[0]), dayjs(parts[1])]); + logger.debug('UnifiedSearchBox', '初始化日期范围', { + date_range: filters.date_range + }); } } - // 初始化搜索值(只在 allStocks 加载完成后处理股票搜索) - if (filters.stock_code) { - // 股票搜索模式 - if (allStocks && allStocks.length > 0) { - const stock = allStocks.find(s => s.code === filters.stock_code); - if (stock) { - const displayValue = `${stock.code} ${stock.name}`; - setSearchValue(displayValue); - setSelectedStockInfo(stock); - } else { - setSearchValue(filters.stock_code); - } - setIsStockSearch(true); - } else { - // allStocks 还未加载,先设置 stock_code - setSearchValue(filters.stock_code); - setIsStockSearch(true); - } - } else if (filters.q) { - // 话题搜索模式 - setSearchValue(filters.q); - setIsStockSearch(false); - } - // ✅ 初始化行业分类(需要 industryData 加载完成) if (filters.industry_code && industryData && industryData.length > 0) { const path = findIndustryPath(filters.industry_code, industryData); @@ -158,7 +146,7 @@ const UnifiedSearchBox = ({ }); } } - }, [filters, allStocks, industryData, findIndustryPath]); // ✅ 添加完整依赖 + }, [filters.sort, filters.importance, filters.date_range, filters.industry_code, industryData, findIndustryPath]); // AutoComplete 搜索股票(模糊匹配 code 或 name) const handleSearch = (value) => { @@ -191,32 +179,24 @@ const UnifiedSearchBox = ({ }); }; - // 选中股票(从下拉选择) - const handleStockSelect = (value, option) => { + // ✅ 选中股票(从下拉选择) - 直接构建参数并触发搜索 + const handleStockSelect = (_value, option) => { const stockInfo = option.stockInfo; if (stockInfo) { - // 显示格式:股票代码 + 股票名称 - const displayValue = `${stockInfo.code} ${stockInfo.name}`; - setSearchValue(displayValue); - setIsStockSearch(true); - setSelectedStockInfo(stockInfo); // 保存完整股票信息 logger.debug('UnifiedSearchBox', '选中股票', { code: stockInfo.code, name: stockInfo.name }); - // 自动触发搜索(股票搜索模式) - // 注意:此时 isStockSearch 状态还没更新,需要手动构建参数 - setTimeout(() => { - const params = buildFilterParams(); - // 手动覆盖股票相关参数(因为状态还没更新) - params.stock_code = stockInfo.code; - params.q = ''; - params.industry_code = ''; - params.industry_classification = ''; - logger.debug('UnifiedSearchBox', '自动触发股票搜索', params); - onSearch(params); - }, 100); + // 直接构建参数并触发搜索(股票搜索模式) + const params = buildFilterParams({ + stock_code: stockInfo.code, + q: '', + industry_code: '', + industry_classification: '' + }); + logger.debug('UnifiedSearchBox', '自动触发股票搜索', params); + triggerSearch(params); } }; @@ -267,84 +247,59 @@ const UnifiedSearchBox = ({ } }; - // ✅ 热门概念点击处理(立即搜索,不使用防抖) + // ✅ 热门概念点击处理(立即搜索,不使用防抖) - 直接构建参数并触发搜索 const handleKeywordClick = (keyword) => { - // 设置搜索框的值为热门概念文本 - setSearchValue(keyword); - setIsStockSearch(false); // 作为话题搜索 - setSelectedStockInfo(null); - // 立即触发搜索(取消之前的防抖) if (debouncedSearchRef.current) { debouncedSearchRef.current.cancel(); } - const params = buildFilterParams({ q: keyword }); + const params = buildFilterParams({ + q: keyword, + stock_code: '', + industry_code: '', + industry_classification: '' + }); logger.debug('UnifiedSearchBox', '热门概念点击,立即触发搜索', { keyword, params }); - onSearch(params); + triggerSearch(params); }; // 主搜索(点击搜索按钮或回车) const handleMainSearch = () => { - // 直接输入文本(未选择下拉股票)时,作为话题搜索 - if (!isStockSearch && searchValue) { - logger.debug('UnifiedSearchBox', '话题搜索', { topic: searchValue }); - } + // 直接输入文本时,构建参数并触发搜索 + logger.debug('UnifiedSearchBox', '主搜索触发', { searchValue }); handleApplyFilters(); }; - // 处理输入变化 + // ✅ 处理输入变化 - AutoComplete 内部处理显示,不需要更新状态 const handleInputChange = (value) => { - setSearchValue(value); - // 输入变化时重置股票搜索标记(只有从下拉选择才是股票搜索) - if (isStockSearch) { - setIsStockSearch(false); - setSelectedStockInfo(null); // 清除保存的股票信息 - logger.debug('UnifiedSearchBox', '切换为话题搜索模式'); - } + // AutoComplete 组件会自动处理显示值 + // 这里可以添加其他逻辑(如实时搜索建议) + logger.debug('UnifiedSearchBox', '输入变化', { value }); }; - // ✅ 生成完整的筛选参数对象 - const buildFilterParams = (overrides = {}) => { - // 构建基础参数(overrides 优先级高于本地状态) - const baseParams = { + // ✅ 生成完整的筛选参数对象 - 直接从 filters 和本地筛选器状态构建 + const buildFilterParams = useCallback((overrides = {}) => { + return { + // 基础参数(overrides 优先级高于本地状态) sort: overrides.sort ?? sort, importance: overrides.importance ?? importance, date_range: dateRange ? `${dateRange[0].format('YYYY-MM-DD')} 至 ${dateRange[1].format('YYYY-MM-DD')}` : '', - page: 1 - }; + page: 1, - // 构建搜索相关参数 - let searchParams; - if (isStockSearch && searchValue) { - // 股票搜索模式 - const stockCode = searchValue.split(' ')[0]; - searchParams = { - stock_code: stockCode, - q: '', - industry_code: '', - industry_classification: '' - }; - } else { - // 话题搜索模式 - searchParams = { - q: overrides.q ?? searchValue || '', - stock_code: '', - industry_code: industryValue?.[industryValue.length - 1] || '', - industry_classification: industryValue?.[0] || '' - }; - } + // 搜索参数(从 filters 继承,overrides 可覆盖) + q: overrides.q ?? filters.q ?? '', + stock_code: overrides.stock_code ?? filters.stock_code ?? '', + industry_code: overrides.industry_code ?? (industryValue?.[industryValue.length - 1] || ''), + industry_classification: overrides.industry_classification ?? (industryValue?.[0] || ''), - // 合并所有参数,overrides 具有最高优先级 - return { - ...baseParams, - ...searchParams, + // 最终 overrides 具有最高优先级 ...overrides }; - }; + }, [sort, importance, dateRange, filters.q, filters.stock_code, industryValue]); // ✅ 应用筛选(立即搜索,取消防抖) const handleApplyFilters = () => { @@ -355,17 +310,14 @@ const UnifiedSearchBox = ({ const params = buildFilterParams(); logger.debug('UnifiedSearchBox', '应用筛选,立即触发搜索', params); - onSearch(params); + triggerSearch(params); }; - // 重置筛选 + // ✅ 重置筛选 - 清空所有筛选器并触发搜索 const handleReset = () => { - // 重置所有状态 - setSearchValue(''); - setIsStockSearch(false); + // 重置所有筛选器状态 setStockOptions([]); setIndustryValue([]); - setSelectedStockInfo(null); setSort('new'); setImportance('all'); setDateRange(null); @@ -386,15 +338,17 @@ const UnifiedSearchBox = ({ onSearch(resetParams); }; - // 生成已选条件标签(包含所有筛选条件) + // 生成已选条件标签(包含所有筛选条件) - 从 filters 和本地状态读取 const filterTags = useMemo(() => { const tags = []; - // 搜索关键词/股票标签 - if (isStockSearch && searchValue) { - tags.push({ key: 'stock', label: `股票: ${searchValue}` }); - } else if (!isStockSearch && searchValue) { - tags.push({ key: 'topic', label: `话题: ${searchValue}` }); + // 股票/话题标签 - 从 filters 读取 + if (filters.stock_code) { + const stock = allStocks.find(s => s.code === filters.stock_code); + const label = stock ? `${stock.code} ${stock.name}` : filters.stock_code; + tags.push({ key: 'stock', label: `股票: ${label}` }); + } else if (filters.q) { + tags.push({ key: 'topic', label: `话题: ${filters.q}` }); } // 行业标签 @@ -421,37 +375,40 @@ const UnifiedSearchBox = ({ } return tags; - }, [searchValue, isStockSearch, industryValue, dateRange, importance, sort]); + }, [filters.stock_code, filters.q, allStocks, industryValue, dateRange, importance, sort]); - // 移除单个标签 + // ✅ 移除单个标签 - 构建新参数并触发搜索 const handleRemoveTag = (key) => { logger.debug('UnifiedSearchBox', '移除标签', { key }); if (key === 'topic' || key === 'stock') { - // 清除搜索框内容和股票选择状态 - setSearchValue(''); - setIsStockSearch(false); - setSelectedStockInfo(null); + // 清除搜索关键词/股票,立即触发搜索 + const params = buildFilterParams({ q: '', stock_code: '' }); + logger.debug('UnifiedSearchBox', '移除搜索标签后触发搜索', { key, params }); + triggerSearch(params); } else if (key === 'industry') { // 清除行业选择 setIndustryValue([]); + const params = buildFilterParams({ industry_code: '', industry_classification: '' }); + triggerSearch(params); } else if (key === 'date_range') { // 清除日期范围 setDateRange(null); + setTimeout(() => { + const params = buildFilterParams(); + triggerSearch(params); + }, 50); } else if (key === 'importance') { // 重置重要性为默认值 setImportance('all'); + const params = buildFilterParams({ importance: 'all' }); + triggerSearch(params); } else if (key === 'sort') { // 重置排序为默认值 setSort('new'); + const params = buildFilterParams({ sort: 'new' }); + triggerSearch(params); } - - // 延迟触发搜索,确保状态已更新 - setTimeout(() => { - const params = buildFilterParams(); - logger.debug('UnifiedSearchBox', '移除标签后触发搜索', { key, params }); - onSearch(params); - }, 50); }; return ( @@ -475,7 +432,6 @@ const UnifiedSearchBox = ({ options={stockOptions} placeholder="请输入股票代码/股票名称/相关话题" onPressEnter={handleMainSearch} - disabled={loading} style={{ flex: 1 }} size="large" notFoundContent={searchValue && stockOptions.length === 0 ? "未找到匹配的股票" : null} @@ -483,7 +439,6 @@ const UnifiedSearchBox = ({