feat: 调整搜索框UI
This commit is contained in:
80
src/views/Community/components/CompactSearchBox.css
Normal file
80
src/views/Community/components/CompactSearchBox.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -4,30 +4,49 @@
|
|||||||
import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
Input, Cascader, Button, Space, Tag, AutoComplete, Select as AntSelect,
|
Input, Cascader, Button, Space, Tag, AutoComplete, Select as AntSelect,
|
||||||
Tooltip
|
Tooltip, Divider, Flex
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
SearchOutlined, CloseCircleOutlined, StockOutlined, FilterOutlined,
|
SearchOutlined, CloseCircleOutlined, StockOutlined, FilterOutlined,
|
||||||
CalendarOutlined, SortAscendingOutlined
|
CalendarOutlined, SortAscendingOutlined, ReloadOutlined, ThunderboltOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
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';
|
||||||
import { stockService } from '../../../services/stockService';
|
import { stockService } from '@services/stockService';
|
||||||
import { logger } from '../../../utils/logger';
|
import { logger } from '@utils/logger';
|
||||||
import TradingTimeFilter from './TradingTimeFilter';
|
import TradingTimeFilter from './TradingTimeFilter';
|
||||||
import { PROFESSIONAL_COLORS } from '../../../constants/professionalTheme';
|
import { PROFESSIONAL_COLORS } from '@constants/professionalTheme';
|
||||||
|
import './CompactSearchBox.css';
|
||||||
|
|
||||||
const { Option } = AntSelect;
|
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 = ({
|
const CompactSearchBox = ({
|
||||||
onSearch,
|
onSearch,
|
||||||
onSearchFocus,
|
onSearchFocus,
|
||||||
filters = {},
|
filters = {},
|
||||||
mode,
|
mode,
|
||||||
pageSize,
|
pageSize,
|
||||||
trackingFunctions = {}
|
trackingFunctions = {},
|
||||||
|
isMobile = false
|
||||||
}) => {
|
}) => {
|
||||||
// 状态
|
// 状态
|
||||||
const [stockOptions, setStockOptions] = useState([]);
|
const [stockOptions, setStockOptions] = useState([]);
|
||||||
@@ -420,19 +439,21 @@ const CompactSearchBox = ({
|
|||||||
dispatch(fetchIndustryData());
|
dispatch(fetchIndustryData());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{ padding: 0, background: 'transparent' }}>
|
||||||
padding: window.innerWidth < 768 ? '12px 16px' : '16px 20px',
|
{/* 第一行:搜索框 + 日期筛选 */}
|
||||||
background: PROFESSIONAL_COLORS.background.card,
|
<Flex
|
||||||
borderRadius: '12px',
|
align="center"
|
||||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3), 0 0 20px rgba(255, 195, 0, 0.1)',
|
gap={isMobile ? 8 : 12}
|
||||||
border: `1px solid ${PROFESSIONAL_COLORS.border.default}`,
|
style={{
|
||||||
backdropFilter: 'blur(10px)'
|
background: 'rgba(255, 255, 255, 0.03)',
|
||||||
}}>
|
border: `1px solid ${PROFESSIONAL_COLORS.border.light}`,
|
||||||
{/* 单行紧凑布局 - 移动端自动换行 */}
|
borderRadius: '24px',
|
||||||
<Space wrap style={{ width: '100%' }} size={window.innerWidth < 768 ? 'small' : 'medium'}>
|
padding: isMobile ? '2px 4px' : '8px 16px',
|
||||||
{/* 搜索框 */}
|
marginBottom: isMobile ? 8 : 12
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 搜索框 - flex: 1 占满剩余空间 */}
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
@@ -440,46 +461,57 @@ const CompactSearchBox = ({
|
|||||||
onSelect={handleStockSelect}
|
onSelect={handleStockSelect}
|
||||||
onFocus={onSearchFocus}
|
onFocus={onSearchFocus}
|
||||||
options={stockOptions}
|
options={stockOptions}
|
||||||
placeholder="搜索股票/话题..."
|
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
handleMainSearch();
|
handleMainSearch();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{ width: window.innerWidth < 768 ? '100%' : 240, minWidth: window.innerWidth < 768 ? 0 : 240 }}
|
style={{ flex: 1, minWidth: isMobile ? 100 : 200 }}
|
||||||
|
className="gold-placeholder"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={<SearchOutlined style={{ color: PROFESSIONAL_COLORS.gold[500] }} />}
|
prefix={<SearchOutlined style={{ color: PROFESSIONAL_COLORS.gold[500] }} />}
|
||||||
|
placeholder="搜索股票/话题..."
|
||||||
style={{
|
style={{
|
||||||
borderRadius: '8px',
|
border: 'none',
|
||||||
border: `1px solid ${PROFESSIONAL_COLORS.border.default}`,
|
background: 'transparent',
|
||||||
boxShadow: `0 2px 8px rgba(255, 195, 0, 0.1)`,
|
color: PROFESSIONAL_COLORS.text.primary,
|
||||||
background: PROFESSIONAL_COLORS.background.secondary,
|
boxShadow: 'none'
|
||||||
color: PROFESSIONAL_COLORS.text.primary
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</AutoComplete>
|
</AutoComplete>
|
||||||
|
|
||||||
{/* 时间筛选 */}
|
{/* 分隔线 - H5 时隐藏 */}
|
||||||
<Tooltip title="时间筛选">
|
{!isMobile && <Divider type="vertical" style={{ height: 24, margin: '0 8px', borderColor: 'rgba(255,255,255,0.15)' }} />}
|
||||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
|
|
||||||
<CalendarOutlined style={{ color: PROFESSIONAL_COLORS.gold[500], fontSize: 12 }} />
|
|
||||||
<TradingTimeFilter
|
|
||||||
value={tradingTimeRange?.key || null}
|
|
||||||
onChange={handleTradingTimeChange}
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* 行业筛选 */}
|
{/* 日期筛选按钮组 */}
|
||||||
<Tooltip title="行业分类">
|
<div style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
|
||||||
|
<CalendarOutlined style={{ color: PROFESSIONAL_COLORS.gold[500], fontSize: 14, marginRight: 8 }} />
|
||||||
|
<TradingTimeFilter
|
||||||
|
value={tradingTimeRange?.key || null}
|
||||||
|
onChange={handleTradingTimeChange}
|
||||||
|
compact={!isMobile}
|
||||||
|
mobile={isMobile}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* 第二行:筛选条件 */}
|
||||||
|
<Flex justify="space-between" align="center">
|
||||||
|
{/* 左侧筛选 */}
|
||||||
|
<Space size={isMobile ? 4 : 8}>
|
||||||
|
{/* 行业筛选 */}
|
||||||
<Cascader
|
<Cascader
|
||||||
value={industryValue}
|
value={industryValue}
|
||||||
onChange={handleIndustryChange}
|
onChange={handleIndustryChange}
|
||||||
onFocus={handleCascaderFocus}
|
onFocus={handleCascaderFocus}
|
||||||
options={industryData || []}
|
options={industryData || []}
|
||||||
placeholder="行业"
|
placeholder={
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
|
<FilterOutlined style={{ fontSize: 12 }} />
|
||||||
|
{isMobile ? '行业' : '行业筛选'}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
changeOnSelect
|
changeOnSelect
|
||||||
showSearch={{
|
showSearch={{
|
||||||
filter: (inputValue, path) =>
|
filter: (inputValue, path) =>
|
||||||
@@ -489,145 +521,65 @@ const CompactSearchBox = ({
|
|||||||
}}
|
}}
|
||||||
allowClear
|
allowClear
|
||||||
expandTrigger="hover"
|
expandTrigger="hover"
|
||||||
displayRender={(labels) => labels[labels.length - 1] || '行业'}
|
displayRender={(labels) => labels[labels.length - 1] || (isMobile ? '行业' : '行业筛选')}
|
||||||
disabled={industryLoading}
|
disabled={industryLoading}
|
||||||
style={{
|
style={{ minWidth: isMobile ? 70 : 80 }}
|
||||||
width: window.innerWidth < 768 ? '100%' : 120,
|
suffixIcon={null}
|
||||||
minWidth: window.innerWidth < 768 ? 0 : 120,
|
className="transparent-cascader"
|
||||||
borderRadius: '8px'
|
|
||||||
}}
|
|
||||||
suffixIcon={<FilterOutlined style={{ fontSize: 14, color: PROFESSIONAL_COLORS.gold[500] }} />}
|
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* 重要性筛选 */}
|
{/* 事件等级 */}
|
||||||
<Tooltip title="事件等级筛选">
|
|
||||||
<AntSelect
|
<AntSelect
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
value={importance}
|
value={importance}
|
||||||
onChange={handleImportanceChange}
|
onChange={handleImportanceChange}
|
||||||
style={{
|
style={{ minWidth: isMobile ? 100 : 120 }}
|
||||||
width: window.innerWidth < 768 ? '100%' : 120,
|
placeholder={
|
||||||
minWidth: window.innerWidth < 768 ? 0 : 120,
|
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
borderRadius: '8px'
|
<ThunderboltOutlined style={{ fontSize: 12 }} />
|
||||||
}}
|
{isMobile ? '等级' : '事件等级'}
|
||||||
placeholder="事件等级"
|
</span>
|
||||||
|
}
|
||||||
maxTagCount={0}
|
maxTagCount={0}
|
||||||
maxTagPlaceholder={(omittedValues) => `已选 ${omittedValues.length} 项`}
|
maxTagPlaceholder={(omittedValues) => isMobile ? `${omittedValues.length}项` : `已选 ${omittedValues.length} 项`}
|
||||||
|
className="bracket-select"
|
||||||
>
|
>
|
||||||
<Option value="S">S级</Option>
|
{IMPORTANCE_OPTIONS.map(opt => (
|
||||||
<Option value="A">A级</Option>
|
<Option key={opt.value} value={opt.value}>{opt.label}</Option>
|
||||||
<Option value="B">B级</Option>
|
))}
|
||||||
<Option value="C">C级</Option>
|
|
||||||
</AntSelect>
|
</AntSelect>
|
||||||
</Tooltip>
|
</Space>
|
||||||
|
|
||||||
{/* 排序 */}
|
{/* 右侧排序和重置 */}
|
||||||
<Tooltip title="排序方式">
|
<Space size={isMobile ? 4 : 8}>
|
||||||
|
{/* 排序 */}
|
||||||
<AntSelect
|
<AntSelect
|
||||||
value={sort}
|
value={sort}
|
||||||
onChange={handleSortChange}
|
onChange={handleSortChange}
|
||||||
style={{
|
style={{ minWidth: isMobile ? 55 : 120 }}
|
||||||
width: window.innerWidth < 768 ? '100%' : 130,
|
className="bracket-select"
|
||||||
minWidth: window.innerWidth < 768 ? 0 : 130,
|
|
||||||
borderRadius: '8px'
|
|
||||||
}}
|
|
||||||
suffixIcon={<SortAscendingOutlined style={{ fontSize: 14, color: PROFESSIONAL_COLORS.gold[500] }} />}
|
|
||||||
>
|
>
|
||||||
<Option value="new">⏰ 最新</Option>
|
{SORT_OPTIONS.map(opt => (
|
||||||
<Option value="hot">🔥 最热</Option>
|
<Option key={opt.value} value={opt.value}>
|
||||||
<Option value="importance">⭐ 重要性</Option>
|
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
<Option value="returns_avg">📊 平均收益</Option>
|
<SortAscendingOutlined style={{ fontSize: 12 }} />
|
||||||
<Option value="returns_week">📈 周收益</Option>
|
{isMobile ? opt.mobileLabel : opt.label}
|
||||||
|
</span>
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
</AntSelect>
|
</AntSelect>
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{/* 重置按钮 */}
|
{/* 重置按钮 */}
|
||||||
<Tooltip title="重置所有筛选">
|
|
||||||
<Button
|
<Button
|
||||||
icon={<CloseCircleOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
danger
|
type="text"
|
||||||
type="primary"
|
style={{ color: PROFESSIONAL_COLORS.text.secondary }}
|
||||||
style={{
|
|
||||||
borderRadius: '8px',
|
|
||||||
boxShadow: '0 2px 8px rgba(255, 77, 79, 0.2)'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
重置
|
{!isMobile && '重置筛选'}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Space>
|
||||||
</Space>
|
</Flex>
|
||||||
|
|
||||||
{/* 激活的筛选标签(如果有的话) */}
|
|
||||||
{(inputValue || industryValue.length > 0 || importance.length > 0 || tradingTimeRange || sort !== 'new') && (
|
|
||||||
<div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
|
||||||
{inputValue && (
|
|
||||||
<Tag closable onClose={() => {
|
|
||||||
setInputValue('');
|
|
||||||
const params = buildFilterParams({ q: '' });
|
|
||||||
triggerSearch(params);
|
|
||||||
}} color="blue">
|
|
||||||
搜索: {inputValue}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
{tradingTimeRange && (
|
|
||||||
<Tag closable onClose={() => {
|
|
||||||
setTradingTimeRange(null);
|
|
||||||
const params = buildFilterParams({
|
|
||||||
start_date: '',
|
|
||||||
end_date: '',
|
|
||||||
recent_days: ''
|
|
||||||
});
|
|
||||||
triggerSearch(params);
|
|
||||||
}} color="green">
|
|
||||||
{tradingTimeRange.label}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
{industryValue.length > 0 && industryData && (
|
|
||||||
<Tag closable onClose={() => {
|
|
||||||
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);
|
|
||||||
})()}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
{importance.length > 0 && (
|
|
||||||
<Tag closable onClose={() => {
|
|
||||||
setImportance([]);
|
|
||||||
const params = buildFilterParams({ importance: 'all' });
|
|
||||||
triggerSearch(params);
|
|
||||||
}} color="purple">
|
|
||||||
重要性: {importance.map(imp => ({ 'S': '极高', 'A': '高', 'B': '中', 'C': '低' }[imp] || imp)).join(', ')}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
{sort && sort !== 'new' && (
|
|
||||||
<Tag closable onClose={() => {
|
|
||||||
setSort('new');
|
|
||||||
const params = buildFilterParams({ sort: 'new' });
|
|
||||||
triggerSearch(params);
|
|
||||||
}} color="cyan">
|
|
||||||
排序: {({ 'hot': '最热', 'importance': '重要性', 'returns_avg': '平均收益', 'returns_week': '周收益' }[sort] || sort)}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
// src/views/Community/components/TradingTimeFilter.js
|
// src/views/Community/components/TradingTimeFilter.js
|
||||||
// 交易时段智能筛选组件
|
// 交易时段智能筛选组件
|
||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { Space, Button, Tag, Tooltip, DatePicker, Popover } from 'antd';
|
import { Space, Button, Tag, Tooltip, DatePicker, Popover, Select } from 'antd';
|
||||||
import { ClockCircleOutlined, CalendarOutlined } from '@ant-design/icons';
|
import { ClockCircleOutlined, CalendarOutlined, FilterOutlined } from '@ant-design/icons';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import locale from 'antd/es/date-picker/locale/zh_CN';
|
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 { RangePicker } = DatePicker;
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 交易时段筛选组件
|
* 交易时段筛选组件
|
||||||
* @param {string} value - 当前选中的 key(受控)
|
* @param {string} value - 当前选中的 key(受控)
|
||||||
* @param {Function} onChange - 时间范围变化回调 (timeConfig) => void
|
* @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 [selectedKey, setSelectedKey] = useState(null);
|
||||||
const [customRangeVisible, setCustomRangeVisible] = useState(false);
|
const [customRangeVisible, setCustomRangeVisible] = useState(false);
|
||||||
const [customRange, setCustomRange] = useState(null);
|
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 (
|
||||||
|
<React.Fragment key={config.key}>
|
||||||
|
<Tooltip title={fullTooltip}>
|
||||||
|
<span
|
||||||
|
onClick={() => 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}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{showDivider && (
|
||||||
|
<span style={{ color: 'rgba(255, 255, 255, 0.2)', margin: '0 2px' }}>|</span>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染按钮(默认模式)
|
||||||
const renderButton = (config) => {
|
const renderButton = (config) => {
|
||||||
const isSelected = selectedKey === config.key;
|
const isSelected = selectedKey === config.key;
|
||||||
|
|
||||||
@@ -321,6 +358,98 @@ const TradingTimeFilter = ({ value, onChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 移动端模式:下拉选择器
|
||||||
|
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 (
|
||||||
|
<Select
|
||||||
|
value={selectedKey}
|
||||||
|
onChange={handleMobileSelect}
|
||||||
|
placeholder={
|
||||||
|
<span style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||||
|
<FilterOutlined style={{ fontSize: 12 }} />
|
||||||
|
筛选
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
allowClear
|
||||||
|
onClear={() => {
|
||||||
|
setSelectedKey(null);
|
||||||
|
onChange(null);
|
||||||
|
}}
|
||||||
|
style={{ minWidth: 80 }}
|
||||||
|
className="transparent-select"
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
>
|
||||||
|
{allButtons.map(config => (
|
||||||
|
<Option key={config.key} value={config.key}>
|
||||||
|
{config.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 紧凑模式:PC 端搜索栏内的样式
|
||||||
|
if (compact) {
|
||||||
|
// 合并所有按钮配置
|
||||||
|
const allButtons = [...timeRangeConfig.dynamic, ...timeRangeConfig.fixed];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'nowrap' }}>
|
||||||
|
{allButtons.map((config, index) =>
|
||||||
|
renderCompactButton(config, index < allButtons.length - 1)
|
||||||
|
)}
|
||||||
|
{/* 更多时间 */}
|
||||||
|
<span style={{ color: 'rgba(255, 255, 255, 0.2)', margin: '0 2px' }}>|</span>
|
||||||
|
<Popover
|
||||||
|
content={customRangeContent}
|
||||||
|
title="选择自定义时间范围"
|
||||||
|
trigger="click"
|
||||||
|
open={customRangeVisible}
|
||||||
|
onOpenChange={setCustomRangeVisible}
|
||||||
|
placement="bottomLeft"
|
||||||
|
>
|
||||||
|
<Tooltip title="自定义时间范围">
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '4px 8px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: selectedKey === 'custom' ? 600 : 400,
|
||||||
|
color: selectedKey === 'custom' ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.text.secondary,
|
||||||
|
background: selectedKey === 'custom' ? 'rgba(255, 195, 0, 0.15)' : 'transparent',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CalendarOutlined style={{ fontSize: 12 }} />
|
||||||
|
更多
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认模式:移动端/独立使用
|
||||||
return (
|
return (
|
||||||
<Space wrap size={[8, 8]} style={{ display: 'flex', alignItems: 'flex-start' }}>
|
<Space wrap size={[8, 8]} style={{ display: 'flex', alignItems: 'flex-start' }}>
|
||||||
{/* 动态按钮(根据时段显示多个) */}
|
{/* 动态按钮(根据时段显示多个) */}
|
||||||
|
|||||||
Reference in New Issue
Block a user