690 lines
26 KiB
JavaScript
690 lines
26 KiB
JavaScript
// src/views/Community/components/UnifiedSearchBox.js
|
||
// 搜索组件:三行布局(主搜索 + 热门概念 + 筛选区)
|
||
import React, { useState, useMemo, useEffect, useCallback, useRef } 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 dayjs from 'dayjs';
|
||
import locale from 'antd/es/date-picker/locale/zh_CN';
|
||
import debounce from 'lodash/debounce';
|
||
import { useSelector, useDispatch } from 'react-redux';
|
||
import { fetchIndustryData, selectIndustryData, selectIndustryLoading } from '../../../store/slices/industrySlice';
|
||
import { stockService } from '../../../services/stockService';
|
||
import { logger } from '../../../utils/logger';
|
||
import PopularKeywords from './PopularKeywords';
|
||
|
||
const { RangePicker } = DatePicker;
|
||
const { Option } = AntSelect;
|
||
|
||
const UnifiedSearchBox = ({
|
||
onSearch,
|
||
onSearchFocus,
|
||
popularKeywords = [],
|
||
filters = {}
|
||
}) => {
|
||
|
||
// 其他状态
|
||
const [stockOptions, setStockOptions] = useState([]); // 股票下拉选项列表
|
||
const [allStocks, setAllStocks] = useState([]); // 所有股票数据
|
||
const [industryValue, setIndustryValue] = useState([]);
|
||
|
||
// 筛选条件状态
|
||
const [sort, setSort] = useState('new'); // 排序方式
|
||
const [importance, setImportance] = useState('all'); // 重要性
|
||
const [dateRange, setDateRange] = useState(null); // 日期范围
|
||
|
||
// ✅ 本地输入状态 - 管理用户的实时输入
|
||
const [inputValue, setInputValue] = useState('');
|
||
|
||
// 使用 Redux 获取行业数据
|
||
const dispatch = useDispatch();
|
||
const industryData = useSelector(selectIndustryData);
|
||
const industryLoading = useSelector(selectIndustryLoading);
|
||
|
||
// 加载行业数据函数
|
||
const loadIndustryData = useCallback(() => {
|
||
if (!industryData) {
|
||
dispatch(fetchIndustryData());
|
||
}
|
||
}, [dispatch, industryData]);
|
||
|
||
// 搜索触发函数
|
||
const triggerSearch = useCallback((params) => {
|
||
logger.debug('UnifiedSearchBox', '【5/5】✅ 最终触发搜索 - 调用onSearch回调', {
|
||
params: params,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
onSearch(params);
|
||
}, [onSearch]);
|
||
|
||
// ✅ 创建防抖的搜索函数(300ms 延迟)
|
||
const debouncedSearchRef = useRef(null);
|
||
|
||
useEffect(() => {
|
||
// 创建防抖函数,使用 triggerSearch 而不是直接调用 onSearch
|
||
debouncedSearchRef.current = debounce((params) => {
|
||
logger.debug('UnifiedSearchBox', '⏱️ 防抖延迟结束,执行搜索', {
|
||
params: params,
|
||
delayMs: 300
|
||
});
|
||
triggerSearch(params);
|
||
}, 300);
|
||
|
||
// 清理函数
|
||
return () => {
|
||
if (debouncedSearchRef.current) {
|
||
debouncedSearchRef.current.cancel();
|
||
}
|
||
};
|
||
}, [triggerSearch]);
|
||
|
||
// 加载所有股票数据
|
||
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();
|
||
}
|
||
};
|
||
|
||
// 从 props.filters 初始化所有内部状态 (只在组件首次挂载时执行)
|
||
// 辅助函数:递归查找行业代码的完整路径
|
||
const findIndustryPath = React.useCallback((targetCode, data, currentPath = []) => {
|
||
if (!data || data.length === 0) return null;
|
||
|
||
for (const item of data) {
|
||
const newPath = [...currentPath, item.value];
|
||
|
||
if (item.value === targetCode) {
|
||
return newPath;
|
||
}
|
||
|
||
if (item.children && item.children.length > 0) {
|
||
const found = findIndustryPath(targetCode, item.children, newPath);
|
||
if (found) return found;
|
||
}
|
||
}
|
||
return null;
|
||
}, []);
|
||
|
||
// ✅ 从 props.filters 初始化筛选条件和输入框值
|
||
useEffect(() => {
|
||
if (!filters) return;
|
||
|
||
// 初始化排序和重要性
|
||
if (filters.sort) setSort(filters.sort);
|
||
if (filters.importance) setImportance(filters.importance);
|
||
|
||
// ✅ 初始化日期范围
|
||
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 加载完成)
|
||
if (filters.industry_code && industryData && industryData.length > 0) {
|
||
const path = findIndustryPath(filters.industry_code, industryData);
|
||
if (path) {
|
||
setIndustryValue(path);
|
||
logger.debug('UnifiedSearchBox', '初始化行业分类', {
|
||
industry_code: filters.industry_code,
|
||
path
|
||
});
|
||
}
|
||
}
|
||
|
||
// ✅ 同步 filters.q 到输入框显示值
|
||
if (filters.q) {
|
||
setInputValue(filters.q);
|
||
} else if (!filters.q) {
|
||
// 如果 filters 中没有搜索关键词,清空输入框
|
||
setInputValue('');
|
||
}
|
||
}, [filters.sort, filters.importance, filters.date_range, filters.industry_code, filters.q, industryData, findIndustryPath]);
|
||
|
||
// 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: (
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||
<StockOutlined style={{ color: '#1890ff' }} />
|
||
<span style={{ fontWeight: 500, color: '#333' }}>{stock.code}</span>
|
||
<span style={{ color: '#666' }}>{stock.name}</span>
|
||
</div>
|
||
),
|
||
// 保存完整的股票信息,用于选中后显示
|
||
stockInfo: stock
|
||
}));
|
||
|
||
setStockOptions(options);
|
||
logger.debug('UnifiedSearchBox', '股票模糊搜索', {
|
||
query: value,
|
||
resultCount: options.length
|
||
});
|
||
};
|
||
|
||
// ✅ 选中股票(从下拉选择) - 更新输入框并触发搜索
|
||
const handleStockSelect = (_value, option) => {
|
||
const stockInfo = option.stockInfo;
|
||
if (stockInfo) {
|
||
logger.debug('UnifiedSearchBox', '选中股票', {
|
||
code: stockInfo.code,
|
||
name: stockInfo.name
|
||
});
|
||
|
||
// 更新输入框显示
|
||
setInputValue(`${stockInfo.code} ${stockInfo.name}`);
|
||
|
||
// 直接构建参数并触发搜索 - 使用股票代码作为 q 参数
|
||
const params = buildFilterParams({
|
||
q: stockInfo.code, // 使用股票代码作为搜索关键词
|
||
industry_code: ''
|
||
});
|
||
logger.debug('UnifiedSearchBox', '自动触发股票搜索', params);
|
||
triggerSearch(params);
|
||
}
|
||
};
|
||
|
||
// ✅ 日期范围变化(使用防抖)
|
||
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) => {
|
||
logger.debug('UnifiedSearchBox', '【1/5】重要性值改变', {
|
||
oldValue: importance,
|
||
newValue: value
|
||
});
|
||
setImportance(value);
|
||
|
||
// ⚠️ 注意:setState是异步的,此时importance仍是旧值
|
||
logger.debug('UnifiedSearchBox', '【2/5】调用buildFilterParams前的状态', {
|
||
importance: importance, // 旧值
|
||
sort: sort,
|
||
dateRange: dateRange,
|
||
industryValue: industryValue
|
||
});
|
||
|
||
// 使用防抖搜索
|
||
const params = buildFilterParams({ importance: value });
|
||
logger.debug('UnifiedSearchBox', '【3/5】buildFilterParams返回的参数', params);
|
||
|
||
if (debouncedSearchRef.current) {
|
||
logger.debug('UnifiedSearchBox', '【4/5】调用防抖函数(300ms延迟)');
|
||
debouncedSearchRef.current(params);
|
||
}
|
||
};
|
||
|
||
// ✅ 排序变化(使用防抖)
|
||
const handleSortChange = (value) => {
|
||
logger.debug('UnifiedSearchBox', '【1/5】排序值改变', {
|
||
oldValue: sort,
|
||
newValue: value
|
||
});
|
||
setSort(value);
|
||
|
||
// ⚠️ 注意:setState是异步的,此时sort仍是旧值
|
||
logger.debug('UnifiedSearchBox', '【2/5】调用buildFilterParams前的状态', {
|
||
sort: sort, // 旧值
|
||
importance: importance,
|
||
dateRange: dateRange,
|
||
industryValue: industryValue
|
||
});
|
||
|
||
// 使用防抖搜索
|
||
const params = buildFilterParams({ sort: value });
|
||
logger.debug('UnifiedSearchBox', '【3/5】buildFilterParams返回的参数', params);
|
||
|
||
if (debouncedSearchRef.current) {
|
||
logger.debug('UnifiedSearchBox', '【4/5】调用防抖函数(300ms延迟)');
|
||
debouncedSearchRef.current(params);
|
||
}
|
||
};
|
||
|
||
// ✅ 行业分类变化(使用防抖)
|
||
const handleIndustryChange = (value) => {
|
||
logger.debug('UnifiedSearchBox', '【1/5】行业分类值改变', {
|
||
oldValue: industryValue,
|
||
newValue: value
|
||
});
|
||
setIndustryValue(value);
|
||
|
||
// ⚠️ 注意:setState是异步的,此时industryValue仍是旧值
|
||
logger.debug('UnifiedSearchBox', '【2/5】调用buildFilterParams前的状态', {
|
||
industryValue: industryValue, // 旧值
|
||
sort: sort,
|
||
importance: importance,
|
||
dateRange: dateRange
|
||
});
|
||
|
||
// 使用防抖搜索 (需要从新值推导参数)
|
||
const params = {
|
||
...buildFilterParams(),
|
||
industry_code: value?.[value.length - 1] || ''
|
||
};
|
||
logger.debug('UnifiedSearchBox', '【3/5】buildFilterParams返回的参数', params);
|
||
|
||
if (debouncedSearchRef.current) {
|
||
logger.debug('UnifiedSearchBox', '【4/5】调用防抖函数(300ms延迟)');
|
||
debouncedSearchRef.current(params);
|
||
}
|
||
};
|
||
|
||
// ✅ 热门概念点击处理(立即搜索,不使用防抖) - 更新输入框并触发搜索
|
||
const handleKeywordClick = (keyword) => {
|
||
// 更新输入框显示
|
||
setInputValue(keyword);
|
||
|
||
// 立即触发搜索(取消之前的防抖)
|
||
if (debouncedSearchRef.current) {
|
||
debouncedSearchRef.current.cancel();
|
||
}
|
||
|
||
const params = buildFilterParams({
|
||
q: keyword,
|
||
industry_code: ''
|
||
});
|
||
logger.debug('UnifiedSearchBox', '热门概念点击,立即触发搜索', {
|
||
keyword,
|
||
params
|
||
});
|
||
triggerSearch(params);
|
||
};
|
||
|
||
// 主搜索(点击搜索按钮或回车)
|
||
const handleMainSearch = () => {
|
||
// 取消之前的防抖
|
||
if (debouncedSearchRef.current) {
|
||
debouncedSearchRef.current.cancel();
|
||
}
|
||
|
||
// 构建参数并触发搜索 - 使用用户输入作为 q 参数
|
||
const params = buildFilterParams({
|
||
q: inputValue, // 使用用户输入(可能是话题、股票代码、股票名称等)
|
||
industry_code: ''
|
||
});
|
||
|
||
logger.debug('UnifiedSearchBox', '主搜索触发', {
|
||
inputValue,
|
||
params
|
||
});
|
||
triggerSearch(params);
|
||
};
|
||
|
||
// ✅ 处理输入变化 - 更新本地输入状态
|
||
const handleInputChange = (value) => {
|
||
logger.debug('UnifiedSearchBox', '输入变化', { value });
|
||
setInputValue(value);
|
||
};
|
||
|
||
// ✅ 生成完整的筛选参数对象 - 直接从 filters 和本地筛选器状态构建
|
||
const buildFilterParams = useCallback((overrides = {}) => {
|
||
logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输入参数', {
|
||
overrides: overrides,
|
||
currentState: {
|
||
sort,
|
||
importance,
|
||
dateRange,
|
||
industryValue,
|
||
'filters.q': filters.q
|
||
}
|
||
});
|
||
|
||
const result = {
|
||
// 基础参数(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,
|
||
|
||
// 搜索参数: 统一使用 q 参数进行搜索(话题/股票/关键词)
|
||
q: (overrides.q ?? filters.q) ?? '',
|
||
// 行业代码: 取选中路径的最后一级(最具体的行业代码)
|
||
industry_code: overrides.industry_code ?? (industryValue?.[industryValue.length - 1] || ''),
|
||
|
||
// 最终 overrides 具有最高优先级
|
||
...overrides
|
||
};
|
||
|
||
logger.debug('UnifiedSearchBox', '🔧 buildFilterParams - 输出结果', result);
|
||
return result;
|
||
}, [sort, importance, dateRange, filters.q, industryValue]);
|
||
|
||
// ✅ 应用筛选(立即搜索,取消防抖)
|
||
const handleApplyFilters = () => {
|
||
// 取消之前的防抖搜索
|
||
if (debouncedSearchRef.current) {
|
||
debouncedSearchRef.current.cancel();
|
||
}
|
||
|
||
const params = buildFilterParams();
|
||
logger.debug('UnifiedSearchBox', '应用筛选,立即触发搜索', params);
|
||
triggerSearch(params);
|
||
};
|
||
|
||
// ✅ 重置筛选 - 清空所有筛选器并触发搜索
|
||
const handleReset = () => {
|
||
// 重置所有筛选器状态
|
||
setInputValue(''); // 清空输入框
|
||
setStockOptions([]);
|
||
setIndustryValue([]);
|
||
setSort('new');
|
||
setImportance('all');
|
||
setDateRange(null);
|
||
|
||
// 输出重置后的完整参数
|
||
const resetParams = {
|
||
q: '',
|
||
industry_code: '',
|
||
sort: 'new',
|
||
importance: 'all',
|
||
date_range: '',
|
||
page: 1
|
||
};
|
||
|
||
logger.debug('UnifiedSearchBox', '重置筛选', resetParams);
|
||
onSearch(resetParams);
|
||
};
|
||
|
||
// 生成已选条件标签(包含所有筛选条件) - 从 filters 和本地状态读取
|
||
const filterTags = useMemo(() => {
|
||
const tags = [];
|
||
|
||
// 搜索关键词标签 - 从 filters.q 读取
|
||
if (filters.q) {
|
||
tags.push({ key: 'search', label: `搜索: ${filters.q}` });
|
||
}
|
||
|
||
// 行业标签
|
||
if (industryValue && industryValue.length > 0) {
|
||
const industryLabel = industryValue.slice(1).join(' > ');
|
||
tags.push({ key: 'industry', label: `行业: ${industryLabel}` });
|
||
}
|
||
|
||
// 日期范围标签
|
||
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}` });
|
||
}
|
||
|
||
// 重要性标签(排除默认值 'all')
|
||
if (importance && importance !== 'all') {
|
||
tags.push({ key: 'importance', label: `重要性: ${importance}级` });
|
||
}
|
||
|
||
// 排序标签(排除默认值 'new')
|
||
if (sort && sort !== 'new') {
|
||
const sortLabel = sort === 'hot' ? '最热' : sort === 'importance' ? '重要性' : sort;
|
||
tags.push({ key: 'sort', label: `排序: ${sortLabel}` });
|
||
}
|
||
|
||
return tags;
|
||
}, [filters.q, industryValue, dateRange, importance, sort]);
|
||
|
||
// ✅ 移除单个标签 - 构建新参数并触发搜索
|
||
const handleRemoveTag = (key) => {
|
||
logger.debug('UnifiedSearchBox', '移除标签', { key });
|
||
|
||
if (key === 'search') {
|
||
// 清除搜索关键词和输入框,立即触发搜索
|
||
setInputValue(''); // 清空输入框
|
||
const params = buildFilterParams({ q: '' });
|
||
logger.debug('UnifiedSearchBox', '移除搜索标签后触发搜索', { key, params });
|
||
triggerSearch(params);
|
||
} else if (key === 'industry') {
|
||
// 清除行业选择
|
||
setIndustryValue([]);
|
||
const params = buildFilterParams({ industry_code: '' });
|
||
triggerSearch(params);
|
||
} else if (key === 'date_range') {
|
||
// 清除日期范围
|
||
setDateRange(null);
|
||
const params = buildFilterParams({ date_range: '' });
|
||
triggerSearch(params);
|
||
} 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);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<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 size="middle" wrap>
|
||
<span style={{ fontSize: 14, color: '#666', fontWeight: 'bold' }}>筛选:</span>
|
||
{/* 行业分类 */}
|
||
<Cascader
|
||
value={industryValue}
|
||
onChange={handleIndustryChange}
|
||
onFocus={handleCascaderFocus}
|
||
options={industryData || []}
|
||
placeholder="行业分类"
|
||
changeOnSelect
|
||
showSearch={{
|
||
filter: (inputValue, path) =>
|
||
path.some(option =>
|
||
option.label.toLowerCase().includes(inputValue.toLowerCase())
|
||
)
|
||
}}
|
||
allowClear
|
||
expandTrigger="hover"
|
||
displayRender={(labels) => labels.join(' > ')}
|
||
disabled={industryLoading}
|
||
style={{ width: 200 }}
|
||
size="middle"
|
||
/>
|
||
|
||
{/* 日期范围 */}
|
||
<RangePicker
|
||
value={dateRange}
|
||
onChange={handleDateRangeChange}
|
||
locale={locale}
|
||
placeholder={['开始日期', '结束日期']}
|
||
style={{ width: 240 }}
|
||
size="middle"
|
||
/>
|
||
|
||
{/* 重要性 */}
|
||
<Space size="small">
|
||
<span style={{ fontSize: 14, color: '#666' }}>重要性:</span>
|
||
<AntSelect
|
||
value={importance}
|
||
onChange={handleImportanceChange}
|
||
style={{ width: 100 }}
|
||
size="middle"
|
||
>
|
||
<Option value="all">全部</Option>
|
||
<Option value="S">S级</Option>
|
||
<Option value="A">A级</Option>
|
||
<Option value="B">B级</Option>
|
||
<Option value="C">C级</Option>
|
||
</AntSelect>
|
||
</Space>
|
||
|
||
{/* 重置按钮 - 现代化设计 */}
|
||
<Button
|
||
icon={<CloseCircleOutlined />}
|
||
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)';
|
||
}}
|
||
>
|
||
重置
|
||
</Button>
|
||
</Space>
|
||
|
||
{/* 右侧:排序 */}
|
||
<Space size="small">
|
||
<span style={{ fontSize: 14, color: '#666' }}>排序:</span>
|
||
<AntSelect
|
||
value={sort}
|
||
onChange={handleSortChange}
|
||
style={{ width: 120 }}
|
||
size="middle"
|
||
>
|
||
<Option value="new">最新</Option>
|
||
<Option value="hot">最热</Option>
|
||
<Option value="importance">重要性</Option>
|
||
</AntSelect>
|
||
</Space>
|
||
</Space>
|
||
|
||
{/* 已选条件标签 */}
|
||
{filterTags.length > 0 && (
|
||
<Space size={[8, 8]} wrap style={{ marginTop: 12 }}>
|
||
{filterTags.map(tag => (
|
||
<Tag
|
||
key={tag.key}
|
||
closable
|
||
onClose={() => handleRemoveTag(tag.key)}
|
||
color="blue"
|
||
>
|
||
{tag.label}
|
||
</Tag>
|
||
))}
|
||
</Space>
|
||
)}
|
||
</Card>
|
||
);
|
||
};
|
||
|
||
export default UnifiedSearchBox;
|