From 0a0d617b209e40a5dcf385417113b321236b3318 Mon Sep 17 00:00:00 2001
From: zdl <3489966805@qq.com>
Date: Sat, 25 Oct 2025 18:23:20 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=A1=8C=E4=B8=9A?=
=?UTF-8?q?=E7=AD=9B=E9=80=89=E5=99=A8Box?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Community/components/UnifiedSearchBox.js | 405 ++++++++++++++++++
1 file changed, 405 insertions(+)
create mode 100644 src/views/Community/components/UnifiedSearchBox.js
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"
+ />
+
+ {/* 日期范围 */}
+
+
+ {/* 重要性 */}
+
+ 重要性:
+
+
+
+
+
+
+
+
+
+ {/* 重置按钮 - 现代化设计 */}
+ }
+ onClick={handleReset}
+ size="middle"
+ style={{
+ borderRadius: 6,
+ border: '1px solid #d9d9d9',
+ backgroundColor: '#fff',
+ color: '#666',
+ fontWeight: 500,
+ padding: '4px 12px',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 4,
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
+ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)'
+ }}
+ onMouseEnter={(e) => {
+ e.currentTarget.style.borderColor = '#ff4d4f';
+ e.currentTarget.style.color = '#ff4d4f';
+ e.currentTarget.style.backgroundColor = '#fff1f0';
+ e.currentTarget.style.boxShadow = '0 2px 8px rgba(255, 77, 79, 0.15)';
+ e.currentTarget.style.transform = 'translateY(-1px)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.borderColor = '#d9d9d9';
+ e.currentTarget.style.color = '#666';
+ e.currentTarget.style.backgroundColor = '#fff';
+ e.currentTarget.style.boxShadow = '0 1px 2px rgba(0, 0, 0, 0.05)';
+ e.currentTarget.style.transform = 'translateY(0)';
+ }}
+ >
+ 重置
+
+
+
+ {/* 右侧:排序 */}
+
+ 排序:
+
+
+
+
+
+
+
+
+ {/* 已选条件标签 */}
+ {filterTags.length > 0 && (
+
+ {filterTags.map(tag => (
+ handleRemoveTag(tag.key)}
+ color="blue"
+ >
+ {tag.label}
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default UnifiedSearchBox;