diff --git a/src/views/Community/components/CompactSearchBox.css b/src/views/Community/components/CompactSearchBox.css new file mode 100644 index 00000000..6a8c0ede --- /dev/null +++ b/src/views/Community/components/CompactSearchBox.css @@ -0,0 +1,80 @@ +/* CompactSearchBox.css */ +/* 紧凑版搜索和筛选组件样式 */ + +/* 搜索框 placeholder 白色 - 全覆盖选择器 */ +.gold-placeholder input::placeholder, +.gold-placeholder input[type="text"]::placeholder, +.gold-placeholder .ant-input::placeholder, +.gold-placeholder .ant-input-affix-wrapper input::placeholder, +.gold-placeholder .ant-select-selection-search-input::placeholder, +.gold-placeholder .ant-input-affix-wrapper .ant-input::placeholder { + color: #FFFFFF !important; + opacity: 0.8 !important; +} + +/* AutoComplete placeholder - 关键选择器 */ +.gold-placeholder .ant-select-selection-placeholder { + color: #FFFFFF !important; + opacity: 0.8 !important; +} + +.gold-placeholder .ant-input-affix-wrapper .ant-input, +.gold-placeholder .ant-input { + color: #FFFFFF !important; +} + +.gold-placeholder .ant-input-affix-wrapper { + background: transparent !important; +} + +/* 透明下拉框样式 */ +.transparent-select .ant-select-selector, +.transparent-cascader .ant-select-selector { + background: transparent !important; + border: none !important; + box-shadow: none !important; +} + +/* 行业筛选宽度自适应,减少间距 */ +.transparent-cascader { + width: auto !important; +} + +.transparent-cascader .ant-select-selector { + padding-right: 8px !important; + min-width: unset !important; +} + +/* 行业筛选 Cascader placeholder 白色 */ +.transparent-select .ant-select-selection-placeholder, +.transparent-cascader .ant-select-selection-placeholder, +.transparent-cascader input::placeholder, +.transparent-cascader .ant-cascader-input::placeholder { + color: #FFFFFF !important; +} + +.transparent-cascader .ant-cascader-input { + background: transparent !important; +} + +/* 行业筛选 Cascader 选中值白色 */ +.transparent-cascader .ant-select-selection-item, +.transparent-cascader .ant-cascader-picker-label { + color: #FFFFFF !important; +} + +/* 方括号样式下拉框 - 无边框 */ +.bracket-select .ant-select-selector { + background: transparent !important; + border: none !important; + box-shadow: none !important; +} + +.bracket-select .ant-select-selection-item, +.bracket-select .ant-select-selection-placeholder { + color: #FFFFFF !important; +} + +.bracket-select .ant-select-arrow { + color: rgba(255, 255, 255, 0.65) !important; +} diff --git a/src/views/Community/components/CompactSearchBox.js b/src/views/Community/components/CompactSearchBox.js index b51eb8ba..1cb9d51a 100644 --- a/src/views/Community/components/CompactSearchBox.js +++ b/src/views/Community/components/CompactSearchBox.js @@ -4,30 +4,49 @@ import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react'; import { Input, Cascader, Button, Space, Tag, AutoComplete, Select as AntSelect, - Tooltip + Tooltip, Divider, Flex } from 'antd'; import { SearchOutlined, CloseCircleOutlined, StockOutlined, FilterOutlined, - CalendarOutlined, SortAscendingOutlined + CalendarOutlined, SortAscendingOutlined, ReloadOutlined, ThunderboltOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; 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 { fetchIndustryData, selectIndustryData, selectIndustryLoading } from '@store/slices/industrySlice'; +import { stockService } from '@services/stockService'; +import { logger } from '@utils/logger'; import TradingTimeFilter from './TradingTimeFilter'; -import { PROFESSIONAL_COLORS } from '../../../constants/professionalTheme'; +import { PROFESSIONAL_COLORS } from '@constants/professionalTheme'; +import './CompactSearchBox.css'; const { Option } = AntSelect; +// 排序选项常量 +const SORT_OPTIONS = [ + { value: 'new', label: '最新排序', mobileLabel: '最新' }, + { value: 'hot', label: '最热排序', mobileLabel: '热门' }, + { value: 'importance', label: '重要性排序', mobileLabel: '重要' }, + { value: 'returns_avg', label: '平均收益', mobileLabel: '均收' }, + { value: 'returns_week', label: '周收益', mobileLabel: '周收' }, +]; + +// 重要性等级常量 +const IMPORTANCE_OPTIONS = [ + { value: 'S', label: 'S级' }, + { value: 'A', label: 'A级' }, + { value: 'B', label: 'B级' }, + { value: 'C', label: 'C级' }, +]; + const CompactSearchBox = ({ onSearch, onSearchFocus, filters = {}, mode, pageSize, - trackingFunctions = {} + trackingFunctions = {}, + isMobile = false }) => { // 状态 const [stockOptions, setStockOptions] = useState([]); @@ -420,19 +439,21 @@ const CompactSearchBox = ({ dispatch(fetchIndustryData()); } }; - return ( -
- {/* 单行紧凑布局 - 移动端自动换行 */} - - {/* 搜索框 */} +
+ {/* 第一行:搜索框 + 日期筛选 */} + + {/* 搜索框 - flex: 1 占满剩余空间 */} { if (e.key === 'Enter') { handleMainSearch(); } }} - style={{ width: window.innerWidth < 768 ? '100%' : 240, minWidth: window.innerWidth < 768 ? 0 : 240 }} + style={{ flex: 1, minWidth: isMobile ? 100 : 200 }} + className="gold-placeholder" > } + placeholder="搜索股票/话题..." style={{ - borderRadius: '8px', - border: `1px solid ${PROFESSIONAL_COLORS.border.default}`, - boxShadow: `0 2px 8px rgba(255, 195, 0, 0.1)`, - background: PROFESSIONAL_COLORS.background.secondary, - color: PROFESSIONAL_COLORS.text.primary + border: 'none', + background: 'transparent', + color: PROFESSIONAL_COLORS.text.primary, + boxShadow: 'none' }} /> - {/* 时间筛选 */} - -
- - -
-
+ {/* 分隔线 - H5 时隐藏 */} + {!isMobile && } - {/* 行业筛选 */} - + {/* 日期筛选按钮组 */} +
+ + +
+
+ + {/* 第二行:筛选条件 */} + + {/* 左侧筛选 */} + + {/* 行业筛选 */} + + {isMobile ? '行业' : '行业筛选'} + + } changeOnSelect showSearch={{ filter: (inputValue, path) => @@ -489,145 +521,65 @@ const CompactSearchBox = ({ }} allowClear expandTrigger="hover" - displayRender={(labels) => labels[labels.length - 1] || '行业'} + displayRender={(labels) => labels[labels.length - 1] || (isMobile ? '行业' : '行业筛选')} disabled={industryLoading} - style={{ - width: window.innerWidth < 768 ? '100%' : 120, - minWidth: window.innerWidth < 768 ? 0 : 120, - borderRadius: '8px' - }} - suffixIcon={} + style={{ minWidth: isMobile ? 70 : 80 }} + suffixIcon={null} + className="transparent-cascader" /> - - {/* 重要性筛选 */} - + {/* 事件等级 */} + + {isMobile ? '等级' : '事件等级'} + + } maxTagCount={0} - maxTagPlaceholder={(omittedValues) => `已选 ${omittedValues.length} 项`} + maxTagPlaceholder={(omittedValues) => isMobile ? `${omittedValues.length}项` : `已选 ${omittedValues.length} 项`} + className="bracket-select" > - - - - + {IMPORTANCE_OPTIONS.map(opt => ( + + ))} - + - {/* 排序 */} - + {/* 右侧排序和重置 */} + + {/* 排序 */} } + style={{ minWidth: isMobile ? 55 : 120 }} + className="bracket-select" > - - - - - + {SORT_OPTIONS.map(opt => ( + + ))} - - {/* 重置按钮 */} - + {/* 重置按钮 */} - - - - {/* 激活的筛选标签(如果有的话) */} - {(inputValue || industryValue.length > 0 || importance.length > 0 || tradingTimeRange || sort !== 'new') && ( -
- {inputValue && ( - { - setInputValue(''); - const params = buildFilterParams({ q: '' }); - triggerSearch(params); - }} color="blue"> - 搜索: {inputValue} - - )} - {tradingTimeRange && ( - { - setTradingTimeRange(null); - const params = buildFilterParams({ - start_date: '', - end_date: '', - recent_days: '' - }); - triggerSearch(params); - }} color="green"> - {tradingTimeRange.label} - - )} - {industryValue.length > 0 && industryData && ( - { - setIndustryValue([]); - const params = buildFilterParams({ industry_code: '' }); - triggerSearch(params); - }} color="orange"> - 行业: {(() => { - const findLabel = (code, data) => { - for (const item of data) { - if (code.startsWith(item.value)) { - if (item.value === code) { - return item.label; - } else { - return findLabel(code, item.children); - } - } - } - return null; - }; - const lastLevelCode = industryValue[industryValue.length - 1]; - return findLabel(lastLevelCode, industryData); - })()} - - )} - {importance.length > 0 && ( - { - setImportance([]); - const params = buildFilterParams({ importance: 'all' }); - triggerSearch(params); - }} color="purple"> - 重要性: {importance.map(imp => ({ 'S': '极高', 'A': '高', 'B': '中', 'C': '低' }[imp] || imp)).join(', ')} - - )} - {sort && sort !== 'new' && ( - { - setSort('new'); - const params = buildFilterParams({ sort: 'new' }); - triggerSearch(params); - }} color="cyan"> - 排序: {({ 'hot': '最热', 'importance': '重要性', 'returns_avg': '平均收益', 'returns_week': '周收益' }[sort] || sort)} - - )} -
- )} + +
); }; diff --git a/src/views/Community/components/TradingTimeFilter.js b/src/views/Community/components/TradingTimeFilter.js index 2ebe7d5b..f1ee1749 100644 --- a/src/views/Community/components/TradingTimeFilter.js +++ b/src/views/Community/components/TradingTimeFilter.js @@ -1,20 +1,25 @@ // src/views/Community/components/TradingTimeFilter.js // 交易时段智能筛选组件 import React, { useState, useMemo, useEffect } from 'react'; -import { Space, Button, Tag, Tooltip, DatePicker, Popover } from 'antd'; -import { ClockCircleOutlined, CalendarOutlined } from '@ant-design/icons'; +import { Space, Button, Tag, Tooltip, DatePicker, Popover, Select } from 'antd'; +import { ClockCircleOutlined, CalendarOutlined, FilterOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import locale from 'antd/es/date-picker/locale/zh_CN'; -import { logger } from '../../../utils/logger'; +import { logger } from '@utils/logger'; +import { PROFESSIONAL_COLORS } from '@constants/professionalTheme'; const { RangePicker } = DatePicker; +const { Option } = Select; + /** * 交易时段筛选组件 * @param {string} value - 当前选中的 key(受控) * @param {Function} onChange - 时间范围变化回调 (timeConfig) => void + * @param {boolean} compact - 是否使用紧凑模式(PC 端搜索栏内使用) + * @param {boolean} mobile - 是否使用移动端模式(下拉选择) */ -const TradingTimeFilter = ({ value, onChange }) => { +const TradingTimeFilter = ({ value, onChange, compact = false, mobile = false }) => { const [selectedKey, setSelectedKey] = useState(null); const [customRangeVisible, setCustomRangeVisible] = useState(false); const [customRange, setCustomRange] = useState(null); @@ -266,7 +271,39 @@ const TradingTimeFilter = ({ value, onChange }) => { } }; - // 渲染按钮 + // 渲染紧凑模式按钮(PC 端搜索栏内使用,文字按钮 + | 分隔符) + const renderCompactButton = (config, showDivider = true) => { + const isSelected = selectedKey === config.key; + const fullTooltip = config.timeHint ? `${config.tooltip} · ${config.timeHint}` : config.tooltip; + + return ( + + + handleButtonClick(config)} + style={{ + cursor: 'pointer', + padding: '4px 8px', + borderRadius: '4px', + fontSize: '13px', + fontWeight: isSelected ? 600 : 400, + color: isSelected ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.text.secondary, + background: isSelected ? 'rgba(255, 195, 0, 0.15)' : 'transparent', + transition: 'all 0.2s ease', + whiteSpace: 'nowrap', + }} + > + {config.label} + + + {showDivider && ( + | + )} + + ); + }; + + // 渲染按钮(默认模式) const renderButton = (config) => { const isSelected = selectedKey === config.key; @@ -321,6 +358,98 @@ const TradingTimeFilter = ({ value, onChange }) => {
); + // 移动端模式:下拉选择器 + if (mobile) { + const allButtons = [...timeRangeConfig.dynamic, ...timeRangeConfig.fixed]; + + const handleMobileSelect = (key) => { + if (key === selectedKey) { + // 取消选中 + setSelectedKey(null); + onChange(null); + } else { + const config = allButtons.find(b => b.key === key); + if (config) { + handleButtonClick(config); + } + } + }; + + return ( + + ); + } + + // 紧凑模式:PC 端搜索栏内的样式 + if (compact) { + // 合并所有按钮配置 + const allButtons = [...timeRangeConfig.dynamic, ...timeRangeConfig.fixed]; + + return ( +
+ {allButtons.map((config, index) => + renderCompactButton(config, index < allButtons.length - 1) + )} + {/* 更多时间 */} + | + + + + + 更多 + + + +
+ ); + } + + // 默认模式:移动端/独立使用 return ( {/* 动态按钮(根据时段显示多个) */}