diff --git a/src/views/Community/components/UnifiedSearchBox.js b/src/views/Community/components/UnifiedSearchBox.js new file mode 100644 index 00000000..ff0bc74a --- /dev/null +++ b/src/views/Community/components/UnifiedSearchBox.js @@ -0,0 +1,405 @@ +// src/views/Community/components/UnifiedSearchBox.js +// 搜索组件:三行布局(主搜索 + 热门概念 + 筛选区) +import React, { useState, useMemo, useEffect } from 'react'; +import { + Card, Input, Cascader, Button, Space, Tag, AutoComplete, DatePicker, Select as AntSelect +} from 'antd'; +import { + SearchOutlined, CloseCircleOutlined, StockOutlined +} from '@ant-design/icons'; +import moment from 'moment'; +import locale from 'antd/es/date-picker/locale/zh_CN'; +import { useIndustry } from '../../../contexts/IndustryContext'; +import { stockService } from '../../../services/stockService'; +import { logger } from '../../../utils/logger'; +import PopularKeywords from './PopularKeywords'; + +const { RangePicker } = DatePicker; +const { Option } = AntSelect; + +const UnifiedSearchBox = ({ + onSearch, + popularKeywords = [], + filters = {}, + loading = false +}) => { + const [searchValue, setSearchValue] = useState(''); // 统一搜索值 + const [isStockSearch, setIsStockSearch] = useState(false); // 是否股票搜索 + const [stockOptions, setStockOptions] = useState([]); // 股票下拉选项列表 + const [allStocks, setAllStocks] = useState([]); // 所有股票数据 + const [industryValue, setIndustryValue] = useState([]); + + // 使用全局行业数据 + const { industryData, loadIndustryData, loading: industryLoading } = useIndustry(); + + // 加载所有股票数据 + useEffect(() => { + const loadStocks = async () => { + const response = await stockService.getAllStocks(); + if (response.success && response.data) { + setAllStocks(response.data); + logger.debug('UnifiedSearchBox', '股票数据加载成功', { + count: response.data.length + }); + } + }; + + loadStocks(); + }, []); + + // Cascader 获得焦点时加载数据 + const handleCascaderFocus = async () => { + if (!industryData || industryData.length === 0) { + logger.debug('UnifiedSearchBox', 'Cascader 获得焦点,开始加载行业数据'); + await loadIndustryData(); + } + }; + + // ⚡ 提取 filters 中的原始值,避免对象引用导致无限循环 + const stockCode = filters.stock_code; + const q = filters.q; + const industryCode = filters.industry_code; + + // 初始化:从 URL 恢复状态 + React.useEffect(() => { + if (stockCode) { + setSearchValue(stockCode); + setIsStockSearch(true); + } else if (q) { + setSearchValue(q); + setIsStockSearch(false); + } + if (industryCode) { + // TODO: 从 industry_code 恢复 industryValue 需要查找对应路径 + } + }, [stockCode, q, industryCode]); // ⚡ 只依赖原始值,不依赖整个 filters 对象 + + // AutoComplete 搜索股票(模糊匹配 code 或 name) + const handleSearch = (value) => { + if (!value || !allStocks || allStocks.length === 0) { + setStockOptions([]); + return; + } + + // 使用 stockService 进行模糊搜索 + const results = stockService.fuzzySearch(value, allStocks, 10); + + // 转换为 AutoComplete 选项格式 + const options = results.map(stock => ({ + value: stock.code, + label: ( +
+ + {stock.code} + {stock.name} +
+ ), + // 保存完整的股票信息,用于选中后显示 + stockInfo: stock + })); + + setStockOptions(options); + logger.debug('UnifiedSearchBox', '股票模糊搜索', { + query: value, + resultCount: options.length + }); + }; + + // 选中股票(从下拉选择) + const handleStockSelect = (value, option) => { + const stockInfo = option.stockInfo; + if (stockInfo) { + // 显示格式:股票代码 + 股票名称 + const displayValue = `${stockInfo.code} ${stockInfo.name}`; + setSearchValue(displayValue); + setIsStockSearch(true); + logger.debug('UnifiedSearchBox', '选中股票', { + code: stockInfo.code, + name: stockInfo.name + }); + } + }; + + // 日期范围变化 + const handleDateRangeChange = (dates) => { + // 这里不直接调用 onSearch,等用户点击搜索按钮 + logger.debug('UnifiedSearchBox', '日期范围变化', { dates }); + }; + + // 重要性变化 + const handleImportanceChange = (value) => { + logger.debug('UnifiedSearchBox', '重要性变化', { value }); + }; + + // 排序变化 + const handleSortChange = (value) => { + // 排序直接生效 + onSearch({ sort: value, page: 1 }); + }; + + // 主搜索(点击搜索按钮或回车) + const handleMainSearch = () => { + // 直接输入文本(未选择下拉股票)时,作为话题搜索 + if (!isStockSearch && searchValue) { + logger.debug('UnifiedSearchBox', '话题搜索', { topic: searchValue }); + } + handleApplyFilters(); + }; + + // 处理输入变化 + const handleInputChange = (value) => { + setSearchValue(value); + // 输入变化时重置股票搜索标记(只有从下拉选择才是股票搜索) + if (isStockSearch) { + setIsStockSearch(false); + logger.debug('UnifiedSearchBox', '切换为话题搜索模式'); + } + }; + + // 应用筛选 + const handleApplyFilters = () => { + const params = { page: 1 }; + + if (isStockSearch && searchValue) { + // 股票搜索模式:从下拉选择的股票 + // 提取股票代码(searchValue 格式:"000001 平安银行") + const stockCode = searchValue.split(' ')[0]; + params.stock_code = stockCode; + params.q = ''; + params.industry_code = ''; + params.industry_classification = ''; + logger.debug('UnifiedSearchBox', '应用股票筛选', { stockCode }); + } else { + // 话题搜索模式:直接输入的文本 + params.q = searchValue || ''; + params.stock_code = ''; + + if (industryValue && industryValue.length > 0) { + params.industry_code = industryValue[industryValue.length - 1]; + params.industry_classification = industryValue[0]; + } else { + params.industry_code = ''; + params.industry_classification = ''; + } + logger.debug('UnifiedSearchBox', '应用话题筛选', { topic: searchValue }); + } + + onSearch(params); + }; + + // 重置筛选 + const handleReset = () => { + setSearchValue(''); + setIsStockSearch(false); + setStockOptions([]); + setIndustryValue([]); + + onSearch({ + q: '', + stock_code: '', + industry_code: '', + industry_classification: '', + sort: 'new', + importance: 'all', + page: 1 + }); + }; + + // 生成已选条件标签 + const filterTags = useMemo(() => { + const tags = []; + + if (isStockSearch && searchValue) { + tags.push({ key: 'stock', label: `股票: ${searchValue}` }); + } else if (!isStockSearch && searchValue) { + tags.push({ key: 'topic', label: `话题: ${searchValue}` }); + } + + if (industryValue && industryValue.length > 0) { + const industryLabel = industryValue.slice(1).join(' > '); + tags.push({ key: 'industry', label: `行业: ${industryLabel}` }); + } + + return tags; + }, [searchValue, isStockSearch, industryValue]); + + // 移除单个标签 + const handleRemoveTag = (key) => { + if (key === 'topic' || key === 'stock') { + setSearchValue(''); + setIsStockSearch(false); + } else if (key === 'industry') { + setIndustryValue([]); + } + + setTimeout(handleApplyFilters, 100); + }; + + return ( + + {/* 第一行:主搜索框 */} + + + + + + + {/* 第二行:热门概念 */} +
+ +
+ + {/* 第三行:筛选器 + 排序 */} + + {/* 左侧:筛选器组 */} + + 筛选: + {/* 行业分类 */} + + path.some(option => + option.label.toLowerCase().includes(inputValue.toLowerCase()) + ) + }} + allowClear + expandTrigger="hover" + displayRender={(labels) => labels.join(' > ')} + disabled={industryLoading} + loading={industryLoading} + style={{ width: 200 }} + size="middle" + /> + + {/* 日期范围 */} + + + {/* 重要性 */} + + 重要性: + + + + + + + + + + {/* 重置按钮 - 现代化设计 */} + + + + {/* 右侧:排序 */} + + 排序: + + + + + + + + + {/* 已选条件标签 */} + {filterTags.length > 0 && ( + + {filterTags.map(tag => ( + handleRemoveTag(tag.key)} + color="blue" + > + {tag.label} + + ))} + + )} +
+ ); +}; + +export default UnifiedSearchBox;