feat: 搜索框布局调整
This commit is contained in:
@@ -2,14 +2,12 @@
|
|||||||
// 搜索组件:三行布局(主搜索 + 热门概念 + 筛选区)
|
// 搜索组件:三行布局(主搜索 + 热门概念 + 筛选区)
|
||||||
import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Card, Input, Cascader, Button, Space, Tag, AutoComplete, DatePicker, Select as AntSelect
|
Card, Input, Cascader, Button, Space, Tag, AutoComplete, Select as AntSelect
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
SearchOutlined, CloseCircleOutlined, StockOutlined
|
SearchOutlined, CloseCircleOutlined, StockOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import moment from 'moment';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import locale from 'antd/es/date-picker/locale/zh_CN';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { fetchIndustryData, selectIndustryData, selectIndustryLoading } from '../../../store/slices/industrySlice';
|
import { fetchIndustryData, selectIndustryData, selectIndustryLoading } from '../../../store/slices/industrySlice';
|
||||||
@@ -18,7 +16,6 @@ import { logger } from '../../../utils/logger';
|
|||||||
import PopularKeywords from './PopularKeywords';
|
import PopularKeywords from './PopularKeywords';
|
||||||
import TradingTimeFilter from './TradingTimeFilter';
|
import TradingTimeFilter from './TradingTimeFilter';
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
|
||||||
const { Option } = AntSelect;
|
const { Option } = AntSelect;
|
||||||
|
|
||||||
const UnifiedSearchBox = ({
|
const UnifiedSearchBox = ({
|
||||||
@@ -36,7 +33,6 @@ const UnifiedSearchBox = ({
|
|||||||
// 筛选条件状态
|
// 筛选条件状态
|
||||||
const [sort, setSort] = useState('new'); // 排序方式
|
const [sort, setSort] = useState('new'); // 排序方式
|
||||||
const [importance, setImportance] = useState([]); // 重要性(数组,支持多选)
|
const [importance, setImportance] = useState([]); // 重要性(数组,支持多选)
|
||||||
const [dateRange, setDateRange] = useState(null); // 日期范围
|
|
||||||
const [tradingTimeRange, setTradingTimeRange] = useState(null); // 交易时段筛选
|
const [tradingTimeRange, setTradingTimeRange] = useState(null); // 交易时段筛选
|
||||||
|
|
||||||
// ✅ 本地输入状态 - 管理用户的实时输入
|
// ✅ 本地输入状态 - 管理用户的实时输入
|
||||||
@@ -148,17 +144,6 @@ const UnifiedSearchBox = ({
|
|||||||
setImportance([]);
|
setImportance([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 初始化日期范围
|
|
||||||
if (filters.date_range) {
|
|
||||||
const parts = filters.date_range.split(' 至 ');
|
|
||||||
if (parts.length === 2) {
|
|
||||||
setDateRange([dayjs(parts[0]), dayjs(parts[1])]);
|
|
||||||
logger.debug('UnifiedSearchBox', '初始化日期范围', {
|
|
||||||
date_range: filters.date_range
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 初始化行业分类(需要 industryData 加载完成)
|
// ✅ 初始化行业分类(需要 industryData 加载完成)
|
||||||
if (filters.industry_code && industryData && industryData.length > 0) {
|
if (filters.industry_code && industryData && industryData.length > 0) {
|
||||||
const path = findIndustryPath(filters.industry_code, industryData);
|
const path = findIndustryPath(filters.industry_code, industryData);
|
||||||
@@ -178,7 +163,7 @@ const UnifiedSearchBox = ({
|
|||||||
// 如果 filters 中没有搜索关键词,清空输入框
|
// 如果 filters 中没有搜索关键词,清空输入框
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
}
|
}
|
||||||
}, [filters.sort, filters.importance, filters.date_range, filters.industry_code, filters.q, industryData, findIndustryPath]);
|
}, [filters.sort, filters.importance, filters.industry_code, filters.q, industryData, findIndustryPath]);
|
||||||
|
|
||||||
// AutoComplete 搜索股票(模糊匹配 code 或 name)
|
// AutoComplete 搜索股票(模糊匹配 code 或 name)
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
@@ -233,35 +218,6 @@ const UnifiedSearchBox = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ 日期范围变化(使用防抖)
|
|
||||||
const handleDateRangeChange = (dates) => {
|
|
||||||
logger.debug('UnifiedSearchBox', '【1/5】日期范围值改变', {
|
|
||||||
oldValue: dateRange,
|
|
||||||
newValue: dates
|
|
||||||
});
|
|
||||||
setDateRange(dates);
|
|
||||||
|
|
||||||
// ⚠️ 注意:setState是异步的,此时dateRange仍是旧值
|
|
||||||
logger.debug('UnifiedSearchBox', '【2/5】调用buildFilterParams前的状态', {
|
|
||||||
dateRange: dateRange, // 旧值
|
|
||||||
sort: sort,
|
|
||||||
importance: importance,
|
|
||||||
industryValue: industryValue
|
|
||||||
});
|
|
||||||
|
|
||||||
// 使用防抖搜索(需要从新值推导参数)
|
|
||||||
const params = {
|
|
||||||
...buildFilterParams(),
|
|
||||||
date_range: dates ? `${dates[0].format('YYYY-MM-DD')} 至 ${dates[1].format('YYYY-MM-DD')}` : ''
|
|
||||||
};
|
|
||||||
logger.debug('UnifiedSearchBox', '【3/5】buildFilterParams返回的参数', params);
|
|
||||||
|
|
||||||
if (debouncedSearchRef.current) {
|
|
||||||
logger.debug('UnifiedSearchBox', '【4/5】调用防抖函数(300ms延迟)');
|
|
||||||
debouncedSearchRef.current(params);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ✅ 重要性变化(立即执行)- 支持多选
|
// ✅ 重要性变化(立即执行)- 支持多选
|
||||||
const handleImportanceChange = (value) => {
|
const handleImportanceChange = (value) => {
|
||||||
logger.debug('UnifiedSearchBox', '重要性值改变', {
|
logger.debug('UnifiedSearchBox', '重要性值改变', {
|
||||||
@@ -376,7 +332,7 @@ const UnifiedSearchBox = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { range, type, label } = timeConfig;
|
const { range, type, label, key } = timeConfig;
|
||||||
let params = {};
|
let params = {};
|
||||||
|
|
||||||
if (type === 'recent_days') {
|
if (type === 'recent_days') {
|
||||||
@@ -391,7 +347,7 @@ const UnifiedSearchBox = ({
|
|||||||
params.recent_days = '';
|
params.recent_days = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
setTradingTimeRange({ ...params, label });
|
setTradingTimeRange({ ...params, label, key });
|
||||||
|
|
||||||
// 立即触发搜索
|
// 立即触发搜索
|
||||||
const searchParams = buildFilterParams(params);
|
const searchParams = buildFilterParams(params);
|
||||||
@@ -435,7 +391,6 @@ const UnifiedSearchBox = ({
|
|||||||
currentState: {
|
currentState: {
|
||||||
sort,
|
sort,
|
||||||
importance,
|
importance,
|
||||||
dateRange,
|
|
||||||
industryValue,
|
industryValue,
|
||||||
'filters.q': filters.q
|
'filters.q': filters.q
|
||||||
}
|
}
|
||||||
@@ -466,7 +421,6 @@ const UnifiedSearchBox = ({
|
|||||||
// 基础参数(overrides 优先级高于本地状态)
|
// 基础参数(overrides 优先级高于本地状态)
|
||||||
sort: actualSort,
|
sort: actualSort,
|
||||||
importance: importanceValue,
|
importance: importanceValue,
|
||||||
date_range: dateRange ? `${dateRange[0].format('YYYY-MM-DD')} 至 ${dateRange[1].format('YYYY-MM-DD')}` : '',
|
|
||||||
page: 1,
|
page: 1,
|
||||||
|
|
||||||
// 搜索参数: 统一使用 q 参数进行搜索(话题/股票/关键词)
|
// 搜索参数: 统一使用 q 参数进行搜索(话题/股票/关键词)
|
||||||
@@ -490,19 +444,7 @@ const UnifiedSearchBox = ({
|
|||||||
|
|
||||||
logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输出结果', result);
|
logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输出结果', result);
|
||||||
return result;
|
return result;
|
||||||
}, [sort, importance, dateRange, filters.q, industryValue, tradingTimeRange]);
|
}, [sort, importance, filters.q, industryValue, tradingTimeRange]);
|
||||||
|
|
||||||
// ✅ 应用筛选(立即搜索,取消防抖)
|
|
||||||
const handleApplyFilters = () => {
|
|
||||||
// 取消之前的防抖搜索
|
|
||||||
if (debouncedSearchRef.current) {
|
|
||||||
debouncedSearchRef.current.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = buildFilterParams();
|
|
||||||
logger.debug('UnifiedSearchBox', '应用筛选,立即触发搜索', params);
|
|
||||||
triggerSearch(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ✅ 重置筛选 - 清空所有筛选器并触发搜索
|
// ✅ 重置筛选 - 清空所有筛选器并触发搜索
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
@@ -512,7 +454,6 @@ const UnifiedSearchBox = ({
|
|||||||
setIndustryValue([]);
|
setIndustryValue([]);
|
||||||
setSort('new');
|
setSort('new');
|
||||||
setImportance([]); // 改为空数组
|
setImportance([]); // 改为空数组
|
||||||
setDateRange(null);
|
|
||||||
setTradingTimeRange(null); // 清空交易时段筛选
|
setTradingTimeRange(null); // 清空交易时段筛选
|
||||||
|
|
||||||
// 输出重置后的完整参数
|
// 输出重置后的完整参数
|
||||||
@@ -521,7 +462,6 @@ const UnifiedSearchBox = ({
|
|||||||
industry_code: '',
|
industry_code: '',
|
||||||
sort: 'new',
|
sort: 'new',
|
||||||
importance: 'all', // 传给后端时转为'all'
|
importance: 'all', // 传给后端时转为'all'
|
||||||
date_range: '',
|
|
||||||
start_date: '',
|
start_date: '',
|
||||||
end_date: '',
|
end_date: '',
|
||||||
recent_days: '',
|
recent_days: '',
|
||||||
@@ -547,9 +487,9 @@ const UnifiedSearchBox = ({
|
|||||||
const findLabel = (code, data) => {
|
const findLabel = (code, data) => {
|
||||||
for (const item of data) {
|
for (const item of data) {
|
||||||
if (code.startsWith(item.value)) {
|
if (code.startsWith(item.value)) {
|
||||||
if(item.value === code){
|
if (item.value === code) {
|
||||||
return item.label;
|
return item.label;
|
||||||
}else {
|
} else {
|
||||||
return findLabel(code, item.children);
|
return findLabel(code, item.children);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,12 +507,6 @@ const UnifiedSearchBox = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 日期范围标签
|
|
||||||
if (dateRange && dateRange.length === 2) {
|
|
||||||
const dateLabel = `${dateRange[0].format('YYYY-MM-DD')} 至 ${dateRange[1].format('YYYY-MM-DD')}`;
|
|
||||||
tags.push({ key: 'date_range', label: `日期: ${dateLabel}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 交易时段筛选标签
|
// 交易时段筛选标签
|
||||||
if (tradingTimeRange?.label) {
|
if (tradingTimeRange?.label) {
|
||||||
tags.push({
|
tags.push({
|
||||||
@@ -599,7 +533,7 @@ const UnifiedSearchBox = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
}, [filters.q, industryValue, dateRange, importance, sort, tradingTimeRange]);
|
}, [filters.q, industryValue, importance, sort, tradingTimeRange]);
|
||||||
|
|
||||||
// ✅ 移除单个标签 - 构建新参数并触发搜索
|
// ✅ 移除单个标签 - 构建新参数并触发搜索
|
||||||
const handleRemoveTag = (key) => {
|
const handleRemoveTag = (key) => {
|
||||||
@@ -621,11 +555,6 @@ const UnifiedSearchBox = ({
|
|||||||
setIndustryValue([]);
|
setIndustryValue([]);
|
||||||
const params = buildFilterParams({ industry_code: '' });
|
const params = buildFilterParams({ industry_code: '' });
|
||||||
triggerSearch(params);
|
triggerSearch(params);
|
||||||
} else if (key === 'date_range') {
|
|
||||||
// 清除日期范围
|
|
||||||
setDateRange(null);
|
|
||||||
const params = buildFilterParams({ date_range: '' });
|
|
||||||
triggerSearch(params);
|
|
||||||
} else if (key === 'trading_time') {
|
} else if (key === 'trading_time') {
|
||||||
// 清除交易时段筛选
|
// 清除交易时段筛选
|
||||||
setTradingTimeRange(null);
|
setTradingTimeRange(null);
|
||||||
@@ -650,61 +579,11 @@ const UnifiedSearchBox = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
{/* 第一行:主搜索框 */}
|
{/* 第三行:行业 + 重要性 + 排序 */}
|
||||||
<Space.Compact style={{ width: '100%', marginBottom: 12 }} size="large">
|
|
||||||
<SearchOutlined style={{
|
|
||||||
fontSize: 20,
|
|
||||||
padding: '8px 12px',
|
|
||||||
background: '#f5f5f5',
|
|
||||||
borderRadius: '6px 0 0 6px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
color: '#666'
|
|
||||||
}} />
|
|
||||||
<AutoComplete
|
|
||||||
value={inputValue}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
onSearch={handleSearch}
|
|
||||||
onSelect={handleStockSelect}
|
|
||||||
onFocus={onSearchFocus}
|
|
||||||
options={stockOptions}
|
|
||||||
placeholder="请输入股票代码/股票名称/相关话题"
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleMainSearch();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
size="large"
|
|
||||||
notFoundContent={inputValue && stockOptions.length === 0 ? "未找到匹配的股票" : null}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
onClick={handleMainSearch}
|
|
||||||
size="large"
|
|
||||||
style={{ minWidth: 80 }}
|
|
||||||
>
|
|
||||||
搜索
|
|
||||||
</Button>
|
|
||||||
</Space.Compact>
|
|
||||||
|
|
||||||
{/* 第二行:热门概念 */}
|
|
||||||
<div style={{ marginBottom: 12 }}>
|
|
||||||
<PopularKeywords
|
|
||||||
keywords={popularKeywords}
|
|
||||||
onKeywordClick={handleKeywordClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 第三行:筛选器 + 排序 */}
|
|
||||||
<Space style={{ width: '100%', justifyContent: 'space-between' }} size="middle">
|
<Space style={{ width: '100%', justifyContent: 'space-between' }} size="middle">
|
||||||
{/* 左侧:筛选器组 */}
|
{/* 左侧:筛选器组 */}
|
||||||
<Space size="middle" wrap>
|
<Space size="middle" wrap>
|
||||||
<span style={{ fontSize: 14, color: '#666', fontWeight: 'bold' }}>筛选:</span>
|
<span style={{ fontSize: 14, color: '#666', fontWeight: 'bold' }}>筛选:</span>
|
||||||
|
|
||||||
{/* 交易时段筛选 */}
|
|
||||||
<TradingTimeFilter onChange={handleTradingTimeChange} />
|
|
||||||
|
|
||||||
{/* 行业分类 */}
|
{/* 行业分类 */}
|
||||||
<Cascader
|
<Cascader
|
||||||
value={industryValue}
|
value={industryValue}
|
||||||
@@ -727,16 +606,6 @@ const UnifiedSearchBox = ({
|
|||||||
size="middle"
|
size="middle"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 日期范围 */}
|
|
||||||
<RangePicker
|
|
||||||
value={dateRange}
|
|
||||||
onChange={handleDateRangeChange}
|
|
||||||
locale={locale}
|
|
||||||
placeholder={['开始日期', '结束日期']}
|
|
||||||
style={{ width: 240 }}
|
|
||||||
size="middle"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 重要性 */}
|
{/* 重要性 */}
|
||||||
<Space size="small">
|
<Space size="small">
|
||||||
<span style={{ fontSize: 14, color: '#666' }}>重要性:</span>
|
<span style={{ fontSize: 14, color: '#666' }}>重要性:</span>
|
||||||
@@ -756,6 +625,49 @@ const UnifiedSearchBox = ({
|
|||||||
</AntSelect>
|
</AntSelect>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
|
{/* 搜索图标(可点击) + 搜索框 */}
|
||||||
|
<Space.Compact style={{ flex: 1, minWidth: 300 }}>
|
||||||
|
<SearchOutlined
|
||||||
|
onClick={handleMainSearch}
|
||||||
|
style={{
|
||||||
|
fontSize: 18,
|
||||||
|
padding: '8px 12px',
|
||||||
|
background: '#f5f5f5',
|
||||||
|
borderRadius: '6px 0 0 6px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#666',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.3s'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = '#1890ff';
|
||||||
|
e.currentTarget.style.background = '#e6f7ff';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = '#666';
|
||||||
|
e.currentTarget.style.background = '#f5f5f5';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<AutoComplete
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
onSelect={handleStockSelect}
|
||||||
|
onFocus={onSearchFocus}
|
||||||
|
options={stockOptions}
|
||||||
|
placeholder="请输入股票代码/股票名称/相关话题"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleMainSearch();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
size="middle"
|
||||||
|
notFoundContent={inputValue && stockOptions.length === 0 ? "未找到匹配的股票" : null}
|
||||||
|
/>
|
||||||
|
</Space.Compact>
|
||||||
|
|
||||||
{/* 重置按钮 - 现代化设计 */}
|
{/* 重置按钮 - 现代化设计 */}
|
||||||
<Button
|
<Button
|
||||||
icon={<CloseCircleOutlined />}
|
icon={<CloseCircleOutlined />}
|
||||||
@@ -811,6 +723,25 @@ const UnifiedSearchBox = ({
|
|||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
|
{/* 第一行:筛选 + 时间按钮 + 搜索图标 + 搜索框 */}
|
||||||
|
<Space wrap style={{ width: '100%', marginBottom: 12, marginTop: 8 }} size="middle">
|
||||||
|
<span style={{ fontSize: 14, color: '#666', fontWeight: 'bold' }}>时间筛选:</span>
|
||||||
|
|
||||||
|
{/* 交易时段筛选 */}
|
||||||
|
<TradingTimeFilter
|
||||||
|
value={tradingTimeRange?.key || null}
|
||||||
|
onChange={handleTradingTimeChange}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
{/* 第二行:热门概念 */}
|
||||||
|
<div style={{ marginTop: 8 }}>
|
||||||
|
<PopularKeywords
|
||||||
|
keywords={popularKeywords}
|
||||||
|
onKeywordClick={handleKeywordClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 已选条件标签 */}
|
{/* 已选条件标签 */}
|
||||||
{filterTags.length > 0 && (
|
{filterTags.length > 0 && (
|
||||||
<Space size={[8, 8]} wrap style={{ marginTop: 12 }}>
|
<Space size={[8, 8]} wrap style={{ marginTop: 12 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user